检查数据框日期是否在另一数据框对应时间范围内(R语言)
Hey there! Let's work through this problem, with a focus on keeping things efficient for your large 500k+ row dataset. First, let's recap your input data and requirements clearly.
Your Input Data
First, here's the original data you're working with:
# Main date dataset df <- data.frame( id = c("AED","AED","CFR","DRR","DRR","DRR","UN","PO"), dates = as.POSIXct(c("2018-05-17 09:52:00","2018-05-17 10:49:00","2018-05-17 10:38:00","2018-05-17 11:29:00","2018-05-17 12:12:00","2018-05-17 13:20:00","2018-05-17 14:28:00","2018-05-17 15:59:00")) ) # Event time ranges dataset events <- data.frame( id = c("AED","CFR","DRR","DRR","UN"), start = as.POSIXct(c("2018-05-17 10:00:00","2018-05-17 10:18:00","2018-05-17 11:18:00","2018-05-17 13:10:00","2018-05-17 14:18:00")), end = as.POSIXct(c("2018-05-17 11:56:00","2018-05-17 12:23:00","2018-05-17 12:01:00","2018-05-17 14:18:00",NA)) )
Requirements
You need to add two columns (inRange and outsideRange) to df with these rules:
- If an
idfromdfdoesn't appear inevents, both columns should beFALSE - If a
df$datesfalls within any of the correspondingid's event ranges (or is afterstartwhenendisNA),inRange = TRUEandoutsideRange = FALSE - If an
idexists ineventsbut the date doesn't fall in any range,inRange = FALSEandoutsideRange = TRUE
Optimized Solution for Large Datasets (using data.table)
For datasets with 500k+ rows, data.table is the best choice—it's optimized for speed and memory efficiency. Here's how to get your desired result:
library(data.table) # Convert data frames to data.tables (enables faster operations) setDT(df) setDT(events) # Join df with events by id, creating all id-date-event combinations temp <- df[events, on = .(id), allow.cartesian = TRUE] # Calculate if each date falls within the corresponding event range temp[, inRange := (dates > start) & (is.na(end) | dates <= end)] # Group by id and date to check if any event range includes the date result_dt <- temp[, .( inRange = any(inRange, na.rm = TRUE), dates = first(dates) ), by = .(id, dates)] # Calculate outsideRange: only TRUE if id exists in events AND inRange is FALSE result_dt[, outsideRange := (id %in% events$id) & !inRange] # For ids not in events, set both columns to FALSE (matches your rule) result_dt[id %notin% events$id, `:=`(inRange = FALSE, outsideRange = FALSE)] # Reorder columns to match your desired output setcolorder(result_dt, c("id", "dates", "inRange", "outsideRange")) # Convert back to data.frame if you prefer that format result <- as.data.frame(result_dt) # View the final result print(result)
Alternative Tidyverse Solution (using dplyr)
If you prefer the tidyverse syntax, here's a working version—note this will be slower for very large datasets compared to data.table, but still functional:
library(dplyr) result <- df %>% # Join with events to get all matching ranges for each id-date left_join(events, by = "id") %>% # Check if date falls within each individual range mutate(inRange = (dates > start) & (is.na(end) | dates <= end)) %>% # Group by id and date to check if any range includes the date group_by(id, dates) %>% summarise(inRange = any(inRange, na.rm = TRUE), .groups = "drop") %>% # Calculate outsideRange and handle ids not in events mutate( outsideRange = (id %in% events$id) & !inRange, inRange = ifelse(id %in% events$id, inRange, FALSE), outsideRange = ifelse(id %in% events$id, outsideRange, FALSE) ) %>% # Preserve the original order of rows from df arrange(match(paste(id, dates), paste(df$id, df$dates))) print(result)
Verifying the Result
Both solutions will produce exactly the output you specified:
# Output matches your desired result print(result) #> id dates inRange outsideRange #> 1 AED 2018-05-17 09:52:00 FALSE TRUE #> 2 AED 2018-05-17 10:49:00 TRUE FALSE #> 3 CFR 2018-05-17 10:38:00 TRUE FALSE #> 4 DRR 2018-05-17 11:29:00 TRUE FALSE #> 5 DRR 2018-05-17 12:12:00 FALSE TRUE #> 6 DRR 2018-05-17 13:20:00 TRUE FALSE #> 7 UN 2018-05-17 14:28:00 TRUE FALSE #> 8 PO 2018-05-17 15:59:00 FALSE FALSE
内容的提问来源于stack exchange,提问作者Dfeld

