【问题标题】:How to flag first change in a variable value between years, per group?如何标记每组之间变量值的第一次变化?
【发布时间】:2017-10-09 00:35:54
【问题描述】:

给定一个包含不同组的非常大的纵向数据集,我需要创建一个标志,以指示每个组 (id) 在年份 (year) 之间某个变量 (code) 的第一次变化。同一id-year内观察的type只是表示不同的组成员。

样本数据:

library(tidyverse)    
sample <- tibble(id = rep(1:3, each=6),
                     year = rep(2010:2012, 3, each=2),
                     type = (rep(1:2, 9)),
                     code = c("abc","abc","","","xyz","xyz", "","","lmn","","efg","efg","def","def","","klm","nop","nop"))

我需要将组内的第一个更改标记为code,在几年之间。第二个变化无所谓。缺失代码 ("") 可以视为NA,但无论如何不应影响flag。以下是上面带有标志字段的小标题:

# A tibble: 18 × 5
      id  year  type  code  flag
   <int> <int> <int> <chr> <dbl>
1      1  2010     1   abc     0
2      1  2010     2   abc     0
3      1  2011     1           0
4      1  2011     2           0
5      1  2012     1   xyz     1
6      1  2012     2   xyz     1
7      2  2010     1           0
8      2  2010     2           0
9      2  2011     1   lmn     0
10     2  2011     2           0
11     2  2012     1   efg     1
12     2  2012     2   efg     1
13     3  2010     1   def     0
14     3  2010     2   def     0
15     3  2011     1           1
16     3  2011     2   klm     1
17     3  2012     1   nop     1
18     3  2012     2   nop     1

我仍然有一个循环的心态,我正在尝试使用矢量化 dplyr 来做我需要的事情。 任何意见将不胜感激!

编辑:感谢您指出year 的重要性。 id 是按年份排列的,因为这里的顺序很重要,而且所有 typesidyear 都需要具有相同的标志。因此,在编辑后的第 15 行中,e 代码是 "",它本身不需要更改,但由于在同一年第 16 行有一个新的 code,因此两个观察值都需要将它们的代码更改为 1。

【问题讨论】:

  • 您可能应该更新您的示例以更好地展示年份列的相关性,因为大多数答案都没有考虑到它
  • 年份仅在两种情况下很重要 - (1) 它是一种排序机制,以及 (2) id i 的所有标志在任何一年中都应该相同

标签: r loops dplyr


【解决方案1】:

我们可以使用data.table

library(data.table)
setDT(sample)[, flag :=0][code!="",  flag := {rl <- rleid(code)-1; cummax(rl*(rl < 2)) }, id]
sample
#    id year type code flag
# 1:  1 2010    1  abc    0
# 2:  1 2010    2  abc    0
# 3:  1 2011    1         0
# 4:  1 2011    2         0
# 5:  1 2012    1  xyz    1
# 6:  1 2012    2  xyz    1
# 7:  2 2010    1         0
# 8:  2 2010    2         0
# 9:  2 2011    1  lmn    0
#10:  2 2011    2         0
#11:  2 2012    1  efg    1
#12:  2 2012    2  efg    1
#13:  3 2010    1  def    0
#14:  3 2010    2  def    0
#15:  3 2011    1  klm    1
#16:  3 2011    2  klm    1
#17:  3 2012    1  nop    1
#18:  3 2012    2  nop    1

更新

如果我们还需要包含“年份”,

setDT(sample)[, flag :=0][code!="",  flag := {rl <- rleid(code, year)-1
                   cummax(rl*(rl < 2)) }, id]

【讨论】:

  • +1 因为它有效(尽管我不知道为什么 - 我想我需要学习 data.table)。将生成的 data.table 更改为 data.frame 或 tibble 是否会导致问题?
  • 您没有使用与 OP 描述相关的year
  • @docendodiscimus 根据 cmets,我猜 OP 希望安排它。例子已经安排好了,不过可以在i上用order(year)完成
  • 嗯,我对这个问题有不同的理解——我认为他们只想在有年份变化和新代码时标记它。因此,这不仅仅是对数据进行排序。
  • @docendodiscimus 在这种情况下,在year 上使用rleid 以及setDT(sample)[, flag :=0][code!="", flag := {rl &lt;- rleid(code, year)-1; cummax(rl*(rl &lt; 2)) }, id] 我得到相同的输出
