You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用R更高效统计各年月区间内重叠的数据行数?

问题描述

我有如下R数据集:

library(ggplot2)
library(dplyr)
library(lubridate)

set.seed(123)
mydf <- data.frame(
  name = sample(LETTERS, 10, replace = TRUE),
  start_date = as.Date("2022-01-01") + sample(0:500, 10, replace = TRUE),
  end_date = as.Date("2022-01-01") + sample(200:700, 10, replace = TRUE)
)

数据集示例:

> head(mydf)
  name start_date   end_date
1    O 2022-01-14 2023-07-02
2    S 2023-01-09 2022-12-03
3    N 2022-06-02 2023-11-08
4    C 2022-03-31 2023-07-09
5    J 2022-04-01 2023-06-12
6    R 2022-09-13 2022-08-14

我需要统计最小start_date到最大end_date之间的每个年月区间内,与该区间重叠的数据行数(即start_date <= 区间起始且end_date >= 区间起始的行),并已手动实现该功能:

min_date <- min(mydf$start_date)
max_date <- max(mydf$end_date)

month_seq <- seq(from = floor_date(min_date, "month"), 
                 to = floor_date(max_date, "month"), 
                 by = "month")

valid_counts <- data.frame(month = month_seq) %>%
  rowwise() %>%
  mutate(valid_count = sum(mydf$start_date <= month & mydf$end_date >= month)) %>%
  ungroup()

ggplot(valid_counts, aes(x = month, y = valid_count)) +
  geom_line(color = "blue", size = 1) +
  geom_point(color = "red", size = 2) +
  labs(title = "Number of Valid Rows Over Time",
       x = "Month",
       y = "Valid Count") +
  theme_minimal()

请问R中是否有更直接的预定义函数或更高效的方法实现该需求?


解决方案

有几种更高效且简洁的方法可以替代手动的逐行计算,主要利用向量化操作或专门的工具包:

1. 用dplyr+lubridate的向量化替代rowwise

你的手动实现用了rowwise(),数据量大时效率较低。可以改用向量化操作,直接对整个月份序列计算,避免逐行循环:

min_date <- floor_date(min(mydf$start_date), "month")
max_date <- floor_date(max(mydf$end_date), "month")
month_seq <- seq(min_date, max_date, by = "month")

# 向量化计算每个月份的有效行数
valid_counts <- tibble(month = month_seq) %>%
  mutate(valid_count = colSums(outer(month, mydf$start_date, `>=`) & outer(month, mydf$end_date, `<=`)))

这里用outer()生成月份与每个数据行的日期比较矩阵,再用colSums()统计符合条件的数量,效率比rowwise()高很多。

2. 使用fuzzyjoin包的模糊连接

fuzzyjoin包专门处理非精确匹配的连接,可以直接将月份序列与原数据集做模糊连接,再按月份计数:

library(fuzzyjoin)

valid_counts <- tibble(month = month_seq) %>%
  fuzzy_left_join(mydf, 
                  by = c("month" = "start_date", "month" = "end_date"),
                  match_fun = list(`>=`, `<=`)) %>%
  group_by(month) %>%
  summarise(valid_count = n())

这种方法逻辑直观,适合理解模糊匹配的场景。

3. 事件流累加方法(最高效)

把每个数据行的start_date标记为+1事件,end_date + 1天标记为-1事件,然后按日期排序累加,再按月聚合:

event_df <- bind_rows(
  mydf %>% transmute(date = floor_date(start_date, "month"), event = 1),
  mydf %>% transmute(date = floor_date(end_date + days(1), "month"), event = -1)
) %>%
  arrange(date) %>%
  group_by(date) %>%
  summarise(event = sum(event)) %>%
  complete(date = seq(min_date, max_date, by = "month"), fill = list(event = 0)) %>%
  mutate(valid_count = cumsum(event)) %>%
  select(month = date, valid_count)

这种方法时间复杂度为O(n log n),是数据量较大时的最优方案,避免了所有行与所有月份的两两比较。

注意事项

你的原数据中存在start_date > end_date的异常行(比如示例中的第2、6行),如果需要排除这类无效行,建议先过滤:

mydf_clean <- mydf %>% filter(start_date <= end_date)

内容的提问来源于stack exchange,提问作者stats_noob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 05:04:54