【问题标题】:R: How to make transformations on subset of a subset fast (optimize or alternative to `ddply`)?R:如何快速对子集的子集进行转换(优化或替代`ddply`)?
【发布时间】:2014-02-21 12:09:46
【问题描述】:

我有一个较大的数据集(784,932 行/项目,27,492 个唯一 ID)。对于每个 ID 中的每个 Item,如果日期之间的差异小于 60 秒,我会尝试创建一个等于 1 的虚拟变量。

风格化的数据和代码:

ID <- c(1,1,1,1,1,1,3,3,3,3,3,3)
Item <- c(10,10,10,20,20,20,10,20,10,10,10,20)
Date <- c("19/11/13 18:58:00","19/11/13 18:58:21","19/11/13 20:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 19:58:00")
df <- data.frame(ID, Item, Date)
df <- df[order(ID, Date), ]
df[, "Date"] = lapply(df["Date"],function(x){strptime(x, "%d/%m/%y %H:%M:%S")})

# less than 60 sec difference = 1 (first item set to 999 -> 0)
fnDummy <- function(date) { ifelse(c(999, diff(date))<60, 1, 0) }

library(plyr)
ddply(df, .(ID, Item), transform, Dummy=fnDummy(Date) )

输出:

   ID Item                Date Dummy
1   1   10 2013-11-19 18:58:00     0
2   1   10 2013-11-19 18:58:21     1
3   1   10 2013-11-19 20:58:00     0
4   1   20 2013-11-19 18:58:00     0
5   1   20 2013-11-19 18:58:00     1
6   1   20 2013-11-19 18:58:00     1
7   3   10 2013-11-19 18:58:00     0
8   3   10 2013-11-19 18:58:00     1
9   3   10 2013-11-19 18:58:00     1
10  3   10 2013-11-19 18:58:00     1
11  3   20 2013-11-19 18:58:00     0
12  3   20 2013-11-19 19:58:00     1

从输出中可以看出第一行和第二行有共同的ID和Item,而Date的差只有21秒,所以dummy是1。第二行和第三行也有共同的ID和Item,但是这里日期差远大于 60 秒,所以 dummy 为 0。

我设法得到了我想要的输出,但是操作很慢。对于 1000 行,大约需要 40 秒(参见下面的 system.time 结果)。这相当于大约。整个数据集需要 180 分钟(我的计算机内存不足并在此之前很久就崩溃了)。

   user  system elapsed 
 36.485   3.328  39.800 

我怎样才能使这个操作更快?我可以使用data.table 完成相同的输出吗?它更快吗?

【问题讨论】:

  • 你为什么使用ifelsec(FALSE, diff(date) &lt; 60) 应该足够了。如果您确实需要 0 和 1,请使用 as.integer
  • 我知道你得到了答案,但我有点困惑。您问题中的代码甚至不适合我。我在ddply 行收到错误消息。

标签: r data.table subset plyr


【解决方案1】:

您可以按照您的建议使用data.table。不过,您必须将 POSIXlt 转换为 POSIXct

library(data.table)
df$Date <- as.POSIXct(df$Date)
DT <- as.data.table(df)

DT[, dummy_date := fnDummy(Date), by=c('ID', 'Item')]

但是,减速的很大一部分可能是在 ifelse 函数中,您并不真正需要它,因为您正在创建一个布尔值:

as.integer(c(FALSE, diff(date) < 60))

【讨论】:

  • 小提示:我会使用as.data.table(.) 而不是data.table(.)。前者要快得多。
  • @Arun 正确!但是在回答问题时,三个额外的字符太多了......
  • 非常感谢。现在对整个数据集运行该操作需要 14 秒。这太美了!
  • @bonna 很高兴为您提供帮助。对于后代,您可能需要进行一些代码分析。 data.tableplyr 快得多,但加速的很大一部分来自避免 ifelse
【解决方案2】:

你也可以使用 dplyr 来解决这个问题:

ID <- c(1,1,1,1,1,1,3,3,3,3,3,3)
Item <- c(10,10,10,20,20,20,10,20,10,10,10,20)
Date <- c("19/11/13 18:58:00","19/11/13 18:58:21","19/11/13 20:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 18:58:00","19/11/13 19:58:00")
df <- data.frame(ID, Item, Date = as.POSIXct(Date))

library(dplyr)

df %.% 
  group_by(ID, Item) %.%
  mutate(
    lagged = lag(Date, order_by = Date, default = 999), 
    dummy = Date - lagged < 60
  )

lag() 有效地概括了diff(),允许您为第一个元素指定排序值和默认值。 (目前它也丢失了向量的属性,但这将在未来的版本中修复。在https://github.com/hadley/dplyr/issues/166 跟踪进度)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-13
    • 1970-01-01
    • 2018-08-07
    • 2019-09-26
    • 1970-01-01
    相关资源
    最近更新 更多