purrr 将 t.test 映射到拆分 df

purrr map a t.test onto a split df

我是 purrr 的新手,Hadley's promising functional programming R library。我正在尝试对一个变量进行分组和拆分数据框和 运行 的 t 检验。使用示例数据集的示例可能如下所示。

mtcars %>% 
  dplyr::select(cyl, mpg) %>% 
  group_by(as.character(cyl)) %>% 
  split(.$cyl) %>% 
  map(~ t.test(.$`4`$mpg, .$`6`$mpg))

这会导致以下错误:

Error in var(x) : 'x' is NULL
In addition: Warning messages:
1: In is.na(x) : is.na() applied to non-(list or vector) of type 'NULL'
2: In mean.default(x) : argument is not numeric or logical: returning NA

我是不是误解了 map 的工作原理?或者有更好的方法来考虑这个问题吗?

我不完全理解预期的结果,但这可能是一个答案的起点。 purrr 中的 map() 在公式参数中使用 .x

这里有一种方法可以完成我认为你正在尝试做的事情 purrr

mtcars %>%
  split(as.character(.$cyl)) %>%
  map(~t.test(.x$mpg)) 

但是,purrr::by_slice()dplyr::group_by() 搭配得很好。

library(purrr)
library(dplyr)

mtcars %>% 
  dplyr::select(cyl, mpg) %>% 
  group_by(as.character(cyl)) %>%
  by_slice(~ t.test(.x$mpg))

或者,您可以使用 dplyr:::summarise().

完全跳过 purrr
library(purrr)
library(dplyr)

mtcars %>% 
  dplyr::select(cyl, mpg) %>% 
  group_by(as.character(cyl)) %>%
  summarise(t_test = data_frame(t.test(.$mpg)))

如果嵌套的 data.frame 令人困惑,broom 可以帮助我们得到一个简单的 data.frame 结果摘要。

purrr + broom + tidyr

library(broom)
library(tidyr)
mtcars %>%
  group_by(as.character(cyl)) %>%
  by_slice(~tidy(t.test(.x$mpg))) %>%
  unnest()

dplyr + broom

library(broom)

mtcars %>% 
  dplyr::select(cyl, mpg) %>% 
  group_by(as.character(cyl)) %>%
  do(tidy(t.test(.$mpg)))

已编辑以包括对评论的回复

有了管道,我们很快就会忘乎所以。我认为 Walt 的回答做得很好,但我想确保我提供了 purrr-ty 答案。我希望 pipeR 的使用不会过于混乱。

library(purrr)
library(dplyr)
library(broom)
library(tidyr)
library(pipeR)

mtcars %>>%
  (split(.,.$cyl)) %>>%
  (split_cyl~
    names(split_cyl) %>>%
     (
       cross_d(
         list(against=.,tested=.),
         .filter = `==`
       )
     ) %>>%
     by_row(
       ~tidy(t.test(split_cyl[[.x$tested]]$mpg,split_cyl[[.x$against]]$mpg))
     )
  ) %>>%
  unnest()

要执行两个样本 t 检验,您必须创建气缸数的组合。我看不到您可以使用 purrr 函数创建组合。然而,仅使用 purrr 和基本 R 函数的方法是

library(purrr)
t_test2 <- mtcars %>% split(.$cyl) %>%
          transpose() %>%
          .[["mpg"]] %>%
          (function(x) combn(names(x), m=2, function(y) t.test(flatten_dbl(x[y[1]]), flatten_dbl(x[y[2]])) , simplify=FALSE))

虽然这看起来有点做作。

仅使用具有链接的基本 R 函数的类似方法是

t_test <- mtcars %>% split(.$cyl) %>%
                          (function(x) combn(names(x), m=2, function(y) x[y], simplify=FALSE)) %>%
                           lapply( function(x) t.test(x[[1]]$mpg, x[[2]]$mpg))

尤其是处理需要多路输入的管道时(我们这里没有Haskell的Arrows),我觉得先用types/signatures推理比较容易,再把逻辑封装在函数中(你可以单元测试),然后写一个简洁的链。

在这种情况下,您想比较所有可能的向量对,因此我将设定一个目标,即编写一个函数,该函数接受一对(即 2 个向量的列表)和 returns 2-其中 t.test 个。

完成此操作后,您只需要一些胶水。所以计划是:

  1. 编写接受向量列表并执行 2 路 t 检验的函数。
  2. 写一个 function/pipe 从 mtcars 获取向量(简单)。
  3. 将上面的内容映射到对列表上。

在编写任何代码之前制定此计划很重要。由于 R 不是强类型的,所以事情在某种程度上被混淆了,但这样你就可以首先推理 "types",其次实现。

第 1 步

t.test 取点,所以我们使用 purrr:lift 让它取一个列表。因为我们不想匹配列表元素的名称,所以我们使用 .unnamed = TRUE。此外,我们还明确表示我们正在使用元数为 2 的 t.test 函数(尽管代码运行不需要这个额外的步骤)。

t.test2 <- function(x, y) t.test(x, y)
liftedTT <- lift(t.test2, .unnamed = TRUE)

第 2 步

将我们在步骤1中得到的函数包装成一个函数链,它采用一个简单的对(这里我使用索引,使用cyl factor levels应该很容易,但我没有时间去弄清楚) .

doTT <- function(pair) {
  mtcars %>%
    split(as.character(.$cyl)) %>%
    map(~ select(., mpg)) %>% 
    extract(pair) %>% 
    liftedTT %>% 
    broom::tidy
}

步骤 3

既然我们已经准备好了所有的乐高积木,构图就很简单了。

1:length(unique(mtcars$cyl)) %>% 
  combn(2) %>% 
  as.data.frame %>% 
  as.list %>% 
  map(~ doTT(.))

$V1
  estimate estimate1 estimate2 statistic      p.value parameter conf.low conf.high
1 6.920779  26.66364  19.74286  4.719059 0.0004048495  12.95598 3.751376  10.09018

$V2
  estimate estimate1 estimate2 statistic      p.value parameter conf.low conf.high
1 11.56364  26.66364      15.1  7.596664 1.641348e-06  14.96675 8.318518  14.80876

$V3
  estimate estimate1 estimate2 statistic      p.value parameter conf.low conf.high
1 4.642857  19.74286      15.1  5.291135 4.540355e-05  18.50248 2.802925  6.482789

这里有很多东西需要清理,主要是使用因子级别并将它们保留在输出中(而不是在第二个函数中使用全局变量),但我认为你想要的核心就在这里。根据我的经验,不迷路的诀窍是从内到外工作。