R语言时间逻辑判断失效求助:基于日出日落时间分类时段
问题分析与解决:时区不匹配导致时段分类失效
核心问题
你使用lubridate、dplyr、suncalc处理数据时,捕获时间(Time列,UTC时区)与日出日落时间(dawn/sunriseEnd等,MST时区)直接对比,因时区不统一导致逻辑判断几乎不生效,仅少数行得到结果。
原数据与代码
数据样例
library(lubridate) library(dplyr) library(suncalc) # 数据前6行 md <- structure(list(Date.of.Capture = structure(c(18383, 18393, 18395, 18395, 18402, 18815), class = "Date"), Month = c(5L, 5L, 5L, 5L, 5L, 7L), Day = c(1L, 11L, 13L, 13L, 20L, 7L), Year = c(2020L, 2020L, 2020L, 2020L, 2020L, 2021L), Time.of.Capture = c("6:24", "6:27", "8:55", "8:55", "20:22", "6:26"), Time = structure(c(1588314240, 1589178420, 1589360100, 1589360100, 1590006120, 1625639160), class = c("POSIXct", "POSIXt"), tzone = "UTC"), ToD = c(NA, NA, NA, NA, "Daytime", NA), lat = c(40.75336, 40.75336, 40.75336, 40.75336, 40.75336, 40.75336), lon = c(-111.624088, -111.624088, -111.624088, -111.624088, -111.624088, -111.624088), sunriseEnd = structure(c(1588336111, 1589199430, 1589372111, 1589372111, 1589976545, 1625659614), class = c("POSIXct", "POSIXt"), tzone = "MST"), sunsetStart = structure(c(1588386056, 1589250673, 1589423593, 1589423593, 1590028795, 1625713092), class = c("POSIXct", "POSIXt"), tzone = "MST"), dawn = structure(c(1588334143, 1589197398, 1589370065, 1589370065, 1589974454, 1625657442), class = c("POSIXct", "POSIXt"), tzone = "MST"), dusk = structure(c(1588388024, 1589252706, 1589425639, 1589425639, 1590030886, 1625715264), class = c("POSIXct", "POSIXt"), tzone = "MST")), class = "data.frame", row.names = c(NA, 6L))
原数据格式化代码
md$Time <- paste0(md$Year, "-", md$Month, "-", md$Day, " ", md$Time.of.Capture,":00") md$Time <- ymd_hms(md$Time) md$Date.of.Capture <- paste0(md$Year, "-", md$Month, "-", md$Day) md$Date.of.Capture <- as.Date(md$Date.of.Capture, format = "%Y-%m-%d", tz = "MST") timesofday <- getSunlightTimes(date = md$Date.of.Capture, lat = 40.753360, lon = -111.624088, tz="MST", keep=c("sunriseEnd", "sunsetStart", "dawn", "dusk")) md <- left_join(md, timesofday, by = c("Date.of.Capture" = "date"))
原时段判断代码
md$ToD<-NA for(i in 1: nrow(md)){ if (md$Time[i] > md$dawn[i] & md$Time[i] < md$sunriseEnd[i]){ md$ToD[i] <- "Dawn" } else if (md$Time[i] > md$sunsetStart[i] & md$Time[i] < md$dusk[i]){ md$ToD[i] <- "Dusk" } else if (md$Time[i] > md$dusk[i] & md$Time[i] < md$dawn[i]){ md$ToD[i] <- "Nighttime" } else if (md$Time[i] > md$sunriseEnd[i] & md$Time[i] < md$sunsetStart[i]){ md$ToD[i] <- "Daytime" } } unique(md$ToD)
问题根源
- 时区不统一:
Time列是UTC时区,日出日落列是MST时区(比UTC晚7小时)。直接对比时,底层时间戳数值差7小时,导致大部分判断条件不成立。比如第一行UTC时间2020-05-01 06:24转换为MST是2020-04-30 23:24,和当天MST的dawn(2020-05-01 05:55)对比完全不在区间内。 - Nighttime逻辑错误:同一天的
dusk时间戳必然大于dawn,Time > dusk & Time < dawn的条件永远无法满足,忽略了跨天的夜晚时段。
修正方案
步骤1:统一时区
将Time列转换为MST时区,与日出日落时间保持一致:
md$Time <- with_tz(md$Time, tzone = "MST")
步骤2:修正时段判断逻辑
改用dplyr::case_when简化代码,同时修正Nighttime的跨天判断:
md <- md %>% mutate( ToD = case_when( between(Time, dawn, sunriseEnd) ~ "Dawn", between(Time, sunriseEnd, sunsetStart) ~ "Daytime", between(Time, sunsetStart, dusk) ~ "Dusk", # 夜晚包含:当天黄昏到午夜、午夜到次日黎明 Time > dusk | Time < dawn ~ "Nighttime", TRUE ~ NA_character_ ) )
完整修正代码
library(lubridate) library(dplyr) library(suncalc) # 优化数据格式化 md$Time <- ymd_hms(paste(md$Year, md$Month, md$Day, md$Time.of.Capture, sep = " "), tz = "UTC") md$Date.of.Capture <- ymd(paste(md$Year, md$Month, md$Day, sep = "-")) timesofday <- getSunlightTimes(date = md$Date.of.Capture, lat = 40.753360, lon = -111.624088, tz="MST", keep=c("sunriseEnd", "sunsetStart", "dawn", "dusk")) md <- left_join(md, timesofday, by = c("Date.of.Capture" = "date")) # 统一时区为MST md$Time <- with_tz(md$Time, tzone = "MST") # 修正时段判断 md <- md %>% mutate( ToD = case_when( between(Time, dawn, sunriseEnd) ~ "Dawn", between(Time, sunriseEnd, sunsetStart) ~ "Daytime", between(Time, sunsetStart, dusk) ~ "Dusk", Time > dusk | Time < dawn ~ "Nighttime", TRUE ~ NA_character_ ) ) # 查看结果 md %>% select(Time, dawn, sunriseEnd, sunsetStart, dusk, ToD)
验证结果
修正后6行数据的ToD列将得到正确分类:
- 第1行:
Dawn(06:24在05:55-06:28区间内) - 第2行:
Dawn(06:27在05:53-06:28区间内) - 第3-4行:
Daytime(08:55在06:25-20:13区间内) - 第5行:
Dusk(20:22在20:19-20:48区间内) - 第6行:
Dawn(06:26等于sunriseEnd,若需包含边界可将between改为Time >= dawn & Time <= sunriseEnd)
内容的提问来源于stack exchange,提问作者BlakeLedbet
相关产品推荐
相关产品推荐

