【问题标题】:Using a custom object in pandas.read_csv()在 pandas.read_csv() 中使用自定义对象
【发布时间】:2018-02-28 19:19:21
【问题描述】:

我有兴趣将自定义对象流式传输到 pandas 数据框。根据the documentation,可以使用任何具有 read() 方法的对象。但是,即使在实现此功能后,我仍然会收到此错误:

ValueError:无效的文件路径或缓冲区对象类型:

这是对象的简单版本,以及我如何称呼它:

class DataFile(object):
    def __init__(self, files):
        self.files = files

    def read(self):
        for file_name in self.files:
            with open(file_name, 'r') as file:
                for line in file:
                    yield line

import pandas as pd
hours = ['file1.csv', 'file2.csv', 'file3.csv']

data = DataFile(hours)
df = pd.read_csv(data)

是我遗漏了什么,还是无法在 Pandas 中使用自定义生成器?当我调用 read() 方法时,它工作得很好。

编辑: 我想使用自定义对象而不是将数据帧连接在一起的原因是看看是否可以减少内存使用量。我过去使用过gensim 库,它使使用自定义数据对象变得非常容易,所以我希望能找到一些类似的方法。

【问题讨论】:

  • 即使这按记录工作,我怀疑您的 read 方法是否有效,因为通常,read(x) 从缓冲区读取 x 个字节。相反,您的 read 方法会返回一个生成器对象。

标签: python python-3.x pandas


【解决方案1】:

这个替代方法怎么样:

def get_merged_csv(flist, **kwargs):
    return pd.concat([pd.read_csv(f, **kwargs) for f in flist], ignore_index=True)

df = get_merged_csv(hours)

【讨论】:

    【解决方案2】:

    文档中提到了 read 方法,但它实际上是在检查它是否是 is_file_like 参数(这是引发异常的地方)。那个函数其实很简单:

    def is_file_like(obj):
        if not (hasattr(obj, 'read') or hasattr(obj, 'write')):
            return False
        if not hasattr(obj, "__iter__"):
            return False
        return True
    

    所以它还需要一个__iter__ 方法。

    但这不是唯一的问题。 Pandas 要求它的行为实际上类似于文件。所以read 方法应该接受一个额外的字节数参数(所以你不能让read 成为一个生成器——因为它必须可以用两个参数调用并且应该返回一个字符串)。

    例如:

    class DataFile(object):
        def __init__(self, files):
            self.data = """a b
    1 2
    2 3
    """
            self.pos = 0
    
        def read(self, x):
            nxt = self.pos + x
            ret = self.data[self.pos:nxt]
            self.pos = nxt
            return ret
    
        def __iter__(self):
            yield from self.data.split('\n')
    

    将被识别为有效输入。

    不过多文件比较难,我希望fileinput 可以有一些合适的例程,但看起来不像:

    import fileinput
    
    pd.read_csv(fileinput.input([...]))
    # ValueError: Invalid file path or buffer object type: <class 'fileinput.FileInput'>
    

    【讨论】:

    • 我找不到is_file_like 函数,因为导入语句暗示它会在pandas.core.dtypes.common,但它在pandas.core.dtypes.inference ...奇怪...无论如何,看着实际的 csv 解析代码,我相信如果你通过 engine='python' 它使用 .readline
    • 啊!看看我在pandas.core.dtypes.common:from .inference import * # noqa 发现了什么,是的,确实没有质量保证......
    • hm,我刚刚使用了pd.io.common.is_file_like.__module__pd.io.common 是调用函数的模块)。 :)
    【解决方案3】:

    通过子类化io.RawIOBase 在 Python3 中创建类文件对象的一种方法。 并使用Mechanical snail's iterstream, 您可以将 任何可迭代的字节 转换为类似文件的对象:

    import tempfile
    import io
    import pandas as pd
    
    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)
    
    
    class DataFile(object):
        def __init__(self, files):
            self.files = files
    
        def read(self):
            for file_name in self.files:
                with open(file_name, 'rb') as f:
                    for line in f:
                        yield line
    
    def make_files(num):
        filenames = []
        for i in range(num):
            with tempfile.NamedTemporaryFile(mode='wb', delete=False) as f:
                f.write(b'''1,2,3\n4,5,6\n''')
                filenames.append(f.name)
        return filenames
    
    # hours = ['file1.csv', 'file2.csv', 'file3.csv']
    hours = make_files(3)
    print(hours)
    data = DataFile(hours)
    df = pd.read_csv(iterstream(data.read()), header=None)
    
    print(df)
    

    打印

       0  1  2
    0  1  2  3
    1  4  5  6
    2  1  2  3
    3  4  5  6
    4  1  2  3
    5  4  5  6
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-04-07
      • 2019-07-28
      • 2019-10-26
      • 1970-01-01
      • 1970-01-01
      • 2018-11-09
      • 1970-01-01
      • 2020-09-07
      相关资源
      最近更新 更多