Capítulo 4 de 17 14 secciones 21 min

Compartir

Listas, tuplas y diccionarios

Guardar muchas cosas juntas, las rebanadas, y la copia que no se copió, que es el error más traicionero del principio.

Una lista guarda varios valores en orden y se puede cambiar. Una tupla es igual pero no se toca. Un diccionario guarda pares de clave y valor, que es como llega casi cualquier dato de una API. Y el error que yo cometí y que comete todo el mundo es creer que asignar una lista a otra variable la copia. No la copia. Las dos etiquetas apuntan a la misma lista.

Hola! Ahora guardamos muchas cosas juntas

En el capítulo 3 cada variable guardaba un solo valor. Eso alcanza para aprender y no alcanza para trabajar 🙂

En un archivo real tienes tres mil ventas, no una. Así que necesitas estructuras que guarden muchas cosas, y en Python son tres las que vas a usar todo el tiempo.

Antes de la tabla, piensa en el último archivo que abriste: ¿qué guardaba cada fila, una cosa sola o varias con nombre? Según cómo contestes eso, la estructura que necesitas ya está elegida 🙂

EstructuraSe escribeSe puede cambiarPara qué
Lista[1, 2, 3]Lo que sea que tenga orden y vaya cambiando
Tupla(1, 2, 3)NoCosas que no deben cambiar: coordenadas, una fila fija
Diccionario{'ciudad': 'Lima'}Datos con nombre. Es como llega cualquier API
Árbol para elegir estructura en Python: si cada dato tiene nombre, diccionario de clave y valor, que es como llega cualquier API. Si van en orden, lista cuando va a cambiar después y tupla cuando no.
Dos preguntas y la estructura queda decidida. La segunda es la que casi nadie se hace, y es la que evita que algo que no debía cambiar cambie en medio del proceso.

Listas: lo que más vas a usar

ciudades = ['Lima', 'Arequipa', 'Cusco', 'Piura', 'Trujillo']

print(ciudades[0])
print(ciudades[2])
print(len(ciudades))
Lima
Cusco
5

Se cuenta desde cero. Ya lo vimos en el capítulo 2 con el IndexError, y te lo repito porque es la fuente número uno de errores por uno de diferencia.

Ahora el atajo que te evita hacer cuentas: los índices negativos.

print(ciudades[-1])     # la ultima, siempre
print(ciudades[-2])     # la penultima
Trujillo
Piura

Nunca más ciudades[len(ciudades) - 1]. Yo escribí esa monstruosidad durante meses antes de que alguien me enseñara el -1 😅

Rebanadas, que es como se corta una lista

Los dos puntos dentro de los corchetes sacan un pedazo:

print(ciudades[0:3])     # de la 0 hasta ANTES de la 3
print(ciudades[:3])      # lo mismo, el 0 se sobreentiende
print(ciudades[2:])      # de la 2 hasta el final
print(ciudades[-2:])     # las dos ultimas
print(ciudades[::2])     # de dos en dos
['Lima', 'Arequipa', 'Cusco']
['Lima', 'Arequipa', 'Cusco']
['Cusco', 'Piura', 'Trujillo']
['Piura', 'Trujillo']
['Lima', 'Cusco', 'Trujillo']

El límite de la derecha no se incluye. Eso confunde al principio y tiene una gracia: ciudades[0:3] te da exactamente 3 elementos, y ciudades[:3] + ciudades[3:] te devuelve la lista entera sin repetir ni perder nada.

Y una que parece un truco de fiesta y sirve de verdad:

print(ciudades[::-1])
['Trujillo', 'Piura', 'Cusco', 'Arequipa', 'Lima']

Modificar una lista

ventas = [480.37, 524.90, 154.83]

ventas.append(2480.84)          # agrega al final
ventas.insert(0, 100.0)         # inserta en una posicion
print(ventas)

ultimo = ventas.pop()           # saca el ultimo Y te lo devuelve
print(ultimo, ventas)

