Comment créer une liste de blocs de données?

186

Comment créer une liste de blocs de données et comment accéder à chacun de ces blocs de données à partir de la liste?

Par exemple, comment puis-je mettre ces blocs de données dans une liste?

d1 <- data.frame(y1 = c(1, 2, 3),
                 y2 = c(4, 5, 6))
d2 <- data.frame(y1 = c(3, 2, 1),
                 y2 = c(6, 5, 4))
Ben
la source
13
Ceci est dans quelques réponses, mais cela vaut la peine d'avoir un commentaire visible ici aussi: n'utilisez =pas à l' <-intérieur data.frame(). En utilisant <-vous créez y1et y2dans votre environnement global et votre bloc de données n'est pas ce que vous voulez qu'il soit.
Gregor Thomas
37
Regardez ce désordre de code sans espaces et <-s à l'intérieur de data.frame (). Quel newb j'étais.
Ben
5
Plus maintenant. Je viens de modifier votre question pour corriger la mise en forme du code. N'hésitez pas à revenir si vous vous sentez nostalgique.
Claus Wilke

Réponses:

133

Ce n'est pas lié à votre question, mais vous souhaitez utiliser =et non <-dans l'appel de fonction. Si vous utilisez <-, vous finirez par créer des variables y1et y2dans n'importe quel environnement dans lequel vous travaillez:

d1 <- data.frame(y1 <- c(1, 2, 3), y2 <- c(4, 5, 6))
y1
# [1] 1 2 3
y2
# [1] 4 5 6

Cela n'aura pas l'effet apparemment souhaité de créer des noms de colonnes dans le bloc de données:

d1
#   y1....c.1..2..3. y2....c.4..5..6.
# 1                1                4
# 2                2                5
# 3                3                6

L' =opérateur, quant à lui, associera vos vecteurs aux arguments de data.frame.

Quant à votre question, faire une liste de blocs de données est facile:

d1 <- data.frame(y1 = c(1, 2, 3), y2 = c(4, 5, 6))
d2 <- data.frame(y1 = c(3, 2, 1), y2 = c(6, 5, 4))
my.list <- list(d1, d2)

Vous accédez aux blocs de données comme vous accéderiez à tout autre élément de liste:

my.list[[1]]
#   y1 y2
# 1  1  4
# 2  2  5
# 3  3  6
Peyton
la source
344

Les autres réponses vous montrer comment faire une liste de data.frames lorsque vous avez déjà un tas de data.frames, par exemple d1, d2.... Ayant successivement nommées trames de données est un problème, et de les mettre dans une liste est une bonne solution, mais la meilleure pratique est d' éviter d'avoir un tas de data.frames pas dans une liste en premier lieu.

Les autres réponses donnent beaucoup de détails sur la façon d'attribuer des trames de données à des éléments de liste, d'y accéder, etc. Nous aborderons cela un peu ici aussi, mais le point principal est de dire n'attendez pas d'avoir un tas de data.framespour les ajouter à une liste. Commencez par la liste.

