UdelaR  ·  Máster en Análisis de Datos

Introducción a Python

Clase 4 — Condicionales y flujo de programa

Asignatura  Introducción a Python Profesor  Daniel Miles Touya Universidad  UdelaR-Uvigo
Parte I

El flujo de ejecución

El flujo de ejecución: cómo decide un programa qué hacer teoría

Por defecto, Python ejecuta las instrucciones de un programa línea a línea, de arriba a abajo. Cada línea se ejecuta exactamente una vez y en orden. A esto se le llama flujo secuencial.

Pero casi ningún programa real es puramente secuencial. Para que un programa pueda tomar decisiones, repetir acciones o reutilizar código, necesita alterar ese flujo. En Python hay tres mecanismos para hacerlo:

Decisión

Ejecutar un bloque u otro según una condición

if / elif / else
match / case

← esta clase

Repetición

Ejecutar un bloque múltiples veces

for
while

→ Clase 5

Llamada a función

Saltar a otro bloque de código y volver

def
return

→ Clase 6

— Flujo secuencial vs flujo con decisión —

# Flujo secuencial: todas las líneas se ejecutan siempre
temperatura = 35

print("Comprobando temperatura...")
print(f"Temperatura actual: {temperatura}°C")
print("Temperatura comprobada.")

# ¿Cómo avisamos solo si hace mucho calor?
# Con flujo secuencial no podemos — se ejecuta todo siempre
# Flujo con decisión: solo se ejecuta el bloque correcto
temperatura = 35

print("Comprobando temperatura...")

if temperatura > 30:
    print("⚠ Alerta: temperatura muy alta")
    print("Recomendación: mantente hidratado")

print("Temperatura comprobada.")

# Cambia temperatura a 20 y observa qué cambia

Las dos líneas dentro del if solo se ejecutan si la condición es verdadera. La última línea se ejecuta siempre — está fuera del bloque condicional. La indentación es la que define qué pertenece al bloque y qué no. No es una cuestión de estilo: en Python la indentación es sintaxis.

— El error más peligroso: flujo incorrecto —

En la Clase 1 distinguimos dos tipos de error: el error de Nivel 1 produce un mensaje de error visible — el programa se detiene y avisa. El error de Nivel 2 es silencioso — el programa termina sin error pero el resultado es incorrecto.

El flujo incorrecto es la fuente más frecuente de errores de Nivel 2. Una condición mal escrita no produce ningún mensaje — simplemente ejecuta el bloque equivocado, o no ejecuta ninguno cuando debería, o ejecuta uno cuando no debería.

# Error de Nivel 2: condición incorrecta, sin mensaje de error
nota = 4.9

# ❌ Condición mal escrita — ¿qué imprime?
if nota > 5:
    print("Aprobado")
else:
    print("Suspenso")

# ✓ Condición correcta — ¿cambia el resultado?
if nota >= 5:
    print("Aprobado")
else:
    print("Suspenso")

# El programa no produce ningún error en ninguno de los dos casos
# Solo el resultado final revela cuál es correcto
La lección: cuando la IA genera código con condicionales, el primer punto de verificación son las condiciones — > vs >=, and vs or, el orden de los elif. Ninguno de estos errores produce un mensaje. Solo los detectas leyendo el código o probando casos límite.
  1. En el ejemplo de temperatura: cambia temperatura = 35 a temperatura = 20 y predice qué se imprime antes de ejecutar. ¿Cuántas líneas se ejecutan en cada caso?
  2. En el ejemplo de la nota: ¿qué imprime cada versión con nota = 4.9? ¿Y con nota = 5.0? ¿Cuál de las dos condiciones (> vs >=) es la correcta para un sistema donde el 5.0 es aprobado?
  3. ¿Por qué el error de flujo incorrecto es más peligroso que un SyntaxError? ¿En qué tipo de situaciones podría tener consecuencias graves en un análisis de datos real?
Respuesta orientativa
a

Con temperatura = 20: se imprime "Comprobando temperatura..." y "Temperatura comprobada." — las dos líneas fuera del bloque. Las dos líneas dentro del if no se ejecutan porque la condición 20 > 30 es False. Con temperatura = 35 se ejecutan las 4 líneas.

b

Con nota = 4.9: ambas versiones imprimen "Suspenso" — en este caso el resultado es el mismo. Con nota = 5.0: la versión con > imprime "Suspenso" porque 5.0 > 5 es False. La versión con >= imprime "Aprobado" porque 5.0 >= 5 es True. La correcta para un sistema donde el 5.0 aprueba es >=.

El caso límite: los errores de condición solo se revelan en los valores frontera — exactamente en el 5.0, no antes ni después. Un alumno con 4.9 o con 6.0 vería el mismo resultado en ambas versiones. Por eso hay que probar siempre los casos límite al verificar código con condicionales.
c

Un SyntaxError detiene el programa inmediatamente y señala exactamente dónde está el problema. Es imposible ignorarlo. Un error de flujo incorrecto deja que el programa termine con normalidad — produce un resultado que parece válido pero no lo es.

En análisis de datos las consecuencias pueden ser graves: clasificar clientes incorrectamente, aplicar un descuento a quien no le corresponde, excluir registros válidos de un análisis o incluir registros que deberían filtrarse. Ninguno de estos errores produce un mensaje — solo los detectas conociendo el resultado esperado o probando casos límite.

Parte II

if / elif / else

El if básico: condición e indentación teoría

Un if evalúa una expresión booleana — una pregunta cuya respuesta es True o False. Si la respuesta es True, ejecuta el bloque de código que tiene debajo. Si es False, lo salta.

La sintaxis tiene dos partes obligatorias: los dos puntos al final de la línea del if, y la indentación del bloque que se ejecuta si la condición es verdadera. En Python la indentación no es estilo — es sintaxis. Define qué instrucciones pertenecen al bloque.

— Sintaxis básica —

# Estructura mínima
if condición:
    instrucción_1   # se ejecuta si condición es True
    instrucción_2   # también dentro del bloque

instrucción_3       # fuera del bloque — se ejecuta siempre
# Ejemplo concreto
x = 10
y = 20

if x < y:
    print("x es menor que y")

print("Esto se imprime siempre")

— La indentación define el bloque —

La indentación estándar en Python es 4 espacios. JupyterLab la añade automáticamente después de los dos puntos. Si la indentación es incorrecta, Python lanza un IndentationError — uno de los pocos errores de flujo que sí produce un mensaje visible.

# Correcto: instrucción dentro del bloque
x = 15
if x > 10:
    print("x es mayor que 10")   # 4 espacios
    print("esto también está dentro")

# La indentación define qué está dentro y qué fuera
temperatura = 38
if temperatura > 37.5:
    print("Fiebre detectada")          # dentro — solo si fiebre
    print("Consulta con un médico")     # dentro — solo si fiebre
print("Medición completada")            # fuera — siempre

# Error de indentación — produce IndentationError
if x > 10:
print("falta indentación")   # ← error

— La condición es cualquier expresión que devuelva True o False —

x = 10
nombre = "Ana"
numeros = [1, 2, 3]

# Comparación numérica
if x > 5:
    print("x es mayor que 5")

# Comparación de strings
if nombre == "Ana":
    print(f"Hola, {nombre}")

# Pertenencia
if 2 in numeros:
    print("El 2 está en la lista")

# Variable booleana directamente
es_valido = True
if es_valido:
    print("Registro válido")

# Resultado de una función
if len(numeros) > 0:
    print(f"La lista tiene {len(numeros)} elementos")
  1. Predice qué imprime este código antes de ejecutarlo:
    a = 7
    b = 7
    if a == b:
        print("iguales")
        print("ambos valen 7")
    print("fin")
  2. ¿Cuántas líneas se ejecutan si temperatura = 36.8? ¿Y si temperatura = 38.2? Indica para cada caso qué líneas se ejecutan y cuáles no.
  3. Este código tiene un error. ¿De qué tipo es y en qué línea está? ¿Cómo lo corregirías?
    precio = 150
    if precio > 100:
    print("precio alto")
        print("aplicar descuento")
  4. Dado este fragmento de código de validación de datos, predice el resultado para cada valor de edad: 17, 18, 65:
    edad = 18
    if edad >= 18:
        print("acceso permitido")
    print(f"edad registrada: {edad}")
Respuesta orientativa
a

Imprime las tres líneas: "iguales", "ambos valen 7" y "fin". La condición a == b es True porque ambas variables valen 7, así que el bloque completo se ejecuta. "fin" se imprime siempre porque está fuera del bloque.

b

Con temperatura = 36.8: la condición 36.8 > 37.5 es False — solo se ejecuta "Medición completada". Las dos líneas del bloque se saltan.

Con temperatura = 38.2: la condición es True — se ejecutan las tres líneas: "Fiebre detectada", "Consulta con un médico" y "Medición completada".

c

Es un IndentationError en la línea del primer print — le falta la indentación de 4 espacios. La segunda línea tiene indentación pero la primera no, lo que produce un error de sintaxis. La corrección:

precio = 150
if precio > 100:
    print("precio alto")
    print("aplicar descuento")
d

Con edad = 17: 17 >= 18 es False — solo imprime "edad registrada: 17". Con edad = 18: 18 >= 18 es True — imprime "acceso permitido" y "edad registrada: 18". Con edad = 65: igual que el caso 18 — imprime ambas líneas.

El caso límite clave: edad = 18 — exactamente en la frontera. Con >= el acceso está permitido. Con > no lo estaría. Siempre prueba los valores exactamente en el límite de la condición.
elif y else: eficiencia en las decisiones teoría

