将dplyr中的汇总函数转换为非标准评估(NSE)。

4
考虑以下互动示例,它生成一个汇总表格:
library(dplyr)

tg <- ToothGrowth
ci_int <- 0.95

tg %>%
  group_by(supp, dose) %>%
  summarise(N = n(),
            mean = mean(len, na.rm = T),
            sd = sd(len, na.rm = T),
            se = sd / sqrt(N),
            ci = se * qt(ci_int / 2 + 0.50, N - 1))

#     supp  dose     N  mean       sd        se       ci
#   (fctr) (dbl) (int) (dbl)    (dbl)     (dbl)    (dbl)
# 1     OJ   0.5    10 13.23 4.459709 1.4102837 3.190283
# 2     OJ   1.0    10 22.70 3.910953 1.2367520 2.797727
# 3     OJ   2.0    10 26.06 2.655058 0.8396031 1.899314
# 4     VC   0.5    10  7.98 2.746634 0.8685620 1.964824
# 5     VC   1.0    10 16.77 2.515309 0.7954104 1.799343
# 6     VC   2.0    10 26.14 4.797731 1.5171757 3.432090

我想把这个转化成一个函数,将 data.frame, measure 变量, groupvars 分组变量和 conf.int 抽象出来。有了下面的开端:

library(lazyeval)

summarySE <- function(df, measure, groupvars, conf.int = 0.95) {
  summary_dots <- list(
    ~ n(), 
    interp(~ mean(var, na.rm = T), var = as.name(measure)),
    interp(~ sd(var, na.rm = T), var = as.name(measure))
  )

  df %>%
    group_by_(.dots = groupvars) %>%
    summarise_(.dots = setNames(summary_dots, c("N", "mean", "sd")))
}

summarySE(tg, "len", c("supp", "dose"))

这将产生:

#     supp  dose     N  mean       sd
#   (fctr) (dbl) (int) (dbl)    (dbl)
# 1     OJ   0.5    10 13.23 4.459709
# 2     OJ   1.0    10 22.70 3.910953
# 3     OJ   2.0    10 26.06 2.655058
# 4     VC   0.5    10  7.98 2.746634
# 5     VC   1.0    10 16.77 2.515309
# 6     VC   2.0    10 26.14 4.797731

然而,这似乎不太DRY?此外,我不确定如何实现seci,而不会变得过于复杂/冗长?也许有一个更好的方法,或者也许应该将其分成几个函数?

如何将上面的摘要表转换为函数,以便我可以传递任何组合具有不同measuregroupvarsdata.frame,并具有dplyr的“精神”?

2个回答

4

我不是很明白为什么计算SE和CI比你已经做的更加复杂。

我使用...参数来捕获您的分组参数,因为这似乎更容易使用。

总体上,我得到了以下函数:

summarySE <- function(.data, measure, ..., conf.int = 0.95) {
  dots <- lazyeval::lazy_dots(...)
  measure <- lazyeval::lazy(measure)

  summary_dots <- list(
    N = ~ n(),
    mean = lazyeval::interp(~ mean(var, na.rm = T), var = measure),
    sd = lazyeval::interp(~ sd(var, na.rm = T), var = measure),
    se = ~ sd / sqrt(N),
    ci = ~ se * qt(conf.int / 2 + 0.50, N - 1))

  .data <- dplyr::group_by_(.data, .dots = dots)
  dplyr::summarise_(.data, .dots = summary_dots)
}

你可以将其制作成SE版本和NSE版本,如果你想的话(就像Hadley可能会做的那样)。
使用方法:
summarySE(tg, len, supp, dose)

Source: local data frame [6 x 7]
Groups: supp [?]

    supp  dose     N  mean       sd        se       ci
  (fctr) (dbl) (int) (dbl)    (dbl)     (dbl)    (dbl)
1     OJ   0.5    10 13.23 4.459709 1.4102837 3.190283
2     OJ   1.0    10 22.70 3.910953 1.2367520 2.797727
3     OJ   2.0    10 26.06 2.655058 0.8396031 1.899314
4     VC   0.5    10  7.98 2.746634 0.8685620 1.964824
5     VC   1.0    10 16.77 2.515309 0.7954104 1.799343
6     VC   2.0    10 26.14 4.797731 1.5171757 3.432090

是的,我将suppdose作为向量传递,即c(supp, dose)-- 我总是害怕使用...-- 是否有一种快速的替代方法将它们作为group = c(...)传递? - JasonAizkalns
1
你需要摆脱 lazy_dots。然后使用一个列表来作为分组变量,并将其直接提供给 group_by_。但是它们必须被引用。例如:summarySE(tg, len, list(~supp, ~dose))。无法想象如何将一系列裸名称懒惰地捕获到懒惰对象的列表中。 - Axeman

1

我不确定这是否符合dplyr的“精神”,但您也可以尝试使用字符串来计算meansd等:

summarySE <- function(df, measure, groupvars, conf.int = 0.95) {
  df %>% group_by_(.dots = groupvars)%>%
    summarise_(N="n()",
               mean = paste0("mean(",measure,", na.rm = T)"),
               sd = paste0("sd(",measure,", na.rm = T)"),
               se = "sd/sqrt(N)",
               ci = paste0("se * stats::qt(",conf.int," / 2 + 0.50, N - 1)"))
}

summarySE(tg, "len", c("supp", "dose"))

#    supp  dose     N  mean       sd        se       ci
#  (fctr) (dbl) (int) (dbl)    (dbl)     (dbl)    (dbl)
#1     OJ   0.5    10 13.23 4.459709 1.4102837 3.190283
#2     OJ   1.0    10 22.70 3.910953 1.2367520 2.797727
#3     OJ   2.0    10 26.06 2.655058 0.8396031 1.899314
#4     VC   0.5    10  7.98 2.746634 0.8685620 1.964824
#5     VC   1.0    10 16.77 2.515309 0.7954104 1.799343
#6     VC   2.0    10 26.14 4.797731 1.5171757 3.432090

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接