Búsqueda de sitios web

Ejemplos prácticos numpy: técnicas útiles


La biblioteca Numpy es una biblioteca de Python utilizada para la informática científica. Le proporciona un objeto de matriz multidimensional para almacenar y analizar datos de una amplia variedad de maneras. En este tutorial, verá ejemplos de algunas características que Numpy proporciona que no siempre se destacan en otros tutoriales. También tendrá la oportunidad de practicar sus nuevas habilidades con varios ejercicios.

En este tutorial, aprenderá a:

  • Crear matrices multidimensionales a partir de datos almacenados en archivos
  • Identificar y eliminar datos duplicados de una matriz NumPy
  • Utilice matrices numpy estructuradas para conciliar las diferencias entre los conjuntos de datos
  • Analizar y graficar partes específicas de datos jerárquicos
  • Crear versiones vectorizadas de sus propias funciones

Si eres nuevo en Numpy, es una buena idea familiarizarse con los conceptos básicos de la ciencia de datos en Python antes de comenzar. Además, usará matplotlib en este tutorial para crear gráficos. Si bien no es esencial, familiarizarse con Matplotlib de antemano podría ser beneficioso.

Configuración de su entorno de trabajo

Antes de comenzar con este tutorial, deberá realizar una configuración inicial. Además de NumPy, necesitarás instalar la biblioteca Matplotlib, que usarás para representar gráficamente tus datos. También utilizará la biblioteca pathlib de Python para acceder al sistema de archivos de su computadora, pero no es necesario instalar pathlib porque es parte de la biblioteca estándar de Python.

Puede considerar usar un entorno virtual para asegurarse de que la configuración de su tutorial no interfiera con nada en su entorno de Python existente.

Usar un cuaderno de Jupyter dentro de JupyterLab para ejecutar su código en lugar de un Python REPL es otra opción útil. Le permite experimentar y documentar sus hallazgos, así como ver y editar rápidamente archivos. La versión descargable del código y las soluciones de ejercicio se presentan en formato de cuaderno Jupyter.

Los comandos para configurar cosas en las plataformas comunes se muestran a continuación:

El primer fragmento de código debe usarse en Windows y el segundo fragmento de código es para Linux + macOS:

Encienda un Windows PowerShell (Admin) o terminal (admin) indicador, dependiendo de la versión de Windows que esté utilizando. Ahora escriba los siguientes comandos:

PS> python -m venv venv\
PS> venv\Scripts\activate
(venv) PS> python -m pip install numpy matplotlib jupyterlab
(venv) PS> jupyter lab

Aquí crea un entorno virtual llamado venv\, que luego activa. Si la activación se realiza correctamente, el nombre del entorno virtual precederá al mensaje de Powershell. A continuación, instala numpy y matplotlib en este entorno virtual, seguido del jupyterlab opcional. Finalmente, inicia JupyterLab.

Encienda una terminal y escriba los siguientes comandos:

$ python -m venv venv/
$ source venv/bin/activate
(venv) $ python -m pip install numpy matplotlib jupyterlab
(venv) $ jupyter lab

Aquí crea un entorno virtual llamado venv/, que luego activa. Si la activación se realiza correctamente, el nombre del entorno virtual precederá al símbolo del sistema. A continuación, instala numpy y matplotlib en este entorno virtual, seguido del jupyterlab opcional. Finalmente, inicia JupyterLab.

Notarás que tu mensaje está precedido por (venv). Esto significa que todo lo que haga a partir de este momento permanecerá en este entorno y estará separado de otros trabajos de Python que tenga en otros lugares.

Ahora que tiene todo configurado, es hora de comenzar la parte principal de su viaje de aprendizaje.

Ejemplo 1 de NumPy: creación de matrices multidimensionales a partir de archivos

Cuando crea una matriz NumPy, crea una estructura de datos altamente optimizada. Una de las razones de esto es que una matriz NumPy almacena todos sus elementos en un área contigua de memoria. Esta técnica de gestión de memoria significa que los datos se almacenan en la misma región de la memoria, lo que agiliza los tiempos de acceso. Esto es, por supuesto, muy deseable, pero ocurre un problema cuando necesita expandir su matriz.

Suponga que necesita importar múltiples archivos en una matriz multidimensional. Puede leerlos en matrices separadas y luego combinarlas usando np.concatenate() . Sin embargo, esto crearía una copia de su matriz original antes de expandir la copia con los datos adicionales. La copia es necesaria para garantizar que la matriz actualizada aún existirá contiguamente en la memoria ya que la matriz original puede haber tenido contenido no relacionado adyacente.

Copiar las matrices constantemente cada vez que agrega nuevos datos de un archivo puede hacer que el procesamiento sea lento y desplome la memoria de su sistema. El problema empeora a cuanto más datos agregue a su matriz. Aunque este proceso de copia está integrado en Numpy, puede minimizar sus efectos con estos dos pasos:

  1. Al configurar su matriz inicial, determine qué tan grande debe ser antes de poblarla. Incluso puede considerar sobreestimar su tamaño para admitir cualquier adición de datos futuros. Una vez que conozca estos tamaños, puede crear su matriz por adelantado.

  2. El segundo paso es llenarlo con los datos de origen. Estos datos se colocarán en su matriz existente sin ninguna necesidad de que se expanda.

A continuación, explorará cómo completar una matriz NumPy tridimensional.

Llenar matrices con datos de archivos

En este primer ejemplo, utilizará los datos de tres archivos para completar una matriz tridimensional. El contenido de cada archivo se muestra a continuación, y también encontrará estos archivos en los materiales descargables:

El primer archivo tiene dos filas y tres columnas con el siguiente contenido:

1.1, 1.2, 1.3
1.4, 1.5, 1.6

Este segundo archivo, que también tiene las mismas dimensiones, contiene esto:

2.1, 2.2, 2.3
2.4, 2.5, 2.6

El tercer archivo, nuevamente con las mismas dimensiones, almacena estos números:

3.1, 3.2, 3.3
3.4, 3.5, 3.6

Antes de continuar, agregue estos tres archivos a la carpeta de su programa. Los materiales descargables también contienen dos archivos llamados file10.csv y file11.csv , y trabajará con estos más adelante.

El siguiente diagrama muestra la matriz Numpy resultante que creará a partir de los tres archivos:

Como puede ver, file1.csv forma el frente de su matriz, file2.csv la sección central y file3.csv está en la sección central. atrás.

El código utilizado para crear esta matriz se muestra a continuación. Antes de ejecutar este código, asegúrese de crear los tres archivos que se muestran en el diagrama o utilizar las versiones proporcionadas en los materiales descargables. De cualquier manera, colóquelos en el mismo directorio en el que está ejecutando su código y luego ejecútelo:

>>> from pathlib import Path
>>> import numpy as np

>>> array = np.zeros((3, 2, 3))
>>> print(id(array))
2250027286128

>>> for file_count, csv_file in enumerate(Path.cwd().glob("file?.csv")):
...     array[file_count] = np.loadtxt(csv_file.name, delimiter=",")
...
>>> print(id(array))
2250027286128

>>> print(array.shape)
(3, 2, 3)

>>> array
array([[[1.1, 1.2, 1.3],
        [1.4, 1.5, 1.6]],

       [[2.1, 2.2, 2.3],
        [2.4, 2.5, 2.6]],

       [[3.1, 3.2, 3.3],
        [3.4, 3.5, 3.6]]])

Para comenzar, puede revisar cada uno de los archivos y usar la información para determinar la forma final de la matriz esperada. En este ejemplo, los tres archivos tienen la misma cantidad de elementos organizados en dos filas y tres columnas. La matriz resultante tendrá entonces una propiedad shape de (3, 2, 3). Echa otro vistazo al diagrama y podrás ver esto.

Las líneas 1 y 2 muestran que la biblioteca Numpy se importa con su alias estándar de np y también la clase ruta se importa desde la biblioteca pathlib . Esta biblioteca permite a Python acceder al sistema de archivos de su computadora utilizando un enfoque orientado a objetos. Los objetos de la clase ruta le permiten especificar una ruta de archivo y también contener métodos para que pueda realizar llamadas del sistema a su sistema operativo. Esta característica se usa más adelante en el código.

Anteriormente aprendiste que es una buena idea crear una matriz por adelantado antes de comenzar a llenarla con datos para reducir el uso de memoria. En la línea 4, crea una matriz que contiene ceros con la forma (3, 2, 3) como determinó anteriormente.

A continuación, llena su matriz con los datos de los archivos. Usted crea un para bucle usando la función integrada de Python enumerate que se muestra en las líneas 8 y 9. Su parámetro le permite recorrer un conjunto de archivos y devolver una referencia a cada uno uno. También mantiene un recuento de cuántos archivos se han encontrado. Cada referencia de archivo se almacena en la variable csv_file , mientras que el contador de incremento se almacena en la variable file_count .

Para acceder a cada uno de los tres archivos .csv a su vez, usa ruta . Al llamar a path.cwd() , le está diciendo a Python que busque en su directorio de trabajo actual para los archivos. En otras palabras, el directorio desde donde está ejecutando el programa. Esto devuelve un objeto ruta que representa el directorio actual, desde el cual llama a su .Glob() método para especificar los nombres de los archivos a los que desea acceder.

En este caso, como desea acceder a archivos llamados file1.csv, file2.csv y file3.csv, pasa sus nombres como la cadena file ?.csv. Esto le indica a .glob() que seleccione sólo archivos cuyos nombres deben coincidir exactamente con estos caracteres, pero cuyo quinto carácter puede ser cualquier carácter, según lo especificado por el carácter comodín (>).

