dplyr:将分组的tibble传递给自定义函数

9

以下情景简化了我的实际情况:
我的数据来自村庄,我想通过村庄变量总结一个结果变量。

> data
   village     A     Z      Y 
     <chr> <int> <int>   <dbl> 
 1       a     1     1   500     
 2       a     1     1   400     
 3       a     1     0   800  
 4       b     1     0   300  
 5       b     1     1   700  

例如,我想仅通过村庄使用Z==z计算Y的均值。在这种情况下,我希望村庄"a"的结果为(500 + 400)/2 = 450 ,村庄"b"的结果为700。
请注意,实际情况更加复杂,不能直接使用此答案,但关键是我需要将分组后的表格和全局变量(z)传递给我的函数
z <- 1 # z takes 0 or 1
data %>%
    group_by(village) %>% # grouping by village
    summarize(Y_village = Y_hat_village(., z)) # pass a part of tibble and a global variable

Y_hat_village <- function(data_village, z){
    # This function takes a part of tibble (`data_village`) and a variable `z`
    # Calculate the mean for a specific z in a village
    data_z <- data_village %>% filter(Z==get("z"))
    return(mean(data_z$Y))
}

然而,我发现.会传递整个tibble,上面的代码对于所有组返回相同的值。

1
我认为你正在寻找 do,你也可以考虑使用 split 然后 map - moodymudskipper
3个回答

6

有几个可以简化的地方。其中一个是在你的函数中:由于你将值z传递给函数,因此不需要使用get("z")。你在全局环境中有一个z,你传递它;或者更安全的是,将你的z值分配给一个带有其他名称的变量,这样你就不会遇到作用域问题,并将其传递给函数。在这种情况下,我称之为z_val

library(tidyverse)

z_val <- 1

Y_hat_village2 <- function(data, z) {
  data_z <- data %>% filter(Z == z)
  return(mean(data_z$Y))
}

您可以使用do对每个组进行函数调用,这将为您提供一个列表列,然后解压缩该列。请注意,我将变量z_val传递给参数z
df %>%
  group_by(village) %>%
  do(y_hat = Y_hat_village2(., z = z_val)) %>%
  unnest()
#> # A tibble: 2 x 2
#>   village y_hat
#>   <chr>   <dbl>
#> 1 a         450
#> 2 b         700

然而,do已经被弃用,建议使用purrr::map,但我仍然不太熟悉。在这种情况下,您可以进行分组和嵌套,从而得到一个名为data的数据框列,然后对该列进行映射,并再次提供z = z_val。当您展开y_hat列时,仍然会保留原始数据作为嵌套列,因为您仍然需要访问其余列。
df %>%
  group_by(village) %>%
  nest() %>%
  mutate(y_hat = map(data, ~Y_hat_village2(., z = z_val))) %>%
  unnest(y_hat)
#> # A tibble: 2 x 3
#>   village data             y_hat
#>   <chr>   <list>           <dbl>
#> 1 a       <tibble [3 × 3]>   450
#> 2 b       <tibble [2 × 3]>   700

为了确保一切工作正常,我还传入了z = 0来检查1.作用域问题和2.z的其他值是否有效。

df %>%
  group_by(village) %>%
  nest() %>%
  mutate(y_hat = map(data, ~Y_hat_village2(., z = 0))) %>%
  unnest(y_hat)
#> # A tibble: 2 x 3
#>   village data             y_hat
#>   <chr>   <list>           <dbl>
#> 1 a       <tibble [3 × 3]>   800
#> 2 b       <tibble [2 × 3]>   300

1
作为对@patL答案的扩展/修改,您还可以在purrr:map中包装tidyverse解决方案,以返回两个tibble列表,一个用于每个z值:
z <- c(0, 1);
map(z, ~df %>% filter(Z == .x) %>% group_by(village) %>% summarise(Y.mean = mean(Y)))
#[[1]]
## A tibble: 2 x 2
#  village Y.mean
#  <fct>    <dbl>
#1 a         800.
#2 b         300.
#
#[[2]]
## A tibble: 2 x 2
#  village Y.mean
#  <fct>    <dbl>
#1 a         450.
#2 b         700.

样例数据

df <- read.table(text =
    "  village     A     Z      Y
 1       a     1     1   500
 2       a     1     1   400
 3       a     1     0   800
 4       b     1     0   300
 5       b     1     1   700  ", header = T)

0
你可以使用 dplyr 来完成它:
library(dplyr)

df %>% 
  group_by(village) %>% 
  filter(Z == 1) %>% 
  summarise(Y_village = mean(Y))

## A tibble: 2 x 2
#  village Y_village
#  <chr>       <dbl>
#1 a             450
#2 b             700

获取所有列:

df %>% 
  group_by(village) %>% 
  filter(Z == 1) %>% 
  mutate(Y_village = mean(Y)) %>% 
  distinct(village, A, Z, Y_village)

## A tibble: 2 x 4
## Groups:   village [2]
#  village     A     Z Y_village
#  <chr>   <dbl> <dbl>     <dbl>
#1 a           1     1       450
#2 b           1     1       700

数据

df <- data_frame(village = c("a", "a", "a", "b", "b"),
                 A = rep(1, 5),
                 Z = c(1, 1, 0, 0, 1),
                 Y = c(500, 400, 800, 30, 700))

谢谢!我认为你的答案传递了一个列而不是 tibble 的一部分。对于这个玩具示例,我知道它可以工作,但我想在我的函数中拥有所有列。 - user2978524
你想保留所有列,包括 Y 还是所有带有 Y_village 的列? - patL

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