在列中查找重复的ID,并在R中将行组合在一起

时间:2018-01-05 18:06:55

标签: r duplicates dplyr aggregate

我正在使用标识鱼标签ID的历史数据集。多年来,一些鱼被多次标记,因此具有与同一个体相关联的多个标签ID(即,标签被新标签替换)。我想确定每条鱼的所有可能的标签ID。每条鱼没有唯一的标识符;我只能识别标签ID之间的关联。我已设法清理一些数据集并在一列中标识唯一标记ID,并将任何关联的其他标记ID添加到同一行中的其他列。但是,列之间存在重复。以下是我的数据集的示例:

<html>
  <head>
    <meta charset="utf-8">

    <link href="https://fonts.googleapis.com/css?family=Raleway" rel="stylesheet">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" crossorigin="anonymous">
     <!-- jQuery library -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <!-- Latest compiled JavaScript -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
  </head>
  <body>
    <div class="container">
      <div>
        <button id="toggleBtn">Show and Hide</button>
      </div>
      <div class="laptops">
        <p>
        The content you want to show and hide. Here is the mac/laptop section.<br>
        More stuff about laptops.<br>
        Event more content if you wanted it.
        </p>
      </div>
    </div>
  </body>
</html>
ID1<-c(101,102,103,105,106,107,108,110,111,112,113,114)
ID2<-c(NA,101,290,309,105,108,NA,220,NA,113,112,112)
ID3<-c(NA,NA,400,106,NA,NA,NA,111,NA,NA,NA,NA)
data<-data.frame(ID1,ID2,ID3)

正如你在这个例子中看到的那样,鱼1会有标签ID 101和102 - 现在看起来像两条独立的鱼,如果你只看着ID1栏,但我们知道它是同一条鱼,因为它也是标签ID 101与标签102相关联。

我的结果数据框应如下所示:

    ID1   ID2  ID3 
1   101    NA   NA
2   102   101   NA     
3   103   290  400    
4   105   309  106     
5   106   105   NA    
6   107   108   NA  
7   108    NA   NA
8   110   220  111  
9   111    NA   NA
10  112   113   NA
11  113   112   NA
12  114   112   NA

虽然ID1(第一列)中没有重复的标记ID,但 ID1和ID2之间以及ID1和ID3之间存在重复项(ID2和ID3之间不存在重复项,但NA除外)。 ID2中有一些重复项,因为它们与ID1中的另一个ID相关联(请参阅上面示例中的行10:12,其中标记ID 112在ID1中显示一次,在ID2中显示两次)。

我已经使用%in%命令确定了哪些ID在列之间重复,例如

    ID1   ID2  ID3 
1   101   102   NA
2   103   290  400        
3   105   309  106         
4   107   108   NA  
5   110   220  111  
6   112   113  114

我已经把它建成了一个ifelse声明:

data$ID1[data$ID1 %in% data$ID2]
data$ID1[data$ID1 %in% data$ID3]

但这只能告诉我哪些ID是重复的,而我却忘记了如何将信息实际组合成一行。

我也尝试将这些数据分成两个不同的数据帧,以便我可以使用join命令,但是我丢失了相关信息。

我在想我可能需要使用aggregate()或者merge()并将它包装到我的ifelse语句中?或者也许在dplyr中有一种方法可以做到这一点?任何帮助将不胜感激!

3 个答案:

答案 0 :(得分:0)

我建议你提出一个不合适的问题解决方案,只适合你的玩具数据和你说的条件:“ID1和ID2之间存在重复,ID1和ID3之间存在重复(ID2和ID3之间不存在重复,但NAs除外) )”。 事实上,根据您的数据,似乎每条鱼最多有三个重复的ID。出于这个原因,这是我的解决方案:

library(tidyverse)

您的数据:

ID1<-c(101,102,103,105,106,107,108,110,111,112,113,114)
ID2<-c(NA,101,290,309,105,108,NA,220,NA,113,112,112)
ID3<-c(NA,NA,400,106,NA,NA,NA,111,NA,NA,NA,NA)
Your data frame:
data <- data.frame(cbind(ID1,ID2,ID3))

我创建了两个数据框,根据您的声明确认ID仅在ID1和ID2之间或ID1和ID3之间存在ID:

data1 <- data.frame(cbind(ID1,ID2)) %>% 
    rename(A=ID1,B=ID2)
data2 <- data.frame(cbind(ID1,ID3)) %>% 
    rename(A=ID1,B=ID3)

我绑定两个数据帧,并按行从最小值到最大值反转ID值。所以我可能只选择不同的ID对,我可以通过一些数据争用操作创建重复ID的三元组:

bind_rows(data1,data2) %>% 
    filter(complete.cases(.)) %>% 
    mutate(ID1=pmin(A,B),
           ID2=pmax(A,B)) %>% 
    select(ID1,ID2) %>% 
    arrange(ID1,ID2) %>% 
    group_by(ID1) %>% 
    mutate(ID3=max(ID2)) %>% 
    distinct(.) %>% 
    filter(ID2==first(ID2))

结果(没有NAs):

# A tibble: 6 x 3
# Groups: ID1 [6]
    ID1   ID2   ID3
  <dbl> <dbl> <dbl>
1   101   102   102
2   103   290   400
3   105   106   309
4   107   108   108
5   110   111   220
6   112   113   114

答案 1 :(得分:0)

我对此问题采取了不同的方法,我认为这与从您提供的数据生成ID值集相关。我没有假设三个ID列是不同的,或者在解决方案中是否正确。该解决方案也不会对应用于实际数据所产生的列数做出任何假设 - 如果任何结果集中有超过三个条目,则带有结果的数据框将增加其列数以适应相应的这些。

我已经使用tidyverse函数和集合的循环处理的组合复制了所请求的结果。 @Scipione的回答很好地证明了单独使用tidyverse函数完成所有这些,但正如他提到的那样,解决方案与示例中的数据一致;我希望尽可能使用基于集合的方法进行概括。

在@Scipione的回答中,我从您的数据开始,然后使用set union识别重复的ID。这些在下面的解决方案中没有进一步使用,但这些可能本身就是有意义的:

library(dplyr)
library(tidyr)

ID1<-c(101,102,103,105,106,107,108,110,111,112,113,114)
ID2<-c(NA,101,290,309,105,108,NA,220,NA,113,112,112)
ID3<-c(NA,NA,400,106,NA,NA,NA,111,NA,NA,NA,NA)
data<-data.frame(ID1,ID2,ID3)

data.duplicates = union(intersect(ID1, ID2), intersect(ID1, ID3))

> data.duplicates
[1] 101 105 108 112 113 106 111

因此,在样本数据中,有七个ID在ID1和ID2或ID1和ID3列中出现多次。

要开始正确生成ID行,我转置数据然后将结果转换为长格式,然后自动加入ID上的表:

data.t = 
  data.frame(t(data)) %>%
  mutate(Cols = rownames(.)) %>%
  gather(key = row, value = ID, starts_with("X"))

data.t.joined = 
  filter(data.t, !is.na(ID)) %>% 
  inner_join(data.t, by = "ID")

 > head(data.t.joined, 6)
   Cols.x row.x  ID Cols.y row.y
1     ID1    X1 101    ID1    X1
2     ID1    X1 101    ID2    X2
3     ID1    X2 102    ID1    X2
4     ID2    X2 101    ID1    X1
5     ID2    X2 101    ID2    X2
6     ID1    X3 103    ID1    X3

这会生成来自不同行的ID,然后可以进一步处理这些ID以生成原始行的唯一ID列表(数据框中的row.x):

data.t.combined =
  data.t.joined %>%
  mutate(row.x = as.integer(gsub("X", "", row.x))) %>%
  select(row.x, ID) %>%
  group_by(row.x) %>%
  summarise(IDs = list(sort(unique(ID))))

此时我们有许多包含ID集的列表,但这些列表包括单个ID和其他子集,这些子集随后将合并在一起以生成一个联合的最终ID集。中间ID列的摘录如下所示:

> head(data.t.combined$IDs)
[[1]]
[1] 101

[[2]]
[1] 101 102

[[3]]
[1] 103 290 400

[[4]]
[1] 105 106 309

[[5]]
[1] 105 106

[[6]]
[1] 107 108

现在是一种蛮力的方法。下面列出的函数合并子集并删除重复的集。如果数据中的行数非常大,这是一种低效的方法,因为它涉及n *(n-1)个比较,n ^ 2有效,如果实际行数达到数十,这将是耗时的。成千上万。

mergesubsets <- function(thedata){
  thedata$NewIDs = thedata$IDs
  rows = nrow(thedata)
  for (i in 1:rows){
    entry = unlist(thedata$NewIDs[i])
    for (j in 1:rows){
      if (i != j){
        otherentry = unlist(thedata$NewIDs[j])
        if(max(entry %in% otherentry)==1) {
          thedata$NewIDs[i] = list(sort(union(entry, otherentry)))
        }
      }
    }
  }
  thedata[!duplicated(thedata$NewIDs),]
}

data.t.merged = 
  mergesubsets(data.t.combined)

以上生成了一系列列表,因此最后一步是将这些列表转换为矩阵,然后将数据帧转换为输出。蛮力再次,但这次相当快(我确定有更简单的方法来转换不同长度的列表,但我无法在可用的时间内找到它们):

listtodataframe <- function(thedata){
  rows = nrow(thedata)
  cols = max(sapply(thedata$NewIDs, length))
  result = matrix(nrow = rows, ncol = cols)
  for (i in 1:rows){
    entry = unlist(thedata$NewIDs[i])
    for (j in 1:length(entry)){
      result[i, j] = entry[j]
    }
  }
  data.frame(result)
}

