Как подмножить data.frame по неделям, а затем суммировать?

Допустим, у меня есть данные за несколько лет, которые выглядят следующим образом.

# load date package and set random seed
library(lubridate)
set.seed(42)

# create data.frame of dates and income
date <- seq(dmy("26-12-2010"), dmy("15-01-2011"), by = "days")
df <- data.frame(date = date, 
                 wday = wday(date),
                 wday.name = wday(date, label = TRUE, abbr = TRUE),
                 income = round(runif(21, 0, 100)),
                 week = format(date, format="%Y-%U"),
                 stringsAsFactors = FALSE)

#          date wday wday.name income    week
# 1  2010-12-26    1       Sun     91 2010-52
# 2  2010-12-27    2       Mon     94 2010-52
# 3  2010-12-28    3      Tues     29 2010-52
# 4  2010-12-29    4       Wed     83 2010-52
# 5  2010-12-30    5     Thurs     64 2010-52
# 6  2010-12-31    6       Fri     52 2010-52
# 7  2011-01-01    7       Sat     74 2011-00
# 8  2011-01-02    1       Sun     13 2011-01
# 9  2011-01-03    2       Mon     66 2011-01
# 10 2011-01-04    3      Tues     71 2011-01
# 11 2011-01-05    4       Wed     46 2011-01
# 12 2011-01-06    5     Thurs     72 2011-01
# 13 2011-01-07    6       Fri     93 2011-01
# 14 2011-01-08    7       Sat     26 2011-01
# 15 2011-01-09    1       Sun     46 2011-02
# 16 2011-01-10    2       Mon     94 2011-02
# 17 2011-01-11    3      Tues     98 2011-02
# 18 2011-01-12    4       Wed     12 2011-02
# 19 2011-01-13    5     Thurs     47 2011-02
# 20 2011-01-14    6       Fri     56 2011-02
# 21 2011-01-15    7       Sat     90 2011-02

Я хотел бы суммировать «доход» за каждую неделю (с воскресенья по субботу). В настоящее время я делаю следующее:

Weekending 2011-01-01 = sum(df$income[1:7]) = 487
Weekending 2011-01-08 = sum(df$income[8:14]) = 387
Weekending 2011-01-15 = sum(df$income[15:21]) = 443

Однако я хотел бы более надежный подход, который будет автоматически суммироваться по неделям. Я не могу понять, как автоматически разделить данные на недели. Любая помощь приветствуется.


person Tony Breyal    schedule 09.07.2012    source источник
comment
Пожалуйста, не добавляйте ответы на вопросы. Если вы хотите ответить на свой вопрос, сделайте это в ответе (это рекомендуется на SO).   -  person Andrie    schedule 09.07.2012
comment
Ах, хорошо, я не знал, будет ли это плохим тоном. Я добавлю его, как вы предлагаете.   -  person Tony Breyal    schedule 09.07.2012


Ответы (7)


Сначала используйте format, чтобы преобразовать даты в номера недель, затем plyr::ddply(), чтобы вычислить итоги:

library(plyr)
df$week <- format(df$date, format="%Y-%U")
ddply(df, .(week), summarize, income=sum(income))
     week income
1 2011-52    413
2 2012-01    435
3 2012-02    379

Для получения дополнительной информации о format.date см. ?strptime, в частности бит, определяющий %U как номер недели.


ИЗМЕНИТЬ:

Учитывая измененные данные и требования, один из способов — разделить дату на 7, чтобы получить числовое число, обозначающее неделю. (Или, точнее, разделите на количество секунд в неделе, чтобы получить количество недель, прошедших с эпохи, которая по умолчанию равна 1970-01-01.

В коде:

df$week <- as.Date("1970-01-01")+7*trunc(as.numeric(df$date)/(3600*24*7))
library(plyr)
ddply(df, .(week), summarize, income=sum(income))

        week income
1 2010-12-23    298
2 2010-12-30    392
3 2011-01-06    294
4 2011-01-13    152

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

person Andrie    schedule 09.07.2012
comment
Почти готово, см. редактирование в моем вопросе о проблеме, которую я заметил с помощью этого подхода :) - person Tony Breyal; 09.07.2012
comment
спасибо любезно. Я придумал свое собственное решение, основанное на дальнейшем чтении ?format и plyr, как вы предложили. Я изменил data.frame в своем вопросе, чтобы лучше отразить то, что мне нужно, и я думаю, что именно поэтому ваши результаты не совсем то, что я искал (после применения вашего кода к новым данным), но с помощью вашего ответа я смог получить нужное мне решение (я добавил его выше). Большое спасибо, я не мог бы сделать это, не прочитав ваш подход :) - person Tony Breyal; 09.07.2012

