【问题标题】:Merging values from 2 different datasets within a certain time interval into a single dataset (R)将特定时间间隔内来自 2 个不同数据集的值合并到单个数据集 (R)
【发布时间】:2020-02-19 05:58:03
【问题描述】:

我有两个独立的数据集:df1 和 df2。我想创建一个新的数据集 df3,如果日期时间在 20 秒内,它将匹配 df1 的 endtime 列和 df2 的发送列。

 df1

 endtime                     ID

 1/7/2020  1:35:08 AM         A
 1/7/2020  1:39:00 AM         B
 1/20/2020 1:45:00 AM         C



 df2

sent                         ID

1/7/2020  1:35:20 AM          E
1/7/2020  1:42:00 AM          F
1/20/2020 1:55:00 AM          G
1/20/2020 2:00:00 AM          E

这是我想要的 df3 输出。只有一行,因为只有两个值符合 endtime 和 sent 列的 20 秒内的条件。

endtime                  sent 

1/7/2020 1:35:08 AM      1/7/2020  1:35:20 AM       

这是输出:

df1

structure(list(endtime = structure(c(2L, 3L, 1L), .Label = c("1/10/2020 1:45:00 AM", 
"1/7/2020 1:35:08 AM", "1/7/2020 1:39:00 AM"), class = "factor"), 
ID = structure(1:3, .Label = c("A", "B", "C"), class = "factor")), class = "data.frame", row.names =   c(NA, 
 -3L))





 df2

 structure(list(sent = structure(c(3L, 4L, 1L, 2L), .Label = c("1/20/2020 1:55:00 AM", 
 "1/20/2020 2:00:00 AM", "1/7/2020 1:35:20 AM", "1/7/2020 1:42:00 AM"
 ), class = "factor"), ID = structure(c(1L, 2L, 3L, 1L), .Label = c("E", 
"F", "G"), class = "factor")), class = "data.frame", row.names = c(NA, 
-4L))

这是我尝试过的:

我正在考虑执行左连接并匹配值,或者我可以使用 merge(),但棘手的部分是将值与条件语句匹配。任何建议表示赞赏。

library(dplyr)
left_join(df1, df2)

