【问题标题】:Simple sum if expression简单求和 if 表达式
【发布时间】:2016-01-01 20:49:54
【问题描述】:

这些是我的示例数据:

dt <- data.table(id=c("a","a","a","a","b","b"), monthsinarrears=c(0,1,0,0,1,0), date=c(2013,2014,2015,2016,2014,2015))

表格如下所示:

> dt
   id monthsinarrears date
1:  a               0 2013
2:  a               1 2014
3:  a               0 2015
4:  a               0 2016
5:  b               1 2014
6:  b               0 2015

现在我想创建一个名为“EverinArrears”的附加列,如果 id 曾经(历史上)拖欠,则该列将分配为“1”,如果不是,则分配为“0”。因此我想要获得的输出是:

   id monthsinarrears date EverinArrears
1:  a               0 2013             0
2:  a               1 2014             1
3:  a               0 2015             1
4:  a               0 2016             1
5:  b               1 2014             1
6:  b               0 2015             1

请注意,贷款 ID a 在 2013 年历史上没有拖欠(这发生在 2014 年),这就是为什么 EverinArrear 在 2013 年也得到 0 的原因。

【问题讨论】:

  • 您的结果与您提供的数据不符!
  • 为什么?我只是想根据我提供的第一个表和我提供的条件添加这个额外的列 EverinArrears。
  • 看看你最初的 dt,你写了什么......这完全不同,即使你的问题总体上是可以理解的 ;)
  • @ColonelBeauvel 你是对的!我没有注意到这一点,但我看到问题已正确更新。

标签: r sum conditional data.table


【解决方案1】:

你可以使用ave:

dt$EverinArrears = as.integer(!!ave(dt$monthsinarrears, dt$id, FUN=cumsum))

或者使用 data.table 的好方法:

dt[, EverinArrears := +(!!cumsum(monthsinarrears)), id][]

【讨论】:

  • 不要将 ave 与 data.table 一起使用。这太慢了。
  • 使用as.logicalas.integer 是更好的做法(代码更清晰,速度稍快)。
  • 不要使用“聪明”的语法技巧,例如!!。它们完全是不必要的,并且模糊了实际含义。在这里明确表达没有害处:x != 0 是你应该写的。但实际上不是,因为结果无论如何都会转换回整数。顺便说一句,+as.logical(x) 也是如此:它同样是一个语法黑客。这里的实际逻辑被sign(cumsum(x))封装了。
  • @KonradRudolph 如果您想保持透明,请执行ifelse( x &gt; 0, 1, 0)sign 不是很好的替代品(因为它的使用表明这个数字可能是负数;x != 0 而不是 x &gt; 0 也是如此)。虽然 +x 是一个 hack,但它并没有像 ?as.integer 更好地告诉你从逻辑到整数的强制是如何工作的。也许正确的做法是告诉 OP“逻辑就是你想要的——不要费心转换为 0/1。”
  • @Frank 由于您提到的原因,我也不是as.integer 的忠实粉丝。我总是很明确;唉,ifelse(…) 慢得多,所以必须做出一些让步。
【解决方案2】:

您可以执行以下操作(感谢@Roland 提供的避免数字 > 1 的提示):

dt[, EverinArrears := as.integer(as.logical(cumsum(monthsinarrears))), by=id]

输出:

#   id monthsinarrears date EA
#1:  a               0 2013  0
#2:  a               1 2014  1
#3:  a               0 2015  1
#4:  a               0 2016  1
#5:  b               1 2014  1
#6:  b               0 2015  1

注意:如果你喜欢更短的代码,你也可以这样做

dt[, EverinArrears := +(!!(cumsum(monthsinarrears))), by=id]

虽然不像as.integer(as.logical(...))那样“好习惯”

正如@Jaap 所说,您也可以这样做:

dt[, EverinArrears := +(cumsum(monthsinarrears) > 0), by = id]

或者,为了更好的做法:

dt[, EverinArrears := as.integer(cumsum(monthsinarrears) > 0), by = id]

正如@Arun 在评论中所建议的,另一种更简单的方式:

dt[, EverinArrears := cummax(monthsinarrears), by = id]

【讨论】:

  • 只需换上as.logical
  • 另一种可能:dt[, EverinArrears := +(cumsum(monthsinarrears) &gt; 0), by = id]
  • cummax(monthsinarrears)cummax(monthsinarrears &gt; 0L) 也适合这里..
  • @Arun,它确实是一个更适合的函数(我必须说我不知道​​这个函数)。非常感谢,我已经添加了选项
【解决方案3】:

使用包dplyr:

library(dplyr)

dt %>% 
  group_by(id) %>% 
  arrange(date) %>% 
  mutate(EverinArrears = +as.logical(cumsum(monthsinarrears))) %>% 
  data.table

   id monthsinarrears date EverinArrears
1:  a               0 2013             0
2:  a               1 2014             1
3:  a               0 2015             1
4:  a               0 2016             1
5:  b               1 2014             1
6:  b               0 2015             1

【讨论】:

    【解决方案4】:

    其他人的回答略有不同:

    dt[, newcol := cummax(monthsinarrears > 0), by=id]
    

    通过使用cummax 而不是cumsum,我们可以节省一些计算。


    这里有一种比较第一个条目的位置的方法,其中拖欠的月份为正数:

    dt[, newcol := {
      z = which(monthsinarrears > 0)
      if (!length(z)) rep(0L,.N)
      else            replace(rep(1L,.N), 1:.N < z[1], 0L)
    }, by=id]
    

    不确定这是否更有效;这在一定程度上当然取决于数据。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-16
      • 2015-08-02
      • 2023-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多