R 数据帧通过匹配值不等式连接到由 2 列定义的范围
R data frames joined by matching value inequality to a range defined by 2 columns
在 R 中,我知道 joining/merging 数据帧有很多不同的方法,它们基于两列或多列之间的相等条件。
但是,我需要根据值与值范围的匹配来连接两个数据框,值范围由 2 列定义,在一种情况下使用大于或等于,在一种情况下使用小于或-等于另一个。如果我使用 SQL,查询可能是:
SELECT * FROM Table1,
LEFT JOIN Table2
ON Table1.Value >= Table2.LowLimit AND Table1.Value <= Table2.HighLimit
我知道 sqldf
软件包,但我想尽可能避免使用它。
我正在处理的数据是一个带有 ip 地址的数据帧,如下所示:
ipaddresses <- data.frame(IPAddress=c("1.1.1.1","2.2.2.2","3.3.3.3","4.4.4.4"))
另一个数据框是MaxMind geolite2数据库,包含一个ip-address range start和ip-address range end,以及一个地理位置ID:
ip_range_start <- c("1.1.1.0","3.3.3.0")
ip_range_end <- c("1.1.1.255","3.3.3.100")
geolocationid <- c("12345","67890")
ipranges <- data.frame(ip_range_start,ip_range_end,geolocationid)
所以,我需要实现的是将 ipranges$geolocationid
连接到 ipaddresses
,在每种情况下
ipaddresses$IPAddress >= ipranges$ip_range_start
AND
ipaddresses$IPAddress <= ipranges$ip_range_end
根据上面的示例数据,这意味着我需要正确找到 1.1.1.1 在 1.1.1.0-1.1.1.255 范围内,3.3.3.3 在 3.3.3.0-3.3 范围内。 3.100.
这种方法可能无法很好地扩展,因为它涉及最初通过 broom::inflate()
进行外部连接,但如果您没有大量的 ip 地址,它应该可行:
library(dplyr)
library(broom)
ipranges %>%
inflate(ipaddresses) %>%
ungroup %>%
filter(
numeric_version(IPAddress) >= numeric_version(ip_range_start),
numeric_version(IPAddress) <= numeric_version(ip_range_end)
)
结果
Source: local data frame [2 x 4]
IPAddress ip_range_start ip_range_end geolocationid
(fctr) (fctr) (fctr) (fctr)
1 1.1.1.1 1.1.1.0 1.1.1.255 12345
2 3.3.3.3 3.3.3.0 3.3.3.100 67890
经过一些额外的研究,我实际上找到了适合我的特定用例的解决方案。尽管如此,它仍然不是一般问题的解决方案:如何连接两个数据帧,其中连接条件是 key >= value1 AND key <= value2。但是,它确实解决了我遇到的实际问题。
我最终找到的解决我对 ip 地址地理位置需求的好方法是包 rgeolocate in combination with the downloadable binary version of the MaxMind GeoLite2 database。
解决方案快如闪电; 500 多个 IP 地址与 3 多万个 IP 范围的匹配在一秒钟内完成。我之前的尝试涉及将 MaxMind 数据库的 CSV 版本加载到数据框中并从那里开始工作。不要那样做。感谢 rgeolocate 包和二进制 MaxMind 数据库,它要快得多。
我的代码最终变成了这个(dataunion 是我收集的 ip 地址的数据框的名称)
library(rgeolocate)
ipaddresslist <- as.character(dataunion$IPAddress)
geoloc <- maxmind(ipaddresslist, "GeoLite2-City.mmdb", c("latitude","longitude", "continent_name","country_name","region_name","city_name"))
colnames(geoloc) <- c("Lat","Long","Continent","Country","Region","City")
dataunion <- cbind(dataunion, geoloc)
最后,我找到了 一般 问题的解决方案,除了上述使用 MaxMind 数据库地理定位 IP 地址的特定问题的解决方案。
这是连接两个长度相等或不相等的数据帧的通用解决方案,其中必须将一个值与一个或多个列的不等条件(小于或大于)进行比较。
解决方案是使用 sapply
,它是基础 R。
有了问题中定义的两个数据框,ipranges
和ipaddresses
,我们有:
ipaddresses$geolocationid <- sapply(ipaddresses$IPAddress,
function(x)
ipranges$geolocationid[ipranges$ip_range_start <= x & ipranges$ip_range_end >= x])
sapply
所做的是从向量 ipaddresses$IPAddress
中获取每个元素,一次一个,并将其应用于作为参数提供给 sapply
的函数表达式。将函数应用于每个元素的结果元素附加到一个向量,这是sapply
的输出结果。这就是我们作为新列插入 ipaddresses$geolocationid
的内容。
在这种情况下,如果首先将 IP 地址转换为整数,sapply
操作可能会更快。这里有几行将使用包含每个 ip 地址的整数版本的列扩展 ipaddresses 数据框:
#calculating the integer version of each IP-address
octet <- data.frame(read.table(text=as.character(ipaddresses$IPAddress), sep="."))
octet$IPint <- 256^3*octet[,1] + 256^2*octet[,2] + 256*octet[,3] + octet[,4]
ipaddresses$IPint <- octet$IPint
# cleaning "octet" from memory
octet <- NULL
您显然必须对 ipranges
数据帧中的 IP 地址进行相同类型的转换。
在 R 中,我知道 joining/merging 数据帧有很多不同的方法,它们基于两列或多列之间的相等条件。
但是,我需要根据值与值范围的匹配来连接两个数据框,值范围由 2 列定义,在一种情况下使用大于或等于,在一种情况下使用小于或-等于另一个。如果我使用 SQL,查询可能是:
SELECT * FROM Table1,
LEFT JOIN Table2
ON Table1.Value >= Table2.LowLimit AND Table1.Value <= Table2.HighLimit
我知道 sqldf
软件包,但我想尽可能避免使用它。
我正在处理的数据是一个带有 ip 地址的数据帧,如下所示:
ipaddresses <- data.frame(IPAddress=c("1.1.1.1","2.2.2.2","3.3.3.3","4.4.4.4"))
另一个数据框是MaxMind geolite2数据库,包含一个ip-address range start和ip-address range end,以及一个地理位置ID:
ip_range_start <- c("1.1.1.0","3.3.3.0")
ip_range_end <- c("1.1.1.255","3.3.3.100")
geolocationid <- c("12345","67890")
ipranges <- data.frame(ip_range_start,ip_range_end,geolocationid)
所以,我需要实现的是将 ipranges$geolocationid
连接到 ipaddresses
,在每种情况下
ipaddresses$IPAddress >= ipranges$ip_range_start
AND
ipaddresses$IPAddress <= ipranges$ip_range_end
根据上面的示例数据,这意味着我需要正确找到 1.1.1.1 在 1.1.1.0-1.1.1.255 范围内,3.3.3.3 在 3.3.3.0-3.3 范围内。 3.100.
这种方法可能无法很好地扩展,因为它涉及最初通过 broom::inflate()
进行外部连接,但如果您没有大量的 ip 地址,它应该可行:
library(dplyr)
library(broom)
ipranges %>%
inflate(ipaddresses) %>%
ungroup %>%
filter(
numeric_version(IPAddress) >= numeric_version(ip_range_start),
numeric_version(IPAddress) <= numeric_version(ip_range_end)
)
结果
Source: local data frame [2 x 4]
IPAddress ip_range_start ip_range_end geolocationid
(fctr) (fctr) (fctr) (fctr)
1 1.1.1.1 1.1.1.0 1.1.1.255 12345
2 3.3.3.3 3.3.3.0 3.3.3.100 67890
经过一些额外的研究,我实际上找到了适合我的特定用例的解决方案。尽管如此,它仍然不是一般问题的解决方案:如何连接两个数据帧,其中连接条件是 key >= value1 AND key <= value2。但是,它确实解决了我遇到的实际问题。
我最终找到的解决我对 ip 地址地理位置需求的好方法是包 rgeolocate in combination with the downloadable binary version of the MaxMind GeoLite2 database。
解决方案快如闪电; 500 多个 IP 地址与 3 多万个 IP 范围的匹配在一秒钟内完成。我之前的尝试涉及将 MaxMind 数据库的 CSV 版本加载到数据框中并从那里开始工作。不要那样做。感谢 rgeolocate 包和二进制 MaxMind 数据库,它要快得多。
我的代码最终变成了这个(dataunion 是我收集的 ip 地址的数据框的名称)
library(rgeolocate)
ipaddresslist <- as.character(dataunion$IPAddress)
geoloc <- maxmind(ipaddresslist, "GeoLite2-City.mmdb", c("latitude","longitude", "continent_name","country_name","region_name","city_name"))
colnames(geoloc) <- c("Lat","Long","Continent","Country","Region","City")
dataunion <- cbind(dataunion, geoloc)
最后,我找到了 一般 问题的解决方案,除了上述使用 MaxMind 数据库地理定位 IP 地址的特定问题的解决方案。
这是连接两个长度相等或不相等的数据帧的通用解决方案,其中必须将一个值与一个或多个列的不等条件(小于或大于)进行比较。
解决方案是使用 sapply
,它是基础 R。
有了问题中定义的两个数据框,ipranges
和ipaddresses
,我们有:
ipaddresses$geolocationid <- sapply(ipaddresses$IPAddress,
function(x)
ipranges$geolocationid[ipranges$ip_range_start <= x & ipranges$ip_range_end >= x])
sapply
所做的是从向量 ipaddresses$IPAddress
中获取每个元素,一次一个,并将其应用于作为参数提供给 sapply
的函数表达式。将函数应用于每个元素的结果元素附加到一个向量,这是sapply
的输出结果。这就是我们作为新列插入 ipaddresses$geolocationid
的内容。
在这种情况下,如果首先将 IP 地址转换为整数,sapply
操作可能会更快。这里有几行将使用包含每个 ip 地址的整数版本的列扩展 ipaddresses 数据框:
#calculating the integer version of each IP-address
octet <- data.frame(read.table(text=as.character(ipaddresses$IPAddress), sep="."))
octet$IPint <- 256^3*octet[,1] + 256^2*octet[,2] + 256*octet[,3] + octet[,4]
ipaddresses$IPint <- octet$IPint
# cleaning "octet" from memory
octet <- NULL
您显然必须对 ipranges
数据帧中的 IP 地址进行相同类型的转换。