2016-10-24 19 views
3

私はIPをとる関数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) 

それはとても簡単なので、私が欲しいのは>400000行を持つデータフレーム内の既存の列から新しい列を評価することです。

それが実行されますが、ひどく遅いと以下のような例外をスロー:

...........:SettingWithCopyWarning: 値はAのコピーに設定されるようにしようとしていますDataFrameからスライスします。 http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy

かの名前 == 'メイン':[38]で :

ドキュメントの注意事項を参照してください[row_indexer、col_indexer] =値の代わりに

の.locを使用してみてください

私はこの問題を理解していますが、locapplylambdaをどのように使用するかを理解できません。

N.B.より効果的な代替ソリューションがあれば、最終結果をもたらす可能性があることをお勧めします。

**** EDIT

********機能は主に以下のような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

私はより効率的なソリューションは、 '省略apply'、可能であれば機能をベクトル化、いくつかのパンダにカスタム関数を書き換えることができると思います。 – jezrael

+0

これで、すべての関数 'find_country_from_ip'に質問することができますか? – jezrael

+0

@jezrael、編集中。あなたは今見ているかもしれません。 –

答えて

2

を行うことができます私はそのため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アドレスを持つあなたのための35秒DFは:

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

偉大なもの...まさに私が探している解決策。 –

+0

@AhsanulHaque、こんにちは私は助けることができる:) – MaxU

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 
1

あなたの問題はapplyまたはlocを使用する方法ではありません。問題は、dfに別のデータフレームのコピーとしてフラグが立てられていることです。

のは、これは少し

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

enter image description here

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が作成された場所を見つけて、あなたがソースデータフレームをスライスするときの代わり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 

enter image description here

+0

ありがとう、うまく説明しました。しかし、 'd2 = d1.loc [d1 ['Country'] == '$$']'を使った後、 'bool(d2.is_copy)'は 'True'に評価されます。そして、100行には約5秒かかり、これは400000行が完了するのに約6時間かかることを意味する。かなり長い時間ですね。 –

+0

これを高速化するには、IP変換コードを見て、何らかの方法でベクター化できるかどうかを確認するしかありません。 – piRSquared

関連する問題