使用 pd.read_csv(不规则分隔符,自定义 NA)读取 borked CSV 文件

reading borked CSV files with pd.read_csv (irregular delimiters, custom NA)

我正在读取 CSV 数据用作 pandas 数据框,但 CSV 似乎没有遵循任何理智的约定(除了使用 ; 作为分隔符,每个人都应该.. .).似乎唯一的目标是让它们在文本编辑器中打开时看起来

下面是一些示例(设置为变量,以便它们可用于 reader 示例):

ex1="""
Type: Some Metadata
User: More Metadata
Data:
01.10.1939 00:00:00   ;   1,1 ;     
01.12.1939 00:00:00   ;   1   ;     
01.01.1940 00:00:00   ;  10   ;     
"""

好的,十进制逗号(简单)、分号分隔符(简单)、dayfirst(简单)和一堆元数据(skiprows,也简单)。

ts = pd.read_csv(io.StringIO(ex1), skiprows=4, decimal=',', sep=';',
                 index_col=0, usecols=[0,1], dayfirst=True,
                 parse_dates=True, names=['date', 'val'])

print(ts 生成一个漂亮的数据框

             val
date
1939-10-01   1.1
1939-12-01   1.0
1940-01-01  10.0

ts.index 是一个很好的 DatetimeIndex 并且 type(ts.val[0]) 是一个 numpy.float64,这是应该的。不过介绍一个创意的方式来标记NaN:

ex2="""
Type: Some Metadata
User: More Metadata
Data:
01.10.1939 00:00:00   ;   1,1;        
01.12.1939 00:00:00   ; NÄ  ;       
01.01.1940 00:00:00   ;  10   ;   
"""

上面的 ts=read_csv... 仍然没有错误,但是 打破了 val 列并将其转换为字符串。但是当我将其更改为

ts = pd.read_csv(io.StringIO(ex2), skiprows=4, decimal=',', sep=';',
                 index_col=0, usecols=[0,1], dayfirst=True,
                 parse_dates=True, names=['date', 'val'],
                 na_values='NÄ')

使用na_values,整个事情都失败了。 print(ts)

                val
date
1939-10-01      1,1
1939-12-01     NÄ
1940-01-01    10

不仅不接受 作为 NaN,这还将所有 val 变成字符串,从而忽略小数点逗号并保留尾随空格。 ts.val[0] 现在是 ' 1,1',所以简单的 ts.val = ts.val.astype(float) 当然失败了。

我做错了什么 na_values='NÄ'

为什么它也打断 decimal',' 并添加空格?

似乎 skipinitialspace=True 应该有所帮助,但当然 仍然打破了 val 列。 sep='\s*[;]s*' 看起来很有前途,

ts = pd.read_csv(io.StringIO(ex2), skiprows=4, decimal=',' ,sep='\s*[;]s*',
                 index_col=0, usecols=[0,1], dayfirst=True,
                 parse_dates=True, names=['date', 'val'],
                 na_values='NÄ')

看起来不错 print(ts)

               val
date
1939-10-01     1.1
1939-12-01      NÄ
1940-01-01      10

(注意小数点!),但现在我遇到了奇怪的情况,它确实取代了逗号,但 ts.val[0] 现在又是一个字符串,并且仍然有尾随空格(' 1.1').

那么我该如何阅读那些无聊的文件呢?

我目前使用的解决方法是使用纯 python 读取 CSV(无论如何我必须读取 header(实际文件中的 40 行))并写入它输出到适当的 CSV 中,用 pandas:

读取
file = open(myfile, 'r', encoding='UTF-8')
table = file.readlines()
file.close()

for v1 in range(0, len(table)):
    table[v1] = table[v1].replace("NÄ", "NaN")
    table[v1] = table[v1].replace(",", ".")

dataoutput = ["date;val\r\n"]
for v1 in range(3, len(table)):
    dataoutput.append(table[v1])
f2 = open(myfile.replace('.csv', 'good.csv'), 'w')
for v1 in range(0, len(dataoutput)):
    f2.write(dataoutput[v1])
f2.close()

ts = pd.read_csv(myfile.replace('.csv', 'good.csv'), 
                 sep=';', usecols=[0, 1], index_col=0,
                 dayfirst=True, parse_dates=True)

ts.val = ts.val.astype(float)

但是对于几千个 CSV 文件,最大可达 1 兆字节,这并不是一个真正的最佳解决方案,所以我想解决导入中的 问题。

您的 sep 是 mis-specified(它以 s* 而不是 \s* 结尾,这意味着它正在寻找 0 到无限 s 个字符) .这就是为什么您只捕获 ; 之后的前导空格而不捕获尾随空格的原因。顺便说一下,这也干扰了 (1),因为您试图替换 'NÄ' 但值是 ' NÄ'。请改用 sep='\s*\;\s*'

你将来可以做的一件事是自己打印出有问题的值,以确保它们包含他们认为你包含的内容,例如ts.iloc[1].val.

此外,如果 NaN 值在 unicode 中是一个问题,您可以在解析之前将其删除:

csv = io.StringIO(ex2.replace(u'N\xc4', '[MISSING]'))
ts = pd.read_csv(csv, 
    skiprows=4, decimal=',', index_col=0, usecols=[0,1], 
    dayfirst=True, parse_dates=True, names=['date', 'val'], 
    na_values='[MISSING]', sep='\s*\;\s*')

...这会给出...

             val
date
1939-10-01   1.1
1939-12-01   NaN
1940-01-01  10.0