R中sapply运行自定义函数时首个ID全为NA返回错误结果
R自定义面板缺失值填充函数异常问题修复
问题现象
编写了按规则填充面板数据缺失值的自定义函数,仅当首个ID分组的目标变量全为NA时,全表生成的填充变量全部变为NA,其余场景运行正常。
基础依赖与示例数据
library(tidyverse) library(data.table) # 正常示例数据 dt <- data.frame(id = c(rep('a', 5), rep('b', 5), rep('c', 5)), var1 = c(rep('', 4), 'bonjour', 'bye', NA, 'bye', 'bye', NA, 'hi', 'hi', NA, 'hi', 'hi'), year = c(2005:2009, 1995:1998, 2002, 1995:1999))
自定义填充函数
fill.in <- function(var, yr, finyr) { leadv <- lead(var, n=1, order_by = yr) lagv <- lag(var, n=1, order_by = yr) leadyr <- lead(yr, n=1, order_by = yr) lagyr <- lag(yr, n=1, order_by = yr) # 非缺失值直接保留 try1 <- ifelse(test = !is.na(var), yes = var, no = NA) # 前后观测值一致、间隔不超过2年的缺失值填充 try2 <- ifelse(test = is.na(try1) & leadv == lagv & abs(leadyr-lagyr) <= 3 & !is.na(leadv), yes = leadv, no = try1) # 最后一年观测缺失的特殊填充规则 ifelse(test = is.na(try2) & yr == finyr & abs(yr-lagyr) <= 3 & !is.na(lagv), yes = lagv, no = try2) }
正常运行流程
setDT(dt) # 按ID分组生成最大年份标识 dt[, finalyr := max(year), by = id] # 转为字符型避免因子问题 dt$var1 <- as.character(dt$var1) # 空字符串转NA dt[, var1 := na_if(var1, '')] fill.in.vs <- c('var1') fixed.vnames <- paste0('fixed.', fill.in.vs) # 按ID分组调用填充函数 dt[, (fixed.vnames) := sapply(.SD, FUN = fill.in, year, finalyr, simplify = FALSE, USE.NAMES = FALSE), by = id, .SDcols = fill.in.vs]
正常运行结果符合预期,仅符合规则的缺失值被填充。
异常复现场景
当首个ID分组的var1全为空字符串,转NA后运行上述代码,全表fixed.var1全部变为NA:
# 异常测试数据,id=a的var1全为空 dt <- data.frame(id = c(rep('a', 5), rep('b', 5), rep('c', 5)), var1 = c(rep('', 5), 'bye', NA, 'bye', 'bye', NA, 'hi', 'hi', NA, 'hi', 'hi'), year = c(2005:2009, 1995:1998, 2002, 1995:1999))
已验证的规律
- 若不将空字符串转为NA,即使首个ID全为空字符串也不会出现问题
- 只有首个ID分组全为NA时才会触发该问题,若第二个及之后的ID分组全为NA则结果正常
- 空字符串转NA的方式不影响结果,无论用
na_if还是ifelse都会出现该问题
问题原因
R中默认的NA为逻辑类型,你的函数中ifelse返回值在首个分组全为NA时,返回的向量类型为逻辑型。data.table在创建新列时会以首个分组的返回值类型作为整列的类型,后续分组返回的字符型填充值会被强制转换为逻辑型,非逻辑值会被转为NA,最终导致全列异常。
修复方案
将函数中所有显式使用的NA替换为字符型缺失值NA_character_,保证所有分组的返回值类型统一为字符型即可:
# 修复后的填充函数 fill.in <- function(var, yr, finyr) { leadv <- lead(var, n=1, order_by = yr) lagv <- lag(var, n=1, order_by = yr) leadyr <- lead(yr, n=1, order_by = yr) lagyr <- lag(yr, n=1, order_by = yr) try1 <- ifelse(test = !is.na(var), yes = var, no = NA_character_) try2 <- ifelse(test = is.na(try1) & leadv == lagv & abs(leadyr-lagyr) <= 3 & !is.na(leadv), yes = leadv, no = try1) ifelse(test = is.na(try2) & yr == finyr & abs(yr-lagyr) <= 3 & !is.na(lagv), yes = lagv, no = try2) }
内容的提问来源于stack exchange,提问作者dmcd
相关产品推荐
相关产品推荐

