具有如下所示的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
的最初示例不够复杂-它给人一种错误的印象,即第一个或最后一个值始终与序列相关。
情况并非如此。即使下面的input
在input
1的开头和结尾以及ID
2的结尾添加了附加值,我也希望与上面相同的ID
:< / p>
output
一个字母并不是仅仅出现在前面,而是序列的开始部分。后来的重复将其追溯建立为起点。最后一个字母也是如此:如果有一个字母,则不一定是序列的一部分。
答案 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创建