Divida un marco de datos grande en una lista de marcos de datos según el valor común en la columna

86

Tengo un marco de datos con 10 columnas, que recopilan acciones de "usuarios", donde una de las columnas contiene un ID (no único, que identifica al usuario) (columna 10). la longitud del marco de datos es de aproximadamente 750000 filas. Estoy tratando de extraer marcos de datos individuales (obteniendo una lista o vector de marcos de datos) divididos por la columna que contiene el identificador de "usuario", para aislar las acciones de un solo actor.

ID | Data1 | Data2 | ... | UserID
1  | aaa   | bbb   | ... | u_001
2  | aab   | bb2   | ... | u_001
3  | aac   | bb3   | ... | u_001
4  | aad   | bb4   | ... | u_002

resultando en

list(
ID | Data1 | Data2 | ... | UserID
1  | aaa   | bbb   | ... | u_001
2  | aab   | bb2   | ... | u_001
3  | aac   | bb3   | ... | u_001
,
4  | aad   | bb4   | ... | u_002
...)

Lo siguiente funciona muy bien para mí en una pequeña muestra (1000 filas):

paths = by(smallsampleMat, smallsampleMat[,"userID"], function(x) x)

y luego acceder al elemento que quiero por rutas [1] por ejemplo.

Cuando se aplica en el marco de datos grande original o incluso en una representación matricial, esto ahoga mi máquina (4GB RAM, MacOSX 10.6, R 2.15) y nunca se completa (sé que existe una versión R más nueva, pero creo que este no es el problema principal ).

Parece que la división es más eficaz y después de mucho tiempo se completa, pero no sé (conocimiento inferior de R) cómo dividir la lista resultante de vectores en un vector de matrices.

path = split(smallsampleMat, smallsampleMat[,10]) 

También he considerado usar big.matrixetc., pero sin mucho éxito, eso aceleraría el proceso.

MartinT
fuente

Respuestas:

103

Puede acceder fácilmente a cada elemento de la lista utilizando, por ejemplo path[[1]]. No se puede poner un conjunto de matrices en un vector atómico y acceder a cada elemento. Una matriz es un vector atómico con atributos de dimensión. Usaría la estructura de lista devuelta por split, es para lo que fue diseñada. Cada elemento de la lista puede contener datos de diferentes tipos y tamaños, por lo que es muy versátil y puede usar *applyfunciones para seguir operando en cada elemento de la lista. Ejemplo a continuación.

#  For reproducibile data
set.seed(1)

#  Make some data
userid <- rep(1:2,times=4)
data1 <- replicate(8 , paste( sample(letters , 3 ) , collapse = "" ) )
data2 <- sample(10,8)
df <- data.frame( userid , data1 , data2 )

#  Split on userid
out <- split( df , f = df$userid )
#$`1`
#  userid data1 data2
#1      1   gjn     3
#3      1   yqp     1
#5      1   rjs     6
#7      1   jtw     5

#$`2`
#  userid data1 data2
#2      2   xfv     4
#4      2   bfe    10
#6      2   mrx     2
#8      2   fqd     9

Acceda a cada elemento usando el [[operador así:

out[[1]]
#  userid data1 data2
#1      1   gjn     3
#3      1   yqp     1
#5      1   rjs     6
#7      1   jtw     5

O use una *applyfunción para realizar más operaciones en cada elemento de la lista. Por ejemplo, para tomar la media de la data2columna, podría usar sapply así:

sapply( out , function(x) mean( x$data2 ) )
#   1    2 
#3.75 6.25 
Simon O'Hanlon
fuente
2
Me preguntaba el rendimiento dlply(df, .(userid))y descubrí que es malo en comparación con splitincluso sin involucrar el tiempo de ejecución de require(plyr), ¡gracias y OP!
Francis
18

Desde la versión 0.8.0, dplyrofrece una práctica función llamada group_split():

# On sample data from @Aus_10
df %>%
  group_split(g)

[[1]]
# A tibble: 25 x 3
   ran_data1 ran_data2 g    
       <dbl>     <dbl> <fct>
 1     2.04      0.627 A    
 2     0.530    -0.703 A    
 3    -0.475     0.541 A    
 4     1.20     -0.565 A    
 5    -0.380    -0.126 A    
 6     1.25     -1.69  A    
 7    -0.153    -1.02  A    
 8     1.52     -0.520 A    
 9     0.905    -0.976 A    
10     0.517    -0.535 A    
# … with 15 more rows

[[2]]
# A tibble: 25 x 3
   ran_data1 ran_data2 g    
       <dbl>     <dbl> <fct>
 1     1.61      0.858 B    
 2     1.05     -1.25  B    
 3    -0.440    -0.506 B    
 4    -1.17      1.81  B    
 5     1.47     -1.60  B    
 6    -0.682    -0.726 B    
 7    -2.21      0.282 B    
 8    -0.499     0.591 B    
 9     0.711    -1.21  B    
10     0.705     0.960 B    
# … with 15 more rows

Para no incluir la columna de agrupación:

df %>%
 group_split(g, keep = FALSE)
tmfmnk
fuente
9

Me encontré con esta respuesta y en realidad quería AMBOS grupos (datos que contienen ese usuario y datos que contienen todo menos ese usuario). No es necesario para los detalles de esta publicación, pero pensé que lo agregaría en caso de que alguien estuviera buscando en Google el mismo problema que yo.

df <- data.frame(
     ran_data1=rnorm(125),
     ran_data2=rnorm(125),
     g=rep(factor(LETTERS[1:5]), 25)
 )

test_x = split(df,df$g)[['A']]
test_y = split(df,df$g!='A')[['TRUE']]

Así es como se ve:

head(test_x)
            x          y g
1   1.1362198  1.2969541 A
6   0.5510307 -0.2512449 A
11  0.0321679  0.2358821 A
16  0.4734277 -1.2889081 A
21 -1.2686151  0.2524744 A

> head(test_y)
            x          y g
2 -2.23477293  1.1514810 B
3 -0.46958938 -1.7434205 C
4  0.07365603  0.1111419 D
5 -1.08758355  0.4727281 E
7  0.28448637 -1.5124336 B
8  1.24117504  0.4928257 C
Aus_10
fuente