La idea
Un solo precio cabe en una variable. Un carrito, una semana de pedidos o una hoja de stock necesitan una colección: un nombre que guarda muchos valores. Python tiene dos que usarás todo el tiempo, y la diferencia es cómo encuentras un valor dentro.
Una lista encuentra valores por posición: primero, segundo, último. Un diccionario los encuentra por clave: "web", "marketplace", el nombre de un producto. Elige la que corresponda a la pregunta que haces. "¿Qué pasó el día 3?" es una posición. "¿Cuántos pedidos vinieron de la app?" es una clave.
Figura 1
Pedidos por día, primera semana de enero: posiciones y slices
| position | date | orders |
|---|---|---|
| 0 | 2023-01-01 | 1 |
| 1 | 2023-01-02 | 8 |
| 2 | 2023-01-03 | 6 |
| 3 | 2023-01-04 | 4 |
| 4 | 2023-01-05 | 10 |
| 5 | 2023-01-06 | 9 |
| 6 | 2023-01-07 | 5 |
| position | orders |
|---|---|
| 1 | 8 |
| 2 | 6 |
| 3 | 4 |
- Una lista guarda sus valores en orden, y cada valor tiene una posición. Las posiciones empiezan en 0, así que el primer día es
daily[0], que vale 1. - Una posición negativa cuenta desde el final.
daily[-1]es el último valor, 5, sin necesidad de saber la longitud. - Un slice
daily[1:4]empieza en la posición 1 y para antes de la 4. Salen tres valores: las posiciones 1, 2 y 3. sum(daily[1:4])son 18 pedidos de lunes a miércoles. El final de un slice es exclusivo, así quedaily[1:4]tiene4 - 1 = 3elementos.
Por qué existe
Imagina sumar un carrito con una variable por línea: line1, line2, line3. Funciona con tres líneas y se desmorona con cuatro. Y no puedes escribir código que sirva para un carrito de cualquier tamaño, porque los nombres quedan fijos cuando escribes el programa. Una lista deja que los datos decidan su tamaño. Un diccionario resuelve el problema de emparejar: cuando el stock es "Alder Oak Coffee Table: 12", recorrer una lista buscando el nombre cada vez es lento y torpe, mientras que un diccionario salta directo a la respuesta.
Junto con un bucle, esto es la primera versión de lo que pandas hará por ti más adelante. Una columna de pandas actúa como una lista, una fila como un diccionario, y groupby es el bucle de conteo de abajo, escrito una vez y ejecutado sobre millones de filas.
Cómo funciona por dentro
Listas: posición, slices y longitud
Una lista está ordenada, y cada elemento tiene una posición que empieza en 0. len(daily) es 7, la última posición es len(daily) - 1, y daily[-1] llega a ella desde el otro extremo. Un slice daily[start:stop] toma desde start hasta stop sin incluirlo, así que el número de elementos es stop - start. Los slices hacen copias, y pasarse del final se perdona: daily[5:100] devuelve lo que exista. Una posición suelta pasada del final es un IndexError.
Las listas se pueden cambiar en el sitio. append añade un elemento, y daily[0] = x reemplaza uno. Dos nombres que apuntan a la misma lista ven el mismo cambio, lo que sorprende a la gente en el capítulo 3.
Diccionarios: claves y valores
Un diccionario es un conjunto de pares clave-valor. Las claves son únicas, y encontrar un valor por su clave tarda igual de poco con 3 claves que con 3 millones. Así encuentra pandas una columna por su nombre, y así encuentra SQL una fila por su clave primaria. d['web'] lee una clave y falla con KeyError si no está. d.get('web', 0) la lee y recurre a 0 cuando no está. Asignar d['app'] = 1 añade la clave si es nueva y reemplaza el valor si existe.
Bucles y el acumulador
Un bucle for ejecuta su bloque con sangría una vez por elemento, con la variable del bucle puesta en el actual. La mayoría de los bucles reales hacen el mismo trabajito: mantienen algo que crece. Ese algo es el acumulador, y debe existir antes de que empiece el bucle.
Figura 2
Contar pedidos por canal con un diccionario
| order_id | channel |
|---|---|
| 1001 | web |
| 1002 | web |
| 1003 | marketplace |
| 1004 | marketplace |
| 1005 | web |
| 1006 | marketplace |
| 1007 | marketplace |
| 1008 | web |
| 1009 | app |
| 1010 | web |
| key | value |
|---|---|
| web | 5 |
| marketplace | 4 |
| app | 1 |
- Diez pedidos, cada uno con su canal. Queremos un número por canal, y los canales no se conocen de antemano.
- Un diccionario guarda pares: una clave y su valor. Empezamos vacíos. El primer pedido es
web, que todavía no es una clave, así quecounts.get('web', 0)da 0 y guardamos 0 + 1. - Cada
webposterior encuentra su clave y suma 1. Marketplace y app aparecen la primera vez que los vemos, y cada uno empieza en 1. - Al terminar el bucle, el diccionario es la respuesta: 5 web, 4 marketplace, 1 app, diez pedidos en total. Buscar
counts['app']es instantáneo, sin importar cuántas claves haya.
Contar por canal es un acumulador cuyo valor es un diccionario. Un total es un acumulador cuyo valor es un número.
Figura 3
Un total acumulado sobre un carrito de cuatro líneas
| price | qty |
|---|---|
| 92.99 | 1 |
| 20.99 | 2 |
| 149.00 | 1 |
| 66.99 | 2 |
| pass | total |
|---|---|
| 0 | 0.00 |
| 1 | 92.99 |
| 2 | 134.97 |
| 3 | 283.97 |
| 4 | 417.95 |
- Antes del bucle,
total = 0. Este es el acumulador: una variable que vive fuera del bucle y recuerda lo visto hasta ahora. - La vuelta 1 toma la primera línea:
total += price * qtysuma 92.99 × 1. El cuerpo del bucle se ejecutó una vez, ytotalvale 92.99. - Las vueltas 2 y 3 suman 41.98 y 149.00. Cada vuelta parte del total que dejó la anterior.
- La vuelta 4 suma 133.98 y el bucle termina porque no quedan más líneas. El carrito vale 417.95. Si pusieras
total = 0dentro del bucle, cada vuelta olvidaría la anterior.
La forma es siempre la misma: crear el acumulador, recorrer, actualizarlo con el elemento actual, usarlo después del bucle. Si la respuesta es incorrecta, el fallo casi siempre es uno de estos cuatro: se creó dentro del bucle, se actualizó con = en vez de +=, la actualización quedó con sangría fuera del bucle, o el resultado se usó antes de que el bucle terminara.
Comprehensions
Una list comprehension es un bucle que construye una lista, escrito en una línea: [p for p in products if p['price'] < 20]. Léela como "p, para cada p en products, si el precio es menor que 20". Es el mismo bucle, con el acumulador escondido. Úsala para filtrar o transformar; usa un bucle normal cuando el cuerpo necesite más de un paso.
Cuál elegir
Pregúntate qué harás con los datos. Si el orden importa y los vas a recorrer, cortar o tomar el primero y el último, usa una lista: pedidos por día, precios de un carrito. Si vas a buscar por un nombre, o contar por una etiqueta, usa un diccionario: stock por producto, pedidos por canal. Cuando necesites ambas cosas, usa una lista de diccionarios, uno por fila, con las mismas claves en cada uno. Esa es exactamente la forma de una tabla, y la forma con la que pandas construye un DataFrame.
Errores comunes
"El primer elemento de una lista es el número 1"
Las posiciones empiezan en 0, así que daily[1] es el segundo día. Es consecuencia de lo que es una posición: la distancia desde el inicio, y el primer elemento está a 0 pasos. Cuando un resultado se desvía en uno, revisa si contaste desde 1 y Python desde 0. En un slice, recuerda que el final se excluye: daily[0:7] son los siete días.
"Un diccionario tiene orden como una lista"
Recuerda el orden en que se añadieron las claves, pero no puedes pedir "la tercera entrada" como se lo pides a una lista. Pides una clave. Si necesitas "el canal principal", recorres los pares y te quedas con el mayor tú mismo, o ordenas los elementos, que es lo que hacen los niveles a continuación.
"Puedo cambiar la lista que estoy recorriendo"
Añadir o quitar elementos de una lista mientras la recorres hace que el bucle se salte elementos o no termine nunca. Construye una lista nueva, con una comprehension o añadiendo a una lista vacía. El bucle lee una lista y escribe otra, y ninguna molesta a la otra.
Ponte a prueba
Una pregunta. Al responderla, la lección queda marcada en el mapa; nunca bloquea un nivel.
No del todo. Un diccionario normal no crea claves cuando las lees. Para eso están .get(key, 0), una comprobación if key in counts o un defaultdict.Prueba otra respuesta.
Correcto. += primero lee counts['web'], y leer una clave que no está lanza KeyError. Empieza la cuenta con counts.get(channel, 0) + 1.
No del todo. Los diccionarios conservan el orden en que se insertaron las claves. El orden no es el problema; la clave que falta sí.Prueba otra respuesta.
No del todo. Aquí nada se salta en silencio. La primera lectura de una clave que falta detiene el programa con un error.Prueba otra respuesta.
Lección hecha: ya está marcada en el mapa.