在R

时间:2016-02-27 16:39:04

标签: r algorithm optimization

我写了一个将IP转换为Country的脚本。

我是怎么做的。
我们采用IP并将其转换为IP号码 假设IP地址是A.B.C.D.
IP号= A×(256×256×256)+ B×(256×256)+ C×256 + D
例如:

ip<-"5.102.240.155"
ip_Number<-5*(256*256*256)+102*(256*256)+240*(256)+1

我有另一个将IP号码转换为国家/地区的数据(可以从https://db-ip.com/db下载)。格式如下:
enter image description here

下一件事是检查IP_FROM&lt; = IP-Number&lt; = IP_TO
的位置 如果16779008&lt; = IP-Number&lt; = 16779263,那么该地点是Austarlia Melbourne。

这是将大量IP转换为位置的代码:

ipNumberFun<-function(ip){
  strIP<-as.integer(strsplit(x = ip,split = "\\.")[[1]])
  strIP[1]*(256*256*256)+strIP[2]*(256*256)+strIP[3]*(256)+strIP[4]
}

geoIp <- function(ip)
{
  if(!is.na(ip)){
    ip<-ipNumberFun(ip)
    IpData<-Data[which(Data$IP_FROM<=ip & Data$IP_TO>=ip),]
    out<-data.frame(COUNTRY_CODE=IpData$COUNTRY_CODE,
                    COUNTRY_NAME=IpData$COUNTRY_NAME,
                    REGION=IpData$REGION,
                    CITY=IpData$CITY)
  } else{
    out<-NULL
  }

  return(out)
}

outcomes <- lapply(X = BigArrayOfIP$ip,FUN =  geoIp)

问题是此脚本确实无法运行。 我认为问题在于:

IpData<-Data[which(Data$IP_FROM<=ip & Data$IP_TO>=ip),]

数据是非常大的文件。它包含9364224行 我该如何优化脚本。
谢谢!

1 个答案:

答案 0 :(得分:2)

使用data.table

可以获得很多速度提升

假设您拥有data.table个IP地址,结构如下:

library(data.table)
dt_ips <- data.table(ip_string = c("100.100.100.100", "...", "etc"))  
## This will likely be your 'BigArrayOfIP'

然后,您可以使用ipNumberFun

将每个值转换为整数
## This update by reference is not working
## dt_ips[, ip := ipNumberFun(ip_string)]
## so instead, we can use
dt_ips <- dt_ips[, .(ip = ipNumberFun(ip_string)), by=ip_string]

现在我们可以使用这些新形成的整数,找到适合Data查找表的位置

例如,假设我的上一次操作的结果给出:

dt_ips <- data.table(ip = c(16778245, 16777213, 16778497))
> dt_ips
#         ip
#1: 16778245
#2: 16777213
#3: 16778497

我们将Data设置为data.table

setDT(Data)
Data
# IP_FROM    IP_TO COUNTRY_CODE      CITY
# 1:        0 16777215            .         .
# 2: 16777216 16777471    Australia  Brisbane
# 3: 16777472 16778239        China    Fuzhou
# 4: 16778240 16778495    Australia Melbourne
# 5: 16778496 16779007    Australia    Sydney
# 6: 16779008  1677923    Australia Melbourne

然后我们有几种方法可以使用ip包找到data.table整数适合此表的位置。最简单的方法是将它们全部加在一起并过滤掉不正确的结果:

定义一个公共密钥列并加入两个表togeter,然后过滤掉不正确的结果。

Data[ , key_col := 1][ dt_ips[, key_col := 1], on="key_col", allow.cartesian = T][IP_FROM <= ip & ip < IP_TO]


#        CITY COUNTRY_CODE  IP_FROM    IP_TO key_col       ip
#1: Melbourne    Australia 16778240 16778495       1 16778245
#2:         .            .        0 16777215       1 16777213
#3:    Sydney    Australia 16778496 16779007       1 16778497

编辑 - 替代解决方案

您看到的错误表明'加入所有内容'方法的结果将无效,因为结果中的行太多。

正如错误消息所示,另一种方法是在I

中的每个J上执行联接
dt_ips[, key_col := 1]
DATA[, key_col := 1]

dt <- DATA[dt_ips, 
           {
             idx = IP_FROM <= ip & ip < IP_TO
             .(ip_string = i.ip,
               IP_FROM = IP_FROM[idx],
               IP_TO = IP_TO[idx],
               COUNTRY_CODE = COUNTRY_CODE[idx],
               CITY = CITY[idx])
           },
           on=c("key_col"),
           by=.EACHI]

编辑 - foverlap解决方案

或者,我们可以尝试foverlap解决方案

DATA <- DATA[, .(COUNTRY_CODE, CITY, IP_FROM, IP_TO)]

dt_ips[, `:=`(IP_FROM = ip, IP_TO = ip)]

setkey(DATA, IP_FROM, IP_TO)
setkey(dt_ips, IP_FROM, IP_TO)

foverlaps(dt_ips, 
          DATA)

修改 - 数据

OP正在使用此数据:

dput(head(dt_ips))
structure(list(ip_string = c("71.190.193.124", "70.130.142.86", 
"66.32.18.22", "87.155.51.131", "217.195.236.114", "162.195.53.38"
), ip = c(1203683708, 1203683708, 1203683708, 1203683708, 1203683708, 
1203683708)), .Names = c("ip_string", "ip"), class = c("data.table", 
"data.frame"), row.names = c(NA, -6L), .internal.selfref = <pointer: 0x00000000013f0788>)