使用purrr包的pmap()调用var()返回NA的问题排查
pmap_dbl(x, var)返回NA? 这个问题的核心在于pmap()的参数传递逻辑,和var()、mean()的参数处理方式不匹配,咱们一步步拆解:
1. 先搞懂pmap()的工作方式
当你用pmap(x, .f)时,pmap会遍历列表x中每个向量的对应位置,把这些位置的元素作为独立参数传给.f。比如你的例子里x <- list(1:10, 1:10, 1:10),第一次调用.f时会传入三个1,第二次传入三个2,以此类推。
2. 为什么pmap_dbl(x, mean)看似有效?
这其实是个巧合!mean()的函数签名是mean(x, ...)——它只把第一个参数当作待计算的向量,后面的...是用来传递trim之类的辅助参数的,不会被当作数据处理。当你调用mean(1,1,1)时,它只计算第一个1的均值,而刚好你的每个位置的三个值都相同,所以结果看起来是对的。如果你的列表里向量元素不同,比如x <- list(c(1,2), c(3,4), c(5,6)),pmap_dbl(x, mean)会返回1,2,但正确的每个位置均值应该是2,3,这时候这个写法就错了。
3. 为什么pmap_dbl(x, var)返回NA?
var()的函数签名是var(x, y = NULL, ...):
- 它期望第一个参数
x是一个向量 - 如果传入第二个参数
y,它会计算x和y的协方差 - 多余的参数会被忽略
当你调用var(1,1,1)时,var会把第一个1当作长度为1的向量x,第二个1当作y,第三个1忽略。而长度为1的向量方差没有意义(计算时分母是n-1=0),所以返回NA。
正确的解决方案
要让pmap()把每个位置的所有元素打包成一个向量传给函数,我们需要用匿名函数收集所有参数并转成向量,或者用purrr的lift系列函数做转换:
方法1:用匿名函数打包参数
library(purrr) x <- list(1:10, 1:10, 1:10) # 正确计算每个位置的均值 pmap_dbl(x, function(...) mean(c(...))) ## [1] 1 2 3 4 5 6 7 8 9 10 # 正确计算每个位置的方差 pmap_dbl(x, function(...) var(c(...))) ## [1] 0 0 0 0 0 0 0 0 0 0
方法2:用lift_vd()转换函数
lift_vd()可以把原本接受向量输入的函数,转换成接受多个分散参数的函数,完美适配pmap()的参数传递:
# 转换var为接受多个参数的版本 var_multi <- lift_vd(var) pmap_dbl(x, var_multi) ## [1] 0 0 0 0 0 0 0 0 0 0 # 同理,mean也应该用这种方式保证正确性(避免巧合生效的问题) mean_multi <- lift_vd(mean) pmap_dbl(x, mean_multi) ## [1] 1 2 3 4 5 6 7 8 9 10
总结
pmap()是把对应位置的元素作为独立参数传递,不是打包成向量mean()的巧合生效不代表用法正确,正确的做法是将参数打包成向量var()必须接收向量参数才能计算方差,所以一定要先把分散的参数打包,否则会返回NA
内容的提问来源于stack exchange,提问作者Richard Herron

