【问题标题】:R data.table add column as function of another data.tableR data.table 添加列作为另一个 data.table 的函数
【发布时间】:2018-02-08 21:53:39
【问题描述】:

我有一个只包含一系列时间的数据表。我有另一个包含两列的数据表:start_time 和 end_time。我想获取第一个数据表并添加一个列,其中的值是第二个数据表中所有行的计数,其中第一个数据表的时间适合开始和结束时间。这是我的代码

start_date <- as.POSIXct(x = "2017-01-31 17:00:00", format = "%Y-%m-%d %H:%M:%S")
end_date <- as.POSIXct(x = "2017-02-01 09:00:00", format = "%Y-%m-%d %H:%M:%S")

all_dates <- as.data.table(seq(start_date, end_date, "min"))

colnames(all_dates) <- c("Bin")

start_times <- sample(seq(start_date,end_date,"min"), 100)
offsets <- sample(seq(60,7200,60), 100)
end_times <- start_times + offsets
input_data <- data.table(start_times, end_times)

这是我想要做的,但这是错误的并给出了错误。写这个的正确方法是什么?

all_dates[, BinCount := input_data[start_times < Bin & end_times > Bin, .N] ]

最后我应该得到类似的东西

Bin                   BinCount
2017-01-31 17:00:00   1
2017-01-31 17:01:00   5
...

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    使用sqldf 可以很容易地解决这个问题,因为它提供了通过范围检查连接表的简单方法。因此,一种解决方案可能是:

    The data from OP:
    library(data.table)
    start_date <- as.POSIXct(x = "2017-01-31 17:00:00", format = "%Y-%m-%d %H:%M:%S")
    end_date <- as.POSIXct(x = "2017-02-01 09:00:00", format = "%Y-%m-%d %H:%M:%S")
    
    all_dates <- as.data.table(seq(start_date, end_date, "min"))
    
    colnames(all_dates) <- c("Bin")
    
    start_times <- sample(seq(start_date,end_date,"min"), 100)
    offsets <- sample(seq(60,7200,60), 100)
    end_times <- start_times + offsets
    input_data <- data.table(start_times, end_times)
    
    
    library(sqldf)
    
    result <- sqldf("SELECT all_dates.bin, count() as BinCount 
                    FROM all_dates, input_data
                     WHERE all_dates.bin > input_data.start_times AND 
                     all_dates.bin < input_data.end_times
                     GROUP BY bin" )
    
    result
                        Bin BinCount
    1   2017-01-31 17:01:00        1
    2   2017-01-31 17:02:00        1
    3   2017-01-31 17:03:00        1
    4   2017-01-31 17:04:00        1
    5   2017-01-31 17:05:00        1
    6   2017-01-31 17:06:00        1
    ...........
    ...........
    497 2017-02-01 01:17:00        6
    498 2017-02-01 01:18:00        5
    499 2017-02-01 01:19:00        5
    500 2017-02-01 01:20:00        4
     [ reached getOption("max.print") -- omitted 460 rows ]
    

    【讨论】:

    • 谢谢 - 我也使用 for 循环解决了它(但它很丑).. 我希望有一种很好的方法可以直接在数据表语法中执行此操作
    • data.table 也需要类似的语法。显然 for 循环会很慢,但 sqldfdata.table 的性能是相当的。
    • 使用between可以得到SQL语句的小幅缩短。
    【解决方案2】:

    data.table 中,您正在执行范围连接。

    library(data.table)
    
    start_date <- as.POSIXct(x = "2017-01-31 17:00:00", format = "%Y-%m-%d %H:%M:%S")
    end_date <- as.POSIXct(x = "2017-02-01 09:00:00", format = "%Y-%m-%d %H:%M:%S")
    
    all_dates <- as.data.table(seq(start_date, end_date, "min"))
    
    colnames(all_dates) <- c("Bin")
    
    set.seed(123)
    start_times <- sample(seq(start_date,end_date,"min"), 100)
    offsets <- sample(seq(60,7200,60), 100)
    end_times <- start_times + offsets
    input_data <- data.table(start_times, end_times)
    
    ## doing the range-join and calculating the number of items per bin in one chained step
    input_data[
        all_dates
        , on = .(start_times < Bin, end_times > Bin)
        , nomatch = 0
        , allow.cartesian = T
    ][, .N, by = start_times]
    
    #             start_times N
    # 1:  2017-01-31 17:01:00 1
    # 2:  2017-01-31 17:02:00 1
    # 3:  2017-01-31 17:03:00 1
    # 4:  2017-01-31 17:04:00 1
    # 5:  2017-01-31 17:05:00 1
    # ---                      
    # 956: 2017-02-01 08:56:00 6
    # 957: 2017-02-01 08:57:00 4
    # 958: 2017-02-01 08:58:00 4
    # 959: 2017-02-01 08:59:00 5
    # 960: 2017-02-01 09:00:00 5
    

    注意:

    • 我已将all_dates 对象放在连接的右侧,因此结果包含input_data 列的名称,即使它们是您的Bin(参见this issue 的讨论)关于这个话题)
    • 我用过set.seed(),因为你正在取样

    【讨论】:

    • 非常干净的解决方案。你很快:) 在得到 OP 的反馈后,我开始使用基于 data.table 的解决方案。
    • 谢谢 - 我经常使用这种方法。它与您的 SQL 方法非常相似,但写成 data.table
    • 是的。语法相似,然后取决于个人选择。我认为最好以两种方式检查性能。如果基础data.table 被索引,IMO,sqldf 将同样快。
    • 谢谢 - 这正是我想要的!
    【解决方案3】:

    没有要求,但这里有一个使用tidyverse 的紧凑型替代解决方案。使用 lubridate 解析器、interval%within% 以及 purrr::map_int 生成所需的 bin 计数。

    library(tidyverse)
    library(lubridate)
    start_date <- ymd_hms(x = "2017-01-31 17:00:00") # lubridate parsers
    end_date <- ymd_hms(x = "2017-02-01 09:00:00")
    
    all_dates <- tibble(seq(start_date, end_date, "min")) # tibble swap for data.table
    
    colnames(all_dates) <- c("Bin")
    
    start_times <- sample(seq(start_date,end_date,"min"), 100)
    offsets <- sample(seq(60,7200,60), 100)
    end_times <- start_times + offsets
    input_data <- tibble(
      start_times,
      end_times,
      intvl = interval(start_times, end_times) # Add interval column
      )
    
    all_dates %>% # Checks date in Bin and counts intervals it lies within
      mutate(BinCount = map_int(.$Bin, ~ sum(. %within% input_data$intvl)))
    # A tibble: 961 x 2
       Bin                 BinCount
       <dttm>                 <int>
     1 2017-01-31 17:00:00        0
     2 2017-01-31 17:01:00        0
     3 2017-01-31 17:02:00        0
     4 2017-01-31 17:03:00        0
     5 2017-01-31 17:04:00        0
     6 2017-01-31 17:05:00        0
     7 2017-01-31 17:06:00        0
     8 2017-01-31 17:07:00        1
     9 2017-01-31 17:08:00        1
    10 2017-01-31 17:09:00        1
    # ... with 951 more rows
    

    【讨论】:

    • 总是很高兴看到做同样事情的替代方法,谢谢!
    猜你喜欢
    • 1970-01-01
    • 2017-02-23
    • 2012-07-03
    • 1970-01-01
    • 2021-11-27
    • 2020-07-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多