ventas.remove(154.83)           # quita por valor, no por posicion
print(ventas)
[100.0, 480.37, 524.9, 154.83, 2480.84]
2480.84 [100.0, 480.37, 524.9, 154.83]
[100.0, 480.37, 524.9]

Ojo con remove: quita la primera aparición del valor, no todas. Y si el valor no está, revienta.

ventas.remove(999)
ValueError: list.remove(x): x not in list

Ordenar, y el error que da la primera vez

Hay dos formas de ordenar y confundirlas te deja con una variable vacía.

montos = [480.37, 154.83, 2480.84, 524.90]

ordenados = sorted(montos)          # devuelve una lista nueva
print(ordenados)
print(montos)                       # la original no se toco
[154.83, 480.37, 524.9, 2480.84]
[480.37, 154.83, 2480.84, 524.9]
montos.sort()                       # ordena la original y devuelve None
print(montos)
[154.83, 480.37, 524.9, 2480.84]

Y aquí el error clásico, que quiero que veas de frente:

resultado = montos.sort()
print(resultado)
None

Sí: None. Porque .sort() ordena la lista en el sitio y no devuelve nada. Si guardas su resultado, guardas la nada 🙃

La regla para acordarte: sorted() te da una lista nueva, .sort() modifica la que ya tienes. Cuando dudes, usa sorted(), que es más difícil de romper.

print(sorted(montos, reverse=True))
print(sorted(['Cusco', 'arequipa', 'Lima'], key=str.lower))
[2480.84, 524.9, 480.37, 154.83]
['arequipa', 'Cusco', 'Lima']

Ese key=str.lower es lo que evita que las mayúsculas se ordenen antes que todas las minúsculas, que es lo que pasa por defecto.

La copia que no se copió

Presta atención a esto, que es el error más traicionero del principio y no falla: te da un resultado equivocado sin decir nada.

original = [1, 2, 3]
copia = original          # esto NO es una copia

copia.append(4)
print('copia   :', copia)
print('original:', original)
copia   : [1, 2, 3, 4]
original: [1, 2, 3, 4]

Se modificaron las dos 😱

Acuérdate del capítulo 3: una variable es una etiqueta, no una caja. Con copia = original le pegaste una segunda etiqueta a la misma lista. No hay dos listas, hay una con dos nombres.

Se comprueba así:

print(copia is original)
True

Para copiar de verdad hay tres formas, y las tres valen:

original = [1, 2, 3]

copia_a = original.copy()
copia_b = list(original)
copia_c = original[:]        # una rebanada completa

copia_a.append(99)
print(original, copia_a)
print(copia_a is original)
[1, 2, 3] [1, 2, 3, 99]
False

Con esto te ahorro una tarde de "pero si yo no toqué esa variable". A mí me pasó con un dataframe y perdí medio día buscando el fantasma 👻

Tuplas: las que no se pueden cambiar

Una tupla es una lista que no se puede modificar. Se escribe con paréntesis.

coordenada = (-12.0464, -77.0428)      # Lima
print(coordenada[0])

coordenada[0] = 0
TypeError: 'tuple' object does not support item assignment

¿Y para qué querría algo que no puedo cambiar? Por tres razones muy concretas:

  • 🔒 Dice "esto no se toca". Un par de coordenadas, un rango de fechas fijo.
  • 🗝️ Puede ser clave de un diccionario. Una lista no puede.
  • 📦 Es lo que devuelve una función cuando devuelve varias cosas.

Ese último punto lo vas a ver todo el tiempo:

def minimo_y_maximo(numeros):
    return min(numeros), max(numeros)

bajo, alto = minimo_y_maximo([480.37, 154.83, 2480.84])
print(bajo, alto)
154.83 2480.84

Eso de repartir una tupla en varias variables se llama desempaquetado y es puro Python. Lo vamos a usar mucho en el capítulo 6.

Diccionarios: datos con nombre

Aquí está la estructura que más te va a servir en datos, porque es como llega la información del mundo real.