Cuando hay más de dos posibilidades, la primera tentación es encadenar varios if independientes. Funciona, pero tiene un problema: Python evalúa todas las condiciones aunque ya haya encontrado una verdadera. Es ineficiente y puede producir resultados inesperados si los bloques se interfieren entre sí.

elif y else resuelven esto: forman una cadena donde Python evalúa las condiciones en orden y se detiene en la primera que sea verdadera. El resto no se evalúa.

— Múltiples if vs elif: la diferencia —

x = 10
y = 20

# ❌ Múltiples if: evalúa las TRES condiciones siempre
if x < y:
    print("x es menor que y")
if x > y:
    print("x es mayor que y")
if x == y:
    print("x es igual a y")

# ✓ elif: evalúa solo las necesarias — se detiene en la primera True
if x < y:
    print("x es menor que y")
elif x > y:
    print("x es mayor que y")
elif x == y:
    print("x es igual a y")

Con x = 10 y y = 20, la primera condición es True. Con múltiples if, Python evalúa las tres condiciones de todas formas — dos evaluaciones innecesarias. Con elif, encuentra la primera True y no evalúa las siguientes. Para tres condiciones la diferencia es pequeña. Para cien condiciones, es enorme.

— else: el caso por defecto —

else captura todos los casos que no han satisfecho ninguna condición anterior. No tiene condición propia — se ejecuta si y solo si ningún if ni elif anterior fue True.

La ventaja del else es que elimina la última condición explícita — si algo no es menor ni mayor, debe ser igual. No hace falta escribirlo. Además garantiza que siempre se ejecuta al menos un bloque.

x = 10
y = 10

# Con elif final — todavía necesita evaluar la última condición
if x < y:
    print("x es menor que y")
elif x > y:
    print("x es mayor que y")
elif x == y:           # esta condición es redundante
    print("x es igual a y")

# Con else — más limpio y eficiente
if x < y:
    print("x es menor que y")
elif x > y:
    print("x es mayor que y")
else:
    print("x es igual a y")   # se ejecuta si no es menor ni mayor

— Cómo fluye la ejecución —

Múltiples if — evalúa todo

inicio
  ↓
¿x < y? → sí → bloque A
  ↓ (siempre)
¿x > y? → sí → bloque B
  ↓ (siempre)
¿x == y? → sí → bloque C
  ↓
fin

if / elif / else — se detiene

inicio
  ↓
¿x < y? → sí → bloque A → fin
  ↓ no
¿x > y? → sí → bloque B → fin
  ↓ no
else   →       bloque C → fin

— Cuándo usar if independientes vs elif —

No siempre es mejor elif. La elección depende de si las condiciones son mutuamente excluyentes o no.

# Condiciones NO excluyentes → múltiples if independientes
# Un dato puede cumplir VARIAS condiciones a la vez
valor = 85
if valor > 50:
    print("supera la mitad")
if valor > 80:
    print("supera el umbral alto")
if valor > 90:
    print("supera el umbral máximo")
# Las dos primeras son True — queremos que ambas se ejecuten

# Condiciones SÍ excluyentes → if / elif / else
# Un dato solo puede caer en UNA categoría
nota = 85
if nota >= 90:
    print("Sobresaliente")
elif nota >= 70:
    print("Notable")
elif nota >= 50:
    print("Aprobado")
else:
    print("Suspenso")
# Una nota no puede ser Notable y Aprobado a la vez
  1. Con x = 10 y y = 20: ¿cuántas condiciones evalúa la versión con múltiples if? ¿Y la versión con elif? ¿Qué imprime cada una?
  2. Predice qué imprime este código con nota = 85:
    nota = 85
    if nota >= 90:
        print("Sobresaliente")
    elif nota >= 70:
        print("Notable")
    elif nota >= 50:
        print("Aprobado")
    else:
        print("Suspenso")
    ¿Qué ocurre si cambias el orden de los elif y pones el >= 50 primero?
  3. En el bloque de condiciones no excluyentes con valor = 85: ¿cuántas líneas se imprimen? ¿Qué pasaría si usaras elif en lugar de múltiples if?
  4. Este código tiene un error lógico — no de sintaxis sino de flujo. Identifícalo y corrígelo:
    temperatura = 38
    if temperatura > 36:
        print("temperatura normal")
    elif temperatura > 37.5:
        print("fiebre leve")
    elif temperatura > 39:
        print("fiebre alta")
    else:
        print("hipotermia")
Respuesta orientativa
a

Con múltiples if: evalúa las tres condiciones. Con elif: evalúa solo la primera — 10 < 20 es True y se detiene. Ambas versiones imprimen "x es menor que y" — el resultado es el mismo pero el camino es distinto.

b

Con nota = 85 imprime "Notable" — la primera condición verdadera es 85 >= 70. El resto no se evalúa.

Si pones >= 50 primero, con nota = 85 imprimiría "Aprobado" — incorrecto. Porque 85 >= 50 es True y el elif se detiene ahí sin llegar al >= 70.

El orden importa: en una cadena elif las condiciones más restrictivas deben ir primero. Si pones la condición más amplia al inicio, los casos más específicos nunca se alcanzan.
c

Con valor = 85 se imprimen dos líneas: "supera la mitad" y "supera el umbral alto" — las dos primeras condiciones son True y como son if independientes, ambas se ejecutan. Con elif solo se imprimiría la primera "supera la mitad" y las demás se saltarían — resultado incorrecto para este caso.

d

El error está en el orden de condiciones. Con temperatura = 38 la primera condición 38 > 36 es True — imprime "temperatura normal" cuando debería imprimir "fiebre leve". Las condiciones de fiebre nunca se alcanzan porque la primera es demasiado amplia. La corrección: ordenar de más restrictivo a menos restrictivo.

if temperatura > 39:
    print("fiebre alta")
elif temperatura > 37.5:
    print("fiebre leve")
elif temperatura > 36:
    print("temperatura normal")
else:
    print("hipotermia")
Regla general: en una cadena elif con rangos numéricos, siempre empieza por el extremo más restrictivo — el valor más alto si usas >, el más bajo si usas <.
Condicionales anidados y simplificación práctica

Un condicional puede contener otro condicional dentro de su bloque. A esto se le llama anidamiento. Es útil cuando la segunda decisión solo tiene sentido si la primera ya fue verdadera.

Sin embargo, el anidamiento excesivo produce código difícil de leer — cada nivel añade 4 espacios más de indentación y el flujo se vuelve difícil de seguir. Casi siempre hay una forma más clara de escribir lo mismo usando elif o combinando condiciones con and.

— Condicionales anidados —

# Anidamiento: la segunda decisión depende de la primera
edad = 20
tiene_carnet = True

if edad >= 18:
    print("Es mayor de edad")
    if tiene_carnet:
        print("Puede conducir")
    else:
        print("No tiene carnet")
else:
    print("Es menor de edad — no puede conducir")

# La misma lógica con and — más compacto
if edad >= 18 and tiene_carnet:
    print("Puede conducir")
elif edad >= 18:
    print("Mayor de edad pero sin carnet")
else:
    print("Menor de edad")

Las dos versiones producen el mismo resultado pero la segunda es más fácil de leer de un vistazo. En general, si puedes evitar el anidamiento combinando condiciones con and u or, el código gana en claridad. El Zen de Python lo dice directamente: "Flat is better than nested."

— El ejemplo de notas: de la versión larga a la simplificada —

Un patrón muy frecuente en código generado por IA es usar and para definir rangos numéricos explícitamente — score >= 90 and score <= 100. Funciona, pero es más largo de lo necesario. Aprovechando que elif solo se evalúa si las anteriores fueron falsas, podemos simplificar enormemente.

# Versión larga — con and explícito en cada rango
score = 85

if score >= 90 and score <= 100:
    print("Sobresaliente")
elif score >= 70 and score < 90:
    print("Notable")
elif score >= 50 and score < 70:
    print("Aprobado")
else:
    print("Suspenso")
# Versión simplificada — aprovechando el cortocircuito del elif
# Si llegamos al elif >= 70, YA SABEMOS que score < 90
# porque si fuera >= 90 habría entrado en el primer if
score = 85

if score >= 90:
    print("Sobresaliente")
elif score >= 70:
    print("Notable")
elif score >= 50:
    print("Aprobado")
else:
    print("Suspenso")
Por qué funciona la simplificación: cuando Python evalúa el elif score >= 70, ya sabe que score < 90 — de lo contrario habría entrado en el if score >= 90 anterior y nunca habría llegado hasta aquí. El elif hereda implícitamente la negación de todas las condiciones anteriores.

— Comparaciones encadenadas: pythónico y conciso —

Python permite encadenar comparaciones de forma natural — como en matemáticas. 90 <= score <= 100 es equivalente a score >= 90 and score <= 100 pero más legible. Es una característica exclusiva de Python que no existe en la mayoría de lenguajes — aparece frecuentemente en código generado por IA.

score = 85

# Comparación encadenada — pythónico
if 90 <= score <= 100:
    print("Sobresaliente")
elif 70 <= score < 90:
    print("Notable")
elif 50 <= score < 70:
    print("Aprobado")
else:
    print("Suspenso")

# Verificar rango de edad
edad = 25
if 18 <= edad <= 65:
    print("En edad laboral activa")

# Verificar si un valor está en rango de datos válidos
temperatura = 36.8
if 35.0 <= temperatura <= 42.0:
    print("Temperatura en rango fisiológico")
