2
votes

Créer une boucle imbriquée pour segmenter les données dans le dataframe

J'essaie de créer une boucle imbriquée pour segmenter les données dans une trame de données en une série de tables plus petites en utilisant la fonction subset ().

Les données sont segmentées géographiquement par état, puis en catégories pour chaque état, qui contiennent ensuite les chiffres de ventes au fil du temps. Le travail a traditionnellement été effectué uniquement à l'aide d'Excel, mais les données elles-mêmes sont assez volumineuses, avec environ 10 à 12 000 points de données, et la structure des données change constamment, de nouvelles catégories étant ajoutées, supprimées ou renommées, d'où la raison pour laquelle je veux pour automatiser le processus dans R plutôt que de reconstruire manuellement les rapports dans Excel.

Le problème est que je n'arrive pas à faire fonctionner correctement la deuxième boucle. Lorsque j'exécute le code, les données sont sous-ensemble dans le premier ensemble de deux tables qui contiennent les observations correctes, mais le deuxième ensemble de tables dans la deuxième boucle contient le nombre correct de tables, mais sans observations. Il y a évidemment quelque chose qui ne va pas dans la deuxième fonction d'assignation que je ne peux pas résoudre.

MODIFIE POUR AJOUTER:

La sortie souhaitée de ceci servira à construire un rapport avec une série de tableaux imprimés. L'idée sous-jacente est que les données initiales se trouvent dans une seule table massive stockée sous forme de fichier csv ou Excel, mais différentes personnes sont intéressées par différentes parties des données, ce qui signifie qu'elles doivent être séparées en divers composants, chacun étant ensuite imprimé , agrégés et résumés de diverses manières. L'idée est donc de prendre le grand ensemble de données, puis de le décomposer en morceaux qui peuvent être travaillés individuellement. Différentes versions du rapport auront différentes structures internes avec différents nombres de catégories, c'est pourquoi je voulais pouvoir créer dynamiquement les tables via une boucle, afin qu'un seul morceau de code puisse gérer différentes structures de données.

Ce n'est probablement pas la manière idéale d'aborder les choses, mais c'est ainsi que certains managers insistent pour travailler.

    library(dplyr)

    # Create trial data

    by_state <- c("state1", "state1", "state1", "state1", "state1",  
   "state1", "state1", "state1", "state1", "state2", "state2", "state2",  
   "state2", "state2", "state2", "state2", "state2", "state2")
    by_category <- c("cat1", "cat1","cat1", "cat2", "cat2", "cat2",  
   "cat3", "cat3", "cat3", "cat1", "cat1","cat1", "cat2", "cat2", "cat2",  
   "cat3", "cat3", "cat3")
    y2001 <- runif(18, 1, 100) %>%
    round(digits = 0)
    y2002 <- runif(18, 1, 100) %>%
      round(digits = 0)
    y2003 <- runif(18, 1, 100) %>%
      round(digits = 0)

    df <- data.frame(by_state, by_category, y2001, y2002, y2003)

    # Create two lists for each loop

    sec1 <- data.frame(unique(df$by_state))
    sec2 <- data.frame(unique(df$by_category))

    # Create loop to segment data 

    for (c in 1:nrow(sec1)) {
      for (d in 1:nrow(sec2)) {
        assign(paste0("table", c),
               subset(df, df$by_state == paste0(sec1[c,])))
        assign(paste0("table", c, d),
               subset(get(paste0("table", c)), paste0("table", c,  
    "$by_category") == paste0(sec2[d,])))
      }
    }

r

3 commentaires

Il existe une fonction pour cela dans la base R, elle s'appelle split . De plus, n'utilisez jamais assign pour stocker quelque chose dans une variable nommée dynamiquement, vous devriez utiliser une liste (nommée) à la place. Vous pouvez utiliser des listes imbriquées pour stocker des structures de données imbriquées.


Quel est votre résultat attendu? Je vais accepter l'idée de ne pas avoir besoin de créer des tables indépendantes


Le cas d'utilisation que vous décrivez (construire un rapport contenant les différentes tables) est en fait ce que j'avais à l'esprit comme contre-exemple où le fractionnement des données est utile. Vous pourriez toujours utiliser group_by , en particulier en conjonction avec tidyr :: nest , mais le fractionnement a certainement du sens dans ce cas.


3 Réponses :


4
votes

Tout d'abord, je ferai simplement remarquer qu'il n'est probablement pas nécessaire de diviser vos données en plusieurs petits data.frames. Vous pouvez probablement faire tout ce que vous essayez de faire simplement en utilisant group_by (état, catégorie) .

Cela étant dit, voici comment diviser vos données par état: en utilisant la fonction split fournie par la base R.

by_state <- list()
by_state_cat <- list()

for (sta in unique(df$state)) {
  for (cat in unique(df$category)) {
    by_state[[sta]] <- filter(df, state == sta)
    by_state_cat[[paste(sta, cat, sep = "_")]] <- 
      filter(by_state[[sta]], category == cat)
  }
}

Créé le 09/09/2019 par le package reprex (v0.3.0)

Il n'est pas nécessaire d'utiliser une boucle mais, si vous le deviez, voici comment vous pourriez améliorer votre code:

  • Ne définissez pas sec1 et sec2 comme data.frames alors qu'ils ne peuvent être que des vecteurs. Vous pouvez effectuer une boucle sur les valeurs directement au lieu des indices.
  • Attribuez les valeurs dans une liste nommée (que vous avez initialisée auparavant)
  • Utilisez des noms de variables significatifs