venta = {
    'cliente': 'C0045',
    'ciudad': 'Trujillo',
    'canal': 'Marketplace',
    'monto': 480.37,
    'unidades': 10,
}

print(venta['ciudad'])
print(venta['monto'] * 1.18)
Trujillo
566.8366

Ya no dependes de recordar que la ciudad estaba en la posición 1. Pides por nombre, que es como piensa una persona.

get, que evita que reviente

print(venta['descuento'])
KeyError: 'descuento'

Con corchetes, si la clave no existe, se cae. Con .get() te devuelve None, o lo que tú digas:

print(venta.get('descuento'))
print(venta.get('descuento', 0))
None
0

Ese segundo caso es oro puro cuando procesas datos que a veces traen un campo y a veces no, que es siempre 🌸

Recorrer un diccionario

for clave in venta:
    print(clave)
cliente
ciudad
canal
monto
unidades
for clave, valor in venta.items():
    print(f'{clave:10} {valor}')
cliente    C0045
ciudad     Trujillo
canal      Marketplace
monto      480.37
unidades   10

.items() es el que vas a usar el 90% de las veces. Los otros dos son .keys() y .values(), y se llaman igual de fácil.

Lo que de verdad te va a llegar: una lista de diccionarios

Junta las dos estructuras y tienes la forma en que viene cualquier respuesta de una API, cualquier JSON, cualquier exportación de un sistema.

ventas = [
    {'ciudad': 'Lima', 'canal': 'Web', 'monto': 480.37},
    {'ciudad': 'Cusco', 'canal': 'Tienda', 'monto': 154.83},
    {'ciudad': 'Lima', 'canal': 'WhatsApp', 'monto': 2480.84},
    {'ciudad': 'Piura', 'canal': 'Web', 'monto': 524.90},
]

total = 0
for v in ventas:
    total = total + v['monto']

print(f'Total: S/{total:,.2f}')
print(f'Ticket promedio: S/{total / len(ventas):,.2f}')
Total: S/3,640.94
Ticket promedio: S/910.24

Y ahora agrupemos por ciudad, que es la operación que más veces vas a hacer en tu vida con datos:

por_ciudad = {}

for v in ventas:
    ciudad = v['ciudad']
    por_ciudad[ciudad] = por_ciudad.get(ciudad, 0) + v['monto']

print(por_ciudad)
{'Lima': 2961.21, 'Cusco': 154.83, 'Piura': 524.9}

Guárdate ese patrón, que es un clásico: diccionario.get(clave, 0) + algo. La primera vez que aparece una ciudad, .get devuelve 0 y arranca la suma; las siguientes, sigue sumando.

En el capítulo 12 esto mismo se escribe así: df.groupby('ciudad') ['monto'].sum(). Una línea. Pero quería que vieras primero lo que hay por debajo, porque el día que groupby haga algo raro vas a saber qué está pasando 💜

Comprensiones de lista

Esta es la forma corta de crear una lista a partir de otra, y es tan de Python que se nota cuando alguien no la usa.

montos = [480.37, 154.83, 2480.84, 524.90]

# La forma larga
con_igv = []
for m in montos:
    con_igv.append(round(m * 1.18, 2))

# La forma de Python
con_igv_corto = [round(m * 1.18, 2) for m in montos]

print(con_igv)
print(con_igv == con_igv_corto)
[566.84, 182.7, 2927.39, 619.38]
True

Se lee de izquierda a derecha casi como una frase: "redondea eme por 1,18, para cada eme en montos".

Y se le puede poner una condición al final:

grandes = [m for m in montos if m > 500]
print(grandes)

ciudades = ['Lima', 'cusco', 'AREQUIPA']
print([c.title() for c in ciudades])
[2480.84, 524.9]
['Lima', 'Cusco', 'Arequipa']

Un consejo de alguien que se pasó de lista: si tu comprensión no cabe en dos líneas, escríbela como bucle normal. La gracia es que se lea mejor, no que sea más corta.

