我想使用tidyverse计算tibble中两个向量的几何平均值。两个变量的计算平均值应按行进行。为此,我写了下面的函数,它起了作用,但我只是想知道如何以更高效的编码方式完成或编写此操作,更高效我指的是更少的代码,更快、更整洁。有更好的主意吗?只是想一想,map_*()
在这种情况下能实现吗?我也知道使用rowwise()
,但据我最近所知,这篇文章的作者哈德利·威克姆在策略上淡化了rowwise()
的使用。
一个最小的代表性例子如下:
Reprex
df <- tribble(
~v1, ~ v2,
4, 5,
NA, 7,
2, 2,
3, NA,
NA, NA,
9, 9)
建议功能
gMean <- function (df, v1, v2){
output <- vector ("double", nrow (df))
for (i in 1:nrow(df)){
output[[i]] <- case_when (!is.na(df$v1[i]) && !is.na(df$v2[i]) ~ ((df$v1[i] * df$v2[i]) ^ 0.5),
is.na (df$v1[i]) && is.na (df$v2[i]) ~ 1,
!is.na(df$v1[i]) && is.na(df$v2[i]) ~ df$v1[i],
is.na(df$v1[i]) && !is.na(df$v2[i]) ~ df$v2[i]
)
}
output
}
输出
df %>%
gMean (v1, v2)
[1] 4.472136 7.000000 2.000000 3.000000 1.000000 9.000000
您也可以(只(使用mutate
,而不是在每一行上循环。
在您的情况下,不需要map
或使用rowwise
,而且由于case_when
是从下到上进行评估的,因此您也可以简化is.na
调用。
df |> mutate(gMean = case_when(is.na(v1) & is.na(v2) ~ 1,
is.na(v1) ~ v2,
is.na(v2) ~ v1,
TRUE ~ sqrt(v1 * v2)))
然而,如果我们想使用rowwise()
或map2_dbl()
,我们可以使用prod
来允许na.rm
-选项,并且只取平方根(^(1/2)
(,其中有2个值可用。利用1/0
定义为R中的Inf
,1^Inf
定义为1
。
df |>
rowwise() |>
mutate(gMean = prod(v1, v2, na.rm = TRUE) ^ (1 / sum(c(!is.na(v1), !is.na(v2))))) |>
ungroup()
df |>
mutate(gMean = map2_dbl(v1, v2, ~ prod(.x, .y, na.rm = TRUE) ^ (1 / sum(c(!is.na(.x), !is.na(.y))))))
输出:
# A tibble: 6 × 3
v1 v2 gMean
<dbl> <dbl> <dbl>
1 4 5 4.47
2 NA 7 7
3 2 2 2
4 3 NA 3
5 NA NA 1
6 9 9 9
另一种可能的解决方案:
library(tidyverse)
df %>%
mutate(gMean = map2_dbl(v1, v2, ~ sqrt(.x * .y)) %>%
coalesce(v1, v2) %>% if_else(is.na(.), 1, .))
#> # A tibble: 6 × 3
#> v1 v2 gMean
#> <dbl> <dbl> <dbl>
#> 1 4 5 4.47
#> 2 NA 7 7
#> 3 2 2 2
#> 4 3 NA 3
#> 5 NA NA 1
#> 6 9 9 9