Series y DataFrame — las dos estructuras fundamentales de pandas
Qué es una columna, qué es una tabla y por qué cada valor de una columna debe compartir un mismo tipo. Incluye la diferencia entre df["col"] y df[["col"]].
¿Cómo quieres aprender?
El problema que vamos a resolver
En el capítulo anterior escribimos una línea y funcionó:
print(sales["quantity"].sum())19La línea es fácil de leer, pero oculta una pregunta. ¿Qué devolvió sales["quantity"]? ¿Una lista? Las listas no tienen .sum(): escribir [12, 5, 2].sum() en Python genera un error.
Vale la pena saber la respuesta, porque en pandas siempre tienes en tus manos una de dos cosas, y casi todas las dudas empiezan con la misma pregunta: ¿cuál de las dos tengo en este momento? ¿Una columna o una tabla entera?
Este capítulo trata sobre esas dos cosas: sus nombres, sus formas y qué las diferencia.
Al final de este capítulo podrás
- Explicar en una sola oración qué diferencia a una
Seriesde unDataFrame - Crear una
Seriese inspeccionar su índice, sus valores y su tipo por separado - Explicar por qué cada valor de una columna debe compartir el mismo tipo
- Usar
df["col"]ydf[["col"]]sabiendo cuál es cuál - Leer un
KeyErrory saber con exactitud qué falló en el nombre de una columna
Requisitos previos: instalar pandas y tu primera tabla.
Series — una columna
Comienza con la unidad más pequeña. Una Series es una secuencia de valores, cada uno emparejado con un nombre:
import pandas as pd
quantity = pd.Series([12, 5, 2])
print(quantity)0 12
1 5
2 2
dtype: int64Entraron tres valores y salieron tres pares. Los números 0, 1, 2 de la izquierda son el índice: un nombre para cada valor. Tú no los indicaste; pandas empezó a contar desde cero.
Y en la parte inferior, dtype: int64: el tipo de toda la serie. Un solo tipo, no uno por cada valor. Eso es lo que hace que pandas sea rápido, y también es su principal limitación.
Estas tres partes se pueden examinar por separado:
import pandas as pd
quantity = pd.Series([12, 5, 2])
print(quantity.index)
print(quantity.values)
print(quantity.dtype)RangeIndex(start=0, stop=3, step=1)
[12 5 2]
int64RangeIndex significa que el índice no es más que un conteo: empieza en cero y sube de uno en uno. values son los valores sin sus nombres. Y dtype es el tipo de toda la columna.
Un índice no tiene por qué ser numérico:
import pandas as pd
quantity = pd.Series([12, 5, 2], index=["pen", "notebook", "bag"])
print(quantity)
print(quantity["notebook"])pen 12
notebook 5
bag 2
dtype: int64
5Ahora las filas llevan los nombres de los productos y se puede obtener un valor mediante ese nombre. Esto suena como un diccionario de Python, y la similitud es real, con una gran diferencia:
import pandas as pd
quantity = pd.Series([12, 5, 2], index=["pen", "notebook", "bag"])
print(quantity * 2)pen 24
notebook 10
bag 4
dtype: int64Toda la columna se multiplicó por dos a la vez, sin ningún bucle. Hacer eso con un diccionario implica recorrer cada valor por tu cuenta. Aplica una operación a una Series y pandas la aplicará a cada valor; todos los capítulos siguientes se basan en este comportamiento.
DataFrame — varias columnas, un solo índice
Un DataFrame son varias Series lado a lado que comparten un índice.
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(sales)
print(sales.shape)
print(sales.columns)product quantity price
0 pen 12 15.0
1 notebook 5 60.0
2 bag 2 850.0
(3, 3)
Index(['product', 'quantity', 'price'], dtype='object')Esa frase «comparten un índice» es la esencia de todo. product, quantity y price en el índice 0 hacen referencia a lo mismo: un bolígrafo. El índice es lo que une tres columnas en filas.
Una tabla, varios tipos — pero una columna, un solo tipo
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(sales.dtypes)product object
quantity int64
price float64
dtype: objectTres columnas, tres tipos. int64 representa números enteros, float64 decimales y object, que generalmente significa texto.
Observa que no aparece str. Los tipos de pandas provienen de NumPy, donde cada valor ocupa la misma cantidad de espacio; algo posible para números, pero no para texto de longitud variable. Así que, para almacenar texto, pandas guarda punteros a objetos de Python, y a esa estructura se la llama object.
Ahora, la regla importante: cada valor de una columna comparte el mismo tipo. Entonces, ¿qué ocurre cuando se mezclan números y texto en una sola columna?
import pandas as pd
mixed = pd.Series([1, "two", 3.0])
print(mixed)
print(mixed.dtype)0 1
1 two
2 3.0
dtype: objectPandas no genera ningún error: asigna object a toda la columna, recurriendo al tipo más permisivo disponible. La consecuencia no es llamativa, es silenciosa: la columna deja de ser numérica, por lo que .mean() fallará en ella y .sum() empezará a unir texto. Un N/A suelto en una sola fila de un archivo CSV provoca exactamente esto, y ese será el tema del capítulo seis.
¿Cuál de los dos tengo: una Series o un DataFrame?
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
column = sales["quantity"]
print(type(column))
print(type(sales))
print(column.mean())<class 'pandas.core.series.Series'>
<class 'pandas.core.frame.DataFrame'>
6.333333333333333Y aquí está la respuesta a la pregunta con la que abrimos este capítulo. sales["quantity"] no es una lista, es una Series, y una Series tiene .sum(), .mean(), .max() y los demás métodos.
También hay aquí un par de casos que desconcierta a todo el mundo la primera vez:
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(sales["quantity"])
print("---")
print(sales[["quantity"]])0 12
1 5
2 2
Name: quantity, dtype: int64
---
quantity
0 12
1 5
2 2Un solo corchete adicional y el resultado es un tipo de objeto completamente distinto. El primero es una Series: una columna, con su nombre y tipo impresos debajo. El segundo es un DataFrame: una tabla que casualmente tiene una sola columna, por lo que el encabezado de la columna se sitúa arriba.
La regla es que los corchetes interiores representan una lista de columnas, y pedir una lista te devuelve una tabla. Así, sales[["product", "price"]] produce una tabla de dos columnas.
¿Por qué tener en cuenta esta distinción? Porque aquí es donde los mensajes de error divergen. UnaSeriestiene.mean(), pero no nombres de columnas; unDataFrameselecciona columnas, pero no devuelve un solo número. Cuando un mensaje indica que un objeto no tiene dicho atributo, la primera pregunta es: ¿cuál de los dos tengo realmente en mis manos?
Un ejemplo completo
inventory.py:
import pandas as pd
# Product names become the index, so a row can be reached by name.
stock = pd.DataFrame(
{
"quantity": [12, 5, 2, 30],
"price": [15.0, 60.0, 850.0, 8.0],
},
index=["pen", "notebook", "bag", "eraser"],
)
print(stock)
print()
print("Shape :", stock.shape)
print("Columns :", list(stock.columns))
print("Index :", list(stock.index))
print()
# A column is a Series, so an operation applies to every value at once.
value = stock["quantity"] * stock["price"]
print("Value per product:")
print(value)
print()
print("Total value:", value.sum())quantity price
pen 12 15.0
notebook 5 60.0
bag 2 850.0
eraser 30 8.0
Shape : (4, 2)
Columns : ['quantity', 'price']
Index : ['pen', 'notebook', 'bag', 'eraser']
Value per product:
pen 180.0
notebook 300.0
bag 1700.0
eraser 240.0
dtype: float64
Total value: 2420.0Vale la pena fijarse en dos detalles.
En primer lugar, stock["quantity"] * stock["price"] multiplicó dos Series y produjo otra Series. Pandas las emparejó por índice: la cantidad de bolígrafos con el precio del bolígrafo, la de mochilas con el de la mochila. Por nombre, no por orden de fila. Ese comportamiento es la base para unir dos tablas más adelante.
En segundo lugar, el tipo de la columna resultante es float64, aunque quantity era int64. Multiplicar un número entero por un decimal da como resultado un decimal: la misma regla que en Python estándar; pandas no está haciendo nada nuevo aquí.
Cuando algo falla
KeyError: 'Quantity' Esa columna no existe. Los nombres de columna distinguen entre mayúsculas y minúsculas, y los nombres provenientes de un archivo CSV a menudo contienen espacios al principio o al final. Ejecuta print(list(df.columns)) para ver exactamente qué hay allí: imprimirlos como una lista añade comillas a cada uno, por lo que cualquier espacio quedará a la vista.
AttributeError: 'DataFrame' object has no attribute 'mean_price' df.column_name también obtiene una columna, pero solo con nombres simples; no cuando el nombre contiene un espacio, empieza por un dígito o coincide con un método de pandas como count. df["column_name"] funciona siempre, así que acostúmbrate a escribir esa forma.
TypeError: unsupported operand type(s) al realizar cálculos en una columna numérica La columna probablemente no sea numérica. Comprueba df.dtypes; ver un object allí significa que hay texto mezclado en alguna parte.
.sum() une texto en lugar de sumar números La misma causa: la columna es object. Un solo N/A o una celda vacía en una sola fila es suficiente para cambiar el tipo de toda la columna.
ValueError: All arrays must be of the same length Las listas pasadas a pd.DataFrame({...}) no tienen la misma longitud. Cada columna necesita el mismo número de valores, porque las filas deben alinearse sobre un índice compartido.
Paso 4 de 6 — Predice
Comprueba lo que has entendido
Se dieron tres valores. ¿Qué se imprime exactamente?
import pandas as pd
quantity = pd.Series([12, 5, 2])
print(quantity)- A12 5 2
- B[12, 5, 2]
- C12 5 2 dtype: int64
- D0 12 1 5 2 2 dtype: int64
Las dos líneas solo se diferencian por un par adicional de corchetes. ¿Qué indica type()?
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(type(sales["quantity"]))
print(type(sales[["quantity"]]))- AAmbas una Series
- BAmbas un DataFrame
- CLa primera una Series, la segunda un DataFrame
- DLa primera un DataFrame, la segunda una Series
Se han mezclado números y texto en una sola columna. ¿Qué hace pandas?
import pandas as pd
mixed = pd.Series([1, "two", 3.0])
print(mixed.dtype)- ANo lanza ningún error — asigna el tipo `object` a toda la columna
- BUn `TypeError` — dos tipos no pueden convivir en una misma columna
- CDescarta el texto y mantiene la columna como `float64`
- DMantiene un tipo independiente para cada valor
Para responder necesitas una cuenta
Inicia sesión para comprobar tus respuestas
Las preguntas están arriba, y resolverlas mentalmente es lo que de verdad importa. Inicia sesión para ver las respuestas, las explicaciones y las pistas de tres niveles.
Tu turno
Escribe un archivo llamado library.py que contenga un DataFrame de al menos cinco libros: los títulos como índice y columnas para la cantidad de ejemplares (números enteros), el precio (decimales) y el nombre del autor (texto).
Luego imprime:
- Toda la tabla, su
shapey susdtypes - La columna de precio por sí sola — y muestra con
type()que es unaSeries - La columna de precio con corchetes dobles — y muestra que esta es un
DataFrame - El valor total de cada libro (ejemplares × precio) y el total de todos ellos
Después, haz un experimento: reemplaza una de las cantidades de ejemplares por "unknown" y vuelve a ejecutar el archivo. ¿Qué indica dtypes ahora y qué ocurrió con el total?
Estos son los cinco minutos más valiosos de este capítulo. Una sola palabra en una celda cambia el tipo de toda una columna y rompe los cálculos aritméticos sin generar ningún error; y con datos reales, esto ocurre constantemente.
Paso 6 de 6
Reto — el quiz del capítulo
Diez preguntas de fácil a difícil. Las últimas son difíciles a propósito.
Inicia sesión para hacer el quiz