Conjuntos: para preguntar quién está y quién no

Hay una cuarta estructura que casi nunca sale en los tutoriales y que resuelve en una línea preguntas que con listas ocupan diez 🎯

Un conjunto es una bolsa sin repetidos y sin orden. Se escribe con llaves, como el diccionario, pero sin dos puntos.

enero = ['C0045', 'C0325', 'C0260', 'C0111', 'C0045']
febrero = ['C0325', 'C0777', 'C0045', 'C0888']

clientes_enero = set(enero)
clientes_febrero = set(febrero)

print('compras de enero      :', len(enero))
print('clientes distintos    :', len(clientes_enero))
print('compraron los dos meses:', sorted(clientes_enero & clientes_febrero))
print('solo enero, se fueron  :', sorted(clientes_enero - clientes_febrero))
print('solo febrero, nuevos   :', sorted(clientes_febrero - clientes_enero))
print('distintos en total     :', len(clientes_enero | clientes_febrero))
compras de enero      : 5
clientes distintos    : 4
compraron los dos meses: ['C0045', 'C0325']
solo enero, se fueron  : ['C0111', 'C0260']
solo febrero, nuevos   : ['C0777', 'C0888']
distintos en total     : 6

Eso es un análisis de retención entero en seis líneas 💜

  • 🤝 & es los que están en los dos: se quedaron.
  • - es los que están en el primero y no en el segundo: se fueron. Ojo al orden, que aquí sí importa.
  • | es todos, sin repetir.

Y la otra cosa para la que sirven: preguntar si algo está dentro. En una lista larga, Python va mirando uno por uno; en un conjunto va directo, sin recorrer nada.

print('C0045 compro en enero:', 'C0045' in clientes_enero)
print('C0999 compro en enero:', 'C0999' in clientes_enero)
C0045 compro en enero: True
C0999 compro en enero: False

Con cinco clientes da igual. Con doscientos mil, la lista tarda y el conjunto no. Si vas a preguntar in muchas veces sobre lo mismo, conviértelo a conjunto una vez y pregunta ahí 🏃‍♀️

Lo que un conjunto no tiene es orden, y eso es literal:

clientes_enero[0]
TypeError: 'set' object is not subscriptable

No hay primero. No hay posición. Si necesitas verlos ordenados, pásalo por sorted() como hice arriba, y si necesitas conservar el orden en que llegaron, entonces lo que querías era una lista 📋

Contar y agrupar sin escribir el bucle a mano

El ejercicio de contar por categoría lo hiciste con un diccionario y un get. Funciona perfecto y hay dos herramientas que lo dejan en una línea, las dos en el módulo collections 🧰

from collections import Counter, defaultdict

movimientos = [
    {'ciudad': 'Lima', 'categoria': 'Abarrotes', 'monto': 480.37},
    {'ciudad': 'Arequipa', 'categoria': 'Bebidas', 'monto': 154.83},
    {'ciudad': 'Lima', 'categoria': 'Abarrotes', 'monto': 2480.84},
    {'ciudad': 'Cusco', 'categoria': 'Limpieza', 'monto': 524.90},
    {'ciudad': 'Lima', 'categoria': 'Bebidas', 'monto': 890.00},
]

cuenta = Counter(v['ciudad'] for v in movimientos)

print(cuenta)
print('la que mas vende:', cuenta.most_common(1))
print('cuantas de Piura:', cuenta['Piura'])
Counter({'Lima': 3, 'Arequipa': 1, 'Cusco': 1})
la que mas vende: [('Lima', 3)]
cuantas de Piura: 0

Tres regalos ahí 🎁

Counter cuenta lo que le pases y ya viene ordenado de mayor a menor. most_common(3) te da el podio sin ordenar nada tú. Y una clave que no existe devuelve cero en vez de reventar, que es exactamente lo que quieres al contar.

Para agrupar, en vez de contar, está el otro:

por_ciudad = defaultdict(list)