Le reste de cette réponse couvrira certains cas courants où vous pourriez être tenté de créer des variables séquentielles et vous montrera comment accéder directement aux listes. Si vous êtes nouveau dans les listes de R, vous voudrez peut-être également lire Quelle est la différence entre [[et [dans l'accès aux éléments d'une liste? .


Listes depuis le début

Ne créez jamais d1 d2 d3, ... dnen premier lieu. Créez une liste davec des néléments.

Lecture de plusieurs fichiers dans une liste de blocs de données

Cela se fait assez facilement lors de la lecture de fichiers. Peut-être avez-vous des fichiers data1.csv, data2.csv, ...dans un répertoire. Votre objectif est une liste de data.frames appelée mydata. La première chose dont vous avez besoin est un vecteur avec tous les noms de fichiers. Vous pouvez le construire avec coller (par exemple, my_files = paste0("data", 1:5, ".csv")), mais il est probablement plus facile à utiliser list.filespour récupérer tous les fichiers appropriés:my_files <- list.files(pattern = "\\.csv$") . Vous pouvez utiliser des expressions régulières pour faire correspondre les fichiers, en savoir plus sur les expressions régulières dans d'autres questions si vous avez besoin d'aide. De cette façon, vous pouvez récupérer tous les fichiers CSV même s'ils ne suivent pas un bon schéma de dénomination. Ou vous pouvez utiliser un modèle de regex plus sophistiqué si vous avez besoin de sélectionner certains fichiers CSV parmi plusieurs d'entre eux.

À ce stade, la plupart des débutants en R utiliseront une forboucle, et il n'y a rien de mal à cela, cela fonctionne très bien.

my_data <- list()
for (i in seq_along(my_files)) {
    my_data[[i]] <- read.csv(file = my_files[i])
}

Une façon plus R-like de le faire est avec lapply, qui est un raccourci pour ce qui précède

my_data <- lapply(my_files, read.csv)

Bien sûr, remplacez par une autre fonction d'importation de données read.csvle cas échéant. readr::read_csvou data.table::freadsera plus rapide, ou vous pouvez également avoir besoin d'une fonction différente pour un type de fichier différent.

Dans tous les cas, il est pratique de nommer les éléments de la liste pour qu'ils correspondent aux fichiers

names(my_data) <- gsub("\\.csv$", "", my_files)
# or, if you prefer the consistent syntax of stringr
names(my_data) <- stringr::str_replace(my_files, pattern = ".csv", replacement = "")

Fractionnement d'un bloc de données en une liste de blocs de données

C'est très simple, la fonction de base le split()fait pour vous. Vous pouvez diviser par une colonne (ou des colonnes) des données, ou par tout ce que vous voulez

mt_list = split(mtcars, f = mtcars$cyl)
# This gives a list of three data frames, one for each value of cyl

C'est également un bon moyen de diviser une trame de données en morceaux pour une validation croisée. Vous souhaitez peut-être vous diviser mtcarsen éléments de formation, de test et de validation.

groups = sample(c("train", "test", "validate"),
                size = nrow(mtcars), replace = TRUE)
mt_split = split(mtcars, f = groups)
# and mt_split has appropriate names already!

Simulation d'une liste de trames de données

Peut-être que vous simulez des données, quelque chose comme ceci:

my_sim_data = data.frame(x = rnorm(50), y = rnorm(50))

Mais qui fait une seule simulation? Vous voulez faire cela 100 fois, 1000 fois, plus! Mais vous ne voulez pas 10 000 blocs de données dans votre espace de travail. Utilisez-les replicateet mettez-les dans une liste:

sim_list = replicate(n = 10,
                     expr = {data.frame(x = rnorm(50), y = rnorm(50))},
                     simplify = F)

Dans ce cas en particulier, vous devez également vous demander si vous avez vraiment besoin de trames de données séparées ou si une seule trame de données avec une colonne "groupe" fonctionnerait tout aussi bien? En utilisant data.tableou, dplyril est assez facile de faire des choses "par groupe" dans un bloc de données.

Je n'ai pas mis mes données dans une liste: (Je le ferai la prochaine fois, mais que puis-je faire maintenant?

S'il s'agit d'un assortiment étrange (ce qui est inhabituel), vous pouvez simplement les attribuer:

mylist <- list()
mylist[[1]] <- mtcars
mylist[[2]] <- data.frame(a = rnorm(50), b = runif(50))
...

Si vous avez des trames de données nommées dans un modèle, par exemple, df1, df2, df3et vous les voulez dans une liste, vous pouvez getles si vous pouvez écrire une expression régulière pour correspondre aux noms. Quelque chose comme

df_list = mget(ls(pattern = "df[0-9]"))
# this would match any object with "df" followed by a digit in its name
# you can test what objects will be got by just running the
ls(pattern = "df[0-9]")
# part and adjusting the pattern until it gets the right objects.

Généralement, mgetest utilisé pour obtenir plusieurs objets et les renvoyer dans une liste nommée. Son équivalent getest utilisé pour obtenir un seul objet et le renvoyer (pas dans une liste).

Combinaison d'une liste de trames de données en une seule trame de données

Une tâche courante consiste à combiner une liste de trames de données en une seule trame Big Data. Si vous souhaitez les empiler les uns sur les autres, vous les utiliserez rbindpour une paire d'entre eux, mais pour une liste de blocs de données, voici trois bons choix:

# base option - slower but not extra dependencies
big_data = do.call(what = rbind, args = df_list)

# data table and dplyr have nice functions for this that
#  - are much faster
#  - add id columns to identify the source
#  - fill in missing values if some data frames have more columns than others
# see their help pages for details
big_data = data.table::rbindlist(df_list)
big_data = dplyr::bind_rows(df_list)

(De même en utilisant cbindou dplyr::bind_colspour les colonnes.)

Pour fusionner (joindre) une liste de blocs de données, vous pouvez voir ces réponses . Souvent, l'idée est d'utiliser Reduceavecmerge (ou une autre fonction de jonction) pour les réunir.

Pourquoi mettre les données dans une liste?

Mettez des données similaires dans les listes parce que vous voulez faire des choses semblables à chaque trame de données et des fonctions telles que lapply, sapply do.call, l' purrremballage , et les anciennes plyr l*plyfonctions , il est facile de le faire. Des exemples de personnes faisant facilement des choses avec des listes sont partout SO.

Même si vous utilisez une boucle for modeste, il est beaucoup plus facile de boucler sur les éléments d'une liste que de construire des noms de variables avec pasteet d'accéder aux objets avec get. Plus facile à déboguer également.

Pensez à l' évolutivité . Si vous avez vraiment besoin de trois variables, il est bon d'utiliser d1, d2, d3. Mais s'il s'avère que vous avez vraiment besoin de 6, c'est beaucoup plus de frappe. Et la prochaine fois, quand vous avez besoin de 10 ou 20, vous vous surprenez à copier et coller des lignes de code, peut-être en utilisant find / replace pour passer d14à d15, et vous pensez que ce n'est pas comme ça que la programmation devrait être . Si vous utilisez une liste, la différence entre 3 cas, 30 cas et 300 cas est au plus une ligne de code --- aucun changement si votre nombre de cas est automatiquement détecté par, par exemple, combien.csv fichiers dans votre annuaire.

Vous pouvez nommer les éléments d'une liste, au cas où vous voudriez utiliser autre chose que des index numériques pour accéder à vos blocs de données (et vous pouvez utiliser les deux, ce n'est pas un choix XOR).

Dans l'ensemble, l'utilisation de listes vous amènera à écrire un code plus propre et plus facile à lire, ce qui entraînera moins de bogues et moins de confusion.

Gregor Thomas
la source
2
Quel livre recommandez-vous pour travailler avec des listes?
Abandonné le
15
Je recommande de lire les questions et réponses sur Stack Overflow qui sont marquées à la fois ret list.
Gregor Thomas
2
@Gregor Je voudrais ajouter que nous pouvons éviter de nommer les éléments de la liste pour qu'ils correspondent aux fichiers simplement en assignant le my_data <- NULLplutôt que `my_data <- list () '! :)
Daniel
6
C'est possible, mais my_data <- list()il est clair que vous créez une liste, ce qui est bien! Un code clair est une bonne chose. Je ne vois aucun avantage à utiliser à la my_data <- NULLplace.
Gregor Thomas
3
Je suis d'accord, sur ce que vous avez dit, mais comme je l'ai dit, vous pouvez échapper à l'étape de nommer les fichiers. names(my_data) <- gsub("\\.csv$", "", my_files) ;) <br> Mais je respecte vos conseils car j'apprends beaucoup d'eux en tant que débutant et je l'apprécie vraiment :)
Daniel
21

