代码之家  ›  专栏  ›  技术社区  ›  jpp

使用多个分隔符将csv中的数据高效地读取到数据帧中

  •  12
  • jpp  · 技术社区  · 7 年前

    我有一个笨拙的csv文件,它有多个分隔符:非数字部分的分隔符是 ',' ,对于数字部分 ';' . 我希望尽可能高效地从数字部分构造一个数据帧。

    我做了5次尝试:其中,利用 converters 论证 pd.read_csv ,使用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
    

    更新

    我愿意最后使用命令行工具。在这个程度上,我已经包括了这样一个答案。我希望有一个纯蟒蛇或熊猫解决方案,具有相当的效率。

    6 回复  |  直到 7 年前
        1
  •  6
  •   jpp    7 年前

    使用命令行工具

    到目前为止,我发现最有效的解决方案是使用专业的命令行工具来替换 ";" 具有 "," 然后 读熊猫的故事。熊猫或纯python解决方案在效率方面并不接近。

    本质上,使用cpython或用C/C++编写的工具很可能胜过Python级操作。

    例如,使用 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
    
        2
  •  2
  •   Max Snijders    7 年前

    如何使用一个生成器来进行替换,并将其与适当的装饰器相结合,以获得适合熊猫的类似文件的对象?

    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
  •  1
  •   Daniele    7 年前

    如果这是一个选项,则替换字符 ; 具有 , 在字符串中更快。 我已经写好了 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() 函数是避免Python2中出现类型错误所必需的。

    标杆管理:

    %timeit csv_reader_2('test.dat')  # 1.6 s per loop
    %timeit csv_reader_4('test.dat')  # 1.2 s per loop
    
        4
  •  1
  •   U13-Forward    7 年前

    一个非常快的, 3.51 结果就是,简单地 csv_reader_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
    
        5
  •  1
  •   sancelot    7 年前

    Python具有强大的操作数据的功能,但不要期望使用Python进行性能。当需要性能时,C和C++是您的朋友。 Python中的任何快速库都是用C/C++编写的。在Python中使用C/C++代码非常简单,看看SWIG实用程序( http://www.swig.org/tutorial.html )您可以编写一个C++类,它可以包含一些在需要时在Python代码中使用的快速实用工具。

        6
  •  1
  •   Leon    7 年前

    在我的环境中(Ubuntu16.04,4GB RAM,python 3.5.2),最快的方法是 ) csv_reader_5 (取自 U9-Forward's answer )它只比读取整个没有转换的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)
    

    我称之为原型,因为它假定输入流是 StringIO 类型(因为它调用 .getvalue() 关于它)。