使用多个定界符有效地将数据从CSV读取到数据帧中

时间:2019-01-04 18:07:55

标签: python pandas performance csv dataframe

我有一个笨拙的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解决方案。

6 个答案:

答案 0 :(得分:6)

Use a command-line tool

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代码中使用它们。