pandas: Series, DataFrame, Index
Los tres tipos fundamentales
pandas tiene tres clases que conviene tener separadas mentalmente:
Series: una columna individual con un índice. Como un vector de R pero con nombres explícitos en cada posición.DataFrame: una tabla con filas y columnas. Como undata.frameotibblede R.Index: la etiqueta de las filas (o columnas). En R no hay equivalente directo, es más explícito de lo que estamos acostumbrados.
import numpy as np
import pandas as pdpd es el alias convencional. Verás import pandas as pd en todo código de análisis.
Series: el vector con nombres
Una Series es un array unidimensional con índice explícito:
ventas = pd.Series([1500, 2100, 1800, 2400],
index=["Q1", "Q2", "Q3", "Q4"],
name="ventas_2024")
ventas
# Q1 1500
# Q2 2100
# Q3 1800
# Q4 2400
# Name: ventas_2024, dtype: int64Lectura:
- Datos: los valores.
index: las etiquetas de cada elemento.name: el nombre del array (opcional pero útil cuando una Series va dentro de un DataFrame).dtype: tipo de los datos (igual que en NumPy).
Acceso:
ventas["Q2"] # 2100 (por etiqueta)
ventas[1] # 2100 (por posición — funciona pero está deprecado en pandas 2.x)
ventas.iloc[1] # 2100 (por posición, forma moderna)
ventas.loc["Q2"] # 2100 (por etiqueta, forma moderna).loc para acceso por etiqueta, .iloc para acceso por posición numérica. Es la distinción más importante de pandas, la veremos en detalle en el tutorial siguiente.
Operaciones:
ventas.sum() # 7800
ventas.mean() # 1950
ventas * 1.21 # aplica IVA elemento a elemento
ventas[ventas > 2000] # boolean indexing — solo Q2 y Q4Todas las operaciones vectorizadas de NumPy funcionan, respetando el índice.
DataFrame: la tabla con índice
Un DataFrame es una colección de Series con el mismo índice:
df = pd.DataFrame({
"region": ["Norte", "Sur", "Este", "Oeste"],
"ventas": [1500, 2100, 1800, 2400],
"clientes": [120, 180, 145, 200],
"activo": [True, True, False, True]
})
df
# region ventas clientes activo
# 0 Norte 1500 120 True
# 1 Sur 2100 180 True
# 2 Este 1800 145 False
# 3 Oeste 2400 200 TrueLo que tienes:
- Columnas: cada columna es una
Seriescon su propiodtype. En este caso:object(strings),int64,int64,bool. - Índice: por defecto, enteros 0, 1, 2, 3. Es lo que aparece en la primera columna sin nombre del print.
- Forma:
df.shape→(4, 4).
Diferencia con R: en R, un data.frame o tibble no tiene “índice” explícito, las filas se identifican por número. En pandas, siempre hay un Index, aunque sea el default 0, 1, 2… Esto importa para joins, reshapes y operaciones avanzadas.
El Index: por qué importa más de lo que parece
El índice de un DataFrame no es solo un número de fila. Es la etiqueta de cada fila. Puedes asignar etiquetas significativas:
df = df.set_index("region")
df
# ventas clientes activo
# region
# Norte 1500 120 True
# Sur 2100 180 True
# Este 1800 145 False
# Oeste 2400 200 TrueAhora puedes acceder por nombre:
df.loc["Norte"] # toda la fila de Norte
df.loc["Norte", "ventas"] # 1500¿Por qué importa?
- Joins implícitos: si dos DataFrames tienen el mismo Index, operaciones como sumar dos DataFrames alinean automáticamente por índice.
- Reshape (
stack,unstack,pivot): el índice define qué se mantiene y qué se pivota. - Series temporales: con índice de fechas, pandas te da
df.loc["2024-03"]para todo marzo.
Para muchos analistas que vienen de R, el Index resulta sobre-explícito al principio. Para análisis ad-hoc puedes ignorarlo dejando el default 0, 1, 2… Para análisis más sofisticado, usarlo bien duplica la velocidad de tu código.
Crear DataFrames
Tres formas habituales:
Desde un diccionario (cada clave es una columna):
df = pd.DataFrame({
"col_a": [1, 2, 3],
"col_b": ["x", "y", "z"]
})La forma más limpia y la que verás más.
Desde lista de listas (cada lista es una fila):
datos = [
[1, "x"],
[2, "y"],
[3, "z"]
]
df = pd.DataFrame(datos, columns=["col_a", "col_b"])Útil cuando los datos vienen así de alguna API.
Desde lista de diccionarios (cada dict es una fila):
datos = [
{"col_a": 1, "col_b": "x"},
{"col_a": 2, "col_b": "y"},
{"col_a": 3, "col_b": "z"}
]
df = pd.DataFrame(datos)Patrón habitual para datos JSON.
Inspección básica
Los métodos imprescindibles cuando recibes un DataFrame nuevo:
df.shape # (filas, columnas) — tupla
df.dtypes # tipo de cada columna
df.columns # nombres de columnas (un Index)
df.index # índice de filas
df.head() # primeras 5 filas
df.head(20) # primeras 20
df.tail() # últimas 5
df.sample(3) # 3 filas aleatoriasResumen estadístico (equivalente a summary() de R):
df.describe() # estadísticas para columnas numéricas
df.describe(include="all") # incluye categóricas con conteo y frecuenciaInspección completa:
df.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 4 entries, 0 to 3
# Data columns (total 4 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 region 4 non-null object
# 1 ventas 4 non-null int64
# 2 clientes 4 non-null int64
# 3 activo 4 non-null bool
# memory usage: 248.0+ bytesinfo() da: tipo, no-nulos por columna, uso de memoria. Es lo primero que ejecuto en cualquier DataFrame nuevo, equivalente a skimr::skim() en R, aunque algo menos completo.
Acceder a columnas
Dos formas, equivalentes:
df["ventas"] # Series
df.ventas # mismo resultado (atajo de atributo)df["ventas"] siempre funciona. df.ventas solo cuando el nombre de columna es un identificador válido de Python (sin espacios, sin caracteres especiales). Convención: usa la forma con corchetes para código robusto, el atributo solo en exploración interactiva.
Para varias columnas:
df[["region", "ventas"]] # DataFrame con esas dos columnasTrampa: doble corchete. df["region"] es Series. df[["region"]] es DataFrame de una columna. La diferencia importa cuando otras operaciones esperan un tipo concreto.
Método encadenamiento (method chaining)
A diferencia de R con el pipe |>, pandas no tiene un operador especial. Encadenas con .:
(df
.query("ventas > 1500")
.sort_values("clientes", ascending=False)
.head(2)
)Los paréntesis son opcionales para el código pero necesarios para que Python permita el salto de línea en cada paso. Patrón idiomático en código pandas moderno.
Para usuarios de R: piensa en esto como el pipe pero con . en lugar de |>. El resto de la sintaxis es muy similar al tidyverse.
Trampas habituales
- Tratar el índice como columna. El índice no aparece en
df.columns, no se puede seleccionar condf["region"]si está como índice. Para devolverlo a columna,df.reset_index(). - Asignar a un slice sin copy.
df_sub = df[df.ventas > 1500]devuelve una vista. Modificarla puede dar warning críptico (SettingWithCopyWarning). Si vas a modificar,df_sub = df[df.ventas > 1500].copy(). df["nueva_col"] = ...para crear columnas con cálculos. Funciona pero no es el patrón idiomático moderno. Mejordf = df.assign(nueva_col=...)que se compone limpiamente con method chaining.- Pensar en filas como en R.
df[0]en R te daría la primera fila (en algunos casos). En pandas,df[0]busca una columna llamada0y suele fallar. Para la primera fila,df.iloc[0].
En la siguiente entrega
Tienes los tres tipos. La siguiente pieza es traer datos del exterior, CSVs, Excel, Parquet. pd.read_csv() y sus parientes tienen muchas opciones. Saber las cinco que importan ahorra horas. Lo siguiente.