不同用户的滚动计数
rolling count of distinct users
我想计算具有可变时间的唯一用户的滚动计数 windows。这是我所拥有的和我想要的结果的示例。
have <- data.frame(user = c(1, 2,
2, 3,
1, 2, 3,
4,
3, 4,
4),
when = lubridate::ymd("2020-01-01",
"2020-01-01",
"2020-01-02",
"2020-01-02",
"2020-01-03",
"2020-01-03",
"2020-01-03",
"2020-01-05",
"2020-01-06",
"2020-01-06",
"2020-01-07"))
have
# user when
#1 1 2020-01-01
#2 2 2020-01-01
#3 2 2020-01-02
#4 3 2020-01-02
#5 1 2020-01-03
#6 2 2020-01-03
#7 3 2020-01-03 # note that Jan 4 is missing
#8 4 2020-01-05
#9 3 2020-01-06
#10 4 2020-01-06
#11 4 2020-01-07
want <- data.frame(when=c("2020-01-01",
"2020-01-02",
"2020-01-03",
"2020-01-04",
"2020-01-05",
"2020-01-06",
"2020-01-07"),
twoDayCount=c(2, # Jan 1: 1, 2
3, # Jan 1-2: 1, 2, 3
3, # Jan 2-3: 1, 2, 3
3, # Jan 3-4: 1, 2, 3
1, # Jan 4-5: 4
2, # Jan 5-6: 3, 4
2 # Jan 6-7: 3, 4
)
)
want
# when twoDayCount
#1 2020-01-01 2 # users: 1, 2
#2 2020-01-02 3 # users: 1, 2, 3
#3 2020-01-03 3 # users: 1, 2, 3
#4 2020-01-04 3 # users: 1, 2, 3
#5 2020-01-05 1 # users: 4
#6 2020-01-06 2 # users: 3, 4
#7 2020-01-07 2 # users: 3, 4
我尝试了一些方法,但他们让我计算每个 window 的所有行,而不是每个 window 的不同用户。例如,1 月 3 日所需的 2 天唯一用户数是 3(用户 1、2、3),而不是 5 行(用户 2 和 3 各出现两次)。
我的实际用例需要滚动 window 时间段(在本例中为 2 天)作为输入。
理想情况下,该解决方案适用于 {dbplyr}
可以转换为 sql 的函数,或者通过本机 sql 可以是 运行 和 {dbplyr}
。
This answer 给出了如何解决 sql:
的想法
SELECT when, count(DISTINCT user) AS dist_users
FROM (SELECT generate_series('2020-01-01'::date, '2020-01-07'::date, '1d')::date) AS g(when)
LEFT JOIN tbl t ON t.when BETWEEN g.when - 2 AND g.when
GROUP BY 1
ORDER BY 1;
使用 dplyr
和 tidyr
中的函数,对于 1 天 window 案例:
have %>%
group_by(when) %>%
summarise(twoDayCount = n_distinct(user))
对于更大的 windows:
window <- 2
have %>%
rowwise() %>%
mutate(when = list(when + lubridate::days(0:(window - 1)))) %>%
unnest(cols = when) %>%
group_by(when) %>%
summarise(twoDayCount = n_distinct(user))
请注意,此方法将为您提供稍后日期(在本例中为 1 月 8 日)的行,您可能希望将其删除。
如果性能是较大数据集的问题,这里有一个更快(但稍微不那么优雅)的解决方案:
window <- 2
seq.Date(min(have$when), max(have$when), by = "day") %>%
purrr::map(function(date) {
have %>%
filter(when <= date, when >= date - days(window - 1)) %>%
summarise(userCount = n_distinct(user)) %>%
mutate(when = date)
}) %>%
bind_rows()
循环可能有点笨拙。但似乎有效...
want <- data.frame(when = seq.Date(min(have$when), max(have$when), by = 1),
twoDayCount = NA)
for (iDate in min(want$when):(max(want$when))) {
dateWindow = c(iDate, iDate - 1)
uniqueUsers = unique(have$user[have$when %in% dateWindow])
want$twoDayCount[want$when == iDate] = length(uniqueUsers)
}
when twoDayCount
1 2020-01-01 2
2 2020-01-02 3
3 2020-01-03 3
4 2020-01-04 3
5 2020-01-05 1
6 2020-01-06 2
7 2020-01-07 2
这可能不会移植到 dbplyr。但是您可以使用 tidyverse 方法来解决这个问题。
您首先要创建一个嵌套数据框。 3 列。首先是日期。第二个是该日期的用户,第二个是前一天的用户(如果有)。然后,您可以使用 purrr::map2
对这些数据集应用一个函数,以了解您拥有多少唯一身份用户。
library(dplyr)
library(lubridate)
library(tidyr)
library(purrr)
# A function to get the number of distinct elements in a couple of dfs
num_distinct <- function(x,y){
length(unique(c(x$user,y$user)))
}
df <- have %>%
distinct() %>%
group_by(when) %>%
nest() %>%
ungroup() %>%
inner_join(
have %>%
distinct() %>%
group_by(when) %>%
nest() %>%
ungroup() %>%
mutate(when = when + days(1)) %>%
rename(lag = data)
)
# calculate the rolling number of uniques
df %>%
mutate(rolling = map2(data, lag, num_distinct)) %>%
select(-data, -lag) %>%
unnest(rolling)
这仅显示具有实际 2 天可用时段的日期的结果,因此可能需要根据您是否希望包括在内进行修改。
对于非常大的数据集,可扩展的解决方案是使用 data.table。在下面的示例中,我展示了如果 day 是自开始日期以来的天数,这将如何工作。
library(tidyverse)
library(data.table)
window <- 30
dt <- tibble(day = seq(1:10000)) %>%
mutate(user = purrr::map(day, function(.) sample(1:10000, 10000, replace = TRUE))) %>%
unnest(user) %>%
as.data.table()
all_res <- list()
setkey(dt, day)
tracker <- 1
for(dd in unique(dt$day)){
sub_dd <- dt[.(max(1,(dd-window)):dd)]
all_res[[tracker]] <- tibble(day = dd, users =
length(unique(sub_dd[,user])))
tracker <- tracker + 1
}
all_res <- all_res %>%
bind_rows()
这里的关键是设置key,使data.table可以使用二分查找来加速过滤https://cran.r-project.org/web/packages/data.table/vignettes/datatable-keys-fast-subset.html。
我想计算具有可变时间的唯一用户的滚动计数 windows。这是我所拥有的和我想要的结果的示例。
have <- data.frame(user = c(1, 2,
2, 3,
1, 2, 3,
4,
3, 4,
4),
when = lubridate::ymd("2020-01-01",
"2020-01-01",
"2020-01-02",
"2020-01-02",
"2020-01-03",
"2020-01-03",
"2020-01-03",
"2020-01-05",
"2020-01-06",
"2020-01-06",
"2020-01-07"))
have
# user when
#1 1 2020-01-01
#2 2 2020-01-01
#3 2 2020-01-02
#4 3 2020-01-02
#5 1 2020-01-03
#6 2 2020-01-03
#7 3 2020-01-03 # note that Jan 4 is missing
#8 4 2020-01-05
#9 3 2020-01-06
#10 4 2020-01-06
#11 4 2020-01-07
want <- data.frame(when=c("2020-01-01",
"2020-01-02",
"2020-01-03",
"2020-01-04",
"2020-01-05",
"2020-01-06",
"2020-01-07"),
twoDayCount=c(2, # Jan 1: 1, 2
3, # Jan 1-2: 1, 2, 3
3, # Jan 2-3: 1, 2, 3
3, # Jan 3-4: 1, 2, 3
1, # Jan 4-5: 4
2, # Jan 5-6: 3, 4
2 # Jan 6-7: 3, 4
)
)
want
# when twoDayCount
#1 2020-01-01 2 # users: 1, 2
#2 2020-01-02 3 # users: 1, 2, 3
#3 2020-01-03 3 # users: 1, 2, 3
#4 2020-01-04 3 # users: 1, 2, 3
#5 2020-01-05 1 # users: 4
#6 2020-01-06 2 # users: 3, 4
#7 2020-01-07 2 # users: 3, 4
我尝试了一些方法,但他们让我计算每个 window 的所有行,而不是每个 window 的不同用户。例如,1 月 3 日所需的 2 天唯一用户数是 3(用户 1、2、3),而不是 5 行(用户 2 和 3 各出现两次)。
我的实际用例需要滚动 window 时间段(在本例中为 2 天)作为输入。
理想情况下,该解决方案适用于 {dbplyr}
可以转换为 sql 的函数,或者通过本机 sql 可以是 运行 和 {dbplyr}
。
This answer 给出了如何解决 sql:
的想法SELECT when, count(DISTINCT user) AS dist_users
FROM (SELECT generate_series('2020-01-01'::date, '2020-01-07'::date, '1d')::date) AS g(when)
LEFT JOIN tbl t ON t.when BETWEEN g.when - 2 AND g.when
GROUP BY 1
ORDER BY 1;
使用 dplyr
和 tidyr
中的函数,对于 1 天 window 案例:
have %>%
group_by(when) %>%
summarise(twoDayCount = n_distinct(user))
对于更大的 windows:
window <- 2
have %>%
rowwise() %>%
mutate(when = list(when + lubridate::days(0:(window - 1)))) %>%
unnest(cols = when) %>%
group_by(when) %>%
summarise(twoDayCount = n_distinct(user))
请注意,此方法将为您提供稍后日期(在本例中为 1 月 8 日)的行,您可能希望将其删除。
如果性能是较大数据集的问题,这里有一个更快(但稍微不那么优雅)的解决方案:
window <- 2
seq.Date(min(have$when), max(have$when), by = "day") %>%
purrr::map(function(date) {
have %>%
filter(when <= date, when >= date - days(window - 1)) %>%
summarise(userCount = n_distinct(user)) %>%
mutate(when = date)
}) %>%
bind_rows()
循环可能有点笨拙。但似乎有效...
want <- data.frame(when = seq.Date(min(have$when), max(have$when), by = 1),
twoDayCount = NA)
for (iDate in min(want$when):(max(want$when))) {
dateWindow = c(iDate, iDate - 1)
uniqueUsers = unique(have$user[have$when %in% dateWindow])
want$twoDayCount[want$when == iDate] = length(uniqueUsers)
}
when twoDayCount
1 2020-01-01 2
2 2020-01-02 3
3 2020-01-03 3
4 2020-01-04 3
5 2020-01-05 1
6 2020-01-06 2
7 2020-01-07 2
这可能不会移植到 dbplyr。但是您可以使用 tidyverse 方法来解决这个问题。
您首先要创建一个嵌套数据框。 3 列。首先是日期。第二个是该日期的用户,第二个是前一天的用户(如果有)。然后,您可以使用 purrr::map2
对这些数据集应用一个函数,以了解您拥有多少唯一身份用户。
library(dplyr)
library(lubridate)
library(tidyr)
library(purrr)
# A function to get the number of distinct elements in a couple of dfs
num_distinct <- function(x,y){
length(unique(c(x$user,y$user)))
}
df <- have %>%
distinct() %>%
group_by(when) %>%
nest() %>%
ungroup() %>%
inner_join(
have %>%
distinct() %>%
group_by(when) %>%
nest() %>%
ungroup() %>%
mutate(when = when + days(1)) %>%
rename(lag = data)
)
# calculate the rolling number of uniques
df %>%
mutate(rolling = map2(data, lag, num_distinct)) %>%
select(-data, -lag) %>%
unnest(rolling)
这仅显示具有实际 2 天可用时段的日期的结果,因此可能需要根据您是否希望包括在内进行修改。
对于非常大的数据集,可扩展的解决方案是使用 data.table。在下面的示例中,我展示了如果 day 是自开始日期以来的天数,这将如何工作。
library(tidyverse)
library(data.table)
window <- 30
dt <- tibble(day = seq(1:10000)) %>%
mutate(user = purrr::map(day, function(.) sample(1:10000, 10000, replace = TRUE))) %>%
unnest(user) %>%
as.data.table()
all_res <- list()
setkey(dt, day)
tracker <- 1
for(dd in unique(dt$day)){
sub_dd <- dt[.(max(1,(dd-window)):dd)]
all_res[[tracker]] <- tibble(day = dd, users =
length(unique(sub_dd[,user])))
tracker <- tracker + 1
}
all_res <- all_res %>%
bind_rows()
这里的关键是设置key,使data.table可以使用二分查找来加速过滤https://cran.r-project.org/web/packages/data.table/vignettes/datatable-keys-fast-subset.html。