【问题标题】:Detecting records with different number of columns in a CSV检测 CSV 中具有不同列数的记录
【发布时间】:2021-07-23 19:15:06
【问题描述】:

我需要每天将 CSV 文件提取到 pandas dataframe 中。 CSV 有几千行,但每天我都会收到一些列比预期更多的记录。让我举一个例子。取以下CSV

Name, Address, Phone
John Doe, 777 Street, 3145678777
Jane Doe, 888 Street, 3145678888
Chris Doe, Adam Smith, 999 Street, 3145679999
Ellen Page, 222 Street, 3145679222

这是我的导入行:

df = pd.read_csv(myfile.csv, header = 0, names = ['Name, 'Address', 'Phone'])

正如预期的那样,第 3 行 (Chris Doe, Adam Smith, 999 Street, 3145679999) 中断了进程,因为它有一个额外的列。似乎我的源数据来自的应用程序允许用户在Name 字段中输入逗号,有时,当多个用户共享同一个家庭时,他们会这样做。我无法更改应用程序。

我的目标是简单地检测这些行并将它们移动到单独的文本文件或数据框,任何有意义的事情。我可以单独处理这些记录,很好。

我似乎有一些帖子旨在处理具有不同记录数的 CSV。我认为这会使我的过程复杂化,就我而言,这是不值得的。

对于如何以最直接和最简单的方式实现这一目标的任何帮助,我将不胜感激。

谢谢!

【问题讨论】:

    标签: python pandas csv awk


    【解决方案1】:

    如果您以后可以处理不良记录,您可以在读取csv 文件时使用error_bad_lineswarn_bad_lines,并将跳过记录的行号保存到这样的日志文件中:

    import contextlib
    
    with open('bad_lines.txt', 'w') as log:
        with contextlib.redirect_stderr(log):
            df = pd.read_csv('output.csv', warn_bad_lines=True, error_bad_lines=False)
    

    以上代码将跳过所有错误行并将错误行重定向到日志文件,然后您可以将其用于重新处理。 让我知道这是否有帮助。

    编辑:

    如果您不想重新处理不良记录,我为您提供了一些老生常谈的解决方案。 我在这里所做的是通过使用不同的分隔符将csv 的三列​​读为一列,然后对于元素数大于列数(在本例中为 3)的每一行,只保留最后 2 个值,然后将它们之前的所有内容连接起来,因此无论您在 Name 字段中获得多少 commas,它都应该可以工作:

    df = pd.read_csv('output.csv', sep=';') # Notice the sep here
    
    col_count = 3
    
    def str_check(x):
        x = x.split(',')
        if len(x) > col_count:
            x = [', '.join(x[:-(col_count-1)])] + x[-(col_count-1):] 
            # Here the col_count is 3 so if you hardcode the values, 
            # it should look like [', '.join(x[:-2])] + x[-2:]
            # Join everything before the last two elements as one element
        
        return ';'.join(x)
    
    df['Name, Address, Phone'] = df['Name, Address, Phone'].apply(str_check)
    df = df['Name, Address, Phone'].str.split(';', expand=True)
    df.columns = ['Name', 'Address', 'Phone']
    df
    

    【讨论】:

    • 哦,这可能正是我需要的!谢谢!,我会试一试并报告。
    • @Wilmar 当然,请告诉我。
    【解决方案2】:
    $ awk -F, 'NF>3' file
    Chris Doe, Adam Smith, 999 Street, 3145679999
    

    $ awk -F, '{print > (NF>3 ? "bad" : "good")}' file
    
    $ head bad good
    ==> bad <==
    Chris Doe, Adam Smith, 999 Street, 3145679999
    
    ==> good <==
    Name, Address, Phone
    John Doe, 777 Street, 3145678777
    Jane Doe, 888 Street, 3145678888
    Ellen Page, 222 Street, 3145679222
    

    或者,如果“名称”字段包含逗号,您也可以修改 CSV 以引用它:

    $ awk -F, 'NF>3{rest=$(NF-1) FS $NF; sub(/(,[^,]*){2}$/,""); $0="\"" $0 "\"," rest} 1' file
    Name, Address, Phone
    John Doe, 777 Street, 3145678777
    Jane Doe, 888 Street, 3145678888
    "Chris Doe, Adam Smith", 999 Street, 3145679999
    Ellen Page, 222 Street, 3145679222
    

    或总是:

    $ awk -F, '{rest=$(NF-1) FS $NF; sub(/(,[^,]*){2}$/,""); $0="\"" $0 "\"," rest} 1' file
    "Name", Address, Phone
    "John Doe", 777 Street, 3145678777
    "Jane Doe", 888 Street, 3145678888
    "Chris Doe, Adam Smith", 999 Street, 3145679999
    "Ellen Page", 222 Street, 3145679222
    

    如果您使用的是非常旧的、不符合 POSIX 且不支持正则表达式间隔的 awk,那么您可以使用 sub(/,[^,]*,[^,]*$/,"") 而不是 sub(/(,[^,]*){2}$/,"")

    有关使用 awk 操作 CSV 的更多信息,请参阅 What's the most robust way to efficiently parse CSV using awk?

    【讨论】:

    • 哇,这太棒了!埃德,现在就试试吧!谢谢!
    【解决方案3】:

    这是一种方法,通过遍历 csv 文件中的行并使用正则表达式来查找与标题名称 NameAddressPhone 对应的所有匹配字段值

    import re
    
    data = []
    regex = re.compile(r'(.*),\s?(.*),\s?(.*)')
    with open('data.csv') as file:
        for line in file:
            v = regex.search(line)
            data += [v.groups() if v else []]
    
    df = pd.DataFrame(data[1:], columns=data[0])
    

    >>> df
                        Name     Address       Phone
    0               John Doe  777 Street  3145678777
    1               Jane Doe  888 Street  3145678888
    2  Chris Doe, Adam Smith  999 Street  3145679999
    3             Ellen Page  222 Street  3145679222
    

    【讨论】:

    • 你打败了我。我刚刚形成正则表达式并看到了这个。
    【解决方案4】:

    我会以两种方式处理这个问题:

    1-在python摄取之前做一些预先处理,根据列数(由分隔符出现次数反映)分离记录,然后在python中分别处理每组不同的列:

    ## separate csv based on number of columns per row:
    awk 'BEGIN{FS=","; OFS=","} {filename="outputfile_"NF"columns.csv"; print $0 > filename}' inputfile.csv
    

    上面的衬里会接受一个输入 csv,计算列数并将每条记录指向一个名为“output_Xcolumns.csv”的不同文件,然后您可以在 python 中处理该文件

    2- 或将您的 python 代码更改为,而不是使用 pandas 直接加载到 df 中,而是作为列表逐行加载,并根据长度将列表附加到不同的 Dfs。

    我更喜欢方法 1,因为我认为它会更有效。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-21
      • 2021-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-24
      • 1970-01-01
      • 2019-05-15
      相关资源
      最近更新 更多