R:根据多个变量/列/约束查找相似/ "duplicate" 对条目
R: Finding similiar/ "duplicate" PAIRS of entries based on multiple variables/ columns/ constraints
我对 R 比较陌生,对 Whosebug 绝对陌生(反正我在这里研究了很多,因为我之前有一些 Stata、Excel、VBA 和一点 C 的经验)。
我有一个 R 数据帧 df1,它看起来像下面的示例,只有几千行:
ID Date Value Class ZIP
TRA0001 2007-09-25 150 1 75019
TRA0002 2002-08-09 200 2 30152
TRA0003 2010-08-31 500 3 12451
TRA0004 2005-06-17 75 1 45242
TRA0005 2010-08-26 410 3 14618
TRA0006 2008-07-07 155 1 70139
TRA0007 2010-01-15 450 3 12883
TRA0008 2000-11-03 80 4 45242
TRA0009 2003-05-01 120 2 63017
TRA0010 2000-10-01 85 5 23712
每一行代表一笔交易。我需要找到的是基于以下 "matching criteria" (AND connected) 组合的每笔交易的相似交易:
- 日期 必须在 +/- 18 个月内,例如对于 TRA0001,唯一的匹配项是 TRA 0006
- Value 必须在原始行值的 +/- 20% 范围内,例如TRA0001 的匹配项是 TRA0006 和 TRA0009
- Class 必须是完全匹配,例如根据此标准,TRA0001 的匹配项将是 TRA0004 和 TRA0006
请注意,每个交易/行可以没有匹配项、一个匹配项或多个匹配项。最后我需要的是关于上述三个标准的组合的匹配列表。
对于给定的示例,结果 df2 将如下所示:
ID ID_Match ZIP_Match
TRA0001 TRA0006 70139
TRA0003 TRA0005 14618
TRA0003 TRA0007 12883
TRA0005 TRA0007 12883
TRA0006 TRA0001 75019
TRA0007 TRA0003 12451
TRA0007 TRA0005 14618
到目前为止,我尝试了重复搜索的各种组合,通过至少满足一个匹配条件来接近我想要的结果,然后 "filtering down" 根据其他限制条件得到这个结果。我从 Class 条件开始,因为在我看来这是最简单的标准(也可能也是最具选择性的)。我最后想到的只是例如所有 类 有重复项的列表,以及可以找到重复项的相应索引位置。为此,我使用了以下代码(在 Whosebug 上找到,归功于用户 "eddi"):
dups = duplicated(df1$Class) | duplicated(d1$Class, fromLast = T)
split(which(dups), df1$Class[dups])
然而,这仍然让我离我想要的结果很远,我不知道如何 "integrate" 其他条件。希望我能提供所有必要的信息,并能弄清楚我的问题。任何类型的提示、建议或解决方案都非常受欢迎!提前致谢!
另外:如果有人想出如何使用 Stata 完成所需工作的想法,这也将受到欢迎 - 我对 Stata 的了解略多于对 R 的了解。
我想我找到了一种可以做到的方法。基本上,我们定义一个函数,它将为一个 ID 执行您想要的操作,然后使用 sapply
遍历所有 ID,然后调用 rbind
将结果放在一起。
月数函数来自@Dirk,在this post
df <- read.table(text =
"ID Date Value Class ZIP
TRA0001 2007-09-25 150 1 75019
TRA0002 2002-08-09 200 2 30152
TRA0003 2010-08-31 500 3 12451
TRA0004 2005-06-17 75 1 45242
TRA0005 2010-08-26 410 3 14618
TRA0006 2008-07-07 155 1 70139
TRA0007 2010-01-15 450 3 12883
TRA0008 2000-11-03 80 4 45242
TRA0009 2003-05-01 120 2 63017
TRA0010 2000-10-01 85 5 23712",
header = T)
# turn a date into a 'monthnumber' relative to an origin
monnb <- function(d) {
lt <- as.POSIXlt(as.Date(d, origin="1900-01-01"))
lt$year*12 + lt$mon
}
# compute a month difference as a difference between two monnb's
mondf <- function(d1, d2) { monnb(d2) - monnb(d1) }
find_fxn <- function(data, origID){
#create subset with ID of interest
orig_data <- subset(data, ID == origID)
#subset of all other IDs
other_data <- subset(data, ID != origID)
#three matching criteria
find_first <- which(abs(mondf(orig_data$Date, other_data$Date)) <= 18)
find_second <- which(other_data$Value >= 0.8 * orig_data$Value & other_data$Value <= 1.2 * orig_data$Value)
find_third <- which(other_data$Class == orig_data$Class)
#use intersect to remove dups
find_all <- intersect(intersect(find_first, find_second), find_third)
if(length(find_all) > 0){
cbind.data.frame(ID = orig_data$ID,
IDMatch = other_data[find_all, 1],
ZipMatch = other_data[find_all, 5])
}
}
do.call('rbind', sapply(df$ID, FUN = function(x) find_fxn(data = df, origID = x)))
ID IDMatch ZipMatch
1 TRA0001 TRA0006 70139
2 TRA0003 TRA0005 14618
3 TRA0003 TRA0007 12883
4 TRA0005 TRA0007 12883
5 TRA0006 TRA0001 75019
6 TRA0007 TRA0003 12451
7 TRA0007 TRA0005 14618
首先使用data.table包。
然后你可以编写简单的函数,它会为提供的交易查找所有类似的交易。
最后循环您的数据集以获取所有相似的集合:
dt1 <- data.table::fread('ID Date Value Class ZIP
TRA0001 2007-09-25 150 1 75019
TRA0002 2002-08-09 200 2 30152
TRA0003 2010-08-31 500 3 12451
TRA0004 2005-06-17 75 1 45242
TRA0005 2010-08-26 410 3 14618
TRA0006 2008-07-07 155 1 70139
TRA0007 2010-01-15 450 3 12883
TRA0008 2000-11-03 80 4 45242
TRA0009 2003-05-01 120 2 63017
TRA0010 2000-10-01 85 5 23712')
dt1[, Date:=as.POSIXct(Date)]
myTransaction <- dt1[1]
dt1[Class==myTransaction$Class & abs(difftime(Date, myTransaction$Date, units='weeks')) < 4*18 & abs((Value-myTransaction$Value)/pom$Value) < .2]
similar <- lapply(1:nrow(dt1), function(x)
{
myTransaction <- dt1[x]
dt1[ID!=myTransaction$ID & Class==myTransaction$Class & abs(difftime(Date, myTransaction$Date, units='weeks')) < 4*18 & abs((Value-myTransaction$Value)/pom$Value) < .2]
})
names(similar) <- dt1$ID
使用similar[['TRA0006']]
检查类似交易。
有一个名为 rangejoin
(来自 SSC)的新用户编写的程序可以用来在 Stata 中轻松解决这个问题。为了使用 rangejoin
,您还必须安装 rangestat
(也来自 SSC)。要同时安装两者,请输入 Stata 的命令 window:
ssc install rangestat
ssc install rangejoin
rangejoin
形成指定范围内的所有成对组合的观察值。由于您想要匹配具有相同 Class 值的观察,因此可以在 Class 组内执行连接。由于您有每日日期,我将解决方案设置为使用 +/- 548 天的 window(基于一年 365.25 天)。一旦形成所有成对组合(在每个观察的指定时间 window 内),您可以删除那些与 Value 的 20% 阈值不匹配的组合。
这是一个使用您发布的数据的功能齐全的示例:
* Example generated by -dataex-. To install: ssc install dataex
clear
input str7 ID str10 Date int Value byte Class str5 ZIP
"TRA0001" "2007-09-25" 150 1 "75019"
"TRA0002" "2002-08-09" 200 2 "30152"
"TRA0003" "2010-08-31" 500 3 "12451"
"TRA0004" "2005-06-17" 75 1 "45242"
"TRA0005" "2010-08-26" 410 3 "14618"
"TRA0006" "2008-07-07" 155 1 "70139"
"TRA0007" "2010-01-15" 450 3 "12883"
"TRA0008" "2000-11-03" 80 4 "45242"
"TRA0009" "2003-05-01" 120 2 "63017"
"TRA0010" "2000-10-01" 85 5 "23712"
end
* convert string date to Stata numeric date
gen ndate = daily(Date, "YMD")
format %td ndate
* save a copy to disk
save "using_copy.do", replace
* match, within the same Class, obs +/- 18 months (365.25 * 1.5 =~ 548 days)
rangejoin ndate -548 548 using "using_copy.do", by(Class) suffix(_Match)
* drop matched ID if amount is off by 20% and match to self
drop if (abs(Value - Value_Match) / Value) > .2
drop if ID == ID_Match
* final results
sort ID ID_Match
list ID ID_Match ZIP_Match, sepby(ID) noobs
结果:
. list ID ID_Match ZIP_Match, sepby(ID) noobs
+-------------------------------+
| ID ID_Match ZIP_Ma~h |
|-------------------------------|
| TRA0001 TRA0006 70139 |
|-------------------------------|
| TRA0003 TRA0005 14618 |
| TRA0003 TRA0007 12883 |
|-------------------------------|
| TRA0005 TRA0007 12883 |
|-------------------------------|
| TRA0006 TRA0001 75019 |
|-------------------------------|
| TRA0007 TRA0003 12451 |
| TRA0007 TRA0005 14618 |
+-------------------------------+
我对 R 比较陌生,对 Whosebug 绝对陌生(反正我在这里研究了很多,因为我之前有一些 Stata、Excel、VBA 和一点 C 的经验)。
我有一个 R 数据帧 df1,它看起来像下面的示例,只有几千行:
ID Date Value Class ZIP
TRA0001 2007-09-25 150 1 75019
TRA0002 2002-08-09 200 2 30152
TRA0003 2010-08-31 500 3 12451
TRA0004 2005-06-17 75 1 45242
TRA0005 2010-08-26 410 3 14618
TRA0006 2008-07-07 155 1 70139
TRA0007 2010-01-15 450 3 12883
TRA0008 2000-11-03 80 4 45242
TRA0009 2003-05-01 120 2 63017
TRA0010 2000-10-01 85 5 23712
每一行代表一笔交易。我需要找到的是基于以下 "matching criteria" (AND connected) 组合的每笔交易的相似交易:
- 日期 必须在 +/- 18 个月内,例如对于 TRA0001,唯一的匹配项是 TRA 0006
- Value 必须在原始行值的 +/- 20% 范围内,例如TRA0001 的匹配项是 TRA0006 和 TRA0009
- Class 必须是完全匹配,例如根据此标准,TRA0001 的匹配项将是 TRA0004 和 TRA0006
请注意,每个交易/行可以没有匹配项、一个匹配项或多个匹配项。最后我需要的是关于上述三个标准的组合的匹配列表。
对于给定的示例,结果 df2 将如下所示:
ID ID_Match ZIP_Match
TRA0001 TRA0006 70139
TRA0003 TRA0005 14618
TRA0003 TRA0007 12883
TRA0005 TRA0007 12883
TRA0006 TRA0001 75019
TRA0007 TRA0003 12451
TRA0007 TRA0005 14618
到目前为止,我尝试了重复搜索的各种组合,通过至少满足一个匹配条件来接近我想要的结果,然后 "filtering down" 根据其他限制条件得到这个结果。我从 Class 条件开始,因为在我看来这是最简单的标准(也可能也是最具选择性的)。我最后想到的只是例如所有 类 有重复项的列表,以及可以找到重复项的相应索引位置。为此,我使用了以下代码(在 Whosebug 上找到,归功于用户 "eddi"):
dups = duplicated(df1$Class) | duplicated(d1$Class, fromLast = T)
split(which(dups), df1$Class[dups])
然而,这仍然让我离我想要的结果很远,我不知道如何 "integrate" 其他条件。希望我能提供所有必要的信息,并能弄清楚我的问题。任何类型的提示、建议或解决方案都非常受欢迎!提前致谢!
另外:如果有人想出如何使用 Stata 完成所需工作的想法,这也将受到欢迎 - 我对 Stata 的了解略多于对 R 的了解。
我想我找到了一种可以做到的方法。基本上,我们定义一个函数,它将为一个 ID 执行您想要的操作,然后使用 sapply
遍历所有 ID,然后调用 rbind
将结果放在一起。
月数函数来自@Dirk,在this post
df <- read.table(text =
"ID Date Value Class ZIP
TRA0001 2007-09-25 150 1 75019
TRA0002 2002-08-09 200 2 30152
TRA0003 2010-08-31 500 3 12451
TRA0004 2005-06-17 75 1 45242
TRA0005 2010-08-26 410 3 14618
TRA0006 2008-07-07 155 1 70139
TRA0007 2010-01-15 450 3 12883
TRA0008 2000-11-03 80 4 45242
TRA0009 2003-05-01 120 2 63017
TRA0010 2000-10-01 85 5 23712",
header = T)
# turn a date into a 'monthnumber' relative to an origin
monnb <- function(d) {
lt <- as.POSIXlt(as.Date(d, origin="1900-01-01"))
lt$year*12 + lt$mon
}
# compute a month difference as a difference between two monnb's
mondf <- function(d1, d2) { monnb(d2) - monnb(d1) }
find_fxn <- function(data, origID){
#create subset with ID of interest
orig_data <- subset(data, ID == origID)
#subset of all other IDs
other_data <- subset(data, ID != origID)
#three matching criteria
find_first <- which(abs(mondf(orig_data$Date, other_data$Date)) <= 18)
find_second <- which(other_data$Value >= 0.8 * orig_data$Value & other_data$Value <= 1.2 * orig_data$Value)
find_third <- which(other_data$Class == orig_data$Class)
#use intersect to remove dups
find_all <- intersect(intersect(find_first, find_second), find_third)
if(length(find_all) > 0){
cbind.data.frame(ID = orig_data$ID,
IDMatch = other_data[find_all, 1],
ZipMatch = other_data[find_all, 5])
}
}
do.call('rbind', sapply(df$ID, FUN = function(x) find_fxn(data = df, origID = x)))
ID IDMatch ZipMatch
1 TRA0001 TRA0006 70139
2 TRA0003 TRA0005 14618
3 TRA0003 TRA0007 12883
4 TRA0005 TRA0007 12883
5 TRA0006 TRA0001 75019
6 TRA0007 TRA0003 12451
7 TRA0007 TRA0005 14618
首先使用data.table包。
然后你可以编写简单的函数,它会为提供的交易查找所有类似的交易。
最后循环您的数据集以获取所有相似的集合:
dt1 <- data.table::fread('ID Date Value Class ZIP
TRA0001 2007-09-25 150 1 75019
TRA0002 2002-08-09 200 2 30152
TRA0003 2010-08-31 500 3 12451
TRA0004 2005-06-17 75 1 45242
TRA0005 2010-08-26 410 3 14618
TRA0006 2008-07-07 155 1 70139
TRA0007 2010-01-15 450 3 12883
TRA0008 2000-11-03 80 4 45242
TRA0009 2003-05-01 120 2 63017
TRA0010 2000-10-01 85 5 23712')
dt1[, Date:=as.POSIXct(Date)]
myTransaction <- dt1[1]
dt1[Class==myTransaction$Class & abs(difftime(Date, myTransaction$Date, units='weeks')) < 4*18 & abs((Value-myTransaction$Value)/pom$Value) < .2]
similar <- lapply(1:nrow(dt1), function(x)
{
myTransaction <- dt1[x]
dt1[ID!=myTransaction$ID & Class==myTransaction$Class & abs(difftime(Date, myTransaction$Date, units='weeks')) < 4*18 & abs((Value-myTransaction$Value)/pom$Value) < .2]
})
names(similar) <- dt1$ID
使用similar[['TRA0006']]
检查类似交易。
有一个名为 rangejoin
(来自 SSC)的新用户编写的程序可以用来在 Stata 中轻松解决这个问题。为了使用 rangejoin
,您还必须安装 rangestat
(也来自 SSC)。要同时安装两者,请输入 Stata 的命令 window:
ssc install rangestat
ssc install rangejoin
rangejoin
形成指定范围内的所有成对组合的观察值。由于您想要匹配具有相同 Class 值的观察,因此可以在 Class 组内执行连接。由于您有每日日期,我将解决方案设置为使用 +/- 548 天的 window(基于一年 365.25 天)。一旦形成所有成对组合(在每个观察的指定时间 window 内),您可以删除那些与 Value 的 20% 阈值不匹配的组合。
这是一个使用您发布的数据的功能齐全的示例:
* Example generated by -dataex-. To install: ssc install dataex
clear
input str7 ID str10 Date int Value byte Class str5 ZIP
"TRA0001" "2007-09-25" 150 1 "75019"
"TRA0002" "2002-08-09" 200 2 "30152"
"TRA0003" "2010-08-31" 500 3 "12451"
"TRA0004" "2005-06-17" 75 1 "45242"
"TRA0005" "2010-08-26" 410 3 "14618"
"TRA0006" "2008-07-07" 155 1 "70139"
"TRA0007" "2010-01-15" 450 3 "12883"
"TRA0008" "2000-11-03" 80 4 "45242"
"TRA0009" "2003-05-01" 120 2 "63017"
"TRA0010" "2000-10-01" 85 5 "23712"
end
* convert string date to Stata numeric date
gen ndate = daily(Date, "YMD")
format %td ndate
* save a copy to disk
save "using_copy.do", replace
* match, within the same Class, obs +/- 18 months (365.25 * 1.5 =~ 548 days)
rangejoin ndate -548 548 using "using_copy.do", by(Class) suffix(_Match)
* drop matched ID if amount is off by 20% and match to self
drop if (abs(Value - Value_Match) / Value) > .2
drop if ID == ID_Match
* final results
sort ID ID_Match
list ID ID_Match ZIP_Match, sepby(ID) noobs
结果:
. list ID ID_Match ZIP_Match, sepby(ID) noobs
+-------------------------------+
| ID ID_Match ZIP_Ma~h |
|-------------------------------|
| TRA0001 TRA0006 70139 |
|-------------------------------|
| TRA0003 TRA0005 14618 |
| TRA0003 TRA0007 12883 |
|-------------------------------|
| TRA0005 TRA0007 12883 |
|-------------------------------|
| TRA0006 TRA0001 75019 |
|-------------------------------|
| TRA0007 TRA0003 12451 |
| TRA0007 TRA0005 14618 |
+-------------------------------+