Lamentablemente, es posible que .glob() no devuelva los archivos en el orden esperado. En este ejemplo, todo funciona como se esperaba porque el nombre de cada archivo contiene un solo dígito como quinto carácter. Si hubiera habido un archivo llamado file11.csv, entonces se habría leído en el orden incorrecto. Aprenderá más sobre por qué ocurre esto y cómo solucionarlo más adelante.

>>> for csv_file in sorted(Path.cwd().glob("file?.csv")):
...     print(csv_file.name)
...
file1.csv
file2.csv
file3.csv

Como verá, este tipo de clasificación no siempre es suficiente cuando se trabaja con archivos numerados.

Cada vez que el bucle bucea, llama a la función np.loadtxt() y pasa un nombre de archivo, que se especifica utilizando su propiedad name . También le dice que use una coma (, ) como un delimitador de campo para permitirle que separe cada número individual en el archivo. Luego se asigna el contenido de cada archivo a la matriz que creó anteriormente.

Para asegurarse de que el contenido de cada archivo se inserta en la posición correcta a lo largo del eje 0 , utiliza array [file_count] . La primera vez que se ejecuta el bucle, el contenido de file1.csv se asignará a array [0] , o posición 0 a lo largo del eje-0. La siguiente iteración en el bucle asignará file2.csv a array [1] a lo largo de este eje, antes de file3.csv se asigna a Array [2] . Mire una vez más el diagrama y verá exactamente lo que sucedió.

En las líneas 5 y 11, imprimió el resultado de ID (Array) . La función id() devuelve la identidad de un objeto. Cada objeto tiene un valor de identidad único porque cada objeto ocupa un lugar único en la memoria de la computadora. Cuando ejecuta el código en su computadora, los números también serán idénticos entre sí, pero probablemente serán diferentes de los mostrados.

En las líneas 6 y 12, los valores de identidad que se muestran demuestran que el objeto Array que comenzó como conteniendo solo ceros, es el mismo objeto de matriz que luego contenía el contenido de cada archivo. Esto muestra que solo se usó un objeto en todo momento y que la memoria se usó de manera eficiente.

Cuando crea matrices de esta manera, es una buena idea asegurarse de que cada uno de los archivos de entrada tenga la misma cantidad de filas y columnas de elementos. A continuación, verá cómo lidiar con situaciones en las que sus archivos de datos no son tan uniformes.

Tratar con diferentes tamaños de datos

Para empezar, agregará algunos datos de tamaño insuficiente. Lo encontrarás en un archivo llamado short_file.csv, que solo tiene una fila:

4.1, 4.2, 4.3

Desea agregarlo a la parte posterior de su matriz como se muestra a continuación:

Antes de ejecutar el código utilizado para crear esta segunda matriz, asegúrese de descargar o agregar el archivo llamado short_file.csv en el mismo directorio que ejecuta su código:

>>> array = np.zeros((4, 2, 3))

>>> for file_count, csv_file in enumerate(Path.cwd().glob("file?.csv")):
...     array[file_count] = np.loadtxt(csv_file.name, delimiter=",")
...
>>> array[3, 0] = np.loadtxt("short_file.csv", delimiter=",")

>>> array
array([[[1.1, 1.2, 1.3],
        [1.4, 1.5, 1.6]],

       [[2.1, 2.2, 2.3],
        [2.4, 2.5, 2.6]],

       [[3.1, 3.2, 3.3],
        [3.4, 3.5, 3.6]],

       [[4.1, 4.2, 4.3],
        [0. , 0. , 0. ]]])

Esta vez, está leyendo en cuatro archivos separados, por lo que la matriz que crea inicialmente tendrá una forma de (4, 2, 3) . Necesitará una dimensión adicional a lo largo de Axis-0 para acomodar el cuarto archivo, por lo que lo crea en la línea 1.

El bucle for se utiliza para leer los primeros tres archivos como antes. Para leer el archivo corto, debe decirle a Python exactamente dónde desea colocarlo en su matriz. Anteriormente, esto se hacía indicando una posición, como array[2], para insertar los datos en la posición 2 a lo largo del eje 0. Este enfoque funcionó porque los datos que insertó llenaron la matriz existente que ya estaba en esa posición. Sin embargo, esta vez las cosas son diferentes.

Para decirle a Python que desea insertar el archivo corto comenzando en la parte superior de la posición de índice 3 en el eje 2, use array[3, 0]. El 3 representa la posición del eje 2 como antes, pero también debe proporcionar un 0 para indicar que los datos deben insertarse comenzando en la fila 0. Puede echar un vistazo en las líneas 18 y 19 de la salida del programa y luego en el diagrama para ver dónde se colocaron los datos.

Como antes, el objeto de matriz creado al comienzo del código es el único que se utiliza en todo momento. Aunque su código le agregó datos varias veces, no fue necesaria una copia ineficiente porque creó la matriz por adelantado.

Supongamos que en lugar de ser demasiado corto, el cuarto archivo fuera demasiado largo. Quizás se pregunte cómo tratar estos archivos, que pueden resultar problemáticos. Esta vez, usarás un archivo llamado long_file.csv, que tiene una fila adicional:

4.1, 4.2, 4.3
4.4, 4.5, 4.6
4.7, 4.8, 4.9

Ahora desea incorporarlo a su matriz en la posición que se muestra a continuación. Como puede ver en el diagrama, será necesario ampliar el resto de la matriz con una fila adicional para acomodarla:

El código utilizado para crear esta tercera matriz se muestra a continuación. Antes de ejecutarlo, asegúrese de descargar o crear el archivo llamado long_file.csv en el mismo directorio donde está ejecutando su código:

>>> array = np.zeros((4, 2, 3))

>>> print(id(array))
2250027278352

>>> for file_count, csv_file in enumerate(Path.cwd().glob("file?.csv")):
...     array[file_count] = np.loadtxt(csv_file.name, delimiter=",")
...
>>> array = np.insert(arr=array, obj=2, values=0, axis=1)

>>> array[3] = np.loadtxt("long_file.csv", delimiter=",")

>>> print(id(array))
2250027286224

>>> array
array([[[1.1, 1.2, 1.3],
        [1.4, 1.5, 1.6],
        [0. , 0. , 0. ]],

       [[2.1, 2.2, 2.3],
        [2.4, 2.5, 2.6],
        [0. , 0. , 0. ]],

       [[3.1, 3.2, 3.3],
        [3.4, 3.5, 3.6],
        [0. , 0. , 0. ]],

       [[4.1, 4.2, 4.3],
        [4.4, 4.5, 4.6],
        [4.7, 4.8, 4.9]]])

Esta vez, debido a que su matriz original es demasiado corta para acomodar el contenido de long_file.csv, necesita alargarla con una fila adicional a lo largo del eje 1. Luego puede agregar el contenido de long_file.csv. Esto se hace en la línea 9 usando la función np.insert(), que es una función que le permite insertar valores a lo largo de un eje.

Pasas cuatro parámetros a np.insert(). El parámetro arr especifica la matriz en la que desea insertar valores, mientras establece obj en 2, axis en 1, y el parámetro values a 0, le permite insertar valores 0 en la posición de índice 2 a lo largo del eje 1. En otras palabras, a lo largo de la fila inferior de la matriz como se muestra en el diagrama. Finalmente, para agregar el contenido de long_file.csv a su matriz, use una vez más loadtxt() como se muestra en la línea 11.

Tómese un momento para mirar el diagrama y la matriz resultante producida por su código y verá que todo funciona como se esperaba.

Observe que las líneas 4 y 14 indican que los objetos de la matriz antes y después de insertar los nuevos datos son diferentes. Esto ocurre porque la función insert() devuelve una copia de la matriz original. Para evitar este desperdicio de memoria, realmente es una buena idea asegurarse de dimensionar correctamente su matriz inicial antes de comenzar a llenarla.

Asegurarse de que el orden de los archivos sea correcto

