我有一个函数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]中:
我理解这个问题,但无法弄明白如何将loc
与apply
和lambda
一起使用。
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个小时。
答案 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)
您的问题不在于如何使用apply
或loc
。问题是您的df
被标记为另一个数据框的副本。
让我们稍微探讨一下
df = pd.DataFrame(dict(IP=[1, 2, 3], A=list('xyz')))
df
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
没问题
让我们提出一些问题
# 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__':
你如何解决?
您创建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
的位置,并在切片源数据框时使用loc
或iloc
。或者,你可以简单地做到这一点......
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
答案 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': '新泽西州'}}]}