AulaML Python · C++
Python intermedio · 50 min · Python para ciencia de datos

Pandas: Series y DataFrames

La tabla que sí recuerda qué significa cada columna, cada fila y cada celda

Pandas convierte archivos y consultas en tablas con etiquetas: cada fila tiene un índice y cada columna un nombre. Aprenderás a crear Series y DataFrames, leer un CSV, seleccionar con loc e iloc, filtrar, calcular columnas, agrupar y unir tablas sin perder de vista qué significa cada número.

Lección 2 de 5 del módulo
Al terminar esta lección podrás
  • Crear una Series y un DataFrame y explicar qué papel cumple el índice
  • Leer un CSV y revisar su forma, sus tipos y sus valores ausentes
  • Seleccionar datos por etiqueta con loc y por posición con iloc
  • Filtrar filas con máscaras booleanas y añadir columnas calculadas
  • Resumir datos con groupby y combinar tablas con merge

Antes necesitas: NumPy: vectores, matrices y vectorización · Diccionarios y sets

En esta lección
  1. ¿Qué es Pandas?
  2. ¿Para qué sirve?
  3. ¿Cómo funciona por dentro?
  4. Sintaxis: crear, leer y mirar
  5. Ejemplo básico: una columna con nombre
  6. Ejemplo intermedio: filtrar, calcular y agrupar
  7. Caso práctico: informe de ventas por región
  8. Visualización: la tabla por dentro
  9. Errores comunes
  10. Buenas prácticas
  11. Ejercicios
  12. Soluciones explicadas
  13. Resumen
  14. Evaluación

Has guardado datos en listas, diccionarios y arreglos de NumPy. Todos funcionan, pero ninguno responde a la pregunta que aparece en cuanto abres un archivo real: ¿qué columna es esta y qué significa cada fila? Un arreglo guarda los números, pero no sus nombres: si la tercera columna era de soles o de unidades lo recuerdas tú, no el programa.

Una hoja de cálculo resuelve esa duda a medias, pero no es reproducible: las fórmulas viven en celdas que nadie versiona. Pandas traslada esa comodidad a Python, con nombres, tipos y operaciones que puedes guardar y repetir.

¿Qué es Pandas?

Pandas es una biblioteca de Python para datos tabulares y series temporales. Su nombre viene de panel data, el término econométrico para observaciones repetidas en el tiempo. Se construye sobre NumPy: los valores viajan en arreglos contiguos, pero cada fila y cada columna llevan una etiqueta.

Ofrece dos estructuras. Una Series es una columna con índice: valores del mismo tipo, cada uno con su etiqueta. Un DataFrame es una tabla de columnas que comparten un índice de filas. Si una Series es una lista de precios con nombre, un DataFrame es la hoja entera.

¿Para qué sirve?

Pandas es la puerta de entrada a casi cualquier proyecto de datos: leer, unir y resumir tablas ocupa más tiempo que entrenar el modelo. Estos son los usos que verás en el curso:

  • Leer archivos: CSV, Excel, Parquet o el resultado de una consulta SQL, con tipos y fechas interpretados.
  • Limpiar: detectar nulos, duplicados y textos inconsistentes antes de calcular nada.
  • Resumir: sumar ventas por sede, contar pedidos por mes, calcular el ticket medio por producto.
  • Combinar: cruzar una tabla de pedidos con un catálogo de precios usando una columna en común.
  • Preparar Machine Learning: construir la matriz de características y la columna objetivo que recibirá scikit-learn.

¿Cómo funciona por dentro?

Una Series guarda dos cosas: un arreglo de valores y otro de etiquetas llamado índice. El índice es la dirección de cada dato: serie["té"] busca esa etiqueta y devuelve el valor; si no la encuentra, lanza KeyError.

Un DataFrame se organiza como una colección de Series que comparten el índice, agrupadas en bloques por tipo de dato. Esa agrupación explica dos sorpresas iniciales: añadir una columna no modifica el original si trabajas sobre una selección, y las operaciones se alinean por etiqueta, no por posición.

