Python en Español

Tutorial para NumPy where (Con ejemplos)

Buscar entradas que satisfagan una condición específica es un proceso doloroso, especialmente si se busca en un gran conjunto de datos que tiene cientos o miles de entradas.

Si conoce las consultas SQL fundamentales, debe tener en cuenta la cláusula ‘WHERE’ que se utiliza con la sentencia SELECT para buscar tales entradas en una base de datos relacional que satisfagan ciertas condiciones.

NumPy ofrece una funcionalidad similar para encontrar tales elementos en una matriz NumPy que satisfagan una condición booleana dada a través de su función ‘where()’ – excepto que se utiliza de una forma ligeramente diferente a la sentencia SQL SELECT con la cláusula WHERE.

En este tutorial, veremos las diversas formas en que la función NumPy where puede ser utilizada para una variedad de casos de uso. Vamos a empezar.

 

 

Un uso muy simple de Numpy Where

Comencemos con una simple aplicación de ‘np.where()’ en un arreglo de números enteros de una dimensión NumPy.

Usaremos la función ‘np.where’ para encontrar posiciones con valores inferiores a 5.

Primero crearemos una matriz unidimensional de 10 valores enteros elegidos al azar entre 0 y 9

import numpy as np

np.random.seed(42)

a = np.random.randint()

print("a = {}".format(a))

Salida:

matriz a para uso simple de np.where

Ahora llamaremos a ‘np.where’ con la condición ‘a < 5’, es decir, preguntamos a ‘np.where’ para que nos diga en qué lugar de la matriz a están los valores inferiores a 5.

Nos devolverá una matriz de índices donde se cumple la condición especificada.

result = np.where(a < 5)

print(result)

Salida:

salida de uso simple de np.where en la matriz

Obtenemos los índices 1,3,6,9 como salida, y puede verificarse a partir de la matriz que los valores en estas posiciones son efectivamente menores que 5.

Obsérvese que el valor devuelto es una tupla de 1 elemento. Esta tupla tiene una matriz de índices.

Entenderemos la razón por la que el resultado se devuelve como una tupla cuando discutamos np.donde en las matrices 2D.

 

¿Como funciona NumPy where?

Para entender lo que ocurre dentro de la compleja expresión que implica la función “np.where”, es importante entender el primer parámetro de “np.where”, que es la condición.

Cuando llamamos a una expresión booleana que involucra una matriz NumPy como ‘a > 2’ o ‘a % 2 == 0’, en realidad devuelve una matriz NumPy de valores booleanos.

Esta matriz tiene el valor Verdadero en las posiciones en las que la condición se evalúa como Verdadera y tiene el valor Falso en otros lugares. Esto sirve como una ‘máscara’ para la función NumPy where.

Aquí hay un ejemplo de código.

a = np.array([1, 10, 13, 8, 7, 9, 6, 3, 0])

print ("a > 5:")

print(a > 5)

Salida:

Conjunto de máscaras booleanas usando condición en conjunto numpy

Así que lo que hacemos efectivamente es que pasamos una matriz de valores booleanos a la función ‘np.where’, que luego devuelve los índices donde la matriz tenía el valor True.

Esto puede verificarse pasando una matriz constante de valores booleanos en lugar de especificar la condición en la matriz que solemos hacer.

bool_array = np.array([True, True, True, False, False, False, False, False, False])

print(np.where(bool_array))

Salida:

pasando máscara booleana a np.where

Nota cómo, en lugar de pasar una condición en una matriz de valores reales, pasamos una matriz booleana, y la función ‘np.where’ nos devolvió los índices donde los valores eran Verdaderos.

 

Matrices 2D

Lo hemos visto en matrices NumPy unidimensionales, entendamos cómo se comportaría ‘np.where’ en matrices 2D.

La idea sigue siendo la misma. Llamamos a la función ‘np.where’ y pasamos una condición en una matriz 2D. La diferencia está en la forma en que devuelve los índices de resultado.

