Hoja de Trucos de pandas
Última actualización
Crear un DataFrame
DataFrames pequeños creados a mano, para ejemplos, tests y tablas de consulta.
| Tarea | Código |
|---|---|
| Importar pandas y NumPy | import pandas as pd
import numpy as np |
| Desde un dict de columnas | df = pd.DataFrame({"name": ["Ada", "Ben"], "age": [36, 28]}) |
| Desde una lista de filas | pd.DataFrame([{"name": "Ada", "age": 36}, {"name": "Ben", "age": 28}]) |
| Desde un array de NumPy | pd.DataFrame(np.arange(6).reshape(3, 2), columns=["x", "y"]) |
| Una Series con etiquetas | s = pd.Series([3, 5, 2], index=["a", "b", "c"]) |
| Un rango de fechas | pd.date_range("2026-01-01", periods=7, freq="D") |
Leer y escribir archivos
Una llamada carga un archivo en un DataFrame y otra lo vuelve a escribir.
| Tarea | Código |
|---|---|
| Leer un archivo CSV | df = pd.read_csv("sales.csv") |
| Solo algunas columnas, con fechas reales | pd.read_csv("sales.csv", usecols=["date", "city", "sales"], parse_dates=["date"]) |
| Usar una columna como índice | pd.read_csv("sales.csv", index_col="id") |
| Tabulador u otro separador | pd.read_csv("sales.tsv", sep="\t") |
| Tratar otros marcadores como faltantes | pd.read_csv("sales.csv", na_values=["n/a", "-"]) |
| Leer solo las primeras filas | pd.read_csv("sales.csv", nrows=1000) |
| Leer una hoja de Excel (requiere openpyxl) | pd.read_excel("report.xlsx", sheet_name="2026") |
| Leer registros JSON | pd.read_json("records.json") |
| Leer de una base de datos SQL | pd.read_sql("SELECT * FROM orders", conn) |
| Guardar un CSV sin el índice | df.to_csv("clean.csv", index=False) |
| Guardar en Excel o Parquet | df.to_excel("clean.xlsx", index=False), df.to_parquet("clean.parquet") |
Inspeccionar los datos
Las primeras llamadas que conviene hacer con cualquier dataset nuevo.
| Tarea | Código |
|---|---|
| Primeras y últimas filas | df.head(), df.tail(3) |
| Número de filas y columnas | df.shape # (6, 7) |
| Tipos de columna y conteo de no nulos | df.info() |
| Estadísticas de las columnas numéricas | df.describe() |
| Resumen de todas las columnas | df.describe(include="all") |
| Nombres y tipos de columna | df.columns, df.dtypes |
| Contar cada valor | df["city"].value_counts() |
| Proporción de cada valor | df["city"].value_counts(normalize=True) |
| Valores distintos y cuántos hay | df["city"].unique(), df["city"].nunique() |
| Valores faltantes por columna | df.isna().sum() |
| Muestra aleatoria de filas | df.sample(3, random_state=0) |
| Correlación de columnas numéricas | df.corr(numeric_only=True) |
Seleccionar columnas y filas
loc selecciona por etiqueta e incluye el final del slice. iloc selecciona por posición y lo excluye.
| Tarea | Código |
|---|---|
| Una columna, como Series | df["age"] |
| Varias columnas, como DataFrame | df[["name", "age"]] |
| Filas y columnas por etiqueta | df.loc[0:2, ["name", "age"]] |
| Filas y columnas por posición | df.iloc[0:2, 0:3] |
| Un solo valor | df.at[0, "age"], df.iat[0, 3] |
| Última fila | df.iloc[-1] |
| Columnas de un tipo | df.select_dtypes(include="number") |
| Columnas de texto (pandas 3) | df.select_dtypes(include="str") |
| Columnas por coincidencia de nombre | df.filter(like="sal"), df.filter(regex="^(age|salary)$") |
| Buscar filas por una columna | df.set_index("name").loc["Ada", "salary"] |
| Volver a un índice 0, 1, 2 | df.reset_index(drop=True) |
| Eliminar columnas o filas | df.drop(columns=["email"]), df.drop(index=[0, 1]) |
Filtrar filas
Pon cada condición entre paréntesis y combínalas con & (y), | (o) y ~ (no).
| Tarea | Código |
|---|---|
| Filas que cumplen una condición | df[df["age"] > 30] |
| Ambas condiciones | df[(df["age"] > 30) & (df["city"] == "Oslo")] |
| Cualquiera de las dos | df[(df["city"] == "Oslo") | (df["salary"] > 6000)] |
| Valor en una lista | df[df["city"].isin(["Oslo", "Rome"])] |
| Valor fuera de una lista | df[~df["city"].isin(["Oslo", "Rome"])] |
| Entre dos valores, extremos incluidos | df[df["age"].between(30, 40)] |
| Filtrar con una query | df.query("age > 30 and city == 'Oslo'") |
| Usar una variable en una query | min_age = 30
df.query("age > @min_age") |
| El texto contiene | df[df["name"].str.contains("e", case=False)] |
| Valores faltantes o presentes | df[df["age"].isna()], df[df["age"].notna()] |
| Filtrar filas y elegir columnas | df.loc[df["age"] > 30, ["name", "salary"]] |
| Las n filas mayores o menores | df.nlargest(3, "salary"), df.nsmallest(2, "age") |
| Filas con un valor repetido | df[df.duplicated(subset=["city"], keep=False)] |
Ordenar y rankear
| Tarea | Código |
|---|---|
| Ordenar por una columna | df.sort_values("salary") |
| De mayor a menor | df.sort_values("salary", ascending=False) |
| Ordenar por varias columnas | df.sort_values(["city", "salary"], ascending=[True, False]) |
| Faltantes primero | df.sort_values("age", na_position="first") |
| Ordenar por el índice | df.sort_index() |
| Rankear valores, 1 es el más alto | df["salary"].rank(ascending=False) |
Añadir y modificar columnas
Las operaciones sobre columnas completas son rápidas. Recurre a apply solo cuando ningún método integrado sirva.
| Tarea | Código |
|---|---|
| Columna nueva a partir de otras | df["bonus"] = df["salary"] * 0.1 |
| Añadir una columna dentro de una cadena | df.assign(bonus=lambda d: d["salary"] * 0.1) |
| Valor según una condición | df["level"] = np.where(df["age"] >= 40, "senior", "junior") |
| Valor según varias condiciones | df["band"] = np.select([df["age"] < 30, df["age"] < 45], ["young", "mid"], default="senior") |
| Cambiar valores donde se cumple una condición | df.loc[df["city"] == "Lima", "salary"] = 4000 |
| Mapear valores con un dict | df["country"] = df["city"].map({"Oslo": "NO", "Rome": "IT", "Lima": "PE"}) |
| Aplicar una función a una columna | df["salary"].apply(lambda x: round(x, -3)) |
| Aplicar una función a cada celda | df[["age", "salary"]].map(lambda x: x / 1000) |
| Renombrar columnas | df.rename(columns={"salary": "pay"}) |
| Cambiar el tipo de una columna | df["salary"].astype("float64") |
| Texto a números, valores inválidos a NaN | pd.to_numeric(s, errors="coerce") |
| Números en intervalos con etiqueta | pd.cut(df["age"], bins=[0, 30, 45, 120], labels=["young", "mid", "senior"]) |
| Números en grupos del mismo tamaño | pd.qcut(df["salary"], q=4, labels=False) |
| Recorrer filas (lento, último recurso) | for row in df.itertuples():
print(row.name, row.salary) |
Valores faltantes y limpieza
Los números faltantes se muestran como NaN y las fechas faltantes como NaT. Cada una de estas llamadas devuelve un objeto nuevo, así que asigna el resultado.
| Tarea | Código |
|---|---|
| Proporción de faltantes por columna | df.isna().mean() |
| Eliminar filas con algún faltante | df.dropna() |
| Eliminar filas sin una columna clave | df.dropna(subset=["age"]) |
| Rellenar con un valor por columna | df.fillna({"age": df["age"].median()}) |
| Rellenar con la fila anterior o siguiente | df["age"].ffill(), df["age"].bfill() |
| Rellenar huecos por interpolación | df["age"].interpolate() |
| Convertir marcadores en faltantes | df.replace(["n/a", "-", ""], np.nan) |
| Eliminar filas duplicadas | df.drop_duplicates() |
| Quedarse con la última fila por clave | df.drop_duplicates(subset=["city"], keep="last") |
| Limpiar nombres de columna | df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_") |
| Recortar valores extremos | df["salary"].clip(lower=4000, upper=6000) |
| Tipo category para texto repetido | df["city"] = df["city"].astype("category") |
Columnas de texto (.str)
En pandas 3 las columnas de texto reciben el dtype str por defecto. Los métodos .str trabajan sobre toda la columna y los valores faltantes siguen siendo faltantes.
| Tarea | Código |
|---|---|
| Mayúsculas, minúsculas o tipo título | df["name"].str.upper(), df["name"].str.lower(), df["name"].str.title() |
| Quitar espacios | df["name"].str.strip() |
| Reemplazar texto | df["email"].str.replace("mail.com", "example.com") |
| Reemplazar con una regex | df["email"].str.replace(r"@.*", "", regex=True) |
| Contiene, empieza por | df["name"].str.contains("a"), df["name"].str.startswith("E") |
| Longitud de cada valor | df["name"].str.len() |
| Primeros caracteres | df["name"].str[:2] |
| Separar en columnas | df["email"].str.split("@", expand=True) |
| Extraer con un grupo de regex | df["email"].str.extract(r"@(\w+)") |
| Unir columnas de texto | df["name"] + " (" + df["city"] + ")" |
| Rellenar números con ceros | df["salary"].astype("str").str.zfill(6) |
| El nuevo dtype de texto | df["name"].dtype # str |
Fechas y horas
El accesor .dt funciona con columnas datetime. Un índice de fechas ordenado (ts abajo) permite hacer slicing con texto y remuestrear.
| Tarea | Código |
|---|---|
| Convertir una columna a datetime | df["date"] = pd.to_datetime(df["date"]) |
| Convertir con un formato exacto | pd.to_datetime(["03/01/2026", "15/01/2026"], format="%d/%m/%Y") |
| Año, mes, nombre del día | df["date"].dt.year, df["date"].dt.month, df["date"].dt.day_name() |
| Agrupar fechas por mes | df["date"].dt.to_period("M") |
| Días entre dos fechas | (pd.Timestamp("2026-12-31") - df["date"]).dt.days |
| Sumar días o meses | df["date"] + pd.Timedelta(days=30), df["date"] + pd.DateOffset(months=1) |
| Filas en un rango de fechas | df[df["date"].between("2021-01-01", "2022-12-31")] |
| Índice de fechas, slicing con texto | ts = df.set_index("date").sort_index()
ts.loc["2021":"2022"] |
| Total por mes | ts["salary"].resample("ME").sum() |
Media semanal; "QE" es trimestral, "YE" anual | ts["salary"].resample("W").mean() |
| Media móvil | ts["salary"].rolling(3).mean() |
| Cambio respecto a la fila anterior | ts["salary"].diff(), ts["salary"].pct_change() |
| Desplazar valores una fila abajo | ts["salary"].shift(1) |
| Total acumulado | ts["salary"].cumsum() |
| Asignar y convertir zonas horarias | ts.index.tz_localize("UTC").tz_convert("Europe/Oslo") |
Agrupar y agregar
groupby divide las filas por clave, aplica una función a cada grupo y combina los resultados.
| Tarea | Código |
|---|---|
| Media por grupo | df.groupby("city")["salary"].mean() |
| Varias estadísticas a la vez | df.groupby("city")["salary"].agg(["mean", "max", "count"]) |
| Columnas de resultado con nombre | df.groupby("city").agg(avg_pay=("salary", "mean"), people=("name", "count")) |
| Agrupar por dos columnas | df.groupby(["city", "team"])["salary"].sum() |
| Mantener las claves como columnas | df.groupby("city", as_index=False)["salary"].mean() |
| Número de filas por grupo | df.groupby("city").size() |
| Resultado del grupo en cada fila | df["city_avg"] = df.groupby("city")["salary"].transform("mean") |
| Quedarse solo con algunos grupos | df.groupby("city").filter(lambda g: len(g) >= 2) |
| Fila con el máximo por grupo | df.loc[df.groupby("city")["salary"].idxmax()] |
| Las n primeras filas por grupo | df.sort_values("salary", ascending=False).groupby("city").head(2) |
| Numerar las filas de cada grupo | df.groupby("city").cumcount() |
| Recorrer los grupos | for city, group in df.groupby("city"):
print(city, len(group)) |
| Filtrar, agrupar y ordenar en una cadena | (df.query("age > 30")
.groupby("city", as_index=False)
.agg(avg_pay=("salary", "mean"))
.sort_values("avg_pay", ascending=False)) |
Pivotar y reorganizar
Los datos en formato largo tienen una fila por observación. Los datos en formato ancho reparten una variable entre varias columnas.
| Tarea | Código |
|---|---|
| Tabla dinámica | df.pivot_table(index="city", columns="team", values="salary", aggfunc="mean", fill_value=0) |
| Tabla dinámica con totales | df.pivot_table(index="city", values="salary", aggfunc="sum", margins=True) |
| Contar combinaciones | pd.crosstab(df["city"], df["team"]) |
| Proporciones por fila | pd.crosstab(df["city"], df["team"], normalize="index") |
| De ancho a largo | long = df.melt(id_vars="name", value_vars=["age", "salary"], var_name="field", value_name="value") |
| De largo a ancho | long.pivot(index="name", columns="field", values="value") |
| Nivel interno del índice a columnas | df.groupby(["city", "team"])["salary"].sum().unstack(fill_value=0) |
| Una fila por elemento de lista | pd.DataFrame({"id": [1, 2], "tags": [["a", "b"], ["c"]]}).explode("tags") |
| One-hot encoding de una columna | pd.get_dummies(df["team"], prefix="team") |
| Intercambiar filas y columnas | df.T |
Combinar DataFrames
concat apila DataFrames y merge los une por columnas clave, igual que en SQL.
| Tarea | Código |
|---|---|
| Apilar filas | pd.concat([df_2025, df_2026], ignore_index=True) |
| Poner lado a lado | pd.concat([left, right], axis=1) |
| Unir por una columna clave | orders.merge(customers, on="customer_id", how="left") |
| Tipos de join | how="inner", "left", "right", "outer" |
| Claves con nombres distintos | orders.merge(users, left_on="customer_id", right_on="id") |
| Validar el join y marcar el origen de cada fila | orders.merge(customers, on="customer_id", how="left", validate="many_to_one", indicator=True) |
| Unir por el índice | left.join(right, how="inner") |
| Filas sin coincidencia en otra tabla | orders[~orders["customer_id"].isin(customers["customer_id"])] |
Graficar con pandas
pandas dibuja con Matplotlib. Cada llamada de gráfico devuelve un ax de Matplotlib, así que puedes seguir dándole estilo.
| Tarea | Código |
|---|---|
| Gráfico de líneas de una serie | ts["salary"].plot() |
| Gráfico de líneas con dos columnas | df.plot(x="date", y="salary") |
| Gráfico de barras de un resumen | df.groupby("city")["salary"].mean().plot.bar() |
| Barras horizontales ordenadas | df["city"].value_counts().sort_values().plot.barh() |
| Histograma | df["salary"].plot.hist(bins=20) |
| Gráfico de dispersión | df.plot.scatter(x="age", y="salary") |
| Diagrama de caja por grupo | df.boxplot(column="salary", by="city") |
| Título y etiqueta de eje | ax = df.plot(x="date", y="salary", title="Salary")
ax.set_ylabel("EUR") |
| Mostrar o guardar el gráfico | plt.show(), ax.figure.savefig("salary.png", dpi=150) |
pandas 3: formas antiguas y qué escribir ahora
El código copiado de tutoriales antiguos suele fallar o lanzar avisos en pandas 3. Usa en su lugar las formas de la columna pandas 3.
| Tarea | pandas 3 | Forma antigua |
|---|---|---|
| Función en cada celda | df.map(f) | df.applymap(f) |
| Rellenar con la fila anterior | df.ffill() | df.fillna(method="ffill") |
| Añadir filas | pd.concat([df, new_rows]) | df.append(new_rows) |
| Remuestreo a fin de mes | ts["salary"].resample("ME").sum() | resample("M") |
| Fechas por hora | pd.date_range("2026-01-01", periods=24, freq="h") | freq="H" |
| Asignar un valor | df.loc[0, "age"] = 37 | df["age"][0] = 37 |
| Cambiar un subconjunto filtrado | high = df[df["salary"] > 5000]
high["salary"] = 0 | high = df[df["salary"] > 5000].copy() |
| ¿Es una columna de texto? | df["name"].dtype == "str" | df["name"].dtype == object |
El código de pandas que un analista de datos usa a diario, ordenado por tarea: crear y cargar datos, inspeccionarlos, seleccionar y filtrar filas, añadir columnas, limpiar valores faltantes, trabajar con texto y fechas, agrupar y agregar, reorganizar, combinar y graficar. Cada fila es un fragmento corto que puedes pegar, con una línea que explica qué hace.
Todo está escrito para pandas 3, la versión que usa Coddy: las columnas de texto usan el nuevo dtype str, Copy-on-Write está siempre activo y las APIs eliminadas, como applymap y fillna(method=...), solo aparecen en la tabla de formas antiguas. Los ejemplos usan un DataFrame df con las columnas name, city, team, age, salary, date y email. ¿Empiezas con Python? Arranca por la hoja de trucos de Python y el playground de Python, y ten a mano las hojas de trucos de NumPy, Matplotlib y scikit-learn junto a esta.
Preguntas frecuentes sobre la hoja de trucos de pandas
¿Esta hoja de trucos de pandas es gratis?
¿Qué versión de pandas cubre esta hoja de trucos?
str, Copy-on-Write está siempre activo y las llamadas eliminadas en pandas 3 (applymap, fillna(method=...), append, los alias de frecuencia M y H) solo aparecen en la última tabla, junto a lo que debes escribir en su lugar. Casi todos los fragmentos también funcionan en pandas 2.2, salvo las filas que dependen del dtype str o de Copy-on-Write.¿Cuál es la diferencia entre loc e iloc?
loc selecciona por etiqueta: los valores del índice y los nombres de columna, y un slice como df.loc[0:2] incluye su final. iloc selecciona por posición entera, como una lista de Python, así que df.iloc[0:2] devuelve las dos primeras filas y excluye el final. Usa loc con una condición para filtrar filas y elegir columnas en un solo paso: df.loc[df["age"] > 30, ["name"]].¿Cómo filtro un DataFrame con varias condiciones?
& para y, | para o, y ~ para no: df[(df["age"] > 30) & (df["city"] == "Oslo")]. Los operadores and y or de Python no funcionan con columnas completas. Para filtros largos, df.query("age > 30 and city == 'Oslo'") se lee mejor.¿Cuál es la diferencia entre merge, join y concat?
merge combina dos DataFrames por columnas clave, como un join de SQL, con how="inner", "left", "right" u "outer". join es un atajo que hace el merge por el índice. concat no empareja claves: apila los DataFrames uno encima de otro, o lado a lado con axis=1.¿Por qué mi asignación no cambia el DataFrame?
df["age"][0] = 37 modifica un objeto temporal y deja df sin cambios. Escríbela como una sola llamada a loc: df.loc[0, "age"] = 37. Métodos como dropna y fillna también devuelven un objeto nuevo, así que asigna el resultado.