Lección 14 de 16 · Nivel 4: Experto
Python para análisis de datos con pandas
- ⏱ 12 min de lectura
- ❓ 4 preguntas de repaso
- ✍️ Ejercicio con solución
- 📥 Plantilla descargable
En esta lección
- Cargar y explorar un archivo de ventas con pandas.
- Automatizar la limpieza completa: duplicados, textos, fechas y valores faltantes.
- Resumir con describe, groupby, pivot_table y filtros.
- Unir tablas con merge, analizar por mes y hacer un gráfico.
- Exportar los resultados a Excel para compartirlos.
Necesitas los dos archivos en la misma carpeta que tu notebook (o súbelos al panel Archivos de Google Colab).
- ventas-practica-analisis.csv · los 200 pedidos sin limpiar
- ventas-practica-analisis.xlsx · hoja Regiones
pandas es la librería de Python más usada para analizar datos en tablas. Hace lo mismo que Excel (filtrar, limpiar, resumir, cruzar tablas), pero con código: lo escribes una vez y lo repites cada semana con un clic, sobre archivos de miles o millones de filas. En esta lección harás todo el análisis de Nexo, desde el CSV sin limpiar hasta un Excel con resultados, en unas 50 líneas. No necesitas ser programador: si entendiste las fórmulas de las lecciones anteriores, entenderás cada paso.
- Cargar
- Explorar
- Limpiar
- Analizar
- Graficar y exportar
Prepara el entorno
La forma más rápida es Google Colab: entras con tu cuenta de Google, creas un notebook y pandas ya está instalado. Sube los dos archivos con el icono de carpeta del panel izquierdo. Si prefieres trabajar en tu computadora, instala Python (o Anaconda) y usa Jupyter o VS Code; el curso de Python lo explica en qué es Python e instalación. El código de esta lección está probado con pandas 2 y 3.
Paso 1: carga y mira los datos
import pandas as pd
df = pd.read_csv("ventas-practica-analisis.csv")
print(df.shape)
print(df.head(3))
(200, 9)
id_pedido fecha region producto categoria canal unidades precio_unitario importe
0 P-0001 2025-01-03 Antioquia Silla ergonómica Oficina Web 1 189.0 189.0
1 P-0002 2025-01-03 Lima Mouse inalámbrico Accesorios Web 7 17.5 122.5
2 P-0003 2025-01-04 Lima Teclado mecánico Accesorios Marketplace 6 54.9 329.4
df es un DataFrame: una tabla con filas y columnas, como una hoja. shape dice que tiene 200 filas y 9 columnas, y head(3) muestra las tres primeras. Si tu CSV usa punto y coma y coma decimal (frecuente cuando se guarda desde Excel en español), léelo con pd.read_csv("archivo.csv", sep=";", decimal=",").
Paso 2: explora los problemas
print(df.isna().sum()) # vacíos por columna
print(df.duplicated().sum()) # filas duplicadas
print(df["region"].nunique()) # regiones distintas
print(df["region"].value_counts().tail(6)) # las menos frecuentes
id_pedido 0
fecha 0
region 0
producto 0
categoria 0
canal 0
unidades 0
precio_unitario 0
importe 4
dtype: int64
3
15
region
CDMX 2
lima 2
antioquia 1
Lima 1 ← en realidad es "Lima " (con un espacio al final)
Bogota 1
Nuevo Leon 1
Name: count, dtype: int64
En cuatro líneas aparecen los mismos problemas que encontraste a mano en la lección 5: 4 importes vacíos, 3 filas duplicadas y 15 regiones donde debería haber 9. Las variantes mal escritas son las menos frecuentes, por eso tail() las muestra.
1. ¿Qué devuelve df.duplicated().sum()?
duplicated() marca con True cada fila idéntica a otra que ya apareció antes; sum() cuenta los True. Aquí da 3, las segundas apariciones de P-0026, P-0071 y P-0136.
Paso 3: limpia con código repetible
# 1. Duplicados
df = df.drop_duplicates()
# 2. Regiones: espacios y variantes
df["region"] = df["region"].str.strip()
correcciones = {
"lima": "Lima",
"CDMX": "Ciudad de México",
"Bogota": "Bogotá",
"Nuevo Leon": "Nuevo León",
"antioquia": "Antioquia",
}
df["region"] = df["region"].replace(correcciones)
# 3. Fechas: dos formatos en la misma columna
iso = pd.to_datetime(df["fecha"], format="%Y-%m-%d", errors="coerce")
dma = pd.to_datetime(df["fecha"], format="%d/%m/%Y", errors="coerce")
df["fecha"] = iso.fillna(dma)
# 4. Importes vacíos: se recalculan
calculado = (df["unidades"] * df["precio_unitario"]).round(2)
df["importe"] = df["importe"].fillna(calculado)
df = df.reset_index(drop=True)
# 5. Validación: si algo falla, assert detiene el programa
assert df["id_pedido"].is_unique
assert ((df["importe"] - df["unidades"] * df["precio_unitario"]).abs() < 0.01).all()
print(df.shape, df["region"].nunique(), df.isna().sum().sum())
print(round(df["importe"].sum(), 2))
(197, 9) 9 0
38472.2
Fíjate en el truco de las fechas: errors="coerce" convierte en vacío (NaT) lo que no encaja con el formato. La primera conversión entiende las 196 fechas ISO; la segunda, la fecha «13/08/2025»; fillna combina ambas. Las dos líneas assert son tu red de seguridad: si la semana que viene llega un archivo con un pedido repetido, el código se detiene en lugar de darte un total equivocado. El resultado coincide al centavo con la hoja Ventas_limpias.
Paso 4: analiza
Estadística descriptiva en una línea
print(df["importe"].describe().round(2))
count 197.00
mean 195.29
std 225.98
min 17.50
25% 52.50
50% 119.80
75% 269.40
max 1298.00
Name: importe, dtype: float64
Son las mismas cifras de la lección 6: media 195,29, mediana 119,80 y desviación estándar muestral 225,98.
Agrupar: el equivalente a SUMAR.SI.CONJUNTO y GROUP BY
resumen = (
df.groupby("categoria")
.agg(pedidos=("id_pedido", "count"),
unidades=("unidades", "sum"),
ventas=("importe", "sum"),
ticket=("importe", "mean"))
.sort_values("ventas", ascending=False)
.round(2)
)
print(resumen)
pedidos unidades ventas ticket
categoria
Cómputo 44 71 20701.4 470.49
Accesorios 79 241 7477.1 94.65
Audio 50 133 6736.7 134.73
Oficina 24 53 3557.0 148.21
La columna ventas del resultado, como % del total: Cómputo 53,8%, Accesorios 19,4%, Audio 17,5% y Oficina 9,2%.
Filtrar: el equivalente a FILTRAR y WHERE
grandes = df[(df["importe"] >= 600) & (df["canal"] == "Web")]
print(len(grandes), grandes["importe"].sum())
print(df.query("region == 'Lima' and canal == 'Web'")["importe"].sum())
5 5026.0
3658.0
Hay 5 pedidos web de 600 USD o más, que suman 5.026,00, y las ventas web de Lima son 3.658,00 (el mismo número que con SUMAR.SI.CONJUNTO y con SQL). Dentro de los corchetes, cada condición va entre paréntesis y se unen con & (y) o | (o).
2. ¿Cuál es la forma correcta de filtrar pedidos de Bogotá por la web?
Con columnas de pandas se usa &, no and, y cada comparación entre paréntesis porque & se evalúa antes que ==. La primera da un error («the truth value of a Series is ambiguous») y la tercera, otro error o un resultado incorrecto.
Unir tablas y cruzar: merge y pivot_table
merge es el JOIN de pandas (o el BUSCARX aplicado a toda la columna). pivot_table es la tabla dinámica:
regiones = pd.read_excel("ventas-practica-analisis.xlsx", sheet_name="Regiones")
df = df.merge(regiones, on="region", how="left")
print(df["pais"].isna().sum()) # 0: todas las regiones encontraron su país
tabla = df.pivot_table(index="pais", columns="canal", values="importe",
aggfunc="sum", margins=True, margins_name="Total").round(2)
print(tabla)
0
canal Marketplace Tienda física Web Total
pais
Colombia 1267.9 3057.5 3836.4 8161.8
México 5566.1 4130.0 5878.6 15574.7
Perú 4896.3 1695.5 8143.9 14735.7
Total 11730.3 8883.0 17858.9 38472.2
Comprobar isna().sum() después de un merge con how="left" es el equivalente al LEFT JOIN + IS NULL de SQL: si hubieras hecho el merge antes de limpiar, habría dado 8.
3. Haces el merge con la tabla sin limpiar y df["pais"].isna().sum() devuelve 8. ¿Qué significa?
Con how="left" se conservan todas las filas de ventas; las que no encuentran pareja quedan con país vacío. pandas distingue mayúsculas y espacios, así que «lima», «Lima » o «CDMX» no coinciden.
Paso 5: analiza por mes, grafica y exporta
mensual = df.groupby(df["fecha"].dt.to_period("M"))["importe"].sum()
print(mensual)
print(mensual.pct_change().round(3).tail(3)) # variación vs. mes anterior
fecha
2025-01 2307.0
2025-02 1481.1
2025-03 2345.7
2025-04 1729.6
2025-05 2991.0
2025-06 2265.2
2025-07 2576.2
2025-08 3828.6
2025-09 3260.8
2025-10 4798.8
2025-11 4967.8
2025-12 5920.4
Freq: M, Name: importe, dtype: float64
fecha
2025-10 0.472
2025-11 0.035
2025-12 0.192
Freq: M, Name: importe, dtype: float64
Octubre creció 47,2% frente a septiembre y diciembre 19,2% frente a noviembre. Para el gráfico de líneas, matplotlib se integra con pandas:
import matplotlib.pyplot as plt
ax = mensual.plot(kind="line", marker="o", title="Ventas mensuales 2025 (USD)")
ax.set_xlabel("Mes")
ax.set_ylabel("USD")
plt.tight_layout()
plt.savefig("ventas_mensuales.png", dpi=150)
La matriz de correlaciones de la lección 13 también sale en una línea, y con ExcelWriter guardas varios resultados en un solo archivo para compartirlo con quien no usa Python:
print(df[["unidades", "precio_unitario", "importe"]].corr().round(2))
with pd.ExcelWriter("resultados_nexo.xlsx") as writer:
resumen.to_excel(writer, sheet_name="Por categoria")
tabla.to_excel(writer, sheet_name="Pais x canal")
unidades precio_unitario importe
unidades 1.00 -0.25 0.10
precio_unitario -0.25 1.00 0.83
importe 0.10 0.83 1.00
Para ir más lejos con pandas y matplotlib, sigue con las lecciones de análisis de datos con pandas y gráficos con matplotlib del curso de Python.
De Excel a pandas: tabla de equivalencias
| En Excel o SQL | En pandas |
|---|---|
| Quitar duplicados | df.drop_duplicates() |
| ESPACIOS / Buscar y reemplazar | df["col"].str.strip() / .replace({...}) |
| FILTRAR / WHERE | df[condición] o df.query("...") |
| SUMAR.SI.CONJUNTO / GROUP BY | df.groupby("col")["importe"].sum() |
| Tabla dinámica | df.pivot_table(index=..., columns=..., values=..., aggfunc=...) |
| BUSCARX / JOIN | df.merge(otra, on="col", how="left") |
| Estadística descriptiva | df["col"].describe() |
- Curva de aprendizaje
- ●●●●
- Repetir el análisis
- ●●●●
- Volumen
- Hasta algo más de un millón de filas por hoja
- Curva de aprendizaje
- ●●●●
- Repetir el análisis
- ●●●●
- Volumen
- Millones de filas, limitado por la memoria de tu equipo
Errores comunes (y qué hacer en su lugar)
Usar and / or en filtros de columnas.
Usa & / | y paréntesis en cada condición.
Leer un CSV con ; y obtener una sola columna.
Indica sep=";" y, si hay coma decimal, decimal=",".
Limpiar con celdas sueltas ejecutadas en desorden.
Ejecuta el notebook de arriba abajo (Reiniciar y ejecutar todo) antes de entregar.
Confiar en un merge sin revisar vacíos ni el número de filas.
Compara len(df) antes y después y revisa isna().sum().
4. ¿Qué hace df.pivot_table(index="pais", columns="canal", values="importe", aggfunc="sum")?
Es la tabla dinámica de pandas. Con margins=True agrega totales de fila y columna, como en Excel.
Ejercicio práctico
Con el DataFrame limpio y con la columna país:
- Encuentra las tres combinaciones de país y categoría que más venden.
- Calcula el porcentaje acumulado de ventas por producto (de mayor a menor) y responde: ¿cuántos productos hacen falta para llegar al 80% de las ventas?
Ver solución
top3 = (df.groupby(["pais", "categoria"])["importe"].sum()
.reset_index()
.sort_values("importe", ascending=False)
.head(3))
print(top3)
productos = df.groupby("producto")["importe"].sum().sort_values(ascending=False)
acumulado = (productos.cumsum() / productos.sum() * 100).round(1)
print(acumulado)
pais categoria importe
6 México Cómputo 8634.2
10 Perú Cómputo 8132.5
2 Colombia Cómputo 3934.7
producto
Laptop 14 pulgadas 23.6
Tablet 10 pulgadas 38.8
Monitor 24 pulgadas 53.8
Audífonos Bluetooth 63.4
Teclado mecánico 72.2
Parlante portátil 80.2
Silla ergonómica 86.6
Mouse inalámbrico 92.8
Mochila para laptop 97.1
Lámpara LED de escritorio 100.0
Name: importe, dtype: float64
Las tres combinaciones son Cómputo en cada país. Hacen falta 6 de los 10 productos para llegar al 80% (80,2% con el parlante). Este análisis de Pareto es la base del proyecto de la próxima lección.
Resumen
- pandas trabaja con DataFrames: tablas que se cargan con read_csv o read_excel.
- isna, duplicated, nunique y value_counts revelan los problemas en segundos.
- La limpieza en código es repetible y se protege con assert.
- groupby, filtros, merge y pivot_table equivalen a SUMAR.SI.CONJUNTO, FILTRAR, BUSCARX y la tabla dinámica.
- Con to_period, plot y ExcelWriter analizas por mes, graficas y compartes resultados.
En la próxima lección: Proyecto de portafolio: analiza un negocio de principio a fin. Unirás todo lo aprendido en un proyecto completo que podrás mostrar a empleadores.
Crea tu cuenta en 30 segundos, marca tus lecciones y descarga el certificado al terminar.