Anteriormente, np.where devolvía una matriz unidimensional de índices (almacenados dentro de una tupla) para una matriz 1-D, especificando las posiciones donde los valores satisfacen una condición dada.

Pero en el caso de una matriz 2D, se especifica una posición única utilizando dos valores: el índice de fila y el índice de columna.

Así que en este caso, np.where devolverá dos matrices, la primera de ellas con los índices de fila y la segunda con los índices de columna correspondientes.

Tanto estas matrices de índices de filas como de columnas se almacenan dentro de una tupla (ahora se sabe por qué tenemos una tupla como respuesta incluso en el caso de una matriz 1-D).

Veamos esto en acción para entenderlo mejor.

Escribiremos un código para encontrar dónde en una matriz de 3×3 están las entradas divisibles por 2.

np.random.seed(42)

a = np.random.randint(0,10, size=(3,3))

print("a =\n{}\n".format(a))

result = np.where(a % 2 == 0)

print("result: {}".format(result))

Salida:

np.where en una matriz 2D

La tupla devuelta tiene dos matrices, cada una de las cuales contiene los índices de fila y columna de las posiciones de la matriz donde los valores son divisibles por 2.

La selección ordenada por pares de los valores de las dos matrices nos da una posición cada uno.

La longitud de cada una de las dos matrices es de 5, lo que indica que hay cinco posiciones de este tipo que satisfacen la condición dada.

Si miramos el 3er par – (1,1), el valor a (1,1) en la matriz es seis, que es divisible por 2.

De la misma manera, se puede comprobar y verificar con otros pares de índices también.

 

Arreglos Multidimensionales

Al igual que vimos el funcionamiento de ‘np.where’ en una matriz 2-D, obtendremos resultados similares cuando apliquemos np.where en una matriz multidimensional NumPy.

La longitud de la tupla devuelta será igual al número de dimensiones de la matriz de entrada.

Cada matriz en la posición k en la tupla devuelta representará los índices en la dimensión k de los elementos que satisfacen la condición especificada.

Veamos rápidamente un ejemplo.

np.random.seed(42)

a = np.random.randint(0,10, size=(3,3,3,3)) #4-dimensional array

print("a =\n{}\n".format(a))

result = np.where(a == 5) #checking which values are equal to 5

print("len(result)= {}".format(len(result)))

print("len(result[0]= {})".format(len(result[0])))

Salida:

np.where en una matriz multidimensional

len(resultado) = 4 indica que la matriz de entrada es de 4 dimensiones.

La longitud de uno de los arreglos en la tupla de resultados es 6, lo que significa que hay seis posiciones en el arreglo dado de 3x3x3x3 donde la condición dada (es decir, que contiene el valor 5) se satisface.

 

Utilizar el resultado como un indice

Hasta ahora hemos visto cómo obtenemos la tupla de índices, en cada dimensión, de los valores que satisfacen la condición dada.

La mayoría de las veces estaríamos interesados en obtener los valores reales que satisfacen la condición dada en lugar de sus índices.

Para lograr esto, podemos usar la tupla devuelta como un índice en la matriz dada. Esto devolverá sólo aquellos valores cuyos índices estén almacenados en la tupla.

Comprobemos esto para el ejemplo de la matriz 2D.

np.random.seed(42)

a = np.random.randint(0,10, size=(3,3))

print("a =\n{}\n".format(a))

result_indices = np.where(a % 2 == 0)

result = a[result_indices]

print("result: {}".format(result))

Salida:

usando el resultado de np.where como índice de una matriz numpy

Como ya se ha dicho, obtenemos todos aquellos valores (no sus índices) que satisfacen la condición dada, que en nuestro caso era la divisibilidad por 2, es decir, números pares.

Parámetros ‘x’ and ‘y’

En lugar de obtener los índices como resultado de llamar a la función ‘np.where’, también podemos proporcionar como parámetros, dos matrices opcionales x e y de la misma forma (o forma transmisible) como matriz de entrada, cuyos valores serán devueltos cuando la condición especificada en los valores correspondientes de la matriz de entrada sea Verdadera o Falsa respectivamente.