La alineación es la diferencia clave frente a NumPy. Si sumas dos Series con índices distintos, pandas empareja las etiquetas y rellena con NaN las que faltan. Es cómodo con datos completos y peligroso cuando no lo están: un NaN inesperado suele venir de un índice mal construido.

EstructuraDimensionesEtiquetasUso típico
list1Posiciones 0..n-1Secuencia heterogénea pequeña
ndarraynPosiciones y ejesCálculo numérico y álgebra
Series1Índice propioUna columna con nombre
DataFrame2Índice de filas y nombres de columnaTabla completa con tipos por columna

Sintaxis: crear, leer y mirar

La convención universal es import pandas as pd. Una Series se crea con una lista de valores y, si quieres, un índice explícito; un DataFrame, con un diccionario donde cada clave es una columna y cada valor, la lista de sus datos.

PYTHON crear.py
import pandas as pd

# Una columna con etiquetas propias
serie = pd.Series([120, 80, 45], index=["café", "té", "jugo"], name="unidades")

# Una tabla: cada clave del diccionario es una columna
df = pd.DataFrame({
    "producto": ["café", "té", "jugo"],
    "unidades": [120, 80, 45],
    "precio": [7.5, 6.0, 8.0],
})

print(serie)
print(df)
  • df.head() muestra las cinco primeras filas; df.tail(3) las tres últimas.
  • df.shape devuelve una tupla (filas, columnas); df.columns lista los nombres.
  • df.info() resume tipos y cuántos valores no nulos tiene cada columna.
  • df.describe() calcula media, desviación y cuartiles de las columnas numéricas.
  • df.dtypes muestra el tipo de cada columna: int64, float64, object o category.

Los datos reales no se escriben a mano: se leen con pd.read_csv. Si el archivo usa punto y coma, indícalo con sep=";"; si los decimales van con coma, añade decimal=","; y si hay una columna de fechas, pásala en parse_dates para que se convierta en tipo fecha.

SHELL Instalación y comprobación
pip install pandas
python -c "import pandas as pd; print(pd.__version__)"

Ejemplo básico: una columna con nombre

Empieza por lo más pequeño: una Series con unidades vendidas y el nombre del producto como índice, para pedir cada valor por su etiqueta y no por su posición.

PYTHON serie.py
import pandas as pd

serie = pd.Series([120, 80, 45], index=["café", "té", "jugo"], name="unidades")

print(serie["té"])
print(round(serie.mean(), 2))
print(serie.sum())
print(serie.idxmax())

La línea 5 devuelve 80, cuya etiqueta es "té". La 6 calcula la media de 120, 80 y 45, esto es 245 entre 3, y la redondea a 81.67. La 7 suma: 245. La 8 devuelve "café", la etiqueta del valor máximo: idxmax da el índice, no el valor, y esa distinción importa cuando quieres saber cuál fue el mejor, no cuánto vendió.

Ejemplo intermedio: filtrar, calcular y agrupar

Con un DataFrame ya puedes hacer el trabajo de un analista en cuatro líneas: añadir una columna calculada, filtrar filas y resumir por categoría.

PYTHON ventas.py
import pandas as pd

df = pd.DataFrame({
    "producto": ["café", "té", "jugo", "café", "té"],
    "sede": ["Lima", "Lima", "Arequipa", "Arequipa", "Lima"],
    "unidades": [120, 80, 45, 60, 95],
    "precio": [7.5, 6.0, 8.0, 7.5, 6.0],
})

df["ingreso"] = df["unidades"] * df["precio"]
caros = df[df["ingreso"] > 500]
por_sede = df.groupby("sede")["ingreso"].sum()

print(df.shape)
print(caros[["producto", "ingreso"]])
print(por_sede)

La línea 10 multiplica dos columnas: pandas alinea las Series por el índice y produce los ingresos 900, 480, 360, 450 y 570. Al asignar esa Series con corchetes se convierte en la quinta columna, y df.shape pasa a (5, 5).

La línea 11 construye una máscara booleana del mismo largo que la tabla: True donde el ingreso supera 500. Al indexar el DataFrame con esa máscara, obtienes solo las filas 0 y 4, con 900 y 570. La línea 12 agrupa por sede y suma: Lima aporta 900 + 480 + 570 = 1950 y Arequipa 360 + 450 = 810, que juntas dan los 2760 soles del total.