Vous pouvez également accéder à des colonnes et des valeurs spécifiques dans chaque élément de liste avec [et [[. Voici quelques exemples. Tout d'abord, nous ne pouvons accéder qu'à la première colonne de chaque bloc de données de la liste avec lapply(ldf, "[", 1), où 1signifie le numéro de colonne.

ldf <- list(d1 = d1, d2 = d2)  ## create a named list of your data frames
lapply(ldf, "[", 1)
# $d1
#   y1
# 1  1
# 2  2
# 3  3
#
# $d2
#   y1
# 1  3
# 2  2
# 3  1

De même, nous pouvons accéder à la première valeur de la deuxième colonne avec

lapply(ldf, "[", 1, 2)
# $d1
# [1] 4
# 
# $d2
# [1] 6

Ensuite, nous pouvons également accéder aux valeurs des colonnes directement, sous forme de vecteur, avec [[

lapply(ldf, "[[", 1)
# $d1
# [1] 1 2 3
#
# $d2
# [1] 3 2 1
Rich Scriven
la source
13

Si vous avez un grand nombre de blocs de données nommés séquentiellement, vous pouvez créer une liste du sous-ensemble souhaité de blocs de données comme ceci:

d1 <- data.frame(y1=c(1,2,3), y2=c(4,5,6))
d2 <- data.frame(y1=c(3,2,1), y2=c(6,5,4))
d3 <- data.frame(y1=c(6,5,4), y2=c(3,2,1))
d4 <- data.frame(y1=c(9,9,9), y2=c(8,8,8))

my.list <- list(d1, d2, d3, d4)
my.list

my.list2 <- lapply(paste('d', seq(2,4,1), sep=''), get)
my.list2

my.list2renvoie une liste contenant les 2e, 3e et 4e trames de données.

[[1]]
  y1 y2
1  3  6
2  2  5
3  1  4

[[2]]
  y1 y2
1  6  3
2  5  2
3  4  1

[[3]]
  y1 y2
1  9  8
2  9  8
3  9  8

Notez cependant que les blocs de données de la liste ci-dessus ne sont plus nommés. Si vous souhaitez créer une liste contenant un sous-ensemble de blocs de données et que vous souhaitez conserver leurs noms, vous pouvez essayer ceci:

list.function <-  function() { 

     d1 <- data.frame(y1=c(1,2,3), y2=c(4,5,6))
     d2 <- data.frame(y1=c(3,2,1), y2=c(6,5,4))
     d3 <- data.frame(y1=c(6,5,4), y2=c(3,2,1))
     d4 <- data.frame(y1=c(9,9,9), y2=c(8,8,8))

     sapply(paste('d', seq(2,4,1), sep=''), get, environment(), simplify = FALSE) 
} 

my.list3 <- list.function()
my.list3

qui renvoie:

> my.list3
$d2
  y1 y2
1  3  6
2  2  5
3  1  4

$d3
  y1 y2
1  6  3
2  5  2
3  4  1

$d4
  y1 y2
1  9  8
2  9  8
3  9  8

> str(my.list3)
List of 3
 $ d2:'data.frame':     3 obs. of  2 variables:
  ..$ y1: num [1:3] 3 2 1
  ..$ y2: num [1:3] 6 5 4
 $ d3:'data.frame':     3 obs. of  2 variables:
  ..$ y1: num [1:3] 6 5 4
  ..$ y2: num [1:3] 3 2 1
 $ d4:'data.frame':     3 obs. of  2 variables:
  ..$ y1: num [1:3] 9 9 9
  ..$ y2: num [1:3] 8 8 8

> my.list3[[1]]
  y1 y2
1  3  6
2  2  5
3  1  4

> my.list3$d4
  y1 y2
1  9  8
2  9  8
3  9  8
Mark Miller
la source
2
Au lieu de lapply(foo, get), utilisez simplementmget(foo)
Gregor Thomas
9

En prenant pour acquis que vous avez un "grand" nombre de data.frames avec des noms similaires (ici d # où # est un entier positif), ce qui suit est une légère amélioration de la méthode de @ mark-miller. Il est plus laconique et renvoie une liste nommée de data.frames, où chaque nom de la liste est le nom du data.frame d'origine correspondant.

La clé utilise mgetavec ls. Si les blocs de données d1 et d2 fournis dans la question étaient les seuls objets avec des noms d # dans l'environnement, alors

my.list <- mget(ls(pattern="^d[0-9]+"))

qui reviendrait

my.list
$d1
  y1 y2
1  1  4
2  2  5
3  3  6

$d2
  y1 y2
1  3  6
2  2  5
3  1  4

Cette méthode tire parti de l'argument pattern in ls, qui nous permet d'utiliser des expressions régulières pour faire une analyse plus fine des noms des objets dans l'environnement. Une alternative à l'expression régulière "^d[0-9]+$"est"^d\\d+$" .

Comme le souligne @gregor , il est globalement préférable de configurer votre processus de construction de données de sorte que les data.frames soient placés dans des listes nommées au début.

Les données

d1 <- data.frame(y1 = c(1,2,3),y2 = c(4,5,6))
d2 <- data.frame(y1 = c(3,2,1),y2 = c(6,5,4))
lmo
la source
3

C'est peut-être un peu tard, mais pour revenir à votre exemple, j'ai pensé que je prolongerais un peu la réponse.

 D1 <- data.frame(Y1=c(1,2,3), Y2=c(4,5,6))
 D2 <- data.frame(Y1=c(3,2,1), Y2=c(6,5,4))
 D3 <- data.frame(Y1=c(6,5,4), Y2=c(3,2,1))
 D4 <- data.frame(Y1=c(9,9,9), Y2=c(8,8,8))

Ensuite, vous faites votre liste facilement:

mylist <- list(D1,D2,D3,D4)

Vous avez maintenant une liste mais au lieu d'accéder à la liste à l'ancienne, par exemple

mylist[[1]] # to access 'd1'

vous pouvez utiliser cette fonction pour obtenir et attribuer la trame de données de votre choix.

GETDF_FROMLIST <- function(DF_LIST, ITEM_LOC){
   DF_SELECTED <- DF_LIST[[ITEM_LOC]]
   return(DF_SELECTED)
}

Maintenant, prenez celui que vous voulez.

D1 <- GETDF_FROMLIST(mylist, 1)
D2 <- GETDF_FROMLIST(mylist, 2)
D3 <- GETDF_FROMLIST(mylist, 3)
D4 <- GETDF_FROMLIST(mylist, 4)

J'espère que ce petit plus vous aidera.

À votre santé!

ML_for_now
la source
2
Oui, je sais, mais pour une raison quelconque, lorsque j'ai copié et collé, tout est allé en majuscules. :( Dans tous les cas, le code en minuscules fonctionne.
ML_for_now
4
Je suis curieux de savoir pourquoi vous préférez GETDF_FROMLIST(mylist, 1)à mylist[[1]]? Si vous préférez la syntaxe de fonction, vous pouvez même vous "[["(mylist, 1)passer de la définition d'une fonction personnalisée.
Gregor Thomas
4
Vous pouvez également simplifier la définition de votre fonction, le corps entier de la fonction pourrait simplement être return(DF_LIST[[ITEM_LOC]]), pas besoin d'attribuer une variable intermédiaire.
Gregor Thomas le
1

Très simple ! Voici ma suggestion:

Si vous souhaitez sélectionner des dataframes dans votre espace de travail, essayez ceci:

Filter(function(x) is.data.frame(get(x)) , ls())

ou

ls()[sapply(ls(), function(x) is.data.frame(get(x)))]

tout cela donnera le même résultat.

Vous pouvez changer is.data.framepour vérifier d'autres types de variables commeis.function

Soufiane Chami
la source
1

Je me considère comme un débutant complet, mais je pense avoir une réponse extrêmement simple à l'une des sous-questions originales qui n'a pas été énoncée ici: accéder aux blocs de données, ou à des parties de celui-ci.

Commençons par créer la liste avec les cadres de données comme indiqué ci-dessus:

d1 <- data.frame(y1 = c(1, 2, 3), y2 = c(4, 5, 6))

d2 <- data.frame(y1 = c(3, 2, 1), y2 = c(6, 5, 4))

my.list <- list(d1, d2)

Ensuite, si vous souhaitez accéder à une valeur spécifique dans l'un des blocs de données, vous pouvez le faire en utilisant les doubles crochets de manière séquentielle. Le premier ensemble vous amène dans le bloc de données, et le second ensemble vous amène aux coordonnées spécifiques:

my.list[[1]][[3,2]]

[1] 6
Loek van der Kallen
la source