¿Puedo acceder al motor de inferencia dtype de read_csv () al crear un DataFrame desde una lista anidada?

Esto se deduce de una discusión con piRSquared aquí , donde descubrí que read_csv parece tener sus propios métodos de inferencia de tipos que parecen tener una capacidad más amplia para obtener el tipo correcto. También parece ser más tolerante a los fallos en el caso de datos faltantes, eligiendo NaN lugar de lanzar ValueError como su comportamiento predeterminado.

Hay muchos casos en los que los tipos de datos inferidos son perfectamente aceptables para mi trabajo, pero esta funcionalidad no parece estar expuesta cuando se DataFrame una instancia de un DataFrame , o en cualquier otro lugar de la API que pueda encontrar, lo que significa que tengo que tratar manualmente los dtypes innecesariamente Esto puede ser tedioso si tienes cientos de columnas. Lo más cercano que puedo encontrar es convert_objects() pero no maneja los bools en este caso. La alternativa que podría usar es volcar en el disco y volver a leerlo, lo cual es extremadamente ineficiente.

El siguiente ejemplo ilustra el comportamiento predeterminado de read_csv frente al comportamiento predeterminado de los métodos convencionales para establecer dtype (correcto en V 0.20.3 ). ¿Hay alguna forma de acceder a la inferencia de tipo de read_csv sin volcar el disco? Más en general, ¿hay alguna razón por la que read_csv comporte de esta manera?

Ejemplo:

 import numpy as np import pandas as pd import csv data = [['string_boolean', 'numeric', 'numeric_missing'], ['FALSE', 23, 50], ['TRUE', 19, 12], ['FALSE', 4.8, '']] with open('my_csv.csv', 'w') as outfile: writer = csv.writer(outfile) writer.writerows(data) # Reading from CSV df = pd.read_csv('my_csv.csv') print(df.string_boolean.dtype) # Automatically converted to bool print(df.numeric.dtype) # Float, as expected print(df.numeric_missing.dtype) # Float, doesn't care about empty string # Creating directly from list without supplying datatypes df2 = pd.DataFrame(data[1:], columns=data[0]) df2.string_boolean = df2.string_boolean.astype(bool) # Doesn't work - ValueError df2.numeric_missing = df2.numeric_missing.astype(np.float64) # Doesn't work # Creating but forcing dtype doesn't work df3 = pd.DataFrame(data[1:], columns=data[0], dtype=[bool, np.float64, np.float64]) # The working method df4 = pd.DataFrame(data[1:], columns=data[0]) df4.string_boolean.map({'TRUE': True, 'FALSE': False}) df4.numeric_missing = pd.to_numeric(df4.numeric_missing) 

Una solución es usar un objeto StringIO . La única diferencia es que mantiene todos los datos en la memoria, en lugar de escribir en el disco y volver a leer.

El código es el siguiente (nota: Python 3!):

 import numpy as np import pandas as pd import csv from io import StringIO data = [['string_boolean', 'numeric', 'numeric_missing'], ['FALSE', 23, 50], ['TRUE', 19, 12], ['FALSE', 4.8, '']] with StringIO() as fobj: writer = csv.writer(fobj) writer.writerows(data) fobj.seek(0) df = pd.read_csv(fobj) print(df.head(3)) print(df.string_boolean.dtype) # Automatically converted to bool print(df.numeric.dtype) # Float, as expected print(df.numeric_missing.dtype) # Float, doesn't care about empty string 

El with StringIO() as fobj no es realmente necesario: fobj = String() funcionará igual de bien. Y dado que el administrador de contexto cerrará el objeto StringIO() fuera de su scope, el df = pd.read_csv(fobj) tiene que estar dentro de él.
Tenga en cuenta también el fobj.seek(0) , que es otra necesidad, ya que su solución simplemente cierra y vuelve a abrir un archivo, lo que establecerá automáticamente el puntero del archivo al inicio del archivo.

Una nota sobre Python 2 vs Python 3

En realidad traté de hacer que el código anterior Python 2/3 sea compatible. Eso se convirtió en un desastre, debido a lo siguiente: Python 2 tiene un módulo io , al igual que Python 3, cuya clase StringIO hace que todo sea unicode (también en Python 2; en Python 3 es, por supuesto, el valor predeterminado).
Eso es genial, excepto que el módulo csv writer en Python 2 no es compatible con Unicode.
Por lo tanto, la alternativa es usar el módulo (c)StringIO Python 2 (c)StringIO (más antiguo), por ejemplo, como sigue:

 try: from cStringIO import StringIO except ModuleNotFoundError: # Python 3 from io import StringIO 

y las cosas serán texto plano en Python 2, y unicode en Python 3.
Excepto que ahora, cStringIO.StringIO no tiene un administrador de contexto, y la instrucción with fallará. Como mencioné, no es realmente necesario, pero mantenía las cosas lo más cerca posible de tu código original.
En otras palabras, no pude encontrar una buena manera de estar cerca del código original sin hacks ridículos.

También he buscado evitar por completo al escritor CSV, lo que lleva a:

 text = '\n'.join(','.join(str(item).strip("'") for item in items) for items in data) with StringIO(text) as fobj: df = pd.read_csv(fobj) 

que es quizás más limpio (aunque un poco menos claro), y compatible con Python 2/3. (No espero que funcione para todo lo que puede manejar el módulo csv , pero aquí funciona bien).


¿Por qué no puede pd.DataFrame(...) hacer la conversión?

Aquí, sólo puedo especular.

Creo que el razonamiento es que cuando la entrada son objetos de Python (dicts, listas), la entrada es conocida y está en manos del progtwigdor. Por lo tanto, es poco probable, tal vez incluso ilógico, que esa entrada contenga cadenas como 'FALSE' o '' . En su lugar, normalmente contendría los objetos False y np.nan (o math.nan ), ya que el progtwigdor ya se habría ocupado de la traducción (cadena).
Mientras que para un archivo (CSV u otro), la entrada puede ser cualquier cosa: su colega puede enviar un archivo CSV de Excel, o alguien más le envía un archivo CSV gnumérico. No sé qué tan estandarizados están los archivos CSV, pero es probable que necesite algo de código para permitir excepciones, y en general para la conversión de las cadenas al formato Python (NumPy).

Entonces, en ese sentido, es realmente ilógico esperar que pd.DAtaFrame(...) acepte cualquier cosa: en su lugar, debe aceptar algo que esté correctamente formateado.

Podría argumentar por un método conveniente que tome una lista como la suya, pero una lista no es un archivo CSV (que es solo un grupo de caracteres, incluidas las nuevas líneas). Además, espero que pd.read_csv tenga la opción de leer los archivos en trozos (quizás incluso línea por línea), lo que se vuelve más difícil si le asignas una cadena con líneas nuevas (en realidad no puedes leer esa línea). línea, ya que tendría que dividirla en líneas nuevas y mantener todas las líneas en la memoria. Y ya tiene la cadena completa en la memoria en algún lugar, en lugar de en el disco. Pero estoy divagando).

Además, el truco de StringIO es solo unas pocas líneas para realizar este truco con precisión.