R语言中高效判断DateTime是否处于任意时间区间的方法
高效判断心率时间是否处于睡眠区间的优化方案
我有一个含约38000条观测的hr_clean数据框,每行是Fitbit采集的连续一个月每分钟平均心率对应的POSIXct类型时间。需要判断每行的dateTime是否处于睡眠时段,现有sleep_ranges数据框存储睡眠起始(startTime)和结束(endTime)时间(均为POSIXct类型)。原for循环实现逻辑但速度极慢,现寻求优化方案。
原低效代码:
# Set seed for reproducibility set.seed(123) # Define the start and end dates start_date <- ymd_hms("2025-03-01 00:00:00") end_date <- ymd_hms("2025-04-01 23:59:59") hr_clean <- data.frame(dateTime = start_date + runif(10, 0, as.numeric(difftime(end_date, start_date, units = "secs")))) sleep_ranges <- data.frame(startTime = ymd_hms(c("2025-03-15 00:58:00", "2025-03-14 00:17:00", "2025-03-12 23:38:00", "2025-03-12 00:21:30")), endTime = ymd_hms(c("2025-03-15 09:10:30", "2025-03-14 07:30:30", "2025-03-13 07:45:30", "2025-03-12 07:26:30"))) is_sleeping_df <- data.frame(dateTime = character(), is_sleeping = character()) for(minute in hr_clean$dateTime){ print(as_datetime(minute)) sleepTF = (any(minute > sleep_ranges$startTime & minute < sleep_ranges$endTime)) df <- data.frame(dateTime = as_datetime(minute), is_sleeping = sleepTF) is_sleeping_df <- rbind(is_sleeping_df, df) }
原代码低效原因
- 循环中频繁使用
rbind拼接数据框:每次rbind都会创建新数据框并复制全部数据,38000次操作会导致大量内存开销和时间浪费。 - 逐行处理:R的循环本身对逐元素操作效率较低,数据量较大时劣势更明显。
优化方案
方案1:基础R向量化操作
利用R的向量化特性,直接对整个dateTime列进行判断,避免循环和频繁拼接:
library(lubridate) # 对每个dateTime,检查是否存在任意睡眠区间包含它 hr_clean$is_sleeping <- sapply(hr_clean$dateTime, function(x) { any(x > sleep_ranges$startTime & x < sleep_ranges$endTime) })
注:相比原for循环,该方案避免了rbind的重复内存复制,效率提升明显。
方案2:data.table非等连接(性能最优)
data.table的非等连接专门针对区间匹配场景,处理大数据量速度极快:
library(data.table) library(lubridate) # 转换为data.table格式 setDT(hr_clean) setDT(sleep_ranges) # 初始化标记列 hr_clean[, is_sleeping := FALSE] # 非等连接匹配符合区间的记录 matches <- hr_clean[sleep_ranges, on = .(dateTime > startTime, dateTime < endTime), nomatch = 0] # 标记匹配成功的时间点 hr_clean[matches$dateTime, is_sleeping := TRUE] # 如需转回data.frame格式 # hr_clean <- as.data.frame(hr_clean)
方案3:fuzzyjoin包(语法简洁)
fuzzyjoin提供直观的模糊区间匹配函数,无需复杂操作:
library(fuzzyjoin) library(lubridate) # 模糊左连接,匹配dateTime处于睡眠区间的记录 result <- fuzzy_left_join( hr_clean, sleep_ranges, by = c("dateTime" = "startTime", "dateTime" = "endTime"), match_fun = list(`>`, `<`) ) # 标记是否处于睡眠区间:存在匹配则为TRUE result$is_sleeping <- !is.na(result$startTime) # 保留原心率数据列和标记列 final_result <- result[, c("dateTime", "is_sleeping")]
内容的提问来源于stack exchange,提问作者Evan N.
相关产品推荐
相关产品推荐

