Divida um grande dataframe em uma lista de frames de dados com base no valor comum na coluna

86

Tenho um data frame com 10 colunas, coletando ações de “usuários”, onde uma das colunas contém um ID (não único, identificando usuário) (coluna 10). o comprimento do quadro de dados é de cerca de 750000 linhas. Estou tentando extrair quadros de dados individuais (obtendo assim uma lista ou vetor de quadros de dados) divididos pela coluna que contém o identificador de "usuário", para isolar as ações de um único ator.

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 em

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

O seguinte funciona muito bem para mim em uma pequena amostra (1000 linhas):

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

e então acessar o elemento que eu quero por caminhos [1] por exemplo.

Ao aplicar no grande quadro de dados original ou mesmo em uma representação de matriz, isso bloqueia minha máquina (4 GB de RAM, MacOSX 10.6, R 2.15) e nunca termina (sei que existe uma versão R mais recente, mas acredito que este não seja o problema principal )

Parece que a divisão é mais eficiente e depois de muito tempo completa, mas não sei (conhecimento inferior de R) como juntar a lista de vetores resultante em um vetor de matrizes.

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

Também considerei usar big.matrixetc, mas sem muito sucesso isso aceleraria o processo.

MartinT
fonte

Respostas:

103

Você pode acessar facilmente cada elemento da lista usando, por exemplo path[[1]]. Você não pode colocar um conjunto de matrizes em um vetor atômico e acessar cada elemento. Uma matriz é um vetor atômico com atributos de dimensão. Eu usaria a estrutura de lista retornada por split, é para isso que foi projetada. Cada elemento da lista pode conter dados de diferentes tipos e tamanhos, portanto, é muito versátil e você pode usar *applyfunções para operar ainda mais em cada elemento da lista. Exemplo abaixo.

#  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

Acesse cada elemento usando o [[operador como este:

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

Ou use uma *applyfunção para fazer outras operações em cada elemento da lista. Por exemplo, para data2calcular a média da coluna, você poderia usar sapply assim:

sapply( out , function(x) mean( x$data2 ) )
#   1    2 
#3.75 6.25 
Simon O'Hanlon
fonte
2
Fiquei me perguntando o desempenho do dlply(df, .(userid))e descobri que é ruim comparado a splitmesmo sem envolver o tempo de execução de require(plyr), obrigado e OP!
Francis
18

A partir da versão 0.8.0, dplyroferece uma função útil chamada 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 não incluir a coluna de agrupamento:

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

Encontrei esta resposta e, na verdade, queria AMBOS os grupos (dados contendo aquele usuário e dados contendo tudo, exceto aquele usuário). Não é necessário para os detalhes deste post, mas pensei em acrescentar caso alguém estivesse pesquisando o mesmo problema que eu no Google.

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']]

Esta é a aparência:

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
fonte