如何标记包含指定日期的不规则时间区间(R语言)
问题:标记时间区间是否包含指定日期
我有一个存储不规则时间区间的dataframe(df1)和一个日期向量(df2),需要为df1新增一列dateoccurs,标记该区间是否包含df2中的任意日期(后续会删除这些包含日期的区间)。现有2000+个区间和2000+个日期,之前尝试的方案要么无法匹配归属区间,要么在大数据集上出现值重复、区间信息被修改的情况,甚至出现日期误判。
示例数据
原始数据df1
> df1 diveno start fin 1 1 2018-08-01 08:20:40 2018-08-01 08:39:20 2 2 2018-08-01 08:40:50 2018-08-01 08:53:40 3 3 2018-08-01 10:01:00 2018-08-01 10:16:30 4 4 2018-08-01 15:45:30 2018-08-01 15:58:20 5 5 2018-08-01 17:06:00 2018-08-01 17:18:20
日期向量df2
> df2 date 1 2018-08-01 08:30:00 2 2018-08-01 15:47:00 3 2018-08-02 17:10:00
期望输出df3
> df3 diveno start fin dateoccurs 1 1 2018-08-01 08:20:40 2018-08-01 08:39:20 Y 2 2 2018-08-01 08:40:50 2018-08-01 08:53:40 N 3 3 2018-08-01 10:01:00 2018-08-01 10:16:30 N 4 4 2018-08-01 15:45:30 2018-08-01 15:58:20 Y 5 5 2018-08-01 17:06:00 2018-08-01 17:18:20 N
数据构造代码
# 构造df1 df1 <- data.frame(diveno=c(1,2,3,4,5), start=c("2018-08-01 08:20:40","2018-08-01 08:40:50", "2018-08-01 10:01:00","2018-08-01 15:45:30","2018-08-01 17:06:00"), fin=c("2018-08-01 08:39:20","2018-08-01 08:53:40","2018-08-01 10:16:30","2018-08-01 15:58:20", "2018-08-01 17:18:20")) df1$start <- as.POSIXct(df1$start,format="%Y-%m-%d %H:%M:%S",tz="CET") df1$fin <- as.POSIXct(df1$fin,format="%Y-%m-%d %H:%M:%S",tz="CET") # 构造df2 df2 <- data.frame(date=c("2018-08-01 08:30:00", "2018-08-01 15:47:00", "2018-08-02 17:10:00")) df2$date <- as.POSIXct(df2$date,format="%Y-%m-%d %H:%M:%S",tz="CET") # 构造期望输出df3 df3 <- data.frame(diveno=c(1,2,3,4,5), start=c("2018-08-01 08:20:40","2018-08-01 08:40:50", "2018-08-01 10:01:00","2018-08-01 15:45:30","2018-08-01 17:06:00"), fin=c("2018-08-01 08:39:20","2018-08-01 08:53:40","2018-08-01 10:16:30","2018-08-01 15:58:20", "2018-08-01 17:18:20"), dateoccurs=c("Y","N","N","Y","N"))
已尝试的方法及问题
曾尝试用data.table做重叠连接,但返回结果中fin时间被修改,大数据集上还出现值重复、diveno数量改变的问题:
intervals <- df1 elements <- df2[,1] library(data.table) #v1.10.0 j <- setDT(intervals)[data.table(elements), on = .(start <= elements, fin >= elements)] j2 <- as.data.frame(j) na.omit(j2)
误判问题
更新df2数据后,出现明显误判:df2中2018-08-01 06:25:00、2018-08-01 06:30:00、2018-08-01 06:35:00这些早于区间开始时间的日期,被错误识别为落在diveno=1和diveno=2的区间内。
单独用lubridate的%within%验证时,逻辑是正确的:
s <- df1[1,2] f <- df1[1,3] int <- interval(s,f) df2[,1] %within% int
解决方案
原因分析
误判的核心是时区不匹配:df1的时间时区是CET,而更新后的df2时间时区是UTC,两者转换时出现偏差,导致时间比较逻辑出错。
正确实现方法
方法1:用lubridate+dplyr实现
确保时区统一后,对每个区间检查是否包含任意df2的日期:
library(lubridate) library(dplyr) # 统一时区:将df2的日期转换为CET时区 df2$date <- with_tz(df2$date, tzone = "CET") # 为df1添加dateoccurs列 df1 <- df1 %>% rowwise() %>% mutate(dateoccurs = ifelse(any(df2$date %within% interval(start, fin)), "Y", "N")) %>% ungroup() # 查看结果 df1
方法2:用data.table高效实现(适合大数据集)
同样先统一时区,再用非等连接标记匹配情况:
library(data.table) # 统一时区 df2$date <- with_tz(df2$date, tzone = "CET") # 转换为data.table setDT(df1) setDT(df2) # 非等连接,标记是否有匹配 df1[, dateoccurs := "N"] matches <- df1[df2, on = .(start <= date, fin >= date), nomatch = 0] df1[diveno %in% matches$diveno, dateoccurs := "Y"] # 查看结果 df1
两种方法都能得到符合预期的结果,且不会出现误判或数据篡改问题,其中data.table方法在2000+数据量下效率更高。
内容的提问来源于stack exchange,提问作者Jess
相关产品推荐
相关产品推荐

