¿Cómo hago una lista de marcos de datos?

186

¿Cómo hago una lista de marcos de datos y cómo accedo a cada uno de esos marcos de datos de la lista?

Por ejemplo, ¿cómo puedo poner estos marcos de datos en una lista?

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
fuente
13
Esto está en un par de respuestas, pero vale la pena tener un comentario visible aquí también: =no lo uses <-dentro data.frame(). Al usar <-crea y1y y2en su entorno global y su marco de datos no es lo que quiere que sea.
Gregor Thomas
37
Mire ese lío de código sin espacios <-ys dentro de data.frame (). Qué nuevo era yo.
Ben
55
Ya no. Acabo de editar su pregunta para corregir el formato del código. Siéntase libre de revertir si siente nostalgia.
Claus Wilke

Respuestas:

133

Esto no está relacionado con su pregunta, pero desea usarlo =y no <-dentro de la llamada a la función. Si lo usa <-, terminará creando variables y1y y2en cualquier entorno en el que esté trabajando:

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

Esto no tendrá el efecto aparentemente deseado de crear nombres de columna en el marco de datos:

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

El =operador, por otro lado, asociará sus vectores con argumentos para data.frame.

En cuanto a su pregunta, es fácil hacer una lista de marcos de datos:

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)

Accede a los marcos de datos como accedería a cualquier otro elemento de la lista:

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

Las otras respuestas le muestran cómo hacer una lista de data.frames cuando ya tiene un montón de data.frames, p. Ej d1. d2, ... Tener cuadros de datos con nombre secuencial es un problema, y ​​ponerlos en una lista es un buena solución, pero la mejor práctica es evitar tener un montón de data.frames que no están en una lista en primer lugar.

Las otras respuestas brindan muchos detalles sobre cómo asignar marcos de datos para enumerar elementos, acceder a ellos, etc. También cubriremos eso un poco aquí, pero el punto principal es decir que no espere hasta que tenga un montón de data.framespara agregarlos a una lista. Comience con la lista.

