为什么 Python 在从 Pandas 系列中提取时似乎忽略了通配符?

Why is Python seemingly ignoring wildcards when extracting from a Pandas Series?

我目前正在做一些数据清理,我正在处理一个 Pandas DataFrame,其中的列包含数字和字符串。有些行的值类似于 012345,而其他行的值看起来像 Excel 格式化为文本(例如 ="012345")

所以请考虑这个示例系列:

raw_series = pd.Series([114254, 958554, '="142142"', '="987654"', 112233])

在 运行 以下片段之后:

re_numbers = re.compile(r"([0-9]*)")
num_series = raw_series.str.extract(re_numbers)

这是预期的结果: ([114254, 958554, 142142, 987654, 112233])

这是实际结果:([114254, 958554, , , 112233])

将正则表达式模式更改为:([0-9]{6})

我达到了预期的效果。使用否定集提取数字时会发生相同的结果(例如 ([^="]*))那么这里发生了什么?

这是一个解决方案:

>>> raw_series = pd.Series([114254, 958554, '="142142"', '="987654"', 112233])                                          
>>> raw_series.astype('str').str.extract(r"([0-9]+)")
        0
0  114254
1  958554
2  142142
3  987654
4  112233

为什么这个(和你自己的解决方案)有效?

因为 * 匹配任何内容,包括零匹配。正则表达式匹配是贪心的,所以它会吸收所有数字作为一个匹配,但如果第一个字符不是数字,那么 first 匹配将是空匹配。如果您使用了 extractall,您会在后面的匹配中找到相关的字符串 (*)。

我的解决方案只要求至少一个数字,因此空字符串将不匹配。

(*) 像这样:

>>> raw_series.astype('str').str.extractall(r"([0-9]*)")
              0                                                                                                         
  match
0 0      114254
  1         NaN
1 0      958554
  1         NaN
2 0         NaN
  1         NaN
  2      142142
  3         NaN
  4         NaN
3 0         NaN
  1         NaN
  2      987654
  3         NaN
  4         NaN
4 0      112233
  1         NaN

@0 0 打败了我,但是,是的,这是因为您使用的是 * 标记,它匹配 0 或更多次。

对您当前的正则表达式进行简单调整即可解决所有问题:

import re

import pandas as pd

re_numbers = re.compile(r"([0-9]+)")

raw_series = pd.Series([114254, 958554, '="142142"', '="987654"', 112233])

match_series = raw_series.str.extract(re_numbers, expand=False)

res_series = raw_series.copy()
res_series[match_series.notna()] = match_series
res_series = res_series.astype(int)

print(f"match_series:\n{match_series}\n")
print(f"res_series:\n{res_series}")

输出:

match_series:
0       NaN
1       NaN
2    142142
3    987654
4       NaN
dtype: object

res_series:
0    114254
1    958554
2    142142
3    987654
4    112233
dtype: int64

它可以而且可能应该使用我们掌握的有关数据格式的信息进一步改进。完整的模式如下所示:=\"(\d{6})\".

import re

import pandas as pd

re_numbers = re.compile(r"=\"(\d{6})\"")

raw_series = pd.Series([114254, 958554, '="142142"', '="987654"', 112233])

match_series = raw_series.str.extract(re_numbers, expand=False)

res_series = raw_series.copy()
res_series[match_series.notna()] = match_series
res_series = res_series.astype(int)

print(f"match_series:\n{match_series}\n")
print(f"res_series:\n{res_series}")

输出:

match_series:
0       NaN
1       NaN
2    142142
3    987654
4       NaN
dtype: object

res_series:
0    114254
1    958554
2    142142
3    987654
4    112233
dtype: int64