else:
    print("Valor fuera de rango — revisar medición")
  1. En la versión simplificada de notas: cuando Python evalúa elif score >= 70 con score = 85, ¿qué sabe implícitamente sobre el valor de score que no necesita comprobar?
  2. Predice qué imprime cada versión — larga y simplificada — con score = 90, score = 69 y score = 50. ¿Coinciden los resultados?
  3. Reescribe este condicional anidado usando and para eliminar el anidamiento:
    saldo = 500
    importe = 200
    if saldo > 0:
        if saldo >= importe:
            print("Transferencia aprobada")
        else:
            print("Saldo insuficiente")
    else:
        print("Cuenta en negativo")
  4. Este código generado por IA clasifica el IMC de un paciente. Identifica si hay algún problema en el orden de condiciones y si la lógica es correcta:
    imc = 27.5
    if imc < 18.5:
        print("Bajo peso")
    elif imc < 25:
        print("Peso normal")
    elif imc < 30:
        print("Sobrepeso")
    else:
        print("Obesidad")
Respuesta orientativa
a

Cuando Python llega al elif score >= 70, sabe implícitamente que score < 90 — porque si fuera 90 o más, habría entrado en el if score >= 90 y no habría llegado hasta aquí. Por eso no es necesario escribir score >= 70 and score < 90 — la condición del límite superior está implícita en la estructura del elif.

b

Sí, los resultados coinciden en todos los casos — ambas versiones son lógicamente equivalentes. Con score = 90: "Sobresaliente". Con score = 69: "Aprobado". Con score = 50: "Aprobado".

Cuándo usar cada versión: la versión simplificada es preferible cuando los rangos son contiguos y ordenados. La versión con and explícito puede ser más clara cuando los rangos no son contiguos o cuando el código será leído por alguien sin experiencia en Python.
c
saldo = 500
importe = 200

if saldo > 0 and saldo >= importe:
    print("Transferencia aprobada")
elif saldo > 0:
    print("Saldo insuficiente")
else:
    print("Cuenta en negativo")

La versión plana es más fácil de leer de un vistazo — el flujo es lineal sin saltos de indentación. Nótese que saldo > 0 and saldo >= importe podría simplificarse a saldo >= importe si el importe siempre es positivo, porque un saldo suficiente para cubrir el importe implica que es mayor que 0.

d

El código es correcto — el orden de condiciones es el adecuado: de menor a mayor, usando < como frontera. Con imc = 27.5: 27.5 < 18.5 es False, 27.5 < 25 es False, 27.5 < 30 es True → imprime "Sobrepeso". La lógica es correcta y el orden es el esperado — este es un ejemplo de código generado por IA que está bien escrito.

Patrón con <: cuando se usa < como operador, el orden correcto es de menor a mayor — lo opuesto al caso con >. Con > hay que ir de mayor a menor. Con < hay que ir de menor a mayor.
Parte III

Operadores de comparación y booleanos en contexto

Operadores de comparación teoría

Una condición en un if es cualquier expresión que devuelva True o False. Los operadores de comparación son la forma más directa de construir esas expresiones — comparan dos valores y producen un booleano.

— Los seis operadores de comparación —

Operador Significado Ejemplo Resultado
> Mayor que 5 > 3 True
< Menor que 2 < 1 False
>= Mayor o igual que 5 >= 5 True
<= Menor o igual que 3 <= 2 False
== Igual a 4 == 4 True
!= Distinto de 4 != 5 True

— La trampa más frecuente: = vs == —

= es asignación — le da un valor a una variable. == es comparación — pregunta si dos valores son iguales. Confundirlos es uno de los errores más frecuentes en Python, especialmente en código generado por IA o copiado de otro lenguaje.

# = asigna — no produce True/False
x = 10          # x ahora vale 10

# == compara — produce True/False
print(x == 10)   # True
print(x == 20)   # False

# Error típico: usar = en lugar de == en un if
if x == 10:       # correcto
    print("x vale 10")

# Verificar igualdad entre strings
nombre = "Ana"
if nombre == "Ana":
    print("Hola Ana")
if nombre == "ana":     # sensible a mayúsculas
    print("esto no se imprime")

# Solución: normalizar antes de comparar
if nombre.lower() == "ana":
    print("Hola Ana — versión insensible a mayúsculas")

— Comparaciones entre strings: orden lexicográfico —

Los operadores >, <, >=, <= también funcionan con strings — comparan carácter a carácter según el código Unicode. Las mayúsculas van antes que las minúsculas, los números antes que las letras. Esto aparece en código de ordenación y validación de datos.

# Orden lexicográfico — como en un diccionario
print("ana" < "bob")      # True — 'a' < 'b'
print("banana" < "manzana") # True — 'b' < 'm'
print("Ana" < "ana")       # True — mayúscula < minúscula
print("10" < "9")           # True — '1' < '9' como caracteres

# Atención: "10" como string vs 10 como número
print(10 < 9)               # False — comparación numérica
print("10" < "9")           # True — comparación lexicográfica

# Comparar tipos distintos lanza TypeError en Python 3
try:
    print(10 < "9")
except TypeError as e:
    print(f"Error: {e}")
  1. Predice el resultado de cada expresión antes de ejecutar:
    print(3 == 3.0)
    print(3 is 3.0)
    print("Python" == "python")
    print("Python".lower() == "python")
    print(10 != 10.0)
  2. ¿Por qué "10" < "9" es True pero 10 < 9 es False? ¿Qué consecuencia tiene esto si comparas números que vienen de un CSV cargado como strings?
  3. Este código tiene un error muy frecuente. Identifícalo, explica qué hace realmente y corrígelo:
    umbral = 100
    ventas = 150
    if ventas = umbral:
        print("ventas exactamente en umbral")
    else:
        print("ventas distintas del umbral")
  4. En análisis de datos es frecuente recibir valores como strings aunque representen números. ¿Qué produce este código y cómo lo corregirías?
    precio_str = "99"
    if precio_str > 50:
        print("precio alto")
    else:
        print("precio bajo")
Respuesta orientativa
a

3 == 3.0 → True: Python compara valores, no tipos — el entero 3 y el float 3.0 son iguales. 3 is 3.0 → False: son objetos distintos en memoria. "Python" == "python" → False: sensible a mayúsculas. "Python".lower() == "python" → True: normalizado. 10 != 10.0 → False: son iguales en valor.

b

"10" < "9" compara carácter a carácter: '1' tiene código Unicode menor que '9', por eso es True. 10 < 9 compara valores numéricos — es False.

La consecuencia práctica es grave: si un CSV tiene una columna de precios cargada como strings, "100" < "20" es True — el filtrado producirá resultados incorrectos sin ningún mensaje de error. Siempre hay que convertir a número antes de comparar: int(precio_str) < 20.

c

ventas = umbral dentro del if es un SyntaxError — Python no permite asignación en una condición de esta forma. El error es haber escrito = en lugar de ==. La corrección:

if ventas == umbral:
    print("ventas exactamente en umbral")
else:
    print("ventas distintas del umbral")
Excepción útil: en este caso el error sí produce un SyntaxError visible — Python detecta que una asignación no es válida como condición. Es uno de los pocos casos donde el error de = vs == no es silencioso.
d

Produce un TypeError: no se pueden comparar un string y un entero con >. La corrección es convertir el string a número antes de comparar:

precio = int(precio_str)   # o float() si tiene decimales
if precio > 50:
    print("precio alto")
else:
    print("precio bajo")
Patrón muy frecuente en datos reales: los datos de fuentes externas — CSVs, APIs, formularios — llegan casi siempre como strings. Antes de cualquier comparación numérica hay que convertir el tipo. Pandas lo hace automáticamente en muchos casos, pero no siempre.
and, or, not: combinar condiciones práctica

En la Clase 2 vimos and, or y not como operadores sobre booleanos. Ahora los usamos en su contexto natural: combinar varias condiciones en un mismo if para expresar decisiones más complejas.

La clave para leer código con condiciones combinadas es entender el cortocircuito: Python no evalúa más condiciones de las necesarias. En cuanto conoce el resultado, se detiene.

— Los tres operadores lógicos —

Operador Resultado True si... Cortocircuito
and Ambas condiciones son True Para en el primer False
or Al menos una es True Para en el primer True
not La condición es False Sin cortocircuito — invierte

— and: ambas condiciones deben ser True —

# Validar que un dato está en rango
edad = 25
if edad >= 18 and edad <= 65:
    print("En edad laboral activa")

# Equivalente con comparación encadenada
if 18 <= edad <= 65:
    print("En edad laboral activa")

# Validar múltiples campos de un registro
nombre = "Ana"
nota   = 8.5
activo = True

if nombre != "" and nota >= 0 and activo:
    print(f"Registro válido: {nombre}, {nota}")

# Cortocircuito de and: si la primera es False,
# la segunda no se evalúa
x = 0
if x != 0 and 100 / x > 10:   # sin cortocircuito: ZeroDivisionError
    print("condición cumplida")
print("no hay error — cortocircuito evitó la división")

— or: basta con que una sea True —

# Aceptar múltiples valores válidos
extension = "csv"
if extension == "csv" or extension == "xlsx" or extension == "json":
    print("formato de datos reconocido")

# Más conciso con in
if extension in {"csv", "xlsx", "json"}:
    print("formato de datos reconocido")

# Condición alternativa — si falla el dato principal
valor_principal = None
valor_backup    = 42
if valor_principal or valor_backup:
    print("al menos un valor disponible")

# Cortocircuito de or: si la primera es True,
# la segunda no se evalúa
datos = [1, 2, 3]
if len(datos) > 0 or sum(datos) > 100:   # sum no se evalúa
    print("condición cumplida")

— not: invertir una condición —

# not invierte el valor booleano
activo = False
if not activo:
    print("usuario inactivo — enviar recordatorio")

# not in — muy frecuente para verificar ausencia
categorias_validas = {"A", "B", "C"}
categoria = "D"
if categoria not in categorias_validas:
    print(f"Categoría '{categoria}' no reconocida")

