在 R 中识别 Duplicate/Unique 个团队(和重组数据)

Identifying Duplicate/Unique Teams (and Restructuring Data) in R

我有一个如下所示的数据集:

 Person Team
   1     30
   2     30
   3     30
   4     30
   11    40
   22    40
   1     50
   2     50
   3     50
   4     50
   15    60
   16    60
   17    60
   1     70
   2     70
   3     70
   4     70
   11    80
   22    80

我的总体目标是组织团队标识代码,以便很容易看出哪些团队是彼此重复的,哪些团队是唯一的。我想把数据总结成这样:

 Team   Duplicate1  Duplicate2
  30        50          70
  40        80  
  60        

如您所见,第 30、50 和 70 队的成员相同,因此他们在同一行。同样,第 40 队和第 80 队的成员相同,因此他们共享一行。只有团队 60(在此示例中)是唯一的。

在团队重复的情况下,我不关心哪个团队 ID 出现在哪一列中。此外,一个团队可能有 2 个以上的副本。团队人数从 2 人到 8 人不等。

将其用于您的样本数据

dd<-structure(list(Person = c(1L, 2L, 3L, 4L, 11L, 22L, 1L, 2L, 3L, 
4L, 15L, 16L, 17L, 1L, 2L, 3L, 4L, 11L, 22L), Team = c(30L, 30L, 
30L, 30L, 40L, 40L, 50L, 50L, 50L, 50L, 60L, 60L, 60L, 70L, 70L, 
70L, 70L, 80L, 80L)), .Names = c("Person", "Team"), 
class = "data.frame", row.names = c(NA, -19L))

您可以尝试使用 table()/interaction() 来查找重复的组。例如

tt <- with(dd, table(Team, Person))
grp <- do.call("interaction", c(data.frame(unclass(tt)), drop=TRUE))
split(rownames(tt), grp)

这个returns

$`1.1.1.1.0.0.0.0.0`
[1] "30" "50" "70"

$`0.0.0.0.0.1.1.1.0`
[1] "60"

$`0.0.0.0.1.0.0.0.1`
[1] "40" "80"

所以组 "names" 实际上只是每个人的成员资格指标。如果你喜欢 setNames(),你可以很容易地重命名它们。但在这里它会折叠相应的团队。

不完全是您想要的格式,但非常有用:

# using MrFlick's data
library(dplyr)
dd %>% group_by(Team) %>%
    arrange(Person) %>%
    summarize(team.char = paste(Person, collapse = "_")) %>%
    group_by(team.char) %>%
    arrange(team.char, Team) %>%
    mutate(duplicate = 1:n())

Source: local data frame [6 x 3]
Groups: team.char

  Team team.char duplicate
1   40     11_22         1
2   80     11_22         2
3   60  15_16_17         1
4   30   1_2_3_4         1
5   50   1_2_3_4         2
6   70   1_2_3_4         3

(在 arrange(Person) 行中编辑,以防数据尚未排序,从@Reed 的回答中得到灵感。)

另外两个基本 R 选项(尽管不完全是所需的输出):

DF2 <- aggregate(Person ~ Team, DF, toString)
> split(DF2$Team, DF2$Person)
$`1, 2, 3, 4`
[1] 30 50 70

$`11, 22`
[1] 40 80

$`15, 16, 17`
[1] 60

或者

( DF2$DupeGroup <- as.integer(factor(DF2$Person)) )
  Team     Person DupeGroup
1   30 1, 2, 3, 4         1
2   40     11, 22         2
3   50 1, 2, 3, 4         1
4   60 15, 16, 17         3
5   70 1, 2, 3, 4         1
6   80     11, 22         2

请注意,问题中显示的预期输出将需要在某些列条目中添加 NA 或空字符串,因为在 data.frame 中,所有列必须具有相同的行数。正如您在某些答案中看到的那样,对于 lists in 是不同的。


第二个选项,但使用data.table,因为aggregate对于大数据来说往往很慢:

library(data.table)
setDT(DF)[, toString(Person), by=Team][,DupeGroup := .GRP, by=V1][]
   Team         V1 DupeGroup
1:   30 1, 2, 3, 4         1
2:   40     11, 22         2
3:   50 1, 2, 3, 4         1
4:   60 15, 16, 17         3
5:   70 1, 2, 3, 4         1
6:   80     11, 22         2

此答案给出了您要求的输出数据格式。我将重复的团队留在一个变量中,因为我认为这是处理任意数量的重复项的更好方法。

require(dplyr)

df %>%
  arrange(Team, Person) %>%   # this line is necessary in case the rest of your data isn't sorted
  group_by(Team) %>%
  summarize(players = paste0(Person, collapse = ",")) %>%
  group_by(players) %>%
  summarize(teams = paste0(Team, collapse = ",")) %>%
  mutate(
    original_team = ifelse(grepl(",", teams), substr(teams, 1, gregexpr(",", teams)[[1]][1]-1), teams),
    dup_teams = ifelse(grepl(",", teams), substr(teams, gregexpr(",", teams)[[1]][1]+1, nchar(teams)), NA)
  )

结果:

Source: local data frame [3 x 4]

   players    teams original_team dup_teams
1  1,2,3,4 30,50,70            30     50,70
2    11,22    40,80            40        80
3 15,16,17       60            60        NA

使用 mgcv 包中的 uniquecombs

library(mgcv)
library(magrittr) # for the pipe %>%

# Using MrFlick's data
team_names <- sort(unique(dd$Team))
unique_teams <- with(dd, table(Team, Person)) %>% uniquecombs %>% attr("index")
printout <- unstack(data.frame(team_names, unique_teams))

> printout
$`1`
[1] 60

$`2`
[1] 40 80

$`3`
[1] 30 50 70

现在您可以使用 this answer 之类的东西以表格形式打印它(请注意,这些组是按列排列的,而不是像您的问题中那样按行排列):

attributes(printout) <- list(names = names(printout)
                             , row.names = 1:max(sapply(printout, length))
                             , class = "data.frame")
> printout
     1    2  3
1   60   40 30
2 <NA>   80 50
3 <NA> <NA> 70
Warning message:
In format.data.frame(x, digits = digits, na.encode = FALSE) :
  corrupt data frame: columns will be truncated or padded with NAs