通过IP地址范围过滤Pandas DataFrame
我想根据IP地址的范围来筛选一个pandas的Dataframe
。有没有办法不使用正则表达式来做到这一点?
Ex. From 61.245.160.0 To 61.245.175.255
3 个回答
2
假设你有一个这样的数据框(DF):
In [48]: df
Out[48]:
ip
0 61.245.160.1
1 61.245.160.100
2 61.245.160.200
3 61.245.160.254
现在我们来找找所有在 61.245.160.99
和 61.245.160.254
之间(不包括这两个IP)的IP地址:
In [49]: ip_from = '61.245.160.99'
In [50]: ip_to = '61.245.160.254'
如果我们把IP地址当作字符串来比较,它会按照字母顺序来比较,这样就不太好用了,正如@adele指出的那样:
In [51]: df.query("'61.245.160.99' < ip < '61.245.160.254'")
Out[51]:
Empty DataFrame
Columns: [ip]
Index: []
In [52]: df.query('@ip_from < ip < @ip_to')
Out[52]:
Empty DataFrame
Columns: [ip]
Index: []
我们可以使用数字化的IP表示法:
In [53]: df[df.ip.apply(lambda x: int(IPAddress(x)))
....: .to_frame('ip')
....: .eval('{} < ip < {}'.format(int(IPAddress(ip_from)),
....: int(IPAddress(ip_to)))
....: )
....: ]
Out[53]:
ip
1 61.245.160.100
2 61.245.160.200
解释:
In [66]: df.ip.apply(lambda x: int(IPAddress(x)))
Out[66]:
0 1039507457
1 1039507556
2 1039507656
3 1039507710
Name: ip, dtype: int64
In [67]: df.ip.apply(lambda x: int(IPAddress(x))).to_frame('ip')
Out[67]:
ip
0 1039507457
1 1039507556
2 1039507656
3 1039507710
In [68]: (df.ip.apply(lambda x: int(IPAddress(x)))
....: .to_frame('ip')
....: .eval('{} < ip < {}'.format(int(IPAddress(ip_from)),
....: int(IPAddress(ip_to))))
....: )
Out[68]:
0 False
1 True
2 True
3 False
dtype: bool
顺便说一下,这里有一个更快(向量化)的函数,可以返回数字化的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)
演示:
In [78]: df['int_ip'] = ip_to_int(df.ip)
In [79]: df
Out[79]:
ip int_ip
0 61.245.160.1 1039507457
1 61.245.160.100 1039507556
2 61.245.160.200 1039507656
3 61.245.160.254 1039507710
检查:
In [80]: (df.ip.apply(lambda x: int(IPAddress(x))) == ip_to_int(df.ip)).all()
Out[80]: True
4
我有一个方法是使用 ipaddress 这个库。
比如说,我想知道 host0 = 10.2.23.5
这个地址是否属于以下这些网络 NETS = ['10.2.48.0/25','10.2.23.0/25','10.2.154.0/24']
。
>>> host0 = ip.IPv4Address('10.2.23.5')
>>> NETS = ['10.2.48.0/25','10.2.23.0/25','10.2.154.0/24']
>>> nets = [ip.IPv4Network(x) for x in NETS]
>>> [x for x in nets if (host2 >= x.network_address and host2 <= x.broadcast_address)]
[IPv4Network('10.2.23.0/25')]
现在,为了把这个方法和 Pandas 结合起来,你需要做以下几步:先创建一个函数,然后把这个函数应用到数据框(DF)的每一行。
def fnc(row):
host = ip.IPv4Address(row)
vec = [x for x in netsPy if (host >= x.network_address and host <= x.broadcast_address)]
if len(vec) == 0:
return '1'
else:
return '-1'
之后,你再把这个函数应用到数据框上。
df['newCol'] = df['IP'].apply(fnc)
这样会生成一个新列 newCol
,在这个新列中,每一行的值要么是 1
,要么是 -1
,这取决于这个 IP 地址是否属于你感兴趣的网络。
5
在Python中,字符串是可以排序的,所以你可以直接这样做:
In [11]: '61.245.160.0' < '61.245.175.255'
Out[11]: True
你可以使用布尔掩码:
In [12]: df[('61.245.160.0' < df.ip) & (df.ip < '61.245.175.255')]
或者如果ip是索引的话,你可以使用切片:
In [13]: df.loc['61.245.160.0':'61.245.175.255']