for v in movimientos:
    por_ciudad[v['ciudad']].append(v['monto'])

for ciudad, montos in por_ciudad.items():
    print(f'{ciudad:<10} {len(montos)} ventas, S/{sum(montos):>8.2f}')
Lima       3 ventas, S/ 3851.21
Arequipa   1 ventas, S/  154.83
Cusco      1 ventas, S/  524.90

La gracia está en el defaultdict(list): cuando le pides una clave que no existe, se la inventa vacía en vez de fallar. Con un diccionario normal, la primera venta de Lima revienta:

sin_default = {}
sin_default['Lima'].append(480.37)
KeyError: 'Lima'

Por eso el patrón de siempre es if ciudad not in d: d[ciudad] = [] antes de cada append. Con defaultdict esa línea desaparece y el bucle se lee de un vistazo 🌸

Ordenar por dos cosas a la vez

Ya ordenaste una lista de números. Con una lista de diccionarios hay que decirle por qué campo, y ahí entra key:

ordenadas = sorted(movimientos, key=lambda v: (v['ciudad'], -v['monto']))

for v in ordenadas:
    print(f"{v['ciudad']:<10} {v['categoria']:<12} {v['monto']:>8.2f}")
Arequipa   Bebidas        154.83
Cusco      Limpieza       524.90
Lima       Abarrotes     2480.84
Lima       Bebidas        890.00
Lima       Abarrotes      480.37

Ciudad en orden alfabético, y dentro de cada ciudad los montos de mayor a menor 📊

El truco está en esa tupla que devuelve el lambda. Python compara tuplas de izquierda a derecha: primero mira la ciudad, y solo si empatan pasa al segundo elemento. Puedes poner tres o cuatro criterios, y se comparan en ese orden.

Y el signo menos delante del monto es el truco para invertir solo ese criterio. Con reverse=True le darías la vuelta a los dos, y las ciudades te saldrían de la Z a la A. El menos solo funciona con números; para invertir un texto hay que ordenar dos veces, primero por el criterio de dentro y después por el de fuera.

Ejercicios

1. Primero, último y cuántos

Con una lista de cinco canales, imprime el primero, el último y cuántos hay, sin contar a mano.

canales = ['Web', 'Tienda', 'WhatsApp', 'Marketplace', 'Telefono']

print(canales[0], canales[-1], len(canales))
Web Telefono 5
2. Las tres del medio

De esa misma lista, saca solo los elementos 2, 3 y 4 con una rebanada.

print(canales[1:4])
['Tienda', 'WhatsApp', 'Marketplace']

Recuerda que el límite derecho no entra: 1:4 te da las posiciones 1, 2 y 3.

3. El top 3 de montos

De una lista de montos, saca los tres más altos ordenados de mayor a menor, sin modificar la lista original.

montos = [480.37, 154.83, 2480.84, 524.90, 95.5, 1200.0]

top = sorted(montos, reverse=True)[:3]
print(top)
print(montos[0])          # la original intacta
[2480.84, 1200.0, 524.9]
480.37
4. Demuestra el aliasing

Crea una lista, hazle una copia falsa y una de verdad, y comprueba con is cuál es cuál.

pedido = ['Abarrotes', 'Bebidas', 'Snacks']
falsa = pedido
buena = pedido.copy()

falsa.append('Limpieza')
print(pedido)
print(buena)
print(falsa is pedido, buena is pedido)
['Abarrotes', 'Bebidas', 'Snacks', 'Limpieza']
['Abarrotes', 'Bebidas', 'Snacks']
True False

is pregunta si son el mismo objeto; == pregunta si tienen el mismo contenido. Dos listas distintas con lo mismo dentro dan True con == y False con is.

5. Un diccionario que no revienta

Con un diccionario de una venta que no trae la clave descuento, calcula el monto final asumiendo 0 si no está.

venta = {'cliente': 'C0325', 'monto': 524.90}

descuento = venta.get('descuento', 0)
print(f'S/{venta["monto"] * (1 - descuento):.2f}')
S/524.90

Fíjate en las comillas dobles dentro del f-string que abre con comillas simples. Si usas las mismas por fuera y por dentro, Python se pierde.

6. Contar por categoría

Con una lista de canales repetidos, cuenta cuántas veces aparece cada uno.

canales = ['Web', 'Tienda', 'Web', 'WhatsApp', 'Web', 'Tienda']

conteo = {}
for c in canales:
    conteo[c] = conteo.get(c, 0) + 1

print(conteo)
{'Web': 3, 'Tienda': 2, 'WhatsApp': 1}

Es el mismo patrón de la suma por ciudad, pero sumando 1 en vez del monto. Cuando lo veas escrito así vas a reconocer value_counts() de pandas sin que nadie te lo explique.

7. El máximo con nombre

De una lista de diccionarios de ventas, encuentra la de mayor monto e imprime su ciudad.

ventas = [
    {'ciudad': 'Lima', 'monto': 480.37},
    {'ciudad': 'Cusco', 'monto': 2480.84},
    {'ciudad': 'Piura', 'monto': 524.90},
]

mayor = max(ventas, key=lambda v: v['monto'])
print(mayor['ciudad'], mayor['monto'])
Cusco 2480.84

Ese lambda es una función chiquita sin nombre que dice "para comparar, mira el monto". Lo vemos con calma en el capítulo 6, tranqui.

8. Comprensión con condición

De la lista de ventas anterior, arma una lista solo con las ciudades cuyo monto pase los S/500.

print([v['ciudad'] for v in ventas if v['monto'] > 500])
['Cusco', 'Piura']
9. Quitar duplicados conservando el orden

De una lista con ciudades repetidas, saca las únicas. Hazlo de dos formas y compara.

ciudades = ['Lima', 'Cusco', 'Lima', 'Piura', 'Cusco', 'Lima']

print(sorted(set(ciudades)))

vistas = []
for c in ciudades:
    if c not in vistas:
        vistas.append(c)
print(vistas)
['Cusco', 'Lima', 'Piura']
['Lima', 'Cusco', 'Piura']

set() es rapidísimo y pierde el orden original; el bucle lo conserva. Cuál usar depende de si el orden significa algo, y muchas veces significa.

10. La tupla que no se deja

Crea una tupla con un rango de fechas e intenta cambiar el primer valor. Después haz lo mismo con una lista y compara.

rango_tupla = ('2026-01-01', '2026-12-31')
rango_lista = ['2026-01-01', '2026-12-31']

rango_lista[0] = '2026-02-01'
print(rango_lista)

rango_tupla[0] = '2026-02-01'
TypeError: 'tuple' object does not support item assignment

La tupla te protege de un cambio accidental. Cuando algo no debe moverse en todo el programa, esa protección vale más que la flexibilidad.

11. Quién se fue y quién llegó

Con tres meses de códigos de cliente, di cuántos se mantuvieron, cuántos se perdieron y cuántos entraron nuevos.

meses = {
    'enero': {'C0045', 'C0325', 'C0260', 'C0111'},
    'febrero': {'C0325', 'C0045', 'C0777', 'C0888'},
    'marzo': {'C0045', 'C0888', 'C0999'},
}

fieles = meses['enero'] & meses['febrero'] & meses['marzo']
alguna_vez = meses['enero'] | meses['febrero'] | meses['marzo']

print('clientes distintos en el trimestre:', len(alguna_vez))
print('compraron los tres meses          :', sorted(fieles))
print('perdidos de enero a febrero       :',
      sorted(meses['enero'] - meses['febrero']))
print('nuevos en marzo                   :',
      sorted(meses['marzo'] - meses['enero'] - meses['febrero']))
print('retencion enero -> febrero        :',
      round(len(meses['enero'] & meses['febrero']) / len(meses['enero']) * 100), '%')
clientes distintos en el trimestre: 7
compraron los tres meses          : ['C0045']
perdidos de enero a febrero       : ['C0111', 'C0260']
nuevos en marzo                   : ['C0999']
retencion enero -> febrero        : 50 %

