【问题标题】:Fastest way for filling-in missing dates for data.table (cont.)为 data.table 填写缺失日期的最快方法(续)
【发布时间】:2019-03-06 16:42:30
【问题描述】:

我正在寻找一种有效且快速的方法来填充缺失日期的表格中的缺失数据。

library(data.table)
dt <- as.data.table(read.csv(textConnection('"date","gr1","gr2","x"
                                            "2017-01-01","A","a",1
                                            "2017-02-01","A","b",2
                                            "2017-02-01","B","a",4
                                            "2017-04-01","B","a",5
                                            "2017-05-01","A","b",3')))
dt[,date := as.Date(date)] 

假设此表包含dategr1gr2 组的所有x 信息。我想通过gr1gr2 重复x 的最后一个已知值来填充缺失的日期并扩展此表。我的做法如下:

# define the period to expand
date_min <- as.Date('2017-01-01')
date_max <- as.Date('2017-06-01')
dates <- setDT(list(ddate = seq.Date(date_min, date_max,by = 'month')))

# cast the data
dt.c <- dcast(dt, date~gr1+gr2, value.var = "x")
# fill missing dates
dt.c <- dt.c[dates, roll=Inf]

# melt the data to return to original table format
dt.m <- melt(dt.c, id.vars = "date", value.name = "x")

# split column - the slowest part of my code
dt.m[,c("gr1","gr2") := tstrsplit(variable,'_')][,variable:=NULL]

# remove unnecessary NAs
dt.m <- dt.m[complete.cases(dt.m[,x])][,.(date,gr1,gr2,x)]
setkey(dt.m)

这是我希望看到的输出:

> dt.m
         date gr1 gr2 x
1: 2017-01-01   A   a 1
2: 2017-02-01   A   b 2
3: 2017-02-01   B   a 4
4: 2017-03-01   A   b 2
5: 2017-03-01   B   a 4
6: 2017-04-01   B   a 5
7: 2017-05-01   A   b 3
8: 2017-06-01   A   b 3

现在的问题是tstrsplit 在包含很多组的大型数据集上非常慢。

This 方法非常接近我的需要,但如果我遵循它,我将无法获得所需的输出,因为它不仅填充了缺失的日期,还填充了 NA。这是我对示例的修改:

# the desired dates by group
date_min <- as.Date('2017-01-01')
date_max <- as.Date('2017-06-01')
indx <- dt[,.(date=seq(date_min,date_max,"months")),.(gr1,gr2)]

# key the tables and join them using a rolling join
setkey(dt,gr1,gr2,date)
setkey(indx,gr1,gr2,date)
dt0 <- dt[indx,roll=TRUE][,.(date,gr1,gr2,x)]
setkey(dt0,date)

这不是我期望看到的输出:

> dt0
          date gr1 gr2  x
 1: 2017-01-01   A   a  1
 2: 2017-01-01   A   b NA
 3: 2017-01-01   B   a NA
 4: 2017-02-01   A   a  1
 5: 2017-02-01   A   b  2
 6: 2017-02-01   B   a  4
 7: 2017-03-01   A   a  1
 8: 2017-03-01   A   b  2
 9: 2017-03-01   B   a  4
10: 2017-04-01   A   a  1
11: 2017-04-01   A   b  2
12: 2017-04-01   B   a  5
13: 2017-05-01   A   a  1
14: 2017-05-01   A   b  3
15: 2017-05-01   B   a  5
16: 2017-06-01   A   a  1
17: 2017-06-01   A   b  3
18: 2017-06-01   B   a  5

重现我上面的输出 (dt.m) 的最佳(最快)方法是什么?

【问题讨论】:

    标签: r date data.table


    【解决方案1】:

    在滚动连接时,一个“正常”连接和一些列切换,然后你就完成了 :)

    temp <- dates[, near.date := dt[dates, x.date, on = .(date=ddate), roll = TRUE, mult = "first"]][]
    dt[temp, on = .(date = near.date)][, date := ddate][,ddate := NULL][]
    
    #          date gr1 gr2 x
    # 1: 2017-01-01   A   a 1
    # 2: 2017-02-01   A   b 2
    # 3: 2017-02-01   B   a 4
    # 4: 2017-03-01   A   b 2
    # 5: 2017-03-01   B   a 4
    # 6: 2017-04-01   B   a 5
    # 7: 2017-05-01   A   b 3
    # 8: 2017-06-01   A   b 3
    

    您(当然)可以通过将第一行整合到最后一行来使其成为单行。

    【讨论】:

    • 谢谢。这似乎是解决问题的最快方法。
    【解决方案2】:

    我会使用 IDate 和一个整数计数器来计算日期序列:

    dt[, date := as.IDate(date)]
    dates = seq(as.IDate("2017-01-01"), as.IDate("2017-06-01"), by="month")
    dDT = data.table(date = dates)[, dseq := .I][]
    
    dt[dDT, on=.(date), dseq := i.dseq]
    

    然后枚举所有需要的组合(gr1、gr2、dseq)并进行几个更新连接:

    cDT = CJ(dseq = dDT$dseq, gr1 = unique(dt$gr1), gr2 = unique(dt$gr2))
    
    cDT[, x := dt[cDT, on=.(gr1, gr2, dseq), x.x]]
    cDT[is.na(x), x := dt[copy(.SD), on=.(gr1, gr2, dseq), roll=1L, x.x]]
    
    res = cDT[!is.na(x)]
    res[dDT, on=.(dseq), date := i.date]
    
        dseq gr1 gr2 x       date
     1:    1   A   a 1 2017-01-01
     2:    2   A   a 1 2017-02-01
     3:    2   A   b 2 2017-02-01
     4:    2   B   a 4 2017-02-01
     5:    3   A   b 2 2017-03-01
     6:    3   B   a 4 2017-03-01
     7:    4   B   a 5 2017-04-01
     8:    5   A   b 3 2017-05-01
     9:    5   B   a 5 2017-05-01
    10:    6   A   b 3 2017-06-01
    

    与 OP 的预期相比,这里多出了两行

    res[!dt.m, on=.(date, gr1, gr2)]
    
       dseq gr1 gr2 x       date
    1:    2   A   a 1 2017-02-01
    2:    5   B   a 5 2017-05-01
    

    因为我正在独立处理每个缺失的 gr1 x gr2 值,而不是在日期根本不在 dt 中时填充它(就像在 OP 中一样)。要应用该规则...

    drop_rows = res[!dt, on=.(gr1,gr2,date)][date %in% dt$date, .(gr1,gr2,date)]
    res[!drop_rows, on=names(drop_rows)]
    

    (需要copy(.SD),因为likely bug。)

    【讨论】:

    • 谢谢。正如你提到的,你的 OP 有额外的行,它不能解决我的问题。你能纠正它,使它不包括额外的行吗?请对照@Wimpel 解决方案对其进行基准测试。
    • @Svilen 好的,我已经更正了。我很确定 Wimpel 的方法更有效,并且该示例无法扩展到大尺寸(通常,您希望创建一个可以根据 n 或 OP 中的某个函数进行扩展的示例),所以我不这样做'没有基准。
    【解决方案3】:

    dt 对于 gr* 的每个组合的所有唯一 date 都应该有 NA,但没有出现。因此,我们使用CJ 和一个连接来用 NA for x 填充那些缺失的日期。

    之后,为所有必需的ddates 展开数据集。

    最后,过滤掉 x 为 NA 的行并按日期排序,以使输出具有与原始 dt 相同的特征。

    dt[, g := .GRP, .(gr1, gr2)][
        CJ(date=date, g=g, unique=T), on=.(date, g)][, 
            .SD[.(date=ddate), on=.(date), roll=Inf], .(g)][
                !is.na(x)][order(date)]
    

    输出:

       g       date gr1 gr2 x
    1: 1 2017-01-01   A   a 1
    2: 2 2017-02-01   A   b 2
    3: 3 2017-02-01   B   a 4
    4: 2 2017-03-01   A   b 2
    5: 3 2017-03-01   B   a 4
    6: 3 2017-04-01   B   a 5
    7: 2 2017-05-01   A   b 3
    8: 2 2017-06-01   A   b 3
    

    数据:

    library(data.table)
    dt <- fread('date,gr1,gr2,x
        2017-01-01,A,a,1
        2017-02-01,A,b,2
        2017-02-01,B,a,4
        2017-04-01,B,a,5
        2017-05-01,A,b,3')
    dt[,date := as.Date(date)] 
    
    date_min <- as.Date('2017-01-01')
    date_max <- as.Date('2017-06-01')
    ddate = seq.Date(date_min, date_max,by = 'month')
    

    请尝试您的实际数据集。

    【讨论】:

    • 谢谢。该解决方案还可以,但与我的数据集上的@Wimpel 方法相比要慢得多。
    【解决方案4】:

    这与另一个问题有点相似,但请注意重复。该方法类似,但具有 data.tables 和多个列。另见:Fill in missing date and fill with the data above

    在这里,不清楚您是要填写 gr2 和 x 列还是 gr2 在做什么。我假设您正在寻找以 1 个月为增量的日期来填补空白。此外,由于输入数据的最大月份为 5(5 月),因此示例所需的输出一直到 6(6 月),因此如果目标是在输入日期之间填写,则不清楚如何达到 6 月——但如果有外部最大值,这可以设置而不是输入日期的最大值

    library(data.table)
    library(tidyr)
    dt <- as.data.table(read.csv(textConnection('"date","gr1","gr2","x"
                                                "2017-01-01","A","a",1
                                                "2017-02-01","A","b",2
                                                "2017-02-01","B","a",4
                                                "2017-04-01","B","a",5
                                                "2017-05-01","A","b",3')))
    dt[,date := as.Date(date)] 
    setkeyv(dt,"date")
    
    all_date_groups <- dt[,list(date=seq.Date(from=min(.SD$date),to=max(.SD$date),by="1 month")),by="gr1"]
    setkeyv(all_date_groups,"date")
    
    all_dates_dt <- dt[all_date_groups,on=c("date","gr1")]
    setorderv(all_dates_dt,c("gr1","date"))
    
    all_dates_dt <- fill(all_dates_dt,c("gr2","x"))
    setorderv(all_dates_dt,c("date","gr1"))
    all_dates_dt
    

    结果:

    > all_dates_dt
             date gr1 gr2 x
    1: 2017-01-01   A   a 1
    2: 2017-02-01   A   b 2
    3: 2017-02-01   B   a 4
    4: 2017-03-01   A   b 2
    5: 2017-03-01   B   a 4
    6: 2017-04-01   A   b 2
    7: 2017-04-01   B   a 5
    8: 2017-05-01   A   b 3
    

    【讨论】:

    • 谢谢。很抱歉我的问题不清楚。实际上,我使用gr1gr2 作为每个x 的属性,所以最后我想重复前一天所有x 的值,无论它是否是数据集中日期之间的间隔未来的日子。另外,我不想用前一天不存在的x 来填补缺失的一天。在您的情况下,解决方案无法正确填写未来日期。如果您决定更正它,请将其与 @Wimpel 解决方案进行基准测试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-24
    • 2018-10-05
    • 2019-06-26
    • 1970-01-01
    • 1970-01-01
    • 2018-07-15
    相关资源
    最近更新 更多