在 R 中的多个“脏”列中匹配两个数据集
Match two datasets across multiple ‘dirty’ columns in R
出于两个原因,我经常需要通过多个匹配列来匹配两个数据集。首先,这些特征中的每一个都是“脏”的,这意味着单个列即使在应该匹配时也不会始终匹配(对于真正匹配的行)。其次,特征不是唯一的(例如,男性和女性)。像这样的匹配对于跨时间匹配(预测试 post-测试分数)、不同数据模式(观察到的特征和实验室值)或研究参与者的多个数据集很有用。
我需要一个 select 是最佳匹配的试探法。 请注意,有很多匹配列和很多 ID,因此必须将它们都指定为列表或向量。例如,我在下面创建了两个数据集来匹配。在示例中,DT1 第 1 行(ID 1)是 DT2 第 1 行(ID 55)的最佳匹配,即使只有“match4”列匹配;这是因为 DT2 第 2 行和第 3 行与 DT1 第 2 行和第 3 行更匹配。奖励:DT1 第 7 行同样匹配 DT2 第 7 行和第 8 行,但与 DT2 第 7 行部分匹配,因此理想情况下 selected.
问题:对于 DT1,select 对 DT2 中匹配行的“最佳猜测”,并且仅使用 DT2 中的每一行一次。在 R 中执行此操作的最佳方法是什么(以高效且“最佳实践”惯用的方式)?
我的初步做法:
我创建了第三个 data.table,其中有一列来自 DT1 的 ID,称为 DTmatch。所有后续列都是来自 DT2 的 ID。对于 DTmatch 的第二列(以 DT2 的第一个 ID 命名),每个值应表示匹配列的计数(在本例中为 0 到 4)。接下来,找到每行和每列唯一的匹配 table 中的最高匹配值。最后,创建最后一列,指定与 DT1 ID 匹配的 DT2 ID(DTmatch 中的第 1 列)。
library(data.table)
# In this example, the datasets are matched by row number, but the real data is not.
DT1 = data.table(
ID = 1:7,
match1 = c("b","b","b","a","a","c",NA),
match2 = c(7, 8, 9, NA, NA, NA, NA),
match3 = c(0, 0, 0, "j", 13:15),
match4 = c(rep("m", 4), rep("f", 3)),
value1 = 45:51,
value2 = 100:106
)
DT2 = data.table(
ID = 55:62,
match1 = c("b","b",4,"a","a","c","j","j"),
match2 = c(77, 8:14),
match3 = c(9:14, 155, 16),
match4 = c(rep("m", 4), NA, rep("f", 3)),
value1 = 145:152,
value2 = 101:108
)
# Fix numeric IDs
DT1[, ID := make.names(ID)]
DT2[, ID := make.names(ID)]
# Make new matching table
DTmatch <- DT1[, .(make.names(ID))]
setnames(DTmatch, old = "V1", new = "DT1ID")
# Start with one ID and one matching column
DT2ID <- DT2$ID[1]
DTmatch[, (DT2ID) := 0]
matchingCols <- c("match1")
# Code for first ID and match1, to be adapted for all IDs and all columns
DTmatch[, (DT2ID) := eval(parse(text=DT2ID)) + as.numeric(DT1[, (matchingCols), with=F] == DT2[ID==DT2ID, matchingCols, with=F][[1]])]
# First attempt at matching doesn't work due to NAs
for (thisID in DT2$ID) {
DTmatch[, (thisID) := 0]
for (matchingCol in matchingCols) {
# if (!is.na(DT1[, matchingCol, with=F]) & !is.na(DT2[ID==thisID, matchingCol, with=F])) {
DTmatch[, (thisID) := eval(parse(text=thisID)) + as.numeric(DT1[, (matchingCol), with=F] == DT2[ID==thisID, matchingCol, with=F][[1]])]
# }
}
}
也许这是一个开始的选项:
首先,通过将匹配列中的所有值粘贴在一起来创建一个新列
#create new column based on matching cols
DT1[, col_join := do.call( paste, c(.SD, sep="")), .SDcols= match1:match4][]
DT2[, col_join := do.call( paste, c(.SD, sep="")), .SDcols= match1:match4][]
然后,使用 fuzzyjoin
包,您可以执行基于字符串距离的连接。
下面,最大距离设置为2。所以如果在2的距离内没有找到匹配的字符串,连接的结果将是<NA>
.
您 can/should 尝试不同的 stringdist 方法和最大距离...
library(fuzzyjoin)
result <- stringdist_join( DT2, DT1,
by = "col_join",
max_dist = 2,
mode = "left",
distance_col = "string_distance" )
result[,c(1,8,9,16,17)][]
# ID.x col_join.x ID.y col_join.y string_distance
# 1: 55 b779m 1 b70m 2
# 2: 56 b810m 1 b70m 2
# 3: 56 b810m 2 b80m 1
# 4: 56 b810m 3 b90m 2
# 5: 57 4911m NA <NA> NA
# 6: 58 a1012m NA <NA> NA
# 7: 59 a1113NA NA <NA> NA
# 8: 60 c1214f 6 cNA14f 2
# 9: 61 j13155f NA <NA> NA
# 10: 62 j1416f NA <NA> NA
如您所见,您仍然需要弄清楚一些东西,例如 "what to do with NA-values"。
对于 Fuzzy 加入,总是(在我看来)涉及很多试验和错误。很多时候你将不得不接受 'the perfect answer' 只是 而不是 ...
出于两个原因,我经常需要通过多个匹配列来匹配两个数据集。首先,这些特征中的每一个都是“脏”的,这意味着单个列即使在应该匹配时也不会始终匹配(对于真正匹配的行)。其次,特征不是唯一的(例如,男性和女性)。像这样的匹配对于跨时间匹配(预测试 post-测试分数)、不同数据模式(观察到的特征和实验室值)或研究参与者的多个数据集很有用。
我需要一个 select 是最佳匹配的试探法。
问题:对于 DT1,select 对 DT2 中匹配行的“最佳猜测”,并且仅使用 DT2 中的每一行一次。在 R 中执行此操作的最佳方法是什么(以高效且“最佳实践”惯用的方式)?
我的初步做法: 我创建了第三个 data.table,其中有一列来自 DT1 的 ID,称为 DTmatch。所有后续列都是来自 DT2 的 ID。对于 DTmatch 的第二列(以 DT2 的第一个 ID 命名),每个值应表示匹配列的计数(在本例中为 0 到 4)。接下来,找到每行和每列唯一的匹配 table 中的最高匹配值。最后,创建最后一列,指定与 DT1 ID 匹配的 DT2 ID(DTmatch 中的第 1 列)。
library(data.table)
# In this example, the datasets are matched by row number, but the real data is not.
DT1 = data.table(
ID = 1:7,
match1 = c("b","b","b","a","a","c",NA),
match2 = c(7, 8, 9, NA, NA, NA, NA),
match3 = c(0, 0, 0, "j", 13:15),
match4 = c(rep("m", 4), rep("f", 3)),
value1 = 45:51,
value2 = 100:106
)
DT2 = data.table(
ID = 55:62,
match1 = c("b","b",4,"a","a","c","j","j"),
match2 = c(77, 8:14),
match3 = c(9:14, 155, 16),
match4 = c(rep("m", 4), NA, rep("f", 3)),
value1 = 145:152,
value2 = 101:108
)
# Fix numeric IDs
DT1[, ID := make.names(ID)]
DT2[, ID := make.names(ID)]
# Make new matching table
DTmatch <- DT1[, .(make.names(ID))]
setnames(DTmatch, old = "V1", new = "DT1ID")
# Start with one ID and one matching column
DT2ID <- DT2$ID[1]
DTmatch[, (DT2ID) := 0]
matchingCols <- c("match1")
# Code for first ID and match1, to be adapted for all IDs and all columns
DTmatch[, (DT2ID) := eval(parse(text=DT2ID)) + as.numeric(DT1[, (matchingCols), with=F] == DT2[ID==DT2ID, matchingCols, with=F][[1]])]
# First attempt at matching doesn't work due to NAs
for (thisID in DT2$ID) {
DTmatch[, (thisID) := 0]
for (matchingCol in matchingCols) {
# if (!is.na(DT1[, matchingCol, with=F]) & !is.na(DT2[ID==thisID, matchingCol, with=F])) {
DTmatch[, (thisID) := eval(parse(text=thisID)) + as.numeric(DT1[, (matchingCol), with=F] == DT2[ID==thisID, matchingCol, with=F][[1]])]
# }
}
}
也许这是一个开始的选项:
首先,通过将匹配列中的所有值粘贴在一起来创建一个新列
#create new column based on matching cols
DT1[, col_join := do.call( paste, c(.SD, sep="")), .SDcols= match1:match4][]
DT2[, col_join := do.call( paste, c(.SD, sep="")), .SDcols= match1:match4][]
然后,使用 fuzzyjoin
包,您可以执行基于字符串距离的连接。
下面,最大距离设置为2。所以如果在2的距离内没有找到匹配的字符串,连接的结果将是<NA>
.
您 can/should 尝试不同的 stringdist 方法和最大距离...
library(fuzzyjoin)
result <- stringdist_join( DT2, DT1,
by = "col_join",
max_dist = 2,
mode = "left",
distance_col = "string_distance" )
result[,c(1,8,9,16,17)][]
# ID.x col_join.x ID.y col_join.y string_distance
# 1: 55 b779m 1 b70m 2
# 2: 56 b810m 1 b70m 2
# 3: 56 b810m 2 b80m 1
# 4: 56 b810m 3 b90m 2
# 5: 57 4911m NA <NA> NA
# 6: 58 a1012m NA <NA> NA
# 7: 59 a1113NA NA <NA> NA
# 8: 60 c1214f 6 cNA14f 2
# 9: 61 j13155f NA <NA> NA
# 10: 62 j1416f NA <NA> NA
如您所见,您仍然需要弄清楚一些东西,例如 "what to do with NA-values"。
对于 Fuzzy 加入,总是(在我看来)涉及很多试验和错误。很多时候你将不得不接受 'the perfect answer' 只是 而不是 ...