Usando un objeto personalizado en pandas.read_csv ()

Estoy interesado en transmitir un objeto personalizado en un dataframe de pandas. De acuerdo con la documentación , se puede utilizar cualquier objeto con un método read (). Sin embargo, incluso después de implementar esta función todavía estoy recibiendo este error:

ValueError: ruta de archivo no válida o tipo de objeto de búfer:

Aquí hay una versión simple del objeto, y cómo lo llamo:

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) 

¿Me estoy perdiendo algo o simplemente no es posible usar un generador personalizado en Pandas? Cuando llamo al método read () funciona bien.

EDITAR: la razón por la que quiero usar un objeto personalizado en lugar de concatenar los marcos de datos juntos es para ver si es posible reducir el uso de la memoria. He usado la biblioteca gensim en el pasado, y hace que sea realmente fácil de usar objetos de datos personalizados, por lo que esperaba encontrar un enfoque similar.

La documentación menciona el método de read , pero en realidad está comprobando si es un argumento is_file_like a is_file_like (ahí es donde se lanza la excepción). Esa función es en realidad muy simple:

 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 

Así que también necesita un método de __iter__ .

Pero ese no es el único problema. Pandas requiere que realmente se comporte como un archivo. Por lo tanto, el método de read debe aceptar un argumento adicional para el número de bytes (por lo que no puede hacer que un generador read , porque debe ser invocable con 2 argumentos y debe devolver una cadena).

Así por ejemplo:

 class DataFile(object): def __init__(self, files): self.data = """ab 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') 

Se reconocerá como entrada válida.

Sin embargo, es más difícil para varios archivos, esperaba que la entrada de fileinput pudiera tener algunas rutinas apropiadas pero no lo parece:

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

Una forma de crear un objeto similar a un archivo en Python3 mediante la subclasificación de io.RawIOBase . Y al usar iterstream Mechanical iterstream , puedes convertir cualquier iterable de bytes en un objeto similar a un archivo:

 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 (eg 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) 

huellas dactilares

  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 

¿Qué hay de este enfoque alternativo:

 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)