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
  1. Prepara el entorno
  2. Paso 1: carga y mira los datos
  3. Paso 2: explora los problemas
  4. Paso 3: limpia con código repetible
  5. Paso 4: analiza
  6. Paso 5: analiza por mes, grafica y exporta
  7. De Excel a pandas: tabla de equivalencias
  8. Errores comunes (y qué hacer en su lugar)
  9. Resumen
Lo que lograrás 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.
📥 Practica esta lección

Necesitas los dos archivos en la misma carpeta que tu notebook (o súbelos al panel Archivos de Google Colab).

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.

  1. Cargar
  2. Explorar
  3. Limpiar
  4. Analizar
  5. 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()?

  • La suma de los importes duplicados.
  • El número de filas que son copia exacta de una fila anterior.
  • El número de columnas con valores repetidos.

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
Cómputo20.701,40
Accesorios7.477,10
Audio6.736,70
Oficina3.557,00

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?

  • df[df["region"] == "Bogotá" and df["canal"] == "Web"]
  • df[(df["region"] == "Bogotá") & (df["canal"] == "Web")]
  • df[df["region"] == "Bogotá" & df["canal"] == "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?

  • Que 8 filas no encontraron su región en la tabla Regiones (regiones mal escritas).
  • Que hay 8 países distintos en los datos.
  • Que el merge eliminó 8 filas duplicadas.

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 SQLEn pandas
Quitar duplicadosdf.drop_duplicates()
ESPACIOS / Buscar y reemplazardf["col"].str.strip() / .replace({...})
FILTRAR / WHEREdf[condición] o df.query("...")
SUMAR.SI.CONJUNTO / GROUP BYdf.groupby("col")["importe"].sum()
Tabla dinámicadf.pivot_table(index=..., columns=..., values=..., aggfunc=...)
BUSCARX / JOINdf.merge(otra, on="col", how="left")
Estadística descriptivadf["col"].describe()
ExcelIdeal para explorar y compartir
Curva de aprendizaje
●●●●
Repetir el análisis
●●●●
Volumen
Hasta algo más de un millón de filas por hoja
Rápido para empezar
pandasIdeal para automatizar
Curva de aprendizaje
●●●●
Repetir el análisis
●●●●
Volumen
Millones de filas, limitado por la memoria de tu equipo
Escala y se repite

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")?

  • Ordena la tabla por país y canal.
  • Une la tabla de ventas con la de regiones.
  • Crea una tabla con países en filas, canales en columnas y la suma de importes en cada cruce.

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:

  1. Encuentra las tres combinaciones de país y categoría que más venden.
  2. 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.

🎓 Guarda tu avance y obtén tu certificado gratis

Crea tu cuenta en 30 segundos, marca tus lecciones y descarga el certificado al terminar.

Inscribirme gratis