Buscar entradas que satisfagan una condición específica es un proceso doloroso, especialmente si lo está buscando en un conjunto de datos grande que tiene cientos o miles de entradas. Si conoce las consultas SQL fundamentales, debe conocer la cláusula 'WHERE' que se usa con la instrucción SELECT para obtener dichas entradas de una base de datos relacional que satisfaga ciertas condiciones.
NumPy ofrece una funcionalidad similar para encontrar dichos elementos en una matriz NumPy que satisfacen una condición booleana dada a través de su función ' where () ', excepto que se usa de una manera ligeramente diferente a la instrucción SQL SELECT con la cláusula WHERE.
En este tutorial, veremos las diversas formas en que la función NumPy where puede usarse para una variedad de casos de uso. Vámonos.
Un uso muy simple de NumPy donde
Primero crearemos una matriz unidimensional de 10 valores enteros elegidos al azar entre 0 y 9.
1 2 3 4 5 6 7 | import numpy as np np.random.seed( 42 ) a = np.random.randint() print( "a = {}" .format(a)) |
Salida:

1 2 3 | result = np.where(a < 5 ) print(result) |
Salida:

¿Cómo funciona NumPy where?
Para comprender lo que sucede dentro de la expresión compleja que involucra la función 'np.where', es importante comprender el primer parámetro de 'np.where', es decir, 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 donde la condición se evalúa como Verdadera y tiene el valor Falso en otros lugares. Esto sirve como una ' máscara ' para NumPy donde funciona.
Aquí hay un ejemplo de código.
1 2 3 4 5 | a = np.array([ 1 , 10 , 13 , 8 , 7 , 9 , 6 , 3 , 0 ]) print ( "a > 5:" ) print(a > 5 ) |
Salida:

Entonces, lo que efectivamente hacemos es pasar una matriz de valores booleanos a la función 'np.where' que luego devuelve los índices donde la matriz tenía el valor Verdadero .
Esto se puede verificar pasando una matriz constante de valores booleanos en lugar de especificar la condición en la matriz que solemos hacer.
1 2 3 | bool_array = np.array([True, True, True, False, False, False, False, False, False]) print(np.where(bool_array)) |
Salida:

Observe 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
Ahora que lo hemos visto en matrices NumPy unidimensionales, entendamos cómo se comportaría 'np.where' en matrices 2D.
Tanto estas matrices de índice de filas como de columnas se almacenan dentro de una tupla (ahora sabe por qué obtuvimos 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.
1 2 3 4 5 6 7 8 9 | 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:

La tupla devuelta tiene 2 matrices, cada una con los índices de fila y columna de las posiciones en la matriz donde los valores son divisibles por 2.
Matriz multidimensional
Así como vimos el funcionamiento de 'np.where' en una matriz 2-D, obtendremos resultados similares cuando aplicamos np.where en una matriz NumPy multidimensional.
Veamos rápidamente un ejemplo.
01 02 03 04 05 06 07 08 09 10 11 | 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:

len (resultado) = 4 indica que la matriz de entrada es de 4 dimensiones.
La longitud de una de las matrices en la tupla de resultados es 6, lo que significa que hay seis posiciones en la matriz 3x3x3x3 dada donde se cumple la condición dada (es decir, que contiene el valor 5).
Usando el resultado como índice
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 índice en la matriz dada. Esto devolverá solo aquellos valores cuyos índices están almacenados en la tupla.
Comprobemos esto para el ejemplo de la matriz 2-D.
01 02 03 04 05 06 07 08 09 10 11 | 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:

Como se discutió anteriormente, obtenemos todos esos valores (no sus índices) que satisfacen la condición dada que, en nuestro caso, era divisibilidad por 2, es decir, números pares.
Parámetros 'x' e '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 xey de la misma forma (o forma broadcastable) como matriz de entrada, cuyos valores se devolverán cuando la condición especificada en los valores correspondientes en la matriz de entrada es Verdadero o Falso respectivamente.
Estos valores de xey en sus respectivas posiciones se devolverán como una matriz de la misma forma que la matriz de entrada.
Comprendamos mejor esto a través del código.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 dieciséis 17 | 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:

Tenga en cuenta que podemos pasar tanto x como y juntos o ninguno de ellos. No podemos pasar uno de ellos y saltarnos el otro.
Aplicar en Pandas DataFrames
La función 'where' de Numpy no tiene por qué aplicarse necesariamente a las matrices NumPy. Se puede usar 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 las cadenas de una columna contienen una subcadena en particular .
01 02 03 04 05 06 07 08 09 10 11 12 | 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:

Ahora vamos a usar 'np.where' para extraer esas filas del DataFrame 'df' donde la columna 'fruta' tiene la subcadena 'manzana'
1 2 3 | apple_df = df.iloc[np.where(df.fruit.str.contains( "apple" ))] print(apple_df) |
Salida:

Probemos con un ejemplo más en el mismo DataFrame donde extraemos filas para las cuales la columna 'color' no contiene la subcadena 'grito'.
Nota: usamos el signo de tilde (~) para invertir los valores booleanos en Pandas DataFrame o una matriz NumPy.
1 2 3 | non_yellow_fruits = df.iloc[np.where(~df.color.str.contains( "yell" ))] print( "Non Yellow fruits:\n{}" .format(non_yellow_fruits)) |
Salida:

Varias condiciones
Hasta ahora hemos estado evaluando una sola condición booleana en la función 'np.where'. A veces, es posible que necesitemos combinar varias condiciones booleanas utilizando operadores booleanos como ' Y ' u 'O' .
Entendamos esto con un ejemplo.
1 2 3 4 5 | np.random.seed( 42 ) a = np.random.randint( 0 , 15 , ( 5 , 5 )) #5x5 matrix with values from 0 to 14 print(a) |
Salida:

Buscaremos valores que sean menores a 8 y que sean impares. Podemos combinar estas dos condiciones usando el operador AND (&).
1 2 3 4 5 | # 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:

1 2 3 4 5 | # 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:

Anidado donde (donde dentro de donde)
Repasemos el ejemplo de nuestra tabla de 'frutas'.
01 02 03 04 05 06 07 08 09 10 11 12 | 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:

Supongamos ahora que queremos crear una 'bandera' de columna más que tendría el valor 1 si la fruta en esa fila tiene una subcadena 'manzana' o es de color 'amarillo'.
Podemos lograr esto usando llamadas where anidadas, es decir, llamaremos a la función 'np.where' como un parámetro dentro de otra llamada 'np.where'.
1 2 3 4 | 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:

La expresión compleja anterior se puede traducir al inglés simple como:
- Si la columna 'fruta' tiene la subcadena 'manzana', establezca el valor de 'bandera' en 1
- Más:
- Si la columna 'color' tiene la subcadena 'amarilla', establezca el valor de la 'bandera' en 1
- De lo contrario, establezca el valor de la 'bandera' en 0
Tenga en cuenta que podemos lograr el mismo resultado utilizando el operador OR ( | ).
1 2 3 4 5 | #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:

Por lo tanto, el lugar anidado es particularmente útil para datos tabulares como Pandas DataFrames y es un buen equivalente de la cláusula WHERE anidada utilizada en las consultas SQL.
Encontrar filas de ceros
A veces, en una matriz 2D, algunas o todas las filas tienen todos los valores iguales a cero. Por ejemplo, consulte la siguiente matriz NumPy.
1 2 3 4 5 6 7 8 | a = np.array([[ 1 , 2 , 0 ], [ 0 , 9 , 20 ], [ 0 , 0 , 0 ], [ 3 , 3 , 12 ], [ 0 , 0 , 0 ] [ 1 , 0 , 0 ]]) print(a) |
Salida:

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, tendremos que crear una matriz booleana que indique qué filas tienen todos los valores iguales a cero .
Podemos usar la función ' np.any () ' con 'axis = 1', que devuelve True si al menos uno de los valores en una fila es distinto de cero.
El resultado de np.any () será una matriz booleana de longitud igual al número de filas en nuestra matriz NumPy, en la que las posiciones con el valor Verdadero indican que la fila correspondiente tiene al menos un valor distinto de cero.
¡Pero necesitábamos una matriz booleana que fuera todo lo contrario a esto!
Es decir, necesitábamos una matriz booleana donde el valor 'Verdadero' indicaría que cada elemento en esa fila es igual a cero.
Bueno, esto se puede obtener mediante 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 se puede pasar a la función 'np.where'.
Ok, esa fue una explicación larga y agotadora. Veamos esto en acción.
1 2 3 | zero_rows = np.where(~np.any(a, axis= 1 ))[ 0 ] print(zero_rows) |
Salida:

Veamos lo que está sucediendo paso a paso:
- np.any () devuelve Verdadero si al menos un elemento de la matriz es Verdadero (distinto de cero). le indica que haga esta operación por filas.
- Devolvería una matriz booleana de longitud igual al número de filas en a, con el valor Verdadero para las filas que tienen valores distintos de cero y Falso para las filas que tienen todos los valores = 0.Salida:

- El operador tilde (~) invierte la matriz booleana anterior:Salida :

- 'np.where ()' acepta esta matriz booleana y devuelve índices que tienen el valor True.
La indexación [0] se usa porque, como se discutió anteriormente, 'np.where' devuelve una tupla.
Encontrar la última aparición 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 solo la primera aparición o la última aparición del valor para el que se cumple la condición especificada.
Tomemos el ejemplo simple de una matriz unidimensional donde encontraremos la última aparición de un valor divisible por 3.
01 02 03 04 05 06 07 08 09 10 11 | 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:

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 aparición 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 función zip que toma múltiples iterables y devuelve una combinación de valores por pares de cada iterable en el orden dado.
Devuelve un objeto iterador, por lo que necesitamos convertir el objeto devuelto en una lista, tupla o cualquier iterable.
Veamos primero cómo funciona zip:
1 2 3 4 5 6 7 | a = ( 1 , 2 , 3 , 4 ) b = ( 5 , 6 , 7 , 8 ) c = list(zip(a,b)) print(c) |
Salida:

Entonces, el primer elemento de ay el primer elemento de b forman una tupla, luego el segundo elemento de ay 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 aparición de una condición que se cumple en una matriz multidimensional.
Usémoslo para una matriz 2D con la misma condición que vimos en el ejemplo anterior.
01 02 03 04 05 06 07 08 09 10 11 | 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:

Podemos ver en la matriz que la última aparición 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 se utiliza para descomprimir una secuencia de valores en argumentos posicionales separados.
Usando datos de 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 usar la función 'np.where' en datos de fecha y hora.
Por ejemplo, podemos verificar en una lista de valores de fecha y hora, cuáles de las instancias de fecha y hora son anteriores / posteriores a una fecha y hora especificada.
Primero definamos un DataFrame que especifique las fechas de nacimiento de 6 personas.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | import datetime names = [ "John" , "Smith" ,
|