排查 CSV 中的无效行

Posted

技术标签:

【中文标题】排查 CSV 中的无效行【英文标题】:Troubleshoot invalid rows in CSV 【发布时间】:2020-03-11 21:18:47 【问题描述】:

我正在处理一个非常大的 CSV 文件(近 6 GB),而且它绝对充满了错误。例如,如果我有以下 csv 文件/表:

+------------+-------------+------------+
|     ID     |    Date     |   String   |
+------------+-------------+------------+
|  123456    |  09-20-2019 |   ABCDEFG  |
|  123abc456 |  10-30-2019 |   HIJKLMN  |
|  7891011   |  jdqhouehwf |   OPQRSTU  |
|  1010101   |  03-15-2018 |   8473737  |
|  4823.00   |  02-11-2015 |   VWXYZ    |
|  2348813.0 |  01-23-2016 |   BAZ      |
+------------+-------------+------------+

或:

"ID","Date","String"
123456,"09-20-2019","ABCDEFG"
123abc456,"10-30-2019","HIJKLMN"
7891011,"jdqhouehwf","OPQRSTU"
1010101,"03-15-2018",8473737
4823.00,"02-11-2015","VWXYZ"
"2348813.0","01-23-2016","BAZ"

我想要一个解决和修复文件的好方法。使用 pandas,我可以读取文件:

import pandas as pd

df = pd.read_csv(inputfile)

熊猫总是会抱怨: sys:1: DtypeWarning: Columns (0) have mixed types. Specify dtype option on import or set low_memory=False

所以我想清理每一列。但由于它是一个非常大的文件,我不能只打印我的整个表格以使用掩码输出并期望读取它。我想要一种简单的方法来获取一列并检查它是否符合类型。另外,如果可能的话,我想要一种删除坏行和/或将行转换为正确格式的方法。毕竟,我希望文件看起来像(不包括内联 cmets):

"ID","Date","String"
123456,"09-20-2019","ABCDEFG"
#  123abc456,"10-30-2019","HIJKLMN" was deleted because the ID wasn't a number
#  7891011,"jdqhouehwf","OPQRSTU" was deleted because the data was not a date
1010101,"03-15-2018","8473737" # The last number could be converted to string
4823,"02-11-2015","VWXYZ" # The first number could be converted to integer
2348813,"01-23-2016","BAZ" # The ID number could be converted to int

【问题讨论】:

使用 csv 模块逐行预处理,并将更正/有效的行/行写入新文件。 【参考方案1】:
def main():

    from pathlib import Path
    import csv
    import datetime as dt

    with Path("thing.csv").open("r") as file:
        for row in csv.DictReader(file):
            try:
                row["ID"] = int(float(row["ID"]))
                row["Date"] = dt.datetime.strptime(row["Date"], "%m-%d-%Y")
            except (KeyError, ValueError):
                continue
            print(*row.values())

    return 0


if __name__ == "__main__":
    import sys
    sys.exit(main())

【讨论】:

【参考方案2】:

正如您标记的 sed,这里有一个命令应该以一种非常有效和可移植的方式完成这项工作,但它有点不可读......

sed -n '1p;s/^"\0,1\\([0-9]\+\)\(\.[0-9]*\)\0,1\"\0,1\\(,"\(0[0-9]\|1[0-2]\)-\([0-2][0-9]\|3[01]\)-2[0-9]\3\",\)"\0,1\\([^"]*\)"\0,1\$/\1\3"\6"/p' file

它的作用是:

    打印标题,即第一行(1p), 在所有行上尝试替换 (s) 命令并仅在替换成功时打印结果(因此仅当行与搜索模式匹配时)s/…/…/p

关于替换模式\1\3"\6",每个转义数字指的是对应的捕获组(\(…\);请记住,根据开头的\( 标记出现的顺序为组分配一个编号)。具体来说:

    \1 指的是前导数字([0-9]\+),有或没有(\0,1\)以下三件事:

    领先的", 尾随小数部分\.[0-9]*, 和以下";

    \3 指的是日期,包括" 周围("\(0[0-9]\|1[0-2]\)-\([0-2][0-9]\|3[01]\)-2[0-9]\3\"请注意,我在这个正则表达式中不准确,因为它也会匹配不存在的日期,比如2 月 31 日);

    "\6" 指的是(并将其放在" 之间)最后的字母数字字符串,对此我几乎没有做任何假设 ([^"]*)。

这应该更好地匹配日期(除了 2 月 29 日始终匹配,无论年份如何):

sed -n '1p;s/^"\0,1\\([0-9]\+\)\(\.[0-9]*\)\0,1\"\0,1\\(,"\(\(0[0-9]\|1[0-2]\)-[0-2][0-9]\|\(0[469]\|11\)-30\|\(0[13578]\|1[02]\)-31\)-2[0-9]\3\",\)"\0,1\\([^"]*\)"\0,1\$/\1\3"\8"/p' file

【讨论】:

这对于清除不良条目非常有用,但如果可能,它不会处理转换。理想情况下,删除一行只能作为最后的手段。 @GeoffreySaunders,转换什么? CSV 文件是文本文件,不是吗?在您对代码的 cmets 中,在我看来,您使用 convert to int 来指代删除 " 和小数部分。

以上是关于排查 CSV 中的无效行的主要内容,如果未能解决你的问题,请参考以下文章

第 1 行错误的 CSV 输入中的列数无效(已检查其他问题)

csv导入错误的Mysql排序规则/类型第1362行的CSV输入中的列计数无效

Pivotal HDB - 投诉“数据行太长。可能是由于 csv 数据无效”

第 1 行的 CSV 输入中的字段计数无效

第 1 行的 CSV 输入中的列数无效错误

CSV 列中的逗号值 - 无效代码