[英]Way to merge dataframe based on several ranges
这个问题与这个问题非常相似: Fastest way to merge pandas dataframe on ranges
但在我的加入过程中,我有几个需要考虑的范围。
我有一个 dataframe A:
ip_address server_port
0 13 2
1 5 2
2 20 4
3 11 4
.. ........
和一个 dataframe B:
lowerbound_ip_address upperbound_ip_address server_low server_high country
0 0 10 2 3 Australia
1 11 20 2 3 China
2 11 20 4 7 Belgium
我如何合并它以遵守规则:
ip_address server_port country
0 13 2 China
1 5 2 Australia
2 20 4 Belgium
3 11 4 Belgium
我最初的想法是为它写一个循环,但是有没有向量化的解决方案呢?
使用numpy广播:
ip_low = df_b['lowerbound_ip_address'].values
ip_high = df_b['upperbound_ip_address'].values
port_low = df_b['server_low'].values
port_high = df_b['server_high'].values
ip = df_a['ip_address'].values[:, None]
port = df_a['server_port'].values[:, None]
mask = (ip_low <= ip) & (ip <= ip_high) & (port_low <= port) & (port <= port_high)
id_b = np.argmax(mask, axis=1)
df_a.assign(id_b=id_b).join(df_b, on='id_b')
结果:
ip_address server_port id_b lowerbound_ip_address upperbound_ip_address server_low server_high country
0 13 2 1 11 20 2 3 China
1 5 2 0 0 10 2 3 Australia
2 20 4 2 11 20 4 7 Belgium
3 11 4 2 11 20 4 7 Belgium
它使用数组广播寻找到每行df_a
在匹配df_b
。 结果存储在mask
,如下所示:
array([[False, True, False], # line 0 in df_a matches to line 1 in df_b
[ True, False, False], # 1 0
[False, False, True], # 2 2
[False, False, True]]) # 3 2
numpy将True/False
视为1/0
因此您可以对其进行比较。 对于每一行,我们想找到第一个True
值的索引。 由于True == 1
> False == 0
,我们可以使用argmax
来做到这一点:
id_b = np.argmax(mask, axis=1)
array([1, 0, 2, 2], dtype=int64)
最后一行只是分配新列并将两个框架连接在一起。
受到在range上合并熊猫数据帧的最快方法的启发,使用pd.IntervalIndex
可以创建多个间隔(在这种情况下,两个间隔;一个用于ip_address
,一个用于server_port
):
ip_intv = pd.IntervalIndex.from_arrays(df_b.lowerbound_ip_address.unique(),
df_b.upperbound_ip_address.unique(),
'both')
server_intv = pd.IntervalIndex.from_arrays(df_b.server_low.unique(),
df_b.server_high.unique(),
'both')
然后使用pd.cut
您将在两个数据帧中找到相应的间隔:
df_a['ip_intv'] = pd.cut(df_a.ip_address, ip_intv)
df_a['server_intv'] = pd.cut(df_a.server_port, server_intv)
df_b['ip_intv'] = pd.cut(df_b.lowerbound_ip_address, ip_intv)
df_b['server_intv'] = pd.cut(df_b.server_low, server_intv)
df_a.set_index(['ip_intv', 'server_intv'], inplace=True)
df_b.set_index(['ip_intv', 'server_intv'], inplace=True)
最后,您将join
df_a.join(df_b.country)
Out:
ip_address server_port country
ip_intv server_intv
[0, 10] [2, 3] 5 2 Australia
[11, 20] [2, 3] 13 2 China
[4, 7] 20 4 Belgium
[4, 7] 11 4 Belgium
一种选择是使用pyjanitor中的conditional_join 。
# pip install pyjanitor
import pandas as pd
import janitor
(A
.conditional_join(
B,
('ip_address', 'lowerbound_ip_address', '>='),
('ip_address', 'upperbound_ip_address', '<='),
('server_port', 'server_low', '>='),
('server_port', 'server_high', '<='))
.loc[:, ['ip_address', 'server_port', 'country']]
)
ip_address server_port country
0 13 2 China
1 5 2 Australia
2 20 4 Belgium
3 11 4 Belgium
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.