Подтвердить что ты не робот

Эмулировать split() с dplyr group_by: вернуть список кадров данных

У меня есть большой набор данных, который зажимает split() в R. Я могу использовать dplyr group_by (что является предпочтительным способом в любом случае), но я не могу сохранить полученный grouped_df как список кадров данных, формат, необходимый для моих последовательных этапов обработки (мне нужно принуждать к SpatialDataFrames и тому подобное).

рассмотрим образец данных:

df = as.data.frame(cbind(c("a","a","b","b","c"),c(1,2,3,4,5), c(2,3,4,2,2)))
listDf = split(df,df$V1)

возвращает

$a
   V1 V2 V3
 1  a  1  2
 2  a  2  3

$b
   V1 V2 V3
 3  b  3  4
 4  b  4  2

$c
   V1 V2 V3
 5  c  5  2

Я хотел бы подражать этому с помощью group_by (что-то вроде group_by(df,V1)), но это возвращает один, grouped_df. Я знаю, что do должен мне помочь, но я не уверен в использовании (см. Также для обсуждения.)

Обратите внимание, что разделение имен каждого списка по имени фактора, который использовался для создания этой группы, - это желаемая функция (в конечном счете, бонусная награда за способ извлечения этих имен из списка dfs).

4b9b3361

Ответ 1

Сравнивая базовые, plyr и dplyr решения, по-прежнему кажется, что база намного быстрее!

library(plyr)
library(dplyr)   

df <- data_frame(Group1=rep(LETTERS, each=1000),
             Group2=rep(rep(1:10, each=100),26), 
             Value=rnorm(26*1000))

microbenchmark(Base=df %>%
             split(list(.$Group2, .$Group1)),
           dplyr=df %>% 
             group_by(Group1, Group2) %>% 
             do(data = (.)) %>% 
             select(data) %>% 
             lapply(function(x) {(x)}) %>% .[[1]],
           plyr=dlply(df, c("Group1", "Group2"), as.tbl),
           times=50) 

дает:

Unit: milliseconds
  expr      min        lq      mean    median        uq       max neval
  Base 12.82725  13.38087  16.21106  14.58810  17.14028  41.67266    50
  dplyr 25.59038 26.66425  29.40503  27.37226  28.85828  77.16062   50
  plyr 99.52911  102.76313 110.18234 106.82786 112.69298 140.97568    50

Ответ 2

Чтобы "привязать" к dplyr, вы можете также использовать plyr вместо split:

library(plyr)

dlply(df, "V1", identity)
#$a
#  V1 V2 V3
#1  a  1  2
#2  a  2  3

#$b
#  V1 V2 V3
#1  b  3  4
#2  b  4  2

#$c
#  V1 V2 V3
#1  c  5  2

Ответ 3

Вы можете получить список фреймов данных из group_by с помощью do, пока вы назовете новый столбец, в котором будут храниться кадры данных, а затем соедините этот столбец с lapply.

listDf = df %>% group_by(V1) %>% do(vals=data.frame(.)) %>% select(vals) %>% lapply(function(x) {(x)})
listDf[[1]]
#[[1]]
#  V1 V2 V3
#1  a  1  2
#2  a  2  3

#[[2]]
#  V1 V2 V3
#1  b  3  4
#2  b  4  2

#[[3]]
#  V1 V2 V3
#1  c  5  2

Ответ 4

group_split в dplyr 0.8:

Версия 0.8 dplyr реализовала group_split: https://dplyr.tidyverse.org/reference/group_split.html

Он разбивает информационный кадр по группам, возвращает список информационных кадров. Каждый из этих фреймов данных является подмножеством исходных фреймов данных, определенных категориями переменной разделения.

Например. Разделите iris набора данных по переменной Species и вычислите итоги каждого поднабора данных:

