2016-08-28 2 views
3

У меня есть два фрейма данных, содержащие некоторую информацию ip, которую я хотел бы объединить эквивалентно левому соединению в sql. В dataframes имеют следующие поля:Pandas Merge, если значение находится между двумя полями

df1: ["company","ip","actions"] 
df2: ["ip_range_start","ip_range_end","country","state","city"] 

В результате dataframe должны иметь заголовки: ["company","ip","actions","country","state","city"]. Проблема здесь в моих критериях слияния. df1 содержит один ip, который я хотел бы использовать, чтобы вытащить информацию о стране, штате и городе из df2.

Этот единственный IP будет падение в одном из диапазонов, указанных df2 в "ip_range_start" и "ip_range_end" полей. Я не уверен, как это сделать, поскольку нормальное слияние/объединение, очевидно, не будет делать трюк, поскольку между df1 и df2 нет соответствующих значений.

Мой вопрос кажется очень похож на этот, но достаточно различен, чтобы оправдать отдельный вопрос: Pandas: how to merge two dataframes on offset dates?

+2

Не забудьте поделиться некоторыми данными? – Abdou

+0

Вы можете разделить иерархические номера IP на отдельные поля. Это может упростить решение. –

ответ

1

предположит, что вы следующие кадры данных:

In [5]: df1 
Out[5]: 
    company   ip actions 
0 comp1 10.10.1.2 act1 
1 comp2 10.10.2.20 act2 
2 comp3 10.10.3.50 act3 
3 comp4 10.10.4.100 act4 

In [6]: df2 
Out[6]: 
    ip_range_start ip_range_end country state city 
0  10.10.2.1 10.10.2.254 country2 state2 city2 
1  10.10.3.1 10.10.3.254 country3 state3 city3 
2  10.10.4.1 10.10.4.254 country4 state4 city4 

мы можем создать векторизованную функцию, которая будет вычислить численное IP представление, аналогичное int(netaddr.IPAddress('192.0.2.1')):

def ip_to_int(ip_ser): 
    ips = ip_ser.str.split('.', expand=True).astype(np.int16).values 
    mults = np.tile(np.array([24, 16, 8, 0]), len(ip_ser)).reshape(ips.shape) 
    return np.sum(np.left_shift(ips, mults), axis=1) 

давайте преобразуем все IP-адреса в их numerica л представление:

df1['_ip'] = ip_to_int(df1.ip) 
df2[['_ip_range_start','_ip_range_end']] = df2.filter(like='ip_range').apply(lambda x: ip_to_int(x)) 

In [10]: df1 
Out[10]: 
    company   ip actions  _ip 
0 comp1 10.10.1.2 act1 168427778 
1 comp2 10.10.2.20 act2 168428052 
2 comp3 10.10.3.50 act3 168428338 
3 comp4 10.10.4.100 act4 168428644 

In [11]: df2 
Out[11]: 
    ip_range_start ip_range_end country state city _ip_range_start _ip_range_end 
0  10.10.2.1 10.10.2.254 country2 state2 city2  168428033  168428286 
1  10.10.3.1 10.10.3.254 country3 state3 city3  168428289  168428542 
2  10.10.4.1 10.10.4.254 country4 state4 city4  168428545  168428798 

Теперь давайте добавим новый столбец в df1 DF, который будет содержать индекс первого соответствия IP интервала от df2 DF:

In [12]: df1['x'] = (df1._ip.apply(lambda x: df2.query('_ip_range_start <= @x <= _ip_range_end') 
    ....:          .index 
    ....:          .values) 
    ....:     .apply(lambda x: x[0] if len(x) else -1)) 

In [14]: df1 
Out[14]: 
    company   ip actions  _ip x 
0 comp1 10.10.1.2 act1 168427778 -1 
1 comp2 10.10.2.20 act2 168428052 0 
2 comp3 10.10.3.50 act3 168428338 1 
3 comp4 10.10.4.100 act4 168428644 2 

, наконец, мы можем объединить оба DF:

In [15]: (pd.merge(df1.drop('_ip',1), 
    ....:   df2.filter(regex=r'^((?!.?ip_range_).*)$'), 
    ....:   left_on='x', 
    ....:   right_index=True, 
    ....:   how='left') 
    ....: .drop('x',1) 
    ....:) 
Out[15]: 
    company   ip actions country state city 
0 comp1 10.10.1.2 act1  NaN  NaN NaN 
1 comp2 10.10.2.20 act2 country2 state2 city2 
2 comp3 10.10.3.50 act3 country3 state3 city3 
3 comp4 10.10.4.100 act4 country4 state4 city4 

Давайте сравним скорость стандартного ввода (IPAddress) с нашей функцией (мы будем используйте 4M строк DF для сравнения):

In [21]: big = pd.concat([df1.ip] * 10**6, ignore_index=True) 

In [22]: big.shape 
Out[22]: (4000000,) 

In [23]: big.head(10) 
Out[23]: 
0  10.10.1.2 
1  10.10.2.20 
2  10.10.3.50 
3 10.10.4.100 
4  10.10.1.2 
5  10.10.2.20 
6  10.10.3.50 
7 10.10.4.100 
8  10.10.1.2 
9  10.10.2.20 
Name: ip, dtype: object 

In [24]: %timeit 
%timeit %%timeit 

In [24]: %timeit big.apply(lambda x: int(IPAddress(x))) 
1 loop, best of 3: 1min 3s per loop 

In [25]: %timeit ip_to_int(big) 
1 loop, best of 3: 25.4 s per loop 

Заключение: наша функция составляет ок. В 2,5 раза быстрее

Смежные вопросы