根据另一个数据框在R数据框中创建变量

7

我在浪费了将近一天的时间后寻求帮助。我有一个大数据框(bdf)和一个小数据框(sdf)。我想根据 sdf$y 的值(它随着时间变量而变化)向 bdf 添加变量 z。

这里是一个可复制的示例:

bdf <- data.frame(tb = seq(as.POSIXct("2013-05-19 17:11:22 GMT", tz="GMT"), by=5624*24, length.out=10))

bdf
                tb
1  2013-05-19 17:11:22
2  2013-05-21 06:40:58
3  2013-05-22 20:10:34
4  2013-05-24 09:40:10
5  2013-05-25 23:09:46
6  2013-05-27 12:39:22
7  2013-05-29 02:08:58
8  2013-05-30 15:38:34
9  2013-06-01 05:08:10
10 2013-06-02 18:37:46


sdf <- data.frame(ts = as.POSIXct(c("2013-05-22", "2013-05-25", "2013-05-30"), tz="GMT"), y = c(0.2, -0.1, 0.3))

> sdf
      ts    y
1 2013-05-22  0.2
2 2013-05-25 -0.1
3 2013-05-30  0.3

我想在bdf中创建变量z,其值如下所示:

  • 对于bdf$tb范围介于sdf$ts的第一个值和第二个值之间的行,将z设为0.2。在这个简单的例子中,即dbf的前3行具有小于“2013-05-23 12:00:00 GMT”的时间戳。

  • 对于bdf$tb范围介于sdf$ts的第二个值和第三个值之间的行,将z设为-0.1。在这个简单的例子中,即dbf的第4和第5行具有时间戳介于“2013-05-23 12:00:00 GMT”和“2013-05-27 12:00:00 GMT”之间。

  • 对于所有bdf$tb范围介于sdf$ts的第三个值和最后一个值之间的行,将z设为0.3。在这个简单的例子中,即dbf的第6到10行具有时间戳大于“2013-05-23 12:00:00 GMT”。

因此,最终,大型数据框bdf应该如下所示:

                 tb    z
1  2013-05-19 17:11:22  0.2
2  2013-05-21 06:40:58  0.2
3  2013-05-22 20:10:34  0.2
4  2013-05-24 09:40:10 -0.1
5  2013-05-25 23:09:46 -0.1
6  2013-05-27 12:39:22  0.3
7  2013-05-29 02:08:58  0.3
8  2013-05-30 15:38:34  0.3
9  2013-06-01 05:08:10  0.3
10 2013-06-02 18:37:46  0.3

我尝试使用dplyr::mutate,但没有成功,使用循环也没有进展...非常感谢任何帮助。我希望我清楚地描述了问题,并遵守了礼仪(这是我的第一个问题)。


1
听起来像是“加入最近的值”。在data.table包中可能使用roll = "nearest",但我对它没有经验,我很好奇在dplyr中是否也有类似的功能。 - ckluss
4个回答

9

以下是使用 data.table滚动连接的解决方案:

require(data.table)
setkey(setDT(sdf), ts)
sdf[bdf, roll = "nearest"]
#                      ts    y
#  1: 2013-05-19 17:11:22  0.2
#  2: 2013-05-21 06:40:58  0.2
#  3: 2013-05-22 20:10:34  0.2
#  4: 2013-05-24 09:40:10 -0.1
#  5: 2013-05-25 23:09:46 -0.1
#  6: 2013-05-27 12:39:22  0.3
#  7: 2013-05-29 02:08:58  0.3
#  8: 2013-05-30 15:38:34  0.3
#  9: 2013-06-01 05:08:10  0.3
# 10: 2013-06-02 18:37:46  0.3
  • setDT通过引用将data.frame转换为data.table

  • setkey按提供的列名以递增顺序对data.table进行排序,并标记这些列为key columns(以便我们稍后可以在这些键列上进行连接)。

  • data.table中,当i是一个data.table时,x[i]执行连接。如果您还不熟悉data.table连接,请参阅此答案

  • x[i]执行等连接。也就是说,它找到x中每一行与i中每一行匹配的行索引,然后从x中提取这些行以返回连接结果和与之对应的i中的行。如果i中的某一行没有在x中找到匹配的行索引,则默认情况下该行的xNA

    然而,x[i, roll = .]执行滚动连接。当没有匹配时,可以将最后一个观测值向前延续(roll = TRUE-Inf),或者将下一个观测值向后延续(roll=Inf),或者将其滚动到最近的值(roll="nearest")。在这种情况下,如果我理解正确,则您需要roll="nearest"

希望对你有所帮助!


