如何使用dplyr添加灵活的delta列?

时间:2017-01-30 11:19:40

标签: r dplyr

我想使用dplyr为数据集添加“delta”列。 delta将被计算为当前行值与前一行的值之间的差值。挑战在于前一行不一定是正确的行,因为需要进行一些过滤。

考虑这个数据集:

LEVEL, TIME
3,     0000
2,     0010
2,     0020
1,     0030
2,     0040
3,     0050

我想添加一个新列DELTA,其中包含TIME值与前一个TIME值之间的差值,对于具有相同LEVEL或更高级别的行。也就是说,我不想与紧接在前的行进行比较,而是向后搜索并跳过任何具有较低LEVEL的行。

对于此示例,预期输出为:

LEVEL, TIME, DELTA
3,     0000, NA
2,     0010, 10
2,     0020, 10
1,     0030, 10
2,     0040, 20
3,     0050, 50

这可以用dplyr直接完成吗? (或者其他?)

我想要一个有效的解决方案,因为我的真实数据集大约有十亿行,并且有七个时间戳列(但只有一个级别。)

(背景:数据来自软件应用程序日志文件,使用CPU提供的许多时间源,例如周期,指令和L1 / L2 / L3 / DRAM访问计数器。我想测量事件之间经过的时间。较低级别的消息不是单独的先前事​​件,而是更精细的细节。)

使用新信息进行编辑:

我尝试使用dplyr的解决方案实际上并没有使用我的百万元素数据集。它们似乎很慢并且炸毁了R过程。

我已经退回学习一些基础R并编写一个相当实用的(1M行数据帧约2秒)实现,如下所示:

level <- c(3,2,2,1,2,3,6,4,7,8,2) # recycled to 1M elements, below
time <- seq(0, 10000000, 10)

# reference timestamp accumulator for update inside closure.
# index is log level and value is reference timestamp for delta.
ref <- numeric(9)
f <- function(level, time) {
  delta <- time - ref[level]
  ref[1:level] <<- time
  delta
}

delta <- mapply(f, level, time)

这合理吗?是否有类似的dplyr解决方案?

我基本满意。我觉得这应该快〜10倍,每个向量元素大约5000个CPU周期似乎有点疯狂,但它对我有用,并且在解释器的上下文中可能是合理的,复制每个向量元素ref累加器步骤

EDIT2:经过反思,这个配方的表现有点拖累。如果可能,我希望加速10倍!

1 个答案:

答案 0 :(得分:1)

我自己加入了data.frame。然后选择符合条件的所有行。然后选择最接近的匹配行。 为了在结果中获得相同数量的行(第一行中的NA),我再次加入基数data.frame(right_join)。

LEVEL <- c(3,2,2,1,2,3)
TIME <- c('0000','0010','0020','0030','0040','0050')

df <- data.frame(LEVEL, TIME, stringsAsFactors = F)

df %>%  
  merge(df, by = NULL, all=T) %>%  
  filter(LEVEL.y >= LEVEL.x & TIME.x > TIME.y) %>%
  group_by(TIME.x, LEVEL.x) %>% 
  filter(row_number(desc(TIME.y))==1) %>%
  mutate(delta = as.numeric(TIME.x) - as.numeric(TIME.y)) %>%
  rename(LEVEL = LEVEL.x, TIME=TIME.x) %>%  
  select(TIME, LEVEL, delta) %>%
  right_join(df)

另一种方法是计算每个组的min(delta),而不是排序和选择第一行。我更喜欢上面的解决方案,因为您可以使用匹配行的其他信息。

df %>% merge(df, by = NULL, all=T) %>%  
  filter(LEVEL.y >= LEVEL.x & TIME.x > TIME.y) %>%
  group_by(TIME.x, LEVEL.x) %>%  
  summarise(delta = min(as.numeric(TIME.x) - as.numeric(TIME.y))) %>%
  rename(LEVEL = LEVEL.x, TIME=TIME.x) %>%  
  select(TIME, LEVEL, delta) %>%
  right_join(df)