我有2个数据帧:一个(A
)带有一些正则表达式形式的白名单主机名(即(.*)microsoft.com
,(*.)go.microsoft.com
...)和另一个(B
)带有网站的实际完整主机名。我想使用白名单(第一个)数据框的正则表达式文本向此第二个数据框添加新列。但是,看来Pandas的.replace()
方法并不关心其to_replace
和value
参数所处的订单项。
我的数据如下:
In [1] A
Out[1]:
wildcards \
42 (.*)activation.playready.microsoft.com
35 (.*)v10.vortex-win.data.microsoft.com
40 (.*)settings-win.data.microsoft.com
43 (.*)smartscreen.microsoft.com
39 (.*).playready.microsoft.com
38 (.*)go.microsoft.com
240 (.*)i.microsoft.com
238 (.*)microsoft.com
regex
42 re.compile('^(.*)activation.playready.microsof...
35 re.compile('^(.*)v10.vortex-win.data.microsoft...
40 re.compile('^(.*)settings-win.data.microsoft.c...
43 re.compile('^(.*)smartscreen.microsoft.com$')
39 re.compile('^(.*).playready.microsoft.com$')
38 re.compile('^(.*)go.microsoft.com$')
240 re.compile('^(.*)i.microsoft.com$')
238 re.compile('^(.*)microsoft.com$')
In [2] B.head()
Out[2]:
server_hostname
146 mobile.pipe.aria.microsoft.com
205 settings-win.data.microsoft.com
341 nav.smartscreen.microsoft.com
406 v10.vortex-win.data.microsoft.com
667 www.microsoft.com
请注意,A
有一列与wildcards
相似的形式的正则表达式。我想像这样向wildcard
添加一个B
列:
B.loc[:,'wildcards'] = B['server_hostname'].replace(A['regex'].tolist(), A['wildcards'].tolist())
但是问题是,B
的所有通配符值都变成(.*)microsoft.com
。无论A
的通配符值的顺序如何,都会发生这种情况。看来.replace()
的目的是首先以最短的值而不是所提供的顺序使用to_replace
正则表达式。
如何提供to_replace
值的列表,以便最终获得与wildcards
的{{1}}值关联的最详细的主机名B
值?
答案 0 :(得分:1)
大多数答案使用apply()
,它比内置矢量函数解决方案要慢。我希望使用.replace()
是因为它是内置向量函数,因此速度很快。 @vlemaistre的答案是唯一不使用.apply()
的答案,就像我在这里的解决方案一样,它不将每个通配符编译成一个正则表达式,而是将其视为使用逻辑的右侧子字符串:“如果{{1} }以server_hostname
结尾,那就是匹配项”。只要我按长度对通配符进行排序,就可以正常工作。
我执行此操作的功能是:
wildcard
在这里,def match_to_whitelist(accepts_df, whitelist_df):
""" Adds `whitelists` column to accepts_df showing which (if any) whitelist entry it matches with """
accepts_df.loc[:, 'wildcards'] = None
for wildcard in whitelist_df['wildcards']:
accepts_df.loc[(accepts_df['wildcards'].isnull()) & (
accepts_df['server_hostname'].str.endswith(wildcard)), 'wildcards'] = wildcard
rows_matched = len(accepts_df['wildcards'].notnull())
matched {rows_matched}")
return accepts_df
就像以前的accepts_df
,B
就像以前的whitelist_df
,但有2个区别:
A
列regex
值不再采用glob / regex格式(即,“(。*)microsoft.com”变为“ microsoft.com” 要在我的机器上建立基准测试基准,我将使用我的基准作为基准,花费27秒的时间来处理100k wildcards
行和400 accepts_df
行。使用相同的数据集,下面是其他解决方案的时间(我很懒:如果它们没有用尽,我就不会花很多时间来找出答案):
whitelist_df
进行排序将是最佳比赛):超过6分钟,所以退出... A
:未测试,因为我无法(快速)以编程方式构建较长的RE。 lastgroup
:错误:“ pandas.core.indexes.base.InvalidIndexError:仅对具有唯一值的Index对象有效的索引” 最终,我们没有一个答案说明如何根据需要使用Series.str.match()
,所以暂时,如果有人可以提供一个更好地使用{{1 },或至少其他一些基于向量的快速解决方案。在此之前,我将保持现有状态,或者在验证结果后使用aws_apprentice的状态。
编辑
我通过向两个DF添加一个“域”列来改进匹配器,该域由每个通配符/服务器主机名的后2个部分组成(即www.microsoft.com变为“ microsoft.com”)。然后,我在两个DF上都使用.replace()
,遍历域的白名单组,从server_hostname DF(B)提取了相同的域组,并且仅使用每个组的通配符/ server_hostnames的子集进行匹配。这将我的处理时间减少了一半。
答案 1 :(得分:0)
这是一种使用双列表理解和re.sub()
函数的方法:
import re
A = pd.DataFrame({'wildcards' : ['(.*)activation.playready.microsoft.com',
'(.*)v10.vortex-win.data.microsoft.com',
'(.*)i.microsoft.com', '(.*)microsoft.com'],
'regex' : [re.compile('^(.*)activation.playready.microsoft.com$'),
re.compile('^(.*)v10.vortex-win.data.microsoft.com$'),
re.compile('^(.*)i.microsoft.com$'),
re.compile('^(.*)microsoft.com$')]})
B = pd.DataFrame({'server_hostname' : ['v10.vortex-win.data.microsoft.com',
'www.microsoft.com']})
# For each server_hostname we try each regex and keep the longest matching one
B['wildcards'] = [max([re.sub(to_replace, value, x) for to_replace, value
in A[['regex', 'wildcards']].values
if re.sub(to_replace, value, x)!=x], key=len)
for x in B['server_hostname']]
Output :
server_hostname wildcards
0 v10.vortex-win.data.microsoft.com (.*)v10.vortex-win.data.microsoft.com
1 www.microsoft.com (.*)microsoft.com
答案 2 :(得分:0)
一种替代方法是使用SequenceMatcher和re.match。
从@vlemaistre给出的答案中获取数据
from difflib import SequenceMatcher
import pandas as pd
import re
A = pd.DataFrame({'wildcards' : ['(.*)activation.playready.microsoft.com',
'(.*)v10.vortex-win.data.microsoft.com',
'(.*)i.microsoft.com', '(.*)microsoft.com'],
'regex' : [re.compile('^(.*)activation.playready.microsoft.com$'),
re.compile('^(.*)v10.vortex-win.data.microsoft.com$'),
re.compile('^(.*)i.microsoft.com$'),
re.compile('^(.*)microsoft.com$')]})
B = pd.DataFrame({'server_hostname' : ['v10.vortex-win.data.microsoft.com',
'www.microsoft.com', 'www.i.microsoft.com']})
def regex_match(x):
match = None
ratio = 0
for w, r in A[['wildcards', 'regex']].to_numpy():
if re.match(r, x) is not None:
pct = SequenceMatcher(None, w, x).ratio()
if ratio < pct: ratio = pct; match = w
return match
B['wildcards'] = B.server_hostname.apply(regex_match)
# print(B.wildcards)
0 (.*)v10.vortex-win.data.microsoft.com
1 (.*)microsoft.com
2 (.*)i.microsoft.com
Name: server_hostname, dtype: object
答案 3 :(得分:0)
这是使用apply
的另一种方法。据我所知,没有 pure pandas
方法可以做到这一点。我还借用了@vlemaistre提供的数据。
A = pd.DataFrame({'wildcards' : ['(.*)activation.playready.microsoft.com',
'(.*)v10.vortex-win.data.microsoft.com',
'(.*)i.microsoft.com', '(.*)microsoft.com'],
'regex' : [re.compile('^(.*)activation.playready.microsoft.com$'),
re.compile('^(.*)v10.vortex-win.data.microsoft.com$'),
re.compile('^(.*)i.microsoft.com$'),
re.compile('^(.*)microsoft.com$')]})
B = pd.DataFrame({'server_hostname' : ['v10.vortex-win.data.microsoft.com',
'www.microsoft.com']})
pats = set(A.regex)
def max_match(hostname):
d = {}
for pat in pats:
maybe_result = pat.search(hostname)
if maybe_result:
p = pat.pattern
d[len(p)] = p
return d.get(max([*d]))
B['wildcards'] = B['server_hostname'].apply(max_match)
server_hostname wildcards
0 v10.vortex-win.data.microsoft.com ^(.*)v10.vortex-win.data.microsoft.com$
1 www.microsoft.com ^(.*)microsoft.com$
答案 4 :(得分:0)
pandas documentation将.replace()
方法描述为:
DataFrame的值动态地替换为其他值。这与使用.loc或.iloc进行更新不同,后者需要您指定要使用某些值进行更新的位置。
这意味着该方法将遍历数据帧中的所有单元格,并为to_replace
参数中提供的每个查询替换它所能执行的操作。一个简单的例子来证明这一点:
df = pd.DataFrame({'A':['a','c'],'B':['b','d']})
df.replace(['a','b'],['b','c'])
Output:
A B
0 c c
1 c d
在您的示例中,当有新的匹配项时,每个正则表达式规则都会覆盖以前的替换项,这就是您最终得到(.*)microsoft.com
结果向量的原因。
您可以改用.apply()
方法。例如,通过按长度降序对白名单(A)进行排序,可以遍历值DataFrame(B)的每一行并返回每个第一个匹配项:
import pandas as pd
import re
# Using the definitions for A and B from your question,
# where A is sorted descending by length.
def first_match(x):
for index, row in A.iterrows():
if bool(re.search(row['wildcards'], x['server_hostname'])) is True:
return row['wildcards']
B['wildcards'] = B.apply(first_match, axis=1)
B
Output:
server_hostname wildcards
0 mobile.pipe.aria.microsoft.com (.*)microsoft.com
1 settings-win.data.microsoft.com (.*)settings-win.data.microsoft.com
2 nav.smartscreen.microsoft.com (.*)smartscreen.microsoft.com
3 v10.vortex-win.data.microsoft.com (.*)v10.vortex-win.data.microsoft.com
4 www.microsoft.com (.*)microsoft.com
也许还应该阅读split-apply-combine模式以获得更高级的策略。希望对您有所帮助。
答案 5 :(得分:0)
不幸的是,仍然需要应用的另一种方法是使用lastgroup
。这需要编译单个正则表达式,然后查找匹配的组(行)的名称:
In [11]: regex = re.compile("|".join([f"(?P<i{i}>{regex})" for i, regex in s["wildcards"].items()]))
In [12]: regex
Out[12]:
re.compile(r'(?P<i42>(.*)activation.playready.microsoft.com)|(?P<i35>(.*)v10.vortex-win.data.microsoft.com)|(?P<i40>(.*)settings-win.data.microsoft.com)|(?P<i43>(.*)smartscreen.microsoft.com)|(?P<i39>(.*).playready.microsoft.com)|(?P<i38>(.*)go.microsoft.com)|(?P<i240>(.*)i.microsoft.com)|(?P<i238>(.*)microsoft.com)',
re.UNICODE)
In [13]: B.server_hostname.apply(lambda s: int(re.match(regex, s).lastgroup[1:]))
Out[13]:
146 238
205 40
341 43
406 35
667 238
Name: server_hostname, dtype: int64
In [14]: B.server_hostname.apply(lambda s: int(re.match(regex, s).lastgroup[1:])).map(s.wildcards)
Out[14]:
146 (.*)microsoft.com
205 (.*)settings-win.data.microsoft.com
341 (.*)smartscreen.microsoft.com
406 (.*)v10.vortex-win.data.microsoft.com
667 (.*)microsoft.com
Name: server_hostname, dtype: object
熊猫没有暴露此属性(但是可能可以对内部进行一些巧妙的处理)...
答案 6 :(得分:0)
我能找到的最纯粹的熊猫方法包括在B.server_hostname上为每个正则表达式运行Series.str.match()
,然后使用idxmax()
从每列中进行第一个匹配。
# Create input data
A = pd.DataFrame({'wildcards' : ['(.*)activation.playready.microsoft.com',
'(.*)v10.vortex-win.data.microsoft.com',
'(.*)i.microsoft.com', '(.*)microsoft.com'],
'regex' : [re.compile('^(.*)activation.playready.microsoft.com$'),
re.compile('^(.*)v10.vortex-win.data.microsoft.com$'),
re.compile('^(.*)i.microsoft.com$'),
re.compile('^(.*)microsoft.com$')]})
B = pd.DataFrame({'server_hostname' : ['v10.vortex-win.data.microsoft.com',
'www.microsoft.com']})
# Ensure B has a unique index
B = B.reset_index(drop=True)
# Check which regexes match each hostname
df_match = A.regex.apply(lambda x: B.server_hostname.str.match(x))
df_match.index= A.wildcards
df_match.columns=B.server_hostname
# Get first match for each hostname
df_first_match = df_match.idxmax().rename('wildcards').reset_index()
输出:
print(df_match)
print(df_first_match)
server_hostname v10.vortex-win.data.microsoft.com www.microsoft.com
wildcards
(.*)activation.playready.microsoft.com False False
(.*)v10.vortex-win.data.microsoft.com True False
(.*)i.microsoft.com False False
(.*)microsoft.com True True
server_hostname wildcards
0 v10.vortex-win.data.microsoft.com (.*)v10.vortex-win.data.microsoft.com
1 www.microsoft.com (.*)microsoft.com
也就是说,这似乎比之前发布的其他解决方案要慢一些。