在 tibble 中的嵌套级别之间移动:如何引用存储在嵌套层次结构的上层中的数据

Posted

技术标签:

【中文标题】在 tibble 中的嵌套级别之间移动:如何引用存储在嵌套层次结构的上层中的数据【英文标题】:Traveling between nesting levels in a tibble: how to refer to data stored in upper levels of nesting hierarchy 【发布时间】:2022-01-21 11:30:54 【问题描述】:

我有一个 tibble,其中包含一个 list-column 数据框。在这个最小的例子中,这样的小标题只有 1 行:

library(tibble)

df_meta <- 
  tibble(my_base_number = 5,
         my_data = list(mtcars))

df_meta
#> # A tibble: 1 x 2
#>   my_base_number my_data       
#>            <dbl> <list>        
#> 1              5 <df [32 x 11]>

我想修改 inside my_data 的表并在其中改变一个新列。这是 mtcars 数据,我想改变一个新列,该列记录 mpg 列的日志。

虽然我可以这样做:

library(dplyr)
library(purrr)

df_meta %>%
  mutate(my_data_with_log_col = map(.x = my_data, .f = ~ .x %>% 
                                                         mutate(log_mpg = map(.x = mpg, .f = ~log(.x, base = 5)))
                                    )
         )
#> # A tibble: 1 x 3
#>   my_base_number my_data        my_data_with_log_col
#>            <dbl> <list>         <list>              
#> 1              5 <df [32 x 11]> <df [32 x 12]>     

我真正想要的是,在内部 map() 中调用 log() 会将值传递给来自 df_meta$my_base_numberbase 参数,而不是在我的示例中硬编码的 5

虽然在这个 1 行示例中这很简单:

df_meta %>%
  mutate(my_data_with_log_col = map(.x = my_data, .f = ~ .x %>% 
                                                         mutate(log_mpg = map(.x = mpg, .f = ~log(.x, base = df_meta$my_base_number)))
                                    )
         )

考虑一个更复杂的管道过程,它不再起作用:

tibble(my_data = rep(list(mtcars), 3)) %>%
  add_column(base_number = 1:3) %>%
  mutate(my_data_with_log_col = map(.x = my_data, .f = ~ .x %>% 
                                      mutate(log_mpg = map(.x = mpg, .f = ~log(.x, base =  # <- ???
                                                                                 )))
                                    )
  )

所以我正在寻找的是一个过程,当我引用存储在“元表”每一行中的任何构造中的不同值时,它允许我在嵌套层次结构中上下“移动”。

现在,随着我对map() 的深入研究,为了处理嵌套表,我无法引用存储的数据upper。如果您愿意,我正在寻找类似于 cd ../../.. 在终端导航时的内容。

【问题讨论】:

使用map2 并同时传递base 和tibble。如果你想遍历多层这样的嵌套,你必须通过函数参数从外层传递东西。您可能不想要太多层(例如 ../../..),否则事情会变得既缓慢又难以理解 谢谢,@MichaelDewar。你能用map2() 展示你是如何做到的吗?我试过了,但还是不行。 请看下面我的回答 【参考方案1】:

在 purrr 中有一个名为as_mapper 的函数,您可以在其中使用公式语法来指定 lambda 函数。您可以指定 n 个以 ..1 开头的参数,用于 map..1..2 用于 map2..1 ..... ..3 .... ..n 用于 @987654332 @。这是一个例子:

library(tidyverse)

set.seed(111)
# create some data
df_meta <- tibble(my_base_number = sample(1:5, 5, replace = TRUE), my_data = rerun(5, mtcars))

mutate_log <- as_mapper(~ mutate(..1, log_mpg = log(mpg, ..2)))

df_meta %>%
  mutate(my_data_with_log_col = map2(my_data, my_base_number, mutate_log))
#> # A tibble: 5 × 3
#>   my_base_number my_data        my_data_with_log_col
#>            <int> <list>         <list>              
#> 1              3 <df [32 × 11]> <df [32 × 12]>      
#> 2              4 <df [32 × 11]> <df [32 × 12]>      
#> 3              3 <df [32 × 11]> <df [32 × 12]>      
#> 4              1 <df [32 × 11]> <df [32 × 12]>      
#> 5              3 <df [32 × 11]> <df [32 × 12]>

由reprex package (v2.0.1) 于 2021-12-20 创建

编辑:使用pmapas_mapper

library(tidyverse)

set.seed(111)
# create some data
df_meta <- tibble(
  my_base_number = sample(1:5, 5, replace = TRUE),
  my_col = sample(names(mtcars), 5, replace = TRUE),
  my_data = rerun(5, mtcars)
)

mutate_log <- as_mapper(~ mutate(..1, "log_..2" := log(get(..2), ..3)))

#pmap takes two arguments, a list and a function.
data <- df_meta %>%
  mutate(my_data_with_log_col = pmap(list(my_data, my_col, my_base_number), mutate_log))

