dplyr::mutate 添加多个值
dplyr::mutate to add multiple values
dplyr Github repo 上已经有几个关于这个的问题,至少有一个相关的 SO 问题,但其中 none 完全涵盖了我的问题——我认为。
- Adding multiple columns in a dplyr mutate call 或多或少是我想要的,但是对于那种情况 (
tidyr::separate
) 有一个特例答案(我认为)对我不起作用。
- This issue ("summarise or mutate with functions returning multiple values/columns") 说 "use
do()
".
这是我的用例:我想计算精确的二项式置信区间
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))
...但现在我们真的 分裂了。
以下是 rowwise
和 nesting
的一些可能性。
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()
}
像以前一样按 x
和 n
分组,删除重复项。
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
保留所有行,但删除 x
和 n
除非您使用 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
意味着我可以直接使用 x
和 n
而不是 .$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::map2
与 tidyr::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_dfr
与 dplyr::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.test
,lm
, 等等)。
它比其他方法更冗长,但我认为它符合您对更具表现力的方法的渴望。
过程是:
- 定义您将 运行
binom.test
所在的组(在本例中,这些组由 x
和 n
定义)和 nest
他们,为每个人创建单独的 data.frames(在完整的 data.frame 中)
map
binom.test
调用每个组的 x
和 n
值
tidy
每个组的 binom.test
输出(这是扫帚的用武之地)
unnest
整理后的测试输出 data.frames 变成完整的 data.frame
现在剩下 data.frame,其中每一行包含 x
和 n
值,以及相应 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
)。它是完全独立的,在我看来更具表现力和灵活性。
dplyr Github repo 上已经有几个关于这个的问题,至少有一个相关的 SO 问题,但其中 none 完全涵盖了我的问题——我认为。
- Adding multiple columns in a dplyr mutate call 或多或少是我想要的,但是对于那种情况 (
tidyr::separate
) 有一个特例答案(我认为)对我不起作用。 - This issue ("summarise or mutate with functions returning multiple values/columns") 说 "use
do()
".
这是我的用例:我想计算精确的二项式置信区间
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))
...但现在我们真的 分裂了。
以下是 rowwise
和 nesting
的一些可能性。
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()
}
像以前一样按 x
和 n
分组,删除重复项。
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
保留所有行,但删除 x
和 n
除非您使用 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
意味着我可以直接使用 x
和 n
而不是 .$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::map2
与 tidyr::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_dfr
与 dplyr::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.test
,lm
, 等等)。
它比其他方法更冗长,但我认为它符合您对更具表现力的方法的渴望。
过程是:
- 定义您将 运行
binom.test
所在的组(在本例中,这些组由x
和n
定义)和nest
他们,为每个人创建单独的 data.frames(在完整的 data.frame 中) map
binom.test
调用每个组的x
和n
值tidy
每个组的binom.test
输出(这是扫帚的用武之地)unnest
整理后的测试输出 data.frames 变成完整的 data.frame
现在剩下 data.frame,其中每一行包含 x
和 n
值,以及相应 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
)。它是完全独立的,在我看来更具表现力和灵活性。