# not con is None — verificar valor faltante
valor = None
if not valor is None:    # funciona pero es menos pythónico
    print("tiene valor")
if valor is not None:    # forma pythónica — más legible
    print("tiene valor")

# Precedencia: not > and > or
x = True
y = False
print(not x or y)       # (not x) or y → False or False → False
print(not (x or y))     # not (True or False) → not True → False

El orden de precedencia de los operadores lógicos es: not primero, luego and, luego or. Cuando no estés seguro, usa paréntesis — hacen explícito el orden de evaluación y el código es más fácil de leer.

  1. Predice el resultado de cada expresión antes de ejecutar:
    a = 5
    b = 10
    c = 15
    print(a < b and b < c)
    print(a > b or c > b)
    print(not a == b)
    print(a < b and b > c or c == 15)
    print(a < b and (b > c or c == 15))
  2. El cortocircuito de and evita el ZeroDivisionError en el ejemplo. Explica por qué: si x = 0, ¿qué hace Python cuando evalúa x != 0 and 100 / x > 10? ¿Qué pasaría sin el cortocircuito?
  3. Dado este fragmento de validación de datos, predice qué imprime con los valores indicados:
    precio = -5
    stock  = 0
    nombre = "Manzana"
    
    if precio > 0 and stock >= 0 and nombre != "":
        print("registro válido")
    elif precio <= 0:
        print("error: precio no puede ser negativo o cero")
    elif stock < 0:
        print("error: stock no puede ser negativo")
    else:
        print("error: nombre vacío")
  4. Reescribe esta cadena de or de forma más concisa usando in:
    dia = "sábado"
    if dia == "sábado" or dia == "domingo":
        print("fin de semana")
    else:
        print("día laborable")
    ¿Qué ventaja tiene la versión con in cuando hay muchos valores posibles?
Respuesta orientativa
a

a < b and b < c → True: ambas son verdaderas. a > b or c > b → True: la segunda es verdadera. not a == b → True: 5 == 10 es False, negado es True. a < b and b > c or c == 15 → True: se evalúa como (a < b and b > c) or c == 15 → False or True → True. a < b and (b > c or c == 15) → True: True and True.

b

Con x = 0, Python evalúa x != 0 primero — es False. Como es and, si la primera condición es False el resultado no puede ser True sin importar la segunda. Python se detiene y no evalúa 100 / x — el ZeroDivisionError nunca ocurre. Sin cortocircuito, Python evaluaría las dos condiciones siempre y la división por cero detendría el programa con un error.

Patrón de seguridad: poner la condición de guardia (x != 0) antes de la operación peligrosa (100 / x) es un patrón frecuente para evitar errores en análisis de datos — verificar que el denominador no es cero antes de dividir, que la lista no está vacía antes de acceder al primer elemento.
c

Con precio = -5: la primera condición precio > 0 es False — el and cortocircuita y no evalúa el resto. El bloque del if no se ejecuta. El primer elif — precio <= 0 es True — imprime "error: precio no puede ser negativo o cero".

d
dia = "sábado"
if dia in {"sábado", "domingo"}:
    print("fin de semana")
else:
    print("día laborable")

La ventaja de in escala mucho mejor — si tuvieras diez valores posibles, la versión con or requeriría diez comparaciones explícitas. Con in solo cambias el contenido del set. Además la verificación de pertenencia en un set es O(1) — independientemente de cuántos valores tenga.

Truthy, falsy e in en condicionales práctica

En Python no hace falta comparar explícitamente con True o False para usar algo como condición. Cualquier objeto puede usarse directamente en un if — Python lo convierte implícitamente a booleano.

Los objetos que se comportan como True se llaman truthy. Los que se comportan como False se llaman falsy. Conocerlos permite escribir condiciones más concisas y leer código ajeno sin confundirse.

— Valores falsy en Python —

Valor Tipo Significado
False bool el propio False
None NoneType ausencia de valor
0, 0.0 int, float cero numérico
"" str string vacío
[], (), {}, set() list, tuple, dict, set colección vacía

Todo lo demás es truthy: cualquier número distinto de cero, cualquier string no vacío, cualquier colección con al menos un elemento. Puedes verificarlo con bool(valor).

— Truthy y falsy en condicionales —

# Verificar si una lista tiene elementos
datos = [1, 2, 3]
vacia = []

if datos:                    # truthy — tiene elementos
    print("hay datos")
if not vacia:               # falsy negado — vacía
    print("lista vacía")

# Equivalente explícito — más verboso, mismo resultado
if len(datos) > 0:
    print("hay datos")

# Verificar si un string tiene contenido
nombre = ""
if nombre:
    print(f"nombre: {nombre}")
else:
    print("nombre vacío — campo obligatorio")

# Verificar si un valor es None
resultado = None
if resultado is None:       # forma correcta para None
    print("sin resultado todavía")
if resultado is not None:   # forma correcta para no-None
    print("resultado disponible")

# Verificar con bool() explícito
print(bool([]))       # False
print(bool([1]))      # True
print(bool(""))       # False
print(bool("0"))      # True — string no vacío
print(bool(0))        # False
print(bool(0.0))      # False

— in y not in: pertenencia como condición —

El operador in — que vimos en la Clase 3 para verificar pertenencia en listas, dicts y sets — es también una de las condiciones más frecuentes en condicionales. Permite expresar de forma natural preguntas como "¿este valor está en la colección?" o "¿esta clave existe en el diccionario?".

# in con listas — O(n)
frutas = ["manzana", "pera", "naranja"]
if "pera" in frutas:
    print("pera disponible")

# in con sets — O(1) más eficiente
categorias_validas = {"A", "B", "C"}
categoria = "D"
if categoria not in categorias_validas:
    print(f"categoría '{categoria}' no válida")

# in con diccionarios — verifica claves
alumno = {"nombre": "Ana", "nota": 8.5}
if "nota" in alumno:
    print(f"nota: {alumno['nota']}")
if "edad" not in alumno:
    print("campo edad ausente")

# in con strings — busca subcadena
email = "ana@uvigo.es"
if "@" in email:
    print("formato de email válido")
if "uvigo" in email:
    print("email institucional")

— Patrones frecuentes en análisis de datos —

import pandas as pd

df = pd.DataFrame({
    "producto": ["manzana", "pera", None, "naranja"],
    "ventas":   [150, 0, 80, 120],
    "activo":   [True, False, True, True]
})

# Patrón 1: verificar si una columna existe
if "ventas" in df.columns:
    print(f"total ventas: {df['ventas'].sum()}")

# Patrón 2: verificar si el DataFrame tiene datos
if not df.empty:
    print(f"filas disponibles: {len(df)}")

# Patrón 3: verificar valores nulos antes de operar
valor = df["producto"].iloc[2]   # None
if valor is not None:
    print(f"producto: {valor.upper()}")
else:
    print("valor faltante — omitir fila")
  1. Predice el resultado de cada bool() antes de ejecutar:
    print(bool(0))
    print(bool("False"))
    print(bool([]))
    print(bool([None]))
    print(bool({}))
    print(bool({0: "cero"}))
  2. ¿Cuál es la diferencia entre estas dos condiciones? ¿Cuándo producen resultados distintos?
    valor = 0
    if valor:
        print("versión truthy")
    if valor is not None:
        print("versión is not None")
  3. Dado este fragmento de validación, predice qué imprime para cada combinación de valores:
    datos = []
    config = {"umbral": 0}
    
    if datos and "umbral" in config and config["umbral"]:
        print("procesando")
    elif not datos:
        print("sin datos")
    elif not config["umbral"]:
        print("umbral en cero")
    else:
        print("otro error")
  4. Reescribe estas condiciones de forma más concisa usando truthy/falsy donde sea posible:
    if len(lista) > 0:
        ...
    if nombre != "":
        ...
    if valor == None:
        ...
    if activo == True:
        ...
Respuesta orientativa
a

bool(0) → False: cero es falsy. bool("False") → True: string no vacío — aunque diga "False", es truthy. bool([]) → False: lista vacía. bool([None]) → True: lista con un elemento — aunque sea None, la lista no está vacía. bool({}) → False: diccionario vacío. bool({0: "cero"}) → True: diccionario con un elemento.

La trampa más frecuente: bool("False") es True — el string "False" no es el booleano False. Si necesitas convertir el string "False" al booleano False, no puedes usar bool() directamente.
b

Con valor = 0: la versión truthy no imprime nada — 0 es falsy. La versión is not None sí imprime — 0 no es None.

La diferencia importa cuando 0 es un valor válido en tus datos — por ejemplo, ventas de cero unidades. Si usas la versión truthy, tratarás el cero como "sin datos" cuando en realidad es un dato válido. En ese caso la forma correcta es valor is not None.

Regla práctica: usa la forma truthy (if valor) cuando quieras excluir todos los valores vacíos o cero. Usa is not None cuando el cero o el string vacío son valores legítimos y solo quieres excluir la ausencia de dato.
c

datos es una lista vacía — falsy. La primera condición del and es False — cortocircuita y no evalúa el resto. El bloque if no se ejecuta. El primer elif not datos es True — imprime "sin datos".

El elif not config["umbral"] nunca se alcanza en este caso. Si datos = [1, 2, 3], entonces pasaría al siguiente elif porque config["umbral"] es 0 — falsy — e imprimiría "umbral en cero".

d
if lista:          # en lugar de len(lista) > 0
    ...
if nombre:         # en lugar de nombre != ""
    ...
if valor is None:  # en lugar de valor == None
    ...
if activo:         # en lugar de activo == True
    ...
