Podziel dużą ramkę danych na listę ramek danych na podstawie wspólnej wartości w kolumnie

86

Mam ramkę danych z 10 kolumnami, zbierającą akcje „użytkowników”, gdzie jedna z kolumn zawiera identyfikator (nie jest unikalny, identyfikujący użytkownika) (kolumna 10). długość ramki danych wynosi około 750000 wierszy. Próbuję wyodrębnić pojedyncze ramki danych (więc otrzymuję listę lub wektor ramek danych) podzielone według kolumny zawierającej identyfikator „użytkownika”, aby wyodrębnić działania pojedynczego aktora.

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

w wyniku

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

Poniższe działa bardzo dobrze dla mnie na małej próbce (1000 wierszy):

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

a następnie uzyskiwanie dostępu do elementu, który chcę, na przykład za pomocą ścieżek [1].

Podczas nakładania na oryginalną dużą ramkę danych lub nawet reprezentację macierzy, to dławi moją maszynę (4 GB RAM, MacOSX 10.6, R 2.15) i nigdy się nie kończy (wiem, że istnieje nowsza wersja R, ale uważam, że to nie jest główny problem ).

Wydaje się, że split jest bardziej wydajny i po długim czasie się kończy, ale nie wiem (gorsza znajomość R), jak podzielić wynikową listę wektorów na wektor macierzy.

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

Rozważałem również użycie big.matrixitp., Ale bez większego sukcesu przyspieszyłoby to proces.

MartinT
źródło

Odpowiedzi:

103

Możesz równie łatwo uzyskać dostęp do każdego elementu na liście, używając np path[[1]]. Nie możesz umieścić zestawu macierzy w atomowym wektorze i uzyskać dostęp do każdego elementu. Macierz to atomowy wektor z atrybutami wymiarów. Użyłbym struktury listy zwróconej przez split, do tego została zaprojektowana. Każdy element listy może zawierać dane różnych typów i rozmiarów, dzięki czemu jest bardzo wszechstronny i można używać *applyfunkcji do dalszej operacji na każdym elemencie listy. Przykład poniżej.

#  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

Uzyskaj dostęp do każdego elementu za pomocą [[operatora w ten sposób:

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

Lub użyj *applyfunkcji, aby wykonać dalsze operacje na każdym elemencie listy. Na przykład, aby wziąć średnią z data2kolumny, możesz użyć sapply w następujący sposób:

sapply( out , function(x) mean( x$data2 ) )
#   1    2 
#3.75 6.25 
Simon O'Hanlon
źródło
2
Zastanawiałem się nad wydajnością dlply(df, .(userid))i stwierdziłem, że jest zła w porównaniu do splitnawet bez angażowania czasu wykonywania require(plyr), dziękuję i OP!
Francis
18

Od wersji 0.8.0 dplyroferuje przydatną funkcję o nazwie 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

Aby nie uwzględniać kolumny grupującej:

df %>%
 group_split(g, keep = FALSE)
tmfmnk
źródło
9

Natknąłem się na tę odpowiedź i tak naprawdę chciałem OBIE grupy (dane zawierające tego jednego użytkownika i dane zawierające wszystko oprócz tego jednego użytkownika). Nie jest to konieczne ze względu na specyfikę tego posta, ale pomyślałem, że dodam na wypadek, gdyby ktoś szukał w Google tego samego problemu co ja.

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

Oto jak to wygląda:

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
źródło