Pandas 数据框读取损坏数据的 CSV 文件

101

我想读取一个非常大的csv文件(在Excel中无法轻松打开和编辑),但在第十万行左右,有一行多了一列,导致程序崩溃。由于这一行出错,因此我需要忽略它是多余的一列。由于有大约50个列,因此硬编码标头并使用名称或usecols不是首选方法。我也可能会在其他csv文件中遇到此问题,因此需要通用解决方案。很遗憾,在read_csv中我找不到任何内容。代码如下:

def loadCSV(filePath):
    dataframe = pd.read_csv(filePath, index_col=False, encoding='iso-8859-1', nrows=1000)
    datakeys = dataframe.keys();
    return dataframe, datakeys

2
除此之外,使用 warn_bad_lines=True 可能进一步帮助诊断有问题的行。 - Herpes Free Engineer
5个回答

139

传递error_bad_lines=False以跳过错误的行:

error_bad_lines: 布尔型,默认值为True,如果某行CSV字段太多(例如逗号太多),默认会导致引发异常,并且不会返回任何DataFrame。如果为False,则返回的DataFrame将删除这些“错误行”。(仅在C解析器中有效)


15
一种替代方案是先读取一行数据以获取正确的列数,然后再重新读取以仅读取那些特定的列。例如:cols = pd.read_csv(file, nrows=1).columns df = pd.read_csv(file, usecols=cols) 这样将会忽略额外的列。我认为你可以尝试这种方法,如果可行,请让我知道是否成功。 - EdChum
3
@Fonti:没有“truncate_bad_lines”的选项。这样做是一个不好的做法。你假设你提前知道数据为什么不好(它有一个额外的值附加到了它上面)。但如果列数太少呢?如果这个额外的值是插入而不是附加的呢?像这样做会引起错误的积累。 - Steven Rumbalski
3
实际上,我刚试过了,不起作用。我认为你需要传递error_bad_lines=False并解析警告以获取行号,然后使用header=None仅读取那些行。 - EdChum
@StevenRumbalski 我完全同意。我不确定为什么数据偶尔会出现这种情况,因为我不是生产者,但我在数据输入方面遇到了一些非常奇怪的错误。在这种特定情况下,所有具有标题的列至少都有0或null值,因此只有当列条目过多时才会出现问题。 - Fonti
误读了你所指的内容(即备选项)。对于这种情况,error_bad_lines适用。 - Fonti
显示剩余3条评论

31

对于像我这样比原来发布时间晚几年才找到这个问题的人,其他答案建议使用error_bad_lines = Falsewarn_bad_lines = True,但两者在pandas中已被弃用。

相反,使用on_bad_lines='warn'可以达到相同的效果,跳过错误的数据行。

dataframe = pd.read_csv(filePath, index_col=False, encoding='iso-8859-1', nrows=1000, on_bad_lines = 'warn')

on_bad_lines = 'warn' 会在遇到错误行时发出警告并跳过该行。


on_bad_lines 的其他可接受值为:

  • 'error':遇到错误行会引发异常。
  • 'skip':将跳过任何错误行。

28

要获取关于引起错误的行的信息,请尝试使用以下组合:error_bad_lines=Falsewarn_bad_lines=True

dataframe = pd.read_csv(filePath, index_col=False, encoding='iso-8859-1', nrows=1000,
                        warn_bad_lines=True, error_bad_lines=False)

error_bad_lines=False 跳过导致错误的行,而 warn_bad_lines=True 则会打印错误详情和行号,如下所示:

'Skipping line 3: expected 4 fields, saw 3401\nSkipping line 4: expected 4 fields, saw 30...'

如果你想保存警告信息(例如用于进一步处理),那么你也可以将其保存到文件中(使用 contextlib):

import contextlib

with open(r'D:\Temp\log.txt', 'w') as log:
    with contextlib.redirect_stderr(log):
        dataframe = pd.read_csv(filePath, index_col=False, encoding='iso-8859-1', 
                                warn_bad_lines=True, error_bad_lines=False)

16

1.4.0 新特性

pandas 1.4.0 开始,read_csv() 函数提供了一种功能,允许您通过将回调函数分配给 on_bad_lines= 来更加优雅、智能地处理这些情况。

例如,假设有一个可能导致错误数据的 CSV 文件:Expected 4 fields in line 3, saw 5

C1,C2,C3,C4
10,11,12,13
25,26,27,28,garbage
80,81,82,83
以下lambda函数简单地忽略了坏行中的最后一列(正如上面的原始问题陈述所期望的那样):
df = pd.read_csv('your.csv', on_bad_lines=lambda x: x[:-1], engine='python')
df

   C1  C2  C3  C4
0  10  11  12  13
1  25  26  27  28
2  80  81  82  83

on_bad_lines这个可调用函数会在每行出错时被调用,其函数签名为(bad_line: list[str]) -> list[str] | None。如果该函数返回None,则该行将被忽略。如你所见,engine='python'是必需的。

最棒的是,它为编写任何你想要的细粒度逻辑以解决问题打开了大门。

例如,假设你想从行的开头或结尾删除错误数据,并且如果在开头和结尾都有错误数据,则只需忽略该行:

CSV

C1,C2,C3,C4
10,11,12,13
20,21,22,23,garbage
60,61,62,63
trash,80,81,82,83
trash,90,91,82,garbage

函数定义

def line_fixer(x):
    if not x[0].isnumeric() and x[-1].isnumeric():
        return x[1:] 
    
    if not x[-1].isnumeric() and x[0].isnumeric():
        return x[:-1]
    
    return None

结果

df = pd.read_csv('your.csv', on_bad_lines=line_fixer, engine='python')
df

   C1  C2  C3  C4
0  10  11  12  13
1  20  21  22  23
2  60  61  62  63
3  80  81  82  83

1
它是否会跳过那些少于所需值的行?举个例子,当所有其他行都有5个值时,它会跳过一个只有4个值的行吗? - Veeraja Veeraesh
2
@VeerajaVeeraesh read_csv 没有跳过短行的功能。 - jch
1
也许这是一个旧帖子,但是否有办法存储这些行呢?我尝试过类似的方法,但它并没有起作用。def bad_lines_collect(bad_line): badlines_list.append(bad_line) - Ljg

-2
这是我解决这些问题的方法,虽然效率较慢但很有效。 简单来说,只需将CSV文件读取为文本文件,并逐行遍历。 如果“,”逗号少于应有的数量,就跳过该行。 最终保留正确的行。
def bad_lines(path):
    import itertools
    num_columns = []
    lines = ""
    
    for i in range(10,50,5):
        content = open(path).readlines(i)[0]
        if (content.count("'") == 0) and (content.count('"') == 0):
            num_columns.append(content.count(","))

    if len(set(num_columns)) == 1:
        for line in itertools.islice(open(path), 0, None):
            if line.count(",") >= num_columns[0]:
                lines = lines + line

    text_file = open("temp.txt", "w")
    n = text_file.write(lines)
    text_file.close()
    
    return("temp.txt")

4
有更好的处理方式,因为你的回答非常容易出错。请考虑修改或删除此回答。 - gbeaven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接