Теперь это просто с помощью dplyr. Также я бы предложил использовать cut(breaks = "week"), а не format(), чтобы разрезать даты на недели.

library(dplyr)
df %>% group_by(week = cut(date, "week")) %>% mutate(weekly_income = sum(income))
person Jim    schedule 13.07.2015

Я погуглил "группировать дни недели в недели R" и наткнулся на этот вопрос SO. Вы упомянули, что у вас есть несколько лет, поэтому я думаю, что нам нужно идти в ногу как с номером недели, так и с годом, поэтому я изменил ответы так format(date, format = "%U%y")

В использовании это выглядит так:

library(plyr) #for aggregating
df <- transform(df, weeknum = format(date, format = "%y%U"))
ddply(df, "weeknum", summarize, suminc = sum(income))
#----
  weeknum suminc
1    1152    413
2    1201    435
3    1202    379

См. ?strptime для всех сокращений формата.

person Chase    schedule 09.07.2012
comment
Почти готово, см. редактирование в моем вопросе о проблеме, которую я заметил с помощью этого подхода :) - person Tony Breyal; 09.07.2012
comment
@TonyBreyal - похоже, что Эндри опередил меня, и у него есть более надежный ответ. Кроме того, ответ Двина на вопрос, который я связал выше, также соответствует этим принципам. Ваше здоровье. - person Chase; 09.07.2012
comment
большое спасибо за ссылку. Я видел это раньше, но не мог понять, как это применимо к моему требованию позволить неделе пересечь границу года. Теперь у меня есть решение, и я ценю вашу помощь :) - person Tony Breyal; 09.07.2012

Попробуйте rollapply из пакета zoo:

rollapply(df$income, width=7, FUN = sum, by = 7)
# [1] 487 387 443

Или используйте period.sum из пакета xts:

period.sum(xts(df$income, order.by=df$date), which(df$wday %in% 7))
#            [,1]
# 2011-01-01  487
# 2011-01-08  387
# 2011-01-15  443

Или, чтобы получить вывод в нужном формате:

data.frame(income = period.sum(xts(df$income, order.by=df$date), 
                               which(df$wday %in% 7)),
           week = df$week[which(df$wday %in% 7)])
#            income    week
# 2011-01-01    487 2011-00
# 2011-01-08    387 2011-01
# 2011-01-15    443 2011-02

Обратите внимание, что первая неделя отображается как 2011-00, потому что именно так она вводится в ваши данные. Вы также можете использовать week = df$week[which(df$wday %in% 1)], который будет соответствовать вашему выводу.

person A5C1D2H2I1M1N2O1R2T1    schedule 09.07.2012

На это решение повлияли @Andrie и @Chase.

# load plyr 
library(plyr)

# format weeks as per requirement (replace "00" with "52" and adjust corresponding year)
tmp <- list()
tmp$y <- format(df$date, format="%Y")
tmp$w <- format(df$date, format="%U")
tmp$y[tmp$w=="00"] <- as.character(as.numeric(tmp$y[tmp$w=="00"]) - 1)
tmp$w[tmp$w=="00"] <- "52"
df$week <- paste(tmp$y, tmp$w, sep = "-")

# get summary
df2 <- ddply(df, .(week), summarize, income=sum(income))

# include week ending date
tmp$week.ending <- lapply(df2$week, function(x) rev(df[df$week==x, "date"])[[1]])
df2$week.ending <- sapply(tmp$week.ending, as.character)

#      week income week.ending
# 1 2010-52    487  2011-01-01
# 2 2011-01    387  2011-01-08
# 3 2011-02    443  2011-01-15
person Community    schedule 09.07.2012

df.index = df['week'] #переменная dt как индекс

df.resample('W').sum() #сумма с использованием повторной выборки

person RunD.M.C.    schedule 18.01.2017

С dplyr:

df %>% 
  arrange(date) %>%
  mutate(week = as.numeric(date - date[1])%/%7) %>%
  group_by(week) %>%
  summarise(weekincome= sum(income))

Вместо даты[1] вы можете указать любую дату, с которой вы хотите начать еженедельное изучение.

person theLudo    schedule 20.11.2017