R语言逐行应用函数跨表统计日度观测期数返回NA问题
问题原因
你的代码返回NA的核心问题出在apply()的运行机制上:apply()处理输入时会先把数据框强制转换为矩阵,而矩阵仅支持存储单一类型的数据,因此你df1中存储的Date类型日期列会被自动转为字符串,传入匿名函数的行值x是字符型,和df2中存储的Date类型日期做大小比较时类型不匹配,最终返回NA。
你最开始直接写sum(df2$start <= df1$dates & df2$end>=df1$dates)的问题在于:R的比较运算符是向量化运算,长度不一致的向量比较时会触发循环规则,最终sum计算的是两个向量所有位置比较结果的总TRUE数,不是逐行对应每个日期的统计值。
可行解决方案
方案1:基础R无依赖实现
不要用apply遍历整个数据框,直接遍历df1$dates这个保留了Date类型的向量即可,逻辑和你最开始写的一致,不会触发类型转换问题:
df1$number <- sapply(df1$dates, function(current_date) { sum(df2$start <= current_date & df2$end >= current_date) })
运行后得到的结果完全符合预期:
> df1 dates number 1 2020-01-01 2 2 2020-01-02 2 3 2020-01-03 1 4 2020-01-04 0 5 2020-01-05 1 6 2020-01-06 1 7 2020-01-07 1 8 2020-01-08 1 9 2020-01-09 1 10 2020-01-10 2
方案2:大数据量高效向量化实现
如果你的数据量级很大,逐行遍历的性能较差,可以用事件累计的向量化写法,不需要逐行判断:
# 构造事件流:观测开始计数+1,观测结束次日计数-1 event_log <- rbind( data.frame(dt = df2$start, val = 1), data.frame(dt = df2$end + 1, val = -1) ) # 按日期汇总变动值,计算累计生效数 event_log <- aggregate(val ~ dt, event_log, sum) event_log$cum <- cumsum(event_log$val) # 匹配回原df1,填充空值 df1 <- merge(df1, event_log, by.x = "dates", by.y = "dt", all.x = TRUE) df1$number <- cumsum(c(0, replace(df1$val, is.na(df1$val), 0)))[-1] df1 <- df1[, c("dates", "number")]
方案3:区间连接计数实现
你之前提到的区间连接方案并非只能做1:1匹配,连接后按日期分组计数同样可以得到正确结果,逻辑更直观:
library(dplyr) library(fuzzyjoin) library(tidyr) df1 <- df1 %>% fuzzy_inner_join( df2, by = c("dates" = "start", "dates" = "end"), match_fun = list(`>=`, `<=`) ) %>% count(dates, name = "number") %>% right_join(df1, by = "dates") %>% mutate(number = replace_na(number, 0)) %>% arrange(dates)
内容的提问来源于stack exchange,提问作者Tan Sing Chee
相关产品推荐
相关产品推荐

