识别重复值的组/顺序

时间:2020-10-09 19:31:18

标签: r

具有如下所示的input数据框:

   ID val
1   1   c
2   1   c
3   1   d
4   1   d
5   1   c
6   2   b
7   2   c
8   2   d
9   2   b
10  2   c
11  2   b
12  3   a
13  3   b
14  3   a
15  3   b
16  3   a
17  4   b
18  4   a
19  4   b
20  5   f
21  5   f

我的目标是以下output

   ID val idx
1   1   c   1
2   1   d   1
3   1   c   1
4   2   b   1
5   2   c   1
6   2   d   1
7   2   b   1
8   2   b   2
9   2   c   2
10  2   b   2
11  3   a   1
12  3   b   1
13  3   a   1
14  3   a   2
15  3   b   2
16  3   a   2

逻辑如下:

  • val列由基本上是序数值的字母组成(即,它们等于as.integer(factor(val)))。
  • 对于每个val,我想确定一个序列,其中值变成更高的值(例如c -> d,但不一定是完全连续的,也可以是{{1} }),然后返回到初始值(例如c -> e),而忽略中间的任何重复项。
  • 这说明了为什么将c -> d -> c 4-5从最终输出中删除。
  • 在某些情况下,初始值会变成一个较高的值,然后重复此循环,例如为ID 2-3。在这些情况下,我想将所有序列重构为具有不同索引的单独序列(如ID输出列中所示)。这必然会复制在此类循环开始时的一些行。

有什么简单,快速的方法来解决这个问题吗?

编辑

不幸的是,idx的最初示例不够复杂-它给人一种错误的印象,即第一个或最后一个值始终与序列相关。

情况并非如此。即使下面的inputinput 1的开头和结尾以及ID 2的结尾添加了附加值,我也希望与上面相同的ID:< / p>

output

一个字母并不是仅仅出现在前面,而是序列的开始部分。后来的重复将其追溯建立为起点。最后一个字母也是如此:如果有一个字母,则不一定是序列的一部分。

2 个答案:

答案 0 :(得分:3)

我想出了这种方法,这也许不是最简洁的答案,但我认为它有点可读性,可以将您的问题分解为更小的步骤。

诀窍实际上只是识别序列中最后一行的以及随后序列中第一行的

由于需要复制这些值,因此我为它们分配了两个IDx值。然后,我们可以轻松地unnest IDx列并列出两行。
在第二步中,我们基本上只是按照给定的规则清理数据。


步骤1:
为每一行标识相应的IDx:
//更新:过滤掉所有未出现的值(如果存在,将被视为一个序列)和有效序列块之后的任何值

library(dplyr)
library(purrr)
library(tidyr)

df_idx <- df %>%
  group_by(ID) %>%
  add_count(val) %>%
  filter(cumsum(n > 1) > 0) %>% # removes leading vals
  filter(rev(cumsum(rev(val == first(val))) > 0)) %>% # removes trailing vals
  select(-n) %>%
  mutate(
    is_sequence_first = case_when(
      row_number() == 1 ~ T,
      lag(val) != val & val == first(val) & lead(val) != val ~ T,
      T ~ F
    ),
    is_sequence_last = case_when(
      row_number() == n() ~ T,
      val == first(val) & lead(is_sequence_first) ~ T,
      lag(val) != first(val) & is_sequence_first ~ T,
      T ~ F
    ),
    IDx = case_when(
      is_sequence_first & is_sequence_last ~ map(cumsum(is_sequence_first), ~c(.x-1,.x)),
      T ~ as.list(cumsum(is_sequence_first))
    )
  ) %>%
  unnest(IDx) %>%
  ungroup()

第2步:
在只有一个IDx的块中过滤出重复项,并排除“非法”(降序)序列:

df_final <- df_idx %>%
  group_by(ID) %>%
  filter(
    case_when(
      max(IDx) == 1 & row_number() == 1 ~ T,
      max(IDx) == 1 & val != lag(val) ~ T,
      max(IDx) > 1 ~ T,
      T ~ F
    )
  ) %>%
  group_by(ID, IDx) %>%
  filter(
    any(val > first(val))
  ) %>%
  ungroup() %>%
  select(-is_sequence_first, -is_sequence_last)

这给出了您在问题中提出的结果。

答案 1 :(得分:2)

这很难。实际上,获取val列非常简单。很难匹配到ID号。

以下内容既不快速也不美观,但是可以正常工作。您描述的算法似乎过于复杂,无法提供一个整洁的解决方案,但我可能是错的...

IDs <- split(as.integer(factor(input$val)), input$ID)

vals <- unlist(lapply(IDs, function(x) {
  y <- which(x == x[1])
  a <- lapply(seq_along(y)[-1], function(z) {
    z <- x[seq(y[z-1], y[z])]
    if(length(z) == 2 | any(z < z[1])) return(NULL)
    z <- levels(factor(input$val))[z[c(which(diff(z) != 0), length(z))]]
    names(z) <- rep(names(IDs)[sapply(IDs, identical, x)], length(z))
    z
    })
  setNames(a, seq_along(a))
  }))

df2 <- data.frame(ID = as.numeric(gsub(".*\\.(\\d+)$", "\\1", names(vals))),
                  val = vals, row.names = seq_along(vals),
                  IDx = as.numeric(gsub("^.*\\.(\\d+)\\..*$", "\\1", names(vals))))

output <- `rownames<-`(do.call(rbind, lapply(split(df2, df2$ID), 
                      function(x) within(x, IDx <- as.numeric(factor(IDx))))),
             seq(nrow(df2)))

给我们:

output
#>    ID val IDx
#> 1   1   c   1
#> 2   1   d   1
#> 3   1   c   1
#> 4   2   b   1
#> 5   2   c   1
#> 6   2   d   1
#> 7   2   b   1
#> 8   2   b   2
#> 9   2   c   2
#> 10  2   b   2
#> 11  3   a   1
#> 12  3   b   1
#> 13  3   a   1
#> 14  3   a   2
#> 15  3   b   2
#> 16  3   a   2

数据

input <- structure(list(ID = c(1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 
2L, 3L, 3L, 3L, 3L, 3L, 4L, 4L, 4L, 5L, 5L), val = c("c", "c", 
"d", "d", "c", "b", "c", "d", "b", "c", "b", "a", "b", "a", "b", 
"a", "b", "a", "b", "f", "f")), class = "data.frame", row.names = c("1", 
"2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", 
"14", "15", "16", "17", "18", "19", "20", "21"))

reprex package(v0.3.0)于2020-10-09创建