Un cliente de cuatro aguantó los tres meses, y la retención del primer mes al segundo es del 50% 📉

Con datos de verdad esto es el análisis de cohortes entero, y sale de tres conjuntos y cuatro operaciones. Ni una librería.

12. El podio, sin ordenar nada

Cuenta las ventas por categoría y saca las dos más frecuentes.

categorias = Counter(v['categoria'] for v in movimientos)

print(categorias)
print('el podio     :', categorias.most_common(2))
print('cuantas hay  :', len(categorias), 'categorias distintas')
print('total ventas :', sum(categorias.values()))
print('una que no esta:', categorias['Congelados'])
Counter({'Abarrotes': 2, 'Bebidas': 2, 'Limpieza': 1})
el podio     : [('Abarrotes', 2), ('Bebidas', 2)]
cuantas hay  : 3 categorias distintas
total ventas : 5
una que no esta: 0

Ese último cero es la razón por la que uso Counter y no un diccionario normal 🛡️

Preguntar por una categoría que no vino este mes devuelve cero, que es la respuesta correcta. Un diccionario normal te lanzaría un KeyError y te tumbaría el reporte por una categoría que simplemente no vendió.

13. Agrupar montos por ciudad

Junta los montos de cada ciudad y saca el total, el promedio y el máximo de cada una.

agrupado = defaultdict(list)
for v in movimientos:
    agrupado[v['ciudad']].append(v['monto'])

for ciudad in sorted(agrupado):
    montos = agrupado[ciudad]
    print(f'{ciudad:<10} {len(montos)} ventas  '
          f'total S/{sum(montos):>9.2f}  '
          f'promedio S/{sum(montos) / len(montos):>8.2f}  '
          f'mayor S/{max(montos):>8.2f}')
Arequipa   1 ventas  total S/   154.83  promedio S/  154.83  mayor S/  154.83
Cusco      1 ventas  total S/   524.90  promedio S/  524.90  mayor S/  524.90
Lima       3 ventas  total S/  3851.21  promedio S/ 1283.74  mayor S/ 2480.84

Ese sorted(agrupado) del bucle ordena las claves alfabéticamente, para que la tabla salga siempre igual. Sin él salen en el orden en que aparecieron, que depende del archivo y cambia cada mes 🔀

Y esto es, literalmente, lo que hace groupby de pandas. Cuando llegues a ese capítulo vas a saber qué está pasando por debajo.

14. Un ranking con dos criterios

Ordena las ventas por ciudad y, dentro de cada ciudad, de mayor a menor monto. Después dale la vuelta al criterio de fuera.

print('ciudad A-Z, monto de mayor a menor:')
for v in sorted(movimientos, key=lambda v: (v['ciudad'], -v['monto'])):
    print(f"  {v['ciudad']:<10} {v['monto']:>8.2f}")

print('y ahora ciudad Z-A, monto de menor a mayor:')
for v in sorted(movimientos, key=lambda v: (v['ciudad'], -v['monto']), reverse=True):
    print(f"  {v['ciudad']:<10} {v['monto']:>8.2f}")
ciudad A-Z, monto de mayor a menor:
  Arequipa     154.83
  Cusco        524.90
  Lima        2480.84
  Lima         890.00
  Lima         480.37
y ahora ciudad Z-A, monto de menor a mayor:
  Lima         480.37
  Lima         890.00
  Lima        2480.84
  Cusco        524.90
  Arequipa     154.83

Mira lo que hizo el reverse=True: le dio la vuelta a los dos criterios 🔄

Las ciudades pasaron de la Z a la A, pero los montos, que iban de mayor a menor por el signo menos, ahora van de menor a mayor. El reverse no distingue: invierte la comparación entera.

Por eso el signo menos es mejor herramienta cuando quieres criterios en direcciones distintas. Es más quisquilloso de escribir y te deja controlar cada uno por separado 🎛️