Por ejemplo, si llamamos al método en una matriz unidimensional de longitud 10, y suministramos dos matrices más x y de la misma longitud.

En este caso, siempre que un valor de la matriz de entrada satisfaga la condición dada, se devolverá el valor correspondiente de la matriz x, mientras que si la condición es falsa en un valor dado, se devolverá el valor correspondiente de la matriz y.

Estos valores de x y y en sus respectivas posiciones se devolverán como una matriz de la misma forma que la matriz de entrada.

Vamos a entender mejor esto a través del código.

np.random.seed(42)

a = np.random.randint(0,10, size=(10))

x = a

y = a*10

print("a = {}".format(a))

print("x = {}".format(x))

print("y = {}".format(y))

result = np.where(a%2 == 1, x, y) #if number is odd return the same number else return its multiple of 10.

print("\nresult = {}".format(result))

Salida:

demostración del uso de los parámetros x e y en numpy donde

Este método es útil si se desea sustituir los valores que satisfacen una condición particular por otro conjunto de valores y dejar los que no satisfacen la condición sin cambios.

En ese caso, pasaremos el valor o valores de reemplazo al parámetro x y la matriz original al parámetro y.

Tenga en cuenta que podemos pasar tanto x como y juntos o ninguno de ellos. No podemos pasar uno de ellos y saltar el otro.

 

Aplicarla en los DataFrames de Pandas

La función de “dónde” de Numpy no es exclusiva de las matrices de NumPy. Puedes usarla con cualquier iterable que produzca una lista de valores booleanos.

Veamos cómo podemos aplicar la función ‘np.where’ en un Pandas DataFrame para ver si la cadena en una columna contiene una subcadena en particular.

import pandas as pd

import numpy as np

df  = pd.DataFrame({"fruit":["apple", "banana", "musk melon",
                             "watermelon", "pineapple", "custard apple"],
                   "color": ["red", "green/yellow", "white",
                            "green", "yellow", "green"]})

print("Fruits DataFrame:\n")

print(df)

Salida:

Aplicarla en los DataFrames de Pandas

Ahora vamos a usar ‘np.where’ para extraer esas filas del DataFrame ‘df’ donde la columna ‘fruit’ tiene la subcadena ‘apple’.

apple_df = df.iloc[np.where(df.fruit.str.contains("apple"))]

print(apple_df)

Salida:

uso de np.where en pandas DataFrame para encontrar dónde los valores de columna contienen una subcadena

Intentemos un ejemplo más en el mismo DataFrame en el que extraemos filas para las que la columna “color” no contiene la subcadena “yell”.

Nota: usamos el signo tilde (~) para invertir los valores booleanos en el DataFrame de Pandas o en una matriz NumPy.

non_yellow_fruits = df.iloc[np.where(~df.color.str.contains("yell"))]

print("Non Yellow fruits:\n{}".format(non_yellow_fruits))

Salida:

uso de np.where en pandas DataFrame para encontrar dónde los valores de columna no contienen una subcadena

Condiciones multiples

Hasta ahora hemos estado evaluando una sola condición booleana en la función ‘np.where’. A veces podemos necesitar combinar múltiples condiciones booleanas usando operadores booleanos como ‘AND’ u ‘Or’.

Es fácil especificar múltiples condiciones y combinarlas usando un operador booleano.

La única salvedad es que para la matriz NumPy de valores booleanos, no podemos usar las palabras clave normales ‘And’ u ‘or’ que usamos típicamente para los valores individuales.

Necesitamos usar el operador ‘&’ para ‘AND’ y el operador ‘|’ para la operación ‘OR’ para las operaciones de combinación booleana por elementos.

Entendamos esto a través de un ejemplo.

np.random.seed(42)

a = np.random.randint(0,15, (5,5)) #5x5 matrix with values from 0 to 14

print(a)

Salida:

Una matriz de 5x5 para ser utilizada para demostrar múltiples condiciones en np.where

