【问题标题】:Splitting irregular time series into regular monthly averages - R将不规则的时间序列拆分为定期的月平均值 - R
【发布时间】:2012-09-15 11:36:34
【问题描述】:

为了确定对能源使用的季节性影响,我需要将我从计费数据库中获得的能源使用信息与每月温度相匹配。

我正在使用一个帐单数据集,其中包含不同长度以及开始和结束日期的帐单,我想获取每个帐户在每个月内的月平均值。例如,我有一个具有以下特征的计费数据库:

   acct amount      begin        end days
1  2242  11349 2009-10-06 2009-11-04   29
2  2242  12252 2009-11-04 2009-12-04   30
3  2242  21774 2009-12-04 2010-01-08   35
4  2242  18293 2010-01-08 2010-02-05   28
5  2243  27217 2009-10-06 2009-11-04   29
6  2243    117 2009-11-04 2009-12-04   30
7  2243  14543 2009-12-04 2010-01-08   35

我想弄清楚如何强制这些有些不规则的时间序列(对于每个帐户)以获得每个账单中跨越的每个月内每天的平均金额,例如:

   acct amount      begin        end days avgamtpday
1  2242  11349 2009-10-01 2009-10-31   31          X
2  2242  12252 2009-11-01 2009-11-30   30          X
3  2242  21774 2009-12-01 2010-12-31   31          X
4  2242  18293 2010-01-01 2010-01-31   31          X
4  2242  18293 2010-02-01 2010-02-28   28          X
5  2243  27217 2009-10-01 2009-10-31   31          X
6  2243    117 2009-11-01 2009-11-30   30          X
7  2243  14543 2009-12-01 2009-12-31   30          X
7  2243  14543 2010-01-01 2010-01-31   31          X

我不知道哪种工具可以做到这一点,因为我只需要这样做一次。

另一个问题是该表大约有 150,000 行长,按照大多数标准,这并不是很大,但足以使 R 中的循环解决方案变得困难。我在 R 中使用 zoo、xts 和 tempdisagg 包进行了调查。我开始编写一个非常丑陋的循环,它会拆分每个账单,然后在现有账单中为每个月创建一行,然后 tapply() 通过 accts 进行汇总和几个月,但老实说,看不出如何有效地做到这一点。

在 MySQL 中,我试过这个:

创建或替换视图 v3 为 select 1 n union all select 1 union all select 1;
创建或替换视图 v 为 select 1 n from v3 a, v3 b union all select 1;
设置@n = 0;
如果存在日历,则删除表; 创建表日历(dt日期主键);
插入日历
select cast('2008-1-1' + interval @n:=@n+1 day as date) as dt 从 v a, v b, v c, v d, v e, v;

选择帐户、金额、开始、结束、billAmtPerDay、sum(billAmtPerDay)、MonthAmt、 count() 天,sum(billAmtPerDay)/count() 平均AmtPerDay,年(dt),月(dt) FROM ( 选择 *, 金额/天 billAmtPerDay 从账单 b 在 begin 和 end 之间的 dt 上的内部连接日历 c 和 begin dt) x 按账户、金额、开始、结束、账单日、年(dt)、月(dt)分组;

但由于我不明白的原因,我的服务器不喜欢这个表,并且在内部连接上挂起,即使我进行不同的计算也是如此。我正在调查它是否有任何临时内存限制。

谢谢!

【问题讨论】:

  • 您的计费周期是否与实际月份一致,或者是某种功能“每个月的第 X 天是新周期开始的时间”类型的情况?
  • 计费周期不规则,因此大多数账单平均为 30 +/- 2 天,但有些账单长达 90 天或更长时间。
  • 那么您需要一些方法来计算仅给定日期的计费周期,以便您可以进行适当的分组。
  • @D.Hsu 答案好吗?如果是,请不要忘记接受其中一个答案。

标签: sql r data.table xts zoo


【解决方案1】:

这是使用data.table 的开始:

