【问题标题】:Python dataframe partial string replacementPython数据框部分字符串替换
【发布时间】:2021-07-25 14:18:32
【问题描述】:

我有一个日期格式为 01JAN2020 的数据集,我希望将其转换为 01-01-2020 它们可能出现在数据框中的任何列中。我的解决方案是将其导出为 CSV 并使用 SED 进行操作,但速度很慢。供参考的 SED 命令为:

conversions="sed -Ei -e 's/(,[0-9][0-9])(JAN)([0-9]{4},)/\\1-01-\\3/g' "
conversions=conversions+"-e 's/(,[0-9][0-9])(FEB)([0-9]{4},)/\\1-02-\\3/g' "
conversions=conversions+"-e 's/(,[0-9][0-9])(MAR)([0-9]{4},)/\\1-03-\\3/g' "
conversions=conversions+"-e 's/(,[0-9][0-9])(APR)([0-9]{4},)/\\1-04-\\3/g' "
conversions=conversions+"-e 's/(,[0-9][0-9])(MAY)([0-9]{4},)/\\1-05-\\3/g' "
conversions=conversions+"-e 's/(,[0-9][0-9])(JUN)([0-9]{4},)/\\1-06-\\3/g' "
conversions=conversions+"-e 's/(,[0-9][0-9])(JUL)([0-9]{4},)/\\1-07-\\3/g' "
conversions=conversions+"-e 's/(,[0-9][0-9])(AUG)([0-9]{4},)/\\1-08-\\3/g' "
conversions=conversions+"-e 's/(,[0-9][0-9])(SEP)([0-9]{4},)/\\1-09-\\3/g' "
conversions=conversions+"-e 's/(,[0-9][0-9])(OCT)([0-9]{4},)/\\1-10-\\3/g' "
conversions=conversions+"-e 's/(,[0-9][0-9])(NOV)([0-9]{4},)/\\1-11-\\3/g' "
conversions=conversions+"-e 's/(,[0-9][0-9])(DEC)([0-9]{4},)/\\1-12-\\3/g' "+filename
subprocess.run(conversions, shell=True)

我希望最有可能使用 re 在 python 程序中直接执行此操作,但无法正确进行部分替换。完成日期格式后,我应该能够自己管理时间戳(01JAN2020:00:00:00)。在多个文件上运行,最终大约 1TB 的数据,所以运行时间很重要。

编辑以获取更多详细信息:

包含日期和时间戳的列是未知的,并且对于将要运行的每个帧都不同。

应该忽略的日期之间可能混有空值。

由于列顺序很重要,因此应进行更改。

也许是一个两步解决方案。 第 1 步确定所有非空值与上述正则表达式匹配的所有列。 然后第二步转换匹配列的匹配格式。

