Python 使用多个分隔符高效地将数据从CSV读入数据帧
我有一个笨拙的CSV文件,它有多个分隔符:非数字部分的分隔符是“Python 使用多个分隔符高效地将数据从CSV读入数据帧,python,pandas,performance,csv,dataframe,Python,Pandas,Performance,Csv,Dataframe,我有一个笨拙的CSV文件,它有多个分隔符:非数字部分的分隔符是“”,“,数字部分的分隔符是“”;”。我希望尽可能高效地仅使用数字部分构造数据帧 我做了5次尝试:其中,使用了pd.read\u csv的converters参数,使用regex和engine='python',使用str.replace。它们都比不进行转换的情况下读取整个CSV文件慢2倍多。这对于我的用例来说太慢了 我理解这种比较不是一对一的比较,但它确实证明了总体性能差不是由I/O驱动的。有没有更有效的方法将数据读入数字数据帧?还
”,“
,数字部分的分隔符是“”;”代码>。我希望尽可能高效地仅使用数字部分构造数据帧
我做了5次尝试:其中,使用了pd.read\u csv
的converters
参数,使用regex和engine='python'
,使用str.replace
。它们都比不进行转换的情况下读取整个CSV文件慢2倍多。这对于我的用例来说太慢了
我理解这种比较不是一对一的比较,但它确实证明了总体性能差不是由I/O驱动的。有没有更有效的方法将数据读入数字数据帧?还是等效的NumPy阵列
以下字符串可用于基准测试目的
# Python 3.7.0, Pandas 0.23.4
from io import StringIO
import pandas as pd
import csv
# strings in first 3 columns are of arbitrary length
x = '''ABCD,EFGH,IJKL,34.23;562.45;213.5432
MNOP,QRST,UVWX,56.23;63.45;625.234
'''*10**6
def csv_reader_1(x):
df = pd.read_csv(x, usecols=[3], header=None, delimiter=',',
converters={3: lambda x: x.split(';')})
return df.join(pd.DataFrame(df.pop(3).values.tolist(), dtype=float))
def csv_reader_2(x):
df = pd.read_csv(x, header=None, delimiter=';',
converters={0: lambda x: x.rsplit(',')[-1]}, dtype=float)
return df.astype(float)
def csv_reader_3(x):
return pd.read_csv(x, usecols=[3, 4, 5], header=None, sep=',|;', engine='python')
def csv_reader_4(x):
with x as fin:
reader = csv.reader(fin, delimiter=',')
L = [i[-1].split(';') for i in reader]
return pd.DataFrame(L, dtype=float)
def csv_reader_5(x):
with x as fin:
return pd.read_csv(StringIO(fin.getvalue().replace(';',',')),
sep=',', header=None, usecols=[3, 4, 5])
检查:
res1 = csv_reader_1(StringIO(x))
res2 = csv_reader_2(StringIO(x))
res3 = csv_reader_3(StringIO(x))
res4 = csv_reader_4(StringIO(x))
res5 = csv_reader_5(StringIO(x))
print(res1.head(3))
# 0 1 2
# 0 34.23 562.45 213.5432
# 1 56.23 63.45 625.2340
# 2 34.23 562.45 213.5432
assert all(np.array_equal(res1.values, i.values) for i in (res2, res3, res4, res5))
基准测试结果:
%timeit csv_reader_1(StringIO(x)) # 5.31 s per loop
%timeit csv_reader_2(StringIO(x)) # 6.69 s per loop
%timeit csv_reader_3(StringIO(x)) # 18.6 s per loop
%timeit csv_reader_4(StringIO(x)) # 5.68 s per loop
%timeit csv_reader_5(StringIO(x)) # 7.01 s per loop
%timeit pd.read_csv(StringIO(x)) # 1.65 s per loop
更新
我愿意使用命令行工具作为最后手段。在这方面,我已经包括了这样一个答案。我希望有一个效率相当的纯Python或Pandas解决方案。如果这是一个选项,请替换字符使用,字符串中的更快。
我已将字符串x
写入文件test.dat
def csv_reader_4(x):
with open(x, 'r') as f:
a = f.read()
return pd.read_csv(StringIO(unicode(a.replace(';', ','))), usecols=[3, 4, 5])
unicode()
函数是避免Python 2中出现类型错误所必需的
基准:
%timeit csv_reader_2('test.dat') # 1.6 s per loop
%timeit csv_reader_4('test.dat') # 1.2 s per loop
使用命令行工具
到目前为止,我找到的最有效的解决方案是使用专业的命令行工具将“;”
替换为“,”
,然后读入Pandas。Pandas或纯Python解决方案在效率方面并不接近
基本上,使用cpython或用C/C++编写的工具可能优于Python级操作。
例如,使用:
如何使用生成器进行替换,并将其与适当的装饰器相结合,以获得适合熊猫的类似文件的对象
import io
import pandas as pd
# strings in first 3 columns are of arbitrary length
x = '''ABCD,EFGH,IJKL,34.23;562.45;213.5432
MNOP,QRST,UVWX,56.23;63.45;625.234
'''*10**6
def iterstream(iterable, buffer_size=io.DEFAULT_BUFFER_SIZE):
"""
http://stackoverflow.com/a/20260030/190597 (Mechanical snail)
Lets you use an iterable (e.g. a generator) that yields bytestrings as a
read-only input stream.
The stream implements Python 3's newer I/O API (available in Python 2's io
module).
For efficiency, the stream is buffered.
"""
class IterStream(io.RawIOBase):
def __init__(self):
self.leftover = None
def readable(self):
return True
def readinto(self, b):
try:
l = len(b) # We're supposed to return at most this much
chunk = self.leftover or next(iterable)
output, self.leftover = chunk[:l], chunk[l:]
b[:len(output)] = output
return len(output)
except StopIteration:
return 0 # indicate EOF
return io.BufferedReader(IterStream(), buffer_size=buffer_size)
def replacementgenerator(haystack, needle, replace):
for s in haystack:
if s == needle:
yield str.encode(replace);
else:
yield str.encode(s);
csv = pd.read_csv(iterstream(replacementgenerator(x, ";", ",")), usecols=[3, 4, 5])
请注意,我们通过str.encode将字符串(或其组成字符)转换为字节,因为熊猫需要这样做
这种方法在功能上与Daniele的答案完全相同,只是我们“动态”地替换值,因为它们是被请求的,而不是一次完成的。一个非常非常非常快速的方法,3.51
就是结果,只需将csv\u reader\u 4
制作如下,它只是将StringIO
转换为str
,然后替换使用,
进行编码,并使用sep=','读取数据帧。
:
def csv_reader_4(x):
with x as fin:
reader = pd.read_csv(StringIO(fin.getvalue().replace(';',',')), sep=',',header=None)
return reader
基准:
%timeit csv_reader_4(StringIO(x)) # 3.51 s per loop
在我的环境(Ubuntu 16.04、4GB RAM、Python 3.5.2)中,最快的方法是(原型1)csv\u reader\u 5
(取自),它的运行速度仅比不进行转换的情况下读取整个csv文件慢25%。我改进了这种方法,实现了一个过滤器/包装器来替换read()
方法中的字符:
class SingleCharReplacingFilter:
def __init__(self, reader, oldchar, newchar):
def proxy(obj, attr):
a = getattr(obj, attr)
if attr in ('read'):
def f(*args):
return a(*args).replace(oldchar, newchar)
return f
else:
return a
for a in dir(reader):
if not a.startswith("_") or a == '__iter__':
setattr(self, a, proxy(reader, a))
def csv_reader_6(x):
with x as fin:
return pd.read_csv(SingleCharReplacingFilter(fin, ";", ","),
sep=',', header=None, usecols=[3, 4, 5])
与无转换读取整个CSV文件相比,结果是性能稍好一些:
In [3]: %timeit pd.read_csv(StringIO(x))
605 ms ± 3.24 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [4]: %timeit csv_reader_5(StringIO(x))
733 ms ± 3.49 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [5]: %timeit csv_reader_6(StringIO(x))
568 ms ± 2.98 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
< P > 1我称它为原型,因为它假定输入流是<代码> StringIO < /C> >类型(因为它上面调用了代码> .GETValueAudioor)/< P> Python具有强大的数据处理功能,但不要期望使用Python性能。当需要性能时,C和C++是您的朋友。
python中的任何快速库都是用C/C++编写的。在python中使用C/C++代码非常容易,请看一下swig实用工具()。您可以编写一个C++类,它可以包含一些快速工具,当需要时,您将在Python代码中使用这些工具。p> 您是否考虑过对多个分隔符使用正则表达式?例如:。不确定它是否会更快。@克里斯,现在我有了(请参见编辑),带有engine='python'
的正则表达式比没有转换器的pd慢8倍。读取\u csv
。@jpp,如果您使用engine=c
,文档建议c引擎更快,而python引擎目前功能更完整。@pygo,文档解释正则表达式仅适用于python引擎。不行。是什么阻止了你只是更换了所有的;例如,在CSV文件中并正常导入?这对我来说会导致MemoryError
,大概是因为它需要有效地读取所有内容两次?一旦进入a
,然后进入pd.DataFrame
。我想a.replace
会创建一个副本。不幸的是,如果不使用更复杂的工具(如cython
),我看不到一个简单的方法来避免这种情况。更好的是,使用流而不是覆盖文件。顺便说一句,使用子流程。check_call
而不是os.system
,因为它检查退出代码。@ivan_pozdeev,您能否详细介绍如何使用流而不是覆盖文件?其他地方有这样的例子吗?您是否在一致的硬件/设置上测试了相对性能?我看到这是一个较慢的解决方案,我已经用基准测试更新了我的问题。@jpp呃,你的计时与我的不同,我在Windows上。@U9 Forward我通过在read()
操作期间进行替换改进了你的方法:好主意,但这一个计时时间为2min1s
!在Python3.7、Pandas 0.23.4上,我在pd.read\u csv
行上得到ValueError:Invalid file path或buffer object type:
。有什么想法吗?@jpp Pandas 0.23.4对要被视为文件的对象有一个额外的要求,就像它必须有一个\uu iter\uu
方法一样。我更新了我的答案以反映这一点。很抱歉延迟。我对此计时,它比我设置的csv\u reader\u 1
长1秒(对于\u 1
为4.28秒,对于\u 6
为5.28秒)。根据我的问题,我正在使用输入x=“”…”*10**6
,Python 3.7.0,Pandas 0.23.4,Windows。我知道这将取决于平台/设置。
In [3]: %timeit pd.read_csv(StringIO(x))
605 ms ± 3.24 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [4]: %timeit csv_reader_5(StringIO(x))
733 ms ± 3.49 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [5]: %timeit csv_reader_6(StringIO(x))
568 ms ± 2.98 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)