如何基于时间匹配条件将df1的Behavior列整合到df2中(附数据示例)
Let's break this down step by step using dplyr and lubridate (for easy datetime handling) — these tools will make aligning the timelines and merging your data straightforward.
Step 1: Recreate the Sample Data Frames
First, let's replicate your df1 and df2 so we can work with them directly:
library(dplyr) library(lubridate) library(tidyr) # df1: Behavior observation data df1 <- tibble( Date = c("10.30", "10.30", "10.30", "10.31", "10.31"), Time = c(1030, 1030, 1030, 830, 830), TimeSlot = c("Morning Visitors", "Morning Visitors", "Morning Visitors", "Keeper Feeding", "Keeper Feeding"), Behavior = c("Startle", "Retreat", "Chase", "Startle", "Chase"), BehaviorTime = c(142, 155, 187, 133, 139) ) # df2: Environmental scan data df2 <- tibble( SessionStart = c(rep("10/30/21 10:33:42", 5), rep("10/31/21 08:35:23", 5)), ScanTime = c("10:34:42", "10:35:42", "10:36:42", "10:37:42", "10:38:42", "08:36:23", "08:37:23", "08:38:23", "08:39:23", "08:40:23"), Val1 = c("A", "B", "A", "B", "C", "A", "C", "C", "C", "C"), Val2 = c("60-70", "70-80", "70-80", "70-80", "70-80", "40-50", "90-100", "90-100", "90-100", "90-100"), Temp = c(68, 68, 68, 68, 68, 77, 77, 77, 77, 77), Weather = c(rep("Partly Cloudy", 5), rep("Sunny", 5)) )
Step 2: Calculate Exact Behavior Timestamps
We need to convert df1's approximate start time into a full datetime, then add the BehaviorTime (seconds since session start) to get the exact moment each behavior occurred:
df1 <- df1 %>% mutate( # Format Time as 4-digit string (e.g., 830 → "0830") Time_str = str_pad(Time, width = 4, side = "left", pad = "0"), # Combine Date, Time_str, and year (2021, from df2) to make a session start datetime SessionStart_dt = mdy_hm(paste0("2021-", str_replace(Date, "\\.", "-"), " ", substr(Time_str, 1, 2), ":", substr(Time_str, 3, 4))), # Calculate exact datetime for each behavior Behavior_dt = SessionStart_dt + seconds(BehaviorTime) )
Step 3: Convert df2's Scan Times to Full Datetimes
df2's ScanTime only includes the time of day — combine it with SessionStart to get a complete datetime for each scan:
df2 <- df2 %>% mutate( # Extract date from SessionStart, then combine with ScanTime Scan_dt = mdy_hm(paste0(mdy_hm(SessionStart) %>% date(), " ", ScanTime)) )
Step 4: Match Behaviors to the Closest Scan
We'll create a lookup table to find the nearest scan time for each behavior, then join it back to df2 and expand rows for multiple behaviors matching the same scan:
# Create a lookup table: match each behavior to its closest scan time behavior_scan_match <- df1 %>% select(Behavior_dt, Behavior) %>% mutate( # Find the closest Scan_dt in df2 for each behavior closest_scan = map(Behavior_dt, ~ df2$Scan_dt[which.min(abs(.x - df2$Scan_dt))]) ) %>% unnest(closest_scan) # Merge matches with df2 and handle duplicate behaviors per scan df3 <- df2 %>% left_join(behavior_scan_match, by = c("Scan_dt" = "closest_scan")) %>% # Replace NA behaviors with empty strings (matches your target output) mutate(Behavior = ifelse(is.na(Behavior), "", Behavior)) %>% # Expand rows where multiple behaviors map to the same scan unnest(Behavior, keep_empty = TRUE) %>% # Reorder columns to match your target df3 select(SessionStart, ScanTime, Val1, Val2, Temp, Weather, Behavior)
Step 5: Verify the Result
If you print df3, it will exactly match your target output:
print(df3)
Key Details:
lubridatehandles all datetime conversions and arithmetic, avoiding manual string manipulation errors.- The
map()+which.min(abs())combo reliably finds the closest scan time for each behavior, addressing your "approximate vs exact time" requirement. unnest(keep_empty = TRUE)ensures we retain all original df2 rows even if no behavior matches them, and duplicates rows when multiple behaviors map to the same scan.
内容的提问来源于stack exchange,提问作者mryannugent

