r语言 - 如何识别两行(或更多行)不同的列(同一 ID)?



>我有一个具有重复 ID 的数据帧,但行并不相同。是否有一个函数来标识出现差异的列(或列)?

我真正的应用程序是一个包含数百列的数据帧。我需要一些方法来检查是否在重要列或一些不相关的列中进行了更改。因此,首先我需要确定更改的列

例:

ID <- c(1,2,2,4,5,5,5,6,6,7)
Info1 <- c(10,20,20,40,50,50,50,65,60,70)
Info2 <- c('A','B','A','D', 'E','E','F', 'Z','A','B')
Info3 <- c(999,998,997,995,995,995,995,946,800,805)
df <- data.frame(ID, Info1, Info2, Info3)
ID Info1 Info2 Info3
1   1    10     A   999
2   2    20     B   998
3   2    20     A   997
4   4    40     D   995
5   5    50     E   995
6   5    50     E   995
7   5    50     F   995
8   6    60     Z   946
9   6    60     A   800
10  7    70     B   805

我的目标是一个额外的列,其中包含更改的列,即所需的输出:

ID Info1 Info2 Info3            col_diff
1   1    10     A   999                <NA>
2   2    20     B   998        Info2; Info3
3   2    20     A   997        Info2; Info3
4   4    40     D   995                <NA>
5   5    50     H   995               Info2
6   5    50     E   995               Info2
7   5    50     F   995               Info2
8   6    65     Z   946 Info1; Info2; Info3
9   6    60     A   800 Info1; Info2; Info3
10  7    70     B   805                <NA>

我希望我的问题变得清晰。我希望 dplyr 中有一些功能,我还不知道。当然,我带有附加列的解决方案并不是很优雅。所以我对任何想法持开放态度,这可能会解决我的问题。

多谢!

func <- function(X) {
paste(names(
Filter(function(z) length(z) > 1,
lapply(X, unique))
), collapse = ";")
}
df %>%
group_by(ID) %>%
mutate(col_diff = func(cur_data())) %>%
ungroup()
# # A tibble: 10 x 5
#       ID Info1 Info2 Info3 col_diff           
#    <dbl> <dbl> <chr> <dbl> <chr>              
#  1     1    10 A       999 ""                 
#  2     2    20 B       998 "Info2;Info3"      
#  3     2    20 A       997 "Info2;Info3"      
#  4     4    40 D       995 ""                 
#  5     5    50 E       995 "Info2"            
#  6     5    50 E       995 "Info2"            
#  7     5    50 F       995 "Info2"            
#  8     6    65 Z       946 "Info1;Info2;Info3"
#  9     6    60 A       800 "Info1;Info2;Info3"
# 10     7    70 B       805 ""                 

如果您打算以后以编程方式使用该col_diff,最好将其保留为list-column,以便您可以测试%in%等成员资格。

func2 <- function(X) {
replicate(nrow(X), names(
Filter(function(z) length(z) > 1,
lapply(X, unique))
), simplify = FALSE)
}
out <- df %>%
group_by(ID) %>%
mutate(col_diff = func2(cur_data())) %>%
ungroup()
out
# # A tibble: 10 x 5
#       ID Info1 Info2 Info3 col_diff 
#    <dbl> <dbl> <chr> <dbl> <list>   
#  1     1    10 A       999 <chr [0]>
#  2     2    20 B       998 <chr [2]>
#  3     2    20 A       997 <chr [2]>
#  4     4    40 D       995 <chr [0]>
#  5     5    50 E       995 <chr [1]>
#  6     5    50 E       995 <chr [1]>
#  7     5    50 F       995 <chr [1]>
#  8     6    65 Z       946 <chr [3]>
#  9     6    60 A       800 <chr [3]>
# 10     7    70 B       805 <chr [0]>
str(out$col_diff)
# List of 10
#  $ : chr(0) 
#  $ : chr [1:2] "Info2" "Info3"
#  $ : chr [1:2] "Info2" "Info3"
#  $ : chr(0) 
#  $ : chr "Info2"
#  $ : chr "Info2"
#  $ : chr "Info2"
#  $ : chr [1:3] "Info1" "Info2" "Info3"
#  $ : chr [1:3] "Info1" "Info2" "Info3"
#  $ : chr(0) 

我们可以将acrosscur_column()一起使用 - 按"ID"分组,循环across"信息"列,if列中不同元素的数量(n_distinct)大于1,返回列名(cur_column())或返回空白(""),然后将列reduce为单个字符串paste(str_c),并删除任何带有trimws的前导/滞后分隔符

