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

如何在data.table中无循环替换特定行的changed_time值?

高效替换data.table中指定行的changed_time值

问题背景

我有一个大型data.table,部分数据如下(实际包含更多列和数千行):

stop_id   path changed_event_status changed_time
                  <i64> <char>               <char>        <i64>
 1: 4398037956893976209      S                 <NA>   2405071040
 2: 1500925206899141237     RT                 <NA>   2405071041
 3: 2333532852925690131      S                 <NA>   2405071105
 4: 4636036529075799544     TÜ                 <NA>   2405071044
 5: 4680830034956468939      S                 <NA>   2405071046
 6: 7584560746915960683      S                    c   2405071049  <- 1a: 替换2405071049
 7: 2333532852925690131     RT                 <NA>   2405071116
 8: 4747322524233582527      S                 <NA>   2405071100  <- 1b: 替换为2405071100
 9:  285273127640529713      S                 <NA>   2405071103
10: 6134967434625106066      S                 <NA>   2405071101
11: 3684003552999415659     RT                 <NA>   2405071103  <- 2b: 替换为2405071103
12: 7584560746915960683     RT                    c   2405071058  <- 2a: 替换2405071058
13: 4680830034956468939     TÜ                 <NA>   2405071103
14: 8123621717351038368      S                 <NA>   2405071113
15: 8702942397103782624     TÜ                 <NA>   2405071114
16: 6134967434625106066     TÜ                 <NA>   2405071114
17: 4138386908727054325      S                 <NA>   2405071115
18:  285273127640529713     RT                 <NA>   2405071123
19: 2445758245483744446      S                 <NA>   2405071119
20: 8153934371487726263     TÜ                 <NA>   2405071132
21: 4138386908727054325     RT                 <NA>   2405071126
22:  310332233182112225      S                 <NA>   2405071127
                stop_id   path changed_event_status changed_time

需求说明

对于所有changed_event_status == "c"的行,需要在相同path的所有行中,找到changed_time列里大于等于当前行changed_time的最小值,并用该值替换当前行的changed_time。比如:

  • 第6行的2405071049要替换为第8行的2405071100
  • 第12行的2405071058要替换为第11行的2405071103

我已经用循环实现了需求,但循环效率太低,希望找到无需循环的高效解决方案。

示例数据

library(data.table)
bahn <- fread("\nstop_id path changed_event_status changed_time\n4398037956893976209 S NA 2405071040\n1500925206899141237 RT NA 2405071041\n2333532852925690131 S NA 2405071105\n4636036529075799544 TÜ NA 2405071044\n4680830034956468939 S NA 2405071046\n7584560746915960683 S c 2405071049\n2333532852925690131 RT NA 2405071116\n4747322524233582527 S NA 2405071100\n285273127640529713 S NA 2405071103\n6134967434625106066 S NA 2405071101\n3684003552999415659 RT NA 2405071103\n7584560746915960683 RT c 2405071058\n4680830034956468939 TÜ NA 2405071103\n8123621717351038368 S NA 2405071113\n8702942397103782624 TÜ NA 2405071114\n6134967434625106066 TÜ NA 2405071114\n4138386908727054325 S NA 2405071115\n285273127640529713 RT NA 2405071123\n2445758245483744446 S NA 2405071119\n8153934371487726263 TÜ NA 2405071132\n4138386908727054325 RT NA 2405071126\n310332233182112225 S NA 2405071127\n")

循环解决方案(低效)

for(i in 1:nrow(bahn)) {
    if(!is.na(bahn[i, changed_event_status]) & bahn[i, changed_event_status] == "c") {
        bahn[i, ]$changed_time <- sort(
                                       bahn[
                                            is.na(changed_event_status)
                                            &
                                            changed_time >= bahn[i, changed_time]
                                            &
                                            path == bahn[i, path]
                                           ]$changed_time
                                      )[1]
    }
}

高效的data.table解决方案

利用data.table的分组和非等值连接特性,可以高效完成需求,无需循环,以下提供两种实用写法:

写法1:非等值连接+.EACHI分组

这种写法直接通过连接匹配条件,对每一行目标行单独计算最小值,代码简洁高效:

# 先提取所有有效行(changed_event_status为NA)
valid_rows <- bahn[is.na(changed_event_status), .(path, valid_time = changed_time)]

# 对目标行执行非等值连接,替换changed_time
bahn[changed_event_status == "c", 
     changed_time := valid_rows[.SD, on = .(path, valid_time >= changed_time), 
                                min(valid_time), by = .EACHI]$V1]

写法2:预排序+findInterval定位

对于超大型数据集,先按path预存排序后的有效时间,再用findInterval快速定位最小值,性能更优:

# 按path分组,预存排序后的有效changed_time
bahn[, sorted_valid := list(sort(changed_time[is.na(changed_event_status)])), by = path]

# 对目标行替换:找到第一个大于等于当前时间的最小值
bahn[changed_event_status == "c", 
     changed_time := sorted_valid[[1]][findInterval(changed_time, sorted_valid[[1]]) + 1], 
     by = path]

# 清理临时列
bahn[, sorted_valid := NULL]

方案解释

  • 写法1利用data.table的非等值连接能力,通过on = .(path, valid_time >= changed_time)匹配同path下时间不小于当前值的行,再用by = .EACHI对每一行目标行取最小值。
  • 写法2先对每个path的有效时间做一次排序,后续用findInterval快速定位当前时间在排序数组中的位置,直接取下一个元素就是符合要求的最小值,排序仅执行一次,适合数据量极大的场景。

验证结果

运行上述任意一种高效代码后,目标行的changed_time会按需求替换:

  • 第6行的changed_time变为2405071100
  • 第12行的changed_time变为2405071103

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 13:09:51