我正在使用标识鱼标签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中有一种方法可以做到这一点?任何帮助将不胜感激!
答案 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