逐行嵌套所有列

时间:2017-10-22 11:50:19

标签: r tidyr

这是一个可重现的示例,其中我使用辅助列(temp)生成嵌套的data列。

如何在不使用辅助列的情况下获得相同的结果?我尝试使用group_by_all,但它没有用。 (所以,我也不确定我是否理解了group_by_all函数的用途)

library(tidyverse)

df <- structure(list(Var1 = c(0L, 1L, 2L, 3L, 0L, 1L, 2L, 3L, 0L, 1L, 
2L, 3L, 0L, 1L, 2L, 3L, 0L, 1L, 2L, 3L, 0L, 1L, 2L, 3L), Var2 = c(0L, 
0L, 0L, 0L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 0L, 0L, 0L, 0L, 1L, 
1L, 1L, 1L, 2L, 2L, 2L, 2L), Var3 = c(0L, 0L, 0L, 0L, 0L, 0L, 
0L, 0L, 0L, 0L, 0L, 0L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L)), .Names = c("Var1", "Var2", "Var3"), out.attrs = structure(list(
dim = c(4L, 3L, 2L), dimnames = structure(list(Var1 = c("Var1=0", 
"Var1=1", "Var1=2", "Var1=3"), Var2 = c("Var2=0", "Var2=1", 
"Var2=2"), Var3 = c("Var3=0", "Var3=1")), .Names = c("Var1", 
"Var2", "Var3"))), .Names = c("dim", "dimnames")), class = "data.frame", row.names = c(NA, 
-24L))

df$temp <- 1:nrow(df)
df %>% group_by(temp) %>% nest %>% select(-temp) 

7 个答案:

答案 0 :(得分:7)

这是4个解决方案


我设计了一个名为tags的软件包(目前仅适用于github),该软件包的功能grouping_by包裹在dplyr::group_by周围,允许与副词/修饰符/函数运算符的行为进行分组,取消分组也一样。按未命名的表达式分组时,不会保留temp列,并且语法更紧凑,希望与您要查找的内容足够接近:

# devtools::install_github("moodymudskipper/tags")
library(tidyverse)
library(tags)
df %>% grouping_by(vars(row_number()))$nest()
#> # A tibble: 24 x 1
#>    data            
#>    <list>          
#>  1 <tibble [1 x 3]>
#>  2 <tibble [1 x 3]>
#>  3 <tibble [1 x 3]>
#>  4 <tibble [1 x 3]>
#>  5 <tibble [1 x 3]>
#>  6 <tibble [1 x 3]>
#>  7 <tibble [1 x 3]>
#>  8 <tibble [1 x 3]>
#>  9 <tibble [1 x 3]>
#> 10 <tibble [1 x 3]>
#> # ... with 14 more rows

如果我们命名temp变量,则将其保留:

df %>% grouping_by(vars(X =row_number()))$nest()
#> # A tibble: 24 x 2
#>        X data            
#>    <int> <list>          
#>  1     1 <tibble [1 x 3]>
#>  2     2 <tibble [1 x 3]>
#>  3     3 <tibble [1 x 3]>
#>  4     4 <tibble [1 x 3]>
#>  5     5 <tibble [1 x 3]>
#>  6     6 <tibble [1 x 3]>
#>  7     7 <tibble [1 x 3]>
#>  8     8 <tibble [1 x 3]>
#>  9     9 <tibble [1 x 3]>
#> 10    10 <tibble [1 x 3]>
#> # ... with 14 more rows

这是另一种解决方法,完全避免了nest

df %>% as_tibble() %>% split(.,1:nrow(.)) %>% tibble(data =.)
#> # A tibble: 24 x 1
#>    data            
#>    <list>          
#>  1 <tibble [1 x 3]>
#>  2 <tibble [1 x 3]>
#>  3 <tibble [1 x 3]>
#>  4 <tibble [1 x 3]>
#>  5 <tibble [1 x 3]>
#>  6 <tibble [1 x 3]>
#>  7 <tibble [1 x 3]>
#>  8 <tibble [1 x 3]>
#>  9 <tibble [1 x 3]>
#> 10 <tibble [1 x 3]>
#> # ... with 14 more rows

您可能不需要as_tibble()步骤,我用它来获得完全相同的输出,没有它,您将在小标题列表列中获得常规的data.frames。


100%的基本方式:

df2 <- data.frame(data = 1:nrow(df)) # initiate with proper number of rows
df2$data <- split(df, 1:nrow(df))    # assign list column

