Here are two data.table solutions, one with two lapply nested, and the other with nonequilibrium connections.
The first is a rather clumsy solution to data.table , but it plays the expected answer. And this will work for an arbitrary number of time frames. (Although the @alistaire solution, the short tidyverse that he proposed in his comment, is also subject to change).
It uses two nested lapply . The first cycle goes through time frames, the second - according to dates. The result of tempory is combined with the source data and then converted from long to wide format, so we will end up with a separate column for each of the time frames.
library(data.table) tmp <- rbindlist( lapply(c(7L, 14L), function(ldays) rbindlist( lapply(unique(dt$date), function(ldate) { dt[between(date, ldate - ldays, ldate), .(distinct = sprintf("distinct_%02i", ldays), date = ldate, N = uniqueN(category)), by = .(user_id)] }) ) ) ) dcast(tmp[dt, on=c("user_id", "date")], ... ~ distinct, value.var = "N")[order(-user_id, date, category)] # date user_id category distinct_07 distinct_14 # 1: 2016-01-01 27 apple 1 1 # 2: 2016-01-03 27 apple 1 1 # 3: 2016-01-05 27 pear 2 2 # 4: 2016-01-07 27 plum 3 3 # 5: 2016-01-10 27 apple 3 3 # 6: 2016-01-14 27 pear 3 3 # 7: 2016-01-16 27 plum 3 3 # 8: 2016-01-01 11 apple 1 1 # 9: 2016-01-03 11 pear 2 2 #10: 2016-01-05 11 pear 2 2 #11: 2016-01-07 11 pear 2 2 #12: 2016-01-10 11 apple 2 2 #13: 2016-01-14 11 apple 2 2 #14: 2016-01-16 11 apple 1 2
Here is an option suggested by @Frank that uses data.table not an equi connection instead of the second lapply :
tmp <- rbindlist( lapply(c(7L, 14L), function(ldays) { dt[.(user_id = user_id, dago = date - ldays, d = date), on=.(user_id, date >= dago, date <= d), .(distinct = sprintf("distinct_%02i", ldays), N = uniqueN(category)), by = .EACHI] } ) )[, date := NULL] # dcast(tmp[dt, on=c("user_id", "date")], ... ~ distinct, value.var = "N")[order(-user_id, date, category)]
Data:
dt <- fread("user_id date category 27 2016-01-01 apple 27 2016-01-03 apple 27 2016-01-05 pear 27 2016-01-07 plum 27 2016-01-10 apple 27 2016-01-14 pear 27 2016-01-16 plum 11 2016-01-01 apple 11 2016-01-03 pear 11 2016-01-05 pear 11 2016-01-07 pear 11 2016-01-10 apple 11 2016-01-14 apple 11 2016-01-16 apple") dt[, date := as.IDate(date)]
BTW: the wording for the last 7, 14 days is somewhat misleading, as the time periods consist of 8 and 15 days, respectively.