Datos booleanos

Introducción

En este capítulo presentaremos los datos booleanos: datos que pueden ser True o False (y que también pueden codificarse como 1 y 0). Primero veremos las variables booleanas fundamentales de Python, verdadero y falso, y después cómo funcionan en los dataframes.

Booleanos

Algunas de las operaciones más importantes que realizarás son con los valores True y False, también conocidos como tipos de dato booleanos. Son variables fundamentales de Python, igual que lo son números como 1.

Variables booleanas y condiciones

Asignar el valor True o False a una variable se hace igual que cualquier otra asignación:

bool_variable = True
bool_variable
True

Hay dos tipos de operaciones asociadas a los booleanos: las operaciones booleanas, en las que se combinan booleanos existentes, y las operaciones de condición, que crean un booleano al ejecutarse.

Los operadores booleanos que devuelven booleanos son los siguientes:

Operador Descripción
x and y ¿son x e y ambos True?
x or y ¿es al menos uno de x e y True?
not x ¿es x False?

Se comportan como cabría esperar: True and False se evalúa como False, mientras que True or False se evalúa como True. También existe la palabra clave not. Por ejemplo

not True
False

como cabría esperar.

Las condiciones son expresiones que se evalúan como booleanos. Un ejemplo sencillo es 10 == 20. == es un operador que compara los objetos a cada lado y devuelve True si tienen los mismos valores, aunque ten cuidado al usarlo con tipos de datos diferentes.

Aquí tienes una tabla de condiciones que devuelven booleanos:

Operador Descripción
x == y ¿es x igual a y?
x != y ¿es x distinto de y?
x > y ¿es x mayor que y?
x >= y ¿es x mayor o igual que y?
x < y ¿es x menor que y?
x <= y ¿es x menor o igual que y?
x is y ¿es x el mismo objeto que y?

Como ves en la tabla, lo contrario de == es !=, que puedes leer como «no es igual al valor de». Aquí tienes un ejemplo de ==:

boolean_condition = 10 == 20
print(boolean_condition)
False
TipEjercicio

¿Cuál es el resultado de evaluar not (not True)?

La verdadera potencia de las condiciones aparece cuando empezamos a usarlas en ejemplos más complejos. Algunas de las palabras clave que evalúan condiciones son if, else, and, or, in, not e is. Aquí tienes un ejemplo que muestra cómo funcionan algunas de estas palabras clave condicionales:

name = "Ada"
score = 99

if name == "Ada" and score > 90:
    print("Ada, you achieved a high score.")

if name == "Smith" or score > 90:
    print("You could be called Smith or have a high score")

if name != "Smith" and score > 90:
    print("You are not called Smith and you have a high score")
Ada, you achieved a high score.
You could be called Smith or have a high score
You are not called Smith and you have a high score

Las tres condiciones se evalúan como True, así que se imprimen los tres mensajes. Dado que == y != comprueban la igualdad y la desigualdad, respectivamente, quizá te preguntes para qué sirven las palabras clave is y not. Recuerda que en Python todo es un objeto y que se pueden asignar valores a los objetos. == y != comparan valores, mientras que is y not comparan objetos. Por ejemplo,

name_list = ["Ada", "Adam"]
name_list_two = ["Ada", "Adam"]

# Compare values
print(name_list == name_list_two)

# Compare objects
print(name_list is name_list_two)
True
False

Ten en cuenta que el código con muchas sentencias if ramificadas no es muy útil ni para ti ni para nadie que lea tu código. Algunos verificadores automáticos de código lo detectarán y te dirán que tu código es demasiado complejo. Casi siempre hay una forma de reescribir el código sin tanta lógica ramificada que resultará mejor y más clara que tener muchas sentencias if anidadas.

Una de las palabras clave condicionales más útiles es in. Seguramente aparece diez veces al día en la vida de la mayoría de los programadores, porque permite localizar una variable o asegurarse de que algo está donde debe estar.

name_list = ["Lovelace", "Smith", "Hopper", "Babbage"]

print("Lovelace" in name_list)

print("Bob" in name_list)
True
False
TipEjercicio

Comprueba con in si “a” está en el string “Walloping weasels”. ¿Está “a” in “Anodyne”?

Lo contrario es not in.

Por último, una construcción condicional que seguro usarás en algún momento es la estructura if…else:

score = 98

if score == 100:
    print("Top marks!")
elif score > 90 and score < 100:
    print("High score!")
elif score > 10 and score <= 90:
    pass
else:
    print("Better luck next time.")
High score!

Observa que esto no hace nada si la puntuación está entre 11 y 90, y en caso contrario imprime un mensaje.

TipEjercicio

Crea una nueva sentencia if … elif … else que imprima “well done” si una puntuación es mayor que 90, “good” si está entre 40 y 90, y “bad luck” en cualquier otro caso.

