Python 检测CSV中具有不同列数的记录

Python 检测CSV中具有不同列数的记录,python,pandas,csv,awk,Python,Pandas,Csv,Awk,我需要每天将CSV文件摄取到数据帧中。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

我需要每天将CSV文件摄取到数据帧中。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的帖子。我认为这会使我的过程复杂化,在我的情况下,这是不值得的

如果您能以最直接、最简单的方式提供帮助,我将不胜感激


谢谢

如果您同意以后处理坏记录,则可以在读取
csv
文件时使用
error\u bad\u line
warn\u bad\u line
,并将跳过记录的行号保存到日志文件中,如下所示:

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个值的原样,然后将它们之前的所有值串联起来,因此无论在
名称
字段中有多少个
逗号
,它都应该起作用:

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
或者总是:

$ 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,它不支持regexp间隔,那么您可以使用
sub(/,[^,]*,[^,]*,[^,]*$/,”“)
而不是
sub(/(,[^,]*){2}$/,”“)


有关使用awk操作csv的更多信息,请参阅。

这里有一种方法,通过在csv文件中的行上迭代,并使用正则表达式来查找与头名
name
Address
Phone
对应的所有匹配字段值

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])


我会用两种方法来处理这个问题:

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\u Xcolumns.csv”的不同文件,然后您可以用python处理该文件

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


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

哦,这可能正是我需要的!谢谢“我会试试并报告的。”威尔玛:当然,让我知道。你比我先做到了。我刚开始做正则表达式,看到了这个。哇,太棒了!艾德,现在就试试!谢谢
$ 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
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
## separate csv based on number of columns per row:
awk 'BEGIN{FS=","; OFS=","} {filename="outputfile_"NF"columns.csv"; print $0 > filename}' inputfile.csv