我有不规则的时间序列数据,代表用户的某种交易类型。每行数据都带有时间戳,表示当时的事务。根据数据的不规则性,一些用户一天可能有100行,而其他用户一天可能有0或1笔交易。
数据可能如下所示:
data.frame(
id = c(1, 1, 1, 1, 1, 2, 2, 3, 4),
date = c("2015-01-01",
"2015-01-01",
"2015-01-05",
"2015-01-25",
"2015-02-15",
"2015-05-05",
"2015-01-01",
"2015-08-01",
"2015-01-01"),
n_widgets = c(1,2,3,4,4,5,2,4,5)
)
id date n_widgets
1 1 2015-01-01 1
2 1 2015-01-01 2
3 1 2015-01-05 3
4 1 2015-01-25 4
5 1 2015-02-15 4
6 2 2015-05-05 5
7 2 2015-01-01 2
8 3 2015-08-01 4
9 4 2015-01-01 5
我经常想知道一些有关用户的滚动统计信息。例如:对于某个特定日期的用户,过去30天内发生的交易次数,过去30天内售出的小部件数量等。
对应上面的例子,数据应该如下:
id date n_widgets n_trans_30 total_widgets_30
1 1 2015-01-01 1 1 1
2 1 2015-01-01 2 2 3
3 1 2015-01-05 3 3 6
4 1 2015-01-25 4 4 10
5 1 2015-02-15 4 2 8
6 2 2015-05-05 5 1 5
7 2 2015-01-01 2 1 2
8 3 2015-08-01 4 1 4
9 4 2015-01-01 5 1 5
如果时间窗口是每天,则解决方案很简单:data %>% group_by(id, date) %>% summarize(...)
同样,如果时间窗口是每月,那么使用lubridate:data %>% group_by(id, year(date), month(date)) %>% summarize(...)
然而,我遇到的挑战是如何设置任意时间段的时间窗口:5天,10天等。
还有RcppRoll
库,RcppRoll
和zoo
中的滚动功能似乎都是常规时间序列的更多设置。据我所知,这些窗口函数基于行数而不是指定的时间段工作 - 关键区别在于某个时间段可能具有不同的行数,具体取决于日期和用户。
例如,对于用户1,2015-01-01
之前5天内的交易数量等于100个交易,而同一用户的交易数量可能是{{1}之前5天内的交易数量。 1}}等于5个事务。因此,回顾一定数量的行将无法正常工作。
此外,还有另一个SO线程讨论不规则时间序列类型数据(Create new column based on condition that exists within a rolling date)的滚动日期,但是接受的解决方案是使用2015-02-01
而我正专门寻找data.table
实现这一目标的方式。
我想这个问题的核心是,这个问题可以通过回答这个问题来解决:我如何在dplyr
中group_by
dplyr
任意时间段。或者,如果在没有复杂dplyr
的情况下有不同的group_by
方式实现上述目标,我该怎么办呢?
编辑:更新示例,使滚动窗口的性质更加清晰。
答案 0 :(得分:4)
这可以使用SQL来完成:
library(sqldf)
dd <- transform(data, date = as.Date(date))
sqldf("select a.*, count(*) n_trans30, sum(b.n_widgets) 'total_widgets30'
from dd a
left join dd b on b.date between a.date - 30 and a.date
and b.id = a.id
and b.rowid <= a.rowid
group by a.rowid")
,并提供:
id date n_widgets n_trans30 total_widgets30
1 1 2015-01-01 1 1 1
2 1 2015-01-01 2 2 3
3 1 2015-01-05 3 3 6
4 1 2015-01-25 4 4 10
5 2 2015-05-05 5 1 5
6 2 2015-01-01 2 1 2
7 3 2015-08-01 4 1 4
8 4 2015-01-01 5 1 5
答案 1 :(得分:2)
另一种方法是扩展数据集以包含所有可能的天数(使用tidyr::complete
),然后使用滚动函数(RcppRoll::roll_sum
)
你每天有多次观察的事实可能会产生一个问题,但是......
library(tidyr)
library(RcppRoll)
df2 <- df %>%
mutate(date=as.Date(date))
## create full dataset with all possible dates (go even 30 days back for first observation)
df_full<- df2 %>%
mutate(date=as.Date(date)) %>%
complete(id,
date=seq(from=min(.$date)-30,to=max(.$date), by=1),
fill=list(n_widgets=0))
## now use rolling function, and keep only original rows (left join)
df_roll <- df_full %>%
group_by(id) %>%
mutate(n_trans_30=roll_sum(x=n_widgets!=0, n=30, fill=0, align="right"),
total_widgets_30=roll_sum(x=n_widgets, n=30, fill=0, align="right")) %>%
ungroup() %>%
right_join(df2, by = c("date", "id", "n_widgets"))
结果与您的相同(偶然)
id date n_widgets n_trans_30 total_widgets_30
<dbl> <date> <dbl> <dbl> <dbl>
1 1 2015-01-01 1 1 1
2 1 2015-01-01 2 2 3
3 1 2015-01-05 3 3 6
4 1 2015-01-25 4 4 10
5 1 2015-02-15 4 2 8
6 2 2015-05-05 5 1 5
7 2 2015-01-01 2 1 2
8 3 2015-08-01 4 1 4
9 4 2015-01-01 5 1 5
但是如上所述,它将会失败一段时间,因为它计算的是最后30天,而不是最近30天。因此,您可能希望白天首先summarise
信息,然后应用此信息。
答案 2 :(得分:1)
我在研究question
时发现了一种方法df <- data.frame(
id = c(1, 1, 1, 1, 1, 2, 2, 3, 4),
date = c("2015-01-01",
"2015-01-01",
"2015-01-05",
"2015-01-25",
"2015-02-15",
"2015-05-05",
"2015-01-01",
"2015-08-01",
"2015-01-01"),
n_widgets = c(1,2,3,4,4,5,2,4,5)
)
count_window <- function(df, date2, w, id2){
min_date <- date2 - w
df2 <- df %>% filter(id == id2, date >= min_date, date <= date2)
out <- length(df2$date)
return(out)
}
v_count_window <- Vectorize(count_window, vectorize.args = c("date2","id2"))
sum_window <- function(df, date2, w, id2){
min_date <- date2 - w
df2 <- df %>% filter(id == id2, date >= min_date, date <= date2)
out <- sum(df2$n_widgets)
return(out)
}
v_sum_window <- Vectorize(sum_window, vectorize.args = c("date2","id2"))
res <- df %>% mutate(date = ymd(date)) %>%
mutate(min_date = date - 30,
n_trans = v_count_window(., date, 30, id),
total_widgets = v_sum_window(., date, 30, id)) %>%
select(id, date, n_widgets, n_trans, total_widgets)
res
id date n_widgets n_trans total_widgets
1 1 2015-01-01 1 2 3
2 1 2015-01-01 2 2 3
3 1 2015-01-05 3 3 6
4 1 2015-01-25 4 4 10
5 1 2015-02-15 4 2 8
6 2 2015-05-05 5 1 5
7 2 2015-01-01 2 1 2
8 3 2015-08-01 4 1 4
9 4 2015-01-01 5 1 5
此版本相当具体,但您可能可以制作更通用的功能版本。
答案 3 :(得分:1)
为简单起见,我建议使用runner软件包来处理滑动窗口操作。在OP请求中,窗口大小k = 30
和窗口取决于日期idx = date
。您可以使用runner
函数,该函数在给定窗口上应用任何R函数,并且sum_run
library(runner)
library(dplyr)
df %>%
group_by(id) %>%
arrange(date, .by_group = TRUE) %>%
mutate(
n_trans30 = runner(n_widgets, k = 30, idx = date, function(x) length(x)),
n_widgets30 = sum_run(n_widgets, k = 30, idx = date),
)
# id date n_widgets n_trans30 n_widgets30
#<dbl> <date> <dbl> <dbl> <dbl>
# 1 2015-01-01 1 1 1
# 1 2015-01-01 2 2 3
# 1 2015-01-05 3 3 6
# 1 2015-01-25 4 4 10
# 1 2015-02-15 4 2 8
# 2 2015-01-01 2 1 2
# 2 2015-05-05 5 1 5
# 3 2015-08-01 4 1 4
# 4 2015-01-01 5 1 5
重要提示:idx = date
应该按升序排列。
有关更多信息,请访问documentation和vignettes
答案 4 :(得分:0)
根据以下评论编辑。
您最多可以尝试5天这样的事情:
df %>%
arrange(id, date) %>%
group_by(id) %>%
filter(as.numeric(difftime(Sys.Date(), date, unit = 'days')) <= 5) %>%
summarise(n_total_widgets = sum(n_widgets))
在这种情况下,目前没有五天之内。因此,它不会产生任何输出。
要获得每个ID的最后五天,您可以执行以下操作:
df %>%
arrange(id, date) %>%
group_by(id) %>%
filter(as.numeric(difftime(max(date), date, unit = 'days')) <= 5) %>%
summarise(n_total_widgets = sum(n_widgets))
产生的结果将是:
Source: local data frame [4 x 2]
id n_total_widgets
(dbl) (dbl)
1 1 4
2 2 5
3 3 4
4 4 5