我有两个数据帧,其中包含我想要合并的一些ip信息(相当于sql中的左连接)。数据框具有以下字段:
df1: ["company","ip","actions"]
df2: ["ip_range_start","ip_range_end","country","state","city"]
结果数据框应包含标题:["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?
答案 0 :(得分:3)
假设您有以下数据框:
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
我们可以创建一个矢量化函数,它将计算类似于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)
让我们将所有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
现在让我们为df1
DF添加一个新列,其中包含来自df2
DF的第一个匹配 IP间隔的索引:< / p>
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
让我们将标准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
结论:我们的功能是约。快2.5倍
答案 1 :(得分:0)
如果您愿意使用R而不是Python,我已经写了一个ipaddress软件包可以解决此问题。
使用来自MaxU答案的相同数据:
library(tidyverse)
library(ipaddress)
library(fuzzyjoin)
addr <- tibble(
company = c("comp1", "comp2", "comp3", "comp4"),
ip = ip_address(c("10.10.1.2", "10.10.2.20", "10.10.3.50", "10.10.4.100")),
actions = c("act1", "act2", "act3", "act4")
)
nets <- tibble(
ip_range_start = ip_address(c("10.10.2.1", "10.10.3.1", "10.10.4.1")),
ip_range_end = ip_address(c("10.10.2.254", "10.10.3.254", "10.10.4.254")),
country = c("country2", "country3", "country4"),
state = c("state2", "state3", "state4"),
city = c("city2", "city3", "city4")
)
nets <- nets %>%
mutate(network = common_network(ip_range_start, ip_range_end)) %>%
select(network, country, state, city)
fuzzy_left_join(addr, nets, c("ip" = "network"), is_within)
#> # A tibble: 4 x 7
#> company ip actions network country state city
#> <chr> <ip_addr> <chr> <ip_netwk> <chr> <chr> <chr>
#> 1 comp1 10.10.1.2 act1 NA <NA> <NA> <NA>
#> 2 comp2 10.10.2.20 act2 10.10.2.0/24 country2 state2 city2
#> 3 comp3 10.10.3.50 act3 10.10.3.0/24 country3 state3 city3
#> 4 comp4 10.10.4.100 act4 10.10.4.0/24 country4 state4 city4
使用相同的400万个地址基准,网络成员身份检查将在3.25秒内完成。
big <- tibble(ip = rep(addr$ip, 1e6))
big
#> # A tibble: 4,000,000 x 1
#> ip
#> <ip_addr>
#> 1 10.10.1.2
#> 2 10.10.2.20
#> 3 10.10.3.50
#> 4 10.10.4.100
#> 5 10.10.1.2
#> 6 10.10.2.20
#> 7 10.10.3.50
#> 8 10.10.4.100
#> 9 10.10.1.2
#> 10 10.10.2.20
#> # … with 3,999,990 more rows
bench::mark(fuzzy_left_join(big, nets, c("ip" = "network"), is_within))$median
#> [1] 3.25s