R 日期内唯一值的条件计数 range/window

R conditional count of unique value over date range/window

在 R 中,如何计算在一个时间范围内满足条件的观测值的数量? 具体来说,我想计算过去 8 个月内 idcountry 的不同数量,但前提是 id 在这 8 个月内至少出现两次。因此,对于计数而言,id 出现 2 次还是 100 次并不重要(分两步进行可能更容易)。 NA同时存在于idcountry中。由于这可以通过其他方式解决,因此没有必要考虑这一点,但仍然有帮助。

我目前最好的尝试是,但没有考虑限制(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测距,如果你喜欢可以改成双关

  • 自非相等连接但按行计数 ids: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