Pandas按范围合并ip-address上的数据帧

sas*_*rup 5 merge ip-address left-join python-3.x pandas

我有两个数据帧包含我想要合并的一些ip信息(相当于sql中的左连接).数据框具有以下字段:

df1: ["company","ip","actions"]  
df2: ["ip_range_start","ip_range_end","country","state","city"]
Run Code Online (Sandbox Code Playgroud)

结果数据框应该有标题:["company","ip","actions","country","state","city"].这里的问题是我的合并标准.df1包含一个ip,我想用来从df2中提取国家,州和城市信息.

此单个IP 落入df2 "ip_range_start""ip_range_end"字段指定的范围之一.我不知道如何实现这一点,因为正常的合并/连接显然不会起作用,因为df1和df2之间没有匹配的值.

我的问题看起来与这个问题非常相似,但又不同以保证一个单独的问题:熊猫:如何在偏移日期合并两个数据帧?

Max*_*axU 4

假设您有以下数据框:

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
Run Code Online (Sandbox Code Playgroud)

我们可以创建一个向量化函数,它将计算类似于int(netaddr.IPAddress('192.0.2.1')​​) 的数字 IP 表示:

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)
Run Code Online (Sandbox Code Playgroud)

让我们将所有 IP 转换为其数字表示形式:

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
Run Code Online (Sandbox Code Playgroud)

现在让我们向df1DF 添加一个新列,其中包含 DF 中第一个 匹配的IP 间隔的索引df2

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
Run Code Online (Sandbox Code Playgroud)

最后我们可以合并两个 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
Run Code Online (Sandbox Code Playgroud)

让我们将标准 int(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
Run Code Online (Sandbox Code Playgroud)

结论:我们的函数大约是。快 2.5 倍