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,])))
}
}
3 Réponses :
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:
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. 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).
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
}
}
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.
Il existe une fonction pour cela dans la base R, elle s'appelle
split. De plus, n'utilisez jamaisassignpour 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 avectidyr :: nest, mais le fractionnement a certainement du sens dans ce cas.