Cuando ejecuta el código path.cwd(). Glob ("File? o Posixpath objetos. Cada uno de estos representa una ruta del archivo del sistema operativo y un nombre de archivo que coincide con el patrón archivo. Sin embargo, el orden en el que el generador devuelve estos archivos no es lo que podría esperar.

Para ver un ejemplo de esto, agregue los dos archivos denominados file10.csv y file11.csv de los materiales descargables a su carpeta existente:

10.1,10.2,10.3
10.4,10.5,10.6

Probablemente ya hayas adivinado lo que había en file10.csv. Si es así, no se sorprenderá cuando vea lo que hay en file11.csv:

11.1,11.2,11.3
11.4,11.5,11.6

Ahora ejecute el siguiente código:

>>> for csv_file in Path.cwd().glob("file*.csv"):
...     print(csv_file.name)
...
file1.csv
file10.csv
file11.csv
file2.csv
file3.csv

Para asegurarse de que cada uno de estos archivos adicionales sea visto por el generador, debe ajustar los criterios de coincidencia a archivo*.csv . El (*) Wildcard Caracter representa cualquier número de caracteres desconocidos. Si hubiera conservado el carácter comodín (? ), entonces solo se habrían incluido los archivos que contienen un carácter después del archivo String . Incluso con esta adición, algo todavía parece mal.

Verá que los nuevos archivos se colocan entre file1.csv y file2.csv y no, como probablemente esperaba, al final. La razón de esto es que los nombres de archivo se están clasificando alfanuméricamente. Esto significa que el tipo lee los nombres de archivo de izquierda a derecha y considera que todo es igual hasta que encuentre una diferencia. Una vez que tiene esto, el tipo se basa en esa diferencia.

Por ejemplo, cuando se analizan los caracteres en cada nombre de archivo, todo se considera igual en los primeros cuatro caracteres del nombre de cada archivo; en este caso, archivo . Python tiene que decidir cuál de los quintos personajes es lo primero. Hace esto considerando los números de código de caracteres Unicode de cada uno de ellos.

El código de caracteres Unicode para el carácter 1 es 49, mientras que los códigos para 2 y 3 son 50 y 51, respectivamente. Como resultado, cualquier nombre de archivo con 1 Como su quinto carácter se ordenará antes que aquellos con 2 o 3 en la misma posición.

En el caso de file1.csv, file10.csv y file11.csv, el quinto carácter de cada nombre de archivo es el mismo. Por lo tanto, el orden de clasificación lo decidirá su sexto carácter. Al considerar sus valores de caracteres Unicode, el punto (.) tiene un valor de 46, que viene antes de los caracteres 0 y 1, cuyos valores son 48 y 49, respectivamente. . En consecuencia, el orden de clasificación será file1.csv, seguido de file10.csv y luego file11.csv.

Es posible que se sienta tentado a dar la función de Python incorporada sorted() una intento para ver si eso ayuda:

>>> for csv_file in sorted(Path.cwd().glob("file*.csv")):
...     print(csv_file.name)
...
file1.csv
file10.csv
file11.csv
file2.csv
file3.csv

Sin embargo, la función sorted() le ha dado el mismo resultado indeseable que antes.

Para leer archivos en un orden más natural, puede utilizar la biblioteca natsort. Primero, instálelo ejecutando el comando python -m pip install natsort. Después de la instalación, puede importar la función natsorted() y usarla en lugar de la función sorted() incorporada para lograr una clasificación más natural de los archivos. A continuación se muestra el código que ilustra esto:

>>> from natsort import natsorted

>>> for csv_file in natsorted(Path.cwd().glob("file*.csv")):
...     print(csv_file.name)
...
file1.csv
file2.csv
file3.csv
file10.csv
file11.csv

Por fin, has logrado solucionar el problema de clasificación de nombres de archivos. Ahora puede llevar su código anterior un paso más allá y agregar el contenido del archivo en los lugares correctos de su matriz:

>>> array = np.zeros((5, 2, 3))

>>> for file_count, csv_file in enumerate(
...     natsorted(Path.cwd().glob("file*.csv"))
... ):
...     array[file_count] = np.loadtxt(csv_file.name, delimiter=",")
...
>>> array
array([[[ 1.1,  1.2,  1.3],
        [ 1.4,  1.5,  1.6]],

       [[ 2.1,  2.2,  2.3],
        [ 2.4,  2.5,  2.6]],

       [[ 3.1,  3.2,  3.3],
        [ 3.4,  3.5,  3.6]],

       [[10.1, 10.2, 10.3],
        [10.4, 10.5, 10.6]],

       [[11.1, 11.2, 11.3],
        [11.4, 11.5, 11.6]]])

Esta vez pasas las distintas rutas de archivos a natsorted(), que los ordenará de la manera que probablemente pretendías. El resultado muestra que el contenido de file10.csv y file11.csv ahora está en sus lugares correctos dentro de las matrices. Nuevamente, observe cómo se usó aquí el operador comodín (*). Además, las dimensiones de esta versión de array se han aumentado a (5, 2, 3) para proporcionar espacio para los nuevos datos.

Como puede ver, es perfectamente posible construir matrices NumPy a partir de datos en archivos. Sin embargo, es necesario asegurarse de que se tengan en cuenta los diferentes tamaños.

Antes de continuar, completará un ejercicio para evaluar su comprensión. Este es el primero de varios ejercicios incluidos en este tutorial para ayudarte a consolidar tu aprendizaje.

Prueba de sus habilidades: poblaciones de matrices con datos de archivos

Es hora de que pruebes tus conocimientos sobre la creación de matrices a partir de datos de archivos. Vea si puede resolver su primer desafío que se muestra a continuación:

Encuentre ex1a.csv , ex1b.csv y ex1c.csv archivos en los materiales descargables. Ahora, aplique las técnicas que ha aprendido para crear una matriz tridimensional de tamaño correcto que permitirá agregar cada archivo. El contenido de cada archivo debe insertarse para que toque la esquina superior izquierda de un índice separado a lo largo del eje 0. Debe insertar el contenido del archivo ex1b.csv como una fila, y el contenido de ex1c.csv como columna.

Una posible solución es:

>>> import numpy as np
>>> from pathlib import Path

>>> array = np.zeros((3, 4, 4), dtype="i8")

>>> array[0] = np.loadtxt("ex1a.csv", delimiter=",")

>>> narrow_array = np.loadtxt("ex1b.csv", delimiter=",")
>>> narrow_array = np.insert(arr=narrow_array, values=0, obj=3, axis=0)
>>> array[1, 0] = narrow_array

>>> short_array = np.loadtxt("ex1c.csv", delimiter=",")
>>> short_array = np.insert(arr=short_array, values=0, obj=3, axis=0)
>>> array[2, :, 0] = short_array

>>> array
array([[[ 5, 10, 15, 20],
        [25, 30, 35, 40],
        [45, 50, 55, 60],
        [65, 70, 75, 80]],

       [[ 1,  3,  5,  0],
        [ 0,  0,  0,  0],
        [ 0,  0,  0,  0],
        [ 0,  0,  0,  0]],

       [[ 2,  0,  0,  0],
        [ 4,  0,  0,  0],
        [ 6,  0,  0,  0],
        [ 0,  0,  0,  0]]])

Primero, mira a través de los archivos y vea que es necesaria una matriz con el tamaño (3, 4, 4) para garantizar que sus datos se ajusten. Luego crea una variedad de ceros con este tamaño y especifica que almacenará enteros. A continuación, lo asigna a la variable array antes de poblar array [0] , o eje-0, con el contenido del archivo ex1a.csv , que se ajusta a la forma cómodamente.

El archivo ex1b.csv tiene solo tres valores, lo que significa que es demasiado corto para ser insertado directamente en array [1] . Para solucionar esto, primero lo lee en estrecho_array y luego usa np.insert() para agregar un extra 0 en su posición de índice 3, Según lo definido por sus parámetros Valores y obj . Finalmente, inserta estrechray_array en la fila superior de array en índice 1 a lo largo de su eje 0 usando array [1, 0] .

El archivo ex1c.csv también tiene solo tres valores. Esto significa que debe ajustarlo para que se ajuste a la columna, por lo que agregará un cero adicional al final. Esta vez, para agregar short_array , usa array [2 ,:, 0] . Se insertará en la posición del índice 2 a lo largo del eje 0, pero :, 0 significa que se insertará hacia abajo el primero columna y no en una fila.

En lugar de extender las matrices que lee, alternativamente puede ser más específico sobre dónde coloca los valores. Por ejemplo, puede insertar short_array en array [2, 0: 3, 0] directamente antes de insertar el extra 0 .

En la siguiente sección, aprenderá un poco sobre los arreglos estructurados y cómo se pueden usar para conciliar las diferencias entre diferentes arreglos.

Numpy Ejemplo 2: Conciliar datos utilizando matrices estructuradas

Con las matrices numpy que creó en la sección anterior, no había forma de conocer el significado de los datos de cada columna. ¿No sería bueno si pudiera hacer referencia a columnas específicas con nombres significativos en lugar de números de índice? Entonces, por ejemplo, en lugar de usar student_grades=resultados [:, 1] , podría usar student_grades=resultados ["examen_grade"] . ¡Albricias! Puede hacerlo creando una matriz estructurada.

Crear una matriz estructurada

Una matriz estructurada es una matriz numpy con un tipo de datos compuesto por un conjunto de tuplas, cada una que contiene un nombre de campo y un tipo de datos regular. Una vez que haya definido estos, puede acceder y modificar cada campo individual usando su nombre de campo.

El siguiente código proporciona un ejemplo de cómo crear y hacer referencia a una matriz Numpy estructurada:

>>> import numpy as np

>>> race_results = np.array(
...     [
...         ("At The Back", 1.2, 3),
...         ("Fast Eddie", 1.3, 1),
...         ("Almost There", 1.1, 2),
...     ],
...     dtype=[
...         ("horse_name", "U12"),
...         ("price", "f4"),
...         ("position", "i4"),
...     ],
... )

>>> race_results["horse_name"]
array(['At The Back', 'Fast Eddie', 'Almost There'], dtype='<U12')

>>> np.sort(race_results, order="position")[
...     ["horse_name", "price"]
... ]
array([('Fast Eddie', 1.3), ('Almost There', 1.1), ('At The Back', 1.2)],
      dtype={'names': ['horse_name', 'price'],
      ⮑ 'formats': ['<U12', '<f4'],
      ⮑ 'offsets': [0, 48], 'itemsize': 56})

>>> race_results[race_results["position"] == 1]["horse_name"]
array(['Fast Eddie'], dtype='<U12')

La matriz estructurada se define en las líneas 3 a 14, pero comience mirando las líneas 4 a 8, que definen lo que parece una matriz NumPy normal. Consta de tres filas y tres columnas de datos relacionados con una carrera de caballos. Puedes distinguir fácilmente los nombres de los caballos a partir de los datos, pero es posible que te cueste un poco descubrir qué significan los otros dos números.

Esta matriz es en realidad una matriz estructurada debido a la definición de sus tipos de datos en la línea 9. Cada tipo de datos consiste en un nombre de campo y un tipo de datos asociado. Los tres campos son Horse_name , precio y posición . Sus tipos de datos asociados se definen utilizando códigos de protocolo de interfaz de matriz. U12 Define una cadena de 12 caracteres, mientras que f4 y i4 especifica 4 --byte Floating-Point y formats enteros , respectivamente.

Una vez que haya configurado su matriz estructurada, puede usar estos nombres de campos para hacer referencia a columnas. En la línea 16, usaste "horse_name" para ver una serie de nombres de caballos de carreras. Para encontrar el orden de finalización, pasó el campo "position" a la función np.sort() en la línea 19. Esto clasificó los corredores en su orden de finalización. Luego filtró la salida para mostrar solo horse_name y price. Finalmente, en la línea 27, seleccionaste el nombre del caballo ganador.

Conciliar diferentes matrices

Incluir nombres de campos en sus matrices NumPy tiene muchos propósitos útiles. Suponga que desea hacer coincidir registros haciendo coincidir los nombres de los campos en matrices NumPy separadas. Para hacer esto, junte sus matrices para que solo se muestren los registros coincidentes de cada matriz. Esta idea le resultará familiar si alguna vez ha realizado una unión interna SQL entre dos tablas de bases de datos relacionales. El término inner se utiliza para definir la combinación utilizada aquí.

En esta sección, trabajará con dos archivos nuevos: issued_checks.csv y cashed_checks.csv.

El archivo emuded_checks.csv contiene cuatro campos: check_id , payee , monto y date_issueed . Este archivo simula un conjunto de cheques emitidos por su empresa a sus acreedores:

Check_ID,Payee,Amount,Date_Issued
1341,K Starmer,150.00,2024-03-29
1342,R Sunak,175.00,2024-03-29
1343,L Truss,30.00,2024-03-29
1344,B Johnson,45.00,2024-03-22
1345,T May,65.00,2024-03-22
1346,D Cameron,430.00,2024-03-22
1347,G Brown,100.00,2024-03-15
1348,T Blair,250.00,2024-03-15
1349,J Major,500.00,2024-03-15
1350,M Thatcher,220.00,2024-03-15

El archivo Cashed_checks.csv contiene solo tres campos: check_id , cantidad y date_cashed . Este archivo simula un conjunto de cheques cobrados por los acreedores de su negocio:

Check_ID,Amount,Date_Cashed
1341,150.00,2024-04-12
1342,175.00,2024-04-16
1343,30.00,2024-04-12
1345,65.00,2024-04-12
1346,430.00,2024-04-08
1349,500.00,2024-04-08
1350,220.00,2024-04-15

Supongamos que desea ver el beneficiario, date_issued y date_cashed de los cheques cobrados. Si observa detenidamente, verá que los detalles de Payee y Date_Issued no están incluidos en cashed_checks.csv, mientras que los otros dos los campos son. Para ver todos los datos que necesita, deberá unir ambos archivos.

Agregue los archivos issued_checks.csv y cashed_checks.csv a la carpeta de su programa, luego ejecute este código:

>>> import numpy.lib.recfunctions as rfn
>>> from pathlib import Path

>>> issued_dtypes = [
...     ("id", "i8"),
...     ("payee", "U10"),
...     ("amount", "f8"),
...     ("date_issued", "U10"),
... ]

>>> cashed_dtypes = [
...     ("id", "i8"),
...     ("amount", "f8"),
...     ("date_cashed", "U10"),
... ]

>>> issued_checks = np.loadtxt(
...     Path("issued_checks.csv"),
...     delimiter=",",
...     dtype=issued_dtypes,
...     skiprows=1,
... )

>>> cashed_checks = np.loadtxt(
...     Path("cashed_checks.csv"),
...     delimiter=",",
...     dtype=cashed_dtypes,
...     skiprows=1,
... )

>>> cashed_check_details = rfn.rec_join(
...     "id",
...     issued_checks,
...     cashed_checks,
...     jointype="inner",
... )

>>> cashed_check_details[
...     ["payee", "date_issued", "date_cashed"]
... ]
array([('K Starmer', '2024-03-29', '2024-04-12'),
       ('R Sunak', '2024-03-29', '2024-04-16'),
       ('L Truss', '2024-03-29', '2024-04-12'),
       ('T May', '2024-03-22', '2024-04-12'),
       ('D Cameron', '2024-03-22', '2024-04-08'),
       ('J Major', '2024-03-15', '2024-04-08'),
       ('M Thatcher', '2024-03-15', '2024-04-15')],
      dtype={'names': ['payee', 'date_issued', 'date_cashed'],
      ⮑'formats': ['<U10', '<U10', '<U10'],
      ⮑'offsets': [8, 64, 104], 'itemsize': 144})

Para unirse a dos matrices numpy, usa una de varias funciones de Helper de Recarrray, que le permiten trabajar con matrices estructuradas. Para acceder a estos, primero debe importar el módulo de biblioteca numpy.lib.recfunctions . Una vez más, usará la biblioteca PathLib para acceder a los archivos. Se importan en las líneas 1 y 2.

En las líneas 4 a 15, crea dos listas Python de tuplas que definen los tipos de datos que se utilizarán tanto para el archivo issued_checks.csv como para el archivo cashed_checks.csv. Estos se introducen en el parámetro dtype de np.loadtext() para permitirle leer correctamente los archivos en las matrices estructuradas de NumPy.

Los archivos reales son leídos por su código en las líneas 17 a 29. Utiliza np.loadtext() como lo hizo antes, pero esta vez establece su parámetro skiprows en < código>1. Esto garantizará que se ignore la primera fila de cada archivo porque cada uno de ellos contiene información de encabezado y no datos.

Las dos matrices NumPy leídas de los archivos luego se unen en las líneas 31 a 36 usando la función auxiliar .rec_join(). Esta función utiliza cuatro parámetros. El primer parámetro define el campo con el que se unirán los dos arrays. En este caso, desea unir las matrices según sus campos id, que contienen números únicos que identifican cada verificación en ambos archivos.

A continuación, pasa los nombres de las matrices para unirse como el segundo y tercer parámetros.

El parámetro final es el más interesante. Al especificar conjunta="Inner" , realiza una unión interna. Esto significa que la matriz resultante contendrá solo registros coincidentes de ambos archivos, proporcionando detalles completos de todos los cheques cobrados. Cualquier registro cuyo id aparezca en un archivo, pero no en el otro no aparecerá en la salida.

Para confirmar que la unión ha funcionado, mire la salida producida por las líneas 38 a 40. El Cashed_check_Details Array contiene el beneficiario , date_issued , y date_cashed datos. Los primeros dos de estos campos provienen del archivo emerged_checks.csv , mientras que este último vino de Cashed_checks.csv . Como puede ver, los registros se han combinado correctamente.

Tratar con nombres de campos duplicados

Ahora suponga que también desea ver el monto del cheque. Es posible que tenga la tentación de probar esto:

>>> cashed_check_details[
...     [
...         "payee",
...         "date_issued",
...         "date_cashed",
...         "amount",
...     ]
... ]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'amount'

El KeyError ocurrió porque el campo cantidad ya no existe en la matriz unida. Esto sucedió porque ambos archivos de datos originales contenían un campo Cantidad . No es posible tener una matriz estructurada que contenga dos campos con la misma etiqueta. La operación de unión los renombró de manera única.

Para saber cómo se llaman ahora, primero vea los tipos de datos de la matriz unida. Se accede a ellos a través de la propiedad .dtype:

>>> cashed_check_details.dtype
dtype([('id', '<i8'), ('payee', '<U10'), ('amount1', '<f8'), ('amount2', '<f8'),
⮑ ('date_issued', '<U10'), ('date_cashed', '<U10')])

Si observa cuidadosamente la salida, notará que los campos originales cantidad han sido renombrados montre1 y montre2 . Para ver su contenido, debe usar estos nombres. En este escenario, ambos campos contienen los mismos datos, por lo que no importa cuál de ellos elija:

>>> cashed_check_details[
...     [
...         "payee",
...         "date_issued",
...         "date_cashed",
...         "amount1",
...     ]
... ]
array([('K Starmer', '2024-03-29', '2024-04-12', 150.),
       ('R Sunak', '2024-03-29', '2024-04-16', 175.),
       ('L Truss', '2024-03-29', '2024-04-12',  30.),
       ('T May', '2024-03-22', '2024-04-12',  65.),
       ('D Cameron', '2024-03-22', '2024-04-08', 430.),
       ('J Major', '2024-03-15', '2024-04-08', 500.),
       ('M Thatcher', '2024-03-15', '2024-04-15', 220.)],
      dtype={'names': ['payee', 'date_issued', 'date_cashed', 'amount1'],
      ⮑ 'formats': ['<U10', '<U10', '<U10', '<f8'],
      ⮑'offsets': [8, 64, 104, 48], 'itemsize': 144})

Esta vez, los montos de cheque están incluidos en su salida.

Ahora que sabe qué cheques ha emitido y qué ha sido cobrado, es posible que desee ver una lista de cheques que actualmente no están en juego:

>>> outstanding_checks = [
...     check_id
...     for check_id in issued_checks["id"]
...     if check_id not in cashed_checks["id"]
... ]

>>> outstanding_checks
[np.int64(1344), np.int64(1347), np.int64(1348)]

>>> [int(check_id) for check_id in outstanding_checks]
[1344, 1347, 1348]

En la línea 1, crea una lista denominada outstanding_checks usando una lista por comprensión. Esto contendrá una lista de cheques con un valor id que está en la matriz issued_checks pero no en la matriz cashed_checks. Estos todavía están pendientes.

Nuevamente, al usar el nombre del campo, puede especificar rápidamente los campos que se usarán para la comparación y mantener su código altamente legible.

Para mayor comodidad, extrae los números de su matriz resultante utilizando una segunda comprensión de la lista en la línea 10.

Para completar, es posible que desee averiguar si ha habido cheques cobrados de manera fraudulenta en su cuenta que no ha emitido:

>>> [
...     check_id
...     for check_id in cashed_checks["id"]
...     if check_id not in issued_checks["id"]
... ]
[]

La lista vacía producida muestra que, afortunadamente, no hay ninguno. ¡Uf!

Lidiar con valores clave duplicados

Antes de unir matrices NumPy, es importante asegurarse de que no haya valores clave duplicados. Estos pueden provocar resultados indeseables porque crean relaciones de uno a muchos. En los casos en los que sus claves duplicadas sean válidas, por ejemplo, cuando necesite realizar una unión de uno a muchos, una mejor alternativa es utilizar la función merge() en pandas.

El siguiente código utiliza el archivo check_list_dupplicates.csv . Este archivo tiene la misma estructura que el archivo emuded_checks.csv que usó anteriormente, pero hay un registro duplicado:

>>> from pathlib import Path
>>> import numpy.lib.recfunctions as rfn

>>> issued_dtypes = [
...     ("id", "i8"),
...     ("payee", "U10"),
...     ("amount", "f8"),
...     ("date_issued", "U10"),
... ]

>>> issued_checks = np.loadtxt(
...     Path("check_list_duplicates.csv"),
...     delimiter=",",
...     dtype=issued_dtypes,
...     skiprows=1,
... )

>>> rfn.find_duplicates(np.ma.asarray(issued_checks))
masked_array(data=[(1344, 'B Johnson', 45.0, '2024-03-22'),
                   (1344, 'B Johnson', 45.0, '2024-03-22')],
             mask=[(False, False, False, False),
                   (False, False, False, False)],
       fill_value=(999999, 'N/A', 1e+20, 'N/A'),
            dtype=[('id', '<i8'), ('payee', '<U10'),
            ⮑ ('amount', '<f8'), ('date_issued', '<U10')])

Para encontrar filas duplicadas en una matriz estructurada, puede usar la función .find_dupplicates() auxiliar. Esta función requiere que lo pase una matriz enmascarada, que es una matriz que puede tener entradas faltantes o no válidas. Aunque su matriz está bien a este respecto, debe convertirla en una matriz enmascarada antes de pasarla a la función. Puede ver en la línea 19 de la salida que hay una fila duplicada: el registro 1344 ocurre dos veces.

Para deshacerse de él, puede utilizar la función np.unique():

>>> issued_checks = np.unique(issued_checks, axis=0)
>>> issued_checks
array([(1341, 'K Starmer', 150., '2024-03-29'),
       (1342, 'R Sunak', 175., '2024-03-29'),
       (1343, 'L Truss',  30., '2024-03-29'),
       (1344, 'B Johnson',  45., '2024-03-22'),
       (1345, 'T May',  65., '2024-03-22'),
       (1346, 'D Cameron', 430., '2024-03-22'),
       (1347, 'G Brown', 100., '2024-03-15'),
       (1348, 'T Blair', 250., '2024-03-15'),
       (1349, 'J Major', 500., '2024-03-15'),
       (1350, 'M Thatcher', 220., '2024-03-15')],
      dtype=[('id', '<i8'), ('payee', '<U10'),
      ⮑ ('amount', '<f8'), ('date_issued', '<U10')])

Para eliminar las filas duplicadas, pasa su matriz emerged_checks , junto con axis=0 a np.unique() para decirle que elimine las filas. Esto crea una matriz nueva y se eliminan cualquier fila duplicada, dejando solo una instancia. Si observa la salida, verá que la fila 1344 ahora aparece solo una vez.

Prueba de sus habilidades: unir matrices juntos

Es hora de tu próximo desafío. A ver si puedes resolver este ejercicio:

Una aerolínea contiene datos en sus pasajeros en dos archivos separados: passengers.csv y pasapts.csv archivo. Eche un vistazo a ambos archivos, que encontrará en los materiales descargables, para familiarizarse con su contenido. Ahora, use sus habilidades para resolver las siguientes tareas:

Tarea 1: Produzca una matriz estructurada que contenga el nombre, apellido y nacionalidad de cada pasajero.

Tarea 2: Determine si hay pasajeros en la lista que no tienen pasaportes.

Tarea 3: Determinar si hay algún pasaporte en la lista que no pertenezca a ningún pasajero.

Una posible solución a la Tarea 1 es:

>>> import numpy as np
>>> import numpy.lib.recfunctions as rfn

>>> passenger_dtype = [
...     ("passenger_no", "i8"),
...     ("first_name", "U20"),
...     ("last_name", "U20"),
... ]

>>> passport_dtype = [
...     ("passport_no", "i8"),
...     ("passenger_no", "i8"),
...     ("nationality", "U20"),
... ]

>>> passengers = np.unique(
...     np.loadtxt(
...         "passengers.csv",
...         delimiter=",",
...         dtype=passenger_dtype,
...         skiprows=1,
...     ),
...     axis=0,
... )

>>> passports = np.unique(
...     np.loadtxt(
...         "passports.csv",
...         delimiter=",",
...         dtype=passport_dtype,
...         skiprows=1,
...     ),
...     axis=0,
... )

>>> flight_details = rfn.rec_join(
...     "passenger_no",
...     passengers,
...     passports,
...     jointype="inner",
... )

>>> flight_details[
...     ["first_name", "last_name", "nationality"]
... ]
rec.array([('Olivia', 'Smith', 'British'), ('Amelia', 'Jones', 'British'),
           ('Isla', 'Williams', 'American'),
           ('Ava', 'Taylor', 'American'), ('Ivy', 'Brown', 'Austrian'),
           ('Freya', 'Davies', 'Norwegian'), ('Lily', 'Evans', 'French'),
           ('Florence', 'Wilson', 'German'), ('Mia', 'Thomas', 'Danish'),
           ('Willow', 'Johnson', 'Dutch'), ('Noah', 'Roberts', 'Dutch'),
           ('Oliver', 'Robinson', 'French'),
           ('George', 'Thompson', 'Danish'),
           ('Muhammad', 'Walker', 'Dutch'), ('Leo', 'White', 'British'),
           ('Harry', 'Edwards', 'American'),
           ('Oscar', 'Hughes', 'Spanish'),
           ('Archie', 'Green', 'Norwegian'), ('Henry', 'Hall', 'British')],
          dtype={'names': ['first_name', 'last_name', 'nationality'],
          ⮑ 'formats': ['<U20', '<U20', '<U20'],
          ⮑ 'offsets': [8, 88, 176], 'itemsize': 256})

Comienza creando dos listas, passenger_dtype y pasapport_dtype , como se muestra en las líneas 4 y 10. Estas tiendas se utilizarán para definir los tipos de datos de cada columna En las matrices estructuradas que contendrán los datos del archivo. Cada tupla consiste en un nombre de campo y un tipo de datos. Puede recordar que i8 especifica un entero, mientras que u20 define una cadena de veinte caracteres.

En las líneas 16 y 26, lee los datos de cada archivo en las matrices pasajeros y pasaportes , respectivamente. También usa np.unique() para garantizar que se eliminen los registros duplicados. Si olvida hacer esto, verá algunos valores n/a aparecen en sus resultados. Observe que está omitiendo la línea superior de cada archivo porque contiene encabezados.

En la línea 36, se une a sus dos cruelas pasajeros y pasaportes matrices usando sus campos passenger_no como la clave de unión. Esta es una unión interna, lo que significa que solo los pasajeros con pasaportes están incluidos en la salida.

En la línea 43, muestra los detalles del vuelo requeridos. El resultado muestra los nombres de los pasajeros del archivo passengers.csv y sus nacionalidades del archivo pasapts.csv . Este enfoque aprovecha la conveniencia y la claridad de tener nombres de campo para extraer los datos.

Una posible solución a la Tarea 2 es:

>>> passengers_without_passports = [
...     passenger
...     for passenger in passengers["passenger_no"]
...     if passenger not in passports["passenger_no"]
... ]

>>> passengers_without_passports
[np.int64(14)]

Para responder a esta pregunta, utiliza una lista de comprensión que recorre cada passenger_no en su matriz passengers y devuelve solo aquellos que no aparecen en su Matriz pasaportes. En este caso, el pasajero 14 no ha proporcionado los datos de su pasaporte.

Una posible solución a la tarea 3 es:

>>> passports_without_passengers = [
...     passenger
...     for passenger in passports["passenger_no"]
...     if passenger not in passengers["passenger_no"]
... ]

>>> passports_without_passengers
[np.int64(21)]

Para responder a esta pregunta, utiliza nuevamente una lista de comprensión, pero esta vez recorre cada passenger_no en su matriz pasaportes y devuelve solo aquellos que no aparecen en su matriz pasajeros. En este caso, se revela el pasaporte del pasajero inexistente 21.

En la siguiente sección, aprenderá a analizar datos jerárquicos.

Numpy Ejemplo 3: Análisis y trazando datos jerárquicos

Los datos jerárquicos son datos que constan de diferentes niveles, y cada nivel está vinculado a los inmediatamente superiores e inferiores. A menudo se dibuja utilizando un diagrama de árbol y los diferentes niveles a menudo se describen como si tuvieran una relación padre-hijo.

Por ejemplo, es posible que tenga una organización con varios departamentos y cada departamento contenga varios miembros del personal. Existe una relación jerárquica entre la organización, sus datos departamentales y los datos del personal que trabaja en cada departamento.

Las matrices estructuradas son adecuadas para unir datos jerárquicos porque le permiten hacer referencia a datos por etiquetas. Para usar Numpy con datos jerárquicos, puede consolidarlo en una sola matriz.

En esta sección, considerará analizar una cartera de acciones. Una cartera de acciones es el nombre dado a la colección de acciones celebradas en una variedad de empresas. Crear una cartera es una estrategia inteligente para los inversores, ya que ayuda a difundir el riesgo de inversión. La idea es que las pérdidas incurridas en algunas acciones se verán compensadas por ganancias en otras.

Su cartera de acciones contiene datos jerárquicos porque consiste en múltiples inversiones, cada una con su propia recopilación de movimientos diarios de precios. Al analizar estos datos, puede ver qué tan bien está funcionando su cartera.

Creando la matriz en blanco

Los datos utilizados en esta sección simulan algunos datos jerárquicos. Suponga que mantiene un archivo que contiene una lista de las diversas compañías en las que tiene acciones. En este ejemplo, encontrará esta información en el archivo portfolio.csv incluido en sus descargas. Aquí se muestra a continuación:

Company,Sector
Company_A,technology
Company_B,finance
Company_C,healthcare
Company_D,technology
Company_E,finance
Company_F,healthcare

La columna Empresa contiene los nombres de las empresas, mientras que la columna Sector contiene los sectores a los que pertenece la empresa.

Cada día, durante el transcurso de una semana, descarga los precios de las acciones de cada una de las empresas en las que tiene intereses y los agrega a una matriz NumPy estructurada denominada portfolio. Encontrará los datos de precios de cada día en una serie de archivos separados llamados share_prices-n.csv, donde n es un número entre uno y cinco. Por ejemplo, share_prices-1.csv contiene los precios del lunes, share_prices-2.csv los precios del martes, etc.

A continuación se muestra un archivo de precios de acciones de muestra:

Company,mon
Company_A,100.5
Company_B,200.1
Company_C,50.3
Company_D,110.5
Company_E,200.1
Company_F,55.3

En este archivo share_prices-1.csv, hay dos columnas. La columna Company muestra los nombres de las empresas, mientras que la columna mon muestra los precios de cada acción de la empresa para el lunes. El resto de archivos siguen un patrón similar, salvo sus columnas de días, que son diferentes.

Para analizar estos datos, su matriz Portfolio necesitará siete campos . Además del nombre de la empresa y el sector al que pertenece, también necesitará cinco campos para mantener los precios diarios de cada compañía. Los dos primeros campos serán cadenas, mientras que el resto serán flotadores.

El código que se muestra a continuación crea la matriz inicial cartera :

>>> import numpy as np
>>> from pathlib import Path

>>> days = ["mon", "tue", "wed", "thu", "fri"]
>>> days_dtype = [(day, "f8") for day in days]
>>> company_dtype = [("company", "U20"), ("sector", "U20")]

>>> portfolio_dtype = np.dtype(company_dtype + days_dtype)
>>> portfolio = np.zeros((6,), dtype=portfolio_dtype)
>>> portfolio
array([('', '', 0., 0., 0., 0., 0.), ('', '', 0., 0., 0., 0., 0.),
       ('', '', 0., 0., 0., 0., 0.), ('', '', 0., 0., 0., 0., 0.),
       ('', '', 0., 0., 0., 0., 0.), ('', '', 0., 0., 0., 0., 0.)],
      dtype=[('company', '<U20'), ('sector', '<U20'), ('mon', '<f8'),
      ⮑ ('tue', '<f8'), ('wed', '<f8'), ('thu', '<f8'), ('fri', '<f8')])

Como antes, usará las bibliotecas numpy y pathlib, por lo que las importará en las líneas 1 y 2.

Para definir los nombres de campo y los tipos de datos de cada campo en su matriz Portfolio , comienza creando las tres listas que se muestran en las líneas 4, 5 y 6. El days_dtype list Contiene una serie de tuplas, una para cada valor en la matriz Days que creó en la línea 4, con un tipo de datos de F8 , que representa un número de punto flotante. La lista Company_dtype contiene definiciones para los datos de la compañía portfolio.csv del archivo.

Para crear el objeto de tipo de datos real que definirá los tipos de datos de la matriz portfolio, concatene las listas company_dtype y days_dtype juntas. Luego, envía el resultado a un objeto dtype usando la función np.dtype() como se muestra en la línea 8.

El objeto dtype se alimenta en el parámetro np.zeros() como su parámetro dtype . También configura su matriz con una forma de (6,) para proporcionar una fila separada para los datos de cada compartir. Esto produce una matriz que contiene cadenas vacías en los dos primeros campos y ceros en el resto, como se muestra en la salida.

Poblando la matriz

Ahora que ha creado una matriz lo suficientemente grande como para almacenar todos los datos que necesita, el siguiente paso es comenzar a completarla. Para comenzar, agregará los detalles de las empresas almacenadas en portfolio.csv:

>>> companies = np.loadtxt(
...     Path("portfolio.csv"),
...     delimiter=",",
...     dtype=company_dtype,
...     skiprows=1,
... ).reshape((6,))

>>> portfolio[["company", "sector"]] = companies
>>> portfolio
array([('Company_A', 'technology', 0., 0., 0., 0., 0.),
       ('Company_B', 'finance', 0., 0., 0., 0., 0.),
       ('Company_C', 'healthcare', 0., 0., 0., 0., 0.),
       ('Company_D', 'technology', 0., 0., 0., 0., 0.),
       ('Company_E', 'finance', 0., 0., 0., 0., 0.),
       ('Company_F', 'healthcare', 0., 0., 0., 0., 0.)],
      dtype=[('company', '<U20'), ('sector', '<U20'), ('mon', '<f8'),
      ⮑ ('tue', '<f8'), ('wed', '<f8'), ('thu', '<f8'), ('fri', '<f8')])

Una vez más, usa la función loadTxt() , esta vez para agregar datos de portfolio.csv en una matriz estructurada llamada compañías . Tenga en cuenta que .Reshape ((6,)) se usó en la línea 6 para darle a la matriz la misma forma que la matriz cartera que creó anteriormente. Esto es necesario para insertar empresas en cartera .

La línea 8 es donde se realiza la inserción. Los dos campos de matriz companies se insertan como los campos company y sector en portfolio, como puede ver en la producción.

Todo lo que queda por hacer es la adición de los diversos precios diarios de las acciones. El código para hacer esto se muestra a continuación:

>>> share_prices_dtype = [("company", "U20"),("day", "f8"),]

>>> for day, csv_file in zip(
...     days, sorted(Path.cwd().glob("share_prices-?.csv"))
... ):
...     portfolio[day] = np.loadtxt(
...         csv_file.name,
...         delimiter=",",
...         dtype=share_prices_dtype,
...         skiprows=1,
...     )["day"]

>>> portfolio
array([('Company_A', 'technology', 100.5, 101.2, 102. , 101.8, 112.5),
       ('Company_B', 'finance', 200.1, 199.8, 200.5, 201. , 200.8),
       ('Company_C', 'healthcare',  50.3,  50.5,  51. ,  50.8,  51.2),
       ('Company_D', 'technology', 110.5, 101.2, 102. , 111.8,  97.5),
       ('Company_E', 'finance', 200.1, 200.8, 200.5, 211. , 200.8),
       ('Company_F', 'healthcare',  55.3,  50.5,  53. ,  50.8,  52.2)],
      dtype=[('company', '<U20'), ('sector', '<U20'), ('mon', '<f8'),
      ⮑ ('tue', '<f8'), ('wed', '<f8'), ('thu', '<f8'), ('fri', '<f8')])

Para comenzar, cree una lista que defina los dos campos dentro de cada uno de los archivos diarios share_prices-. Para agregar estos datos a su matriz portfolio principal, una vez más crea un bucle que itera sobre cada uno de ellos, pero esta vez lo hace usando el zip() integrado de Python. función. Los archivos se procesarán en el orden que aprendió anteriormente.

En este caso, pasas a zip() la lista de días que definiste anteriormente, así como cada uno de los archivos que estás procesando. Esto produce una serie de tuplas, una para cada día y par de archivos encontrados. Cada día de la tupla se asigna a la variable day del bucle, mientras que cada archivo se asigna a la variable csv_file.

Dentro del cuerpo del bucle, cada archivo se lee nuevamente usando np.loadTxt() , pero esta vez, en lugar de mantener todo el archivo, solo se almacenan sus datos de campo Day . La primera vez alrededor del bucle, estos datos se insertan en el campo portfolio array de mon , la segunda vez que se inserta en el campo tue , etcétera. Hizo esto asignando los datos leídos en cartera [día] por cada día diferente.

La versión final de la matriz contiene el nombre de la compañía, así como el sector al que pertenece. Los últimos cinco números en cada récord son los precios de las acciones de lunes a viernes en centavos.

Ahora que ha combinado sus datos jerárquicos en una matriz estructurada, puede analizarlo utilizando los nombres de campo por simplicidad. Supongamos que desea extraer una sola empresa para un vistazo más de cerca:

>>> portfolio[portfolio["company"] == "Company_C"]
array([('Company_C', 'healthcare', 50.3, 50.5, 51., 50.8, 51.2)],
      dtype=[('company', '<U20'), ('sector', '<U20'),
      ⮑ ('mon', '<f8'), ('tue', '<f8'), ('wed', '<f8'),
      ⮑ ('thu', '<f8'), ('fri', '<f8')])

Aquí, extrae una sola empresa ubicándola en la columna company. Para hacer esto, usa portfolio["company"] == "Company_C", que selecciona aquellas filas cuyos valores de columna company coinciden con "Company_C". Este método es mucho más intuitivo que seleccionar filas con indexación.

Del mismo modo, si desea ver el rendimiento de las empresas de tecnología de su cartera un viernes, también puede seleccionar esas cifras:

>>> portfolio[portfolio["sector"] == "technology"]["fri"]
array([112.5,  97.5])

Para ver los registros de tecnología, utilice portfolio["sector"] == "technology". Luego, para ver solo los registros del viernes, filtrelos usando ["fri"].

Supongamos que posee 250 acciones en cada una de sus compañías de tecnología. Es posible que desee ver cuánto dinero valen al final de la semana:

>>> portfolio[portfolio["sector"] == "technology"]["fri"] * 250 * 0.01
array([281.25, 243.75])

Para hacer esto, utiliza las técnicas que ya ha aprendido para seleccionar las cifras del viernes para cada una de las partes de tecnología de su cartera. Luego multiplicas las cifras por 250, que es el número de acciones que posees. Finalmente, multiplica el resultado por 0.01 para convertir las cantidades en dólares, teniendo en cuenta que los precios de las acciones se cotizan en centavos. Si quieres el valor neto, todo lo que necesitas hacer es usar sum():

>>> sum(portfolio[portfolio["sector"] == "technology"]["fri"] * 250 * 0.01)
np.float64(525.0)

La parte Tecnología de su cartera vale $525.00.

Como puedes ver, el uso de matrices estructuradas te permite acceder a los datos de una forma muy intuitiva. Para ir más allá, también puede utilizar este enfoque cuando utilice una matriz estructurada como base para un gráfico Matplotlib. Esto es lo que harás a continuación.

Trazando los datos

Supongamos que desea mostrar el análisis de la parte tecnología de su cartera en un gráfico. Nuevamente, debido a que estás trabajando con una matriz estructurada, el código se vuelve intuitivo:

>>> import matplotlib.pyplot as plt

>>> tech_mask = portfolio["sector"] == "technology"
>>> tech_sector = portfolio[tech_mask]["company"]
>>> tech_valuation = portfolio[tech_mask]["fri"] * 250 * 0.01

>>> (
...     plt.bar(x=tech_sector, height=tech_valuation, data=tech_valuation)[0]
...     .set_color("g")
... )
>>> plt.xlabel("Tech Companies")
>>> plt.ylabel("Friday Price ($)")
>>> plt.title("Tech Share Valuation ($)")
>>> plt.show()

Primero crea la matriz auxiliar tech_mask. A continuación, crea dos matrices que se utilizarán en el gráfico. La matriz tech_sector, definida en la línea 4, contiene los nombres de las empresas para cada empresa tech_sector. La matriz tech_value, definida en la línea 5, contiene las valoraciones del viernes para cada empresa tech_sector.

Las líneas 7 a 10 crean el diagrama de barras. El eje x contiene los nombres de las empresas tech_sector utilizadas para crear las barras, mientras que el parámetro height contiene sus valoraciones. Los valores tech_value son los que se trazan. Las líneas 11, 12, 13 y 14 producen etiquetas para los ejes y le dan un título al gráfico. Finalmente, se muestra la trama.

Si ejecuta el código anterior en un cuaderno Jupyter, no necesita usar plt.show() . Si lo ejecuta en el Python Reply estándar, las referencias de objetos se mostrarán después de las líneas 11, 12 y 13. Puede ignorarlas, y se han eliminado de la salida para mayor claridad.

La gráfica resultante se muestra a continuación:

Como puede ver, Company_a parece estar mejor de los dos, aunque solo ligeramente.

Prueba de sus habilidades: análisis de datos jerárquicos de análisis y trazado

Antes de continuar, aquí está su tercer desafío:

Se le ha pedido que recopile las temperaturas promedio mensuales diarias de cada mes del año para su análisis. Los datos se almacenan en los archivos london_temperatures.csv, new_york_temperatures.csv y rome_temperatures.csv.

Utilizando las habilidades que ha aprendido, cree una matriz weather_data estructurada con nombres de campos y tipos de datos sensibles que contengan cuatro valores de datos para cada mes. El primero de ellos debe contener el mes, mientras que los otros tres deben contener la temperatura del mes para cada ciudad.

Use su matriz estructurada para trazar cada temperatura mensual para las tres ciudades en una parcela de línea.

Una posible solución para crear la matriz es:

>>> import numpy as np
>>> from pathlib import Path

>>> cities = ["london", "new_york", "rome"]
>>> cities_dtype = [(city, "i8") for city in cities]
>>> city_files_dtype = [("month", "U20"), ("temp", "i8")]
>>> weather_data_dtype = np.dtype([("month", "U20")] + cities_dtype)

>>> weather_data = np.zeros((12,), dtype=weather_data_dtype)
>>> weather_data
array([('', 0, 0, 0), ('', 0, 0, 0), ('', 0, 0, 0), ('', 0, 0, 0),
       ('', 0, 0, 0), ('', 0, 0, 0), ('', 0, 0, 0), ('', 0, 0, 0),
       ('', 0, 0, 0), ('', 0, 0, 0), ('', 0, 0, 0), ('', 0, 0, 0)],
      dtype=[('month', '<U20'), ('london', '<i8'),
      ⮑ ('new_york', '<i8'), ('rome', '<i8')])

>>> for city in cities:
...     temps = np.loadtxt(
...         Path(f"{city}_temperatures.csv"),
...         delimiter=",",
...         dtype=city_files_dtype,
...     )
...     weather_data[["month", city]] = temps
...

>>> weather_data
array([('Jan',  5,  2,  8), ('Feb',  7,  2,  9), ('Mar',  9,  4, 12),
       ('Apr', 11, 11, 14), ('May', 14, 16, 21), ('Jun', 16, 22, 23),
       ('Jul', 19, 25, 26), ('Aug', 19, 24, 24), ('Sep', 17, 20, 22),
       ('Oct', 13, 14, 18), ('Nov', 10, 12, 13), ('Dec',  7,  9, 10)],
      dtype=[('month', '<U20'), ('london', '<i8'),
      ⮑ ('new_york', '<i8'), ('rome', '<i8')])

Comienza creando una matriz de tamaño correcto por adelantado para aceptar los datos. Como antes, utiliza listas y comprensiones de listas para definir cada campo y su tipo de datos antes de producir la matriz weather_data en las líneas 9 y 10. Puede ver el resultado en las líneas 11 a 14. Tenga en cuenta que cada < El campo Código> mes se inicializa con una cadena vacía, mientras que cada campo entero se establece en 0 .

En la línea 17, comienzas a recubrir las ciudades individuales. Para Londres, lee el archivo london_temperatures.csv en una matriz temps , y en la línea 23 asigna sus datos al mes y Londres campos en su matriz weather_data . Los datos de Nueva York y Roma se leen y se agregan de la misma manera. Las etiquetas del mes se sobrescriben para cada ciudad. Pero esto está bien siempre que sean iguales en todos los conjuntos de datos.

Las líneas 27 a 32 muestran la matriz completa. Una posibilidad para el diagrama lineal podría ser la siguiente:

>>> import matplotlib.pyplot as plt

>>> plt.plot(weather_data["month"], weather_data["london"])
>>> plt.plot(weather_data["month"], weather_data["new_york"])
>>> plt.plot(weather_data["month"], weather_data["rome"])

>>> plt.ylabel("Temperature (C)")
>>> plt.xlabel("Month")
>>> plt.title("Average Monthly Temperatures")
>>> plt.legend(["London", "New York", "Rome"])
>>> plt.show()

Para construir la gráfica, trazas los meses a lo largo del eje X y creas una línea separada para cada una de las tres temperaturas de las tres ciudades a lo largo del eje Y. Luego agrega etiquetas a los dos ejes de su gráfico y agrega un título y una leyenda.

Su resultado final debería verse así:

Como puede ver, de las tres ciudades, Roma tiene temperaturas consistentemente más altas.

En la sección final, aprenderá sobre una de las principales eficiencias de NumPy y cómo escribir funciones que la aprovechen.

Ejemplo 4 de NumPy: escribir sus propias funciones vectorizadas

Una de las eficiencias de Numpy es su capacidad para realizar cálculos en matrices completas sin que el programador tenga que escribir bucles lentos que sean manualmente en cada fila o elemento. En cambio, Numpy usa el lenguaje C subyacente para realizar el cálculo en toda la matriz. Esto se conoce como vectorización .

En esta sección final, trabajará con el archivo full_portfolio.csv que se muestra a continuación:

Company,Sector,Mon,Tue,Wed,Thu,Fri
Company_A,technology,100.5,101.2,102,101.8,112.5
Company_B,finance,200.1,199.8,200.5,201.0,200.8
Company_C,healthcare,50.3,50.5,51.0,50.8,51.2
Company_D,technology,110.5,101.2,102,111.8,97.5
Company_E,finance,200.1,200.8,200.5,211.0,200.8
Company_F,healthcare,55.3,50.5,53.0,50.8,52.2

Estos datos le parecerán familiares, ya que es una fusión de los archivos utilizados en la sección anterior. El encabezado de cada columna tiene el mismo significado que antes.

El siguiente código muestra la vectorización en acción:

>>> import numpy as np
>>> from pathlib import Path

>>> share_dtypes = [
...     ("company", "U20"),
...     ("sector", "U20"),
...     ("mon", "f8"),
...     ("tue", "f8"),
...     ("wed", "f8"),
...     ("thu", "f8"),
...     ("fri", "f8"),
... ]

>>> portfolio = np.loadtxt(
...     Path("full_portfolio.csv"),
...     delimiter=",",
...     dtype=share_dtypes,
...     skiprows=1,
... )

>>> portfolio["fri"] - portfolio["mon"]
array([ 12. ,   0.7,   0.9, -13. ,   0.7,  -3.1])

Después de construir la matriz estructurada cartera , decide ver cómo se han desempeñado sus acciones durante la semana. Para hacer esto, elige dos matrices, una que contiene los precios de las acciones del lunes y otra que los contiene del viernes. Para ver el cambio semanal, restas los precios del lunes de los precios del viernes.

Observe en la línea 21, que aunque se sudo una matriz de la otra, Numpy resta cada elemento individual de las matrices sin que tenga que escribir un código que eleve a través de ellas individualmente. Esta es la vectorización.

Ahora suponga que obtiene un bono adicional del 10% en las acciones que han aumentado más del 1% en valor durante la semana que está analizando. Para encontrar sus ganancias, incluida la bonificación, debe considerar dos casos: aquellas acciones que obtienen el bono y las que no lo hacen. Para hacer esto, puede probar lo siguiente:

>>> def profit_with_bonus(first_day, last_day):
...     if last_day >= first_day * 1.01:
...         return (last_day - first_day) * 1.1
...     else:
...         return last_day - first_day
...
>>> profit_with_bonus(portfolio["mon"], portfolio["fri"])
Traceback (most recent call last):
  ...
ValueError: The truth value of an array with more than one element is ambiguous.
⮑ Use a.any() or a.all()

En las líneas 1 a 5, ha definido una función llamada beneficio_with_bonus() que devolverá sus ganancias en función de cómo ha aumentado el precio de su acción durante la semana. Si la ganancia es más del 1%, entonces agrega otro 10% como bono.

Cuando llamas a tu función en la línea 7, genera una excepción ValueError porque no puede interpretar las matrices que le has pasado. Sólo funcionará si le pasas dos números. Para que esta función funcione con matrices, siga leyendo.

Agregar funcionalidad de vectorización con np.vectorize()

Para que su función profit_with_bonus() funcione con matrices, deberá convertirla en una función vectorizada. Una forma de hacer esto es utilizar la función np.vectorize(). Esta función devolverá una versión de su función original, pero una que toma matrices como entrada en lugar de escalares.

El código que se muestra a continuación transforma su función en una función vectorizada:

>>> def profit_with_bonus(first_day, last_day):
...     if last_day >= first_day * 1.01:
...         return (last_day - first_day) * 1.1
...     else:
...         return last_day - first_day
...
>>> vectorized_profit_with_bonus = np.vectorize(profit_with_bonus)
>>> vectorized_profit_with_bonus(portfolio["mon"], portfolio["fri"])
array([ 13.2 ,   0.7 ,   0.99, -13.  ,   0.7 ,  -3.1 ])

Para convertir su función graving_with_bonus() en una versión vectorizada, la pasa a la función np.vectorize() en la línea 7. La versión vectorizada luego se asigna a la < Código> Vectored_profit_with_bonus Nombre.

En la línea 8, llama a la nueva función y le pasa las matrices que desea que analice. En este ejemplo, el resultado muestra su beneficio, incluida la bonificación adicional. Si compara los números con la sencilla cálculo de ganancias que hizo antes, notará que ha recibido una bonificación para sus acciones en la primera y tercera compañía. Observe que no necesitaba cambiar la función original en las líneas 1 a 5.

Un punto final sobre el uso de np.vectorize() para que tenga en cuenta que la función original todavía está disponible si la necesita:

>>> in_profit(3, 5)
2.2

Esta vez, se devuelve un solo valor. Dado que 5 - 3 es 2 y 2 es más del 1% de 3 , agrega 10% a Obtenga 2.2 .

Agregar funcionalidad de vectorización con @np.vectorize

Como acabas de ver, la función np.vectorize() crea una segunda función, lo que significa que la versión original todavía está disponible si necesitas usarla. Como alternativa, puedes usar np.vectorize como decorador:

>>> @np.vectorize
... def profit_with_bonus(first_day, last_day):
...     if last_day >= first_day * 1.01:
...         return (last_day - first_day) * 1.1
...     else:
...         return last_day - first_day
...
>>> profit_with_bonus(portfolio["mon"], portfolio["fri"])
array([ 13.2 ,   0.7 ,   0.99, -13.  ,   0.7 ,  -3.1 ])

Al aplicar @np.vectorize a su función en la línea 1, la función profit_with_bonus() se transforma en una versión vectorizada. Esta nueva versión funciona de la misma manera que la función vectorized_profit_with_bonus() que viste anteriormente. Para usarlo, llame a profit_with_bonus() como lo haría normalmente, pero asegúrese de pasarle sus matrices. Esto devolverá la misma matriz de ganancias que antes.

A diferencia de la versión creada por la función np.vectorize() , la versión escalar original de ganado_with_bonus() ya no existe. Sin embargo, si lo pasa dos escalares, seguirá funcionando pero devolverá el resultado como una matriz:

>>> in_profit(3, 5)
array(2.2)

Como puede ver, esta vez la salida es una matriz.

Uso de la funcionalidad de vectorización existente con np.where()

Muchas de las funciones NumPy disponibles ya admiten la vectorización. Es una buena idea consultar la documentación para ver si ya hay una función disponible que se adapte a sus necesidades. Para encontrar su beneficio de las acciones, por ejemplo, podría haber utilizado la función np.where() para contabilizar tanto el caso de bonificación como el caso de beneficio normal en lugar de escribir su propia función:

>>> np.where(
...     portfolio["fri"] > portfolio["mon"] * 1.01,
...     (portfolio["fri"] - portfolio["mon"]) * 1.1,
...     portfolio["fri"] - portfolio["mon"],
... )
array([ 13.2 ,   0.7 ,   0.99, -13.  ,   0.7 ,  -3.1 ])

Esta vez, pasa su condición a np.where() . A diferencia de su función original graving_with_bonus() , .where() admite la vectorización de forma nativa. En este caso, usa .where() pasando una comparación entre dos matrices. También lo pasa dos matrices calculadas. El primero representa la ganancia con un bono adicional del 10%, mientras que el segundo es la ganancia simple.

Ahora, np.where() evaluará la condición para cada elemento y elegirá el elemento correspondiente de una de las dos matrices. Si la condición es true , usa el elemento desde la primera matriz, y si es false , elige el elemento del segundo.

Poniendo a prueba tus habilidades: escribiendo una función vectorizada

Es hora de sumergirse en su desafío de ejercicio final. Casi has terminado.

Se le ha pedido que cree dos matrices que contienen las temperaturas mínimas y máximas de la matriz Weather_data que creó en su último desafío. Si aún no lo ha hecho, construya la matriz weather_data ejecutando el primer bloque de código en la solución al ejercicio de datos jerárquicos de análisis y gráfico antes de continuar.

Luego, escriba una función llamada find_min_max() que acepte tres valores escalares, por ejemplo, números enteros, y devuelva los valores máximo y mínimo. Ahora, use @np.vectorize o np.vectorize() para mejorar su función para que funcione con matrices NumPy. Asegúrese de poder llamar a la versión vectorizada de su función por su nombre original.

Como punto de partida, su función inicial podría verse algo así:

>>> def find_min_max(first, second, third):
...     min_temp = min(first, second, third)
...     max_temp = max(first, second, third)
...     return min_temp, max_temp

>>> find_min_max(2, 1, 3)
(1, 3)

Su función inicial find_min_max() toma tres valores escalares y devuelve los valores más bajo y más alto. Si lo intentas usando datos de tu matriz weather_data, no funcionará:

>>> london_temps = weather_data["london"]
>>> new_york_temps = weather_data["new_york"]
>>> rome_temps = weather_data["rome"]

>>> find_min_max(london_temps, new_york_temps, rome_temps)
>>> # ...
ValueError: The truth value of an array with more than one element is ambiguous.

Como puede ver, ha planteado una excepción valueError . Deberá actualizarlo para trabajar con matrices.

Una posible solución que usa @np.vectorize podría ser:

>>> @np.vectorize
... def find_min_max(first, second, third):
...     min_temp = min(first, second, third)
...     max_temp = max(first, second, third)
...     return min_temp, max_temp

>>> london_temps = weather_data["london"]
>>> new_york_temps = weather_data["new_york"]
>>> rome_temps = weather_data["rome"]

>>> find_min_max(london_temps, new_york_temps, rome_temps)
(array([ 2,  2,  4, 11, 14, 16, 19, 19, 17, 13, 10,  7]),
⮑ array([ 8,  9, 12, 14, 21, 23, 26, 24, 22, 18, 13, 10]))

Para usar la versión vectorizada de su función find_min_max() , la decora con @np.vectorize . Para llamar a la función vectorizada, pasa directamente en las tres matrices de datos meteorológicos. El decorador le permite comprender las matrices como entradas y producirlas como salidas. La función utiliza las funciones incorporadas min() y max() para analizar cada uno de los tres elementos para cada mes y devuelve el resultado en dos matrices.

Una solución alternativa podría ser volver a su función original y usar np.vectorize() en su lugar:

>>> def find_min_max(first, second, third):
...     min_temp = min(first, second, third)
...     max_temp = max(first, second, third)
...     return min_temp, max_temp

>>> london_temps = weather_data["london"]
>>> new_york_temps = weather_data["new_york"]
>>> rome_temps = weather_data["rome"]

>>> find_min_max = np.vectorize(find_min_max)
>>> find_min_max(london_temps, new_york_temps, rome_temps)
(array([ 2,  2,  4, 11, 14, 16, 19, 19, 17, 13, 10,  7]),
⮑ array([ 8,  9, 12, 14, 21, 23, 26, 24, 22, 18, 13, 10]))

Para usar la versión vectorizada de su función find_min_max() , la pasa a np.vectorize() , y asignando la salida de nuevo a la variable find_min_max , puede llamar a la versión vectorizada usando su nombre original. Para llamar a esta versión vectorizada, pasa directamente en las tres matrices de datos meteorológicos.