【问题讨论】:

    标签: python pandas python-re


    【解决方案1】:

    你可以试试这个,因为我没有你的任何数据集,我已经根据你的例子创建了我自己的数据集。
    DataFrame:

                p                  q
    0       TI688 09MAR2078 00:08:00
    1   01JAN2020                 77
    2     73dhi38  08NOV199700:00:00
    3       jdkdn          03DEC2089
    4   01JAN2020               _£^&
    5       783i3                NaN
    6  90YINM7832                NaN
    

    现在我们正在做的是像这样制作月份名称和月份值的字典:

    months = {"JAN":"01","FEB":"02","MAR":"03","APR":"04","MAY":"05","JUN":"06","JUL":"07","AUG":"08","SEP":"09","OCT":"10","NOV":"11","DEC":"12"}
    

    我们现在有了这本字典,如果我们找到月份名称,我们可以很容易地用数字替换它。因此,我们必须使用正则表达式来查找遵循01JAN2020 模式的字符串。这是找到它的代码:

    pat = r"(?P<day>\d{2})(?P<month>[A-Z]{3})(?P<year>\d+)"
    

    现在,我们找到了可以用这个替换它的月份:

    repl = lambda m: f"{m.group('day')}-{months[m.group('month')]}-{m.group('year')}"
    

    不要在日和年中更改任何内容,但在月中根据months 字典替换它。我们快完成了,我们必须像这样在DataFrame 中应用这些东西:

    df=df.apply(lambda x:x.str.replace(pat,repl,regex=True))
    

    最后的输出是:

                p                   q
    0       TI688 09-03-2078 00:08:00
    1  01-01-2020                  77
    2     73dhi38  08-11-199700:00:00
    3       jdkdn          03-12-2089
    4  01-01-2020                _£^&
    5       783i3                 NaN
    6  90YINM7832                 NaN
    

    完整代码:

    import pandas as pd
    import numpy as np
    
    df=pd.DataFrame({"p":["jdkdn","01JAN2020","73dhi38","jdkdn","01JAN2020","783i3","90YINM7832"],"q":["09MAR2078","77","08NOV199700:00:00","03DEC2089","_£^&",np.nan,np.nan]})
    
    months = {"JAN":"01","FEB":"02","MAR":"03","APR":"04","MAY":"05","JUN":"06","JUL":"07","AUG":"08","SEP":"09","OCT":"10","NOV":"11","DEC":"12"}
    
    pat = r"(?P<day>\d{2})(?P<month>[A-Z]{3})(?P<year>\d+)"
    repl = lambda m: f"{m.group('day')}-{months[m.group('month')]}-{m.group('year')}"
    
    df=df.apply(lambda x:x.str.replace(pat,repl,regex=True))
    
    print(df)
    

    对于您的问题,这里是解决它的代码, 相信你有list of files:

    import pandas as pd
    
    months = {"JAN":"01","FEB":"02","MAR":"03","APR":"04","MAY":"05","JUN":"06","JUL":"07","AUG":"08","SEP":"09","OCT":"10","NOV":"11","DEC":"12"}
    
    pat = r"(?P<day>\d{2})(?P<month>[A-Z]{3})(?P<year>\d+)"
    repl = lambda m: f"{m.group('day')}-{months[m.group('month')]}-{m.group('year')}"
    
    #files=os.listdir() #You can use this as well.
    
    for file in files:
        df=pd.read_csv(file)
        df=df.apply(lambda x:x.str.replace(pat,repl,regex=True))
        df.to_csv(file) #If you want to save in other file then that file name.
    

    这应该比你预期的更快。

    编辑:
    如果某些地方的数据类型是int64float64,这将无法按预期工作。因此,我们可能必须通过这样做将数据帧转换为字符串:

    df=df.applymap(str)
    

    在应用替换之前放置。现在它将按预期工作。但是我发现了一个问题。它将NanNone 也转换为字符串。这可能会导致问题,但如果我们因为字符串 nan 而遇到任何问题,那么我们可以这样做:

    df.replace("nan",np.nan,inplace=True)
    

    现在int64float64 变成了字符串,所以之前的代码可以完美运行。

    代码:

    df=df.applymap(str)
    #df.replace("nan",np.nan,inplace=True) #Optional if we need it then only use it
    df=df.apply(lambda x:x.str.replace(pat,repl,regex=True))
    

    【讨论】:

    • @RG 检查性能并通知我。如果它也很慢,那么我还有另一个提高性能的想法。
    • 这可能与一些调整一起工作,但现在它正在中止,因为一些字段的类型是 int64 和 float64。早上我可能会尝试使用它并仅适用于 dtype=object 的字段
    【解决方案2】:

    如果性能是个问题,请尽量减少读取文件的次数(使用 sed 命令读取 12 次!)。此外,可能没有必要使用 pandas,因为 read_csv 和 to_datetime 操作可能也很昂贵。如果您的 sed 方法有效,您可以尝试一次性完成。比如:

    months = {'JAN': '01', 'FEB': '02', 'MAR': '03', 'APR': '04', 'MAY': '05', 'JUN': '06', 'JUL': '07', 'AUG': '08', 'SEP': '09', 'OCT': '10', 'NOV': '11', 'DEC': '12'}
    with open('in.csv', 'r') as f_in, open('out.csv', 'w') as f_out:
        for line in f_in.readlines():
            f_out.write(re.sub(r'(?<=,\d\d)('+'|'.join(months)+')(?=\d\d\d\d,)', lambda m: months.get(m.group()), line))
    

    如果您仍然需要将数据作为数据框读取,则可以使用其中之一,具体取决于您喜欢使用 string 还是 datetime 类型:

    # fix as string
    import re
    months = {'JAN': '01', 'FEB': '02', 'MAR': '03', 'APR': '04', 'MAY': '05', 'JUN': '06', 'JUL': '07', 'AUG': '08', 'SEP': '09', 'OCT': '10', 'NOV': '11', 'DEC': '12'}
    re_months = re.compile(r'(?<=\d\d)('+'|'.join(months)+')(?=\d\d\d\d)')
    
    def fix_date_str(e):
        if type(e) != str:
            return e
        return re_months.sub(lambda m: months.get(m.group()), e)
    
    df.applymap(fix_date_str)
    
    # fix as datetime
    def fix_date2_datetime(e):
        try:
            return pd.to_datetime(e, format='%d%b%Y')
        except ValueError:
            return e
    df.applymap(fix_date_datetime)
    

    【讨论】:

    • 这很有效,与我使用的 SED 方法相比,它显着提升了性能。我仍然认为直接在数据帧中执行此操作可以获得更高的效率和更少的文件 IO(NFS 驱动器),或者可能会巧妙地使用超出我有限的 python 经验的管道。无论如何,我都需要将数据集加载到数据框中,因为有问题的框架实际上是多个 CSV 文件的合并,我会根据数据框的内容生成数据库加载语句。
    • @RG 我相信my answer 会解决您的问题并提高性能。
    • 如果您仍然需要加载数据,请查看建议的替代方案。它应该保持其他类型不变
    猜你喜欢
    • 2012-12-30
    • 2017-09-09
    • 1970-01-01
    • 2021-10-20
    • 2017-07-08
    • 2015-04-17
    • 1970-01-01
    • 1970-01-01
    • 2018-01-22
    相关资源
    最近更新 更多