我有一个笨拙的CSV文件,其中包含多个定界符:非数字部分的定界符为','
,数字部分的定界符为';'
。我想尽可能高效地仅从数字部分构造一个数据框。
我做了5次尝试:其中,利用converters
的{{1}}参数,将正则表达式与pd.read_csv
一起使用,以及engine='python'
。与不进行任何转换的整个CSV文件读取速度相比,它们的速度要慢2倍以上。对于我的用例来说,这太慢了。
我知道这种比较不是“按样进行”的,但是它确实表明总体性能不佳不是由I / O驱动的。是否有更有效的方法将数据读入数字Pandas数据框?还是等效的NumPy数组?
下面的字符串可用于基准测试。
str.replace
检查:
# 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))
我愿意使用命令行工具作为最后的手段。在这种程度上,我已经包括了这样的答案。我希望有一个效率可比的纯Python或Pandas解决方案。
答案 0 :(得分:6)
By far the most efficient solution I've found is to use a specialist command-line tool to replace ";"
with ","
and then read into Pandas. Pandas or pure Python solutions do not come close in terms of efficiency.
Essentially, using CPython or a tool written in C / C++ is likely to outperform Python-level manipulations.
For example, using Find And Replace Text:
import os
os.chdir(r'C:\temp') # change directory location
os.system('fart.exe -c file.csv ";" ","') # run FART with character to replace
df = pd.read_csv('file.csv', usecols=[3, 4, 5], header=None) # read file into Pandas
答案 1 :(得分:2)
如何使用生成器进行替换,然后将其与适当的装饰器组合在一起,以获得适合熊猫的类似文件的对象?
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)的答案相同,不同之处在于我们可以“即时”替换值,因为它们是一头一遍地被请求的,而不是一劳永逸的。
答案 2 :(得分:1)
如果这是一个选项,则在字符串中用;
替换字符,
更快。
我已将字符串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中的TypeError是必需的。
基准化:
%timeit csv_reader_2('test.dat') # 1.6 s per loop
%timeit csv_reader_4('test.dat') # 1.2 s per loop
答案 3 :(得分:1)
一个非常非常快的结果,3.51
,只需将csv_reader_4
放在下面,就简单地将StringIO
转换为str
,然后替换{{1 }}和;
,并使用,
读取数据帧:
sep=','
基准:
def csv_reader_4(x):
with x as fin:
reader = pd.read_csv(StringIO(fin.getvalue().replace(';',',')), sep=',',header=None)
return reader
答案 4 :(得分:1)
在我的环境(Ubuntu 16.04、4GB RAM,Python 3.5.2)中,最快的方法是(原型 1 )csv_reader_5
(取自U9-Forward's answer)比读取没有转换的整个CSV文件仅慢25%。我通过实现用于替换read()
方法中的char的过滤器/包装器来改进了该方法:
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)
1 之所以称其为原型,是因为它假定输入流是StringIO
类型的(因为它在其上调用了.getvalue()
)。
答案 5 :(得分:1)
Python具有强大的功能来处理数据,但是不要期望使用python会产生性能。当需要性能时,C和C ++是您的朋友。 python中的任何快速库都是用C / C ++编写的。在python中使用C / C ++代码非常容易,请看swig实用程序(http://www.swig.org/tutorial.html)。您可以编写一个c ++类,其中可能包含一些快速实用程序,您将在需要时在python代码中使用它们。