Buscaremos valores menores de 8 y que sean extraños. Podemos combinar estas dos condiciones usando el operador AND (&).

# get indices of odd values less than 8 in a
indices = np.where((a < 8) & (a % 2==1)) 

#print the actual values
print(a[indices])

Salida:

combinando múltiples condiciones con la operación AND en np.where

También podemos usar el operador de quirófano (|) para combinar las mismas condiciones.

Esto nos dará valores que son “menos de 8” O “valores impares”, es decir, todos los valores menores de 8 y todos los valores impares mayores de 8 serán devueltos.

# get indices of values less than 8 OR odd values in a
indices = np.where((a < 8) | (a % 2==1))

#print the actual values
print(a[indices])

Salida:

combinando múltiples condiciones con operación OR en np.where

Where anidado (where dentro de where)

Volvamos al ejemplo de nuestra mesa de “frutas”.

import pandas as pd

import numpy as np

df  = pd.DataFrame({"fruit":["apple", "banana", "musk melon",
                             "watermelon", "pineapple", "custard apple"],
                   "color": ["red", "green/yellow", "white",
                            "green", "yellow", "green"]})

print("Fruits DataFrame:\n")

print(df)

Salida:

DataFrame de la fruta que se utilizará para demostrar dónde anidadas

Ahora supongamos que quisiéramos crear una columna más “bandera”, que tendría el valor 1 si la fruta de esa fila tiene una subcadena “manzana” o es de color “amarillo”.

Podemos lograrlo utilizando llamadas “nested where”, es decir, llamaremos a la función “np.where” como un parámetro dentro de otra llamada “np.where”.

df['flag'] = np.where(df.fruit.str.contains("apple"), 1, # if fruit == 'apple', set 1
                     np.where(df.color.str.contains("yellow"), 1, 0)) #else if color has 'yellow' set 1, else set 0

print(df)

Salida:

salida de usar anidado en un pandas DataFrame

Traduzcamos la compleja expresión anterior al español simple como:

  1. Si la columna “fruit” tiene la subcadena “apple”, establezca el valor de “bandera” en 1
  2. Si no:
    1. Si la columna “color” tiene la subcadena “yellow”, ponga el valor de “bandera” en 1
    2. Si no: coloca el valor de la  ‘bandera’

Nota: que podemos lograr el mismo resultado usando el operador OR (|).

#set flag = 1 if any of the two conditions is true, else set it to 0
df['flag'] = np.where(df.fruit.str.contains("apple") | 
                      df.color.str.contains("yellow"), 1, 0)

print(df)

Salida:

salida de equivalencia de anidado donde se usa el operador OR

Por lo tanto, nested where es particularmente útil para datos tabulares como Pandas DataFrames y es un buen equivalente de la cláusula anidada WHERE utilizada en las consultas SQL.

 

Buscar filas de ceros

A veces, en una matriz 2D, algunas o todas las filas tienen todos los valores iguales a cero. Por ejemplo, mira la siguiente matriz NumPy.

a = np.array([[1, 2, 0],
             [0, 9, 20],
             [0, 0, 0],
             [3, 3, 12],
             [0, 0, 0]
             [1, 0, 0]])

print(a)

Salida:

una matriz numpy 2d que tiene dos filas con todos los valores iguales a cero

Como podemos ver, las filas 2 y 4 tienen todos los valores iguales a cero. ¿Pero cómo encontramos esto usando la función ‘np.where’?

Si queremos encontrar tales filas usando la función NumPy where, necesitaremos encontrar una matriz booleana que indica qué filas tienen todos los valores iguales a cero.

Podemos utilizar ‘np.any()‘ con ‘axis = 1’, que devuelve True si al menos uno de los valores de una fila es distinto de cero.

El resultado de np.any() sera un arreglo booleano de longitud igual al numero de filas en nuestras matriz NumPy en la cual las posiciones con el valor verdadero indican la fila correspondiente tenga por lo menos un valor no nulo.