【解决方案2】:

使用dplyr 的可能解决方案。虽然不确定它是最干净的方式

sample %>% 
  group_by(id) %>% 
  #find first year per group where code exists
  mutate(first_year = min(year[code != ""])) %>% 
  #gather all codes from first year (does not assume code is constant within year)
  mutate(first_codes = list(code[year==first_year])) %>% 
  #if year is not first year & code not in first year codes & code not blank
  mutate(flag = as.numeric(year != first_year & !(code %in% unlist(first_codes)) & code != "")) %>% 
  #drop created columns
  select(-first_year, -first_codes) %>% 
  ungroup()

输出

# A tibble: 18 × 5
      id  year  type  code  flag
   <int> <int> <int> <chr> <dbl>
1      1  2010     1   abc     0
2      1  2010     2   abc     0
3      1  2011     1           0
4      1  2011     2           0
5      1  2012     1   xyz     1
6      1  2012     2   xyz     1
7      2  2010     1           0
8      2  2010     2           0
9      2  2011     1   lmn     0
10     2  2011     2           0
11     2  2012     1   efg     1
12     2  2012     2   efg     1
13     3  2010     1   def     0
14     3  2010     2   def     0
15     3  2011     1   klm     1
16     3  2011     2   klm     1
17     3  2012     1   nop     1
18     3  2012     2   nop     1

【讨论】:

    【解决方案3】:

    data.table-package 的简短解决方案:

    library(data.table)
    setDT(samp)[, flag := 0][code!="", flag := 1*(rleid(code)-1 > 0), by = id]
    

    或者:

    setDT(samp)[, flag := 0][code!="", flag := 1*(code!=code[1] & code!=''), by = id][]
    

    它给出了预期的结果:

    > samp
        id year type code flag
     1:  1 2010    1  abc    0
     2:  1 2010    2  abc    0
     3:  1 2011    1         0
     4:  1 2011    2         0
     5:  1 2012    1  xyz    1
     6:  1 2012    2  xyz    1
     7:  2 2010    1         0
     8:  2 2010    2         0
     9:  2 2011    1  lmn    0
    10:  2 2011    2         0
    11:  2 2012    1  efg    1
    12:  2 2012    2  efg    1
    13:  3 2010    1  def    0
    14:  3 2010    2  def    0
    15:  3 2011    1  klm    1
    16:  3 2011    2  klm    1
    17:  3 2012    1  nop    1
    18:  3 2012    2  nop    1
    

    或者当年份也相关时:

    setDT(samp)[, flag := 0][code!="", flag := 1*(rleid(code, year)-1 > 0), id]
    

    一个可能的基础 R 替代方案:

    f <- function(x) {
      x <- rle(x)$lengths
      1 * (rep(seq_along(x), times=x) - 1 > 0)
    }
    
    samp$flag <- 0
    samp$flag[samp$code!=''] <- with(samp[samp$code!=''], ave(as.character(code), id, FUN = f))
    

    注意:最好不要将对象与函数同名。

    使用过的数据:

    samp <- data.frame(id = rep(1:3, each=6),
                       year = rep(2010:2012, 3, each=2),
                       type = (rep(1:2, 9)),
                       code = c("abc","abc","","","xyz","xyz", "","","lmn","","efg","efg","def","def","klm","klm","nop","nop"))
    

    【讨论】:

    • 输出现在看起来是正确的,但正如 akruns 回答下所评论的那样,我认为您缺少年份列相关性
    • @docendodiscimus 已更新,但我认为 OP 必须明确他到底想用 year-variable 做什么。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-18
    • 1970-01-01
    • 2020-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-06
    相关资源
    最近更新 更多