La idea
Un DataFrame es una tabla que vive en la memoria de Python. Tiene filas hacia abajo, columnas hacia el lado y una etiqueta en cada fila llamada índice. orders, order_items y customers de Harbor & Pine son DataFrames, cargados para ti antes del primer nivel de los capítulos de pandas.
Figura 1
Las partes de un DataFrame: índice, columnas y valores
| index | order_id | status | channel | shipping_fee |
|---|---|---|---|---|
| 0 | 1001 | completed | web | 0.00 |
| 1 | 1002 | completed | web | 0.00 |
| 2 | 1003 | completed | marketplace | 4.99 |
| 3 | 1004 | completed | marketplace | 4.99 |
| index | channel |
|---|---|
| 0 | web |
| 1 | web |
| 2 | marketplace |
| 3 | marketplace |
- Este es el comienzo del DataFrame
orders. Parece una tabla porque lo es: filas hacia abajo, columnas hacia el lado. - La primera columna no son datos. Es el índice: una etiqueta para cada fila, aquí los números 0, 1, 2, 3. pandas lo añade solo y lo usa para seguir la pista de las filas.
- Cada una de las otras columnas tiene un nombre y guarda un único tipo de valor de arriba abajo.
shipping_feeson todo números,channeles todo texto. - Si eliges una columna con
orders['channel']obtienes una Series: una sola columna que lleva consigo el mismo índice.
Lo importante es cómo funcionan las columnas. Un DataFrame no es una cuadrícula de celdas independientes, como una hoja de cálculo. Es un conjunto de columnas, y cada columna es un arreglo con tipo: un nombre, un tipo de valor, un valor por fila. Todo lo que harás en pandas es una operación sobre columnas enteras: sumar dos, comparar una con un número, agrupar por una, sumar otra.
Por qué existe
Python a secas te da listas. Una lista de 30.000 precios funciona, pero para duplicarlos escribes un bucle, y un bucle que recorre 30.000 elementos en Python es lento y ruidoso. Para mantener una segunda lista de cantidades alineada con los precios, tienes que recordar que la posición 4.711 de una significa la posición 4.711 de la otra. Nada lo garantiza.
Un DataFrame resuelve los dos problemas. Las columnas se guardan una al lado de otra y siempre tienen la misma longitud, así que la fila 4.711 es la misma fila en todas las columnas. Y las operaciones actúan sobre la columna entera de una vez, en código compilado rápido, de modo que una pregunta sobre 30.000 líneas de pedido tarda un parpadeo y no un café.
Cómo funciona por dentro
Filas, columnas, índice
orders.shape da (12000, 7): 12.000 filas y 7 columnas. El índice es aparte de las 7 columnas. Por defecto son los números de 0 a 11999, y su trabajo es etiquetar filas para que pandas pueda alinear cosas. Cuando filtras u ordenas, las filas conservan sus etiquetas originales, por eso un resultado puede empezar en la fila 3.418 y no en la 0. reset_index() las renumera cuando quieres eso.
Columnas con tipo
Figura 2
Cada columna tiene un solo dtype
| column | dtype | example |
|---|---|---|
| order_id | int64 | 1001 |
| status | object | completed |
| shipping_fee | float64 | 4.99 |
| order_date | object | 2023-01-01 20:51:19 |
orders.info()uorders.dtypesmuestra el dtype de cada columna: el tipo de valor que guarda.order_idesint64, números enteros.shipping_feeesfloat64, números con decimales. Con ambos se puede hacer aritmética.statusesobject, que por ahora significa texto. Puedes compararlo con'completed', pero no sumarlo.order_datetambién esobject: el archivo guarda las fechas como texto, así que pandas todavía no las trata como fechas. Un dtype equivocado es la razón habitual de que un cálculo falle o ordene raro.
Una columna guarda un dtype, y el dtype decide qué puedes hacer. Los números se suman y se promedian. El texto se compara y se corta. Las fechas se restan. Si un número llega como texto ("$1,249.00" en un archivo de proveedor, por ejemplo), ninguna aritmética funciona hasta que lo conviertes. Por eso los capítulos de pandas empiezan con info(): muestra el dtype de cada columna y cuántos valores no faltan. Una columna como coupon_code tiene huecos, y pandas los muestra como NaN.
Operaciones vectorizadas
Una operación sobre una columna se aplica a cada valor, fila a fila, y devuelve una columna.
Figura 3
Una expresión sobre una columna, sin bucle
| quantity | unit_price | discount_pct |
|---|---|---|
| 9 | 31.99 | 0.0 |
| 3 | 11.99 | 0.0 |
| 4 | 11.99 | 0.0 |
| 1 | 149.00 | 0.0 |
| line_total |
|---|
| 287.91 |
| 35.97 |
| 47.96 |
| 149.00 |
- Cuatro líneas de pedido de
order_items. Queremos el dinero de cada línea: cantidad por precio. order_items['quantity'] * order_items['unit_price']multiplica las dos columnas fila a fila: la primera con la primera, la segunda con la segunda, y así sucesivamente.- El resultado es una columna nueva con un valor por fila. No hubo
for, ni contador, ni una primera fila tratada de forma especial.
De ahí se siguen tres cosas. El resultado tiene la misma longitud y el mismo índice que los datos de entrada, así que encaja con la tabla de la que salió. Puedes guardarlo como columna nueva: order_items['line_total'] = order_items['quantity'] * order_items['unit_price']. Y es rápido, porque pandas entrega la columna entera a código compilado en vez de ejecutar Python una vez por fila. Lo mismo ocurre con una comparación: orders['shipping_fee'] > 0 da una columna de True y False, una por pedido. Esa es la base del filtrado.
Un DataFrame, una tabla SQL y una hoja de cálculo
Los tres guardan filas y columnas, y se distinguen en cómo trabajas con ellos.
- Hoja de cálculo. Cada celda es independiente, y una fórmula se escribe una vez por celda. Los tipos son laxos: una celda puede tener un número y la siguiente texto. Ves los datos y los editas a mano.
- Tabla SQL. Las columnas tienen tipo y las filas no tienen orden salvo que lo pidas. Los datos viven en una base de datos; envías una consulta y recibes un resultado. Un cambio es una sentencia como
UPDATE. - DataFrame. Las columnas tienen tipo como en SQL, pero la tabla está en la memoria de tu sesión y tiene un índice que conserva el orden de las filas. Ejecutas una operación tras otra, y cada paso devuelve un DataFrame o una Series nuevos a los que puedes poner nombre y guardar.
El mismo trabajo en cada uno: "total de envío por canal" es una fórmula SUM por grupo en una hoja de cálculo, un GROUP BY en SQL y orders.groupby('channel')['shipping_fee'].sum() en pandas. La lectura es la misma; solo cambia el vocabulario.
Errores comunes
"Necesito un bucle para hacer algo con cada fila"
Casi nunca. Los bucles son el hábito de Python a secas, y funcionan sobre un DataFrame, pero son lentos y largos. orders['shipping_fee'] * 2 es una línea y duplica las 12.000 tarifas. Recurre a un bucle solo cuando el resultado de una fila dependa de verdad del de la anterior. Si te ves escribiendo for i in range(len(df)), busca primero la operación sobre columnas.
"El índice es simplemente la primera columna"
El índice no es una de las 7 columnas de orders. Etiqueta filas y no cuenta en shape. Como viaja con los datos, un resultado filtrado conserva las etiquetas viejas: la tercera fila del resultado puede llevar la etiqueta 2.041. Usa .iloc para "la tercera fila" y .loc para "la fila con etiqueta 2.041", como verás en el capítulo 5.
"Un DataFrame se comporta como una hoja de cálculo: cada celda es independiente"
En pandas una celda nunca se edita aislada con una fórmula que vive en ella. Las operaciones actúan sobre columnas, y una columna tiene un dtype. Por eso un solo valor de texto suelto como "n/a" en una columna de precios convierte toda la columna en object, y entonces sum() no puede sumarla. Corrige el valor o convierte la columna, y la aritmética vuelve.
Ponte a prueba
Una pregunta. Al responderla, la lección queda marcada en el mapa; nunca bloquea un nivel.
No del todo. Multiplicar no colapsa una columna. Solo una agregación como sum() devuelve un número; * 2 conserva un valor por fila.Prueba otra respuesta.
Correcto. La operación se aplica a cada valor de la columna y devuelve una Series nueva con el mismo índice. orders no se toca.
No del todo. Las operaciones devuelven resultados nuevos. La columna original cambia solo cuando asignas el resultado de vuelta, como en orders['shipping_fee'] = ....Prueba otra respuesta.
No del todo. Recorrer con un bucle es justo lo que eliminan las operaciones vectorizadas. Una columna float se puede multiplicar entera.Prueba otra respuesta.
Lección hecha: ya está marcada en el mapa.