Подвижная сумма по датам

У меня большой набор данных, который я хотел бы вычислить для скользящей годовой суммы столбца. Это должен быть точный год, поэтому я не могу использовать rollapply, поскольку он основан на определенном количестве дней, а не на фактических датах.

В качестве примера у меня есть следующий код:

dates = seq.Date(as.Date('2006-01-01'),as.Date('2007-12-31'),by='days')
num = 1:length(dates)
y = cbind(ld,num)

        ld num
[1,] 13149   1
[2,] 13150   2
[3,] 13151   3
[4,] 13152   4
[5,] 13153   5
[6,] 13154   6

Я хотел бы получить скользящую историческую сумму за год в столбце num.

Единственный способ решить эту проблему - использовать цикл и подмножество фрейма данных. Это не очень эффективно, и я надеялся, что кто-нибудь посоветует мне, как учитывать високосные годы, используя вместо этого функцию встраивания, поскольку это намного быстрее.

Используя функцию вставки, у меня есть следующий код, который работает до тех пор, пока это не високосный год.

b = embed(y[,2],366)
sums = colSums(b)

a = ld[length(dates)-365:length(dates)]
final = cbind(dates = a, rollsum = rev(sums))


head(final)
     dates rollsum
[1,] 13513   66795
[2,] 13514   67160
[3,] 13515   67525
[4,] 13516   67890
[5,] 13517   68255
[6,] 13518   68620

Есть ли у кого-нибудь более эффективный способ вычисления скользящей суммы на основе определенных дат, а не количества дней?


person Børge Klungerbo    schedule 27.04.2013    source источник


Ответы (3)


Вы можете добавить к своим данным столбец с датой год назад (с учетом високосных лет) и использовать sqldf для вычисления скользящей суммы.

# Sample data
dates <- seq.Date(as.Date('2006-01-01'),as.Date('2007-12-31'),by='days')
d <- data.frame( date = dates, value = rnorm(length(dates)) )
#d <- d[ sample(length(dates), length(dates)/2), ]  # For more irregular data
d <- d[ order(d$date), ]

# Compute the date one year ago (you can also use lubridate, for date arithmetic)
d$previous_year <- sapply( 
  d$date, 
  function(u) as.character(seq(u, length=2, by="-1 years")[2]) 
)
d$date <- as.character(d$date)

# Compute the rolling sum
library(sqldf)
sqldf( "
  SELECT A.date         AS date, 
         SUM( B.value ) AS sum, 
         MIN( B.date )  AS start, 
         MAX( B.date )  AS end, 
         COUNT(*)       AS observations
  FROM d A, d B
  WHERE A.previous_year < B.date AND B.date <= A.date
  GROUP BY A.date
" )
person Vincent Zoonekynd    schedule 27.04.2013

Это должно сработать быстро, хотя по-прежнему используется цикл:

library(data.table)
library(mondate)

# Create table with sample dates:
dt<-data.table(dates = seq.Date(as.Date('2006-01-01'),as.Date('2012-12-31'),by='days'),key="dates")

# Generate some sample values to be summed, initialize the rolling sum values, and add the row number:
set.seed(6540)
dt[,c("val","valroll","rowid"):=list(sample((1L:1e6L)-1L,.N),0L,1:.N)]

# Subtract one year (12 months) from each date, then subtract that from the original date to get the number of days
# Create a column to indicate the starting row number to sum from:
dt[,rowid_lag:=pmax.int(1,rowid-as.integer(dates-as.Date(mondate(dates) - 12)))]

# For each row, sum from row rowid_lag to rowid:
for(i in 1:nrow(dt)) {
  #dt[i,valroll:=dt[dt[i,rowid_lag:rowid],sum(val)]]
  set(dt, i, "valroll", dt[dt[i,rowid_lag:rowid],sum(val)])
}
rm(i)

Вышеизложенное предполагает, что в датах нет пробелов. Если это плохое предположение, ответ можно будет изменить.

Использование embed интересно - раньше я о нем не слышал. Я пошел по этому пути, но решил вернуться к циклу, когда не мог понять, как обрабатывать первые 365 строк. Я постараюсь доработать это решение и выложить его тоже, если это поможет.

Я также рассмотрел маршрут, который выбрал @VincentZoonekynd, хотя использовал data.table, а не sqldf (поскольку я более знаком с ним). Но, по моему опыту, «перекрестное соединение» в этом типе решения быстро взрывается, поэтому, если у вас очень много строк, это будет невозможно.

person dnlbrky    schedule 28.04.2013

В этом ответе используется embed, но он может не дать желаемых результатов для первых 366 строк:

library(data.table)
library(mondate)

# Create table with sample dates:
dt2<-data.table(dates = seq.Date(as.Date('2006-01-01'),as.Date('2012-12-31'),by='days'),key="dates")

# Generate some sample values to be summed, initialize the rolling sum values, add the row number, and determine the number of days between each date at the prior year (365 or 366):
set.seed(6540)
dt2[,c("val","valroll","rowid","lag"):=list(sample((1L:1e6L)-1L,.N),0L,1:.N,as.integer(dates-as.Date(mondate(dates)-12)))]

# Create a table with column values made up of each of the preceding 366 rows:
dt2b<-data.table(embed(dt2[,val],366))

# Set the 366th column to 0 if the prior year was 365 days ago:
dt2b[dt2[(dt2[lag-rowid==0L,rowid]+1L):nrow(dt2),lag]==365L,V366:=0L]

# Sum the rows of the second table, and add the result to the first table:
dt2[(dt2[lag-rowid==0L,rowid]+1L):nrow(dt2),valroll:=as.integer(rowSums(dt2b))]
rm(dt2b)

Кроме того, столбец «valroll» из моего другого ответа (с использованием цикла for) включает одну дополнительную строку «val» по сравнению с этим ответом. Я думаю, что этот ответ нужно изменить, но я не уверен.

person dnlbrky    schedule 28.04.2013