熊猫:解决国家IP问题的最快方法

时间:2016-10-24 05:33:41

标签: python pandas dataframe geo

我有一个函数find_country_from_connection_ip,它接受​​一个ip,经过一些处理后返回一个国家。如下所示:

def find_country_from_connection_ip(ip):
    # Do some processing
    return county

我正在使用apply方法中的函数。如下所示:

df['Country'] = df.apply(lambda x: find_country_from_ip(x['IP']), axis=1)

由于它非常简单,我想要的是评估DataFrame中具有>400000行的现有列的​​新列。

它运行,但速度非常慢,并抛出如下所示的异常:

  

...........: SettingWithCopyWarning:   尝试在DataFrame的切片副本上设置值。   尝试使用.loc [row_indexer,col_indexer] = value而不是

     

请参阅文档中的警告:http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy

     

如果名称 ==' 主要':   在[38]中:

我理解这个问题,但无法弄明白如何将locapplylambda一起使用。

N.B。请建议您是否有更有效的替代解决方案,这可以带来最终结果。

****编辑********

该函数主要是对mmdb数据库的查找,如下所示:

def find_country_from_ip(ip):
    result = subprocess.Popen("mmdblookup --file GeoIP2-Country.mmdb --ip {} country names en".format(ip).split(" "), stdout=subprocess.PIPE).stdout.read()
    if result:
        return re.search(r'\"(.+?)\"', result).group(1) 
    else:
        final_output = subprocess.Popen("mmdblookup --file GeoIP2-Country.mmdb --ip {} registered_country names en".format(ip).split(" "), stdout=subprocess.PIPE).stdout.read()
        return re.search(r'\"(.+?)\"', final_output).group(1)

但这是一项代价高昂的操作,当你有一个>400000行的DataFrame时,它需要一些时间。但是多少钱?就是那个问题。我认为这需要大约2个小时。

4 个答案:

答案 0 :(得分:3)

我会使用maxminddb-geolite2(GeoLite)模块。

首先安装maxminddb-geolite2模块

pip install maxminddb-geolite2

Python代码:

import pandas as pd
from geolite2 import geolite2

def get_country(ip):
    try:
        x = geo.get(ip)
    except ValueError:
        return pd.np.nan
    try:
        return x['country']['names']['en'] if x else pd.np.nan
    except KeyError:
        return pd.np.nan

geo = geolite2.reader()

# it took me quite some time to find a free and large enough list of IPs ;)
# IP's for testing: http://upd.emule-security.org/ipfilter.zip
x = pd.read_csv(r'D:\download\ipfilter.zip',
                usecols=[0], sep='\s*\-\s*',
                header=None, names=['ip'])

# get unique IPs
unique_ips = x['ip'].unique()
# make series out of it
unique_ips = pd.Series(unique_ips, index = unique_ips)
# map IP --> country
x['country'] = x['ip'].map(unique_ips.apply(get_country))

geolite2.close()

<强>输出:

In [90]: x
Out[90]:
                     ip     country
0       000.000.000.000         NaN
1       001.002.004.000         NaN
2       001.002.008.000         NaN
3       001.009.096.105         NaN
4       001.009.102.251         NaN
5       001.009.106.186         NaN
6       001.016.000.000         NaN
7       001.055.241.140         NaN
8       001.093.021.147         NaN
9       001.179.136.040         NaN
10      001.179.138.224    Thailand
11      001.179.140.200    Thailand
12      001.179.146.052         NaN
13      001.179.147.002    Thailand
14      001.179.153.216    Thailand
15      001.179.164.124    Thailand
16      001.179.167.188    Thailand
17      001.186.188.000         NaN
18      001.202.096.052         NaN
19      001.204.179.141       China
20      002.051.000.165         NaN
21      002.056.000.000         NaN
22      002.095.041.202         NaN
23      002.135.237.106  Kazakhstan
24      002.135.237.250  Kazakhstan
...                 ...         ...

时间:适用于171.884个唯一IP:

In [85]: %timeit unique_ips.apply(get_country)
1 loop, best of 3: 14.8 s per loop

In [86]: unique_ips.shape
Out[86]: (171884,)

结论:需要约。在我的硬件上使用400K唯一IP的DF为35秒:

In [93]: 400000/171884*15
Out[93]: 34.90726303786274

答案 1 :(得分:1)

IIUC您可以通过Series.apply这种方式使用自定义功能:

df['Country'] = df['IP'].apply(find_country_from_ip)

样品:

df = pd.DataFrame({'IP':[1,2,3],
                   'B':[4,5,6]})


def find_country_from_ip(ip):
    # Do some processing 
    # some testing formula
    county = ip + 5
    return county

df['Country'] = df['IP'].apply(find_country_from_ip)

print (df)
   B  IP  Country
0  4   1        6
1  5   2        7
2  6   3        8

答案 2 :(得分:1)

您的问题不在于如何使用applyloc。问题是您的df被标记为另一个数据框的副本。

让我们稍微探讨一下

df = pd.DataFrame(dict(IP=[1, 2, 3], A=list('xyz')))
df

Wordpress Types Toolset

def find_country_from_connection_ip(ip):
    return {1: 'A', 2: 'B', 3: 'C'}[ip]

df['Country'] = df.IP.apply(find_country_from_connection_ip)
df

enter image description here

没问题
让我们提出一些问题

# This should make a copy
print(bool(df.is_copy))
df = df[['A', 'IP']]
print(df)
print(bool(df.is_copy))

False
   A  IP
0  x   1
1  y   2
2  z   3
True

完美,现在我们有了副本。让我们使用apply

