Los datos en bruto casi nunca están listos para analizarse. Las exportaciones de tiendas en línea, formularios de contacto, CRM y hojas de cálculo llegan con espacios de más, mayúsculas inconsistentes, precios guardados como texto, filas duplicadas, valores faltantes y el ocasional pedido de “prueba” que alguien olvidó borrar. Si analizas esos datos tal como están, cada total, promedio y gráfica que construyas encima estará mal sin que lo notes.
Pandas es la herramienta estándar de Python para resolver eso, y para todo lo que viene después. En esta guía vas a llevar un solo conjunto de datos realista y desordenado por un flujo de trabajo completo: cargarlo, explorarlo, limpiarlo y analizarlo. En lugar de fragmentos aislados con nombres de ejemplo, cada ejemplo usa los mismos datos y muestra el resultado real, para que veas exactamente qué hace cada paso.
Al terminar, sabrás hacer las tareas que forman la mayor parte del trabajo real con Pandas:
- Cargar y explorar un conjunto de datos
- Seleccionar y filtrar filas y columnas
- Limpiar texto, números guardados como texto, fechas, duplicados, valores faltantes y valores atípicos
- Agregar columnas calculadas
- Ordenar, agrupar y resumir
- Crear tablas dinámicas y tendencias mensuales
- Combinar conjuntos de datos y cambiar su forma
- Exportar los resultados
¿Qué Es Pandas?
Pandas es una biblioteca de Python gratuita y de código abierto para trabajar con datos tabulares: datos organizados en filas y columnas, como una hoja de cálculo o una tabla de base de datos. Su nombre viene de “panel data” (datos de panel), un término de la estadística y la economía.
Pandas tiene dos piezas principales:
- Un DataFrame es una tabla completa: filas y columnas, cada columna con un nombre. Piensa en él como una hoja de cálculo dentro de Python.
- Una Series es una sola columna (o fila) de un DataFrame: una lista de valores con etiquetas.
DataFrame (the whole table) Series (one column: "price")
order_id customer price 0 $49.00
0 1001 Maria Lopez $49.00 1 $1,200.00
1 1002 carlos ruiz $1,200.00 2 $9.99
2 1003 Ana Torres $9.99
¿Por qué usar Pandas en lugar de una hoja de cálculo? Porque cada paso es código: puedes repetir toda la limpieza con la exportación del próximo mes en un segundo, ver exactamente qué se cambió y manejar archivos con millones de filas que congelarían una hoja de cálculo.
Preparación
Instala Pandas en un entorno virtual (o con uv):
pip install pandas
Por convención, Pandas siempre se importa como pd:
import pandas as pd
Versiones de Pandas: los resultados de esta guía vienen de Pandas 3.1. Todos los ejemplos también funcionan sin advertencias en Pandas 2.x. La única diferencia visible está en
df.info()y en los tipos de datos: Pandas 3 identifica las columnas de texto comostr, mientras que Pandas 2 muestraobject.
El Conjunto de Datos de Ejemplo
Nuestro conjunto de datos es una exportación de los pedidos de una pequeña tienda en línea que vende plantillas de sitios web, diseño de logotipos, hosting y servicios de SEO. Guárdalo como orders.csv en la misma carpeta que tu script:
order_id,order_date,customer,email,product,category,quantity,price,status
1001,2026-01-05,Maria Lopez,[email protected],Website Template,Templates,1,$49.00,Completed
1002,2026-01-07, carlos ruiz ,[email protected],Logo Design,design,1,"$1,200.00",completed
1003,2026-01-07,Ana Torres,[email protected],Hosting Plan,Hosting,12,$9.99,Completed
1003,2026-01-07,Ana Torres,[email protected],Hosting Plan,Hosting,12,$9.99,Completed
1004,2026-01-12,Valley Dental,,SEO Audit,Marketing,1,$300.00,Pending
1005,2026-01-20,Lopez Landscaping,[email protected],Website Template,Templates,2,$49.00,Completed
1006,2026-02-02,Calexico Coffee,[email protected],Website Template,templates,2,$49.00,Completed
1007,2026-02-10,Bright Bakery,[email protected],Logo Design,Design,1,"$1,200.00",Refunded
1008,2026-02-14,Lopez Landscaping,[email protected],Hosting Plan,Hosting,,$9.99,Completed
1009,2026-02-20,Maria Lopez,[email protected],SEO Audit,Marketing,1,$300.00,Completed
1010,2026-03-03,Test User,[email protected],Website Template,Templates,1,"$49,000.00",Completed
1011,2026-03-08,Valley Dental,[email protected],Hosting Plan,HOSTING,6,$9.99,Completed
1012,2026-03-15,Calexico Coffee,[email protected],SEO Audit,Marketing,1,$300.00, completed
1013,2026-03-22,Bright Bakery,[email protected],Website Template,Templates,3,$49.00,Completed
Tiene los problemas que verás en las exportaciones reales: un nombre con espacios de más y en minúsculas, un correo en mayúsculas, categorías escritas de cuatro formas distintas, precios guardados como texto con $ y comas, un pedido duplicado, un correo faltante, una cantidad faltante, estados con mayúsculas inconsistentes y un pedido de “prueba” de $49,000.
Más adelante también usaremos un segundo archivo, customers.csv:
customer,city,segment
Maria Lopez,El Centro,Freelancer
Carlos Ruiz,Calexico,Small Business
Ana Torres,Brawley,Freelancer
Valley Dental,El Centro,Small Business
Lopez Landscaping,Imperial,Small Business
Calexico Coffee,Calexico,Small Business
Bright Bakery,El Centro,Small Business
Los ejemplos se construyen uno sobre otro, así que ejecútalos en orden, en un solo script o en un cuaderno de Jupyter.
Parte 1: Cargar y Explorar
Cargar los Datos
import pandas as pd
pd.set_option("display.max_columns", None) # mostrar todas las columnas
pd.set_option("display.width", 200)
df = pd.read_csv("orders.csv")
print(df.shape)
print(df.head())
(14, 9)
order_id order_date customer email product category quantity price status
0 1001 2026-01-05 Maria Lopez [email protected] Website Template Templates 1.0 $49.00 Completed
1 1002 2026-01-07 carlos ruiz [email protected] Logo Design design 1.0 $1,200.00 completed
2 1003 2026-01-07 Ana Torres [email protected] Hosting Plan Hosting 12.0 $9.99 Completed
3 1003 2026-01-07 Ana Torres [email protected] Hosting Plan Hosting 12.0 $9.99 Completed
4 1004 2026-01-12 Valley Dental NaN SEO Audit Marketing 1.0 $300.00 Pending
df.shape te dice el tamaño: 14 filas y 9 columnas. df.head() muestra las primeras cinco filas (usa df.tail() para las últimas cinco). Las dos líneas de set_option evitan que Pandas oculte columnas detrás de ... cuando la tabla es ancha.
Pandas lee muchos formatos además de CSV, como pd.read_excel() para archivos de Excel, pd.read_json() para JSON y pd.read_sql() para bases de datos (consulta Python para Análisis de Datos: Cómo Acceder a tus Datos y SQLAlchemy).
Revisar la Estructura con info()
df.info() es la primera mirada más útil a un conjunto de datos nuevo:
df.info()
<class 'pandas.DataFrame'>
RangeIndex: 14 entries, 0 to 13
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 order_id 14 non-null int64
1 order_date 14 non-null str
2 customer 14 non-null str
3 email 13 non-null str
4 product 14 non-null str
5 category 14 non-null str
6 quantity 13 non-null float64
7 price 14 non-null str
8 status 14 non-null str
dtypes: float64(1), int64(1), str(7)
memory usage: 1.1 KB
Ya se ven tres problemas:
pricees texto (str), no un número, por los signos$y las comas. No se puede sumar texto.order_datees texto, así que todavía no puedes ordenarlo ni agruparlo por mes.quantityesfloat64(un número decimal), aunque las cantidades son números enteros. Es una pista: Pandas usaNaN(“not a number”, no es un número) para los valores faltantes, yNaNes un flotante, así que una columna numérica con cualquier valor faltante se convierte en una columna de flotantes. Los conteos de valores no nulos lo confirman:emailyquantitytienen 13 valores de 14.
Encontrar Valores Faltantes, Duplicados y Categorías Inconsistentes
print(df.isna().sum())
print("Duplicate rows:", df.duplicated().sum())
print(df["category"].value_counts())
order_id 0
order_date 0
customer 0
email 1
product 0
category 0
quantity 1
price 0
status 0
dtype: int64
Duplicate rows: 1
category
Templates 4
Hosting 3
Marketing 3
design 1
templates 1
Design 1
HOSTING 1
Name: count, dtype: int64
isna().sum()cuenta los valores faltantes de cada columna.duplicated().sum()cuenta las filas que son copias exactas de una fila anterior.value_counts()cuenta cada valor distinto de una columna. Es la forma más rápida de detectar escrituras inconsistentes: Pandas veDesign,design,HostingyHOSTINGcomo cuatro categorías distintas.
Seleccionar Columnas y Filas
Vas a seleccionar datos todo el tiempo, así que conviene aprender pronto las tres formas principales:
print(df[["customer", "product", "price"]].head(3)) # columnas por nombre
print(df.loc[0, "customer"]) # un valor, por etiqueta de fila y nombre de columna
print(df.iloc[0:2, 0:3]) # por posición: filas 0-1, columnas 0-2
customer product price
0 Maria Lopez Website Template $49.00
1 carlos ruiz Logo Design $1,200.00
2 Ana Torres Hosting Plan $9.99
Maria Lopez
order_id order_date customer
0 1001 2026-01-05 Maria Lopez
1 1002 2026-01-07 carlos ruiz
df["col"]selecciona una columna como Series;df[["col1", "col2"]](con doble corchete) selecciona varias columnas como DataFrame..loc[]selecciona por etiquetas: las etiquetas de las filas (el índice de la izquierda) y los nombres de las columnas..iloc[]selecciona por posición, como las rebanadas de una lista:0:2significa las posiciones 0 y 1.
Parte 2: Limpiar los Datos
Limpiar las Columnas de Texto
Las columnas de texto necesitan espacios y mayúsculas consistentes antes de poder agruparlas o combinarlas. El accesor .str de Pandas aplica los métodos de cadenas a todos los valores de una columna a la vez:
df["customer"] = df["customer"].str.strip().str.title()
df["email"] = df["email"].str.strip().str.lower()
df["category"] = df["category"].str.strip().str.title()
df["status"] = df["status"].str.strip().str.title()
print(df["category"].value_counts())
print(df["status"].value_counts())
category
Templates 5
Hosting 4
Marketing 3
Design 2
Name: count, dtype: int64
status
Completed 12
Pending 1
Refunded 1
Name: count, dtype: int64
.str.strip() quita los espacios de más, .str.title() pone en mayúscula la primera letra de cada palabra y .str.lower() pone los correos en minúsculas para que la misma dirección siempre coincida. Las siete formas de escribir las categorías ahora son cuatro categorías limpias, y " carlos ruiz " ahora es "Carlos Ruiz". (Son los mismos métodos de cadenas de Fundamentos de Python: Variables y Tipos de Datos, aplicados a toda una columna a la vez.)
Convertir Precios y Fechas al Tipo Correcto
df["price"] = (
df["price"]
.str.replace("$", "", regex=False)
.str.replace(",", "", regex=False)
.astype(float)
)
df["order_date"] = pd.to_datetime(df["order_date"])
print(df[["order_date", "price"]].dtypes)
order_date datetime64[us]
price float64
dtype: object
Quitar tanto el $ como la coma es importante: si solo quitas el $, convertir "1,200.00" en número falla con ValueError: could not convert string to float. regex=False le indica a Pandas que trate el $ como un carácter normal y no como un patrón especial.
pd.to_datetime() convierte el texto en fechas reales, que puedes ordenar, filtrar por rango y agrupar por mes. (Pandas 2 muestra el tipo como datetime64[ns]; ambos funcionan igual.) Si algunas fechas no se pueden interpretar, pd.to_datetime(df["col"], errors="coerce") las convierte en valores faltantes en lugar de lanzar un error, para que puedas encontrarlas y corregirlas.
Quitar Duplicados
df = df.drop_duplicates()
print("Rows after removing duplicates:", len(df))
print("Duplicate order IDs left:", df["order_id"].duplicated().sum())
Rows after removing duplicates: 13
Duplicate order IDs left: 0
drop_duplicates() quita las filas que son idénticas en todas las columnas. En los datos reales, los duplicados no siempre son copias exactas (el mismo pedido podría aparecer dos veces con una hora distinta), así que revisa también la columna que debería ser única, como el ID del pedido. Para conservar solo el registro más reciente de cada cliente, ordena primero y usa subset y keep:
latest = df.sort_values("order_date").drop_duplicates(subset="customer", keep="last")
Manejar los Valores Faltantes
Primero, mira las filas con datos faltantes antes de decidir qué hacer:
print(df[df["quantity"].isna()][["order_id", "customer", "product"]])
df["quantity"] = df["quantity"].fillna(1).astype(int)
df["email"] = df["email"].fillna("unknown")
print(df.isna().sum().sum(), "missing values left")
order_id customer product
8 1008 Lopez Landscaping Hosting Plan
0 missing values left
No hay una sola forma correcta de manejar los valores faltantes; depende de lo que significan los datos:
- Llenar con un valor predeterminado razonable. Aquí, un pedido debe tener al menos un artículo, así que llenar la cantidad faltante con
1es razonable (en un trabajo real, confírmalo con la fuente si puedes). Después de llenarla,.astype(int)vuelve a convertir la columna en números enteros. - Llenar con una etiqueta como
"unknown"para el texto que conviene conservar. - Llenar con una estadística (
df["col"].fillna(df["col"].median())) para las mediciones numéricas. La mediana es más segura que el promedio cuando hay valores atípicos. - Quitar las filas con
df.dropna(subset=["col"])cuando un valor es esencial y no se puede adivinar. - Arrastrar los valores hacia adelante con
df["col"].ffill()en las series de tiempo, donde el último valor conocido sigue siendo válido.
Evita
inplace=Trueen una columna. Los tutoriales viejos escribendf["col"].fillna(0, inplace=True). En Pandas 3, eso no cambia nada endf: modifica una copia temporal, y Pandas advierte que “nunca funciona”. Siempre asigna el resultado de vuelta:df["col"] = df["col"].fillna(0). Esto funciona igual en todas las versiones de Pandas.
Encontrar Valores Atípicos
Los valores atípicos (outliers) son los que están muy lejos del resto. Una forma común de encontrarlos es la regla del IQR (rango intercuartílico): se señala todo lo que esté a más de 1.5 × IQR por encima del tercer cuartil (o por debajo del primero).
q1 = df["price"].quantile(0.25)
q3 = df["price"].quantile(0.75)
iqr = q3 - q1
upper = q3 + 1.5 * iqr
print(f"Upper limit: {upper:.2f}")
print(df[df["price"] > upper][["order_id", "customer", "product", "price"]])
Upper limit: 676.50
order_id customer product price
1 1002 Carlos Ruiz Logo Design 1200.0
7 1007 Bright Bakery Logo Design 1200.0
10 1010 Test User Website Template 49000.0
Esta es una lección importante: un valor atípico no es automáticamente un error. La regla señaló tres pedidos, pero los dos pedidos de diseño de logotipo de $1,200 son reales: el diseño de logotipos simplemente es el servicio más caro. Solo la “Website Template” de $49,000 de “Test User” está claramente mal. Las reglas estadísticas encuentran candidatos; tu conocimiento de los datos decide qué hacer con ellos. Aquí quitamos solo el pedido de prueba:
df = df[df["customer"] != "Test User"].copy()
print("Rows:", len(df))
Rows: 12
El .copy() deja claro que df ahora es una tabla propia e independiente, así que agregarle columnas después nunca provoca la advertencia “SettingWithCopyWarning” de Pandas 2.
Parte 3: Analizar los Datos
Filtrar Filas
Para filtrar se usan máscaras booleanas: una condición que es True o False para cada fila.
completed = df[df["status"] == "Completed"]
print("Completed orders:", len(completed))
big = df[(df["status"] == "Completed") & (df["price"] >= 300)]
print(big[["order_id", "customer", "product", "price"]])
print(df[df["category"].isin(["Design", "Marketing"])][["order_id", "product"]])
print(df.query("quantity > 2 and status == 'Completed'")[["order_id", "customer", "quantity"]])
Completed orders: 10
order_id customer product price
1 1002 Carlos Ruiz Logo Design 1200.0
9 1009 Maria Lopez SEO Audit 300.0
12 1012 Calexico Coffee SEO Audit 300.0
order_id product
1 1002 Logo Design
4 1004 SEO Audit
7 1007 Logo Design
9 1009 SEO Audit
12 1012 SEO Audit
order_id customer quantity
2 1003 Ana Torres 12
11 1011 Valley Dental 6
13 1013 Bright Bakery 3
- Combina condiciones con
&(y),|(o) y~(no), y pon cada condición entre paréntesis: la precedencia de operadores de Python los hace necesarios. .isin([...])revisa si un valor está en una lista..query()te permite escribir las condiciones como una cadena fácil de leer.- Otros filtros útiles:
df["price"].between(10, 100)ydf["email"].str.contains("example.com").
Observa que los números de fila de la izquierda (el índice) conservaron sus valores originales después de filtrar. Es normal; usa .reset_index(drop=True) si quieres que se vuelvan a numerar.
Agregar Columnas Calculadas
df["revenue"] = df["quantity"] * df["price"]
df["month"] = df["order_date"].dt.to_period("M")
df["is_large_order"] = df["revenue"] >= 200
print(df[["order_id", "quantity", "price", "revenue", "month", "is_large_order"]].head())
order_id quantity price revenue month is_large_order
0 1001 1 49.00 49.00 2026-01 False
1 1002 1 1200.00 1200.00 2026-01 True
2 1003 12 9.99 119.88 2026-01 False
4 1004 1 300.00 300.00 2026-01 True
5 1005 2 49.00 98.00 2026-01 False
Las operaciones entre columnas funcionan con todas las filas a la vez, sin necesidad de un ciclo. Esto se llama vectorización, y es más sencillo y mucho más rápido que recorrer las filas una por una. El accesor .dt funciona como .str, pero para fechas: .dt.year, .dt.month, .dt.day_name() y .dt.to_period("M") (el mes) están disponibles.
Ordenar y Clasificar
A partir de aquí analizaremos solo las ventas completadas:
sales = df[df["status"] == "Completed"].copy()
print(sales.sort_values(["revenue", "customer"], ascending=[False, True])[["order_id", "customer", "revenue"]].head(4))
print(sales.nlargest(3, "revenue")[["customer", "revenue"]])
order_id customer revenue
1 1002 Carlos Ruiz 1200.0
12 1012 Calexico Coffee 300.0
9 1009 Maria Lopez 300.0
13 1013 Bright Bakery 147.0
customer revenue
1 Carlos Ruiz 1200.0
9 Maria Lopez 300.0
12 Calexico Coffee 300.0
Ordenar por más de una columna rompe los empates: dos pedidos tienen $300 de ingresos, así que la segunda columna (customer, de la A a la Z) decide su orden. nlargest() y nsmallest() son atajos para las listas de “los N primeros”.
Agrupar y Resumir
groupby() es una de las herramientas más poderosas de Pandas. Funciona en tres pasos: dividir los datos en grupos, aplicar un cálculo a cada grupo y combinar los resultados:
print(sales.groupby("category")["revenue"].sum().sort_values(ascending=False))
category
Design 1200.00
Marketing 600.00
Templates 392.00
Hosting 189.81
Name: revenue, dtype: float64
Para calcular varias estadísticas a la vez, usa .agg() con agregaciones con nombre: cada columna nueva recibe un nombre, una columna de origen y una función:
summary = sales.groupby("customer").agg(
orders=("order_id", "count"),
total_revenue=("revenue", "sum"),
avg_order=("revenue", "mean"),
).sort_values("total_revenue", ascending=False)
print(summary.round(2))
orders total_revenue avg_order
customer
Carlos Ruiz 1 1200.00 1200.00
Calexico Coffee 2 398.00 199.00
Maria Lopez 2 349.00 174.50
Bright Bakery 1 147.00 147.00
Ana Torres 1 119.88 119.88
Lopez Landscaping 2 107.99 54.00
Valley Dental 1 59.94 59.94
Es un reporte completo de clientes en cinco líneas. Algunas funciones de agregación comunes son "sum", "mean", "median", "count", "nunique" (cantidad de valores distintos), "min" y "max".
Crear una Tabla Dinámica
Una tabla dinámica (pivot table) resume los datos en dos dimensiones, igual que en Excel:
pivot = sales.pivot_table(
index="month",
columns="category",
values="revenue",
aggfunc="sum",
fill_value=0,
)
print(pivot)
category Design Hosting Marketing Templates
month
2026-01 1200.0 119.88 0.0 147.0
2026-02 0.0 9.99 300.0 98.0
2026-03 0.0 59.94 300.0 147.0
index define las filas, columns define las columnas, values es lo que se va a resumir y fill_value=0 reemplaza las combinaciones vacías (no hubo ventas de diseño en febrero) con cero.
Para simplemente contar combinaciones, pd.crosstab() es un atajo. Aquí muestra los estados de los pedidos por categoría, usando todos los pedidos:
print(pd.crosstab(df["category"], df["status"]))
status Completed Pending Refunded
category
Design 1 0 1
Hosting 3 0 0
Marketing 2 1 0
Templates 4 0 0
Analizar Tendencias en el Tiempo
Con una columna de fechas real, Pandas puede resumir los datos por cualquier periodo. resample() agrupa por tiempo; aquí, los totales mensuales:
monthly = sales.set_index("order_date")["revenue"].resample("MS").sum()
print(monthly)
print(monthly.pct_change().round(3))
order_date
2026-01-01 1466.88
2026-02-01 407.99
2026-03-01 506.94
Freq: MS, Name: revenue, dtype: float64
order_date
2026-01-01 NaN
2026-02-01 -0.722
2026-03-01 0.243
Freq: MS, Name: revenue, dtype: float64
"MS" significa “inicio de mes” (month start); usa "W" para semanas o "QS" para trimestres. pct_change() calcula el cambio de un periodo al siguiente: los ingresos bajaron 72.2% en febrero y subieron 24.3% en marzo. Enero es NaN porque no hay un mes anterior con el cual compararlo. Usa .rolling(3).mean() para los promedios móviles.
Combinar Conjuntos de Datos con merge()
Para agregar la ciudad y el segmento de cada cliente, combina (merge) las ventas con customers.csv. Funciona igual que un JOIN de SQL (consulta Los JOINs de SQL Explicados):
customers = pd.read_csv("customers.csv")
merged = sales.merge(customers, on="customer", how="left", validate="many_to_one")
print(merged[["customer", "city", "segment", "revenue"]].head())
print(merged.groupby("city")["revenue"].sum().sort_values(ascending=False))
customer city segment revenue
0 Maria Lopez El Centro Freelancer 49.00
1 Carlos Ruiz Calexico Small Business 1200.00
2 Ana Torres Brawley Freelancer 119.88
3 Lopez Landscaping Imperial Small Business 98.00
4 Calexico Coffee Calexico Small Business 98.00
city
Calexico 1598.00
El Centro 555.94
Brawley 119.88
Imperial 107.99
Name: revenue, dtype: float64
how="left"conserva todas las ventas, aunque un cliente no esté en el segundo archivo. ("inner"conserva solo las coincidencias,"outer"conserva todo y"right"conserva todas las filas de la segunda tabla.)validate="many_to_one"hace que Pandas lance un error si un cliente aparece más de una vez encustomers.csv, lo que duplicaría filas del resultado sin avisar.
Fíjate en que Carlos Ruiz encontró su ciudad. Eso solo funcionó porque antes limpiamos " carlos ruiz ": las combinaciones comparan el texto exactamente, así que la limpieza siempre va primero. Para revisar cuántas filas coincidieron, agrega indicator=True:
check = df.merge(customers, on="customer", how="left", indicator=True)
print(check["_merge"].value_counts())
_merge
both 12
left_only 0
right_only 0
Name: count, dtype: int64
Los 12 pedidos encontraron a su cliente. Cualquier fila left_only sería un pedido sin cliente que coincida, normalmente señal de una diferencia de escritura que hay que corregir.
Para apilar tablas con las mismas columnas (como agregar los pedidos del mes siguiente), usa pd.concat():
april = pd.DataFrame({
"order_id": [1014, 1015],
"customer": ["Maria Lopez", "Ana Torres"],
"revenue": [49.0, 300.0],
})
all_orders = pd.concat([sales[["order_id", "customer", "revenue"]], april], ignore_index=True)
print(len(sales), "+", len(april), "=", len(all_orders))
10 + 2 = 12
Cambiar la Forma de los Datos: Ancho y Largo
Los datos vienen en dos formas. Los datos anchos tienen una columna por categoría, como la tabla dinámica de arriba. Los datos largos (u “ordenados”, tidy) tienen una fila por medición, que es lo que esperan la mayoría de las bibliotecas de gráficas y muchos análisis. melt() convierte de ancho a largo (y pivot_table() hace lo contrario):
wide = pivot.reset_index()
long = wide.melt(id_vars="month", var_name="category", value_name="revenue")
print(long.head(6))
month category revenue
0 2026-01 Design 1200.00
1 2026-02 Design 0.00
2 2026-03 Design 0.00
3 2026-01 Hosting 119.88
4 2026-02 Hosting 9.99
5 2026-03 Hosting 59.94
Traducir Valores y Crear Categorías
Hay dos transformaciones más que aparecen todo el tiempo. .map() traduce valores usando un diccionario (consulta Fundamentos de Python: Listas y Diccionarios), y pd.cut() agrupa números en rangos con etiquetas:
tier = {"Templates": "Product", "Hosting": "Recurring", "Design": "Service", "Marketing": "Service"}
df["revenue_type"] = df["category"].map(tier)
df["size"] = pd.cut(df["revenue"], bins=[0, 100, 500, float("inf")], labels=["Small", "Medium", "Large"])
print(df[["product", "category", "revenue_type", "revenue", "size"]].head())
product category revenue_type revenue size
0 Website Template Templates Product 49.00 Small
1 Logo Design Design Service 1200.00 Large
2 Hosting Plan Hosting Recurring 119.88 Medium
4 SEO Audit Marketing Service 300.00 Medium
5 Website Template Templates Product 98.00 Small
Los valores que no están en el diccionario se convierten en NaN, lo que hace de .map() una forma rápida de detectar categorías inesperadas. Para una lógica demasiado compleja para un diccionario, .apply() ejecuta tu propia función con cada valor, pero intenta primero un enfoque vectorizado, porque .apply() es mucho más lento con datos grandes.
Parte 4: Exportar los Resultados
summary.round(2).to_csv("customer_summary.csv")
sales.drop(columns="month").to_json("sales.json", orient="records", date_format="iso", indent=2)
Ahora customer_summary.csv contiene:
customer,orders,total_revenue,avg_order
Carlos Ruiz,1,1200.0,1200.0
Calexico Coffee,2,398.0,199.0
Maria Lopez,2,349.0,174.5
Bright Bakery,1,147.0,147.0
Ana Torres,1,119.88,119.88
Lopez Landscaping,2,107.99,54.0
Valley Dental,1,59.94,59.94
Usa index=False con to_csv() cuando el índice solo son números de fila que no necesitas. Para el JSON quitamos la columna auxiliar month, porque JSON no puede guardar el tipo “Period” de Pandas: un buen recordatorio de exportar columnas simples. Otros formatos son to_excel() (que necesita el paquete openpyxl) y to_sql() para bases de datos.
Para convertir estos resultados en gráficas, consulta Visualización de Datos con Python: Guía Práctica de Gráficas.
Errores Comunes con Pandas
- Usar
inplace=Trueen una columna.df["col"].fillna(0, inplace=True)no hace nada en Pandas 3. Asigna el resultado de vuelta:df["col"] = df["col"].fillna(0). - Olvidar los paréntesis al combinar condiciones.
df[df["a"] > 1 & df["b"] < 5]lanza un error o da resultados incorrectos. Escribedf[(df["a"] > 1) & (df["b"] < 5)]. - Usar
and/oren lugar de&/|. Elandde Python no funciona con columnas completas; Pandas necesita&y|. - Hacer operaciones con números guardados como texto. Revisa primero
df.info()y convierte con.astype()opd.to_numeric(). - Comparar con
NaNusando==.NaN == NaNesFalse. Usa.isna()y.notna(). - Recorrer las filas con un ciclo.
for index, row in df.iterrows()es lento y casi nunca hace falta. Usa operaciones con columnas. - Combinar antes de limpiar.
"Carlos Ruiz"y" carlos ruiz "no coinciden. Limpia primero las columnas clave y usaindicator=Truepara revisar el resultado. - Borrar automáticamente todos los valores atípicos. Revisa los valores señalados antes de quitarlos; algunos son tus datos más importantes.
- Modificar una tabla filtrada sin
.copy(). Puede provocarSettingWithCopyWarningen Pandas 2. Agrega.copy()cuando filtres un DataFrame que vayas a modificar.
Hoja de Referencia de Pandas
| Tarea | Código |
|---|---|
| Cargar un CSV | df = pd.read_csv("file.csv") |
| Primera mirada | df.head(), df.info(), df.describe(), df.shape |
| Valores faltantes | df.isna().sum() |
| Contar valores | df["col"].value_counts() |
| Seleccionar columnas | df[["a", "b"]] |
| Seleccionar por etiqueta / posición | df.loc[row, "col"], df.iloc[0:5, 0:3] |
| Filtrar filas | df[(df["a"] > 1) & (df["b"] == "x")] |
| Limpiar texto | df["col"].str.strip().str.title() |
| Convertir tipos | .astype(float), pd.to_datetime(), pd.to_numeric() |
| Llenar valores faltantes | df["col"] = df["col"].fillna(value) |
| Quitar filas con valores faltantes | df.dropna(subset=["col"]) |
| Quitar duplicados | df.drop_duplicates() |
| Columna nueva | df["c"] = df["a"] * df["b"] |
| Ordenar | df.sort_values("col", ascending=False) |
| Agrupar y resumir | df.groupby("col")["val"].sum() o .agg(...) |
| Tabla dinámica | df.pivot_table(index=..., columns=..., values=..., aggfunc="sum") |
| Periodos de tiempo | df.set_index("date")["val"].resample("MS").sum() |
| Combinar | df.merge(other, on="key", how="left") |
| Apilar tablas | pd.concat([df1, df2], ignore_index=True) |
| De ancho a largo | df.melt(id_vars=..., var_name=..., value_name=...) |
| Exportar | df.to_csv("out.csv", index=False) |
Conclusión
La mayor parte del trabajo real con Pandas sigue el mismo patrón que acabas de usar: cargar los datos, explorarlos con info(), isna() y value_counts(), limpiar el texto, los tipos, los duplicados, los valores faltantes y los atípicos, y luego analizarlos con filtros, columnas calculadas, groupby(), tablas dinámicas, resúmenes por tiempo y combinaciones, antes de exportar los resultados.
Los pasos de limpieza no son solo preparación; deciden si tus respuestas son correctas. Sin ellos, este conjunto de datos habría reportado siete categorías de productos en lugar de cuatro, contado un pedido dos veces, perdido la ciudad de Carlos Ruiz en la combinación y sumado una venta falsa de $49,000 a los ingresos de marzo. Guarda tus pasos de limpieza en un script, y la exportación del próximo mes te tomará segundos en lugar de una tarde.
Para ver más a fondo las técnicas de limpieza usando SQL puro, consulta Limpiar Datos Desordenados con SQL Puro.