library("dplyr")
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union

df <- data.frame(
  state = c("state1", "state1", "state1", "state1", "state1",  
            "state1", "state1", "state1", "state1", "state2", "state2", "state2",  
            "state2", "state2", "state2", "state2", "state2", "state2"),
  category = c("cat1", "cat1","cat1", "cat2", "cat2", "cat2",  
               "cat3", "cat3", "cat3", "cat1", "cat1","cat1", "cat2", "cat2", "cat2",  
               "cat3", "cat3", "cat3"),
  y2001 = runif(18, 1, 100) %>%
    round(digits = 0),
  y2002 = runif(18, 1, 100) %>%
    round(digits = 0),
  y2003 = runif(18, 1, 100) %>%
    round(digits = 0)
)

# This creates a named list of sub data.frames
df_by_state <- split(df, df$state)
# 2 named elements
names(df_by_state)
#> [1] "state1" "state2"
# You can access them by indexing using the name
df_by_state$state1
#>    state category y2001 y2002 y2003
#> 1 state1     cat1    18    95    90
#> 2 state1     cat1    69    15    50
#> 3 state1     cat1    90    62    68
#> 4 state1     cat2    81    29    55
#> 5 state1     cat2    94     9    99
#> 6 state1     cat2    42    30    66
#> 7 state1     cat3    79     7    38
#> 8 state1     cat3     6    95    95
#> 9 state1     cat3    95     4    87
# Or the index
df_by_state[[1]]
#>    state category y2001 y2002 y2003
#> 1 state1     cat1    18    95    90
#> 2 state1     cat1    69    15    50
#> 3 state1     cat1    90    62    68
#> 4 state1     cat2    81    29    55
#> 5 state1     cat2    94     9    99
#> 6 state1     cat2    42    30    66
#> 7 state1     cat3    79     7    38
#> 8 state1     cat3     6    95    95
#> 9 state1     cat3    95     4    87

# This splits every element of df_by_state by category
# Creating a list of lists
df_by_state_cat <- purrr::map(df_by_state, ~ split(., .$category))
# You can access your data.frames like so
df_by_state_cat$state2$cat2
#>     state category y2001 y2002 y2003
#> 13 state2     cat2    87    42    95
#> 14 state2     cat2    97    97    29
#> 15 state2     cat2    40    74    47


# Alternatively, you can directly split df by both state and category
# You need to create a combined state_cat variable:
df_by_state_cat2 <- split(df, paste(df$state, df$category, sep = "_"))
# You get an element for each state_cat combination
names(df_by_state_cat2)
#> [1] "state1_cat1" "state1_cat2" "state1_cat3" "state2_cat1" "state2_cat2"
#> [6] "state2_cat3"
# The list is flat and not nested, you can access elements like this:
df_by_state_cat2$state2_cat2
#>     state category y2001 y2002 y2003
#> 13 state2     cat2    87    42    95
#> 14 state2     cat2    97    97    29
#> 15 state2     cat2    40    74    47

Vous verrez qu'il est équivalent au code utilisant split , sauf plus long et polluant l'environnement (puisque sta ​​code > et cat existent toujours après la boucle).


0 commentaires

0
votes

Voici ma solution de liste:

library(dplyr)

# Create trial data

by_state <- c("state1", "state1", "state1", "state1", "state1",  
              "state1", "state1", "state1", "state1", "state2", "state2", "state2",  
              "state2", "state2", "state2", "state2", "state2", "state2")
by_category <- c("cat1", "cat1","cat1", "cat2", "cat2", "cat2",  
                 "cat3", "cat3", "cat3", "cat1", "cat1","cat1", "cat2", "cat2", "cat2",  
                 "cat3", "cat3", "cat3")
y2001 <- runif(18, 1, 100) %>%
        round(digits = 0)
y2002 <- runif(18, 1, 100) %>%
        round(digits = 0)
y2003 <- runif(18, 1, 100) %>%
        round(digits = 0)

df <- data.frame(by_state, by_category, y2001, y2002, y2003)

# Create two lists for each loop

sec1 <- data.frame(unique(df$by_state))
sec2 <- data.frame(unique(df$by_category))

# creating list by state

list_by_state <- list()
for(i in 1:nrow(sec1)){
        name <- paste('table',paste0(sec1[i,]),sep='_')
        tmp <- subset(df, df$by_state == paste0(sec1[i,]))
        list_by_state[[name]] <- tmp
}

# creating list by state and category

list_bystate_category <- list()
for(i in 1:nrow(sec1)){
        for (j in 1:nrow(sec2)){
                name <- paste('table',paste0(sec1[i,]),paste0(sec2[j,]),sep='_')
                tmp <- filter(df, df$by_state == paste0(sec1[i,]), df$by_category == paste0(sec2[j,]))
                list_bystate_category[[name]] <- tmp
        }
}



0 commentaires

2
votes

Un simple changement dans votre deuxième instruction assign verra les résultats souhaités:

   assign(paste0("table", c, d),
           subset(get(paste0("table", c)), get(paste0("table", c))$by_category == paste0(sec2[d,])))
  }

Besoin d'un get () autour de la deuxième référence au tableau ci-dessus, donc vous peut comparer les valeurs.

L'exemple d'Antoine utilisant des listes, cependant, serait l'approche la plus appropriée.


0 commentaires