El resto de esta respuesta cubrirá algunos casos comunes en los que podría verse tentado a crear variables secuenciales y le mostrará cómo ir directamente a las listas. Si eres nuevo en las listas en R, es posible que también quieras leer ¿Cuál es la diferencia entre [[y [al acceder a los elementos de una lista? .


Listas desde el principio

Nunca crees d1 d2 d3, ..., dnen primer lugar. Crea una lista dcon nelementos.

Leer múltiples archivos en una lista de marcos de datos

Esto se hace con bastante facilidad al leer en archivos. Tal vez tienes archivos data1.csv, data2.csv, ...en un directorio. Su objetivo es una lista de data.frames llamados mydata. Lo primero que necesita es un vector con todos los nombres de archivo. Puede construir este con pasta (por ejemplo, my_files = paste0("data", 1:5, ".csv")), pero es probable que sea más fácil de usar list.filespara agarrar todos los archivos correspondientes: my_files <- list.files(pattern = "\\.csv$"). Puede usar expresiones regulares para hacer coincidir los archivos, leer más sobre expresiones regulares en otras preguntas si necesita ayuda allí. De esta manera, puede tomar todos los archivos CSV, incluso si no siguen un buen esquema de nombres. O puede usar un patrón de expresiones regulares más sofisticado si necesita seleccionar ciertos archivos CSV de entre muchos de ellos.

En este punto, la mayoría de los principiantes de R usarán un forbucle, y no hay nada de malo en eso, funciona bien.

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

Una forma más similar a R es hacerlo con lapply, que es un atajo para lo anterior

my_data <- lapply(my_files, read.csv)

Por supuesto, sustituya otra función de importación de datos read.csvsegún corresponda. readr::read_csvo data.table::freadserá más rápido, o también puede necesitar una función diferente para un tipo de archivo diferente.

De cualquier manera, es útil nombrar los elementos de la lista para que coincidan con los archivos

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 = "")

División de un marco de datos en una lista de marcos de datos

Esto es súper fácil, la función base lo split()hace por usted. Puede dividir por una columna (o columnas) de los datos, o por cualquier otra cosa que desee

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

Esta también es una buena manera de dividir un marco de datos en partes para la validación cruzada. Tal vez quieras dividirte mtcarsen piezas de entrenamiento, prueba y validación.

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

Simulando una lista de marcos de datos

Tal vez estás simulando datos, algo como esto:

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

¿Pero quién hace una sola simulación? ¡Quieres hacer esto 100 veces, 1000 veces, más! Pero no desea 10,000 marcos de datos en su espacio de trabajo. Úselos replicatey póngalos en una lista:

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

En este caso especialmente, también debe considerar si realmente necesita marcos de datos separados, o si un solo marco de datos con una columna de "grupo" funcionaría igual de bien. Usar data.tableo dplyres bastante fácil hacer cosas "por grupo" en un marco de datos.

No puse mis datos en una lista :( Lo haré la próxima vez, pero ¿qué puedo hacer ahora?

Si son una variedad extraña (lo cual es inusual), simplemente puede asignarlos:

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

Si usted tiene marcos de datos con nombre en un patrón, por ejemplo, df1, df2, df3, y usted los quiere en una lista, puede getque si se puede escribir una expresión regular para coincidir con los nombres. Algo como

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.

En general, mgetse usa para obtener varios objetos y devolverlos en una lista con nombre. Su contraparte getse usa para obtener un solo objeto y devolverlo (no en una lista).

Combinando una lista de marcos de datos en un solo marco de datos

Una tarea común es combinar una lista de marcos de datos en un gran marco de datos. Si desea apilarlos uno encima del otro, los usaría rbindpara un par de ellos, pero para una lista de marcos de datos aquí hay tres buenas opciones:

# 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)

(Del mismo modo usando cbindo dplyr::bind_colspara columnas).

Para fusionar (unir) una lista de marcos de datos, puede ver estas respuestas . A menudo, la idea es usar Reducecon merge(o alguna otra función de unión) para unirlos.

¿Por qué poner los datos en una lista?

Poner los datos similares en las listas porque quiere hacer cosas similares a cada trama de datos, y funciones como lapply, sapply do.call, el purrrpaquete , y las antiguas plyr l*plyfunciones hacen que sea fácil para hacer eso. Ejemplos de personas que hacen cosas fácilmente con listas están en todo SO.

Incluso si usa un bucle for bajo, es mucho más fácil recorrer los elementos de una lista que construir nombres de variables pastey acceder a los objetos con ellos get. Más fácil de depurar, también.

Piensa en la escalabilidad . Si realmente necesita solamente tres variables, está bien para su uso d1, d2, d3. Pero si resulta que realmente necesitas 6, eso es mucho más escribir. Y la próxima vez, cuando se necesita 10 o 20, usted se encuentra copiar y pegar líneas de código, tal vez el uso de buscar / reemplazar al cambio d14a d15, y que está pensando que esto no es la forma en la programación debe ser . Si usa una lista, la diferencia entre 3 casos, 30 casos y 300 casos es, como máximo, una línea de código, sin ningún cambio si su número de casos se detecta automáticamente, por ejemplo, cuántos .csvarchivos hay en su directorio.

Puede nombrar los elementos de una lista, en caso de que quiera usar algo más que índices numéricos para acceder a sus marcos de datos (y puede usar ambos, esta no es una opción XOR).

En general, el uso de listas lo llevará a escribir un código más limpio y fácil de leer, lo que dará como resultado menos errores y menos confusión.

Gregor Thomas
fuente
2
¿Qué libro me recomiendan que cubra trabajar con listas?
Abandonado el
15
Recomiendo leer preguntas y respuestas sobre Stack Overflow que están etiquetadas con ambos ry list.
Gregor Thomas
2
@ Gregor Me gustaría agregar que podemos evitar nombrar los elementos de la lista para que coincidan con los archivos simplemente asignando en my_data <- NULLlugar de `my_data <- list () '. :)
Daniel
66
Es posible, pero my_data <- list()deja en claro que está creando una lista, ¡lo cual es bueno! El código claro es algo bueno. No veo ninguna ventaja de usar en su my_data <- NULLlugar.
Gregor Thomas
3
Estoy de acuerdo con lo que dijiste, pero como dije, al hacerlo puedes escapar de la etapa de nombrar los archivos. names(my_data) <- gsub("\\.csv$", "", my_files) ;) <br> Pero respeto sus consejos ya que estoy aprendiendo mucho de ellos como novato y realmente lo aprecio :)
Daniel
21

También puede acceder a columnas y valores específicos en cada elemento de la lista con [y [[. Aquí hay un par de ejemplos. Primero, podemos acceder solo a la primera columna de cada marco de datos en la lista con lapply(ldf, "[", 1), donde 1significa el número de columna.

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

Del mismo modo, podemos acceder al primer valor en la segunda columna con

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

Entonces también podemos acceder a los valores de la columna directamente, como un vector, con [[

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

Si tiene una gran cantidad de marcos de datos con nombre secuencial, puede crear una lista del subconjunto deseado de marcos de datos como este:

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

donde my.list2devuelve una lista que contiene los marcos de datos segundo, tercero y cuarto.

[[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

Sin embargo, tenga en cuenta que los marcos de datos en la lista anterior ya no se nombran. Si desea crear una lista que contenga un subconjunto de marcos de datos y desea preservar sus nombres, puede intentar esto:

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

que devuelve:

> 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
fuente
2
En lugar de lapply(foo, get), simplemente usemget(foo)
Gregor Thomas el
9

Teniendo en cuenta que tiene un "gran" número de marcos de datos con nombres similares (aquí d # donde # es un número entero positivo), la siguiente es una ligera mejora del método de @ mark-miller. Es más conciso y devuelve una lista con nombre de data.frames, donde cada nombre en la lista es el nombre del correspondiente data.frame original.

La clave está usando mgetjunto con ls. Si los marcos de datos d1 y d2 proporcionados en la pregunta eran los únicos objetos con nombres d # en el entorno, entonces

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

que volvería

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

Este método aprovecha el argumento de patrón en ls, que nos permite usar expresiones regulares para hacer un análisis más preciso de los nombres de los objetos en el entorno. Una alternativa a la expresión regular "^d[0-9]+$"es "^d\\d+$".

Como @gregor señala , es mejor en general configurar su proceso de construcción de datos para que los data.frames se coloquen en listas con nombre al principio.

datos

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
fuente
3

Esto puede ser un poco tarde, pero volviendo a su ejemplo, pensé que ampliaría la respuesta solo un poco.

 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))

Entonces haces tu lista fácilmente:

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

Ahora tiene una lista, pero en lugar de acceder a la lista de la manera anterior, como

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

puede usar esta función para obtener y asignar el marco de datos de su elección.

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

Ahora consigue el que quieres.

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

Espero que ese bit extra ayude.

¡Salud!

ML_para_ahora
fuente
2
Sí, lo sé, pero por alguna razón cuando copié y pegué, todo se fue a mayúsculas. :( En cualquier caso, el código en minúsculas funciona.
ML_for_now
44
Tengo curiosidad por qué preferirías GETDF_FROMLIST(mylist, 1)hacerlo mylist[[1]]? Si prefiere la sintaxis de la función, incluso podría hacerlo "[["(mylist, 1)sin definir una función personalizada.
Gregor Thomas el
44
También podría simplificar la definición de su función, todo el cuerpo de la función podría ser return(DF_LIST[[ITEM_LOC]]), sin necesidad de asignar una variable intermedia.
Gregor Thomas el
1

Muy simple ! Aquí está mi sugerencia:

Si desea seleccionar marcos de datos en su espacio de trabajo, intente esto:

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

o

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

todo esto dará el mismo resultado.

Puede cambiar is.data.framepara verificar otros tipos de variables comois.function

Soufiane Chami
fuente
1

Me considero un novato completo, pero creo que tengo una respuesta extremadamente simple a una de las preguntas originales que no se ha mencionado aquí: acceder a los marcos de datos, o partes de ellos.

Comencemos creando la lista con marcos de datos como se indicó anteriormente:

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)

Luego, si desea acceder a un valor específico en uno de los marcos de datos, puede hacerlo utilizando los corchetes dobles secuencialmente. El primer conjunto lo lleva al marco de datos, y el segundo conjunto lo lleva a las coordenadas específicas:

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

[1] 6
Loek van der Kallen
fuente