R 日期内唯一值的条件计数 range/window
R conditional count of unique value over date range/window
在 R 中,如何计算在一个时间范围内满足条件的观测值的数量?
具体来说,我想计算过去 8 个月内 id
与 country
的不同数量,但前提是 id
在这 8 个月内至少出现两次。因此,对于计数而言,id
出现 2 次还是 100 次并不重要(分两步进行可能更容易)。 NA
同时存在于id
和country
中。由于这可以通过其他方式解决,因此没有必要考虑这一点,但仍然有帮助。
我目前最好的尝试是,但没有考虑限制(ID 必须在前 8 个月内至少出现两次)而且我在查看 dates="2017-12-12"
时发现它的计数很奇怪,其中 desired_unrestricted
根据我的计算应该等于 4 但代码给出 2.
dt[, date := as.Date(date)][
, totalids := sapply(date,
function(x) length(unique(id[between(date, x - lubridate::month(8), x)]))),
by = country]
数据
library(data.table)
library(lubridate)
ID <- c("1","1","1","1","1","1","2","2","2","3","3",NA,"4")
Date <- c("2017-01-01","2017-01-01", "2017-01-05", "2017-05-01", "2017-05-01","2018-05-02","2017-01-01", "2017-01-05", "2017-05-01", "2017-05-01","2017-05-01","2017-12-12","2017-12-12" )
Value <- c(2,4,3,5,2,5,8,17,17,3,7,5,3)
Country <- c("UK","UK","US","US",NA,"US","UK","UK","US","US","US","US","US")
Desired <- c(1,1,0,2,NA,0,1,2,2,2,2,1,1)
Desired_unrestricted <- c(2,2,1,3,NA,1,2,2,3,3,3,4,4)
dt <- data.frame(id=ID, date=Date, value=Value, country=Country, desired_output=Desired, desired_unrestricted=Desired_unrestricted)
setDT(dt)
提前致谢。
虽然这个问题被标记为 data.table
,但这里有一个 dplyr::rowwise
解决问题的方法。这是你的想法吗?输出对我来说是有效的:过去 8 个月中 ìd
的数量至少大于 2
.
library(dplyr)
library(lubridate)
dt <- dt %>% mutate(date = as.Date(date))
dt %>%
group_by(country) %>%
group_modify(~ .x %>%
rowwise() %>%
mutate(totalids = .x %>%
filter(date <= .env$date, date >= .env$date %m-% months(8)) %>%
pull(id) %>%
table() %>%
`[`(. >1) %>%
length
))
#> # A tibble: 13 x 7
#> # Groups: country [3]
#> country id date value desired_output desired_unrestricted totalids
#> <chr> <chr> <date> <dbl> <dbl> <dbl> <int>
#> 1 UK 1 2017-01-01 2 1 2 1
#> 2 UK 1 2017-01-01 4 1 2 1
#> 3 UK 2 2017-01-01 8 1 2 1
#> 4 UK 2 2017-01-05 17 2 2 2
#> 5 US 1 2017-01-05 3 0 1 0
#> 6 US 1 2017-05-01 5 1 3 2
#> 7 US 1 2018-05-02 5 0 1 0
#> 8 US 2 2017-05-01 17 1 3 2
#> 9 US 3 2017-05-01 3 2 3 2
#> 10 US 3 2017-05-01 7 2 3 2
#> 11 US <NA> 2017-12-12 5 2 4 1
#> 12 US 4 2017-12-12 3 2 4 1
#> 13 <NA> 1 2017-05-01 2 NA NA 0
由 reprex package (v2.0.1)
于 2021-09-02 创建
这个 data.table
-only 答案的动机是 comment,
dt[, date := as.Date(date)] # if not already `Date`-class
dt[, date8 := do.call(c, lapply(dt$date, function(z) seq(z, length=2, by="-8 months")[2]))
][, results := dt[dt, on = .(country, date > date8, date <= date),
length(Filter(function(z) z > 1, table(id))), by = .EACHI]$V1
][, date8 := NULL ]
# id date value country desired_output desired_unrestricted results
# <char> <Date> <num> <char> <num> <num> <int>
# 1: 1 2017-01-01 2 UK 1 2 1
# 2: 1 2017-01-01 4 UK 1 2 1
# 3: 1 2017-01-05 3 US 0 1 0
# 4: 1 2017-05-01 5 US 1 3 2
# 5: 1 2017-05-01 2 <NA> NA NA 0
# 6: 1 2018-05-02 5 US 0 1 0
# 7: 2 2017-01-01 8 UK 1 2 1
# 8: 2 2017-01-05 17 UK 2 2 2
# 9: 2 2017-05-01 17 US 1 3 2
# 10: 3 2017-05-01 3 US 2 3 2
# 11: 3 2017-05-01 7 US 2 3 2
# 12: <NA> 2017-12-12 5 US 2 4 1
# 13: 4 2017-12-12 3 US 2 4 1
需要吸收的东西很多。
快速浏览:
“8 个月前”:
seq(z, length=2, by="-8 months")[2]
seq.Date
(通过使用 Date
-class 第一个参数调用 seq
推断)从 z
开始(当前 date
每行)并产生一个长度为 2 的序列,它们之间有 8 个月。 seq
总是从第一个参数开始,所以 length=1
不起作用(它只会 return z
); length=2
保证 returned 向量中的第二个值将是我们需要的“date
之前的 8 个月”。
日期减法:
[, date8 := do.call(c, lapply(dt$date, function(z) seq(...)[2])) ]
减去 8 个月的简单 base-R 方法是 seq(date, length=2, by="-8 months")[2]
。 seq.Date
要求它的第一个参数是length-1,所以我们需要sapply
或者lapply
它;不幸的是,sapply
删除了 class,所以我们 lapply
它然后以编程方式 c
将它们与 do.call(c, ...)
组合(因为 c(..)
创建了一个列表-列,unlist
将取消 class 它)。 (也许这部分可以改进。)
我们在 dt
first 中需要它,因为我们基于此值进行非等值(基于范围)连接。
计数 id
有 2 次或更多次访问:
length(Filter(function(z) z > 1, table(id)))
我们生成了一个 table(id)
,它为我们提供了加入周期内每个 id
的计数。 Filter(fun, ...)
允许我们减少计数低于 2 的那些,我们剩下的是具有 2 次或更多次访问的 id
的命名向量。检索 length
是我们需要的。
自非相等连接:
dt[dt, on = .(country, date > date8, date <= date), ... ]
相对简单。这是一个open/closed测距,如果你喜欢可以改成双关
自非相等连接但按行计数 id
s:by=.EACHI
.
检索结果并赋值给原来的dt
:
[, results := dt[...]$V1 ]
由于非相等连接包含一个没有名称的值 (length(Filter(...))
),因此它被命名为 V1
,而我们想要的就是这个。 (说实话,我不明白为什么更直接赋值不行...但是计数都是错的,可能是倒着按行算吧。)
清理:
[, date8 := NULL ]
(这里没什么特别的,只是适当的数据管理:-)
我的计数与您的 desired_output
有一些差异,我想知道这些是否只是 OP 中的拼写错误;我认为数学是正确的...
这是另一个选项:
setkey(dt, country, date, id)
dt[, date := as.IDate(date)][,
eightmthsago := as.IDate(sapply(as.IDate(date), function(x) seq(x, by="-8 months", length.out=2L)[2L]))]
dt[, c("out", "out_unres") :=
dt[dt, on=.(country, date>=eightmthsago, date<=date),
by=.EACHI, {
v <- id[!is.na(id)]
.(uniqueN(v[duplicated(v)]), uniqueN(v))
}][,1L:3L := NULL]
]
dt
输出(与 r2evans 一样,我也得到与期望不同的输出,因为期望输出中似乎有错误计数):
id date value country desired_output desired_unrestricted eightmthsago out out_unres
1: 1 2017-05-01 2 <NA> NA NA 2016-09-01 0 1
2: 1 2017-01-01 2 UK 1 2 2016-05-01 1 2
3: 1 2017-01-01 4 UK 1 2 2016-05-01 1 2
4: 2 2017-01-01 8 UK 1 2 2016-05-01 1 2
5: 2 2017-01-05 17 UK 2 2 2016-05-05 2 2
6: 1 2017-01-05 3 US 0 1 2016-05-05 0 1
7: 1 2017-05-01 5 US 1 3 2016-09-01 2 3
8: 2 2017-05-01 17 US 1 3 2016-09-01 2 3
9: 3 2017-05-01 3 US 2 3 2016-09-01 2 3
10: 3 2017-05-01 7 US 2 3 2016-09-01 2 3
11: <NA> 2017-12-12 5 US 2 4 2017-04-12 1 4
12: 4 2017-12-12 3 US 2 4 2017-04-12 1 4
13: 1 2018-05-02 5 US 0 1 2017-09-02 0 2
在 R 中,如何计算在一个时间范围内满足条件的观测值的数量?
具体来说,我想计算过去 8 个月内 id
与 country
的不同数量,但前提是 id
在这 8 个月内至少出现两次。因此,对于计数而言,id
出现 2 次还是 100 次并不重要(分两步进行可能更容易)。 NA
同时存在于id
和country
中。由于这可以通过其他方式解决,因此没有必要考虑这一点,但仍然有帮助。
我目前最好的尝试是,但没有考虑限制(ID 必须在前 8 个月内至少出现两次)而且我在查看 dates="2017-12-12"
时发现它的计数很奇怪,其中 desired_unrestricted
根据我的计算应该等于 4 但代码给出 2.
dt[, date := as.Date(date)][
, totalids := sapply(date,
function(x) length(unique(id[between(date, x - lubridate::month(8), x)]))),
by = country]
数据
library(data.table)
library(lubridate)
ID <- c("1","1","1","1","1","1","2","2","2","3","3",NA,"4")
Date <- c("2017-01-01","2017-01-01", "2017-01-05", "2017-05-01", "2017-05-01","2018-05-02","2017-01-01", "2017-01-05", "2017-05-01", "2017-05-01","2017-05-01","2017-12-12","2017-12-12" )
Value <- c(2,4,3,5,2,5,8,17,17,3,7,5,3)
Country <- c("UK","UK","US","US",NA,"US","UK","UK","US","US","US","US","US")
Desired <- c(1,1,0,2,NA,0,1,2,2,2,2,1,1)
Desired_unrestricted <- c(2,2,1,3,NA,1,2,2,3,3,3,4,4)
dt <- data.frame(id=ID, date=Date, value=Value, country=Country, desired_output=Desired, desired_unrestricted=Desired_unrestricted)
setDT(dt)
提前致谢。
虽然这个问题被标记为 data.table
,但这里有一个 dplyr::rowwise
解决问题的方法。这是你的想法吗?输出对我来说是有效的:过去 8 个月中 ìd
的数量至少大于 2
.
library(dplyr)
library(lubridate)
dt <- dt %>% mutate(date = as.Date(date))
dt %>%
group_by(country) %>%
group_modify(~ .x %>%
rowwise() %>%
mutate(totalids = .x %>%
filter(date <= .env$date, date >= .env$date %m-% months(8)) %>%
pull(id) %>%
table() %>%
`[`(. >1) %>%
length
))
#> # A tibble: 13 x 7
#> # Groups: country [3]
#> country id date value desired_output desired_unrestricted totalids
#> <chr> <chr> <date> <dbl> <dbl> <dbl> <int>
#> 1 UK 1 2017-01-01 2 1 2 1
#> 2 UK 1 2017-01-01 4 1 2 1
#> 3 UK 2 2017-01-01 8 1 2 1
#> 4 UK 2 2017-01-05 17 2 2 2
#> 5 US 1 2017-01-05 3 0 1 0
#> 6 US 1 2017-05-01 5 1 3 2
#> 7 US 1 2018-05-02 5 0 1 0
#> 8 US 2 2017-05-01 17 1 3 2
#> 9 US 3 2017-05-01 3 2 3 2
#> 10 US 3 2017-05-01 7 2 3 2
#> 11 US <NA> 2017-12-12 5 2 4 1
#> 12 US 4 2017-12-12 3 2 4 1
#> 13 <NA> 1 2017-05-01 2 NA NA 0
由 reprex package (v2.0.1)
于 2021-09-02 创建这个 data.table
-only 答案的动机是 comment,
dt[, date := as.Date(date)] # if not already `Date`-class
dt[, date8 := do.call(c, lapply(dt$date, function(z) seq(z, length=2, by="-8 months")[2]))
][, results := dt[dt, on = .(country, date > date8, date <= date),
length(Filter(function(z) z > 1, table(id))), by = .EACHI]$V1
][, date8 := NULL ]
# id date value country desired_output desired_unrestricted results
# <char> <Date> <num> <char> <num> <num> <int>
# 1: 1 2017-01-01 2 UK 1 2 1
# 2: 1 2017-01-01 4 UK 1 2 1
# 3: 1 2017-01-05 3 US 0 1 0
# 4: 1 2017-05-01 5 US 1 3 2
# 5: 1 2017-05-01 2 <NA> NA NA 0
# 6: 1 2018-05-02 5 US 0 1 0
# 7: 2 2017-01-01 8 UK 1 2 1
# 8: 2 2017-01-05 17 UK 2 2 2
# 9: 2 2017-05-01 17 US 1 3 2
# 10: 3 2017-05-01 3 US 2 3 2
# 11: 3 2017-05-01 7 US 2 3 2
# 12: <NA> 2017-12-12 5 US 2 4 1
# 13: 4 2017-12-12 3 US 2 4 1
需要吸收的东西很多。
快速浏览:
“8 个月前”:
seq(z, length=2, by="-8 months")[2]
seq.Date
(通过使用Date
-class 第一个参数调用seq
推断)从z
开始(当前date
每行)并产生一个长度为 2 的序列,它们之间有 8 个月。seq
总是从第一个参数开始,所以length=1
不起作用(它只会 returnz
);length=2
保证 returned 向量中的第二个值将是我们需要的“date
之前的 8 个月”。日期减法:
[, date8 := do.call(c, lapply(dt$date, function(z) seq(...)[2])) ]
减去 8 个月的简单 base-R 方法是
seq(date, length=2, by="-8 months")[2]
。seq.Date
要求它的第一个参数是length-1,所以我们需要sapply
或者lapply
它;不幸的是,sapply
删除了 class,所以我们lapply
它然后以编程方式c
将它们与do.call(c, ...)
组合(因为c(..)
创建了一个列表-列,unlist
将取消 class 它)。 (也许这部分可以改进。)我们在
dt
first 中需要它,因为我们基于此值进行非等值(基于范围)连接。计数
id
有 2 次或更多次访问:length(Filter(function(z) z > 1, table(id)))
我们生成了一个
table(id)
,它为我们提供了加入周期内每个id
的计数。Filter(fun, ...)
允许我们减少计数低于 2 的那些,我们剩下的是具有 2 次或更多次访问的id
的命名向量。检索length
是我们需要的。自非相等连接:
dt[dt, on = .(country, date > date8, date <= date), ... ]
相对简单。这是一个open/closed测距,如果你喜欢可以改成双关
自非相等连接但按行计数
id
s:by=.EACHI
.检索结果并赋值给原来的
dt
:[, results := dt[...]$V1 ]
由于非相等连接包含一个没有名称的值 (
length(Filter(...))
),因此它被命名为V1
,而我们想要的就是这个。 (说实话,我不明白为什么更直接赋值不行...但是计数都是错的,可能是倒着按行算吧。)清理:
[, date8 := NULL ]
(这里没什么特别的,只是适当的数据管理:-)
我的计数与您的 desired_output
有一些差异,我想知道这些是否只是 OP 中的拼写错误;我认为数学是正确的...
这是另一个选项:
setkey(dt, country, date, id)
dt[, date := as.IDate(date)][,
eightmthsago := as.IDate(sapply(as.IDate(date), function(x) seq(x, by="-8 months", length.out=2L)[2L]))]
dt[, c("out", "out_unres") :=
dt[dt, on=.(country, date>=eightmthsago, date<=date),
by=.EACHI, {
v <- id[!is.na(id)]
.(uniqueN(v[duplicated(v)]), uniqueN(v))
}][,1L:3L := NULL]
]
dt
输出(与 r2evans 一样,我也得到与期望不同的输出,因为期望输出中似乎有错误计数):
id date value country desired_output desired_unrestricted eightmthsago out out_unres
1: 1 2017-05-01 2 <NA> NA NA 2016-09-01 0 1
2: 1 2017-01-01 2 UK 1 2 2016-05-01 1 2
3: 1 2017-01-01 4 UK 1 2 2016-05-01 1 2
4: 2 2017-01-01 8 UK 1 2 2016-05-01 1 2
5: 2 2017-01-05 17 UK 2 2 2016-05-05 2 2
6: 1 2017-01-05 3 US 0 1 2016-05-05 0 1
7: 1 2017-05-01 5 US 1 3 2016-09-01 2 3
8: 2 2017-05-01 17 US 1 3 2016-09-01 2 3
9: 3 2017-05-01 3 US 2 3 2016-09-01 2 3
10: 3 2017-05-01 7 US 2 3 2016-09-01 2 3
11: <NA> 2017-12-12 5 US 2 4 2017-04-12 1 4
12: 4 2017-12-12 3 US 2 4 2017-04-12 1 4
13: 1 2018-05-02 5 US 0 1 2017-09-02 0 2