Caso práctico: informe de ventas por región

En el trabajo real los datos llegan en dos archivos: los pedidos y un catálogo de sedes con su región. El siguiente programa los une con merge, calcula el ingreso y produce una tabla ordenada lista para enviar.

PYTHON informe.py
import pandas as pd

ventas = pd.read_csv("ventas_2026.csv", parse_dates=["fecha"])
sedes = pd.DataFrame({"sede": ["Lima", "Arequipa"], "region": ["Costa", "Sierra"]})

detalle = ventas.merge(sedes, on="sede", how="left")
resumen = (
    detalle.groupby(["region", "producto"])
    .agg(unidades=("unidades", "sum"), ingreso=("ingreso", "sum"))
    .reset_index()
    .sort_values("ingreso", ascending=False)
)

print(detalle["region"].isna().sum())
print(resumen.head())

merge busca la columna sede en ambas tablas y copia la región en cada fila. Con how="left" conservas todos los pedidos, incluso los de una sede que no esté en el catálogo; en esas filas la región queda como NaN. Por eso la penúltima línea cuenta los nulos: si el número no es cero, el catálogo está incompleto. Un how="inner" habría eliminado esos pedidos en silencio, que es peor.

El encadenado final se lee de arriba abajo: agrupar por región y producto, agregar dos métricas con nombre, convertir el índice en columnas con reset_index y ordenar de mayor a menor ingreso. Cada método devuelve un DataFrame nuevo, así que los paréntesis mantienen la expresión legible.

Visualización: la tabla por dentro

La siguiente matriz representa unidades vendidas por producto y mes. Las filas son el índice del DataFrame y las columnas, sus nombres. Antes de escribir código, comprueba que sabes leer la rejilla: fila 0, columna 2 es 150, y significa que el café vendió 150 unidades en marzo.

Mapa de datos ·Unidades por producto y mes
3 × 3 rango [45, 150] La tabla tiene 3 filas y 3 columnas: df.shape devuelve (3, 3).
Ene
Feb
Mar
café
120
130
150
té
80
75
90
jugo
45
60
55

Recorre los pasos para ver cómo cambian las celdas.

loc recibe etiquetas, iloc recibe posiciones. Con el índice por defecto de 0 a 2 ambos coinciden, y ahí nace la confusión: al ordenar o filtrar, las etiquetas dejan de seguir el orden de las filas. Si dudas, pregunta qué quieres decir: «la fila donde el producto es té» es loc; «la segunda fila» es iloc.

El recorrido siguiente construye una columna y la resume. Cada paso conserva el índice: las tres filas siguen identificadas por 0, 1 y 2.

Ejecución visual ·De tres filas a un total
1 / 6
1 import pandas as pd
2
3 df = pd.DataFrame({"sede": ["Lima", "Lima", "Arequipa"], "unidades": [120, 180, 45]})
4 df["precio"] = [7.5, 7.5, 8.0]
5 df["ingreso"] = df["unidades"] * df["precio"]
6 total = df["ingreso"].sum()
7 print(total)
import pandas as pd carga la biblioteca y la deja disponible con el alias pd.
Variables
pd = módulo pandas
Salida
—

Avanza paso a paso: observa la línea resaltada, las variables y la salida.

Errores comunes

MensajePor qué ocurreCómo se arregla
KeyError: 'Ingreso'La columna se llama ingreso, en minúsculasRevisa df.columns y respeta el nombre exacto
SettingWithCopyWarningAsignaste sobre una selección, no sobre el originalUsa df.loc[mascara, "columna"] = valor
ValueError: could not convert string to floatEl CSV usa coma decimal o tiene texto en la columnaLee con decimal="," o convierte con pd.to_numeric(..., errors="coerce")
Una media devuelve nanLa columna contiene valores ausentes o textoCuenta nulos con isna().sum() y revisa dtypes
AttributeError: Can only use .str accessor...Aplicaste .str a una columna que no es de textoConvierte antes con astype("string")