#check the results
map(data[[4]], head)
#> [[1]]
#>                    mpg cyl disp  hp drat    wt  qsec vs am gear carb  log_drat
#> Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4 1.2388142
#> Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4 1.2388142
#> Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1 1.2270691
#> Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1 1.0239550
#> Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2 1.0444107
#> Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1 0.9241028
#> 
#> [[2]]
#>                    mpg cyl disp  hp drat    wt  qsec vs am gear carb log_disp
#> Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4 3.660964
#> Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4 3.660964
#> Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1 3.377444
#> Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1 4.005614
#> Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2 4.245927
#> Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1 3.906891
#> 
#> [[3]]
#>                    mpg cyl disp  hp drat    wt  qsec vs am gear carb log_vs
#> Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4   -Inf
#> Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4   -Inf
#> Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1      0
#> Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1      0
#> Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2   -Inf
#> Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1      0
#> 
#> [[4]]
#>                    mpg cyl disp  hp drat    wt  qsec vs am gear carb log_gear
#> Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4      Inf
#> Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4      Inf
#> Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1      Inf
#> Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1      Inf
#> Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2      Inf
#> Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1      Inf
#> 
#> [[5]]
#>                    mpg cyl disp  hp drat    wt  qsec vs am gear carb  log_mpg
#> Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4 2.771244
#> Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4 2.771244
#> Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1 2.846100
#> Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1 2.788419
#> Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2 2.665657
#> Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1 2.635973

由reprex package (v2.0.1) 于 2021 年 12 月 20 日创建

【讨论】:

非常有趣!谢谢。您能否演示如何使用as_mapper()pmap()?例如,如果我们将mutate_log() 更改为mutate_log &lt;- as_mapper(~ mutate(..1, log_some_col = log(..2, ..3))),那么我希望它可以工作:df_meta %&gt;% mutate(my_data_with_log_col = pmap(my_data, mpg, my_base_number, mutate_log))。但事实并非如此。可以请教吗? @Emman 我编辑添加了pmap 案例。【参考方案2】:

这是您要求的方法。但我实际上建议寻找不那么嵌套的方法,例如@TarJae 的回答。

library(tidyverse)

df_meta <- 
    tibble(my_data = rep(list(mtcars), 3),
           my_base_number = 3:5)

add_log <- function(this_data, this_base)
    this_data %>% mutate(log_mpg = log(mpg, this_base))


# check that it works properly:
mtcars %>% add_log(5)

# now apply to each row in df_meta
df_meta %>% 
    mutate(my_data_with_log_col = map2(my_data, my_base_number, add_log))

你会注意到我不需要在内部函数中使用map。但如果我这样做了,我会使用 map_dbl 而不是您使用的 map,因为您实际上需要一个数字,而不是长度为 1 的向量列表。这也表明,也许您一开始就不需要双层地图。

此外,虽然匿名函数是可能的,但我认为对于像这样复杂的东西来说它是非常难以理解的。这就是我在map2之外定义函数的原因。

【讨论】:

【参考方案3】:

这不完全是您所要求的答案。 我想分享它作为一个选项!

您可以使用unnestnest 的组合来旅行:

library(dplyr)
library(tidyr)

df_meta %>% 
  unnest(cols = c(my_data)) %>% 
  mutate(log_mpg = log(mpg, my_base_number)) %>% 
  nest(my_data=mpg:log_mpg)

变异后的输出:

  my_base_number   mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb log_mpg
            <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>   <dbl>
 1              5  21       6  160    110  3.9   2.62  16.5     0     1     4     4    1.89
 2              5  21       6  160    110  3.9   2.88  17.0     0     1     4     4    1.89
 3              5  22.8     4  108     93  3.85  2.32  18.6     1     1     4     1    1.94
 4              5  21.4     6  258    110  3.08  3.22  19.4     1     0     3     1    1.90
 5              5  18.7     8  360    175  3.15  3.44  17.0     0     0     3     2    1.82
 6              5  18.1     6  225    105  2.76  3.46  20.2     1     0     3     1    1.80
 7              5  14.3     8  360    245  3.21  3.57  15.8     0     0     3     4    1.65
 8              5  24.4     4  147.    62  3.69  3.19  20       1     0     4     2    1.98
 9              5  22.8     4  141.    95  3.92  3.15  22.9     1     0     4     2    1.94
10              5  19.2     6  168.   123  3.92  3.44  18.3     1     0     4     4    1.84

nest之后的最终输出:

  my_base_number my_data           
           <dbl> <list>            
1              5 <tibble [32 × 12]>

【讨论】:

以上是关于在 tibble 中的嵌套级别之间移动:如何引用存储在嵌套层次结构的上层中的数据的主要内容,如果未能解决你的问题,请参考以下文章

按嵌套 tibble 中作为字符串向量给出的变量对 tibble 进行分组

如何在弹性搜索的过滤器聚合中引用多个嵌套级别?

数据框R中的未嵌套数据框

聚合子查询的 FROM 子句中的项目必须引用更高级别 FROM 子句的嵌套表

如何将嵌套 div 向上移动两个 div 级别以匹配父级?

无法使用 Pig 中的 Elephant Bird 访问带有包和元组的嵌套 JSON