Estoy trabajando en un gran Pandas DataFrame que debe convertirse en diccionarios antes de ser procesado por otra API.
Los diccionarios necesarios se pueden generar llamando al .to_dict(orient='records') . Como se indica en los documentos, el valor devuelto depende de la opción de orient :
Devuelve: dict, list o collections.abc.Mapping
Devuelve un objeto collections.abc.Mapping que representa el DataFrame. La transformación resultante depende del parámetro de orientación.
Para mi caso, al pasar orient='records' , se devuelve una lista de diccionarios. Cuando se trata de listas, se reserva/asigna la memoria completa requerida para almacenar los elementos de la lista. Como mi marco de datos puede volverse bastante grande, esto podría generar problemas de memoria, especialmente porque el código podría ejecutarse en sistemas de destino de especificaciones más bajas.
Ciertamente podría eludir este problema procesando el marco de datos por partes y generando la lista de diccionarios para cada parte que luego se pasa a la API. Además, llamar a iter(df.to_dict(orient='records')) devolvería el generador deseado, pero no reduciría la huella de memoria requerida ya que la lista se crea de manera intermedia.
¿Hay alguna forma de devolver directamente una expresión de generador desde df.to_dict(orient='records') en lugar de una lista para reducir el consumo de memoria?
No hay forma de obtener un generador directamente desde to_dict(orient='records') . Sin embargo, es posible modificar el código fuente de to_dict para que sea un generador en lugar de devolver una lista de comprensión:
from pandas.core.common import standardize_mapping from pandas.core.dtypes.cast import maybe_box_native def dataframe_records_gen(df_): columns = df_.columns.tolist() into_c = standardize_mapping(dict) for row in df_.itertuples(index=False, name=None): yield into_c( (k, maybe_box_native(v)) for k, v in dict(zip(columns, row)).items() )Código de muestra:
import pandas as pd df = pd.DataFrame({ 'A': [1, 2], 'B': [3, 4] }) # Using Generator for row in dataframe_records_gen(df): print(row) # For Comparison with to_dict function print("to_dict", df.to_dict(orient='records'))Producción:
{'A': 1, 'B': 3} {'A': 2, 'B': 4} to_dict [{'A': 1, 'B': 3}, {'A': 2, 'B': 4}]Para una sintaxis más natural, también es posible registrar un descriptor de acceso personalizado :
import pandas as pd from pandas.core.common import standardize_mapping from pandas.core.dtypes.cast import maybe_box_native @pd.api.extensions.register_dataframe_accessor("gen") class GenAccessor: def __init__(self, pandas_obj): self._obj = pandas_obj def records(self): columns = self._obj.columns.tolist() into_c = standardize_mapping(dict) for row in self._obj.itertuples(index=False, name=None): yield into_c( (k, maybe_box_native(v)) for k, v in dict(zip(columns, row)).items() ) Lo que hace que este generador sea accesible a través del descriptor de acceso gen en este caso:
df = pd.DataFrame({ 'A': [1, 2], 'B': [3, 4] }) # Using Generator through registered custom accessor for row in df.gen.records(): print(row) # For Comparison with to_dict function print("to_dict", df.to_dict(orient='records'))Producción:
{'A': 1, 'B': 3} {'A': 2, 'B': 4} to_dict [{'A': 1, 'B': 3}, {'A': 2, 'B': 4}]