Quitar duplicados y quedarte con uno

Esta es la primera trampa de listas con la que se topa todo el mundo, y no avisa de ninguna manera: no revienta, no imprime nada raro, y la lista sale más corta como esperabas 🫥

La trampa

Limpias duplicados recorriendo la lista y quitando lo que sobra. Corre, no da error, y la lista sale más corta.

ciudades = ['lima', 'lima', 'cusco']

for c in ciudades:
    if c == 'lima':
        ciudades.remove(c)

print(ciudades)   # ['lima', 'cusco']
Qué está mal

Sobrevivió una Lima 👻 Al quitar la primera, todo lo de detrás se corre un sitio hacia la izquierda, pero el bucle ya va por la posición 1. Se salta la segunda Lima sin mirarla.

Y esto es lo peligroso: la lista se acortó, así que parece que funcionó. Con duplicados sueltos acierta, con duplicados pegados falla, y nadie revisa un limpiador de duplicados que devuelve menos filas de las que entraron.

Nunca se modifica una lista mientras se recorre. Se construye una nueva con una comprensión ([c for c in ciudades if c != 'lima']), que además se lee mejor y es más rápida.

Comprueba que lo tienes

Haces b = a con dos listas, cambias b y a también cambió. ¿Por qué?

  • Porque las dos etiquetas apuntan a la misma lista
  • Porque las listas se copian solas
  • Porque hay que usar tuplas para que no cambien
  • Porque b quedó dentro de a

Lo que te llevas

  • 🔢 Se cuenta desde 0, y [-1] es siempre el último.
  • ✂️ En una rebanada, el límite de la derecha no entra.
  • ⚠️ copia = original no copia nada: usa .copy().
  • 🔀 sorted() devuelve una lista nueva; .sort() devuelve None.
  • 🗂️ Una lista de diccionarios es como llega el mundo real, y agrupar con .get(clave, 0) es el patrón que después hace groupby.

Y si de todo el capítulo te llevas una sola frase, que sea esta:

Un diccionario no es una lista con nombres. Es la respuesta a "y esto de quién es".

Y si un diccionario te suena a "una fila con los nombres de las columnas", vas bien: es exactamente lo que devuelve una consulta, y de eso va el libro de SQL 🗃️

En el capítulo 5 le enseñamos a Python a decidir y a repetir: if, for y while. Ahí es donde esto deja de ser una calculadora y empieza a ser un programa.

Que tengas un hermoso día! 🌟

Preguntas frecuentes

¿Qué son las listas en Python?

Una secuencia ordenada de cosas que se puede modificar. Se escribe entre corchetes y el primer elemento es el cero.

¿Qué son los diccionarios en Python?

Pares de clave y valor. En vez de pedir el elemento tres pides el elemento "ciudad", que es como llegan casi todos los datos de una API.

¿Cuál es la diferencia entre lista y tupla?

La tupla no se puede modificar después de creada. Se usa para cosas que no deberían cambiar, como unas coordenadas.

¿Cómo copio una lista sin que se copie sola?

Con lista.copy() o con una rebanada completa. Si escribes b = a, las dos apuntan a la misma lista y modificar una modifica la otra, que es el error que más desconcierta al principio.

Practica este capítulo 📓

Todo el código de arriba en un cuaderno que corre de principio a fin, y los ejercicios con una celda vacía para que los hagas tú. Se abre en Google Colab de un clic y no hay que instalar nada. Donde veas %%revisa, escribe tu respuesta y el cuaderno te dice si te salió.

¿Prefieres trabajar en tu máquina? Bájate el cuaderno de práctica o el de soluciones. Todos están también en github.com/soymissyera/MissYeraEjercicios.

¿Le sirve a alguien que conoces?

Pásale el libro. Es gratis, está entero y no pide registro 🐣

Instagram y TikTok no dejan compartir enlaces desde la web: esos dos copian la URL para que la pegues en tu historia.

¿Tienes alguna duda o consulta?