由于答案集中在效率上,因此效率会更高:

structure(list(data = split(df, rn <- seq_len(nrow(df)))),  
          row.names = rn, class = "data.frame")

创建nest.rowwise_df可以将nest()rowwise()一起使用,并使@ cj-yetman的想法成为可能:

nest.rowwise_df <- function(data, ..., .key = "data") {
  df %>% group_by(`*temp*` = row_number()) %>% nest() %>% select(-`*temp*`) 
}

df %>% rowwise() %>% nest()
#> # A tibble: 24 x 1
#>    data            
#>    <list>          
#>  1 <tibble [1 x 3]>
#>  2 <tibble [1 x 3]>
#>  3 <tibble [1 x 3]>
#>  4 <tibble [1 x 3]>
#>  5 <tibble [1 x 3]>
#>  6 <tibble [1 x 3]>
#>  7 <tibble [1 x 3]>
#>  8 <tibble [1 x 3]>
#>  9 <tibble [1 x 3]>
#> 10 <tibble [1 x 3]>
#> # ... with 14 more rows

或者使用第一个答案的软件包 tags 获得相同的结果:

using_rowwise$nest(df)

答案 1 :(得分:7)

我们可以使用group_split在每一行进行拆分,并在每一行使用nest

library(tidyverse)

df %>%
  group_split(row_number(), keep = FALSE) %>%
  map_df(nest)

# A tibble: 24 x 1
#   data            
#   <list>          
# 1 <tibble [1 × 3]>
# 2 <tibble [1 × 3]>
# 3 <tibble [1 × 3]>
# 4 <tibble [1 × 3]>
# 5 <tibble [1 × 3]>
# 6 <tibble [1 × 3]>
# 7 <tibble [1 × 3]>
# 8 <tibble [1 × 3]>
# 9 <tibble [1 × 3]>
#10 <tibble [1 × 3]>
# … with 14 more rows

keep = FALSE中,我们不包括row_number()的分组列。

现在代替row_number,我们可以使用不同的变体将其按行拆分。

#Option 2
df %>% group_split(1:nrow(df), keep = FALSE) %>% map_df(nest)

#Option 3
df %>% group_split(seq_len(n()), keep = FALSE) %>% map_df(nest)

#Option 4
df %>% group_split(seq_len(nrow(df))) %>% map_df(nest)

答案 2 :(得分:5)

您可以使用purrr::transpose, 这有点直观, 但是当我尝试了解其背后的逻辑时,却使我头昏脑胀:

tibble(data = lapply(transpose(df), as_tibble))

答案 3 :(得分:2)

TL; DR

假设您的真实数据与示例数据相似,嵌套可能不是您数据的正确策略。如果您坚持嵌套并且您的数据集很大,那么使用split进行操作比使用transpose进行处理更为有效。

一个nest用例

我真的不认为nest是这里的正确选择。具有单行数据框的一个列表列的数据框与具有正常行的数据框基本相同,只是行被遮盖了。

link页面上描述的用例几乎可以肯定需要包含一个分组变量(类似于您的temp变量)。例如:对于“钻石”数据集,嵌套color。然后使用mutate + map为每个子数据帧计算模型。然后取消嵌套model

library(tidyverse)
library(broom)

theme_set(theme_minimal())

dia_mods <- diamonds %>% 
    nest(-color) %>% 
    mutate(model = map(data, ~ lm(price ~ carat + clarity, .) %>% augment)) %>% 
    unnest(model)

优点在于,取消嵌套后,模型数据仍与分组变量相关联,这使得该数据更易于在ggplot中使用,等等。如果删除分组变量,则无法区分未嵌套的数据,这将使这样的事情变得不可能:

dia_mods %>% 
    ggplot(aes(x = carat, y = .fitted, color = clarity)) + 
    geom_line() + 
    facet_wrap(~ color) # facet by same grouping variable used to nest

parallel slopes

对各种策略进行基准测试

如果您仍然打算将nest与数据框一起使用,请考虑以下基准测试,我是使用原始数据框运行的:

benchmarks

以下是基准测试方法的说明:

  • base_r = Moody_Mudskipper的基本R解决方案。
  • split_tib = split(as_tibble(df), 1:nrow(df)),创建数据帧列表。我添加此内容是因为,对于您而言,我认为仅创建列表更有意义。
  • split_df =与split_tib相同,但没有as_tibble
  • split_tib_tib = Moody_Mudskipper的“解决方法”,他拆分了一个三角形并将其添加到小标题中。
  • split_tib_df =与split_tib_tib相同,但没有as_tibble
  • 移调 =亚历克西斯的回答很酷。
  • assign_temp =您的原始解决方案。

