dplyr::mutate 添加多个值

dplyr::mutate to add multiple values

dplyr Github repo 上已经有几个关于这个的问题,至少有一个相关的 SO 问题,但其中 none 完全涵盖了我的问题——我认为。

这是我的用例:我想计算精确的二项式置信区间

dd <- data.frame(x=c(3,4),n=c(10,11))
get_binCI <- function(x,n) {
    rbind(setNames(c(binom.test(x,n)$conf.int),c("lwr","upr")))
}
with(dd[1,],get_binCI(x,n))
##             lwr       upr
## [1,] 0.06673951 0.6524529

我可以使用 do() 完成此操作,但我想知道是否有更具表现力的方法来执行此操作(感觉 mutate() 可以 .n 参数 as is being discussed for summarise() ...)

library("dplyr")
dd %>% group_by(x,n) %>%
    do(cbind(.,get_binCI(.$x,.$n)))

## Source: local data frame [2 x 4]
## Groups: x, n
## 
##   x  n        lwr       upr
## 1 3 10 0.06673951 0.6524529
## 2 4 11 0.10926344 0.6920953

这里有一个使用 data.table 包的快速解决方案

首先,对函数做一点改动

get_binCI <- function(x,n) as.list(setNames(binom.test(x,n)$conf.int, c("lwr", "upr")))

那么,干脆

library(data.table)
setDT(dd)[, get_binCI(x, n), by = .(x, n)]
#    x  n        lwr       upr
# 1: 3 10 0.06673951 0.6524529
# 2: 4 11 0.10926344 0.6920953

这使用 "standard" dplyr 工作流程,但正如@BenBolker 在评论中指出的那样,它需要调用 get_binCI 两次:

dd %>% group_by(x,n) %>%
  mutate(lwr=get_binCI(x,n)[1],
         upr=get_binCI(x,n)[2])

  x  n        lwr       upr
1 3 10 0.06673951 0.6524529
2 4 11 0.10926344 0.6920953

又一个变体,虽然我认为我们都在这里分裂头发。

> dd <- data.frame(x=c(3,4),n=c(10,11))
> get_binCI <- function(x,n) {
+   as_data_frame(setNames(as.list(binom.test(x,n)$conf.int),c("lwr","upr")))
+ }
> 
> dd %>% 
+   group_by(x,n) %>%
+   do(get_binCI(.$x,.$n))
Source: local data frame [2 x 4]
Groups: x, n

  x  n        lwr       upr
1 3 10 0.06673951 0.6524529
2 4 11 0.10926344 0.6920953

就个人而言,如果我们只考虑可读性,我觉得这个更可取:

foo  <- function(x,n){
    bi <- binom.test(x,n)$conf.int
    data_frame(lwr = bi[1],
               upr = bi[2])
}

dd %>% 
    group_by(x,n) %>%
    do(foo(.$x,.$n))

...但现在我们真的 分裂了。

以下是 rowwisenesting 的一些可能性。

library("dplyr")
library("tidyr")

具有重复 x/n 组合的数据框,仅供娱乐

dd <- data.frame(x=c(3, 4, 3), n=c(10, 11, 10))

CI 函数的一个版本 returns 一个数据框,比如@Joran 的

get_binCI_df <- function(x,n) {
  binom.test(x, n)$conf.int %>% 
    setNames(c("lwr", "upr")) %>% 
    as.list() %>% as.data.frame()
}

像以前一样按 xn 分组,删除重复项。

dd %>% group_by(x,n) %>% do(get_binCI_df(.$x,.$n))
# # A tibble: 2 x 4
# # Groups:   x, n [2]
#       x     n       lwr       upr
#   <dbl> <dbl>     <dbl>     <dbl>
# 1     3    10 0.1181172 0.8818828
# 2     4    11 0.1092634 0.6920953