3
这是我的方法:
library(zoo)
m <- c(rollmean(as.POSIXct(sdf$ts), 2), Inf)
transform(bdf, z = sdf$y[sapply(tb, function(x) which.max(x < m))])
#                    tb    z
#1  2013-05-19 17:11:22  0.2
#2  2013-05-21 06:40:58  0.2
#3  2013-05-22 20:10:34  0.2
#4  2013-05-24 09:40:10 -0.1
#5  2013-05-25 23:09:46 -0.1
#6  2013-05-27 12:39:22  0.3
#7  2013-05-29 02:08:58  0.3
#8  2013-05-30 15:38:34  0.3
#9  2013-06-01 05:08:10  0.3
#10 2013-06-02 18:37:46  0.3

更新:删除了转换为数字的部分(不需要)

简要说明:

  • as.POSIXct(sdf$ts) 将日期转换为 POSIXct 风格的日期时间
  • rollmean(as.POSIXct(sdf$ts), 2) 计算每两个连续行的滚动平均值。这恰好是您想要用于分离观察结果的时间。 rollmean 来自包 zoo。计算 rollmean(..,2) 意味着输出向量比输入向量短1。
  • 这就是为什么我将 rollmean 的结果包装在 c(.., Inf) 中,这意味着无穷大值被添加到 rollmean 向量作为最后一个值。这将确保 sdf 中的 z 的最后条目也被返回(在特定示例中为0.3)。
  • 我使用 transformz 列添加到 bdf
  • sapply(tb, function(x) which.max(x < m)) 循环遍历 bdf$tb 条目,并为每个条目计算小于 m(其中保存了 rollmean 条目的向量)的最大索引。仅返回每个 bdf$tb 条目的最大(最新)索引。
  • 该索引向量用于 sdf$y[sapply(tb, function(x) which.max(x < m))] 中,以提取对应的 sdf$y 元素,这些元素将被存储/复制到 bdf 中的新 z 列中。

希望这可以帮助您


使用 rollmean 很方便。可以用来填充我的方法中的 findInterval 向量,并避免我在使用 difftime 时遇到的问题。 - IRTFM

3
编辑说明:我最初得到的结果与你稍有不同,我现在认为这与我对R difftime对象的理解不足有关。 POSIXt对象中的时区仍然是一个谜,但我现在看到,当我将“difftime”对象强制转换为“numeric”时,我得到的值是“天数”。 findInterval函数非常有用,它是一个索引创建函数,可以将一个具有多个相邻且不重叠间隔的值向量映射。您实际上只有两个时间点,分成三个间隔。
bdf$z <- c(0.2,-0.1,0.3)[findInterval(bdf$tb, 
                c(-Inf, 
  sdf$ts[2] - 0.5*as.numeric(difftime(sdf$ts[2], sdf$ts[1], units="secs")), 
  sdf$ts[3] - 0.5*as.numeric(difftime(sdf$ts[3], sdf$ts[2],units="sec")), 
                 Inf))]

> bdf
                    tb    z
1  2013-05-19 17:11:22  0.2
2  2013-05-21 06:40:58  0.2
3  2013-05-22 20:10:34  0.2
4  2013-05-24 09:40:10 -0.1
5  2013-05-25 23:09:46 -0.1
6  2013-05-27 12:39:22  0.3
7  2013-05-29 02:08:58  0.3
8  2013-05-30 15:38:34  0.3
9  2013-06-01 05:08:10  0.3
10 2013-06-02 18:37:46  0.3

我还检查了一下,在findIntervals中,区间是否右闭合(默认情况下左闭合),发现没有任何区别。


3
这似乎现在完全没有必要,但在基础R中。
bdf$z <- numeric(nrow(bdf))
for(i in seq_along(bdf$z)){
  ind <- which.min(abs(bdf$tb[i] - sdf$ts))
  bdf$z[i] <- sdf$y[ind]
}

虽然有点笨拙,但它具有清晰的优点,易于适应dplyr

library(dplyr)
bdf %>% rowwise() %>% 
  mutate(z= sdf$y[which.min(abs(as.numeric(tb)-as.numeric(sdf$ts)))])

#Source: local data frame [10 x 2]
#Groups: <by row>

#                    tb    z
#1  2013-05-19 17:11:22  0.2
#2  2013-05-21 06:40:58  0.2
#3  2013-05-22 20:10:34  0.2
#4  2013-05-24 09:40:10 -0.1
#5  2013-05-25 23:09:46 -0.1
#6  2013-05-27 12:39:22  0.3
#7  2013-05-29 02:08:58  0.3
#8  2013-05-30 15:38:34  0.3
#9  2013-06-01 05:08:10  0.3
#10 2013-06-02 18:37:46  0.3

第二个选项是我最喜欢的。它是最简单的解决方案,因为它不需要任何额外的包,并且非常简短。 - gattuso
误解了投票系统,想要给这个回复点赞。 - gattuso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接