¡Pero necesitamos un arreglo booleano que sea el opuesto a esto!

Bueno, podemos conseguir esto a través de un simple paso de inversión. El operador NOT o tilde (~) invierte cada uno de los valores booleanos en una matriz NumPy.

La matriz booleana invertida puede entonces pasar a la función ‘np.where’.

Ok, esa fue una larga y cansada explicación.

Veamos esta cosa en acción.

zero_rows = np.where(~np.any(a, axis=1))[0]

print(zero_rows)

Salida:

salida de np.where que indica índices de cero filas

Veamos lo que está sucediendo paso a paso:

  1. np.any() devuelve True si al menos un elemento de la matriz es True (no cero). axis = 1 lo indica para hacer esta operación en línea.
  2. Devolvería una matriz booleana de longitud igual al número de filas de a, con el valor True para las filas que tienen valores distintos de cero, y False para las filas que tienen todos los valores = 0.
    np.any(a, axis=1)
    Salida:

    salida de np.any en una matriz 2d con parámetro axis = 1
  3. El operador tilde (~) invierte la matriz booleana anterior:
    ~np.any(a, axis=1)
    Salida:

    salida de invertir la matriz booleana usando el operador tilde
  4. ‘np.where()’ acepta esta matriz booleana y devuelve índices con el valor True.

Se utiliza la indexación [0] porque, como ya se ha dicho, ‘np.where’ devuelve una tupla.

 

Buscar la última ocurrencia de una condición verdadera

Sabemos que la función “dónde” de NumPy devuelve múltiples índices o pares de índices (en el caso de una matriz 2D) para los cuales la condición especificada es verdadera.

Pero a veces nos interesa sólo la primera o la última ocurrencia del valor para el cual se cumple la condición especificada.

Tomemos el simple ejemplo de una matriz unidimensional donde encontraremos la última ocurrencia de un valor divisible por 3.

np.random.seed(42)

a = np.random.randint(0,10, size=(10))

print("Array a:", a)

indices = np.where(a%3==0)[0]

last_occurrence_position = indices[-1]

print("last occurrence at", last_occurrence_position)

Salida:

una matriz ay un índice de salida de la última aparición de una condición en np.where

Aquí podríamos usar directamente el índice ‘-1’ en los índices devueltos para obtener el último valor de la matriz.

¿Pero cómo extraeríamos la posición de la última ocurrencia en una matriz multidimensional, donde el resultado devuelto es una tupla de matrices y cada matriz almacena los índices en una de las dimensiones?

Podemos usar la funcion zip, que toma múltiples iterables y devuelve una combinación de valores de cada iterable en el orden dado.

Devuelve un objeto iterador, por lo que necesitamos convertir el objeto devuelto en una lista o una tupla o cualquier iterable.

Veamos primero cómo funciona el zip:

a = (1, 2, 3, 4)

b = (5, 6, 7, 8)

c = list(zip(a,b))

print(c)

Salida:

demostración de la función zip

Así que el primer elemento de a y el primer elemento de b forman una tupla, luego el segundo elemento de a y el segundo elemento de b forman la segunda tupla en c, y así sucesivamente.

Usaremos la misma técnica para encontrar la posición de la última ocurrencia de una condición que se satisface en un conjunto multidimensional.

Usémosla para una matriz 2D con la misma condición que vimos en el ejemplo anterior.

np.random.seed(42)

a = np.random.randint(0,10, size=(3,3))

print("Matrix a:\n", a)

indices = np.where(a % 3 == 0)

last_occurrence_position = list(zip(*indices))[-1]

print("last occurrence at",last_occurrence_position)

Salida:

una matriz ay un índice de salida de la última aparición de una condición en np.where

Podemos ver en la matriz que la última ocurrencia de un múltiplo de 3 está en la posición (2,1), que es el valor 6.

Nota: El operador * es un operador de desempaquetado que podemos utilizar para desempaquetar una secuencia de valores en argumentos posicionales separados.

 

Uso de los datos del DateTime