Por qué is None en lugar de == None: None es un singleton — existe un único objeto None en Python. La comparación correcta es de identidad (is), no de igualdad (==). Algunos linters como flake8 advierten si usas == None en lugar de is None.
Parte IV

Condicionales con estructuras de datos

Condicionales sobre estructuras de datos práctica

Las estructuras de datos de la Clase 3 — listas, tuplas, diccionarios y sets — aparecen constantemente como condiciones en un if. Conocer los patrones más frecuentes permite leer código ajeno sin confusión y detectar errores antes de ejecutar.

El patrón más importante es el patrón de guardia: verificar que una estructura tiene lo que necesitas antes de operar sobre ella. Sin esta verificación, operaciones como acceder al primer elemento de una lista vacía o buscar una clave inexistente en un dict producen errores en tiempo de ejecución.

— Condicionales sobre listas —

ventas = [150, 80, 200, 95]
vacia  = []

# Patrón de guardia: verificar antes de operar
if ventas:
    print(f"primera venta: {ventas[0]}")   # seguro
    print(f"media: {sum(ventas)/len(ventas):.1f}")

# Sin guardia — IndexError si la lista está vacía
try:
    print(vacia[0])
except IndexError as e:
    print(f"IndexError: {e}")

# Verificar pertenencia
productos_activos = ["manzana", "pera", "naranja"]
pedido = "kiwi"
if pedido in productos_activos:
    print(f"procesando pedido de {pedido}")
else:
    print(f"{pedido} no disponible")

# Verificar longitud mínima
registros = ["Ana", "Bob"]
if len(registros) >= 3:
    print("suficientes registros para analizar")
else:
    print(f"solo {len(registros)} registro/s — se necesitan al menos 3")

— Condicionales sobre diccionarios —

alumno = {"nombre": "Ana", "nota": 8.5, "activo": True}
incompleto = {"nombre": "Bob"}

# Patrón de guardia: verificar clave antes de acceder
if "nota" in alumno:
    print(f"nota: {alumno['nota']}")   # seguro

# Sin guardia — KeyError
try:
    print(incompleto["nota"])
except KeyError as e:
    print(f"KeyError: {e}")

# Verificar si el dict tiene datos
config = {}
if config:
    print("configuración cargada")
else:
    print("usando configuración por defecto")

# Verificar valor dentro del dict
if "activo" in alumno and alumno["activo"]:
    print(f"{alumno['nombre']} está activo")

# Alternativa con get() — sin necesidad de guardia
nota = alumno.get("nota")
if nota is not None:
    print(f"nota disponible: {nota}")

— Condicionales sobre sets —

permisos_usuario  = {"leer", "escribir"}
permisos_admin    = {"leer", "escribir", "borrar", "admin"}
accion_solicitada = "borrar"

# Verificar permiso específico
if accion_solicitada in permisos_usuario:
    print("acción permitida")
else:
    print(f"sin permiso para '{accion_solicitada}'")

# Verificar si tiene todos los permisos necesarios
permisos_necesarios = {"leer", "escribir"}
if permisos_necesarios.issubset(permisos_usuario):
    print("tiene todos los permisos requeridos")

# Verificar solapamiento entre dos grupos
clientes_enero   = {"Ana", "Bob", "Carlos"}
clientes_febrero = {"Bob", "Diana", "Eva"}
if clientes_enero & clientes_febrero:
    print("hay clientes en común")
    print(f"clientes fieles: {clientes_enero & clientes_febrero}")

— Condicionales sobre tuplas —

# Tupla como registro — verificar campos
registro = ("Ana", 8.5, True)
vacio    = ()

if registro:
    nombre, nota, activo = registro   # unpacking seguro
    if activo and nota >= 5:
        print(f"{nombre}: aprobado ({nota})")

# Verificar longitud antes de desempaquetar
coordenadas = (40.4168, -3.7038)
if len(coordenadas) == 2:
    lat, lon = coordenadas
    print(f"latitud: {lat}, longitud: {lon}")

# in con tupla — pertenencia
dias_laborables = ("lunes", "martes", "miércoles",
                   "jueves", "viernes")
hoy = "sábado"
if hoy not in dias_laborables:
    print("hoy no hay clase")
  1. ¿Por qué es importante el patrón de guardia antes de acceder al primer elemento de una lista? ¿Qué error produce omitirlo si la lista está vacía? ¿Cuál es la forma más concisa de escribir la guardia?
  2. Dado este fragmento, predice qué imprime con los valores indicados:
    inventario = {
        "manzana": 50,
        "pera": 0,
        "naranja": 30
    }
    pedido = "pera"
    
    if pedido in inventario and inventario[pedido] > 0:
        print(f"enviando {pedido}")
    elif pedido in inventario:
        print(f"{pedido} agotado")
    else:
        print(f"{pedido} no existe")
  3. ¿Cuál es la diferencia entre estas dos formas de verificar una clave en un diccionario? ¿Cuándo usarías cada una?
    # Forma A
    if "nota" in alumno:
        nota = alumno["nota"]
    
    # Forma B
    nota = alumno.get("nota")
    if nota is not None:
        ...
  4. Este código generado por IA procesa una lista de registros. Identifica qué puede fallar y cómo añadirías las guardias necesarias:
    registros = [
        {"nombre": "Ana", "nota": 8.5},
        {"nombre": "Bob"},
        {"nombre": "Carlos", "nota": 6.0}
    ]
    
    mejor = registros[0]
    for r in registros:
        if r["nota"] > mejor["nota"]:
            mejor = r
    print(f"mejor alumno: {mejor['nombre']}")
Respuesta orientativa
a

Si la lista está vacía, acceder a lista[0] produce un IndexError que detiene el programa. La forma más concisa de escribir la guardia es:

if lista:
    print(lista[0])

Aprovecha que una lista vacía es falsy — es más conciso que if len(lista) > 0 y produce exactamente el mismo resultado.

b

pedido = "pera": "pera" in inventario es True. inventario["pera"] > 0 es False — el stock es 0. El primer bloque no se ejecuta. El elif — "pera" in inventario es True — imprime "pera agotado".

El patrón de guardia encadenado: "pera" in inventario and inventario["pera"] > 0 es seguro porque el cortocircuito del and garantiza que inventario["pera"] solo se evalúa si la clave existe. Sin la guardia, acceder a una clave inexistente produciría un KeyError.
c

La Forma A es más explícita — primero verifica, luego accede. Útil cuando quieres distinguir entre "la clave no existe" y "la clave existe pero vale None".

La Forma B es más compacta — usa get() que devuelve None si la clave no existe, y luego verifica ese None. Pero si el valor legítimo del campo puede ser None, no distingue entre "clave ausente" y "clave presente con valor None". En ese caso la Forma A es más segura.

d

Dos problemas: primero, registros[0] falla si la lista está vacía. Segundo, r["nota"] lanza KeyError en el registro de Bob que no tiene la clave "nota". La versión corregida:

if not registros:
    print("sin registros")
else:
    # solo comparar registros que tienen nota
    con_nota = [r for r in registros if "nota" in r]
    if con_nota:
        mejor = max(con_nota, key=lambda r: r["nota"])
        print(f"mejor alumno: {mejor['nombre']}")
Lección clave: cuando la IA genera código que itera sobre una lista de dicts, casi siempre asume que todos los dicts tienen las mismas claves. En datos reales esto raramente se cumple — siempre hay registros incompletos. El primer punto de verificación es comprobar si la clave existe antes de acceder.
Condicionales en pandas: filtrado y validación de datos práctica

pandas es la librería central del análisis de datos en Python. Su estructura principal es el DataFrame — una tabla con filas y columnas, similar a una hoja de cálculo o a una tabla de base de datos. Cada columna es una Series — una lista con índice y nombre.

No vamos a estudiar pandas en detalle hasta clases posteriores. Pero hay un concepto que conecta directamente con los condicionales de hoy: el filtrado booleano. En pandas, filtrar filas es exactamente lo mismo que un if de Python — pero aplicado a toda una columna a la vez en lugar de a un solo valor.

— Un DataFrame: la tabla de datos de pandas —

import pandas as pd

# Crear un DataFrame desde un diccionario
df = pd.DataFrame({
    "producto":  ["manzana", "pera", "naranja", "kiwi", "fresa"],
    "ventas":    [150, 80, 120, 0, 200],
    "precio":    [1.20, 0.90, 0.80, 1.50, 2.00],
    "categoria": ["fruta", "fruta", "cítrico", "exótico", "fruta"],
    "activo":    [True, True, True, False, True]
})

print(df)
print()
print(f"Filas: {len(df)}, Columnas: {len(df.columns)}")
print(f"Columnas: {list(df.columns)}")

— if de Python vs filtrado booleano en pandas —

En Python un if evalúa una condición sobre un valor y ejecuta un bloque o no. En pandas, el filtrado booleano evalúa una condición sobre toda una columna a la vez — devuelve una Serie de True/False que selecciona las filas que cumplen la condición. La idea es exactamente la misma — solo cambia la escala.

# Python: if sobre un valor
ventas_valor = 150
if ventas_valor > 100:
    print("ventas altas")

# pandas: condición sobre toda la columna
mascara = df["ventas"] > 100
print(mascara)           # Serie de True/False
print(type(mascara))

# Usar la máscara para filtrar filas
df_altas = df[mascara]
print(df_altas)

# En una sola línea — forma más frecuente
df_altas = df[df["ventas"] > 100]
print(df_altas[["producto", "ventas"]])

— Filtros combinados: and, or, not en pandas —

En pandas no se usan and, or y not para combinar condiciones — se usan los operadores &, | y ~. Y cada condición debe ir entre paréntesis. Es uno de los errores más frecuentes en código generado por IA: usar and en lugar de & produce un error críptico.

