你能让 dplyr::mutate 和 dplyr::lag 默认 = 自己的输入值吗?

Posted

技术标签:

【中文标题】你能让 dplyr::mutate 和 dplyr::lag 默认 = 自己的输入值吗?【英文标题】:Can you make dplyr::mutate and dplyr::lag default = its own input value? 【发布时间】:2018-01-27 14:33:58 【问题描述】:

这类似于this dplyr lag post 和this dplyr mutate lag post,但它们都没有问这个关于默认输入值的问题。我正在使用 dplyr 来改变一个新字段,该字段是另一个字段的滞后偏移量(我已转换为 POSIXct)。目标是,对于给定的 ip,我想知道它在我的列表中显示的所有时间之间的增量统计信息。我也有大约 1200 万行。

数据看起来像这样(突变之前)

ip             hour         snap
192.168.1.2    2017070700    0
192.168.1.2    2017070700   15
192.168.1.4    2017070700    0
192.168.1.4    2017070701   45
192.168.1.4    2017070702   30
192.168.1.7    2017070700   15

'hour' 是一个整数,但应该是一个时间戳

'snap' 是代表 15 分钟增量的 4 个'snapshot' 值之一。

这里是data.frame创建代码:

test <- data.frame(ip=c("192.168.1.2","192.168.1.2","192.168.1.4","192.168.1.4","192.168.1.4","192.168.1.7"), hour=c(2017070700,2017070700,2017070700,2017070701,2017070702,2017070700), snap=c(0,15,0,45,30,15))

每个 ip 有数百甚至数千个时间戳。下面的代码使用 dplyr 来

a) 用前导 0 填充 0, b) 将两个整数“日期”字段合并为一个字段, c) 将合并的整数“日期”字段转换为 POSIX 日期, d) 按 ip 分组, e) 改变一个比旧时间戳滞后 1 的新列,如果值为 NA,则参考原始值(这是不起作用的位),并且 f) 改变一个新列,该列采用当前时间和上一次时间的差值(通过 ip)。

这些步骤指的是每行末尾的 cmets。

timedelta <- test %>% 
  mutate(snap = formatC(snap, width=2, flag=0)) %>%                      # a) 
  mutate(fulldateint = paste(hour, snap, sep="")) %>%                    # b) 
  mutate(fulldate = as.POSIXct(strptime(fulldateint, "%Y%m%d%H%M"))) %>% # c) 
  group_by(ip) %>%                                                       # d) 
  mutate(shifted = dplyr::lag(fulldate, default=fulldate)) %>%           # e) 
  mutate(diff = fulldate-shifted)                                        # f) 

变异后的数据应该是这样的:

           ip       hour  snap  fulldateint            fulldate             shifted      diff
       <fctr>      <dbl> <chr>        <chr>              <dttm>              <dttm>    <time>
1 192.168.1.2 2017070700    00 201707070000 2017-07-07 00:00:00 2017-07-07 00:00:00    0 secs
2 192.168.1.2 2017070700    15 201707070015 2017-07-07 00:15:00 2017-07-07 00:00:00  900 secs
3 192.168.1.4 2017070700    00 201707070000 2017-07-07 00:00:00 2017-07-07 00:00:00    0 secs
4 192.168.1.4 2017070701    45 201707070145 2017-07-07 01:45:00 2017-07-07 00:00:00 6300 secs
5 192.168.1.4 2017070702    30 201707070230 2017-07-07 02:30:00 2017-07-07 01:45:00 2700 secs
6 192.168.1.7 2017070700    15 201707070015 2017-07-07 00:15:00 2017-07-07 00:15:00    0 secs

如果我可以将延迟默认为原始值,那么当“delta-T”没有以前的值(这是所需的结果)时,它将始终为 0。

但是,dplyr::lag(fulldate, default=fulldate) 抛出错误

Error in mutate_impl(.data, dots) : 
Column `shifted` must be length 2 (the group size) or one, not 3

如果我使用 fulldate1,它确实有效,但是我丢失了group_by(ip) 结果,这是必要的。是否可以在 dplyr 中使延迟引用其自己的输入?

注意:如果可能的话,我真的更喜欢使用 dplyr 而不是 data.table 的答案,因为我一直使用 dplyr 作为我们的主要数据处理库,而且因为我想向 Wickham 先生建议如果在现有的 dplyr 库中确实没有解决方案,他会考虑这一点。

【问题讨论】:

从你的标题来看,你可以用更短的方式问这个问题......我猜你想要dplyr::lag(fulldate, default = first(fulldate)) 点了。首先与 group_by() 一起工作吗? 好的。为了将来参考,我的意思是除非您的问题是关于 formatC、as.POSIXct 等,否则您可以事先进行这些数据调整,然后使用 dput;如果您不熟悉它,请参阅***.com/questions/5963269/…。顺便说一句,我认为 default 参数需要一个单一的值,但是你给了它一个向量——我认为你错过了这一点,所以我只是想澄清一下。是的,firstmutate 之后 group_by 将分别作用于每个组。 好的,我会发帖的。不,我认为不需要编辑它;只是有用,下次要记住。标题很好,所以人们希望能够找到它并选择跳过小说阅读答案。 另外,我应该提到我已经尝试 dplyr::lag(fulldate, default=fulldate[1])) 出于您所说的原因,但它没有工作,因为它没有选择每组的第一个元素,只是整个列的第一个元素。 【参考方案1】:

在 OP 的代码中 ...

...
d) group_by(ip) %>%
e) mutate(shifted = dplyr::lag(fulldate, default=fulldate)) %>%
...

default= 参数的长度应为 1。在这种情况下,用 default = first(fulldate) 替换 OP 的代码应该可以工作(因为第一个元素不会有滞后,所以我们需要应用默认值)。

相关案例:

类似地,如果有“线索”,我们需要dplyr::lead(x, default=last(x))。 如果滞后或领先超过一步(n 大于 1),default= 无法做到这一点,我们可能需要切换到 if_elsecase_when 或类似的。 (我不确定当前的 tidyverse 成语。)

【讨论】:

【参考方案2】:

我认为Frank 的解决方案效果很好。这是完整的示例:

library(dplyr, warn.conflicts = F)

test <- data.frame(ip=c("192.168.1.2","192.168.1.2","192.168.1.4","192.168.1.4","192.168.1.4","192.168.1.7"),
                   hour=c(2017070700,2017070700,2017070700,2017070701,2017070702,2017070700),
                   snap=c(0,15,0,45,30,15))


test %>%
  mutate(snap = formatC(snap, width = 2, flag = 0)) %>%
  mutate(fulldateint = paste(hour, snap, sep = "")) %>%
  mutate(fulldate = as.POSIXct(strptime(fulldateint, "%Y%m%d%H%M"))) %>%
  group_by(ip) %>%
  mutate(shifted = lag(fulldate, default = first(fulldate))) %>%
  mutate(diff = fulldate - shifted) %>% 
  ungroup() %>% 
  select(ip, fulldate, shifted, diff)

#> # A tibble: 6 x 4
#>            ip            fulldate             shifted      diff
#>        <fctr>              <dttm>              <dttm>    <time>
#> 1 192.168.1.2 2017-07-07 00:00:00 2017-07-07 00:00:00    0 secs
#> 2 192.168.1.2 2017-07-07 00:15:00 2017-07-07 00:00:00  900 secs
#> 3 192.168.1.4 2017-07-07 00:00:00 2017-07-07 00:00:00    0 secs
#> 4 192.168.1.4 2017-07-07 01:45:00 2017-07-07 00:00:00 6300 secs
#> 5 192.168.1.4 2017-07-07 02:30:00 2017-07-07 01:45:00 2700 secs
#> 6 192.168.1.7 2017-07-07 00:15:00 2017-07-07 00:15:00    0 secs

【讨论】:

【参考方案3】:

怎么样

ifelse(is.na(lag(value)), value, lag(value))

【讨论】:

我应该提到我尝试了一些有条件的 NA 替换,但 dplyr 显然对条件语句很古怪,就像我在上面发布的第一个链接中一样......无论如何,你的想法有效,但它以某种方式转换回来到一个双,这是很奇怪的。我使用了这种语法:mutate(shifted = ifelse(is.na(lag(fulldate)), fulldate, lag(fulldate))),但不是像这样移位 值(2017-07-07 00:00: 00),它给了我这样的 值:(1499407200)。我不认为我犯了语法或命名错误,但很高兴看到第二(一百)双眼睛。 @TheProletariat if_else() 是维护格式的 ifelse() 的 dplyr 替代品

以上是关于你能让 dplyr::mutate 和 dplyr::lag 默认 = 自己的输入值吗?的主要内容,如果未能解决你的问题,请参考以下文章

在 R 中:将列名作为参数传递,并在 dplyr::mutate() 和 lazyeval::interp() 的函数中使用它

dplyr mutate 中的 for 循环

使用 dplyr::mutate 对数据帧进行 Fisher 的测试统计

dplyr mutate 和 summarise 在数据表中的等价物是啥? [复制]

在 dplyr mutate_at 调用中使用多列的函数

在列的子集上执行dplyr mutate