【问题标题】:Select previous and next N rows with the same value as a certain row选择与某一行具有相同值的前 N ​​行和下 N 行
【发布时间】:2022-01-12 06:20:20
【问题描述】:

我用 idtime 键构造以下面板数据:

pdata <- tibble(
  id = rep(1:10, each = 5),
  time = rep(2016:2020, times = 10),
  value = c(c(1,1,1,0,0), c(1,1,0,0,0), c(0,0,1,0,0), c(0,0,0,0,0), c(1,0,0,0,1), c(0,1,1,1,0), c(0,1,1,1,1), c(1,1,1,1,1), c(1,0,1,1,1), c(1,1,0,1,1))
)
pdata
# A tibble: 50 × 3
      id  time value
   <int> <int> <dbl>
 1     1  2016     1
 2     1  2017     1
 3     1  2018     1
 4     1  2019     0
 5     1  2020     0
 6     2  2016     1
 7     2  2017     1
 8     2  2018     0
 9     2  2019     0
10     2  2020     0
# … with 40 more rows

让我们假设 2018 年发生了一次冲击。我希望通过 id 对前 N 行和后 N 行进行切片,它们的值与冲击行的值相同。

我举几个例子来说明。对于id == 5,数据集如下所示:

pdata %>% filter(id == 5)
# A tibble: 5 × 3
     id  time value
  <int> <int> <dbl>
1     5  2016     1
2     5  2017     0
3     5  2018     0
4     5  2019     0
5     5  2020     1

id == 5 在 2018 年的 value 为 0,我希望保留上一行和下一行 1 包括当前行,因为所有这些观察值都具有相同的值,等于 0:

# A tibble: 3 × 3
     id  time value
  <int> <int> <dbl>
1     5  2017     0
2     5  2018     0
3     5  2019     0

对于id == 8,我希望得到:

# A tibble: 5 × 3
     id  time value
  <int> <int> <dbl>
1     8  2016     1
2     8  2017     1
3     8  2018     1
4     8  2019     1
5     8  2020     1

对于id == 1,我希望得到空数据集,因为2017年的观察和2019年的观察对没有相同的值。

最终的数据集应该是:

# A tibble: 19 × 3
      id  time value
   <int> <int> <dbl>
 1     4  2016     0
 2     4  2017     0
 3     4  2018     0
 4     4  2019     0
 5     4  2020     0
 6     5  2017     0
 7     5  2018     0
 8     5  2019     0
 9     6  2017     1
10     6  2018     1
11     6  2019     1
12     7  2017     1
13     7  2018     1
14     7  2019     1
15     8  2016     1
16     8  2017     1
17     8  2018     1
18     8  2019     1
19     8  2020     1

【问题讨论】:

  • 你见过@Henrik 的this comment 吗?也许你可以澄清一下?

标签: r dataframe dplyr data.table


【解决方案1】:

的解决方案:

# load the package & convert data to a data.table
library(data.table)
setDT(pdata)

# define shock-year and number of previous/next rows
shock <- 2018
n <- 2

# filter
pdata[, .SD[value == value[time == shock] &
              between(time, shock - n, shock + n) & 
              value == rev(value)][.N > 1 & all(diff(time) == 1)]
      , by = id]

给出:

    id time value
 1:  4 2016     0
 2:  4 2017     0
 3:  4 2018     0
 4:  4 2019     0
 5:  4 2020     0
 6:  5 2017     0
 7:  5 2018     0
 8:  5 2019     0
 9:  6 2017     1
10:  6 2018     1
11:  6 2019     1
12:  7 2017     1
13:  7 2018     1
14:  7 2019     1
15:  8 2016     1
16:  8 2017     1
17:  8 2018     1
18:  8 2019     1
19:  8 2020     1

使用过的数据:

pdata <- data.frame(
  id = rep(1:10, each = 5),
  time = rep(2016:2020, times = 10),
  value = c(c(1,1,1,0,0), c(1,1,0,0,0), c(0,0,1,0,0), c(0,0,0,0,0), c(1,0,0,0,1), c(0,1,1,1,0), c(0,1,1,1,1), c(1,1,1,1,1), c(1,0,1,1,1), c(1,1,0,1,1))
)

【讨论】:

  • 嗨@Jaap!我可能(再次)误解了这个问题......但是当阅读“前 N 行和下 N 行的 id 与冲击行的值具有相同的值”时,我不认为 N​​ 跨组是固定的。我认为我们应该在焦点年之前和之后选择具有相等值的行数最大。例如。使用pdata = data.table(id = 1, time = 2015:2021, value = 0) 我希望选择所有行,而不是“仅”(硬编码)N 行。也许OP愿意澄清?干杯
  • @Henrik 我已经在问题下发布了一条评论,其中包含指向您的评论的链接,以引起 OP 的注意。希望OP可以给出一些澄清。
  • 对不起,我刚看到这条评论。感谢您提供的出色答案。我很受启发。 @Henrik 是对的。 N 是灵活的,而不是跨组固定的。
【解决方案2】:

围绕焦点年的对称范围和范围可能因“id”而异

在每个 'id' (by = id) 中,使用 rleid 根据相等值的运行创建分组变量 'r'。在每个 'id' 和运行 (by = .(id, r)) 中,检查是否至少存在焦点年(例如 2018 年)的上一年和下一年(if(sum(time %in% yr_rng) == 3))。如果是这样,请在焦点年之前和之后选择相同数量的行 (min(c(shock - .I[1], .I[.N] - shock))。请注意,此处选择的年数可能因“id”而异。

library(data.table)
setDT(pdata)
yr = 2018
yr_rng = (yr - 1):(yr + 1)

pdata[ , r := rleid(value), by = id]
pdata[pdata[ , if(sum(time %in% yr_rng) == 3) {
  shock = .I[time == 2018]
  rng = min(c(shock - .I[1], .I[.N] - shock))
  (shock - rng):(shock + rng)
}, by = .(id, r)]$V1] 

    id time value r
 1:  4 2016     0 1
 2:  4 2017     0 1
 3:  4 2018     0 1
 4:  4 2019     0 1
 5:  4 2020     0 1
 6:  5 2017     0 2
 7:  5 2018     0 2
 8:  5 2019     0 2
 9:  6 2017     1 2
10:  6 2018     1 2
11:  6 2019     1 2
12:  7 2017     1 2
13:  7 2018     1 2
14:  7 2019     1 2
15:  8 2016     1 1
16:  8 2017     1 1
17:  8 2018     1 1
18:  8 2019     1 1
19:  8 2020     1 1

允许围绕焦点年的不对称范围

在每个 'id' 和运行 (by = .(id, r)) 中,检查焦点年(例如 2018 年)的上一年和下一年是否都存在 (if(sum(time %in% yr_rng) == 3))。如果是这样,请选择整个组 (.SD)。


pdata[ , r := rleid(value), by = id]
pdata[ , if(sum(time %in% yr_rng) == 3) .SD, by = .(id, r)]

    id r time value
 1:  4 1 2016     0
 2:  4 1 2017     0
 3:  4 1 2018     0
 4:  4 1 2019     0
 5:  4 1 2020     0
 6:  5 2 2017     0
 7:  5 2 2018     0
 8:  5 2 2019     0
 9:  6 2 2017     1
10:  6 2 2018     1
11:  6 2 2019     1
12:  7 2 2017     1
13:  7 2 2018     1
14:  7 2 2019     1
15:  7 2 2020     1
16:  8 1 2016     1
17:  8 1 2017     1
18:  8 1 2018     1
19:  8 1 2019     1
20:  8 1 2020     1

【讨论】:

  • 很好@Henrik,唯一需要改变的是最后一行减少保持像2020 in id == 7 这样的年份,我认为所需的行为只是在目标年份周围保持对称数量的行.我最初有基本相同的解决方案,但在dplyr 中,我能找到的最精简的是我在下面的答案。您可能会发现更时尚的东西!
  • @caldwellst 哎呀,谢谢。好像我误读了这个问题。我当然没有考虑到目标年份周围的相同年数/对称性......我会看看我是否有时间进行编辑。干杯
  • @jaap 有一个很好的解决方案。
  • @jaap always 有很好的解决方案 ;)
  • @Henrik thx & 我也会对你说同样的话 ;-)
【解决方案3】:

据我了解,这是dplyr 的建议:

library(dplyr)

MyF <- function(id2, shock, nb_row) {
  values <- pdata %>%
    filter(id == id2) %>%
    pull(value)
  
  if (length(unique(values)) == 1) {
    pdata %>%
      filter(id == id2)
  } else {
    pdata %>%
      filter(id == id2) %>%
      filter(time >= shock - nb_row & time <= shock + nb_row) %>%
      filter(length(unique(value)) == 1)
  }
  
  
}

map_df(pdata %>%
         select(id) %>% 
         distinct() %>% 
         pull(),
       MyF,
       shock = 2018, nb_row = 1)

## Or map_df(1:8,MyF,shock = 2018, nb_row = 1)

输出:

# A tibble: 19 x 3
      id  time value
   <int> <int> <dbl>
 1     4  2016     0
 2     4  2017     0
 3     4  2018     0
 4     4  2019     0
 5     4  2020     0
 6     5  2017     0
 7     5  2018     0
 8     5  2019     0
 9     6  2017     1
10     6  2018     1
11     6  2019     1
12     7  2017     1
13     7  2018     1
14     7  2019     1
15     8  2016     1
16     8  2017     1
17     8  2018     1
18     8  2019     1
19     8  2020     1

【讨论】:

    【解决方案4】:

    这是另一个dplyr 解决方案。我们基本上按每个id 的唯一值序列进行分组,然后只过滤到重复的冲击时间的最大距离。

    pdata %>%
      group_by(id) %>%
      mutate(value_group = cumsum(value != lag(value, default = value[1]))) %>%
      group_by(id, value_group) %>%
      mutate(shock_diff = abs(time - 2018)) %>%
      filter(shock_diff <= max(shock_diff[duplicated(shock_diff)], -Inf))
    #> # A tibble: 19 × 5
    #> # Groups:   id, value_group [5]
    #>       id  time value value_group shock_diff
    #>    <int> <int> <dbl>       <int>      <dbl>
    #>  1     4  2016     0           0          2
    #>  2     4  2017     0           0          1
    #>  3     4  2018     0           0          0
    #>  4     4  2019     0           0          1
    #>  5     4  2020     0           0          2
    #>  6     5  2017     0           1          1
    #>  7     5  2018     0           1          0
    #>  8     5  2019     0           1          1
    #>  9     6  2017     1           1          1
    #> 10     6  2018     1           1          0
    #> 11     6  2019     1           1          1
    #> 12     7  2017     1           1          1
    #> 13     7  2018     1           1          0
    #> 14     7  2019     1           1          1
    #> 15     8  2016     1           0          2
    #> 16     8  2017     1           0          1
    #> 17     8  2018     1           0          0
    #> 18     8  2019     1           0          1
    #> 19     8  2020     1           0          2
    

    【讨论】:

      【解决方案5】:

      使用 data.table 解决问题的一种方法:

      library(data.table)
      
      yrs=2017:2019
      setDT(pdata)[, if(uniqueN(value)==1) .(time, value) 
                     else if(uniqueN(value <- value[time %in% yrs])==1) .(time=yrs, value), 
                   by=id]
      
      #        id  time value
      #  1:     4  2016     0
      #  2:     4  2017     0
      #  3:     4  2018     0
      #  4:     4  2019     0
      #  5:     4  2020     0
      #  6:     5  2017     0
      #  7:     5  2018     0
      #  8:     5  2019     0
      #  9:     6  2017     1
      # 10:     6  2018     1
      # 11:     6  2019     1
      # 12:     7  2017     1
      # 13:     7  2018     1
      # 14:     7  2019     1
      # 15:     8  2016     1
      # 16:     8  2017     1
      # 17:     8  2018     1
      # 18:     8  2019     1
      # 19:     8  2020     1
      

      【讨论】:

      • 嗨@B。克里斯蒂安·甘刚!我认为 if(uniqueN(value)==1) .(time, value) 可能无法处理问题的“对称方面”。想象一下,所有值都与一个 id 相等,2018 年之前有两行,2018 年之后有三行。AFAIU 然后我们应该选择之前的 2 行和之后的两行,而不是之后的所有行。我希望我没有把事情搞砸......干杯
      猜你喜欢
      • 1970-01-01
      • 2016-07-04
      • 2016-08-11
      • 1970-01-01
      • 2021-10-25
      • 1970-01-01
      • 2017-06-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多