# Python: and, or, not
if ventas_valor > 100 and ventas_valor < 200:
    print("rango medio")

# pandas: & | ~ con paréntesis obligatorios
# & equivale a and
df_rango = df[(df["ventas"] > 100) & (df["ventas"] < 200)]
print(df_rango[["producto", "ventas"]])

# | equivale a or
df_extremos = df[(df["ventas"] == 0) | (df["ventas"] >= 200)]
print(df_extremos[["producto", "ventas"]])

# ~ equivale a not
df_activos = df[df["activo"]]         # activos
df_inactivos = df[~df["activo"]]      # inactivos — ~ invierte
print(df_inactivos[["producto", "activo"]])

# Error frecuente: usar and en lugar de &
try:
    df_error = df[df["ventas"] > 100 and df["activo"]]
except ValueError as e:
    print(f"Error: {e}")

— Validación del DataFrame con if de Python —

El if de Python sigue siendo necesario en pandas para verificar el estado del DataFrame antes de operar — si está vacío, si tiene las columnas que necesitas, si hay valores nulos. Son los patrones de guardia de la Clase 3 aplicados a DataFrames.

# Patrón 1: verificar si el DataFrame tiene datos
if df.empty:
    print("DataFrame vacío — nada que procesar")
else:
    print(f"procesando {len(df)} filas")

# Patrón 2: verificar si una columna existe
if "ventas" in df.columns:
    print(f"total ventas: {df['ventas'].sum()}")
else:
    print("columna 'ventas' no encontrada")

# Patrón 3: verificar si hay valores nulos
df_con_nulos = df.copy()
df_con_nulos.loc[1, "ventas"] = None

if df_con_nulos["ventas"].isnull().any():
    print("hay valores nulos en ventas — revisar datos")
    nulos = df_con_nulos["ventas"].isnull().sum()
    print(f"  filas con nulo: {nulos}")

# Patrón 4: verificar resultado del filtrado
df_filtrado = df[df["categoria"] == "exótico"]
if df_filtrado.empty:
    print("ningún producto de la categoría solicitada")
else:
    print(f"encontrados: {len(df_filtrado)} productos")
  1. ¿Cuál es la diferencia entre el if de Python y el filtrado booleano de pandas? ¿Qué devuelve df["ventas"] > 100? ¿Y df[df["ventas"] > 100]?
  2. Predice qué filas devuelve cada filtro antes de ejecutar:
    df[(df["precio"] < 1.0) & (df["ventas"] > 50)]
    df[(df["categoria"] == "fruta") | (df["ventas"] == 0)]
    df[~df["activo"]]
  3. ¿Por qué en pandas hay que usar & en lugar de and para combinar condiciones? ¿Qué error produce usar and?
  4. Este código generado por IA calcula el precio medio de los productos activos con ventas positivas. Identifica si hay algún problema y añade las guardias necesarias:
    df_validos = df[(df["activo"]) & (df["ventas"] > 0)]
    media = df_validos["precio"].mean()
    print(f"precio medio: {media:.2f}€")
Respuesta orientativa
a

El if de Python evalúa una condición sobre un único valor y decide si ejecutar un bloque o no. El filtrado booleano de pandas evalúa una condición sobre toda una columna a la vez.

df["ventas"] > 100 devuelve una Serie booleana — una columna de True y False para cada fila. df[df["ventas"] > 100] usa esa Serie como máscara y devuelve un DataFrame con solo las filas donde el valor es True.

b

Primer filtro — precio menor que 1.0 and ventas mayor que 50: pera (0.90, 80) y naranja (0.80, 120).

Segundo filtro — categoría fruta or ventas igual a 0: manzana, pera, fresa (frutas) y kiwi (ventas = 0).

Tercer filtro — not activo: solo kiwi, que tiene activo = False.

c

and en Python opera sobre valores escalares — devuelve uno de los dos operandos. En pandas, la condición es una Serie de muchos valores — Python no sabe cómo aplicar and a una Serie entera y lanza un ValueError: "The truth value of a Series is ambiguous". Con & pandas aplica la operación elemento a elemento sobre toda la Serie — que es exactamente lo que queremos.

Error muy frecuente en código de IA: cuando le pides filtrar un DataFrame con varias condiciones, la IA a veces genera and en lugar de &. El mensaje de error "The truth value of a Series is ambiguous" es la señal inequívoca de este problema.
d

El código es correcto en lógica pero puede fallar si df_validos está vacío — mean() sobre un DataFrame vacío devuelve NaN, no un error, pero el :.2f del f-string puede producir resultados confusos. La versión con guardia:

df_validos = df[(df["activo"]) & (df["ventas"] > 0)]
if df_validos.empty:
    print("sin productos válidos para calcular media")
else:
    media = df_validos["precio"].mean()
    print(f"precio medio: {media:.2f}€")
Patrón estándar con pandas: filtrar → verificar si el resultado está vacío → operar. Este orden de tres pasos evita la mayoría de errores silenciosos en análisis de datos.
Parte V

match/case y operador ternario

match / case: múltiples opciones de forma limpia teoría

Introducido en Python 3.10, match/case es una forma más limpia de manejar múltiples opciones cuando comparas un valor contra un conjunto fijo de posibilidades. Es similar al switch de otros lenguajes como Java o JavaScript.

No reemplaza al if/elif/else en general — es específico para el patrón "comparar una variable contra varios valores concretos". Cuando la IA genera código con este patrón, casi siempre usa match/case si detecta que estás comparando contra valores fijos. Saber leerlo es imprescindible.

— Sintaxis básica: elif largo vs match/case —

# Con elif — funciona pero repetitivo
pais = "Francia"

if pais == "España":
    print("Idioma: Español")
elif pais == "Francia":
    print("Idioma: Francés")
elif pais == "Alemania":
    print("Idioma: Alemán")
else:
    print("Idioma desconocido")

# Con match/case — más limpio
match pais:
    case "España":
        print("Idioma: Español")
    case "Francia":
        print("Idioma: Francés")
    case "Alemania":
        print("Idioma: Alemán")
    case _:
        print("Idioma desconocido")

La estructura es: match seguido del valor a comparar, y luego un bloque con los case. Cada case compara el valor con un patrón — si coincide, ejecuta el bloque y sale del match. El case _ es el caso por defecto — equivale al else y captura todo lo que no coincidió con ningún caso anterior. El guión bajo _ es el comodín de Python — significa "cualquier cosa".

— Agrupar casos con | —

Varios valores que producen el mismo resultado pueden agruparse en un único case usando el operador | — igual que el or de los condicionales pero dentro de un case. Evita repetir el mismo bloque para cada valor.

pais = "México"

match pais:
    case "España" | "México" | "Argentina" | "Colombia":
        print("Idioma: Español")
    case "Francia" | "Bélgica" | "Suiza":
        print("Idioma: Francés")
    case "Alemania" | "Austria":
        print("Idioma: Alemán")
    case _:
        print("Idioma desconocido")

— match/case con distintos tipos de valor —

# Con números — clasificar códigos de estado HTTP
codigo = 404

match codigo:
    case 200:
        print("OK — solicitud exitosa")
    case 201:
        print("Creado — recurso nuevo")
    case 400:
        print("Bad Request — error del cliente")
    case 401 | 403:
        print("Sin autorización o prohibido")
    case 404:
        print("Not Found — recurso no existe")
    case 500 | 502 | 503:
        print("Error del servidor")
    case _:
        print(f"Código {codigo} no reconocido")

# Con strings — clasificar extensiones de archivo
extension = "xlsx"

match extension.lower():
    case "csv" | "tsv":
        print("archivo de texto delimitado")
    case "xlsx" | "xls":
        print("libro de Excel")
    case "json" | "jsonl":
        print("archivo JSON")
    case "parquet" | "feather":
        print("formato columnar — pandas nativo")
    case _:
        print(f"extensión '.{extension}' no reconocida")

— ¿Cuándo usar match/case y cuándo if/elif? —

Usa match/case cuando...

Comparas una variable contra valores fijos concretos — strings, números, constantes.
Hay tres o más casos con el mismo patrón de comparación.
Quieres agrupar varios valores que producen el mismo resultado.

Usa if/elif cuando...

Las condiciones son rangos numéricos o expresiones complejas.
Combinas condiciones con and, or.
Solo hay dos casos — un simple if/else es más claro.

  1. ¿Qué hace el case _ en un bloque match? ¿Es obligatorio? ¿Qué pasa si el valor no coincide con ningún case y no hay case _?
  2. Predice qué imprime este código con codigo = 403 y con codigo = 301:
    match codigo:
        case 200 | 201 | 204:
            print("éxito")
        case 301 | 302:
            print("redirección")
        case 401 | 403:
            print("acceso denegado")
        case 404:
            print("no encontrado")
        case _:
            print("otro código")
  3. Reescribe este bloque de elif como match/case:
    rol = "editor"
    if rol == "admin":
        print("acceso total")
    elif rol == "editor" or rol == "moderador":
        print("acceso parcial")
    elif rol == "lector":
        print("solo lectura")
    else:
        print("rol no reconocido")
  4. ¿Por qué match/case no es adecuado para clasificar un IMC en rangos numéricos? ¿Qué estructura usarías en su lugar y por qué?
Respuesta orientativa
a

case _ es el caso por defecto — captura cualquier valor que no coincidió con ningún case anterior. No es obligatorio. Si el valor no coincide con ningún case y no hay case _, Python simplemente no ejecuta ningún bloque y continúa — sin error, sin aviso. Es recomendable incluirlo siempre para manejar valores inesperados.

b

