在pandas.read_csv()中使用自定义对象

时间:2017-09-19 21:29:45

标签: python python-3.x pandas

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

  

ValueError:无效的文件路径或缓冲区对象类型:< class'__main __。DataFile'>

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

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()方法时,它可以正常工作。

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

3 个答案:

答案 0 :(得分:3)

文档提到了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成为一个生成器 - 因为它必须可以用2个参数调用,并且应该返回一个字符串)。 / p>

例如:

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'>

答案 1 :(得分: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)

答案 2 :(得分:0)

这种替代方法怎么样:

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)