【问题讨论】:

    标签: r merge dplyr lubridate stringr


    【解决方案1】:

    由于没有要连接的公共列,我们可以使用 crossing 创建所有行组合,然后使用 filter 创建符合条件的行组合。

    library(dplyr)
    
    df1 %>%
      rename(ID1 = 'ID') %>%
      tidyr::crossing(df2) %>%
      mutate_at(vars(endtime, sent), lubridate::mdy_hms) %>%
      filter(abs(difftime(sent, endtime, 'secs')) < 20)
    
    #  endtime             ID1   sent                ID   
    #  <dttm>              <fct> <dttm>              <fct>
    #1 2020-01-07 01:35:08 A     2020-01-07 01:35:20 E    
    

    【讨论】:

    • 有没有办法显示不符合条件的数据? (几乎像反加入?@Ronak
    • 如果您只是将符号从 &lt; 更改为 &gt;,它应该会给您。
    • 嗨@Ronak 这适用于大约 1000 行的数据集吗?以及如何避免“重复”。我想要独特的匹配,不重复
    • 是的,这适用于 1000 行的数据集。您可以在末尾使用uniquedistinct 来获取唯一行。
    • 好的,当我更改操作员符号时,我没有得到不匹配的标准,我不确定我做错了什么@ronak
    【解决方案2】:

    如果您的数据集太大而无法生成笛卡尔积,您也可以这样做:

    df1 %>% 
        split(1:NROW(.)) %>% 
        map( ~merge(.x,
                    df2[ abs(difftime(df2$sent, .x$endtime, units='s')) < 20, ],
                    by=NULL) ) %>%
        bind_rows()
    

    编辑

    TLDR

    使用non-equi join from data.table,整体性能最好。

    dt1 = as.data.table(df1)
    dt2 = as.data.table(df2)
    
    dt1[, `:=`(endtime_min = endtime - 20, endtime_max = endtime + 20) ]
    dt1[dt2,
        .(ID, ID1, endtime, sent), 
        on = .(endtime_min < sent, endtime_max > sent), nomatch = 0L, allow.cartesian=T]
    

    加长版

    我发布的答案在数据帧太大的情况下会更好,因为首先执行交叉连接会产生一个数据帧,其行数与两个数据帧的行数的乘积一样多。通过先过滤后加入,避免了不必要的内存分配。但是,对于df1 的每一行,它都有开销,检查df2 中是否有任何行匹配。

    这个答案更好的另一个用例是当一个数据帧比另一个数据帧小得多时,即使它们没有那么大。我运行了一些基准来检查这一点。

    但是,在遇到 this answer 并在 data.table 中为 OP 提出的问题制作解决方案版本之后,没有一个答案与此实现的性能相比。

    我运行的测试使用了 OP 提供的数据集,为了模拟更大的数据集,我只是将这些数据集复制了一定次数。我做了 2 个测试:

    1. 复制两个数据集的次数相同
    2. 修复了df1 的大小并复制了df2

    对于每个测试,我测量了接受答案 (merge_filter)、我的原始答案 (filter_merge) 和 data.table 解决方案 (datatable) 的中位执行时间。

    在运行测试之前,我准备好 df1df2 以拥有正确的数据类型,并将列 IDdf1 重命名为 ID1。对于 data.table 解决方案,我将两个数据帧都转换为 data.tables 对应的 dt1dt2

    对于每一种方法,我都要做一些改变,主要是使用merge(..., by=NULL)而不是crossing(...),因为最后一种方法不支持交叉连接重复行,从结果数据集中删除所有重复行。

    这是我用来运行测试的代码:

    library(tidyverse)
    library(data.table)
    
    run_test = function(n, n1=n, n2=n) {
        df1 = bind_rows(rep(list(df1_op), n1))
        df2 = bind_rows(rep(list(df1_op), n2))
        dt1 = as.data.table(df1)
        dt2 = as.data.table(df2)
    
        microbenchmark::microbenchmark(
            merge_filter = df1 %>%
                merge(df2, by=NULL) %>%
                filter(abs(difftime(sent, endtime, 'secs')) < 20),
    
            filter_merge = df1 %>% 
                split(1:NROW(.)) %>% 
                map(~merge( .x,
                            df2[ abs(difftime(df2$sent, .x$endtime, units='s')) < 20, ],
                            by=NULL) ) %>%
                bind_rows(),
    
            datatable={
                dt1[, `:=`(endtime_min = endtime - 20, endtime_max = endtime + 20) ]
                dt1[dt2,
                    .(ID, ID1, endtime, sent), 
                    on = .(endtime_min < sent, endtime_max > sent), nomatch = 0L, allow.cartesian=T]
            }
        )
    }
    
    test_1_list = list()
    for( n in c(1, 2, 5, 10, 20, 50, 100, 200, 500) ) {
        test_1_list[[ toString(n) ]] <- run_test(n)
    }
    
    test_2_list = list()
    for( n in c(1, 2, 5, 10, 20, 50, 100, 200, 500, 
                1000, 2000, 5000, 10000, 20000, 50000) ) {
        test_2_list[[ toString(n) ]] <- run_test(n, n1=1)
    }
    

    下面分别是测试 1 和 2 的结果:

    编辑 2

    您可以像这样进行非 equi 左连接:

    filter_merge

    df1 %>% 
        split(1:NROW(.)) %>% 
        map( ~merge(mutate(.x, k=1),
                    df2 %>%
                        filter( abs(difftime(df2$sent, .x$endtime, units='s')) < 20 ) %>%
                        mutate(k=1),
                    by="k",
                    all.x=T) %>%
                select(-k) ) %>%
        bind_rows() %>%
        select(ID1, endtime, ID, sent)
    
    #   ID1             endtime   ID                sent
    # 1   A 2020-01-07 01:35:08    E 2020-01-07 01:35:20
    # 2   B 2020-01-07 01:39:00 <NA>                <NA>
    # 3   C 2020-01-10 01:45:00 <NA>                <NA>
    

    datatable

    dt1[, `:=`(endtime_min = endtime - 20, endtime_max = endtime + 20) ]
    dt2[dt1,
        .(i.ID1, i.endtime, x.ID, x.sent), 
        on = .(sent > endtime_min, sent < endtime_max), allow.cartesian=T]
    
    #    i.ID1           i.endtime x.ID              x.sent
    # 1:     A 2020-01-07 01:35:08    E 2020-01-07 01:35:20
    # 2:     B 2020-01-07 01:39:00 <NA>                <NA>
    # 3:     C 2020-01-10 01:45:00 <NA>                <NA>
    

    【讨论】:

    • 您好,谢谢,如果数据集太大,第一个解决方案不起作用?你能告诉我极限吗?谢谢
    • 哦,哇,谢谢你。有趣的!我来分析一下! @MkWTF,有没有办法揭示不符合标准的数据? (几乎像反加入?)
    • 我认为您只需要在任何方法中反转连接条件即可实现这一点,除了 data.table 解决方案。对于最后一个,您可能需要进行两次连接(一个用于小于 min 的值,另一个用于大于 max 的值),然后将结果绑定在一起。
    • 更正:不要反转连接条件,而是取反。
    • 我添加了左连接的解决方案。抱歉@TanishaHudson 我忘记了这个要求
    猜你喜欢
    • 2020-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-06
    • 1970-01-01
    • 2021-10-30
    • 2023-03-21
    相关资源
    最近更新 更多