Python · Capítulo 2 · Lección de conceptos · 6 min de lectura

Encontrar valores por posición, por clave y uno a uno

Una lista encuentra valores por posición, un diccionario por clave, y un bucle los visita uno a uno mientras un acumulador recuerda lo que ya ha visto.

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

daily = [1, 8, 6, 4, 10, 9, 5]
positiondateorders
02023-01-011
12023-01-028
22023-01-036
32023-01-044
42023-01-0510
52023-01-069
62023-01-075
daily[1:4]
positionorders
18
26
34

Paso 1 de 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.

  1. 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.
  2. Una posición negativa cuenta desde el final. daily[-1] es el último valor, 5, sin necesidad de saber la longitud.
  3. 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.
  4. sum(daily[1:4]) son 18 pedidos de lunes a miércoles. El final de un slice es exclusivo, así que daily[1:4] tiene 4 - 1 = 3 elementos.

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

channels (pedidos 1001 a 1010)
order_idchannel
1001web
1002web
1003marketplace
1004marketplace
1005web
1006marketplace
1007marketplace
1008web
1009app
1010web
counts (un diccionario)
keyvalue
web5
marketplace4
app1

Paso 1 de 4 Diez pedidos, cada uno con su canal. Queremos un número por canal, y los canales no se conocen de antemano.

  1. Diez pedidos, cada uno con su canal. Queremos un número por canal, y los canales no se conocen de antemano.
  2. 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í que counts.get('web', 0) da 0 y guardamos 0 + 1.
  3. Cada web posterior encuentra su clave y suma 1. Marketplace y app aparecen la primera vez que los vemos, y cada uno empieza en 1.
  4. 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

Pedido 1004: el carrito
priceqty
92.991
20.992
149.001
66.992
total tras cada vuelta
passtotal
00.00
192.99
2134.97
3283.97
4417.95

Paso 1 de 4 Antes del bucle, total = 0. Este es el acumulador: una variable que vive fuera del bucle y recuerda lo visto hasta ahora.

  1. Antes del bucle, total = 0. Este es el acumulador: una variable que vive fuera del bucle y recuerda lo visto hasta ahora.
  2. La vuelta 1 toma la primera línea: total += price * qty suma 92.99 × 1. El cuerpo del bucle se ejecutó una vez, y total vale 92.99.
  3. Las vueltas 2 y 3 suman 41.98 y 149.00. Cada vuelta parte del total que dejó la anterior.
  4. 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 = 0 dentro 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.

Al contar pedidos por canal, alguien escribe counts[channel] += 1 dentro del bucle, sin .get y sin comprobar si la clave existe. El primer pedido es web. ¿Qué pasa?

Elige qué puede ejecutarse en este sitio. Puedes cambiarlo cuando quieras desde "Configurar cookies", al pie de cada página.