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.
- 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
- ¿Qué es Pandas?
- ¿Para qué sirve?
- ¿Cómo funciona por dentro?
- Sintaxis: crear, leer y mirar
- Ejemplo básico: una columna con nombre
- Ejemplo intermedio: filtrar, calcular y agrupar
- Caso práctico: informe de ventas por región
- Visualización: la tabla por dentro
- Errores comunes
- Buenas prácticas
- Ejercicios
- Soluciones explicadas
- Resumen
- 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.
| Estructura | Dimensiones | Etiquetas | Uso típico |
|---|---|---|---|
list | 1 | Posiciones 0..n-1 | Secuencia heterogénea pequeña |
ndarray | n | Posiciones y ejes | Cálculo numérico y álgebra |
Series | 1 | Índice propio | Una columna con nombre |
DataFrame | 2 | Índice de filas y nombres de columna | Tabla 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.
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.shapedevuelve una tupla(filas, columnas);df.columnslista 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.dtypesmuestra el tipo de cada columna:int64,float64,objectocategory.
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.
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.
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.
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.
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.
df.shape devuelve (3, 3).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.
import pandas as pddf = pd.DataFrame({"sede": ["Lima", "Lima", "Arequipa"], "unidades": [120, 180, 45]})df["precio"] = [7.5, 7.5, 8.0]df["ingreso"] = df["unidades"] * df["precio"]total = df["ingreso"].sum()print(total)import pandas as pd carga la biblioteca y la deja disponible con el alias pd.—
Avanza paso a paso: observa la línea resaltada, las variables y la salida.
Errores comunes
| Mensaje | Por qué ocurre | Cómo se arregla |
|---|---|---|
KeyError: 'Ingreso' | La columna se llama ingreso, en minúsculas | Revisa df.columns y respeta el nombre exacto |
SettingWithCopyWarning | Asignaste sobre una selección, no sobre el original | Usa df.loc[mascara, "columna"] = valor |
ValueError: could not convert string to float | El CSV usa coma decimal o tiene texto en la columna | Lee con decimal="," o convierte con pd.to_numeric(..., errors="coerce") |
Una media devuelve nan | La columna contiene valores ausentes o texto | Cuenta nulos con isna().sum() y revisa dtypes |
AttributeError: Can only use .str accessor... | Aplicaste .str a una columna que no es de texto | Convierte 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.
df[df["sede"] == "Lima"]["ingreso"] = 0df.loc[df["sede"] == "Lima", "ingreso"] = 0La 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()yisna().sum()cuestan segundos y evitan horas. - Usa
loceilocde 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_casey sin espacios:ingreso_total, noIngreso 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
Resumen de una Series
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.
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.
Nulos y total por sede
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_csvconvierte un archivo en DataFrame; revisashape,dtypeseisna().sum()al cargar.locselecciona por etiqueta yilocpor posición; con índices ordenados pueden coincidir.- Una máscara booleana filtra filas:
df[df["ingreso"] > 500]. groupbysepara, aplica y combina;mergecruza tablas por una clave común.- Asignar sobre una selección no modifica el original: usa
.loco.copy().
Evaluación
¿Qué diferencia hay entre df.loc[2] y df.iloc[2]?
Después de df["ingreso"] = df["unidades"] * df["precio"], ¿qué se guarda en la nueva columna?
Tienes pedidos de una sede que no aparece en el catálogo y usas merge(..., how="left"). ¿Qué ocurre?
¿Ya lo entendiste?
Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.