【问题标题】:How to merge two data frames in r by a common column with mismatched date/time values如何通过日期/时间值不匹配的公共列合并 r 中的两个数据框
【发布时间】:2023-03-06 18:18:01
【问题描述】:

我希望使用两个数据集都存在的“日期时间”列合并两个数据集(POSIXct 格式:dd/mm/yyyy hh:mm)。以下是来自两个数据集的示例数据:

# Dataset 1 (dts1)

           datetime   count   period   
1  30/03/2011 02:32      27      561
2  30/03/2011 02:42       3      600
3  30/03/2011 02:52       0      574
4  30/03/2011 03:02       1      550
5  30/03/2011 03:12      15      600
6  30/03/2011 03:22       0      597

# Dateset 2 (dts2)

   datetime         dist car   satd      alt
1 30/03/2011 01:59  23.9   1      3     1.76       
2 30/03/2011 02:58  14.7   1      7     6.36       
3 30/03/2011 03:55  10.4   2      9    -0.34      
4 30/03/2011 04:53  35.4   1      3     3.55      
5 30/03/2011 05:52  56.1   1      7    -0.91       
6 30/03/2011 06:48  12.3   1      4     6.58      
7 30/03/2011 07:48  10.7   1      5     4.18      

如果这是合并两个帧中匹配行的简单情况,则可以使用基本的merge(data1, data2, by="datetime")rbind() 函数。

但是,我的问题更复杂,因为 两个数据集中的时间间隔不相等Dataset 1 包含 精确 10 分钟间隔的数据(每行包含关于在指定日期/时间结束的 10 分钟块的信息),而 dataset 2 包含 大约的数据 1 小时间隔(每行包含在指定日期/时间结束的 1 小时块中的信息)。

为了让事情变得更加困难,两个数据集中的行的开始时间之间存在时间不匹配(即dts1: 01/03/2013 10:00:00, @ 987654327@: 01/03/2012 09:58:12)。 dts2 间隔在整个数据集中也有所不同(1 小时左右的±几分钟)。我想将数据集 1 中的每 10 分钟数据行与数据集 2 中适合的小时块链接(以及 dts2 中的所有关联列值)。将有一些行可以适合 2 个不同的小时块(即 30/03/2011 03:02),但我只需要将这些行链接到一个小时块。

我想得到这样的结果:

        datetime_dts1 count period     datetime2_dts2  dist  car satd      alt  
1    30/03/2011 02:32    27    561   30/03/2011 02:58  14.7   1     7     6.36      
2    30/03/2011 02:42     3    600   30/03/2011 02:58  14.7   1     7     6.36
3    30/03/2011 02:52     0    574   30/03/2011 02:58  14.7   1     7     6.36
4    30/03/2011 03:02     1    550   30/03/2011 02:58  14.7   1     7     6.36
5    30/03/2011 03:12    15    600   30/03/2011 03:55  10.4   2     9    -0.34
6    30/03/2011 03:22     0    597   30/03/2011 03:55  10.4   2     9    -0.34

我已经搜索了这个问题的答案,但未能解决它,而且我的 R 能力不高。如果有人能给我一个方向或提供解决方案,我将非常感激。

【问题讨论】:

  • 你能提供一个dput 输出吗?获取数据很痛苦
  • 我真的看不懂你的长文,但你不能把时间四舍五入,然后根据四舍五入的时间合并吗?
  • 第 5 行,3:12 更接近于2:58 而不是3:55。您根据什么将其分配给3:55?如果没有规则,就很难合并。
  • 嗨阿伦/罗兰。谢谢你的cmets。 Arun-我将 3:12 分配到 3:55,希望所有 dts1 值与 dts2 中比自身大的最近的 hh:mm 行匹配,除非 dts1 中的值比 hour:min 行多不到 5 分钟在 dts2 中进行(下面的示例)。抱歉,我不确定如何提供 dput 输出。

标签: r merge unique


【解决方案1】:

首先将您的日期时间字符串转换为 POSIXt 类后,rounding 和 truncating 这些时间的某种组合应该可以为您提供可以用作合并基础的东西。

