You need to enable JavaScript to run this app. R语言高效统计时间窗口内同物料同工厂的工单请求次数
优惠活动
大模型
产品
解决方案
定价
更多

R语言高效统计时间窗口内同物料同工厂的工单请求次数

问题本质

你当前逐行循环实现的性能瓶颈根因非常明确:

  • 每处理1行观测,都需要对全量700万行工单表做一次全表扫描过滤,整体时间复杂度为O(n²),按R的解释执行效率,确实会出现耗时按年计算的情况
  • 循环过程中逐次往空dataframe里写入结果,会反复申请内存做数据拷贝,进一步放大性能损耗
  • 循环内重复对日期字段做类型转换、对同维度字段做全量比对,没有预建索引、预排序的优化空间
  • 原代码本身存在逻辑偏差:过滤条件中(original_basic_start_date - wo.date_f < days.back)在满足“记录日期早于当前行日期”的前提下恒成立,实际并未实现30天回溯窗口的限制,会把所有历史上同物料同工厂的记录都统计进去
高效实现方案

方案1:data.table 实现(最适配千万行级规模的纯R计算场景)

data.table的分组、窗口计算逻辑是C层编译实现,没有R层面的循环开销,是当前R生态下大表计算效率最高的方案之一。
核心思路是先按「工厂+物料」分组,组内按日期预排序,再用自适应滚动窗口统计30天内的历史记录数,不需要重复扫描全表:

library(data.table)
library(lubridate)

# 转换为data.table格式,一次性完成日期类型转换
setDT(work.orders)
work.orders[, original_basic_start_date := as_date(original_basic_start_date)]

# 按分组维度、日期建排序索引,后续所有筛选计算都直接走索引
setkeyv(work.orders, c("plant", "material", "original_basic_start_date"))

days_back <- 30
# 分组计算回溯窗口的请求次数
work.orders[, total_resb_found := {
  # 组内生成全1的计数向量,用来累加窗口内的记录数
  count_vec <- rep(1, .N)
  # 对每一行,计算往前days_back天的窗口内累加值
  frollapply(
    x = count_vec,
    width = function(idx) {
      # 当前行的日期值
      curr_date <- .SD$original_basic_start_date[idx]
      # 统计满足:日期 >= 当前日期 - 30天 且 日期 < 当前日期 的记录数
      sum(.SD$original_basic_start_date >= (curr_date - days_back) & 
          .SD$original_basic_start_date < curr_date)
    },
    FUN = sum,
    adaptive = TRUE
  )
}, by = .(plant, material)]

性能参考:700万行规模下,这套逻辑全量计算耗时通常在1-3分钟区间,相比原实现是百万级的效率提升。

方案2:DuckDB 下推计算(内存占用最低的方案)

如果你的内存不足以支撑全量dataframe的分组计算,可以用DuckDB的列式执行引擎把计算下推到数据库层,内存占用仅为原R内存计算的1/10左右,且会自动生成最优执行计划:

library(dplyr)
library(duckdb)
library(lubridate)

# 建立本地内存数据库连接
con <- dbConnect(duckdb())
# 将数据写入数据库表,自动生成统计索引
work_orders_db <- copy_to(con, work.orders, name = "work_orders", overwrite = TRUE)

days_back <- 30
# 直接写窗口计算逻辑,由数据库引擎执行
result <- work_orders_db %>%
  mutate(original_basic_start_date = as_date(original_basic_start_date)) %>%
  group_by(plant, material) %>%
  arrange(original_basic_start_date) %>%
  mutate(
    total_resb_found = sql("
      COUNT(*) OVER (
        PARTITION BY plant, material
        ORDER BY original_basic_start_date
        -- 统计当前行往前30天到当前行之间的记录数
        RANGE BETWEEN INTERVAL 30 DAY PRECEDING AND CURRENT ROW
        -- 排除当前行本身,匹配原逻辑的日期小于当前行的要求
        EXCLUDE CURRENT ROW
      )
    ")
  ) %>%
  collect()

性能参考:700万行规模下,DuckDB的计算通常只需要几十秒,且不会因为数据规模扩大出现内存溢出的问题。

优化效果对比
  • 原实现:每一行做全表扫描,时间复杂度O(n²),无预排序、无索引
  • 优化后实现:分组后仅需对排序好的组内记录做窗口累加,时间复杂度O(n log n),全流程走预建索引
  • 额外减少的开销:一次性完成日期类型转换,不在循环内重复做类型计算;直接在原表上追加计算列,不反复构造空dataframe做内存拷贝

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.30 15:57:01