【问题标题】:Fastest method to replace substring from a Dataframe from key value pairs从键值对替换数据帧中的子字符串的最快方法
【发布时间】:2021-09-03 15:10:41
【问题描述】:

假设我有一个带有欧洲语言日期的 DataFrame:

In[0]: df_date
Out[0]:
    date
0   ene 1 2021
1   1 ene 2021
2   mar. 1 2021
3   5 mars. 2021
4   10 abr 2021
5   abr. 1 2021

我还有另一个带有相应字符串映射的 DataFrame

In[0]: df_translate
Out[0]:
    foreign eng
0   ene     jan
1   feb     feb
2   mar     mar
3   abr     apr
4   may     may
5   mars    mar

我替换这些的方法通常是将映射 DataFrame 转换为字典,然后使用 Series.str.replace()

date_replace_dict = df_translate.set_index('foreign',drop=True).to_dict()['eng']
    
for k,v in date_replace_dict.items():

    df_date['date'] = df['date'].str.replace(k,v,regex=False)

我发现这个过程有点慢,我想知道是否有更好/更快的pythonic方式。

我不能使用Series.replace() 方法,因为显然它需要对字符串进行精确匹配。

另外请注意,这是一个更普遍的问题,因为我碰巧对包含不同类型字符串和子字符串的不同类型的列进行了很多此操作。因此,要替换的子字符串不一定在字符串的开头或任何其他算法可确定的位置。

如果这是一个很好的解决方法,那就没问题了。但是 python 往往让我觉得每当我使用循环来实现某事时我做错了什么:)

【问题讨论】:

    标签: python pandas string dataframe replace


    【解决方案1】:

    我会考虑把它写成这样的一个正则表达式替换,以避免多次复制整个系列。当然,更重要的是,您需要在每列上应用的替换越多。

    import re
    
    pattern = r"\b(" + "|".join(re.escape(k) for k in date_replace_dict) + r")\b"
    
    df['date'].str.replace(pattern, lambda m: date_replace_dict[m.group(0)], regex=True)
    

    在这里,使用单词边界 \b 匹配器,因为如果要替换的单词本身是彼此的子字符串,则可能需要它,但这取决于用例应该如何应用这些。

    【讨论】:

    • 谢谢,这是一个很酷的建议,但它并没有真正起作用。显然 str.replace() 使用 re.match() 仅匹配字符串的开头。 pandas.pydata.org/pandas-docs/stable/reference/api/…
    • 我可以验证它确实适用于部分匹配,不必是前缀。熊猫 1.2.4。文档似乎没有提到“re.match”(使用匹配对象,不是一回事)。
    • 你是对的,我试过了,它有效。但它不适用于我的完整 DataFrame,原因是我在 df_translate (和其他变体)中有一个 mar 后跟一个 mars。 df_translate.sort_values('foreign', ascending=False) 负责解决这个特殊问题。不完全确定这是否适用于一般的子字符串替换情况
    • 这很棘手,我在答案中的示例中添加了单词边界,但这取决于用例是否需要。使用额外的正则表达式功能可能会减慢正则表达式搜索(对于小字符串应该没那么重要,其他开销占主导地位?)
    【解决方案2】:

    看看dateparserDateparser 将为您完成将 string 转换为 datetime 对象的所有工作。特别是在您的情况下,当所有字符串的格式都不同并且有多种语言时,使用 dateparser 可能是最简单的事情。

    >>> import dateparser as dp
    >>> dp.parse("10 Jan 2020")
        datetime.datetime(2020, 1, 10, 0, 0)
    >>> dp.parse("ene 1 2021")
        datetime.datetime(2021, 1, 1, 0, 0)
    >>> dp.parse("abr. 1 2021")
        datetime.datetime(2021, 4, 1, 0, 0)
    >>> dp.parse("10 abr 2021")
        datetime.datetime(2021, 4, 10, 0, 0)
    >>> dp.parse("2020 dic 25")
        datetime.datetime(2020, 12, 25, 0, 0)
    >>> dp.parse("mart 25 14") # mart is dutch for march
        datetime.datetime(2014, 3, 25, 0, 0)
    

    您只需将dateparser.parse() 函数应用于您的字符串日期列即可!

    换句话说,我会完全摆脱你的 mapping 并尝试 dateparser

    这是他们支持的所有languages and locales 的列表。

    【讨论】:

    • 感谢 Nico,我尝试的第一件事是 dateparser。它确实需要很长时间才能解析整个列,即使我给它使用的语言环境,这就是我选择这个解决方案的原因。除了这个问题不是特别关于日期外,我正在寻找一种更快的方法,适用于任何子字符串替换
    • 很高兴您尝试过。不过,我仍然认为值得再次看一下 dateparser。您可以使用其较低级别的类DateDataParser 来优化解析。此外,不要手动迭代数据框,而是使用 df.applymap()。快速提问,您的数据框有多少行?
    • 我确实使用了 .applymap()。 DataFrame 有大约 500k 行
    【解决方案3】:
    import pandas as pd
    
    df_date = pd.DataFrame({'date':
                                ['ene 1 2021',
                                 '1 ene 2021',
                                 'mar. 1 2021',
                                 '5 abr. 2021',
                                 '10 abr 2021',
                                 'abr. 1 2021']})
    
    df_translate = pd.DataFrame(
        {'foreign': ['ene', 'feb', 'mar', 'abr', 'mar', 'jun'], 'eng': ['jan', 'feb', 'mar', 'apr', 'may', 'jun']})
    d = df_translate.set_index('foreign', drop=True).to_dict()['eng']
    print(df_date['date'].replace(to_replace=d, value=None, regex=True))
    

    输出

    0     jan 1 2021
    1     1 jan 2021
    2    may. 1 2021
    3    5 apr. 2021
    4    10 apr 2021
    5    apr. 1 2021
    Name: date, dtype: object
    

    【讨论】:

    • 谢谢,所以可以使用 Series.replace()。我还尝试了使用 Timeit 的方法,使用 Series.replace(0 比循环慢 %25 出于某种原因(在我的完整 DataFrame 上执行时)
    猜你喜欢
    • 1970-01-01
    • 2018-02-24
    • 2013-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-20
    • 2021-11-12
    • 1970-01-01
    相关资源
    最近更新 更多