首先读入你的数据,并创建相应的 POSIXt 日期时间:

dts1 <- structure(list(datetime = structure(1:6,
   .Label = c("30/03/2011 02:32", "30/03/2011 02:42",
   "30/03/2011 02:52", "30/03/2011 03:02", "30/03/2011 03:12",
   "30/03/2011 03:22"), class = "factor"), count = c(27L, 3L,
   0L, 1L, 15L, 0L), period = c(561L, 600L, 574L, 550L, 600L,
   597L)), .Names = c("datetime", "count", "period"),
   class = "data.frame", row.names = c(NA, -6L))
dts2 <- structure(list(datetime = structure(1:7,
    .Label = c("30/03/2011 01:59", "30/03/2011 02:58",
    "30/03/2011 03:55", "30/03/2011 04:53", "30/03/2011 05:52",
    "30/03/2011 06:48", "30/03/2011 07:48"), class = "factor"),
    dist = c(23.9, 14.7, 10.4, 35.4, 56.1, 12.3, 10.7), car =
    c(1L, 1L, 2L, 1L, 1L, 1L, 1L), satd = c(3L, 7L, 9L, 3L, 7L,
    4L, 5L), alt = c(1.76, 6.36, -0.34, 3.55, -0.91, 6.58,
    4.18)), .Names = c("datetime", "dist", "car", "satd",
    "alt"), class = "data.frame", row.names = c(NA, -7L))

# create corresponding POSIXlt vector
# (you could update the 'datetime' columns in-place if you prefer)
datetime1 <- strptime(dts1$datetime, format="%d/%m/%Y %H:%M")
datetime2 <- strptime(dts2$datetime, format="%d/%m/%Y %H:%M")

以下代码在所有情况下都基于最近的时间生成一个合并表。在合并中,它只是在每个数据帧前面加上一个带有舍入时间的列,基于该列进行合并(即列号 1),然后使用-1 索引在末尾删除该列:

# merge based on nearest hour
merge(
    cbind(round(datetime1, "hours"), dts1),
    cbind(round(datetime2, "hours"), dts2),
    by=1, suffixes=c("_dts1", "_dts2")
)[-1]

     datetime_dts1 count period    datetime_dts2 dist car satd  alt
1 30/03/2011 02:32    27    561 30/03/2011 02:58 14.7   1    7 6.36
2 30/03/2011 02:42     3    600 30/03/2011 02:58 14.7   1    7 6.36
3 30/03/2011 02:52     0    574 30/03/2011 02:58 14.7   1    7 6.36
4 30/03/2011 03:02     1    550 30/03/2011 02:58 14.7   1    7 6.36
5 30/03/2011 03:12    15    600 30/03/2011 02:58 14.7   1    7 6.36
6 30/03/2011 03:22     0    597 30/03/2011 02:58 14.7   1    7 6.36

如上,但这次只是按小时截断:

merge(
    cbind(trunc(datetime1, "hours"), dts1),
    cbind(trunc(datetime2, "hours"), dts2),
    by=1, suffixes=c("_dts1", "_dts2")
)[-1]

     datetime_dts1 count period    datetime_dts2 dist car satd   alt
1 30/03/2011 02:32    27    561 30/03/2011 02:58 14.7   1    7  6.36
2 30/03/2011 02:42     3    600 30/03/2011 02:58 14.7   1    7  6.36
3 30/03/2011 02:52     0    574 30/03/2011 02:58 14.7   1    7  6.36
4 30/03/2011 03:02     1    550 30/03/2011 03:55 10.4   2    9 -0.34
5 30/03/2011 03:12    15    600 30/03/2011 03:55 10.4   2    9 -0.34
6 30/03/2011 03:22     0    597 30/03/2011 03:55 10.4   2    9 -0.34

如上,但对于 dts1,将记录视为属于前一小时,直到该小时后 10 分钟,通过在截断前减去 10*60 秒。这个产生的输出与您指定的相同,但没有更多信息,我不确定它是否是您想要的确切规则。

