import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Configuración de visualización
sns.set_style('whitegrid')
plt.rcParams['figure.figsize'] = (10, 6)
# Para reproducibilidad
np.random.seed(42)Operaciones de Datos con Pandas
Este notebook cubre operaciones fundamentales de manipulación de datos con pandas:
- GroupBy y Agregaciones: Cómo agrupar datos y calcular estadísticas resumidas
- Joins (Uniones): Los diferentes tipos de joins y cuándo usar cada uno
- Creación de Variables: Cómo crear nuevas columnas derivadas
Configuración Inicial
1. Creación de Datasets de Ejemplo
Crearemos datasets de ejemplo que representan ventas de una tienda online.
# Dataset de ventas
ventas = pd.DataFrame({
'id_venta': range(1, 21),
'id_cliente': [101, 102, 103, 101, 104, 105, 102, 106, 103, 107,
101, 108, 104, 109, 105, 102, 110, 103, 106, 101],
'id_producto': ['P1', 'P2', 'P1', 'P3', 'P2', 'P1', 'P3', 'P2', 'P1', 'P3',
'P2', 'P1', 'P3', 'P2', 'P3', 'P1', 'P2', 'P3', 'P1', 'P2'],
'cantidad': np.random.randint(1, 10, 20),
'precio_unitario': np.random.uniform(10, 100, 20).round(2),
'fecha': pd.date_range('2024-01-01', periods=20, freq='D'),
'region': np.random.choice(['Norte', 'Sur', 'Este', 'Oeste'], 20)
})
print("Dataset de Ventas:")
print(ventas.head(10))
print(f"\nDimensiones: {ventas.shape}")# Dataset de clientes
clientes = pd.DataFrame({
'id_cliente': [101, 102, 103, 104, 105, 106, 107, 108, 109, 110],
'nombre': ['Ana García', 'Carlos López', 'María Rodríguez', 'Juan Pérez',
'Laura Martínez', 'Pedro Sánchez', 'Isabel Torres', 'Miguel Ruiz',
'Carmen Díaz', 'Francisco Moreno'],
'edad': [28, 35, 42, 31, 26, 45, 38, 29, 33, 41],
'ciudad': ['CDMX', 'Monterrey', 'Guadalajara', 'CDMX', 'Puebla',
'CDMX', 'Monterrey', 'Guadalajara', 'CDMX', 'Puebla'],
'tipo_cliente': ['Premium', 'Regular', 'Premium', 'Regular', 'Premium',
'Regular', 'Premium', 'Regular', 'Premium', 'Regular']
})
print("Dataset de Clientes:")
print(clientes)# Dataset de productos
productos = pd.DataFrame({
'id_producto': ['P1', 'P2', 'P3'],
'nombre_producto': ['Laptop', 'Mouse', 'Teclado'],
'categoria': ['Computadoras', 'Accesorios', 'Accesorios'],
'costo_produccion': [400.0, 5.0, 15.0]
})
print("Dataset de Productos:")
print(productos)2. GroupBy y Agregaciones
El método groupby() es una de las operaciones más poderosas en pandas. Permite: 1. Dividir los datos en grupos basados en algún criterio 2. Aplicar una función a cada grupo 3. Combinar los resultados en una estructura de datos
Este patrón se conoce como split-apply-combine.
2.1 GroupBy Simple con Una Columna
# Agrupar por región y calcular el total de ventas
ventas['total'] = ventas['cantidad'] * ventas['precio_unitario']
ventas_por_region = ventas.groupby('region')['total'].sum()
print("Total de ventas por región:")
print(ventas_por_region)
print(f"\nTipo de objeto: {type(ventas_por_region)}")2.2 Múltiples Agregaciones con agg()
# Aplicar múltiples funciones de agregación a una columna
estadisticas_region = ventas.groupby('region')['total'].agg([
'sum', # Suma total
'mean', # Promedio
'count', # Cantidad de ventas
'min', # Venta mínima
'max', # Venta máxima
'std' # Desviación estándar
])
print("Estadísticas de ventas por región:")
print(estadisticas_region.round(2))2.3 Agregaciones en Múltiples Columnas
# Aplicar diferentes funciones a diferentes columnas
agregaciones = ventas.groupby('region').agg({
'total': ['sum', 'mean'],
'cantidad': ['sum', 'mean'],
'id_venta': 'count' # Contar número de transacciones
})
print("Agregaciones múltiples por región:")
print(agregaciones.round(2))2.4 GroupBy con Múltiples Columnas
# Agrupar por múltiples columnas
ventas_region_producto = ventas.groupby(['region', 'id_producto']).agg({
'total': 'sum',
'cantidad': 'sum',
'id_venta': 'count'
}).rename(columns={'id_venta': 'num_transacciones'})
print("Ventas por región y producto:")
print(ventas_region_producto.round(2))# Resetear el índice para tener un DataFrame plano
ventas_region_producto_flat = ventas_region_producto.reset_index()
print("\nDataFrame con índice reseteado:")
print(ventas_region_producto_flat)2.5 Funciones Personalizadas con apply()
# Definir una función personalizada
def rango_precios(x):
"""Calcula el rango entre el precio máximo y mínimo"""
return x.max() - x.min()
# Aplicar función personalizada
rangos = ventas.groupby('region')['precio_unitario'].apply(rango_precios)
print("Rango de precios por región:")
print(rangos.round(2))2.6 Transform: Mantener la Forma Original
# Transform devuelve un objeto del mismo tamaño que el input
# Útil para agregar columnas con estadísticas del grupo
ventas['promedio_region'] = ventas.groupby('region')['total'].transform('mean')
ventas['desviacion_promedio'] = ventas['total'] - ventas['promedio_region']
print("Ventas con promedio de región:")
print(ventas[['id_venta', 'region', 'total', 'promedio_region', 'desviacion_promedio']].head(10))2.7 Visualización de Agregaciones
# Crear visualización de ventas por región
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Gráfico de barras
ventas_por_region.sort_values(ascending=False).plot(
kind='bar',
ax=axes[0],
color='steelblue'
)
axes[0].set_title('Total de Ventas por Región')
axes[0].set_ylabel('Ventas Totales')
axes[0].set_xlabel('Región')
# Gráfico de pie
ventas_por_region.plot(
kind='pie',
ax=axes[1],
autopct='%1.1f%%'
)
axes[1].set_title('Distribución de Ventas por Región')
axes[1].set_ylabel('')
plt.tight_layout()
plt.show()3. Joins (Uniones de DataFrames)
Los joins permiten combinar dos DataFrames basándose en una o más columnas comunes (llaves). Pandas ofrece varios tipos de joins:
- Inner Join: Solo mantiene las filas que tienen coincidencias en ambos DataFrames
- Left Join: Mantiene todas las filas del DataFrame izquierdo
- Right Join: Mantiene todas las filas del DataFrame derecho
- Outer Join: Mantiene todas las filas de ambos DataFrames
3.1 Inner Join (Intersección)
# Inner join: Solo mantiene clientes que tienen ventas
ventas_con_clientes = pd.merge(
ventas,
clientes,
on='id_cliente',
how='inner'
)
print("Inner Join - Ventas con información de clientes:")
print(ventas_con_clientes[['id_venta', 'id_cliente', 'nombre', 'ciudad', 'total']].head(10))
print(f"\nNúmero de filas: {len(ventas_con_clientes)}")3.2 Left Join (Todas las filas del izquierdo)
# Crear un cliente adicional sin ventas para demostrar
clientes_extended = pd.concat([
clientes,
pd.DataFrame({
'id_cliente': [111, 112],
'nombre': ['Roberto Silva', 'Andrea Vargas'],
'edad': [30, 27],
'ciudad': ['CDMX', 'Monterrey'],
'tipo_cliente': ['Regular', 'Premium']
})
], ignore_index=True)
# Left join: Mantiene todos los clientes, incluso sin ventas
clientes_con_ventas = pd.merge(
clientes_extended,
ventas,
on='id_cliente',
how='left'
)
print("Left Join - Todos los clientes con sus ventas (si existen):")
print(clientes_con_ventas[['id_cliente', 'nombre', 'id_venta', 'total']].tail(15))
print(f"\nNúmero de filas: {len(clientes_con_ventas)}")
# Identificar clientes sin ventas
clientes_sin_ventas = clientes_con_ventas[clientes_con_ventas['id_venta'].isna()]['nombre'].unique()
print(f"\nClientes sin ventas: {list(clientes_sin_ventas)}")3.3 Right Join (Todas las filas del derecho)
# Right join: Equivalente a left join con orden invertido
ventas_con_info = pd.merge(
clientes_extended,
ventas,
on='id_cliente',
how='right' # Mantiene todas las ventas
)
print("Right Join - Todas las ventas con información de cliente:")
print(ventas_con_info[['id_venta', 'id_cliente', 'nombre', 'total']].head(10))
print(f"\nNúmero de filas: {len(ventas_con_info)}")3.4 Outer Join (Unión completa)
# Outer join: Mantiene todas las filas de ambos DataFrames
union_completa = pd.merge(
clientes_extended,
ventas,
on='id_cliente',
how='outer'
)
print("Outer Join - Todos los clientes y todas las ventas:")
print(union_completa[['id_cliente', 'nombre', 'id_venta', 'total']].tail(15))
print(f"\nNúmero de filas: {len(union_completa)}")3.5 Join con Múltiples DataFrames
# Combinar ventas, clientes y productos
analisis_completo = pd.merge(
ventas,
clientes,
on='id_cliente',
how='inner'
)
analisis_completo = pd.merge(
analisis_completo,
productos,
on='id_producto',
how='inner'
)
print("Análisis completo con tres DataFrames:")
print(analisis_completo[[
'id_venta', 'nombre', 'ciudad', 'tipo_cliente',
'nombre_producto', 'categoria', 'cantidad', 'total'
]].head(10))3.6 Join con Nombres de Columnas Diferentes
# Crear un DataFrame con nombres de columna diferentes
descuentos = pd.DataFrame({
'cliente_id': [101, 102, 103, 104, 105], # Nombre diferente
'descuento': [0.1, 0.05, 0.15, 0.0, 0.2]
})
# Usar left_on y right_on para columnas con nombres diferentes
ventas_con_descuento = pd.merge(
ventas,
descuentos,
left_on='id_cliente',
right_on='cliente_id',
how='left'
)
# Rellenar descuentos faltantes con 0
ventas_con_descuento['descuento'] = ventas_con_descuento['descuento'].fillna(0)
print("Ventas con descuentos aplicables:")
print(ventas_con_descuento[['id_venta', 'id_cliente', 'total', 'descuento']].head(10))3.7 Visualización de Tipos de Joins
# Comparar el número de filas según tipo de join
join_types = ['inner', 'left', 'right', 'outer']
num_filas = []
for join_type in join_types:
resultado = pd.merge(clientes_extended, ventas, on='id_cliente', how=join_type)
num_filas.append(len(resultado))
comparacion_joins = pd.DataFrame({
'Tipo de Join': join_types,
'Número de Filas': num_filas
})
print("Comparación de tipos de join:")
print(comparacion_joins)
# Visualizar
plt.figure(figsize=(10, 6))
plt.bar(comparacion_joins['Tipo de Join'], comparacion_joins['Número de Filas'], color='teal')
plt.title('Número de Filas Resultantes por Tipo de Join')
plt.ylabel('Número de Filas')
plt.xlabel('Tipo de Join')
plt.show()4. Creación de Nuevas Variables
Crear nuevas variables (columnas) es una parte fundamental del análisis de datos. Permite: - Calcular métricas derivadas - Transformar variables existentes - Crear categorías o segmentos - Generar features para modelos de machine learning
4.1 Variables Aritméticas Simples
# Calcular margen de ganancia
analisis_completo['margen'] = analisis_completo['precio_unitario'] - analisis_completo['costo_produccion']
analisis_completo['margen_porcentual'] = (
analisis_completo['margen'] / analisis_completo['precio_unitario'] * 100
)
# Ganancia total por venta
analisis_completo['ganancia_total'] = analisis_completo['margen'] * analisis_completo['cantidad']
print("Variables de ganancia:")
print(analisis_completo[[
'id_venta', 'nombre_producto', 'precio_unitario',
'costo_produccion', 'margen', 'margen_porcentual', 'ganancia_total'
]].head(10).round(2))4.2 Variables Categóricas con Condiciones
# Clasificar ventas por tamaño
def clasificar_venta(total):
if total < 100:
return 'Pequeña'
elif total < 300:
return 'Mediana'
else:
return 'Grande'
analisis_completo['tamaño_venta'] = analisis_completo['total'].apply(clasificar_venta)
# Alternativamente, usar pd.cut para binning
analisis_completo['categoria_total'] = pd.cut(
analisis_completo['total'],
bins=[0, 100, 300, float('inf')],
labels=['Bajo', 'Medio', 'Alto']
)
print("Variables categóricas:")
print(analisis_completo[['id_venta', 'total', 'tamaño_venta', 'categoria_total']].head(10))4.3 Variables con Condicionales (np.where)
# Identificar si el cliente es de CDMX
analisis_completo['es_cdmx'] = np.where(
analisis_completo['ciudad'] == 'CDMX',
'Sí',
'No'
)
# Cliente joven (menor de 30 años)
analisis_completo['cliente_joven'] = np.where(
analisis_completo['edad'] < 30,
True,
False
)
# Venta de alto valor con descuento
analisis_completo = pd.merge(
analisis_completo,
descuentos,
left_on='id_cliente',
right_on='cliente_id',
how='left'
)
analisis_completo['descuento'] = analisis_completo['descuento'].fillna(0)
analisis_completo['venta_premium'] = np.where(
(analisis_completo['total'] > 200) & (analisis_completo['descuento'] > 0),
'Premium con descuento',
'Estándar'
)
print("Variables condicionales:")
print(analisis_completo[[
'id_venta', 'nombre', 'ciudad', 'es_cdmx',
'edad', 'cliente_joven', 'total', 'venta_premium'
]].head(10))4.4 Variables Temporales (Date Features)
# Extraer componentes de fecha
analisis_completo['año'] = analisis_completo['fecha'].dt.year
analisis_completo['mes'] = analisis_completo['fecha'].dt.month
analisis_completo['dia'] = analisis_completo['fecha'].dt.day
analisis_completo['dia_semana'] = analisis_completo['fecha'].dt.day_name()
analisis_completo['trimestre'] = analisis_completo['fecha'].dt.quarter
analisis_completo['es_fin_semana'] = analisis_completo['fecha'].dt.dayofweek >= 5
print("Variables temporales:")
print(analisis_completo[[
'id_venta', 'fecha', 'año', 'mes', 'dia',
'dia_semana', 'trimestre', 'es_fin_semana'
]].head(10))4.5 Variables de Texto (String Operations)
# Extraer primer nombre
analisis_completo['primer_nombre'] = analisis_completo['nombre'].str.split().str[0]
# Crear código de cliente (iniciales + edad)
analisis_completo['codigo_cliente'] = (
analisis_completo['nombre'].str[:3].str.upper() +
'_' +
analisis_completo['edad'].astype(str)
)
# Longitud del nombre
analisis_completo['longitud_nombre'] = analisis_completo['nombre'].str.len()
print("Variables de texto:")
print(analisis_completo[[
'id_venta', 'nombre', 'primer_nombre',
'codigo_cliente', 'longitud_nombre'
]].head(10))4.6 Variables Agregadas (Rolling y Cumulative)
# Ordenar por cliente y fecha
analisis_completo = analisis_completo.sort_values(['id_cliente', 'fecha'])
# Total acumulado por cliente
analisis_completo['total_acumulado'] = analisis_completo.groupby('id_cliente')['total'].cumsum()
# Número de compra del cliente
analisis_completo['num_compra'] = analisis_completo.groupby('id_cliente').cumcount() + 1
# Promedio móvil de las últimas 2 compras (por cliente)
analisis_completo['promedio_movil_2'] = (
analisis_completo.groupby('id_cliente')['total']
.rolling(window=2, min_periods=1)
.mean()
.reset_index(level=0, drop=True)
)
print("Variables agregadas por cliente:")
print(analisis_completo[[
'id_cliente', 'nombre', 'fecha', 'total',
'num_compra', 'total_acumulado', 'promedio_movil_2'
]].head(15).round(2))4.7 Variables de Ranking
# Ranking de ventas (mayor a menor)
analisis_completo['ranking_venta'] = analisis_completo['total'].rank(ascending=False, method='dense')
# Ranking por región
analisis_completo['ranking_en_region'] = (
analisis_completo.groupby('region')['total']
.rank(ascending=False, method='dense')
)
# Percentil
analisis_completo['percentil_venta'] = (
analisis_completo['total'].rank(pct=True) * 100
).round(1)
print("Variables de ranking:")
print(analisis_completo[[
'id_venta', 'region', 'total',
'ranking_venta', 'ranking_en_region', 'percentil_venta'
]].sort_values('ranking_venta').head(10))4.8 One-Hot Encoding (Variables Dummy)
# Crear variables dummy para categorías
region_dummies = pd.get_dummies(analisis_completo['region'], prefix='region')
tipo_cliente_dummies = pd.get_dummies(analisis_completo['tipo_cliente'], prefix='tipo')
# Concatenar con el DataFrame original
analisis_con_dummies = pd.concat([
analisis_completo[['id_venta', 'total', 'region', 'tipo_cliente']],
region_dummies,
tipo_cliente_dummies
], axis=1)
print("Variables dummy (one-hot encoding):")
print(analisis_con_dummies.head(10))5. Casos de Uso Combinados
Combinemos groupby, joins y creación de variables en análisis realistas.
5.1 Análisis de Valor del Cliente (Customer Lifetime Value)
# Calcular métricas por cliente
metricas_cliente = analisis_completo.groupby('id_cliente').agg({
'total': ['sum', 'mean', 'count'],
'ganancia_total': 'sum',
'fecha': ['min', 'max']
})
# Aplanar columnas multi-nivel
metricas_cliente.columns = ['_'.join(col).strip() for col in metricas_cliente.columns.values]
metricas_cliente = metricas_cliente.reset_index()
# Renombrar columnas
metricas_cliente.columns = [
'id_cliente', 'total_ventas', 'promedio_venta', 'num_compras',
'ganancia_total', 'primera_compra', 'ultima_compra'
]
# Calcular días como cliente
metricas_cliente['dias_como_cliente'] = (
metricas_cliente['ultima_compra'] - metricas_cliente['primera_compra']
).dt.days + 1
# Frecuencia de compra
metricas_cliente['frecuencia_compra'] = (
metricas_cliente['num_compras'] / metricas_cliente['dias_como_cliente']
).round(3)
# Agregar información del cliente
metricas_cliente = pd.merge(
metricas_cliente,
clientes[['id_cliente', 'nombre', 'tipo_cliente']],
on='id_cliente'
)
# Clasificar clientes por valor
metricas_cliente['segmento_valor'] = pd.qcut(
metricas_cliente['total_ventas'],
q=3,
labels=['Bajo', 'Medio', 'Alto']
)
print("Análisis de Valor del Cliente:")
print(metricas_cliente.round(2))# Visualizar segmentos de clientes
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Total de ventas por cliente
metricas_cliente.sort_values('total_ventas', ascending=False).head(10).plot(
x='nombre',
y='total_ventas',
kind='barh',
ax=axes[0, 0],
color='steelblue',
legend=False
)
axes[0, 0].set_title('Top 10 Clientes por Ventas Totales')
axes[0, 0].set_xlabel('Total de Ventas')
# Número de compras vs promedio de venta
axes[0, 1].scatter(
metricas_cliente['num_compras'],
metricas_cliente['promedio_venta'],
c=metricas_cliente['total_ventas'],
cmap='viridis',
s=100,
alpha=0.6
)
axes[0, 1].set_title('Número de Compras vs Promedio de Venta')
axes[0, 1].set_xlabel('Número de Compras')
axes[0, 1].set_ylabel('Promedio de Venta')
# Distribución por segmento de valor
metricas_cliente['segmento_valor'].value_counts().plot(
kind='pie',
ax=axes[1, 0],
autopct='%1.1f%%'
)
axes[1, 0].set_title('Distribución por Segmento de Valor')
axes[1, 0].set_ylabel('')
# Comparación Premium vs Regular
comparacion_tipo = metricas_cliente.groupby('tipo_cliente')['total_ventas'].mean()
comparacion_tipo.plot(kind='bar', ax=axes[1, 1], color=['coral', 'skyblue'])
axes[1, 1].set_title('Promedio de Ventas: Premium vs Regular')
axes[1, 1].set_ylabel('Promedio de Ventas')
axes[1, 1].set_xlabel('Tipo de Cliente')
axes[1, 1].tick_params(axis='x', rotation=0)
plt.tight_layout()
plt.show()5.2 Análisis de Productos más Rentables por Región
# Análisis de rentabilidad por producto y región
rentabilidad = analisis_completo.groupby(['region', 'nombre_producto']).agg({
'ganancia_total': 'sum',
'total': 'sum',
'cantidad': 'sum',
'id_venta': 'count'
}).reset_index()
rentabilidad.columns = [
'region', 'producto', 'ganancia', 'ventas', 'unidades', 'transacciones'
]
# ROI por producto y región
rentabilidad['roi_porcentaje'] = (rentabilidad['ganancia'] / rentabilidad['ventas'] * 100).round(2)
# Producto más vendido por región
idx_top = rentabilidad.groupby('region')['ganancia'].idxmax()
top_productos = rentabilidad.loc[idx_top]
print("Producto más rentable por región:")
print(top_productos.round(2))
# Crear pivot table
pivot_ganancia = rentabilidad.pivot_table(
values='ganancia',
index='region',
columns='producto',
aggfunc='sum',
fill_value=0
)
print("\nGanancia por Región y Producto:")
print(pivot_ganancia.round(2))# Visualizar heatmap de rentabilidad
plt.figure(figsize=(10, 6))
sns.heatmap(
pivot_ganancia,
annot=True,
fmt='.0f',
cmap='YlGnBu',
cbar_kws={'label': 'Ganancia Total'}
)
plt.title('Mapa de Calor: Ganancia por Región y Producto')
plt.xlabel('Producto')
plt.ylabel('Región')
plt.tight_layout()
plt.show()Resumen
En este notebook aprendimos:
GroupBy y Agregaciones
groupby()con una o múltiples columnas- Funciones de agregación:
sum(),mean(),count(),min(),max(),std() agg()para aplicar múltiples funcionestransform()para mantener la forma originalapply()para funciones personalizadas
Joins
- Inner Join: Intersección de ambos DataFrames
- Left Join: Todas las filas del DataFrame izquierdo
- Right Join: Todas las filas del DataFrame derecho
- Outer Join: Unión de ambos DataFrames
- Uso de
left_onyright_onpara columnas con nombres diferentes - Joins múltiples encadenados
Creación de Variables
- Variables aritméticas simples
- Variables categóricas con
apply(),np.where(), ypd.cut() - Variables temporales con
.dtaccessor - Variables de texto con
.straccessor - Variables agregadas:
cumsum(),cumcount(),rolling() - Variables de ranking con
rank() - One-hot encoding con
get_dummies()
Mejores Prácticas
- Siempre inspecciona tus datos antes y después de las operaciones
- Usa
reset_index()cuando necesites convertir índices en columnas - Maneja valores nulos apropiadamente con
fillna(),dropna() - Documenta tus transformaciones para reproducibilidad
- Visualiza tus resultados para validar las operaciones
Estas operaciones son fundamentales para el análisis exploratorio de datos y la ingeniería de features en machine learning.