如您所见,最有效的解决方案包括分割小节或数据帧,最流行的答案也是效率最低的解决方案之一。这是因为转置数据在计算上是昂贵的。对于您的数据框来说,它并不太重要,但是在处理较大的数据集时,我会避免使用它。

当我们使用较大的数据集比较不同的策略时,情况会更加清晰。我使用比您的行多1x,25x,50x,75x,100x和125x的数据集对每种方法进行基准测试:

enter image description here

很明显,使用split的方法效率更高,而使用transpose的计算成本很高。就是说,transpose方法似乎比其他方法损失的速度更大,这是不正确的。实际上,transpose方法始终比最快的方法慢7倍,而与数据集的大小无关。当我们转换x任意y轴时,这一点变得更加清晰:

enter image description here

值得注意的是,尽管使用较小的数据集时,解决方案(使用temp的性能相当慢,但随着数据集大小的增加,其性能将接近最有效解决方案的性能。

答案 4 :(得分:2)

dplyr内,人们可以像这样使用rowwise()do()

df %>% rowwise %>% do( nest(data.frame(.)) ) %>% ungroup

# or with less parentheses
df %>% rowwise %>% do( data.frame(.) %>% nest ) %>% ungroup

# test identical with `purrr::transpose`
identical(
  df %>% rowwise %>% do(nest(data.frame(.))) %>% ungroup
  ,      
  tibble(data = lapply(transpose(df), as_tibble))
)

# [1] TRUE

其中rowwise()将按行对data.frame进行分组,并将每个组(行)显示为一个命名列表。

通常,这些“行组”仅对do()mutate()之类的一些函数内部的语句“可见”,例如:

df %>% rowwise %>% nest  # this nest will apply on the entire tibble

# # A tibble: 1 x 1
#   data             
#   <list>           
# 1 <tibble [24 × 3]>

查看通过管道%>%传递的内容的技巧是将其str()传递给do(),尽管这会引起错误消息,因为data.frame期望其中的函数返回df %>% rowwise %>% str(.) # Classes ‘rowwise_df’, ‘tbl_df’, ‘tbl’ and 'data.frame': 24 obs. of 3 variables: # $ Var1: int 0 1 2 3 0 1 2 3 0 1 ... # $ Var2: int 0 0 0 0 1 1 1 1 2 2 ... # $ Var3: int 0 0 0 0 0 0 0 0 0 0 ... # - attr(*, "out.attrs")=List of 2 # ..$ dim : int 4 3 2 # ..$ dimnames:List of 3 # .. ..$ Var1: chr "Var1=0" "Var1=1" "Var1=2" "Var1=3" # .. ..$ Var2: chr "Var2=0" "Var2=1" "Var2=2" # .. ..$ Var3: chr "Var3=0" "Var3=1" df %>% rowwise %>% do(str(.)) # List of 3 # $ Var1: int 0 # $ Var2: int 0 # $ Var3: int 0 # List of 3 # $ Var1: int 1 # $ Var2: int 0 # $ Var3: int 0 # ... # Error: Results 1, 2, 3, 4, 5, ... must be data frames, not NULL # Call `rlang::last_error()` to see a backtrace

tidyr::nest()

由于data.frame接受了rowwise,而list传递了一个命名列表,我们需要强制data.framedata.frame()使用,例如{{ 1}},得出上面的答案。


如果目的是将dplyr::mutate()应用于新创建的列表列,则可以完全避免使用nest(),只需在变量dplyr::mutate()之后使用rowwise,并使用变量名,例如:

df.raw %>% rowwise %>% mutate(data = tibble(Var1, Var2, Var3) %>% list)

# Source: local data frame [24 x 4]
# Groups: <by row>
# 
# # A tibble: 24 x 4
#     Var1  Var2  Var3 data            
#    <int> <int> <int> <list>          
#  1     0     0     0 <tibble [1 × 3]>
#  2     1     0     0 <tibble [1 × 3]>
#  3     2     0     0 <tibble [1 × 3]>
#  4     3     0     0 <tibble [1 × 3]>
#  5     0     1     0 <tibble [1 × 3]>
#  6     1     1     0 <tibble [1 × 3]>
#  7     2     1     0 <tibble [1 × 3]>
#  8     3     1     0 <tibble [1 × 3]>
#  9     0     2     0 <tibble [1 × 3]>
# 10     1     2     0 <tibble [1 × 3]>
# # … with 14 more rows

# compare the newly generated column `data` with `nest` generated
identical(
  ( 
    df.raw %>% rowwise %>% mutate(data = tibble(Var1, Var2, Var3) %>% list) 
    %>% select(data) %>% ungroup
  )
  ,      
  tibble(data = lapply(transpose(df), as_tibble))
)

# [1] TRUE

例如,结合使用函数式编程样式和dplyr::unnest()时,我们可以生成一个表来演示Legendre's three-square theorem

给一个data.frame,将Var1Var2Var3列分别作为xyz行,并添加列x^2y^2z^2n = x^2 + y^2 + z^2

three.square = function(x, y, z) {
  tibble(
    x^2,
    y^2,
    z^2,
    n = x^2 + y^2 + z^2
  )
}

df %>% rowwise %>%
   mutate(three.square = three.square(Var1, Var2, Var3) %>% list)

# Source: local data frame [24 x 4]
# Groups: <by row>
# 
# # A tibble: 24 x 4
#     Var1  Var2  Var3 three.square    
#    <int> <int> <int> <list>          
#  1     0     0     0 <tibble [1 × 4]>
#  2     1     0     0 <tibble [1 × 4]>
#  3     2     0     0 <tibble [1 × 4]>
#  4     3     0     0 <tibble [1 × 4]>
#  5     0     1     0 <tibble [1 × 4]>
#  6     1     1     0 <tibble [1 × 4]>
#  7     2     1     0 <tibble [1 × 4]>
#  8     3     1     0 <tibble [1 × 4]>
#  9     0     2     0 <tibble [1 × 4]>
# 10     1     2     0 <tibble [1 × 4]>
# # … with 14 more rows

# to "expand" the list-column, use `dplyr::unnest()`
df %>% rowwise %>% 
   mutate(three.square = three.square(Var1, Var2, Var3) %>% list) %>% 
   unnest(three.square)

# # A tibble: 24 x 7
#     Var1  Var2  Var3 `x^2` `y^2` `z^2`     n
#    <int> <int> <int> <dbl> <dbl> <dbl> <dbl>
#  1     0     0     0     0     0     0     0
#  2     1     0     0     1     0     0     1
#  3     2     0     0     4     0     0     4
#  4     3     0     0     9     0     0     9
#  5     0     1     0     0     1     0     1
#  6     1     1     0     1     1     0     2
#  7     2     1     0     4     1     0     5
#  8     3     1     0     9     1     0    10
#  9     0     2     0     0     4     0     4
# 10     1     2     0     1     4     0     5
# # … with 14 more rows

答案 5 :(得分:2)

我们可以按行的顺序split

library(tidyverse)
df %>% 
  split(seq_len(nrow(.))) %>% 
  map_dfr(nest)
# A tibble: 24 x 1
#   data            
#   <list>          
# 1 <tibble [1 × 3]>
# 2 <tibble [1 × 3]>
# 3 <tibble [1 × 3]>
# 4 <tibble [1 × 3]>
# 5 <tibble [1 × 3]>
# 6 <tibble [1 × 3]>
# 7 <tibble [1 × 3]>
# 8 <tibble [1 × 3]>
# 9 <tibble [1 × 3]>
#10 <tibble [1 × 3]>
# … with 14 more rows

或者另一个选择是pmap

df %>% 
   pmap_dfr(., ~ tibble(...) %>% 
                    nest)
# A tibble: 24 x 1
#   data            
#   <list>          
# 1 <tibble [1 × 3]>
# 2 <tibble [1 × 3]>
# 3 <tibble [1 × 3]>
# 4 <tibble [1 × 3]>
# 5 <tibble [1 × 3]>
# 6 <tibble [1 × 3]>
# 7 <tibble [1 × 3]>
# 8 <tibble [1 × 3]>
# 9 <tibble [1 × 3]>
#10 <tibble [1 × 3]>
# … with 14 more rows

答案 6 :(得分:1)

这应该做到。得到相同的结果

library(purrr)
tibble(data = map(split(df,1:nrow(df)),tibble) )

或者使用plurrrlyr更加优雅

library("purrrlyr")
df %>% by_row(tibble,.to="data") %>% select(data)