merge(
    cbind(trunc(datetime1 - 10*60, "hours"), dts1),
    cbind(trunc(datetime2, "hours"), dts2),
    by=1, suffixes=c("_dts1", "_dts2")
)[-1]

     datetime_dts1 count period    datetime_dts2 dist car satd   alt
1 30/03/2011 02:32    27    561 30/03/2011 02:58 14.7   1    7  6.36
2 30/03/2011 02:42     3    600 30/03/2011 02:58 14.7   1    7  6.36
3 30/03/2011 02:52     0    574 30/03/2011 02:58 14.7   1    7  6.36
4 30/03/2011 03:02     1    550 30/03/2011 02:58 14.7   1    7  6.36
5 30/03/2011 03:12    15    600 30/03/2011 03:55 10.4   2    9 -0.34
6 30/03/2011 03:22     0    597 30/03/2011 03:55 10.4   2    9 -0.34

您可以根据您的具体规则调整您舍入哪些时间、截断哪些时间以及是否首先减去/增加一些时间的详细信息。

编辑:

不是最优雅的,但这是一种不同的方法,可以适应您在 cmets 中描述的更复杂的条件规则。这在很大程度上依赖于 zoo 包中的 na.locf 来首先确定每个 dts1 记录之前和之后的 dts2 时间。有了这些,只需应用规则选择所需的 dts2 时间,匹配回原始 dts1 表,然后合并即可。

library(zoo)

# create ordered list of all datetimes, using names to keep
# track of which ones come from each data frame
alldts <- sort(c(
    setNames(datetime1, rep("dts1", length(datetime1))),
    setNames(datetime2, rep("dts2", length(datetime2)))))
is.dts1 <- names(alldts)=="dts1"

# for each dts1 record, get previous closest dts2 time
dts2.prev <- alldts
dts2.prev[is.dts1] <- NA
dts2.prev <- na.locf(dts2.prev, na.rm=FALSE)[is.dts1]

# for each dts1 record, get next closest dts2 time
dts2.next <- alldts
dts2.next[is.dts1] <- NA
dts2.next <- na.locf(dts2.next, na.rm=FALSE, fromLast=TRUE)[is.dts1]

# for each dts1 record, apply rule to choose dts2 time
use.prev <- !is.na(dts2.prev) & (alldts[is.dts1] - dts2.prev < 5)
dts2.to.use <- ifelse(use.prev, as.character(dts2.prev), 
    as.character(dts2.next))

# merge based on chosen dts2 times, prepended as character vector
# for the purpose of merging
merge(
    cbind(.dt=dts2.to.use[match(datetime1, alldts[is.dts1])], dts1),
    cbind(.dt=as.character(datetime2), dts2),
    by=".dt", all.x=TRUE, suffixes=c("_dts1", "_dts2")
)[-1]

【讨论】:

  • 感谢您的建议。非常有帮助。在截断之前减去 10*60 的问题是它只考虑小时,而不是分钟。我在 dts2 中的小时值并不总是 60 分钟长。相反,我希望脚本查看 dts1 中的每一行并将其匹配到 dts2 中大于自身的最接近的 hour:min 行,除非 dts1 中的值比 dts2 中的 hour:min 行多不到 5 分钟继续。
  • 一个例子:30/03/2011 03:32匹配到30/03/2011 03:4930/03/2011 03:42匹配到30/03/2011 03:4930/03/2011 03:52匹配到30/03/2011 03:4930/03/2011 04:02匹配到30/03/2011 04:42等等....
  • 太好了,这正是我想要的。非常感谢您的广泛回答。非常感谢!
【解决方案2】:

data.table 的选择器似乎非常适合。它非常有效,并且允许合并最近的值(上或下或两者)。

在此网站上查找:https://www.r-bloggers.com/understanding-data-table-rolling-joins/ 左、右连接...等示例

website[, join_time:=session_start_time]
paypal[, join_time:=purchase_time]
setkey(website, name, join_time)
setkey(paypal, name, join_time)
website[paypal, roll = T]

关于DT:https://cran.r-project.org/web/packages/data.table/vignettes/datatable-intro.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-30
    • 2015-10-30
    • 2022-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-22
    相关资源
    最近更新 更多