> iris %>% 
+     group_split(Species) %>% 
+     map(summary)
[[1]]
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.300   Min.   :2.300   Min.   :1.000   Min.   :0.100   setosa    :50  
 1st Qu.:4.800   1st Qu.:3.200   1st Qu.:1.400   1st Qu.:0.200   versicolor: 0  
 Median :5.000   Median :3.400   Median :1.500   Median :0.200   virginica : 0  
 Mean   :5.006   Mean   :3.428   Mean   :1.462   Mean   :0.246                  
 3rd Qu.:5.200   3rd Qu.:3.675   3rd Qu.:1.575   3rd Qu.:0.300                  
 Max.   :5.800   Max.   :4.400   Max.   :1.900   Max.   :0.600                  

[[2]]
  Sepal.Length    Sepal.Width     Petal.Length   Petal.Width          Species  
 Min.   :4.900   Min.   :2.000   Min.   :3.00   Min.   :1.000   setosa    : 0  
 1st Qu.:5.600   1st Qu.:2.525   1st Qu.:4.00   1st Qu.:1.200   versicolor:50  
 Median :5.900   Median :2.800   Median :4.35   Median :1.300   virginica : 0  
 Mean   :5.936   Mean   :2.770   Mean   :4.26   Mean   :1.326                  
 3rd Qu.:6.300   3rd Qu.:3.000   3rd Qu.:4.60   3rd Qu.:1.500                  
 Max.   :7.000   Max.   :3.400   Max.   :5.10   Max.   :1.800                  

[[3]]
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.900   Min.   :2.200   Min.   :4.500   Min.   :1.400   setosa    : 0  
 1st Qu.:6.225   1st Qu.:2.800   1st Qu.:5.100   1st Qu.:1.800   versicolor: 0  
 Median :6.500   Median :3.000   Median :5.550   Median :2.000   virginica :50  
 Mean   :6.588   Mean   :2.974   Mean   :5.552   Mean   :2.026                  
 3rd Qu.:6.900   3rd Qu.:3.175   3rd Qu.:5.875   3rd Qu.:2.300                  
 Max.   :7.900   Max.   :3.800   Max.   :6.900   Max.   :2.500     

Это также очень полезно для отладки расчетов на вложенных фреймах данных, поскольку это быстрый способ "увидеть", что происходит "внутри" вычислений на вложенных фреймах данных.

Ответ 5

Начиная с dplyr 0.8 вы можете использовать group_split

library(dplyr)
df = as.data.frame(cbind(c("a","a","b","b","c"),c(1,2,3,4,5), c(2,3,4,2,2)))
df %>% group_by(V1) %>% group_split()
#> [[1]]
#> # A tibble: 2 x 3
#>   V1    V2    V3   
#>   <fct> <fct> <fct>
#> 1 a     1     2    
#> 2 a     2     3    
#> 
#> [[2]]
#> # A tibble: 2 x 3
#>   V1    V2    V3   
#>   <fct> <fct> <fct>
#> 1 b     3     4    
#> 2 b     4     2    
#> 
#> [[3]]
#> # A tibble: 1 x 3
#>   V1    V2    V3   
#>   <fct> <fct> <fct>
#> 1 c     5     2

Ответ 6

Поскольку dplyr 0.5.0.9000, самое короткое решение, которое использует group_by(), вероятно, следует do с помощью pull:

df %>% group_by(V1) %>% do(data=(.)) %>% pull(data)

Обратите внимание, что, в отличие от split, это не определяет результирующие элементы списка. Если это необходимо, то вы, вероятно, захотите что-то вроде

df %>% group_by(V1) %>% do(data = (.)) %>% with( set_names(data, V1) )

Чтобы немного переработать, я согласен с тем, что люди говорят, что split() - лучший вариант. Лично мне всегда было досадно, что мне нужно дважды набирать имя кадра данных (например, split( potentiallylongname, potentiallylongname$V1 )), но проблема легко обойти стороной с каналом:

df %>% split( .$V1 )