使用 rowwise 保留所有行,但删除 xn 除非您使用 cbind(. 将它们放回去(就像 Ben 在他的 OP 中所做的那样)。

dd %>% rowwise() %>% do(cbind(., get_binCI_df(.$x,.$n)))
# Source: local data frame [3 x 4]
# Groups: <by row>
#   
# # A tibble: 3 x 4
#       x     n        lwr       upr
# * <dbl> <dbl>      <dbl>     <dbl>
# 1     3    10 0.06673951 0.6524529
# 2     4    11 0.10926344 0.6920953
# 3     3    10 0.06673951 0.6524529

感觉嵌套可以更干净地工作,但这是我能得到的最好的了。使用 mutate 意味着我可以直接使用 xn 而不是 .$x.$n,但是 mutate 需要一个值,因此需要将其包装在list.

dd %>% rowwise() %>% mutate(ci=list(get_binCI_df(x, n))) %>% unnest()
# # A tibble: 3 x 4
#       x     n        lwr       upr
#   <dbl> <dbl>      <dbl>     <dbl>
# 1     3    10 0.06673951 0.6524529
# 2     4    11 0.10926344 0.6920953
# 3     3    10 0.06673951 0.6524529

最后,看起来像是 dplyr 的未解决问题(截至 2017 年 10 月 5 日);见 https://github.com/tidyverse/dplyr/issues/2326;如果实现了类似的东西,那将是最简单的方法!

另一种选择是使用 purrr::map 函数族。

如果在 get_binCI 函数中将 rbind 替换为 dplyr::bind_rows

library(tidyverse)

dd <- data.frame(x = c(3, 4), n = c(10, 11))
get_binCI <- function(x, n) {
  bind_rows(setNames(c(binom.test(x, n)$conf.int), c("lwr", "upr")))
}

您可以将 purrr::map2tidyr::unnest 一起使用:

dd %>% mutate(result = map2(x, n, get_binCI)) %>% unnest()

#>   x  n        lwr       upr
#> 1 3 10 0.06673951 0.6524529
#> 2 4 11 0.10926344 0.6920953

purrr::map2_dfrdplyr::bind_cols

dd %>% bind_cols(map2_dfr(.$x, .$n, get_binCI))

#>   x  n        lwr       upr
#> 1 3 10 0.06673951 0.6524529
#> 2 4 11 0.10926344 0.6920953

老问题(有很多好的答案),但这是 tidyverse broom package 的一个很好的用例,它处理来自测试和建模对象的整理输出(例如 binom.testlm, 等等)。

它比其他方法更冗长,但我认为它符合您对更具表现力的方法的渴望。

过程是:

  1. 定义您将 运行 binom.test 所在的组(在本例中,这些组由 xn 定义)和 nest 他们,为每个人创建单独的 data.frames(在完整的 data.frame 中)
  2. map binom.test 调用每个组的 xn
  3. tidy 每个组的 binom.test 输出(这是扫帚的用武之地)
  4. unnest 整理后的测试输出 data.frames 变成完整的 data.frame

现在剩下 data.frame,其中每一行包含 xn 值,以及相应 binom.test 的所有输出,输出信息的每一位(点估计、upper/lower conf、p 值等)采用单独的列整齐地格式化。

library(tidyverse)
library(broom)
dd <- data.frame(x=c(3,4),n=c(10,11))
dd %>%
  group_by(x, n) %>%
  nest() %>%
  mutate(test = map(data, ~tidy(binom.test(x, n)))) %>%
  unnest(test)
#> # A tibble: 2 x 11
#> # Groups:   x, n [2]
#>       x     n data  estimate statistic p.value parameter conf.low conf.high
#>   <dbl> <dbl> <lis>    <dbl>     <dbl>   <dbl>     <dbl>    <dbl>     <dbl>
#> 1     3    10 <tib…    0.3           3   0.344        10   0.0667     0.652
#> 2     4    11 <tib…    0.364         4   0.549        11   0.109      0.692
#> # … with 2 more variables: method <chr>, alternative <chr>

从这里您可以通过更多的操作、选择所需的输出变量并重命名它们来获得您想要的格式:

dd %>%
  group_by(x, n) %>%
  nest() %>%
  mutate(test = map(data, ~tidy(binom.test(x, n)))) %>%
  unnest(test) %>%
  rename(lwr = conf.low, upr = conf.high) %>%
  select(x, n, lwr, upr)
#> # A tibble: 2 x 4
#> # Groups:   x, n [2]
#>       x     n    lwr   upr
#>   <dbl> <dbl>  <dbl> <dbl>
#> 1     3    10 0.0667 0.652
#> 2     4    11 0.109  0.692

如前所述,它很冗长。比(例如)@joran 的漂亮简洁

dd %>% 
    group_by(x,n) %>%
    do(foo(.$x,.$n))

但是,扫帚方法的好处是您不需要定义函数 foo(或 get_binCI)。它是完全独立的,在我看来更具表现力和灵活性。