Con codigo = 403: coincide con case 401 | 403 → imprime "acceso denegado". Con codigo = 301: coincide con case 301 | 302 → imprime "redirección".

c
rol = "editor"

match rol:
    case "admin":
        print("acceso total")
    case "editor" | "moderador":
        print("acceso parcial")
    case "lector":
        print("solo lectura")
    case _:
        print("rol no reconocido")

La versión match/case es más limpia — elimina la repetición de rol == en cada línea y el or se convierte en el más conciso |.

d

match/case compara contra valores fijos concretos — no puede expresar rangos como imc < 18.5 o 18.5 <= imc < 25. Para rangos numéricos la estructura correcta es if/elif/else con operadores de comparación, ordenando las condiciones de más restrictivo a menos restrictivo como vimos en la Parte II.

Regla de oro: match/case para valores discretos y fijos. if/elif/else para rangos, condiciones compuestas o cualquier expresión que no sea una comparación de igualdad simple.
El operador ternario: un if/else en una línea práctica

El operador ternario permite escribir un if/else simple en una sola línea cuando el resultado es un valor — no una acción. La sintaxis es:

valor_si_true if condicion else valor_si_false

El resultado de la expresión completa es uno de los dos valores — el que corresponde según la condición. Es especialmente frecuente en código generado por IA porque permite asignar valores condicionalmente de forma compacta.

— if/else normal vs operador ternario —

nota = 7.5

# if/else normal — cuatro líneas
if nota >= 5:
    resultado = "aprobado"
else:
    resultado = "suspenso"
print(resultado)

# Operador ternario — una línea
resultado = "aprobado" if nota >= 5 else "suspenso"
print(resultado)

# Directamente en un print o f-string
print("aprobado" if nota >= 5 else "suspenso")
print(f"Nota {nota}: {'aprobado' if nota >= 5 else 'suspenso'}")

— Casos de uso frecuentes —

# Asignar valor por defecto si None
nombre = None
nombre = nombre if nombre is not None else "Sin nombre"
print(nombre)

# Seleccionar entre dos textos según condición
stock = 0
estado = "disponible" if stock > 0 else "agotado"
print(f"Producto: {estado}")

# Seleccionar entre dos valores numéricos
precio_base = 100
es_socio = True
precio_final = precio_base * 0.8 if es_socio else precio_base
print(f"Precio final: {precio_final}€")

# En una lista — etiquetar datos
ventas = [150, 80, 200, 30, 120]
etiquetas = ["alta" if v > 100 else "baja" for v in ventas]
print(etiquetas)

— Cuándo NO usar el operador ternario —

El ternario es útil cuando la condición es simple y el resultado es un valor. Se convierte en un problema cuando la condición es compleja, cuando hay más de dos casos o cuando se anidan varios ternarios. En esos casos el código se vuelve ilegible — exactamente lo contrario de lo que queremos.

# ✓ Ternario apropiado — simple y legible
signo = "positivo" if x > 0 else "no positivo"

# ❌ Ternario inapropiado — difícil de leer
# Ternarios anidados: evitar siempre
x = 5
categoria = "alto" if x > 8 else "medio" if x > 4 else "bajo"
print(categoria)   # funciona, pero nadie lo entiende a primera vista

# ✓ La versión legible con if/elif/else
if x > 8:
    categoria = "alto"
elif x > 4:
    categoria = "medio"
else:
    categoria = "bajo"

# ❌ Ternario con acciones — no es para esto
# Cuando el resultado no es un valor sino una acción
# usar if/else normal es más claro
print("ok") if x > 0 else print("error")  # funciona, pero es feo

— El ternario vectorizado en pandas: np.where() —

En pandas, el equivalente vectorizado del operador ternario es np.where(condición, valor_si_true, valor_si_false). Aplica la lógica ternaria a toda una columna a la vez — igual que el filtrado booleano es el if de pandas, np.where() es el ternario de pandas.

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "producto": ["manzana", "pera", "naranja", "kiwi"],
    "ventas":   [150, 80, 200, 30],
    "precio":   [1.20, 0.90, 0.80, 1.50]
})

# Python: ternario sobre un valor
v = 150
etiqueta = "alta" if v > 100 else "baja"

# pandas: np.where() sobre toda la columna
df["rendimiento"] = np.where(df["ventas"] > 100, "alta", "baja")
print(df[["producto", "ventas", "rendimiento"]])

# Calcular precio con descuento condicionalmente
df["precio_final"] = np.where(
    df["ventas"] > 100,
    df["precio"] * 0.9,   # 10% descuento si ventas altas
    df["precio"]           # precio normal si ventas bajas
)
print(df[["producto", "precio", "precio_final"]])
  1. Reescribe estos bloques if/else como operador ternario en una línea:
    # A
    if edad >= 18:
        acceso = "permitido"
    else:
        acceso = "denegado"
    
    # B
    if temperatura > 37.5:
        alerta = True
    else:
        alerta = False
  2. Predice qué vale resultado en cada caso antes de ejecutar:
    x = 0
    resultado = "cero" if x == 0 else "positivo" if x > 0 else "negativo"
    print(resultado)
    
    x = 5
    resultado = "cero" if x == 0 else "positivo" if x > 0 else "negativo"
    print(resultado)
    
    x = -3
    resultado = "cero" if x == 0 else "positivo" if x > 0 else "negativo"
    print(resultado)
    ¿Es legible este código? ¿Cómo lo reescribirías?
  3. ¿Cuál es la diferencia entre el operador ternario de Python y np.where() de pandas? ¿Cuándo usarías cada uno?
  4. Este código generado por IA etiqueta clientes según su gasto. Identifica si el ternario es adecuado aquí o si sería mejor reescribirlo:
    gasto = 450
    segmento = "premium" if gasto >= 500 \
        else "medio" if gasto >= 200 \
        else "básico"
    print(f"Segmento: {segmento}")
Respuesta orientativa
a
# A
acceso = "permitido" if edad >= 18 else "denegado"

# B — aún más conciso
alerta = temperatura > 37.5
# La comparación ya devuelve True/False directamente
# No hace falta el ternario en este caso

El caso B es interesante: cuando el resultado del ternario es True o False, casi siempre puedes simplificarlo a la comparación directa — alerta = temperatura > 37.5 es equivalente y más conciso.

b

Con x = 0: "cero". Con x = 5: "positivo". Con x = -3: "negativo".

El código funciona pero no es legible — hay que seguir el encadenamiento mentalmente para entenderlo. La versión más clara:

if x == 0:
    resultado = "cero"
elif x > 0:
    resultado = "positivo"
else:
    resultado = "negativo"
Regla práctica: si necesitas anidar dos ternarios para expresar tres casos, usa if/elif/else. El ternario anidado es uno de los patrones más frecuentes en código generado por IA que resulta difícil de leer y verificar.
c

El ternario de Python opera sobre un único valor escalar — devuelve uno de los dos valores según la condición. np.where() opera sobre una Serie o columna entera — aplica la misma lógica a cada elemento de la columna y devuelve una Serie con los resultados.

Usa el ternario de Python cuando trabajas con un valor simple. Usa np.where() cuando quieres crear una columna nueva en un DataFrame basada en una condición sobre otra columna.

d

El ternario anidado funciona pero está en el límite de la legibilidad — tres casos con ternarios encadenados requieren esfuerzo para seguirlos. Con gasto = 450: 450 >= 500 es False → evalúa el segundo: 450 >= 200 es True → resultado: "medio".

Para este caso con tres segmentos la versión más legible es if/elif/else:

if gasto >= 500:
    segmento = "premium"
elif gasto >= 200:
    segmento = "medio"
else:
    segmento = "básico"
Cuándo el ternario es adecuado: dos casos, condición simple, resultado es un valor. En cuanto aparece un tercer caso o la condición se complica, if/elif/else es siempre más claro.
Parte VI

Tareas para casa

Tareas para antes de la Clase 5 tareas

Mismo esquema que la Clase 3: escribe el pseudocódigo, dáselo a la IA, lee el código que genera antes de ejecutarlo. Predice qué estructuras usará y por qué. Después verifica si tu predicción fue correcta y si el código tiene algún problema.

En esta clase el foco está en los condicionales — presta atención especial al orden de los elif, a los operadores (> vs >=, and vs or) y a los casos límite. Son exactamente los puntos donde el código generado por IA falla de forma silenciosa.

— Tarea 1 (obligatoria) —

Pseudocódigo — Clasificador de ventas

"""
Tengo una lista de productos. Cada producto
tiene nombre, ventas del mes y stock actual.

Quiero clasificar cada producto según estas reglas:
- Si el stock es 0: etiqueta "agotado"
  independientemente de las ventas
- Si el stock > 0 y ventas >= 150: "alto rendimiento"
- Si el stock > 0 y ventas >= 80: "rendimiento medio"
- Si el stock > 0 y ventas < 80: "bajo rendimiento"

Para cada producto quiero imprimir:
  nombre → ventas unidades → etiqueta

Los datos de ejemplo son:
  manzana:  ventas=200, stock=50
  pera:     ventas=85,  stock=30
  naranja:  ventas=60,  stock=0
  kiwi:     ventas=150, stock=20
  fresa:    ventas=40,  stock=0
"""

Antes de darle este pseudocódigo a la IA, responde estas preguntas:

  1. ¿Qué condición debe ir primera — la del stock o la de las ventas? ¿Por qué importa el orden?
  2. ¿Usará >= o > para el umbral de 150 ventas? ¿Qué etiqueta recibirá el kiwi con exactamente 150 ventas?
  3. ¿Qué estructura usará para iterar sobre los productos — una lista de tuplas, una lista de dicts o un DataFrame?

