dplyr:group_by 并汇总以折叠(通过串联)包含 NA 的字符串列
dplyr: group_by and summarize to collapse (via concatenation) columns of strings that contain NA
我有一个相对简单的问题。
假设您有以下数据集:
ID
dummy_var
字符串 1
字符串 2
字符串 3
1
0
汤姆
不适用
不适用
1
1
不适用
乔
不适用
2
0
汤姆
不适用
不适用
2
1
不适用
乔
不适用
2
0
不适用
不适用
鲍勃
3
0
史蒂夫
不适用
不适用
3
0
不适用
提米
不适用
4
0
亚历克斯
不适用
不适用
我想使用 group by 和 summarize 得到以下内容:
ID
dummy_var
字符串 1
字符串 2
字符串 3
1
1
汤姆
乔
不适用
2
1
汤姆
乔
鲍勃
3
0
史蒂夫
提米
不适用
4
0
亚历克斯
不适用
不适用
我在汇总函数中使用 dummy_var = max(dummy_var) 的变体“dummy_var”没有遇到任何问题,但我似乎无法找到任何关于如何根据需要获取字符串的信息。
我尝试过以下变体:
group_by(ID) %>%
summarize(
String1 = str_c(String1)
)
或
group_by(ID) %>%
summarize(
String1 = case_when(
length(str_c(String1)) > 0 ~ str_c(String1)
str_c(String1) == rep(NA,length(str_c(String1)) ~ NA
)
)
第一次尝试时,行实际上并没有改变。例如,尽管诸如 max(dummy var) 之类的数字运算将按预期为组中的每一行产生 0 或 1,但不会汇总字符串变量,并且在取消分组和打印数据帧时,每个 ID 会得到多行,就好像你从来没有首先总结过字符串列。
使用第二种方法,当存在每个组的所有值都为 NA 的情况时,函数总是失败,表示“String(i) 的长度必须大于 0”或它的某些变体.=15=]
我注意到如果您尝试以下操作
group_by(ID) %>%
summarize(
String1 = str_replace_na(String1)
)
输出和第一个代码块一样,就好像什么都没发生一样。
关于我的数据的其他事实:每组字符串 1 将始终具有至少一个不带 NA 的值。对于 String2 和 String 3,按照我的示例,我希望折叠的行也显示为 NA。此外,在任何情况下,任何 group_by() 组都不会包含不止一行包含 NA 以外的内容的列;即,在组内,每一行只有三个 String1/2/3 中的一个作为 NA 以外的其他内容,或者它们可能都是 NA(例如我的示例中的 ID=2)。所有其他包含 int 或 double 值的列汇总没有问题。这只是字符串。使用 paste0 而不是 str_c() 也没有区别。
有人可以给我建议吗?我在网上找不到任何像这样的例子,其中 NA 在组内的列内,以及在组内它们有时包含列内的所有值。
我唯一的选择是在所有 NA 上使用 replace_na(),将它们与一些填充文本连接起来,然后返回并为每个值用 stringr 或其他东西将它们取出。它有效,但我知道必须有一个优雅的方法!
编辑:
事实证明,如果我使用 str_replace_na() 而不是 str_c(),您最终会得到,例如,
ID
dummy_var
字符串 1
字符串 2
字符串 3
1
1
汤姆
“与”
“与”
1
1
“与”
“乔”
“与”
2
1
汤姆
“与”
“与”
2
1
“与”
“乔”
“与”
2
1
“与”
“与”
鲍勃
也就是说,值被替换为字符串“NA”而不是一个NA。这是令人惊讶的,因为以下是真实的:
str_replace_na("Something",NA)
> "Something"
str_c("Something",NA)
> NA
您可以使用 tidyr
的 fill
函数:
library(tidyr)
library(dplyr)
df %>%
group_by(ID) %>%
fill(starts_with("String"), .direction="downup") %>%
filter(dummy_var == max(dummy_var)) %>%
distinct() %>%
ungroup()
哪个returns
# A tibble: 4 x 5
ID dummy_var String1 String2 String3
<dbl> <dbl> <chr> <chr> <chr>
1 1 1 Tom Jo NA
2 2 1 Tom Jo Bob
3 3 0 Steve Timmy NA
4 4 0 Alex NA NA
##数据
df <- structure(list(ID = c(1, 1, 2, 2, 2, 3, 3, 4), dummy_var = c(0,
1, 0, 1, 0, 0, 0, 0), String1 = c("Tom", NA, "Tom", NA, NA, "Steve",
NA, "Alex"), String2 = c(NA, "Jo", NA, "Jo", NA, NA, "Timmy",
NA), String3 = c(NA, NA, NA, NA, "Bob", NA, NA, NA)), class = c("spec_tbl_df",
"tbl_df", "tbl", "data.frame"), row.names = c(NA, -8L), spec = structure(list(
cols = list(ID = structure(list(), class = c("collector_double",
"collector")), dummy_var = structure(list(), class = c("collector_double",
"collector")), String1 = structure(list(), class = c("collector_character",
"collector")), String2 = structure(list(), class = c("collector_character",
"collector")), String3 = structure(list(), class = c("collector_character",
"collector"))), default = structure(list(), class = c("collector_guess",
"collector")), skip = 1L), class = "col_spec"))
这是另一种方法,dplyr
# function that will keep one row for each unique ID
coalesce_all_columns <- function(df) {
return(coalesce(!!! as.list(df)))
}
library(dplyr)
df %>%
group_by(ID) %>%
arrange(ID, desc(dummy_var)) %>%
summarise_all(coalesce_all_columns)
输出:
ID dummy_var String1 String2 String3
<dbl> <dbl> <chr> <chr> <chr>
1 1 1 Tom Jo NA
2 2 1 Tom Jo Bob
3 3 0 Steve Timmy NA
4 4 0 Alex NA NA
一个data.table
选项
setDT(df)[
,
dummy_var := max(dummy_var), ID
][
,
lapply(.SD, function(x) fcoalesce(as.list(x))), .(ID, dummy_var)
]
给予
ID dummy_var String1 String2 String3
1: 1 1 Tom Jo <NA>
2: 2 1 Tom Jo Bob
3: 3 0 Steve Timmy <NA>
4: 4 0 Alex <NA> <NA>
使用“聚合+
ave”的基础 R 选项
aggregate(
. ~ ID + dummy_var,
transform(
df,
dummy_var = ave(dummy_var, ID, FUN = max)
),
function(x) ifelse(all(is.na(x)), x, na.omit(x)),
na.action = na.pass
)
给予
ID dummy_var String1 String2 String3
1 3 0 Steve Timmy <NA>
2 4 0 Alex <NA> <NA>
3 1 1 Tom Jo <NA>
4 2 1 Tom Jo Bob
我有一个相对简单的问题。
假设您有以下数据集:
ID | dummy_var | 字符串 1 | 字符串 2 | 字符串 3 |
---|---|---|---|---|
1 | 0 | 汤姆 | 不适用 | 不适用 |
1 | 1 | 不适用 | 乔 | 不适用 |
2 | 0 | 汤姆 | 不适用 | 不适用 |
2 | 1 | 不适用 | 乔 | 不适用 |
2 | 0 | 不适用 | 不适用 | 鲍勃 |
3 | 0 | 史蒂夫 | 不适用 | 不适用 |
3 | 0 | 不适用 | 提米 | 不适用 |
4 | 0 | 亚历克斯 | 不适用 | 不适用 |
我想使用 group by 和 summarize 得到以下内容:
ID | dummy_var | 字符串 1 | 字符串 2 | 字符串 3 |
---|---|---|---|---|
1 | 1 | 汤姆 | 乔 | 不适用 |
2 | 1 | 汤姆 | 乔 | 鲍勃 |
3 | 0 | 史蒂夫 | 提米 | 不适用 |
4 | 0 | 亚历克斯 | 不适用 | 不适用 |
我在汇总函数中使用 dummy_var = max(dummy_var) 的变体“dummy_var”没有遇到任何问题,但我似乎无法找到任何关于如何根据需要获取字符串的信息。
我尝试过以下变体:
group_by(ID) %>%
summarize(
String1 = str_c(String1)
)
或
group_by(ID) %>%
summarize(
String1 = case_when(
length(str_c(String1)) > 0 ~ str_c(String1)
str_c(String1) == rep(NA,length(str_c(String1)) ~ NA
)
)
第一次尝试时,行实际上并没有改变。例如,尽管诸如 max(dummy var) 之类的数字运算将按预期为组中的每一行产生 0 或 1,但不会汇总字符串变量,并且在取消分组和打印数据帧时,每个 ID 会得到多行,就好像你从来没有首先总结过字符串列。
使用第二种方法,当存在每个组的所有值都为 NA 的情况时,函数总是失败,表示“String(i) 的长度必须大于 0”或它的某些变体.=15=]
我注意到如果您尝试以下操作
group_by(ID) %>%
summarize(
String1 = str_replace_na(String1)
)
输出和第一个代码块一样,就好像什么都没发生一样。
关于我的数据的其他事实:每组字符串 1 将始终具有至少一个不带 NA 的值。对于 String2 和 String 3,按照我的示例,我希望折叠的行也显示为 NA。此外,在任何情况下,任何 group_by() 组都不会包含不止一行包含 NA 以外的内容的列;即,在组内,每一行只有三个 String1/2/3 中的一个作为 NA 以外的其他内容,或者它们可能都是 NA(例如我的示例中的 ID=2)。所有其他包含 int 或 double 值的列汇总没有问题。这只是字符串。使用 paste0 而不是 str_c() 也没有区别。
有人可以给我建议吗?我在网上找不到任何像这样的例子,其中 NA 在组内的列内,以及在组内它们有时包含列内的所有值。
我唯一的选择是在所有 NA 上使用 replace_na(),将它们与一些填充文本连接起来,然后返回并为每个值用 stringr 或其他东西将它们取出。它有效,但我知道必须有一个优雅的方法!
编辑: 事实证明,如果我使用 str_replace_na() 而不是 str_c(),您最终会得到,例如,
ID | dummy_var | 字符串 1 | 字符串 2 | 字符串 3 |
---|---|---|---|---|
1 | 1 | 汤姆 | “与” | “与” |
1 | 1 | “与” | “乔” | “与” |
2 | 1 | 汤姆 | “与” | “与” |
2 | 1 | “与” | “乔” | “与” |
2 | 1 | “与” | “与” | 鲍勃 |
也就是说,值被替换为字符串“NA”而不是一个NA。这是令人惊讶的,因为以下是真实的:
str_replace_na("Something",NA)
> "Something"
str_c("Something",NA)
> NA
您可以使用 tidyr
的 fill
函数:
library(tidyr)
library(dplyr)
df %>%
group_by(ID) %>%
fill(starts_with("String"), .direction="downup") %>%
filter(dummy_var == max(dummy_var)) %>%
distinct() %>%
ungroup()
哪个returns
# A tibble: 4 x 5
ID dummy_var String1 String2 String3
<dbl> <dbl> <chr> <chr> <chr>
1 1 1 Tom Jo NA
2 2 1 Tom Jo Bob
3 3 0 Steve Timmy NA
4 4 0 Alex NA NA
##数据
df <- structure(list(ID = c(1, 1, 2, 2, 2, 3, 3, 4), dummy_var = c(0,
1, 0, 1, 0, 0, 0, 0), String1 = c("Tom", NA, "Tom", NA, NA, "Steve",
NA, "Alex"), String2 = c(NA, "Jo", NA, "Jo", NA, NA, "Timmy",
NA), String3 = c(NA, NA, NA, NA, "Bob", NA, NA, NA)), class = c("spec_tbl_df",
"tbl_df", "tbl", "data.frame"), row.names = c(NA, -8L), spec = structure(list(
cols = list(ID = structure(list(), class = c("collector_double",
"collector")), dummy_var = structure(list(), class = c("collector_double",
"collector")), String1 = structure(list(), class = c("collector_character",
"collector")), String2 = structure(list(), class = c("collector_character",
"collector")), String3 = structure(list(), class = c("collector_character",
"collector"))), default = structure(list(), class = c("collector_guess",
"collector")), skip = 1L), class = "col_spec"))
这是另一种方法,dplyr
# function that will keep one row for each unique ID
coalesce_all_columns <- function(df) {
return(coalesce(!!! as.list(df)))
}
library(dplyr)
df %>%
group_by(ID) %>%
arrange(ID, desc(dummy_var)) %>%
summarise_all(coalesce_all_columns)
输出:
ID dummy_var String1 String2 String3
<dbl> <dbl> <chr> <chr> <chr>
1 1 1 Tom Jo NA
2 2 1 Tom Jo Bob
3 3 0 Steve Timmy NA
4 4 0 Alex NA NA
一个data.table
选项
setDT(df)[
,
dummy_var := max(dummy_var), ID
][
,
lapply(.SD, function(x) fcoalesce(as.list(x))), .(ID, dummy_var)
]
给予
ID dummy_var String1 String2 String3
1: 1 1 Tom Jo <NA>
2: 2 1 Tom Jo Bob
3: 3 0 Steve Timmy <NA>
4: 4 0 Alex <NA> <NA>
使用“聚合+
ave”的基础 R 选项
aggregate(
. ~ ID + dummy_var,
transform(
df,
dummy_var = ave(dummy_var, ID, FUN = max)
),
function(x) ifelse(all(is.na(x)), x, na.omit(x)),
na.action = na.pass
)
给予
ID dummy_var String1 String2 String3
1 3 0 Steve Timmy <NA>
2 4 0 Alex <NA> <NA>
3 1 1 Tom Jo <NA>
4 2 1 Tom Jo Bob