billdata <- read.table(text=" acct amount begin end days
1 2242 11349 2009-10-06 2009-11-04 29
2 2242 12252 2009-11-04 2009-12-04 30
3 2242 21774 2009-12-04 2010-01-08 35
4 2242 18293 2010-01-08 2010-02-05 28
5 2243 27217 2009-10-06 2009-11-04 29
6 2243 117 2009-11-04 2009-12-04 30
7 2243 14543 2009-12-04 2010-01-08 35", sep=" ", header=TRUE, row.names=1)

require(data.table)
DT = as.data.table(billdata)

首先,将 beginend 列的类型更改为日期。与 data.frame 不同,这不会复制整个数据集。

DT[,begin:=as.Date(begin)]
DT[,end:=as.Date(end)]

然后找到时间跨度,找到每天的通行账单,然后汇总。

alldays = DT[,seq(min(begin),max(end),by="day")]

setkey(DT, acct, begin)

DT[CJ(unique(acct),alldays),
   mean(amount/days,na.rm=TRUE),
   by=list(acct,month=format(begin,"%Y-%m")), roll=TRUE]

    acct   month        V1
 1: 2242 2009-10 391.34483
 2: 2242 2009-11 406.69448
 3: 2242 2009-12 601.43226
 4: 2242 2010-01 646.27465
 5: 2242 2010-02 653.32143
 6: 2243 2009-10 938.51724
 7: 2243 2009-11  97.36172
 8: 2243 2009-12 375.68065
 9: 2243 2010-01 415.51429
10: 2243 2010-02 415.51429

我认为您会发现 SQL 中流行的连接逻辑相当繁琐,而且速度较慢。

我说这是一个提示,因为它并不完全正确。请注意第 10 行重复,因为与帐户 2242 不同,帐户 2243 不会延伸到 2010-02。要完成它,您可以在每个帐户的最后一行使用rbind,并使用rolltolast 而不是roll。或者,也可以按帐户而不是跨所有帐户创建 alldays

看看上面的速度是否可以接受,我们可以从那里开始。

您可能会遇到 1.8.2 中的错误,该错误已在 1.8.3 中修复。我正在使用 v1.8.3。

组合包含缺失组和分组依据的联接时出现“内部”错误消息 已修复,#2162。例如 : X[Y,.N,by=NonJoinColumn] 其中 Y 包含一些与 X 不匹配的行。此错误也可能导致 seg 故障。

让我知道,我们可以解决问题,或者从 R-Forge 升级到 1.8.3。

顺便说一句,很好的示例数据。这样可以更快地回答。


这是上面提到的完整答案。我不得不承认这有点棘手,因为它结合了data.table 的几个功能。这应该在 1.8.2 中起作用,但我只在 1.8.3 中测试过。

DT[ setkey(DT[,seq(begin[1],last(end),by="day"),by=acct]),
    mean(amount/days,na.rm=TRUE),
    by=list(acct,month=format(begin,"%Y-%m")), roll=TRUE]

   acct   month        V1
1: 2242 2009-10 391.34483
2: 2242 2009-11 406.69448
3: 2242 2009-12 601.43226
4: 2242 2010-01 646.27465
5: 2242 2010-02 653.32143
6: 2243 2009-10 938.51724
7: 2243 2009-11  97.36172
8: 2243 2009-12 375.68065
9: 2243 2010-01 415.51429

【讨论】:

  • 嗨 Matthew,很抱歉没有早点回复——我已经运行了这两种方法,并且 data.table 解决方案 快得多,但它们给出了不同的答案,所以我我现在正在检查两者的代码。
  • @D.Hsu 快速浏览一下,我认为另一个答案可能是重复计算每张账单的结束天数,因为示例数据可能被认为是模棱两可的。我的答案使用 [begin,end) 而不是 [begin,end]。
  • 马特,我检查过了,你的代码运行良好。我花了几天时间检查的原因是我实际上正在考虑一种与我在问题中提出的计算略有不同的计算。尽管如此, data.table 函数非常快,并且相对更容易为我的(最终)目的进行修改。感谢您开发软件包并回答我的问题。
【解决方案2】:

这是一种方法:

billdata <- read.table(text=" acct amount begin end days
1 2242 11349 2009-10-06 2009-11-04 29
2 2242 12252 2009-11-04 2009-12-04 30
3 2242 21774 2009-12-04 2010-01-08 35
4 2242 18293 2010-01-08 2010-02-05 28
5 2243 27217 2009-10-06 2009-11-04 29
6 2243 117 2009-11-04 2009-12-04 30
7 2243 14543 2009-12-04 2010-01-08 35", sep=" ", header=TRUE, row.names=1)

#First, declare your columns "begin" and "end" as dates:
strptime(billdata$begin, format="%Y-%m-%d") -> billdata$begin
strptime(billdata$end, format="%Y-%m-%d") -> billdata$end

#Then create a column with the amount per day on the billing period:
billdata$avg_on_period<-billdata$amount/billdata$days

#Then split it into days:
temp <- data.frame(acct=c(),month=c(),day=c(), avg=c())
for(i in 1:nrow(billdata)){
    X <- billdata[i,]
    seq(X$begin,X$end,by="day") -> list_day
    rbind(temp, data.frame(acct=rep(X$acct,length(list_day)), 
        month=format(list_day, "%Y-%m"), day=format(list_day, "%d"), 
        avg=rep(X$avg_on_period, length(list_day)))) -> temp
    }

# And finally merge the different days of the months together:
output<-aggregate(temp$avg, by=list(temp$month,temp$acct), FUN=mean)

colnames(output) <- c("Month","Account","Average per day")

output
    Month Account Average per day
1 2009-10    2242       391.34483
2 2009-11    2242       406.69448
3 2009-12    2242       595.40000
4 2010-01    2242       645.51964
5 2010-02    2242       653.32143
6 2009-10    2243       938.51724
7 2009-11    2243        97.36172
8 2009-12    2243       364.06250
9 2010-01    2243       415.51429

【讨论】:

  • 感谢您的快速回复。我在尝试代码时延迟了响应。但是,我之前可能应该提到过,我想将它应用于一个相对较大的数据集:150,000 行,而这只在 5-6 小时内完成了大约 10% 的代码(在非常快的服务器上)。我认为使用 SQL 可能会更好地解决这个问题。
  • 确实,如果您的数据集有 150 000 行长,那么中间的 temp 数据集将有数百万行长。使用data.table 可能有一个很好的解决方案,但我从未使用过它,所以在这里我无能为力。
  • plannapus,@D.Hsu,是的,这是一个很好的问题,非常适合data.table。稍后我会添加答案。
猜你喜欢
  • 2013-07-13
  • 1970-01-01
  • 1970-01-01
  • 2015-09-12
  • 1970-01-01
  • 2014-02-17
  • 1970-01-01
  • 2014-12-08
  • 1970-01-01
相关资源
最近更新 更多