result = listtodataframe(data.t.merged)

最终结果反映了你帖子中关于你期望的结果的内容,尽管在我的行中,行按ID递增:

> result
   X1  X2  X3
1 101 102  NA
2 103 290 400
3 105 106 309
4 107 108  NA
5 110 111 220
6 112 113 114

为了进行比较,这是您发布的预期结果:

    ID1   ID2  ID3 
1   101   102   NA
2   103   290  400        
3   105   309  106         
4   107   108   NA  
5   110   220  111  
6   112   113  114

我应该重申循环处理对于大量行来说是低效的。无论如何,就像在@Scipione的解决方案中一样,它从测试数据中看起来可以生成您期望的结果,并且它应该与更大的真实数据集一起使用,尽管它可能不是最节省时间的。

答案 2 :(得分:0)

我发现了我最初发布的例程中的一些遗漏,这些遗漏导致了您找到的重复项。我更正了以下修订后的代码:

data.duplicates = union(intersect(data$ID1, data$ID2), intersect(data$ID1, data$ID3))

data.t = 
  data.frame(t(data)) %>%
  mutate(Cols = rownames(.)) %>%
  gather(key = row, value = ID, starts_with("X"))

data.t.joined = 
  filter(data.t, !is.na(ID)) %>% 
  inner_join(data.t, by = "ID")

data.t.combined =
  data.t.joined %>%
  mutate(row.x = as.integer(gsub("X", "", row.x))) %>%
  select(row.x, ID) %>%
  group_by(row.x) %>%
  summarise(IDs = list(sort(unique(ID))))

mergesubsets <- function(thedata){
  rows = nrow(thedata)
  for (i in 1:rows){
    entry = unlist(thedata$IDs[i])
    for (j in 1:rows){
      if (i!=j){
        otherentry = unlist(thedata$IDs[j])
        if(max(entry %in% otherentry)==1) {
          entry = sort(union(entry, otherentry))
          thedata$IDs[i] = list(entry)
        }
      }
    }
  }
  thedata[!duplicated(thedata$IDs),]
}

listtodataframe <- function(thedata){
  rows = nrow(thedata)
  cols = max(sapply(thedata$IDs, length))
  result = matrix(nrow = rows, ncol = cols)
  for (i in 1:rows){
    entry = unlist(thedata$IDs[i])
    for (j in 1:length(entry)){
      result[i, j] = entry[j]
    }
  }
  data.frame(result)
}


data.t.merged = data.t.combined

prevrows = 0
rows = nrow(data.t.merged)
starttime = proc.time()[3]
while(rows != prevrows) {
  prevrows = rows
  data.t.merged = 
    mergesubsets(data.t.merged)
  rows = nrow(data.t.merged)
}
endtime = proc.time()[3]
timetorun = endtime - starttime 
timetorun


result = listtodataframe(data.t.merged)
result
write.csv(result, "result.csv", row.names = FALSE)

我已经使用500行数字数据的不同数据集测试了修改后的代码,这些数据太大,无法在此处发布。它现在可以正确识别所有唯一值集,无论这些值有多少。当我使用随机数据集进行测试时,在某些情况下,我最终会得到一行,其中包含所有唯一值。

很抱歉,我到目前为止还没有能够加速这个版本,正如你所提到的那样,在11,000行操作时非常耗时。我很欣赏您的真实数据是非数字的,与数字数据相比,处理效率也较低。

我的测试已经进行了500行,大概是25秒完成。

500行全数字测试数据的输出示例如下所示。

> result
        X1     X2    X3    X4    X5    X6    X7    X8    X9   X10   X11   X12   X13
1      100    101   102   103   104   105   200   300    NA    NA    NA    NA    NA
2      110    113   210   321    NA    NA    NA    NA    NA    NA    NA    NA    NA
3      111    211   311    NA    NA    NA    NA    NA    NA    NA    NA    NA    NA
4      112    312   412    NA    NA    NA    NA    NA    NA    NA    NA    NA    NA
5      500    600   601   602   603   604   605   610   613   700   710   800   821
6      611    711   811    NA    NA    NA    NA    NA    NA    NA    NA    NA    NA
7      612    812   912    NA    NA    NA    NA    NA    NA    NA    NA    NA    NA
8     1000   1100  1101  1102  1103  1104  1105  1110  1113  1200  1210  1300  1321
9     1111   1211  1311    NA    NA    NA    NA    NA    NA    NA    NA    NA    NA
10    1112   1312  1412    NA    NA    NA    NA    NA    NA    NA    NA    NA    NA
11    1500   1600  1601  1602  1603  1604  1605  1610  1613  1700  1710  1800  1821