R:有条件地合并来自相邻行的文本,同时保留相关信息
R: conditionally combine text from adjacent rows while retaining associated information
脚本需要:
a) 合并相邻行中的文本,相邻行的数量可能会有所不同,要合并的行的分组由第一行前面的 NA 和最后一行后面的 NA 决定,
b) 保留行 ID 以供将来检查
c) 保留与要合并的相邻行中的一行相关联的数值变量
d) 保留整体顺序
我使用 for 循环和使用 dplyr 和 stringer 处理大量数据实现了这一点。
for 循环不够优雅,因为我正在努力处理按顺序识别相邻行的逻辑。这并不重要,因为分组变量只是一个帮手 - 但它让我很恼火。
我也想知道是否有更有效的方法来完全做到这一点,比如使用 rowwise 和 mutate with lead or lag。
如有任何指导或指点,我们将不胜感激。
library(tidyverse)
tib <- tibble(id = 1:11,
var = c("a", NA, NA, "b", "c" , NA, "d", NA, NA, NA, "e"),
txt = c( NA, "the", "cat", NA, NA, "sat", NA, "on", "the", "mat", NA),
nr = c( NA, NA, 5, NA, NA, 10, 7, NA, NA, 15, 11),
txt_group = NA_integer_)
# txt_group = helper column for text grouping variable
txt_group_counter <- 1L
for(i in seq_len(nrow(tib))){
if (!is.na(tib$txt[i]) | !is.na(lag(tib$txt[i]))){
tib$txt_group[i] <- txt_group_counter
}
if(is.na(tib$txt[i]) | !is.na(lead(tib$txt[i]))){
txt_group_counter <- txt_group_counter + 1
}
}
tib1 <-
tib %>%
filter(!is.na(txt_group)) %>%
group_by(txt_group) %>%
mutate(id_comb = paste(id, collapse = ", "),
txt = paste(txt, collapse = " "),
nr = paste(nr, collapse = "")) %>%
select(-id) %>%
distinct() %>%
ungroup() %>%
mutate(id = as.numeric(str_extract(id_comb, "^\d")),
nr = as.numeric(str_remove_all(nr, "[NA]"))) %>%
select(id, id_comb, everything()) %>%
bind_rows(tib %>% filter(is.na(txt_group))) %>%
arrange(id) %>%
select(-txt_group)
以下使用使用标准 cumsum/diff
技巧创建的辅助变量来定义组,然后 paste
将行放在一起。
代码执行以下操作:
- 创建一个逻辑变量
eq
,判断var
的两个连续值是否相等。由于第一个不能等于之前(在它不存在之前)我用 FALSE
. 填充
- 一些值是
NA
,将它们替换为 FALSE
,所有 NA
都不同于其他所有值,包括其他 NA
。
- 现在有一个
cumsum
技巧,在有 TRUE
的地方设置断点(var
与下一个值不同,请参见上面的第 1 点),就像运行 数。这通过 var
. 的变化给出了分组
- 和
is.na(var)
基本相同的技巧。 cumsum
对于创建分组向量非常有用,值得在 R 技巧包中占有一席之地。
mutate_at
删除了 NA
值,它们将在合并行时重复 NA NA
。像这样,它是 ""
个组合的空字符串。
- 按
ok
、eq
分组并与 paste
组合。 trimws
可能不需要,但也没什么坏处,除非数据集非常大并且代码要针对时间进行优化。
- 取消分组并删除创建的临时列;用逗号替换结果中的空格。
这里是:
tib %>%
mutate(eq = c(FALSE, var[-length(var)] != var[-1]),
eq = ifelse(is.na(eq), FALSE, eq),
eq = cumsum(abs(c(diff(eq), 0))),
ok = cumsum(abs(c(0, diff(is.na(var)))))) %>%
mutate_at(vars(var:txt_group), list(function(x) ifelse(is.na(x), "", x))) %>%
group_by(ok, eq) %>%
summarise_all(funs(trimws(paste(., collapse = " ")))) %>%
ungroup() %>%
select(-ok, -eq) %>%
mutate(id = gsub(" ", ",", id),
var = gsub(" ", ",", var))
## A tibble: 8 x 5
# id var txt nr txt_group
# <chr> <chr> <chr> <chr> <chr>
#1 1 "a" "" "" ""
#2 2,3 "" "the cat" "5" ""
#3 4 "b" "" "" ""
#4 5 "c" "" "" ""
#5 6 "" "sat" "10" ""
#6 7 "d" "" "7" ""
#7 8,9,10 "" "on the mat" "15" ""
#8 11 "e" "" "11" ""
脚本需要:
a) 合并相邻行中的文本,相邻行的数量可能会有所不同,要合并的行的分组由第一行前面的 NA 和最后一行后面的 NA 决定,
b) 保留行 ID 以供将来检查
c) 保留与要合并的相邻行中的一行相关联的数值变量
d) 保留整体顺序
我使用 for 循环和使用 dplyr 和 stringer 处理大量数据实现了这一点。
for 循环不够优雅,因为我正在努力处理按顺序识别相邻行的逻辑。这并不重要,因为分组变量只是一个帮手 - 但它让我很恼火。
我也想知道是否有更有效的方法来完全做到这一点,比如使用 rowwise 和 mutate with lead or lag。
如有任何指导或指点,我们将不胜感激。
library(tidyverse)
tib <- tibble(id = 1:11,
var = c("a", NA, NA, "b", "c" , NA, "d", NA, NA, NA, "e"),
txt = c( NA, "the", "cat", NA, NA, "sat", NA, "on", "the", "mat", NA),
nr = c( NA, NA, 5, NA, NA, 10, 7, NA, NA, 15, 11),
txt_group = NA_integer_)
# txt_group = helper column for text grouping variable
txt_group_counter <- 1L
for(i in seq_len(nrow(tib))){
if (!is.na(tib$txt[i]) | !is.na(lag(tib$txt[i]))){
tib$txt_group[i] <- txt_group_counter
}
if(is.na(tib$txt[i]) | !is.na(lead(tib$txt[i]))){
txt_group_counter <- txt_group_counter + 1
}
}
tib1 <-
tib %>%
filter(!is.na(txt_group)) %>%
group_by(txt_group) %>%
mutate(id_comb = paste(id, collapse = ", "),
txt = paste(txt, collapse = " "),
nr = paste(nr, collapse = "")) %>%
select(-id) %>%
distinct() %>%
ungroup() %>%
mutate(id = as.numeric(str_extract(id_comb, "^\d")),
nr = as.numeric(str_remove_all(nr, "[NA]"))) %>%
select(id, id_comb, everything()) %>%
bind_rows(tib %>% filter(is.na(txt_group))) %>%
arrange(id) %>%
select(-txt_group)
以下使用使用标准 cumsum/diff
技巧创建的辅助变量来定义组,然后 paste
将行放在一起。
代码执行以下操作:
- 创建一个逻辑变量
eq
,判断var
的两个连续值是否相等。由于第一个不能等于之前(在它不存在之前)我用FALSE
. 填充
- 一些值是
NA
,将它们替换为FALSE
,所有NA
都不同于其他所有值,包括其他NA
。 - 现在有一个
cumsum
技巧,在有TRUE
的地方设置断点(var
与下一个值不同,请参见上面的第 1 点),就像运行 数。这通过var
. 的变化给出了分组
- 和
is.na(var)
基本相同的技巧。cumsum
对于创建分组向量非常有用,值得在 R 技巧包中占有一席之地。 mutate_at
删除了NA
值,它们将在合并行时重复NA NA
。像这样,它是""
个组合的空字符串。- 按
ok
、eq
分组并与paste
组合。trimws
可能不需要,但也没什么坏处,除非数据集非常大并且代码要针对时间进行优化。 - 取消分组并删除创建的临时列;用逗号替换结果中的空格。
这里是:
tib %>%
mutate(eq = c(FALSE, var[-length(var)] != var[-1]),
eq = ifelse(is.na(eq), FALSE, eq),
eq = cumsum(abs(c(diff(eq), 0))),
ok = cumsum(abs(c(0, diff(is.na(var)))))) %>%
mutate_at(vars(var:txt_group), list(function(x) ifelse(is.na(x), "", x))) %>%
group_by(ok, eq) %>%
summarise_all(funs(trimws(paste(., collapse = " ")))) %>%
ungroup() %>%
select(-ok, -eq) %>%
mutate(id = gsub(" ", ",", id),
var = gsub(" ", ",", var))
## A tibble: 8 x 5
# id var txt nr txt_group
# <chr> <chr> <chr> <chr> <chr>
#1 1 "a" "" "" ""
#2 2,3 "" "the cat" "5" ""
#3 4 "b" "" "" ""
#4 5 "c" "" "" ""
#5 6 "" "sat" "10" ""
#6 7 "d" "" "7" ""
#7 8,9,10 "" "on the mat" "15" ""
#8 11 "e" "" "11" ""