Después de obtener el código, verifica: ¿puso la condición de stock antes que la de ventas? ¿Qué pasaría si las pusiera al revés? Prueba con naranja (ventas=60, stock=0) — ¿recibe la etiqueta correcta?

— Tarea 2 (obligatoria) —

Pseudocódigo — Validador de registros

"""
Tengo una lista de registros de alumnos del máster.
Cada registro tiene: nombre, nota y matricula_activa.

Quiero validar cada registro y mostrar un informe:
- Si el nombre está vacío: "error — nombre vacío"
- Si la nota está fuera del rango 0-10:
  "error — nota fuera de rango"
- Si la matrícula no está activa:
  "inactivo — no computa para el ranking"
- Si todo está bien y la nota >= 5: "aprobado"
- Si todo está bien y la nota < 5: "suspenso"

Cada error debe identificar el registro afectado.
Si un registro tiene varios errores, mostrar
solo el primero que se detecte.

Los datos de ejemplo son:
  {"nombre": "Ana López",    "nota": 8.5,  "matricula_activa": True}
  {"nombre": "",             "nota": 7.0,  "matricula_activa": True}
  {"nombre": "Bob García",   "nota": 11.0, "matricula_activa": True}
  {"nombre": "Carlos Díaz",  "nota": 6.5,  "matricula_activa": False}
  {"nombre": "Diana Ruiz",   "nota": 4.5,  "matricula_activa": True}
"""

Antes de darle este pseudocódigo a la IA, responde estas preguntas:

  1. ¿Qué patrón usará para verificar si el nombre está vacío — nombre == "" o not nombre? ¿Son equivalentes?
  2. ¿Cómo expresará la condición "nota fuera del rango 0-10"? ¿Con or, con not ... and o con comparación encadenada?
  3. Para mostrar solo el primer error, ¿usará if/elif/else o múltiples if independientes? ¿Cuál es la diferencia?

Después de obtener el código, verifica: ¿qué imprime para el registro vacío? ¿Y para Bob con nota 11? ¿Y para Carlos con matrícula inactiva? ¿Coincide con tu predicción?

— Tarea 3 (opcional) —

Pseudocódigo — Clasificador de archivos

"""
Tengo una lista de nombres de archivo.
Quiero clasificar cada archivo según su extensión:

- csv, tsv, xlsx, xls → "datos"
- jpg, jpeg, png, gif, svg → "imagen"
- pdf, docx, doc, txt, md → "documento"
- py, ipynb, r, sql → "código"
- cualquier otra extensión → "desconocido"

Para cada archivo quiero imprimir:
  nombre_archivo → categoría

Debe funcionar aunque la extensión esté
en mayúsculas o minúsculas.

Los archivos de ejemplo son:
  "ventas_2024.CSV", "informe.pdf",
  "script.py", "foto_reunion.JPG",
  "datos.parquet", "notas.DOCX",
  "analisis.ipynb", "logo.svg"
"""

Antes de darle este pseudocódigo a la IA, responde estas preguntas:

  1. ¿Usará match/case o if/elif con in? ¿Cuál es más adecuado aquí?
  2. ¿Cómo extraerá la extensión de cada nombre de archivo? ¿Qué método de string usará?
  3. ¿Cómo manejará el hecho de que la extensión puede estar en mayúsculas o minúsculas?

Después de obtener el código, verifica: ¿clasifica correctamente "ventas_2024.CSV" y "foto_reunion.JPG" aunque estén en mayúsculas? ¿Y "datos.parquet" que no está en ninguna categoría? ¿Usó match/case o if/elif con in? ¿Cuál genera la IA más frecuentemente?

— Tarea 4 (opcional) —

Pseudocódigo — Análisis de DataFrame con pandas

"""
Tengo un DataFrame de ventas mensuales con columnas:
producto, ventas, precio, categoria y activo.

Quiero hacer tres cosas:

1. Añadir una columna "rendimiento" con tres valores:
   - "alto" si ventas >= 150
   - "medio" si ventas >= 80
   - "bajo" si ventas < 80

2. Añadir una columna "precio_ajustado":
   - Si ventas >= 150: precio * 0.85 (descuento 15%)
   - En caso contrario: precio sin cambio

3. Filtrar y mostrar solo los productos que:
   - Están activos (activo == True)
   - Son de categoría "fruta"
   - Tienen ventas > 0

Los datos de ejemplo son:
  producto   ventas  precio  categoria  activo
  manzana    200     1.20    fruta      True
  pera       85      0.90    fruta      True
  naranja    60      0.80    cítrico    True
  kiwi       0       1.50    exótico    False
  fresa      180     2.00    fruta      True
"""

Antes de darle este pseudocódigo a la IA, responde estas preguntas:

  1. Para la columna "rendimiento" con tres valores, ¿usará np.where() anidado o alguna otra función de pandas?
  2. Para la columna "precio_ajustado" con dos casos, ¿usará np.where()? ¿Qué operador usará para combinar las tres condiciones del filtro — and o &?
  3. Predice qué filas devuelve el filtro con los datos de ejemplo.

Después de obtener el código, verifica: ¿usó np.where() anidado para tres categorías o encontró otra solución? ¿El filtro usa & correctamente con paréntesis? ¿Qué productos pasan el filtro?

Para todas las tareas: antes de ejecutar, identifica en el código generado: el orden de los elif, los operadores de comparación en los límites (> vs >=), y si las condiciones compuestas usan and/or correctamente. Prueba siempre los valores exactamente en el límite de cada condición.

A continuación te pongo un vídeo de una introducción práctica al llamado vibe coding: una forma de programar apoyándose continuamente en asistentes de IA como ChatGPT, Claude o Cursor.

Más allá del tono informal, el vídeo muestra algo importante: cómo organizar un flujo de trabajo real donde el programador no solo escribe código, sino que también planifica, depura errores, formula instrucciones precisas y supervisa lo que genera la IA.

▶ Ver vídeo — Cursor Vibe Coding Tutorial

Mientras lo ves, fíjate especialmente en esto: ¿qué tareas siguen requiriendo comprensión humana? ¿Dónde aparecen los errores? ¿Por qué el autor insiste continuamente en revisar, depurar y entender el código generado?

El objetivo no es “dejar que la IA programe sola”, sino aprender a trabajar con ella de forma crítica y productiva.


Respuesta orientativa

— Tarea 1: Clasificador de ventas —

1

La condición de stock debe ir primera — es la más restrictiva y excluye completamente la lógica de ventas. Si la condición de ventas fuera primera, naranja (ventas=60, stock=0) recibiría "bajo rendimiento" en lugar de "agotado". El orden correcto es: primero la excepción (stock = 0), luego las reglas generales.

2

Debería usar >= para el umbral de 150 — el kiwi con exactamente 150 ventas debería recibir "alto rendimiento". Con > recibiría "rendimiento medio" — error silencioso en el caso límite. El resultado esperado: manzana → alto, pera → medio, naranja → agotado, kiwi → alto, fresa → agotado.

3

La IA probablemente usará una lista de diccionarios — es la estructura más natural para representar registros con campos nombrados. También puede usar una lista de tuplas o directamente un DataFrame de pandas. Las tres opciones son válidas — la más legible para este caso es la lista de diccionarios o el DataFrame.

— Tarea 2: Validador de registros —

1

not nombre y nombre == "" no son completamente equivalentes: not nombre también detecta None como nombre vacío, mientras que nombre == "" solo detecta el string vacío. En datos reales donde el nombre puede ser None, not nombre es más robusto.

2

La condición "nota fuera del rango 0-10" se expresa como nota < 0 or nota > 10. La forma con comparación encadenada sería la negación: not (0 <= nota <= 10). La IA probablemente usará or — es la más directa.

3

Para mostrar solo el primer error, debe usar if/elif/else — que se detiene en el primer True. Con múltiples if independientes, un registro con nombre vacío y nota incorrecta mostraría los dos errores. El resultado esperado: Ana → aprobado, registro vacío → error nombre, Bob → error nota, Carlos → inactivo, Diana → suspenso.

— Tarea 3: Clasificador de archivos —

1

Ambas opciones son válidas. match/case con agrupación de extensiones es más limpio para este caso porque compara un valor fijo contra múltiples opciones concretas. if/elif con in — if ext in {"csv", "tsv"} — también es correcto y quizás más familiar para la IA. Las dos soluciones son aceptables.

2

Usará nombre.split(".")[-1] para extraer la extensión — el último elemento al separar por punto. O el método os.path.splitext(nombre)[1] que es más robusto para nombres con varios puntos. Para normalizar a minúsculas: .lower() antes de comparar.

3

Aplicando .lower() a la extensión antes de comparar, "CSV" se convierte en "csv" y "JPG" en "jpg" — ambos clasificados correctamente. "parquet" no está en ninguna categoría → "desconocido".

— Tarea 4: Análisis de DataFrame —

1

Para tres categorías la IA probablemente usará np.where() anidado:

df["rendimiento"] = np.where(
    df["ventas"] >= 150, "alto",
    np.where(df["ventas"] >= 80, "medio", "bajo")
)

O puede usar pd.cut() que es más elegante para rangos. Ambas son correctas.

2

Para "precio_ajustado" con dos casos usará np.where() simple. El filtro combinado debe usar & con paréntesis:

df_filtrado = df[
    (df["activo"]) &
    (df["categoria"] == "fruta") &
    (df["ventas"] > 0)
]

Con los datos de ejemplo pasan el filtro: manzana, pera y fresa. Naranja es cítrico, kiwi está inactivo y tiene ventas cero.

Lo que hay que verificar: que el filtro usa & y no and, y que cada condición va entre paréntesis. Sin paréntesis la precedencia de operadores puede producir resultados incorrectos.