Una característica práctica de Python es que puedes hacer varias comparaciones booleanas en una sola línea.

a, b = 3, 6

1 < a < b < 20
True

Condiciones en las list comprehensions

Las list comprehensions (listas por comprensión) son un patrón increíblemente útil en Python. Aquí tienes una sencilla que produce una lista con los primeros 12 números empezando por 0:

[x for x in range(12)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

Los booleanos aportan condicionalidad. Añadiremos una sentencia if seguida de una condición que se evalúa como True o False según el valor de x. Así, por ejemplo, podemos pedir solo los números divisibles por 2:

[x for x in range(12) if x % 2 == 0]
[0, 2, 4, 6, 8, 10]

Este truco funciona incluso con una cláusula else (pero observa que hemos movido tanto if como else antes de la parte for x in ...)

[x if x % 2 == 0 else "Not divisible by 2" for x in range(12)]
[0,
 'Not divisible by 2',
 2,
 'Not divisible by 2',
 4,
 'Not divisible by 2',
 6,
 'Not divisible by 2',
 8,
 'Not divisible by 2',
 10,
 'Not divisible by 2']

Valores truthy y falsy

Los objetos de Python pueden usarse en expresiones que devuelven un valor booleano, como cuando una lista, listy, se usa con if listy. Los objetos integrados de Python que están vacíos normalmente se evalúan como False, y se dice que son «falsy». En cambio, cuando estos objetos integrados no están vacíos, se evalúan como True y se dice que son «truthy». Veamos algunos ejemplos:

listy = []
other_listy = [1, 2, 3]

if not (listy):
    print("Falsy")
else:
    print("Truthy")
Falsy
if not (other_listy):
    print("Falsy")
else:
    print("Truthy")
Truthy

El método no solo funciona con listas; sirve para muchos otros objetos truthy y falsy:

if not 0:
    print("Falsy")
else:
    print("Truthy")
Falsy
if not [0, 0, 0]:
    print("Falsy")
else:
    print("Truthy")
Truthy

Observa que el cero era falsy, es la «nada» de un float, pero una lista de tres ceros no es una lista vacía, así que se evalúa como truthy.

if not None:
    print("Falsy")
else:
    print("Truthy")
Falsy

Saber qué es truthy o falsy es útil en la práctica; imagina que quieres recurrir a un comportamiento por defecto si una lista llamada list_vals no contiene ningún valor. Ahora sabes que puedes hacerlo simplemente con if list_vals.

any() y all()

Por supuesto, el mundo de los booleanos es muy amplio; no siempre aparecen solos. Por eso existen los operadores any() y all(). Se aplican a iterables de booleanos, como una lista de booleanos.

any() toma una lista de booleanos con al menos un valor verdadero y devuelve verdadero:

any([True, False, False])
True

all() toma una lista de booleanos y devuelve verdadero solo si todos los valores son verdaderos:

all([True, True, True, True])
True

Ambos funcionan también con 1 y 0:

all([0, 0, 0, 1])
False

Booleanos en dataframes de pandas

Operaciones con booleanos en dataframes

Con bastante frecuencia te encontrarás trabajando con datos que tienen valores True o False en un dataframe. Es fácil crear una columna de booleanos en un dataframe de pandas:

import pandas as pd

df = pd.DataFrame.from_dict(
    {
        "bool_col_1": [False] * 3 + [True, True],
        "bool_col_2": [True, False, True, False, True],
    }
)
df
bool_col_1 bool_col_2
0 False True
1 False False
2 False True
3 True False
4 True True

Podemos realizar operaciones con ellas igual que con las columnas normales de un dataframe de pandas. Aceptan como operaciones & (y), | (o), == (igual) y != (distinto):

df["bool_col_1"] | df["bool_col_2"]
0     True
1    False
2     True
3     True
4     True
dtype: bool

A menudo resulta útil contar el número de valores verdaderos. Si sumas columnas booleanas en un dataframe de pandas, obtendrás el total de valores True:

df.sum()
bool_col_1    2
bool_col_2    3
dtype: int64

Y si alguna vez recibes datos con formato de 1 y 0 en lugar de True y False, es fácil convertirlos cambiando el tipo de dato:

df = pd.DataFrame.from_dict({"bool_col": [0, 1, 0, 1, 1]})
df["bool_col"].astype(bool)
0    False
1     True
2    False
3     True
4     True
Name: bool_col, dtype: bool

Crear booleanos a partir de comparaciones con columnas

También es posible crear columnas booleanas a partir de columnas numéricas (o de otro tipo). Usemos el dataset diamonds para demostrarlo:

diamonds = pd.read_csv(
    "https://github.com/mwaskom/seaborn-data/raw/master/diamonds.csv"
)
diamonds.head()
carat cut color clarity depth table price x y z
0 0.23 Ideal E SI2 61.5 55.0 326 3.95 3.98 2.43
1 0.21 Premium E SI1 59.8 61.0 326 3.89 3.84 2.31
2 0.23 Good E VS1 56.9 65.0 327 4.05 4.07 2.31
3 0.29 Premium I VS2 62.4 58.0 334 4.20 4.23 2.63
4 0.31 Good J SI2 63.3 58.0 335 4.34 4.35 2.75

Vamos a crear una nueva variable booleana que indique cuándo el precio es superior a 1000.

diamonds["expensive"] = diamonds["price"] > 1000
diamonds.sample(10)
carat cut color clarity depth table price x y z expensive
42026 0.38 Ideal E VVS1 61.9 56.0 1267 4.64 4.67 2.88 True
16211 1.07 Ideal H VS1 60.2 59.0 6479 6.66 6.69 4.02 True
10859 1.03 Ideal H VS1 61.5 57.0 4874 6.49 6.45 3.98 True
48655 0.34 Very Good H VS2 62.4 60.0 537 4.41 4.44 2.76 False
1925 0.80 Premium F SI1 60.1 58.0 3082 6.07 6.05 3.64 True
27367 0.28 Premium E VVS1 61.3 60.0 646 4.18 4.21 2.57 False
45643 0.25 Ideal G VVS2 61.5 56.0 525 4.06 4.08 2.50 False
2691 0.90 Good I VS2 63.7 61.0 3246 6.10 6.06 3.87 True
53375 0.85 Ideal H SI2 61.8 54.0 2659 6.10 6.07 3.76 True
20934 1.61 Ideal J VS2 61.6 56.0 9141 7.52 7.54 4.64 True

Por supuesto, esto también podría haberse conseguido con una llamada a assign:

diamonds.assign(expensive=lambda x: x["price"] > 1000).head()
carat cut color clarity depth table price x y z expensive
0 0.23 Ideal E SI2 61.5 55.0 326 3.95 3.98 2.43 False
1 0.21 Premium E SI1 59.8 61.0 326 3.89 3.84 2.31 False
2 0.23 Good E VS1 56.9 65.0 327 4.05 4.07 2.31 False
3 0.29 Premium I VS2 62.4 58.0 334 4.20 4.23 2.63 False
4 0.31 Good J SI2 63.3 58.0 335 4.34 4.35 2.75 False

Otro uso de los booleanos bastante útil con los dataframes es la función .isin(). Por ejemplo, si solo quieres valores True o False que indiquen si un conjunto de columnas está en un dataframe:

diamonds.columns.isin(["x", "y", "z"])
array([False, False, False, False, False, False, False,  True,  True,
        True, False])

any() y all() en dataframes

Una columna de booleanos de pandas se comporta de forma muy parecida a una lista de booleanos, y podemos aplicarle la misma lógica mediante los métodos integrados de pandas .any() y .all(). Esperamos que algunas entradas de "expensive" sean verdaderas, así que any() debería devolver verdadero:

diamonds["expensive"].any()
np.True_

Subconjuntos lógicos

Aunque lo hemos estado usando todo el tiempo, conviene hacerlo explícito: los booleanos pueden usarse para obtener subconjuntos lógicos de un dataframe. Supongamos que solo queremos las partes de un dataframe en las que x es mayor que y:

diamonds[diamonds["x"] > diamonds["y"]]
carat cut color clarity depth table price x y z expensive
1 0.21 Premium E SI1 59.8 61.0 326 3.89 3.84 2.31 False
8 0.22 Fair E VS2 65.1 61.0 337 3.87 3.78 2.49 False
11 0.23 Ideal J VS1 62.8 56.0 340 3.93 3.90 2.46 False
12 0.22 Premium F SI1 60.4 61.0 342 3.88 3.84 2.33 False
14 0.20 Premium E SI2 60.2 62.0 345 3.79 3.75 2.27 False
... ... ... ... ... ... ... ... ... ... ... ...
53928 0.79 Premium E SI2 61.4 58.0 2756 6.03 5.96 3.68 True
53929 0.71 Ideal G VS1 61.4 56.0 2756 5.76 5.73 3.53 True
53930 0.71 Premium E SI1 60.5 55.0 2756 5.79 5.74 3.49 True
53931 0.71 Premium F SI1 59.8 62.0 2756 5.74 5.73 3.43 True
53938 0.86 Premium H SI2 61.0 58.0 2757 6.15 6.12 3.74 True

23423 rows × 11 columns

La expresión diamonds["x"] > diamonds["y"] crea una columna de booleanos que se usa para filtrar y quedarse solo con las filas en las que la condición es verdadera.