Hemos estado usando la función ‘np.where’ para evaluar ciertas condiciones en valores numéricos (mayor que, menor que, igual a, etc.), o datos de cadena (contiene, no contiene, etc.)

También podemos utilizar la función ‘np.where’ en los datos de fecha y hora.

Por ejemplo, podemos comprobar en una lista de valores de fecha y hora, cuáles de las instancias de fecha y hora son anteriores o posteriores a una fecha y hora determinadas.

Entendamos esto a través de un ejemplo.

Nota: Usaremos el módulo datetime de Python para crear objetos datetime.

Primero definamos un DataFrame que especifique las fechas de nacimiento de 6 individuos.

import datetime

names = ["John", "Smith", "Stephen", "Trevor", "Kylie", "Aariz"]

dob = [datetime.datetime(1969, 12, 1),
       datetime.datetime(1988, 3, 13),
      datetime.datetime(1992, 5, 19),
      datetime.datetime(1972, 5, 31),
      datetime.datetime(1989, 11, 28),
      datetime.datetime(1993, 2, 7)]

data_birth = pd.DataFrame({"name":names, "date_of_birth":dob})

print(data_birth)

Salida:

pandas DataFrame especificando nombres y fecha de nacimiento de 6 individuos

Esta tabla tiene gente de diversas edades!

Especifiquemos ahora una condición en la que nos interesan los individuos nacidos el 1 de enero de 1990 o después de esa fecha.

post_90_indices = np.where(data_birth.date_of_birth >= '1990-01-01')[0]

print(post_90_indices)

Salida:

índices de filas de salida de individuos nacidos en o después de 1990-01-01

Las filas 2 y 5 tienen a Smith y Kylie, que nacieron en los años 1992 y 1993 respectivamente.

Aquí estamos usando el operador “mayor o igual a” (>=) en un dato de fecha y hora, que generalmente usamos con datos numéricos.

Esto es posible a través de operator overloading.

Intentemos un ejemplo más. Busquemos a los individuos que nacieron en mayo.

Nota: La serie de Pandas proporciona un sub-módulo “dt” para operaciones específicas de fecha y hora, similar al sub-módulo “str” que vimos en nuestros ejemplos anteriores.

may_babies = data_birth.iloc[np.where(data_birth.date_of_birth.dt.month == 5)]

print("May babies:\n{}".format(may_babies))

Salida:

salida de np.where que muestra personas nacidas en el mes de mayo

Conclusión

Comenzamos el tutorial con el simple uso de la función ‘np.where’ en un conjunto unidimensional con condiciones especificadas en los datos numéricos.

Luego miramos la aplicación de ‘np.where’ en una matriz 2D y luego en una matriz multidimensional general NumPy.

También entendimos cómo interpretar la tupla de matrices devueltas por ‘np.where’ en tales casos.

Luego entendimos la funcionalidad de ‘np.where’ en detalle, usando máscaras booleanas.

También vimos cómo podíamos usar el resultado de este método como un índice para extraer los valores originales reales que satisfacen la condición dada.

Observamos el comportamiento de la función ‘np.where’ con los argumentos opcionales ‘x’ e ‘y’.

Luego comprobamos la aplicación de ‘np.where’ en un DataFrame de Pandas, seguido de su uso para evaluar múltiples condiciones.

También observamos el uso anidado de ‘np.where’, su uso para encontrar las filas cero en una matriz 2D, y luego encontrar la última ocurrencia del valor que satisface la condición especificada por ‘np.where’.

Finalmente, usamos la función ‘np.where’ en un dato de fecha y hora, especificando condiciones cronológicas en una columna de fecha y hora en un Pandas DataFrame.

Mokhtar Ebrahim
Fundadora de LikeGeeks. Estoy trabajando como administrador de sistemas Linux desde 2010. Soy responsable de mantener, proteger y solucionar problemas de servidores Linux para múltiples clientes de todo el mundo. Me encanta escribir guiones de shell y Python para automatizar mi trabajo.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *