【问题标题】:Conditional Sum Depending on Other Data Frame Columns取决于其他数据框列的条件总和
【发布时间】:2012-10-02 18:54:59
【问题描述】:

假设您有一个由以下命令生成的数据框:

date <- seq(as.Date("2012-09-01"), Sys.Date(), 1)
id <- rep(c("a","b","c","d"), 8)
bdate <- seq(as.Date("2012-08-01"), as.Date("2012-11-01"), 1)[sample(1:32, 32)]

# The end date should be random but greater than the begin date. However, I set it to 15 days later for simplicity.
edate <- bdate + 15

value <- seq(1, 1000, 1)[sample(1:1000, 32)]
dfa <- data.frame(id, value, bdate, edate)
names(dfa) <- c("ID", "Value", "Begin.Date", "End.Date")

目标是按以下方式按 ID(即“a”、“b”或“c”)汇总所有观察结果:

Date        a   b   c
2012-08-01  XXX YYY ZZZ
2012-08-02  XXX YYY ZZZ
2012-08-03  XXX YYY ZZZ

对于每个 ID,值 XXX、YYY 和 ZZZ 表示所有观察值的总和,其中“日期”列上的日期介于原始数据框上的 dfa$Begin.Date 和 dfa$End.Date 之间。

我目前的解决方案对于大型数据集几乎没有用,所以我想知道是否有更快的方法来做到这一点。

我当前的脚本:

# Create additional data frame
dfb <- data.frame(seq(as.Date("2012-08-01"), as.Date("2012-11-01"), 1))
names(dfb)[1] <- "Date"

# Variable for unique IDs
nid <- unique(dfa$ID)

# Number of total IDs
tid <- length(nid)

for (i in c(1:tid))
{
sums <- vapply(dfb$Date, function(x)
{
temp <- subset(dfa, dfa$ID == nid[i])
temp <- subset(temp, temp$Begin.Date < x & temp$End.Date > x)
res <- sum(temp$Value)
res
}, FUN.VALUE = 0.1
)
dfb[1+i] <- sums
}

# Change column names to ID
names(dfb) <- c("Date", as.character(nid))

编辑:我在下面发布了一个更有效的答案。但是,我接受了马修的回答,因为它让我走上了正确的道路。

【问题讨论】:

    标签: r


    【解决方案1】:

    感谢@Matthew Dowle,我发现了如何使用 data.table 包更有效地做到这一点。

    # Fire up the bad boy
    library(data.table)
    
    # Create the data table with original data
    value <- seq(1, 1000, 1)[sample(1:1000, 32)]
    dt <- data.table(id, value, bdate, edate)
    setnames(dt, names(dt), c("id", "value", "begin", "end"))
    
    # For each pair of id and value, create a row for each day. (i.e., the first line:
    # a  928  2012-08-11  2012-08-26
    # will now be 15 lines. The first two columns are repeated over 15 different dates.
    dt <- dt[, seq(begin[1], (last(end) - 1), by="days"), by = list(id, value)]
    setnames(dt, names(dt), c(names(dt)[1:2], "date"))
    setkey(dt)
    
    # Sum each pair of id and value over the dates column
    dt <- dt[, sum(value), by = list(id, date)]
    setnames(dt, names(dt), c(names(dt)[1:2], "value"))
    setkey(dt, date, id)
    
    # Define the time span you would like on your final table
    timespan <- dt[, seq(as.Date("2012-07-25"), max(date), by = "day")]
    
    # Now just cross reference the time span with your actual data
    setkey(dt, id, date)
    dt <- dt[CJ(unique(id), timespan), ]
    setnames(dt, names(dt), c(names(dt)[1:2], "value"))
    setkey(dt, date)
    

    哒哒!!

    现在,按照我最初想要的顺序重新排列表格:

    Date        a   b   c
    2012-08-01  XXX YYY ZZZ
    2012-08-02  XXX YYY ZZZ
    2012-08-03  XXX YYY ZZZ
    

    只需使用 reshape2 包中的 dcast。

    那么大家觉得呢?太棒了,对吧?

    【讨论】:

    • 嗨。 :) 很好,很高兴你喜欢它。但是不需要seq(begin[1], (last(end) - 1), by="days") 扩展部分,iiuc。这就是roll=TRUE 的用途。为了速度,它实际上是一样的,没有实际扩展。 roll=TRUE 用于其他链接问题,对于像这样的日期范围连接非常重要。
    • 这有点尴尬,但那部分在另一个问题的最终答案的第一行......我会尝试使用 roll=TRUE 部分(虽然我不知道怎么做)。嗯,也许我还不够努力,但是,您介意分享一下您将如何使用 roll=TRUE 选项吗?
    • Apols,我的意思是说 seq by by = list(id, value) 使它扩展了每一行(每张账单),不是吗?我只在另一个问题中通过acct 这样做。
    【解决方案2】:

    有趣。这似乎与这个问题非常相似:

    Splitting irregular time series into regular monthly averages

    这有帮助吗?在那里,正如您的问题一样,一个技巧可以是使用包data.table 中的roll=TRUE 加入流行的begin。特别是因为您说您拥有大型数据集。

    【讨论】:

    • 有趣...非常有趣。我现在没有时间,但我会在几天后尝试并报告。感谢您的帮助!
    • 谢谢谢谢谢谢谢谢!我只是想出了如何做到这一点,但我会将其作为不同的答案发布以提高可读性。
    【解决方案3】:

    我会做以下事情。首先通过检查所需日期是否在Begin.DateEnd.Date 之间来子集原始数据集。然后只需使用一个简单的table 函数来获取'a''d' 的频率。

    mydate <- as.Date("2012-08-25")  # take Aug 25, 2012 as an example
    ind <- (dfa$Begin.Date <= mydate) & (dfa$End.Date >= mydate)
    temp <- subset(dfa, ind)
    out <- table(temp$ID)
    

    【讨论】:

    • 此解决方案适用于特定日期,我必须遍历所有需要的日期才能获得结果数据框。我没有在大型数据集上尝试过它,所以它可能会起作用,但它看起来类似于我的 vapply 方法。非常感谢您的建议。
    • @Fael 是的,基本思路是一样的。可能有一些包,否则,我认为所有所需日期的循环是不可避免的。祝你好运。
    • 我刚刚尝试过这种方法,我必须说它感觉比我的慢得多。此外,table 命令仅计数观察结果,它实际上并不对它们求和。非常感谢您的帮助,但我认为我原来的方法可能更合适。
    • @Fael 你是什么意思它实际上并没有总结它们?
    • 假设在 2012 年 8 月 25 日,每个 id 求和的期望值为:100、125(对于 a); 40(对于 b); 0(对于 c); 120、100、400(用于 d)。输出应该是 a = 225、b = 40、c = 0 和 d = 620。但是,您的代码只计算相关值。它输出 a = 2, b = 1, c = 0, d = 3。
    【解决方案4】:

    我不知道这是否更快(尚未对其进行基准测试),并且对于特别大的数据,它可能会创建太大的中间数据集,但无论如何我都会展示它。

    也可以设置考虑的日期范围(请求基于对此答案的评论)。

    library("plyr")
    library("reshape2")
    
    earliest.date <- as.Date("2007-01-01")
    latest.date <- as.Date("2012-11-01")
    
    dfa.long <- adply(dfa, 1, function(DF) {
      if(DF$End.Date >= earliest.date & DF$Begin.Date <= latest.date) {
        data.frame(Date=seq(max(DF$Begin.Date, earliest.date), 
                            min(DF$End.Date, latest.date), 
                            1))
      }
    })
    
    dfb <- ddply(dfa.long, .(Date, ID), summarise, sum=sum(Value))
    dfb <- dcast(dfb, Date~ID, value.var="sum", fill=0)
    

    dfa.long 是一个数据集,其中每一行在开始/结束范围内的每个日期都重复(也限制在最早/最晚日期范围内)。然后可以直接按日期和ID 聚合并使用reshape2 中的dcast 将其转换为您想要的宽格式。

    【讨论】:

    • 您的方法类似于 vapply,但根据我有限的经验, plyr 和 reshape2 组合至少与 *pply 函数一样快。我会尝试一下,稍后再报告。
    • 我只是在整个数据集的一个子集上尝试了这种方法,虽然我可能以错误的方式实现它,但它似乎更慢。另外,我无法真正理解 dfb$Date 列。例如,一个数据点的值为 100,开始日期为“1999-01-01”,结束日期为“2050-01-01”。但是,我希望 dfb$Date 列包含“2007-01-01”以后的结果。由于一些原始数据点的日期早于“2007-01-01”,因此我的 dbf$Date 看起来不像我预期的那样。必须有办法纠正这个问题,但现在,它逃过了我的视线。
    • -1 建议plyr 当问题陈述“大型数据集,所以我想知道是否有更快的方法”。
    • @MatthewDowle 这是一个公平的警察。
    猜你喜欢
    • 1970-01-01
    • 2021-10-28
    • 1970-01-01
    • 1970-01-01
    • 2021-07-06
    • 2019-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多