library(dplyr)
library(stringr)
library(purrr)
df %>% 
group_by(ID) %>%
mutate(col_diff = across(starts_with('Info'), 
~ if(n_distinct(.x) > 1) cur_column() else "") %>%
reduce(str_c, sep = "; ") %>% 
trimws(whitespace = ";\s+") ) %>%
ungroup

-输出

# A tibble: 10 × 5
ID Info1 Info2 Info3 col_diff             
<dbl> <dbl> <chr> <dbl> <chr>                
1     1    10 A       999 ""                   
2     2    20 B       998 "Info2; Info3"       
3     2    20 A       997 "Info2; Info3"       
4     4    40 D       995 ""                   
5     5    50 E       995 "Info2"              
6     5    50 E       995 "Info2"              
7     5    50 F       995 "Info2"              
8     6    65 Z       946 "Info1; Info2; Info3"
9     6    60 A       800 "Info1; Info2; Info3"
10     7    70 B       805 ""             

另一种可能的解决方案:

library(tidyverse)
ID <- c(1,2,2,4,5,5,5,6,6,7)
Info1 <- c(10,20,20,40,50,50,50,65,60,70)
Info2 <- c('A','B','A','D', 'E','E','F', 'Z','A','B')
Info3 <- c(999,998,997,995,995,995,995,946,800,805)
df <- data.frame(ID, Info1, Info2, Info3)
df %>% 
group_by(ID) %>% 
mutate(across(starts_with("Info"),
~ if_else(n_distinct(.x) != 1, cur_column(), NA_character_))) %>% ungroup %>% 
unite("col_diff", starts_with("Info"), na.rm = T, sep = "; ") %>% 
na_if("") %>% inner_join(df, ., by = c("ID")) %>% distinct %>% 
inner_join(df, ., by = c("ID", "Info1", "Info2", "Info3"))
#>    ID Info1 Info2 Info3            col_diff
#> 1   1    10     A   999                <NA>
#> 2   2    20     B   998        Info2; Info3
#> 3   2    20     A   997        Info2; Info3
#> 4   4    40     D   995                <NA>
#> 5   5    50     E   995               Info2
#> 6   5    50     E   995               Info2
#> 7   5    50     F   995               Info2
#> 8   6    65     Z   946 Info1; Info2; Info3
#> 9   6    60     A   800 Info1; Info2; Info3
#> 10  7    70     B   805                <NA>

既然您提到您对其他可能更"优雅"的解决方案持开放态度 - 这是一种以更"整洁"的方式报告一个ID内异构列的方法。

library(tidyverse)
# data
df <- structure(list(ID = c(1, 2, 2, 4, 5, 5, 5, 6, 6, 7), Info1 = c(10, 20, 20, 40, 50, 50, 50, 65, 60, 70), Info2 = c("A", "B", "A", "D", "E", "E", "F", "Z", "A", "B"), Info3 = c(999, 998, 997, 995, 995, 995, 995, 946, 800, 805)), class = "data.frame", row.names = c(NA, -10L))
# enumerate columns that are heterogeneous within one ID in tidy format
changes <- df %>% 
group_by(ID) %>% 
mutate(across(everything(), ~n_distinct(.x) > 1)) %>% 
pivot_longer(-ID, names_to = "col", values_to = "changed") %>% 
filter(changed) %>% 
select(-changed) %>% 
distinct()
# inspect result
changes
#> # A tibble: 6 x 2
#> # Groups:   ID [3]
#>      ID col  
#>   <dbl> <chr>
#> 1     2 Info2
#> 2     2 Info3
#> 3     5 Info2
#> 4     6 Info1
#> 5     6 Info2
#> 6     6 Info3

这样就可以轻松地报告哪些ID在"重要"列中发生了更改。

# indicate important columns
important_cols <- c("Info2", "Info3")
# report IDs with changes in important columns
changes %>% 
filter(col %in% important_cols) 
#> # A tibble: 5 x 2
#> # Groups:   ID [3]
#>      ID col  
#>   <dbl> <chr>
#> 1     2 Info2
#> 2     2 Info3
#> 3     5 Info2
#> 4     6 Info2
#> 5     6 Info3

或者甚至将原始数据子集化为"重要"更改,以便您可以根据需要手动检查或进一步分析。

# or subset original data for 'important' changes
df %>%
select(ID,
any_of(intersect(unique(changes$col),
important_cols))) %>%
filter(ID %in% changes$ID)
#>   ID Info2 Info3
#> 1  2     B   998
#> 2  2     A   997
#> 3  5     E   995
#> 4  5     E   995
#> 5  5     F   995
#> 6  6     Z   946
#> 7  6     A   800

创建于 2022-02-09 由 reprex 软件包 (v2.0.1)

最新更新