Tipos de datos eficientes para reducir la memoria
Reduzca el tamaño de las columnas numéricas a int32/float32 y convierta las columnas de texto a Categorical para reducir hasta un 70 % la memoria del DataFrame.
Tipos de datos eficientes para reducir la memoria es una lección gratuita de Pandas & NumPy Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Pandas & NumPy Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Pandas & NumPy Academy incluye 4 lecciones en total.
Por qué los tipos de datos afectan al rendimiento
En Pandas, cada columna tiene un dtype (tipo de datos) que determina cómo se almacenan los valores en memoria y la velocidad de las operaciones. Pandas utiliza tipos grandes de forma predeterminada al cargar datos: int64 (8 bytes por valor), float64 (8 bytes) y object (tamaño variable, a menudo entre 50 y 200 bytes por cadena). En millones de filas, elegir tipos más pequeños puede reducir la memoria entre un 50 y un 80 % y acelerar las operaciones entre 2 y 5 veces gracias a una mejor utilización de la caché.
import pandas as pd
import numpy as np
np.random.seed(0)
df = pd.DataFrame({
'id': np.arange(1000000, dtype='int64'),
'score': np.random.uniform(0, 100, 1000000).astype('float64'),
'category': np.random.choice(['A','B','C','D'], 1000000)
})
mem = df.memory_usage(deep=True)
print('Memory per column:')
print(mem)
print(f'Total: {mem.sum() / 1e6:.1f} MB')Reducir el tamaño de las columnas enteras
Si una columna contiene valores enteros que caben en un intervalo menor, reduzca su tipo de int64 a un tipo entero más pequeño. pd.to_numeric(series, downcast='integer') selecciona automáticamente el tipo entero más pequeño capaz de contener todos los valores: int8 (–128 a 127, 1 byte), int16 (–32768 a 32767, 2 bytes), int32 (±2.000 millones, 4 bytes), o mantiene int64 si es necesario. Una columna int8 utiliza 8 veces menos memoria que una columna int64.
import pandas as pd
import numpy as np
np.random.seed(0)
df = pd.DataFrame({
'age': np.random.randint(18, 90, 500000).astype('int64'),
'score': np.random.randint(0, 100, 500000).astype('int64'),
'large_id': np.random.randint(0, 2**31, 500000).astype('int64')
})
# Downcast integers
for col in df.select_dtypes('int64').columns:
df[col] = pd.to_numeric(df[col], downcast='integer')
print('Dtypes after downcasting:')
print(df.dtypes)
print(f'\nMemory: {df.memory_usage(deep=True).sum()/1e6:.2f} MB')Reducir el tamaño de las columnas de tipo float
pd.to_numeric(series, downcast='float') convierte float64 en float32 (4 bytes en lugar de 8) cuando la precisión lo permite. float32 tiene aproximadamente 7 dígitos decimales de precisión, frente a los 15 de float64. Para la mayoría de las tareas de análisis (porcentajes, precios y características normalizadas), la precisión de float32 es suficiente. Para cálculos científicos que requieren alta precisión, mantenga float64. Reducir a la mitad la precisión de los valores float reduce a la mitad la memoria y mejora el rendimiento de la caché.
import pandas as pd
import numpy as np
np.random.seed(0)
df = pd.DataFrame({'lat': np.random.uniform(-90, 90, 1000000),
'lon': np.random.uniform(-180, 180, 1000000),
'temp': np.random.uniform(-40, 50, 1000000)})
print('Before:', df.dtypes.unique())
print(f'Memory: {df.memory_usage(deep=True).sum()/1e6:.1f} MB')
for col in df.select_dtypes('float64').columns:
df[col] = pd.to_numeric(df[col], downcast='float')
print('After:', df.dtypes.unique())
print(f'Memory: {df.memory_usage(deep=True).sum()/1e6:.1f} MB')El tipo de datos Categorical
El dtype Categorical ofrece el mayor ahorro de memoria para columnas de cadenas con valores repetidos. En lugar de almacenar miles de veces la misma cadena (por ejemplo, 'Electronics'), Pandas almacena un diccionario de valores únicos y un código entero compacto para cada fila. Una columna con 1 millón de filas y solo 50 categorías únicas pasa de unos 50 MB (dtype object) a aproximadamente 1 MB (Categorical): una reducción de memoria de 50 veces.
import pandas as pd
import numpy as np
np.random.seed(0)
categories = ['Electronics', 'Clothing', 'Books', 'Food', 'Furniture',
'Sports', 'Toys', 'Health', 'Garden', 'Automotive']
df = pd.DataFrame({'product_cat': np.random.choice(categories, 1000000)})
mem_before = df.memory_usage(deep=True).sum()
df['product_cat'] = df['product_cat'].astype('category')
mem_after = df.memory_usage(deep=True).sum()
print(f'Object dtype: {mem_before/1e6:.1f} MB')
print(f'Categorical: {mem_after/1e6:.2f} MB')
print(f'Reduction: {mem_before/mem_after:.0f}x')Cuándo Categorical ahorra memoria
El dtype Categorical solo ahorra memoria cuando la columna tiene muchos menos valores únicos que filas totales. El punto de equilibrio se alcanza cuando la proporción de valores únicos respecto al total de filas (cardinalidad) supera aproximadamente el 50 %: si cada fila contiene una cadena única, Categorical utiliza en realidad más memoria que el dtype object, porque almacena tanto la matriz de códigos como la matriz de categorías. Compruebe siempre df['col'].nunique() / len(df) antes de realizar la conversión: una proporción inferior a 0,5 (e idealmente inferior a 0,05) indica que Categorical será beneficioso.
import pandas as pd
import numpy as np
def memory_gain(df, col):
n = len(df)
n_unique = df[col].nunique()
ratio = n_unique / n
mem_obj = df[col].memory_usage(deep=True)
df2 = df.copy()
df2[col] = df2[col].astype('category')
mem_cat = df2[col].memory_usage(deep=True)
print(f'{col}: {n_unique} unique / {n} total (ratio={ratio:.3f})')
print(f' Object: {mem_obj/1e6:.2f} MB → Categorical: {mem_cat/1e6:.2f} MB')
print(f' {"Saves" if mem_cat < mem_obj else "Wastes"} {abs(mem_obj-mem_cat)/1e6:.2f} MB')
np.random.seed(0)
df = pd.DataFrame({
'low_card': np.random.choice(['A','B','C'], 500000), # low cardinality
'high_card': [f'id_{i}' for i in range(500000)] # high cardinality
})
memory_gain(df, 'low_card')
memory_gain(df, 'high_card')Categorical mejora el rendimiento de GroupBy
Además de ahorrar memoria, el dtype Categorical también acelera las operaciones groupby, porque Pandas puede utilizar directamente los códigos enteros en lugar de aplicar funciones hash a las cadenas para encontrar los límites de los grupos. En DataFrames con millones de filas agrupadas por columnas de cadenas de baja cardinalidad (como región, categoría de producto o estado), convertir esas columnas a Categorical antes de usar groupby puede producir mejoras de velocidad de entre 2 y 5 veces.
import pandas as pd
import numpy as np
import timeit
np.random.seed(0)
df = pd.DataFrame({
'region': np.random.choice(['North','South','East','West'], 1000000),
'sales': np.random.randn(1000000)
})
# Object dtype groupby
t1 = timeit.timeit(lambda: df.groupby('region')['sales'].mean(), number=50)
# Categorical dtype groupby
df2 = df.copy()
df2['region'] = df2['region'].astype('category')
t2 = timeit.timeit(lambda: df2.groupby('region')['sales'].mean(), number=50)
print(f'Object dtype groupby: {t1/50*1000:.2f} ms')
print(f'Categorical groupby: {t2/50*1000:.2f} ms')
print(f'Speedup: {t1/t2:.1f}x')Usar el dtype booleano para columnas de indicadores
Las columnas binarias (True/False, 0/1, yes/no) suelen almacenarse como object o int64. Convertirlas al dtype bool utiliza solo 1 byte por valor (frente a 8 bytes para int64 o más de 50 bytes para la cadena 'yes'/'no'). Utilice astype(bool) después de asegurarse de que la columna solo contiene valores 0/1 o True/False. Las columnas booleanas también permiten filtrar más rápido, ya que Pandas puede utilizar operaciones bit a bit internamente.
import pandas as pd
import numpy as np
np.random.seed(0)
df = pd.DataFrame({
'is_premium': np.random.choice([0, 1], 1000000).astype('int64'),
'has_discount': np.random.choice(['yes', 'no'], 1000000)
})
print('Before:')
print(df.dtypes)
print(f'Memory: {df.memory_usage(deep=True).sum()/1e6:.1f} MB')
df['is_premium'] = df['is_premium'].astype(bool)
df['has_discount'] = df['has_discount'].map({'yes': True, 'no': False}).astype(bool)
print('\nAfter:')
print(df.dtypes)
print(f'Memory: {df.memory_usage(deep=True).sum()/1e6:.2f} MB')Automatizar la optimización de tipos
Escriba una función reutilizable optimise_dtypes(df) que aplique automáticamente todas las optimizaciones: reducir el tamaño de los enteros, reducir el tamaño de los números de punto flotante, convertir los objetos con pocas categorías en Categorical y convertir los enteros 0/1 en booleanos. Ejecute esta función al cargar los datos para minimizar el uso de memoria desde el principio. Así, cada miembro del equipo que cargue el mismo conjunto de datos obtendrá la versión optimizada sin tener que recordar aplicar manualmente cada paso.
import pandas as pd
import numpy as np
def optimise_dtypes(df, cat_threshold=0.5):
'''Reduce DataFrame memory by choosing smaller dtypes.'''
for col in df.columns:
col_type = df[col].dtype
if col_type == 'int64':
df[col] = pd.to_numeric(df[col], downcast='integer')
elif col_type == 'float64':
df[col] = pd.to_numeric(df[col], downcast='float')
elif col_type == 'object':
cardinality = df[col].nunique() / len(df)
if cardinality < cat_threshold:
df[col] = df[col].astype('category')
return df
# Test
np.random.seed(0)
df = pd.DataFrame({'a': np.random.randint(0,100,500000),
'b': np.random.randn(500000),
'c': np.random.choice(['X','Y','Z'],500000)})
before = df.memory_usage(deep=True).sum()
df = optimise_dtypes(df)
after = df.memory_usage(deep=True).sum()
print(f'Before: {before/1e6:.2f} MB → After: {after/1e6:.2f} MB ({before/after:.1f}x reduction)')Tipos de enteros sin signo para datos no negativos
Cuando una columna solo contiene enteros no negativos, como identificadores, recuentos o cantidades, utilice tipos de enteros sin signo: uint8 (0–255), uint16 (0–65535), uint32 (0–4 mil millones) o uint64. Los tipos sin signo tienen el mismo tamaño en bytes que sus equivalentes con signo, pero pueden almacenar valores positivos hasta el doble de grandes. Por ejemplo, un identificador de producto cuyos valores van de 0 a 60.000 cabe en uint16 (2 bytes) en lugar de int32 (4 bytes). Use astype('uint16') después de verificar que la columna no contiene valores negativos.
import pandas as pd
import numpy as np
np.random.seed(0)
df = pd.DataFrame({
'product_id': np.random.randint(0, 50000, 1000000).astype('int64'),
'quantity': np.random.randint(0, 255, 1000000).astype('int64')
})
print('Before (int64):', df.memory_usage(deep=True).sum() / 1e6, 'MB')
# product_id fits in uint16 (0-65535)
df['product_id'] = df['product_id'].astype('uint16')
# quantity fits in uint8 (0-255)
df['quantity'] = df['quantity'].astype('uint8')
print('After (uint16+uint8):', df.memory_usage(deep=True).sum() / 1e6, 'MB')
print('\nDtypes:', df.dtypes.to_dict())Comprobación de la memoria después de cada paso de optimización
Aplique las optimizaciones de una en una y compruebe la memoria después de cada paso. Esto muestra exactamente cuánto aporta cada técnica. Un DataFrame grande típico podría pasar de 2 GB (todos los tipos de datos predeterminados) a 600 MB (reducción del tamaño de los enteros), después a 300 MB (reducción del tamaño de los números de punto flotante) y finalmente a 200 MB (uso de Categorical para las columnas de texto). Documentar este desglose ayuda a justificar la complejidad adicional ante los miembros del equipo que vean tipos de datos desconocidos en el código.
import pandas as pd
import numpy as np
np.random.seed(0)
df = pd.DataFrame({
'user_id': np.random.randint(0, 99999, 1000000).astype('int64'),
'age': np.random.randint(18, 80, 1000000).astype('int64'),
'revenue': np.random.uniform(0, 5000, 1000000).astype('float64'),
'country': np.random.choice(['US','UK','DE','FR','JP'], 1000000),
'tier': np.random.choice(['free','basic','pro','enterprise'], 1000000)
})
def mem_mb(df):
return df.memory_usage(deep=True).sum() / 1e6
print(f'Start: {mem_mb(df):.1f} MB')
for c in ['user_id','age']:
df[c] = pd.to_numeric(df[c], downcast='integer')
print(f'After int downcast: {mem_mb(df):.1f} MB')
df['revenue'] = pd.to_numeric(df['revenue'], downcast='float')
print(f'After float downcast: {mem_mb(df):.1f} MB')
for c in ['country','tier']:
df[c] = df[c].astype('category')
print(f'After Categorical: {mem_mb(df):.1f} MB')Guardado y recarga de tipos optimizados
Los tipos de datos optimizados se pierden al guardar en CSV, ya que todo vuelve a convertirse en texto. Para conservarlos, guarde los datos en formato Parquet con df.to_parquet('file.parquet'); Parquet conserva los tipos int32, float32 y Categorical. Al volver a cargar el archivo con pd.read_parquet, el DataFrame tiene los mismos tipos eficientes en memoria sin necesidad de volver a ejecutar la función de optimización. Además, Parquet se carga considerablemente más rápido que CSV en archivos grandes.
import pandas as pd
import numpy as np
np.random.seed(0)
df = pd.DataFrame({
'age': np.random.randint(18, 80, 100000).astype('int8'), # already optimised
'score': np.random.randn(100000).astype('float32'),
'tier': pd.Categorical(np.random.choice(['free','pro'], 100000))
})
print('Dtypes:', df.dtypes.to_dict())
# Save to Parquet (preserves dtypes)
df.to_parquet('/tmp/optimised.parquet', index=False)
# Reload — dtypes preserved!
df_loaded = pd.read_parquet('/tmp/optimised.parquet')
print('\nLoaded dtypes:', df_loaded.dtypes.to_dict())
print(f'Memory: {df_loaded.memory_usage(deep=True).sum()/1e6:.2f} MB')Comprobación rápida
Compruebe su comprensión de los tipos de datos eficientes en memoria explicados en esta lección.
Resumen de la lección
En esta lección ha aprendido que pd.to_numeric(downcast='integer') reduce las columnas de enteros de 8 bytes a entre 1 y 4 bytes, pd.to_numeric(downcast='float') reduce a la mitad la memoria utilizada por los números de punto flotante, Categorical dtype reduce hasta 50 veces el tamaño de las columnas de texto con pocas categorías y además acelera groupby, y el formato Parquet conserva los tipos optimizados al guardar y cargar archivos. A continuación exploraremos la lectura por fragmentos: el procesamiento de archivos que superan la RAM disponible.
Preguntas frecuentes
¿La lección «Tipos de datos eficientes para reducir la memoria» es gratis?
Sí — el texto completo de «Tipos de datos eficientes para reducir la memoria» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Pandas & NumPy Academy, actualiza a CoddyKit PRO. El curso de Pandas & NumPy Academy incluye 4 lecciones en total.
¿Qué aprenderé en «Tipos de datos eficientes para reducir la memoria»?
Reduzca el tamaño de las columnas numéricas a int32/float32 y convierta las columnas de texto a Categorical para reducir hasta un 70 % la memoria del DataFrame. Practicas Pandas & NumPy Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Pandas & NumPy Academy?
No se requiere experiencia previa. Pandas & NumPy Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.
¿Cuánto tiempo toma la lección «Tipos de datos eficientes para reducir la memoria»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Pandas & NumPy Academy?
Sí. Cada lección de Pandas & NumPy Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Perfilar con timeit y memory_profiler
- Evitar iterrows y los bucles de Python
- Tipos de datos eficientes para reducir la memoria
- Lectura por bloques de archivos grandes