执行相同的作业
df['Country'] = df.IP.apply(find_country_from_connection_ip)
df
//anaconda/envs/3.5/lib/python3.5/site-packages/ipykernel/__main__.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  if __name__ == '__main__':

enter image description here

你如何解决?
您创建df的位置可以使用df.loc。我上面的例子,我df = df[:]触发了副本。如果我使用loc代替,我会避免这种混乱。

print(bool(df.is_copy))
df = df.loc[:]
print(df)
print(bool(df.is_copy))

False
   A  IP
0  x   1
1  y   2
2  z   3
False

您需要查找创建df的位置,并在切片源数据框时使用lociloc。或者,你可以简单地做到这一点......

df.is_copy = None

完整演示

df = pd.DataFrame(dict(IP=[1, 2, 3], A=list('xyz')))

def find_country_from_connection_ip(ip):
    return {1: 'A', 2: 'B', 3: 'C'}[ip]

df = df[:]

df.is_copy = None

df['Country'] = df.IP.apply(find_country_from_connection_ip)
df

enter image description here

答案 3 :(得分:0)

首先,@ MaxU的答案是有效的方法,是在矢量化的pd.series / dataframe上并行应用的理想选择。

在提供 IP地址信息的情况下,两个流行的库的对比性能将返回位置数据。 TLDR:使用 geolite2 方法。

1。geolite2库中的geolite2软件包

输入

# !pip install maxminddb-geolite2
import time
from geolite2 import geolite2
geo = geolite2.reader()
df_1 = train_data.loc[:50,['IP_Address']]

def IP_info_1(ip):
    try:
        x = geo.get(ip)
    except ValueError:   #Faulty IP value
        return np.nan
    try:
        return x['country']['names']['en'] if x is not None else np.nan
    except KeyError:   #Faulty Key value
        return np.nan


s_time = time.time()
# map IP --> country
#apply(fn) applies fn. on all pd.series elements
df_1['country'] = df_1.loc[:,'IP_Address'].apply(IP_info_1)
print(df_1.head(), '\n')
print('Time:',str(time.time()-s_time)+'s \n')

print(type(geo.get('48.151.136.76')))

输出

       IP_Address         country
0   48.151.136.76   United States
1    94.9.145.169  United Kingdom
2   58.94.157.121           Japan
3  193.187.41.186         Austria
4   125.96.20.172           China 

Time: 0.09906983375549316s 

<class 'dict'>

2。DbIpCity库中的ip2geotools软件包

输入

# !pip install ip2geotools
import time
s_time = time.time()
from ip2geotools.databases.noncommercial import DbIpCity
df_2 = train_data.loc[:50,['IP_Address']]
def IP_info_2(ip):
    try:
        return DbIpCity.get(ip, api_key = 'free').country
    except:
        return np.nan
df_2['country'] = df_2.loc[:, 'IP_Address'].apply(IP_info_2)
print(df_2.head())
print('Time:',str(time.time()-s_time)+'s')

print(type(DbIpCity.get('48.151.136.76',api_key = 'free')))

输出

       IP_Address country
0   48.151.136.76      US
1    94.9.145.169      GB
2   58.94.157.121      JP
3  193.187.41.186      AT
4   125.96.20.172      CN

Time: 80.53318452835083s 

<class 'ip2geotools.models.IpLocation'>

之所以会造成巨大的时间差异,原因可能是输出的数据结构,即 ie dictionaries 直接子集化,似乎比从专用< em> ip2geotools.models.IpLocation 对象。

此外,第一种方法的输出是包含地理位置数据的字典,该数据分别是子集以获得所需的信息:

x = geolite2.reader().get('48.151.136.76')
print(x)

>>>
    {'city': {'geoname_id': 5101798, 'names': {'de': 'Newark', 'en': 'Newark', 'es': 'Newark', 'fr': 'Newark', 'ja': 'ニューアーク', 'pt-BR': 'Newark', 'ru': 'Ньюарк'}},

 'continent': {'code': 'NA', 'geoname_id': 6255149, 'names': {'de': 'Nordamerika', 'en': 'North America', 'es': 'Norteamérica', 'fr': 'Amérique du Nord', 'ja': '北アメリカ', 'pt-BR': 'América do Norte', 'ru': 'Северная Америка', 'zh-CN': '北美洲'}}, 

'country': {'geoname_id': 6252001, 'iso_code': 'US', 'names': {'de': 'USA', 'en': 'United States', 'es': 'Estados Unidos', 'fr': 'États-Unis', 'ja': 'アメリカ合衆国', 'pt-BR': 'Estados Unidos', 'ru': 'США', 'zh-CN': '美国'}}, 

'location': {'accuracy_radius': 1000, 'latitude': 40.7355, 'longitude': -74.1741, 'metro_code': 501, 'time_zone': 'America/New_York'}, 

'postal': {'code': '07102'}, 

'registered_country': {'geoname_id': 6252001, 'iso_code': 'US', 'names': {'de': 'USA', 'en': 'United States', 'es': 'Estados Unidos', 'fr': 'États-Unis', 'ja': 'アメリカ合衆国', 'pt-BR': 'Estados Unidos', 'ru': 'США', 'zh-CN': '美国'}}, 

'subdivisions': [{'geoname_id': 5101760, 'iso_code': 'NJ', 'names': {'en': 'New Jersey', 'es': 'Nueva Jersey', 'fr': 'New Jersey', 'ja': 'ニュージャージー州', 'pt-BR': 'Nova Jérsia', 'ru': 'Нью-Джерси', 'zh-CN': '新泽西州'}}]}