El aviso más traicionero es el de asignación encadenada, porque el programa no falla: no cambia lo que esperabas. df[df["sede"] == "Lima"] devuelve una tabla nueva, y escribir sobre ella no toca df. Con copy-on-write la escritura se descarta y el error aparece tres pasos después, con los valores viejos en el informe.

Modificar una columna filtrada
Incorrecto
PYTHON No modifica df
df[df["sede"] == "Lima"]["ingreso"] = 0
Correcto
PYTHON Correcto
df.loc[df["sede"] == "Lima", "ingreso"] = 0

La primera forma selecciona filas, obtiene una copia y escribe en la copia. `loc` recibe la máscara y la columna en una sola operación, así que escribe sobre el DataFrame original y además es más rápido.

Buenas prácticas

  • Inspecciona antes de transformar: head(), info() y isna().sum() cuestan segundos y evitan horas.
  • Usa loc e iloc de forma explícita; evita el indexado con corchetes para escribir.
  • Cuando modifiques una selección, añade .copy() para dejar claro que es una tabla independiente.
  • Nombra las columnas en snake_case y sin espacios: ingreso_total, no Ingreso Total (S/).
  • Encadena operaciones en varias líneas con paréntesis; seis métodos en una línea no se revisan.
  • Guarda los pasos en funciones con nombre (cargar, limpiar, resumir) para repetir el informe completo.

Ejercicios

Ejercicio 1

Resumen de una Series

Crea una Series llamada unidades con los valores 120, 80, 45 y 95, usando como índice los productos café, té, jugo y agua. Muestra el total, la media redondeada a dos decimales, el producto que más vendió y el que menos.
Pista

idxmax e idxmin devuelven la etiqueta del máximo y del mínimo. Redondea con round(valor, 2) o con una f-string con formato.

Completar código Completa el filtro y el resumen
import pandas as pd

df = pd.read_csv("ventas.csv")
lima = df[df[""] == "Lima"]
promedio = lima["ingreso"].()
resumen = df.groupby("sede")["ingreso"].()
print(promedio, resumen)

La columna que contiene `"Lima"` es la sede. Para el promedio y el total, pandas usa los mismos nombres que NumPy.

Ejercicio 2

Nulos y total por sede

Parte de una tabla de pedidos con columnas cliente, sede y monto, donde un monto es None. Rellena ese hueco con la mediana, calcula el total por sede y muestra cuántos pedidos hay en cada una. Los montos son 120, 95, None, 210 y 130, con sedes Lima, Lima, Arequipa, Arequipa y Lima.
Pista

fillna acepta un escalar; calcula la mediana después de convertir la columna a numérica. groupby con agg(["sum", "count"]) devuelve las dos métricas a la vez.

Soluciones explicadas

El primer ejercicio no necesita bucles: cada operación recorre la columna internamente. La decisión importante está en el índice: sin index, idxmax devolvería 0, que no dice nada. Construir la etiqueta correcta desde el inicio es más barato que arreglarla después.

En el segundo, el orden importa: calculas la mediana antes de rellenar, porque después de imputar incluirías el valor inventado en la estadística. Imputar cambia la distribución y reduce la varianza artificialmente; en la próxima lección verás cómo documentar esa decisión.

Resumen

  • Una Series es una columna con índice; un DataFrame es una tabla de Series que comparten índice.
  • El índice identifica filas y permite alinear operaciones entre tablas distintas.
  • read_csv convierte un archivo en DataFrame; revisa shape, dtypes e isna().sum() al cargar.
  • loc selecciona por etiqueta y iloc por posición; con índices ordenados pueden coincidir.
  • Una máscara booleana filtra filas: df[df["ingreso"] > 500].
  • groupby separa, aplica y combina; merge cruza tablas por una clave común.
  • Asignar sobre una selección no modifica el original: usa .loc o .copy().

Evaluación

Evaluación 1

¿Qué diferencia hay entre df.loc[2] y df.iloc[2]?

Evaluación 2

Después de df["ingreso"] = df["unidades"] * df["precio"], ¿qué se guarda en la nueva columna?

Evaluación 3

Tienes pedidos de una sede que no aparece en el catálogo y usas merge(..., how="left"). ¿Qué ocurre?

Tus notas

¿Ya lo entendiste?

Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.