Pesquisa de site

Exemplos práticos numpy: técnicas úteis


A Biblioteca Numpy é uma biblioteca Python usada para computação científica. Ele fornece um objeto de matriz multidimensional para armazenar e analisar dados de várias maneiras. Neste tutorial, você verá exemplos de alguns recursos que Numpy fornece que nem sempre são destacados em outros tutoriais. Você também terá a chance de praticar suas novas habilidades com vários exercícios.

Neste tutorial, você aprenderá a:

  • Crie matrizes multidimensionais a partir de dados armazenados em arquivos
  • Identifique e remova dados duplicados de um array NumPy
  • Use matrizes Numpy estruturadas para reconciliar as diferenças entre os conjuntos de dados
  • Analisar e gráfico de partes específicas de dados hierárquica
  • Crie versões vetorizadas de suas próprias funções

Se você é novo no NumPy, é uma boa ideia familiarizar-se com os fundamentos da ciência de dados em Python antes de começar. Além disso, você usará Matplotlib neste tutorial para criar gráficos. Embora não seja essencial, familiarizar-se previamente com o Matplotlib pode ser benéfico.

Configurando seu ambiente de trabalho

Antes de começar com este tutorial, você precisará fazer alguma configuração inicial. Além da Numpy, você precisará instalar a biblioteca Matplotlib, que você usará para traçar seus dados. Você também usará a biblioteca PathLib do Python para acessar o sistema de arquivos do seu computador, mas não há necessidade de instalar pathlib porque faz parte da biblioteca padrão do Python.

Você pode considerar o uso de um ambiente virtual para garantir que a configuração do seu tutorial não interfira em nada em seu ambiente Python existente.

Usar um Jupyter Notebook no JupyterLab para executar seu código em vez de um Python REPL é outra opção útil. Ele permite que você experimente e documente suas descobertas, bem como visualize e edite arquivos rapidamente. A versão para download do código e as soluções dos exercícios são apresentadas no formato Jupyter Notebook.

Os comandos para configurar as coisas nas plataformas comuns são mostrados abaixo:

O primeiro trecho de código deve ser usado no Windows e o segundo snippet de código é para Linux + MacOS:

Iniciar um Windows PowerShell (admin) ou terminal (admin) prompt, dependendo da versão do Windows que você está usando. Agora digite os seguintes comandos:

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

Aqui você cria um ambiente virtual chamado venv\, que você ativa. Se a ativação for bem -sucedida, o nome do ambiente virtual precederá seu prompt PowerShell. Em seguida, você instala numpy e matplotlib nesse ambiente virtual, seguido pelo jupyterLab opcional. Finalmente, você começa a Jupyterlab.

Abra um terminal e digite os seguintes comandos:

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

Aqui você cria um ambiente virtual chamado venv/, que você então ativa. Se a ativação for bem-sucedida, o nome do ambiente virtual precederá o prompt de comando. Em seguida, você instala numpy e matplotlib neste ambiente virtual, seguido pelo jupyterlab opcional. Finalmente, você inicia o JupyterLab.

Você notará que seu prompt é precedido por (venv). Isso significa que qualquer coisa que você fizer deste ponto em diante permanecerá neste ambiente e permanecerá separada de outros trabalhos em Python que você tenha em outro lugar.

Agora que você configurou tudo, é hora de começar a parte principal de sua jornada de aprendizado.

Exemplo NumPy 1: Criando matrizes multidimensionais a partir de arquivos

Ao criar uma matriz Numpy, você cria uma estrutura de dados altamente otimizada. Uma das razões para isso é que uma matriz Numpy armazena todos os seus elementos em uma área contígua de memória. Essa técnica de gerenciamento de memória significa que os dados são armazenados na mesma região de memória, tornando os tempos de acesso rapidamente. Obviamente, isso é altamente desejável, mas ocorre um problema quando você precisa expandir sua matriz.

Suponha que você precise importar vários arquivos para uma matriz multidimensional. Você pode lê -los em matrizes separadas e combiná -las usando np.Concatenate() . No entanto, isso criaria uma cópia da sua matriz original antes de expandir a cópia com os dados adicionais. A cópia é necessária para garantir que a matriz atualizada ainda exista contiguamente na memória, pois a matriz original pode ter tido conteúdo não relacionado adjacente a ela.

Copiar constantemente as matrizes cada vez que você adiciona novos dados de um arquivo pode tornar o processamento lento e é um desperdício da memória do seu sistema. O problema fica pior quanto mais dados você adicionar à sua matriz. Embora esse processo de cópia seja incorporado em Numpy, você pode minimizar seus efeitos com essas duas etapas:

  1. Ao configurar seu array inicial, determine o tamanho que ele precisa ter antes de preenchê-lo. Você pode até considerar superestimar seu tamanho para dar suporte a futuras adições de dados. Depois de conhecer esses tamanhos, você pode criar seu array antecipadamente.

  2. O segundo passo é preenchê -lo com os dados de origem. Esses dados serão colocados em sua matriz existente sem a necessidade de serem expandidos.

Em seguida, você explorará como preencher uma matriz Numpy tridimensional.

Matrizes populantes com dados de arquivo

Neste primeiro exemplo, você usará os dados de três arquivos para preencher um array tridimensional. O conteúdo de cada arquivo é mostrado abaixo, e você também encontrará esses arquivos nos materiais para download:

O primeiro arquivo tem duas linhas e três colunas com o seguinte conteúdo:

1.1, 1.2, 1.3
1.4, 1.5, 1.6

Este segundo arquivo, que também possui as mesmas dimensões, contém isso:

2.1, 2.2, 2.3
2.4, 2.5, 2.6

O terceiro arquivo, novamente com as mesmas dimensões, armazena esses números:

3.1, 3.2, 3.3
3.4, 3.5, 3.6

Antes de continuar, adicione esses três arquivos à pasta do programa. Os materiais para download também contêm dois arquivos chamados file10.csv e file11.csv, e você trabalhará com eles mais tarde.

O diagrama abaixo mostra a matriz Numpy resultante que você criará a partir dos três arquivos:

Como você pode ver, file1.csv forma a frente do seu array, file2.csv a seção intermediária e file3.csv está no voltar.

O código usado para criar essa matriz é mostrado abaixo. Antes de executar esse código, crie os três arquivos mostrados no diagrama ou use as versões fornecidas nos materiais para download. De qualquer maneira, coloque -os no mesmo diretório em que você está executando seu código e execute -o:

>>> 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 começar, você pode examinar cada um dos arquivos e usar as informações para determinar a forma final da matriz esperada. Neste exemplo, todos os três arquivos têm o mesmo número de elementos organizados em duas linhas e três colunas. A matriz resultante terá uma propriedade Shape de (3, 2, 3) . Dê uma outra olhada no diagrama e você poderá ver isso.

As linhas 1 e 2 mostram que a biblioteca Numpy sendo importada com seu alias padrão de np e também a classe Path sendo importada da biblioteca pathlib . Esta biblioteca permite que o Python acesse o sistema de arquivos do seu computador usando uma abordagem orientada a objetos. Os objetos da classe Path permitem especificar um caminho de arquivo e também conter métodos para que você possa fazer chamadas do sistema para o seu sistema operacional. Esse recurso é usado posteriormente no código.

Você aprendeu anteriormente como é uma boa ideia criar uma matriz antecipada antes de começar a preencher -a com dados para reduzir a pegada de memória. Na linha 4, você cria uma matriz contendo zeros com uma forma de (3, 2, 3) conforme determinado anteriormente.

Em seguida, você preenche seu array com os dados dos arquivos. Você cria um loop for usando a função enumerate integrada do Python mostrada nas linhas 8 e 9. Seu parâmetro permite que você faça um loop em um conjunto de arquivos e retorne uma referência para cada um. um. Ele também mantém uma contagem de quantos arquivos foram encontrados. Cada referência de arquivo é armazenada na variável csv_file, enquanto o contador de incremento é armazenado na variável file_count.

Para acessar cada um dos três arquivos .csv Por sua vez, você usa caminho . Ao chamar path.cwd() , você está dizendo ao Python para procurar no seu diretório de trabalho atual para os arquivos. Em outras palavras, o diretório de onde você está executando o programa. Isso retorna um objeto caminho representando o diretório atual, do qual você chama seu método .glob() para especificar os nomes dos arquivos que deseja acessar.

Nesse caso, como você deseja acessar arquivos denominados file1.csv, file2.csv e file3.csv , você passa os nomes como o arquivo do String ? .csv . Isso diz ao .glob() para selecionar apenas arquivos cujos nomes devem corresponder a esses caracteres exatos, mas cujo quinto personagem pode ser qualquer caractere, conforme especificado pelo caractere curinga (? ).

Infelizmente, .glob() pode não retornar os arquivos na ordem esperada. Neste exemplo, tudo funciona conforme o esperado porque o nome de cada arquivo contém um único dígito como quinto caractere. Se houvesse um arquivo chamado file11.csv, ele teria sido lido na ordem errada. Você aprenderá mais sobre por que isso acontece e como resolvê-lo mais tarde.

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

Como você verá, esse tipo de classificação nem sempre é suficiente ao trabalhar com arquivos numerados.

Cada vez que o loop loop, você chama a função np.loadtxt() e passa um nome de arquivo, que é especificado usando sua propriedade name . Você também diz para usar uma vírgula (, ) como um delimitador de campo para permitir que ele separe cada número individual do arquivo. O conteúdo de cada arquivo é atribuído à matriz que você criou anteriormente.

Para garantir que o conteúdo de cada arquivo seja inserido na posição correta ao longo do eixo 0 , você usa Array [file_count] . Na primeira vez em que o loop é executado, o conteúdo do file1.csv será atribuído a Array [0] ou posicionar 0 ao longo do Axis-0. A próxima iteração no loop atribuirá file2.csv a Array [1] Ao longo deste eixo, antes de file3.csv ser atribuído a Array [2] . Veja mais uma vez o diagrama e você verá exatamente o que aconteceu.

Nas linhas 5 e 11, você imprimiu o resultado de id (matriz) . A função id() retorna a identidade de um objeto. Cada objeto tem um valor de identidade exclusivo porque cada objeto ocupa um local único na memória do computador. Quando você executa o código no seu computador, os números também serão idênticos entre si, mas provavelmente serão diferentes dos mostrados.

Nas linhas 6 e 12, os valores de identidade exibidos provam que o objeto array que começou contendo apenas zeros, é o mesmo objeto array que posteriormente continha o conteúdo de cada arquivo. Isso mostra que apenas um objeto foi usado e que a memória foi usada de forma eficiente.

Ao criar arrays dessa maneira, é uma boa ideia garantir que cada um dos arquivos de entrada tenha o mesmo número de linhas e colunas de elementos. A seguir, você verá como lidar com situações em que seus arquivos de dados não são tão uniformes.

Lidar com diferentes tamanhos de dados

Para começar, você adicionará alguns dados subdimensionados. Você o encontrará em um arquivo chamado short_file.csv, que possui apenas uma linha:

4.1, 4.2, 4.3

Você deseja adicioná-lo ao final do seu array, conforme mostrado abaixo:

Antes de executar o código usado para criar esse segundo array, baixe ou adicione o arquivo chamado short_file.csv no mesmo diretório em que você executa seu 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. ]]])

Desta vez, você está lendo quatro arquivos separados, então o array que você criar inicialmente terá o formato (4, 2, 3). Você precisará de uma dimensão extra ao longo do eixo 0 para acomodar o quarto arquivo, então crie-o na linha 1.

O loop for usado para ler os três primeiros arquivos como antes. Para ler no arquivo curto, você precisa dizer ao Python exatamente onde deseja colocá -lo em sua matriz. Anteriormente, você fez isso indicando uma posição, como Array [2] , para inserir os dados na posição 2 ao longo do eixo-0. Essa abordagem funcionou porque os dados que você inseriu preencheram a matriz existente já nessa posição. No entanto, desta vez as coisas são diferentes.

Para informar ao Python que você deseja inserir o arquivo curto começando no topo da posição 3 do índice no eixo 2, você usa array[3, 0]. O 3 representa a posição do eixo 2 como antes, mas você também deve fornecer um 0 para indicar que os dados devem ser inseridos começando na linha 0. Você pode dar uma olhada nas linhas 18 e 19 da saída do programa e depois no diagrama para ver onde os dados foram colocados.

Como antes, o objeto array criado no início do código é o único usado em todo o código. Embora seu código tenha adicionado dados várias vezes, nenhuma cópia ineficiente foi necessária porque você criou o array antecipadamente.

Suponha que, em vez de ser muito curto, o quarto arquivo foi muito longo. Você pode estar se perguntando como lidar com esses arquivos, que podem ser problemáticos. Desta vez, você usará um arquivo chamado long_file.csv , que tem uma linha extra:

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

Agora você deseja incorporá-lo ao seu array na posição mostrada abaixo. Como você pode ver no diagrama, o restante do array precisará ser estendido por uma linha extra para acomodá-lo:

O código usado para criar esta terceira matriz é mostrado abaixo. Antes de executá -lo, certifique -se de baixar ou criar o arquivo chamado long_file.csv No mesmo diretório, você está executando seu 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]]])

Desta vez, como sua matriz original é muito curta para acomodar o conteúdo de long_file.csv , você precisa alongá-lo com uma linha extra ao longo do Axis-1. Em seguida, você pode adicionar o conteúdo de long_file.csv . Você faz isso na linha 9 usando a função np.insert() , que é uma função que permite inserir valores ao longo de um eixo.

Você passa quatro parâmetros para np.insert() . O parâmetro ARR especifica a matriz em que você deseja inserir valores, enquanto configura obj para 2 , eixo para < Código> 1 e o parâmetro /Code> ao longo do eixo 1 . Em outras palavras, ao longo da linha inferior da matriz, como mostrado no diagrama. Por fim, para adicionar o conteúdo de long_file.csv à sua matriz, você mais uma vez use loadtxt() como mostrado na linha 11.

Reserve um momento para olhar para o diagrama e a matriz resultante produzida pelo seu código e você verá que tudo funciona conforme o esperado.

Observe que as linhas 4 e 14 indicam que os objetos da matriz antes e depois que os novos dados são inseridos são diferentes. Isso ocorre porque a função insert() retorna uma cópia da matriz original. Para evitar esse desperdício de memória, é realmente uma boa ideia garantir que você dimensione sua matriz inicial corretamente antes de começar a prendê -la.

Certificando-se de que a ordem dos arquivos está correta

Ao executar o código Path.cwd().glob("file?.csv"), você retorna um iterador gerador que pode ser usado para visualizar uma coleção de WindowsPath ou objetos PosixPath. Cada um deles representa um caminho de arquivo do sistema operacional e um nome de arquivo que corresponde ao padrão file?.csv. No entanto, a ordem em que esses arquivos são retornados pelo gerador não é a que você esperava.

Para ver um exemplo disso, adicione os dois arquivos chamados file10.csv e file11.csv dos materiais para download em sua pasta existente:

10.1,10.2,10.3
10.4,10.5,10.6

Você provavelmente já adivinhou o que estava no file10.csv . Nesse caso, você não ficará surpreso ao ver o que está no file11.csv :

11.1,11.2,11.3
11.4,11.5,11.6

Agora execute o seguinte 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 garantir que cada um desses arquivos adicionais seja visto pelo gerador, você precisa ajustar os critérios de correspondência para arquivo*.csv . O caractere (*) representa qualquer número de caracteres desconhecidos. Se você tivesse retido o caractere (? Mesmo com essa adição, algo ainda parece errado.

Você verá que os novos arquivos são colocados entre file1.csv e file2.csv e não, como você provavelmente esperava, no final. A razão para isso é que os nomes dos arquivos estão sendo classificados alfanumericamente. Isso significa que a classificação lê os nomes dos arquivos da esquerda para a direita e considera tudo igual até encontrar uma diferença. Uma vez obtido isso, a classificação é baseada nessa diferença.

Por exemplo, quando os caracteres de cada nome de arquivo estão sendo analisados, tudo é considerado igual nos primeiros quatro caracteres do nome de cada arquivo – neste caso, arquivo. Python então precisa decidir qual dos quintos caracteres vem primeiro. Isso é feito considerando os números de código dos caracteres Unicode de cada um deles.

O código de caracteres Unicode para o caractere 1 é 49, enquanto os códigos para 2 e 3 são 50 e 51, respectivamente. Como resultado, qualquer nome de arquivo com 1 como quinto caractere será classificado antes daqueles com 2 ou 3 na mesma posição.

No caso de file1.csv , file10.csv e file11.csv , o quinto caractere de cada nome de arquivo é o mesmo. Portanto, a ordem de classificação será decidida por seu sexto caráter. Ao considerar seus valores de caracteres unicode, o período (.) Tem um valor de 46, que vem antes dos caracteres 0 e 1 , cujos valores são 48 e 49, respectivamente, respectivamente . Consequentemente, o pedido de classificação será file1.csv , seguido por file10.csv e depois file11.csv .

Você pode ficar tentado a experimentar a função sorted() integrada do Python para ver se isso ajuda:

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

No entanto, a função classificada() forneceu o mesmo resultado indesejável de antes.

Para ler os arquivos em uma ordem mais natural, você pode usar a biblioteca Natsort . Primeiro, instale -o executando o comando python -m pip install natsort . Após a instalação, você pode importar a função natSorted() e usá-la em vez da função sToled() para obter uma classificação mais natural dos arquivos. Abaixo está o código que ilustra o seguinte:

>>> 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 fim, você conseguiu resolver o problema de classificação de nomes de arquivos. Agora você pode levar seu código anterior um passo adiante e adicionar o conteúdo do arquivo nos locais corretos do seu array:

>>> 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]]])

Desta vez, você passa os vários caminhos de arquivo para Natsorted() , o que os classificará da maneira que você provavelmente pretendia. A saída mostra que o conteúdo dos file10.csv e file11.csv estão agora em seus locais corretos dentro das matrizes. Novamente, observe como o operador (*) foi usado aqui. Além disso, as dimensões desta versão da matriz foram aumentadas para (5, 2, 3) para fornecer espaço para os novos dados.

Como você pode ver, é perfeitamente possível construir arrays NumPy a partir de dados em arquivos. No entanto, você precisa garantir que tamanhos diferentes sejam levados em consideração.

Antes de prosseguir, você fará um exercício para testar sua compreensão. Este é o primeiro de vários exercícios incluídos neste tutorial para ajudá-lo a consolidar seu aprendizado.

Testando suas habilidades: preenchendo matrizes com dados de arquivo

É hora de você testar sua compreensão da criação de matrizes a partir dos dados do arquivo. Veja se você pode resolver seu primeiro desafio mostrado abaixo:

Encontre os arquivos ex1a.csv, ex1b.csv e ex1c.csv nos materiais para download. Agora, aplique as técnicas que você aprendeu para criar um array tridimensional de tamanho correto que permitirá adicionar cada arquivo. O conteúdo de cada arquivo deve ser inserido de forma que toque o canto superior esquerdo de um índice separado ao longo do eixo 0. Você deve inserir o conteúdo do arquivo ex1b.csv como uma linha e o conteúdo de ex1c.csv como uma coluna.

Uma solução possível é:

>>> 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]]])

Primeiro, você olha através dos arquivos e vê que uma matriz com o tamanho (3, 4, 4) é necessária para garantir que seus dados se encaixem. Em seguida, você cria uma variedade de zeros com esse tamanho e especifica que ele armazenará números inteiros. Em seguida, você o atribui à variável Array antes de preencher Array [0] ou Axis-0, com o conteúdo do arquivo ex1a.csv , que se encaixa na forma confortável.

O arquivo ex1b.csv tem apenas três valores, o que significa que é muito curto para ser inserido diretamente na matriz [1] . Para corrigir isso, você primeiro o lê em estreito_array e depois usa np.insert() para adicionar um 0 e Obj parâmetros. Finalmente, você inseriu estreito_array na linha superior da prisão no índice 1 Ao longo de seus 0-exis usando Array [1, 0] .

O arquivo ex1c.csv também possui apenas três valores. Isso significa que você deve ajustá-lo para caber na coluna, então você adiciona um zero extra no final dela. Desta vez, para adicionar short_array, você usa array[2, :, 0]. Ele será inserido na posição de índice 2 ao longo do eixo 0, mas :, 0 significa que será inserido para baixo no primeiro coluna e não através de uma linha.

Em vez de estender as matrizes que você lê, você pode alternativamente ser mais específico sobre onde colocar os valores. Por exemplo, você pode inserir short_array em array[2, 0:3, 0] diretamente antes de inserir o 0 extra.

Na próxima seção, você aprenderá um pouco sobre matrizes estruturadas e como elas podem ser usadas para conciliar as diferenças entre diferentes matrizes.

Numpy Exemplo 2: Reconciliando dados usando matrizes estruturadas

Com os arrays NumPy que você criou na seção anterior, não havia como saber o significado dos dados de cada coluna. Não seria bom se você pudesse referenciar colunas específicas por nomes significativos em vez de números de índice? Então, por exemplo, em vez de usar student_grades=results[:, 1], você poderia usar student_grades=results["exam_grade"]. Boas notícias! Você pode fazer isso criando um array estruturado.

Criando um array estruturado

Um array estruturado é um array NumPy com um tipo de dados composto por um conjunto de tuplas, cada uma contendo um nome de campo e um tipo de dados regular. Depois de defini-los, você poderá acessar e modificar cada campo individual usando seu nome de campo.

O código abaixo fornece um exemplo de como criar e fazer referência a uma matriz Numpy estruturada:

>>> 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')

O array estruturado é definido nas linhas 3 a 14, mas comece observando as linhas 4 a 8, que definem o que parece ser um array NumPy normal. Consiste em três linhas e três colunas de dados relativos a uma corrida de cavalos. Você pode facilmente escolher os nomes dos cavalos a partir dos dados, mas pode ter um pouco de dificuldade para descobrir o que os outros dois números significam.

Essa matriz é na verdade uma matriz estruturada devido à definição de seus tipos de dados na linha 9. Cada tipo de dados consiste em um nome de campo e um tipo de dados associado. Os três campos são haves_name , preço e Posição . Seus tipos de dados associados são definidos usando códigos de protocolo de interface de matriz. u12 define uma sequência de 12 caracteres, enquanto f4 e i4 especificar 4 -Byte Flutuating-Point e formatos inteiros , respectivamente.

Depois de configurar sua matriz estruturada, você pode usar esses nomes de campo para fazer referência a colunas. Na linha 16, você usou "horse_name" para visualizar uma matriz de nomes de cavalos de corrida. Para encontrar a ordem de chegada, você passou o campo "position" para a função np.sort() na linha 19. Isso classificou os corredores em sua ordem de chegada. Em seguida, você filtrou a saída para mostrar apenas horse_name e price. Finalmente, na linha 27, você selecionou o nome do cavalo vencedor.

Reconciliando Matrizes Diferentes

A inclusão de nomes de campo em suas matrizes Numpy tem muitos propósitos úteis. Suponha que você queira combinar registros correspondentes aos nomes de campos em matrizes Numpy separadas. Para fazer isso, você junta suas matrizes para que apenas os registros correspondentes de cada matriz sejam mostrados. Essa ideia será familiar para você se você já executou uma junção interna do SQL entre duas tabelas de banco de dados relacional. O termo interno é usado para definir a junção usada aqui.

Nesta seção, você trabalhará com dois novos arquivos: emited_checks.csv e Cashed_checks.csv .

O arquivo emited_checks.csv código>. Este arquivo simula um conjunto de cheques emitidos pela sua empresa para seus credores:

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

O arquivo Cashed_Checks.csv Este arquivo simula um conjunto de cheques descontados pelos credores da sua empresa:

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

Digamos que você deseja ver o pageee , DATE_ISSUDED e date_cashed para verificações que foram descontadas. Se você olhar com cuidado, verá que os detalhes Payee e date_issued não estão incluídos em Cashed_checks.csv , enquanto os outros dois campos são. Para ver todos os dados necessários, você precisará unir os dois arquivos.

Adicione os arquivos emited_checks.csv e Cashed_Checks.csv na pasta do seu programa e execute 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 unir dois arrays NumPy, você usa uma das várias funções auxiliares de rearray, que permitem trabalhar com arrays estruturados. Para acessá-los, você deve primeiro importar o módulo da biblioteca numpy.lib.recfunctions. Mais uma vez, você usará a biblioteca pathlib para acessar os arquivos. Eles são importados nas linhas 1 e 2.

Nas linhas 4 a 15, você cria duas listas Python de tuplas que definem os tipos de dados a serem usados para o arquivo emited_checks.csv e o arquivo Cashed_Checks.csv . Eles são alimentados no parâmetro dType de np.loadText() para permitir que ele leia os arquivos nas matrizes estruturadas numpy corretamente.

Os arquivos reais são lidos pelo seu código nas linhas 17 a 29. Você usa np.loadText() Como fez anteriormente, mas desta vez você define o parâmetro skiprows para < Código> 1 . Isso garantirá que a primeira linha de cada arquivo seja ignorada porque cada um conter informações do cabeçalho e não dados.

As duas matrizes NumPy lidas nos arquivos são então unidas nas linhas 31 a 36 usando a função auxiliar .rec_join(). Esta função usa quatro parâmetros. O primeiro parâmetro define o campo com o qual os dois arrays serão unidos. Neste caso, você deseja unir os arrays com base em seus campos id, que contêm números únicos que identificam cada verificação em ambos os arquivos.

Em seguida, você passa os nomes das matrizes para serem unidas como o segundo e o terceiro parâmetros.

O parâmetro final é o mais interessante. Ao especificar juntayype="interno" , você executa uma junção interna. Isso significa que a matriz resultante conterá apenas registros correspondentes de ambos os arquivos, fornecendo detalhes completos de todos os cheques em dinheiro. Qualquer registro cujo id aparece em um arquivo, mas não o outro, não aparecerá na saída.

Para confirmar se a junção funcionou, observe a saída produzida pelas linhas 38 a 40. O array cashed_check_details contém o beneficiário, date_issued e Dados date_cashed. Os dois primeiros desses campos vieram do arquivo issued_checks.csv, enquanto o último veio de cashed_checks.csv. Como você pode ver, os registros foram correspondidos corretamente.

Lidando com nomes de campos duplicados

Agora suponha que você também queira visualizar o valor do cheque. Você pode ficar tentado a tentar isto:

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

O KeyError ocorreu porque o campo valor não existe mais na matriz unida. Isso aconteceu porque os dois arquivos de dados originais continham um campo quantia . Não é possível ter uma matriz estruturada contendo dois campos com o mesmo rótulo. A Operação Junção os renomeou exclusivamente.

Para descobrir como eles são chamados agora, você primeiro vê os tipos de dados da matriz unida. Você os acessa através da propriedade .dtype :

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

Se você observar atentamente a saída, notará que os campos originais foram renomeados valores1 e valores2 . Para ver o conteúdo deles, você deve usar esses nomes. Nesse cenário, ambos os campos contêm os mesmos dados, por isso não importa qual deles você escolher:

>>> 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})

Desta vez, os valores dos cheques estão incluídos na sua saída.

Agora que você sabe quais verificações você emitiu e o que foi descontado, convém ver uma lista de cheques que estão atualmente sem castigo:

>>> 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]

Na linha 1, você cria uma lista chamada outstanding_checks usando uma compreensão de lista. Isso conterá uma lista de cheques com um valor id que está na matriz issued_checks, mas não na matriz cashed_checks. Estes ainda estão pendentes.

Novamente, usando o nome do campo, você pode especificar rapidamente os campos a serem usados para comparação e manter seu código altamente legível.

Por conveniência, você extrai os números da sua matriz resultante usando uma segunda compreensão da lista na linha 10.

Para completar, você pode descobrir se houve algum cheques descontados de forma fraudulenta contra sua conta que você não emitiu:

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

A lista vazia produzida mostra que, felizmente, não há nenhum. Ufa!

Lidar com valores -chave duplicados

Antes de ingressar em matrizes Numpy, é importante garantir que não haja valores de chave duplicados. Isso pode causar resultados indesejáveis porque criam relacionamentos um para muitos. Nos casos em que suas chaves duplicadas são válidas, por exemplo, onde você precisa executar uma junção individual, uma alternativa melhor é usar a função merge() em pandas.

O código abaixo usa o arquivo check_list_duplicates.csv. Este arquivo tem a mesma estrutura do arquivo issued_checks.csv que você usou anteriormente, mas há um 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 linhas duplicadas em uma matriz estruturada, você pode usar a função ajudante .find_duplicates() . Esta função requer que você passe uma matriz mascarada, que é uma matriz que pode ter entradas ausentes ou inválidas. Embora sua matriz esteja bem a esse respeito, você precisa convertê -la em uma matriz mascarada antes de passá -la para a função. Você pode ver na linha 19 da saída que há uma linha duplicada - o registro 1344 ocorre duas vezes.

Para se livrar dele, você pode usar a função 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 remover linhas duplicadas, você passa seu emited_checks Array, juntamente com eixo=0 para np.unique() para dizer para remover linhas. Isso cria uma nova matriz e quaisquer linhas duplicadas são removidas, deixando apenas uma instância. Se você olhar para a saída, verá a linha 1344 agora aparece apenas uma vez.

Testando suas habilidades: unindo arrays

É hora do seu próximo desafio. Veja se você pode resolver este exercício:

Uma companhia aérea mantém dados sobre seus passageiros em dois arquivos separados: arquivo passengers.csv e passports.csv. Dê uma olhada em ambos os arquivos, que você encontrará nos materiais para download, para se familiarizar com seu conteúdo. Agora, use suas habilidades para resolver as seguintes tarefas:

Tarefa 1: Produza um array estruturado contendo o nome, sobrenome e nacionalidade de cada passageiro.

Tarefa 2: Determine se há algum passageiro na lista que não tenha passaporte.

Tarefa 3: Determine se há algum passaporte na lista que não pertença a algum passageiro.

Uma solução possível para a Tarefa 1 é:

>>> 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})

Você começa criando duas listas, passageiro_dtype e pasport_dtype , como mostrado nas linhas 4 e 10. Essas tuplas de armazenamento serão usadas para definir os tipos de dados de cada coluna Nas matrizes estruturadas que manterão os dados do arquivo. Cada tupla consiste em um nome de campo e um tipo de dados. Você deve se lembrar que i8 Especifica um número inteiro, enquanto u20 define uma string de vinte caracteres.

Nas linhas 16 e 26, você lê os dados de cada arquivo nos passageiros e Passports Matrizes, respectivamente. Você também usa np.unique() para garantir que os registros duplicados sejam removidos. Se você esquecer de fazer isso, verá alguns valores n/a aparecem nos seus resultados. Observe que você está pulando a linha superior de cada arquivo porque ele contém títulos.

Na linha 36, você une os arrays passengers e passports usando seus campos passenger_no como a chave de junção. Esta é uma junção interna, o que significa que apenas passageiros com passaporte são incluídos na saída.

Na linha 43, você exibe os detalhes do voo necessários. A saída mostra os nomes dos passageiros do arquivo passengers.csv e suas nacionalidades do arquivo passports.csv. Essa abordagem aproveita a conveniência e a clareza de ter nomes de campos para extrair os dados.

Uma solução possível para a Tarefa 2 é:

>>> 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 essa pergunta, você usa uma compreensão de lista que atravessa cada passageiro_no em seus passageiros Matriz e retorna apenas aqueles que não aparecem no seu PASSAPORTS Array. Nesse caso, o passageiro 14 não forneceu detalhes do passaporte.

Uma solução possível para a Tarefa 3 é:

>>> 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 essa pergunta, você usa novamente uma compreensão da lista, mas desta vez ele passa por cada passageiro_no em seu passaports Array e retorna apenas aqueles que não Em> aparecem em seus passageiros Array. Nesse caso, o passaporte pertencente ao passageiro inexistente 21 é revelado.

Na próxima seção, você aprenderá como analisar dados hierárquicos.

Exemplo NumPy 3: Análise e gráficos de dados hierárquicos

Dados hierárquicos são dados que consistem em diferentes níveis, com cada nível vinculado aos imediatamente acima e abaixo. Geralmente é desenhado usando um diagrama de árvore e diferentes níveis são frequentemente descritos como tendo um relacionamento pai-filho.

Por exemplo, você pode ter uma organização com vários departamentos, com cada departamento contendo vários funcionários. Há uma relação hierárquica entre a organização, seus dados departamentais e os dados dos funcionários que trabalham em cada departamento.

As matrizes estruturadas são adequadas para unir dados hierárquicos porque permitem que você faça referência a dados por etiquetas. Para usar o Numpy com dados hierárquicos, você pode consolidá -los em uma única matriz.

Nesta seção, você analisará a análise de um portfólio de ações. Um portfólio de ações é o nome dado à cobrança de ações detidas em uma variedade de empresas. Criar um portfólio é uma estratégia inteligente para os investidores, pois ajuda a espalhar o risco de investimento. A idéia é que as perdas incorridas em algumas ações sejam compensadas por ganhos em outros.

Seu portfólio de ações contém dados hierárquicos porque consiste em vários investimentos, cada um com sua própria coleção de movimentos diários de preços. Ao analisar esses dados, você pode ver o desempenho do seu portfólio.

Criando a matriz em branco

Os dados utilizados nesta seção simulam alguns dados hierárquicos. Suponha que você mantenha um arquivo que contenha uma lista das várias empresas em que você possui ações. Neste exemplo, você encontrará essas informações no arquivo portfolio.csv incluído em seus downloads. Aqui é mostrado abaixo:

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

A coluna Empresa contém os nomes das empresas, enquanto a coluna Setor contém os setores aos quais a empresa pertence.

Todos os dias, ao longo de uma semana, você baixa os preços das ações de cada uma das empresas nas quais tem interesse e os adiciona a uma matriz NumPy estruturada chamada portfólio. Você encontrará os dados de preços de cada dia em uma série de arquivos separados chamados share_prices-n.csv, onde n é um número entre um e cinco. Por exemplo, share_prices-1.csv contém os preços de segunda-feira, share_prices-2.csv os preços de terça-feira e assim por diante.

Um exemplo de arquivo de preços de ações é mostrado abaixo:

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

Neste arquivo share_prices-1.csv , existem duas colunas. A coluna Company exibe os nomes das empresas, enquanto a coluna mon mostra os preços de cada empresa compartilham na segunda -feira. O restante dos arquivos segue um padrão semelhante, além de suas colunas diárias, que são diferentes.

Para analisar esses dados, seu array portfólio precisará de sete campos. Além do nome da empresa e do setor a que pertence, você também precisará de cinco campos para conter os preços diários de cada empresa. Os dois primeiros campos serão strings, enquanto o restante serão flutuantes.

O código mostrado abaixo cria o array portfolio inicial:

>>> 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, você usará as bibliotecas numpy e pathlib, então importe-as nas linhas 1 e 2.

Para definir os nomes dos campos e os tipos de dados de cada campo em seu array portfolio, você começa criando as três listas mostradas nas linhas 4, 5 e 6. A lista days_dtype contém uma série de tuplas, uma para cada valor no array days que você criou na linha 4, com um tipo de dados f8, que representa um número de ponto flutuante. A lista company_dtype contém definições para os dados da empresa do arquivo portfolio.csv.

Para criar o objeto de tipo de dados real que definirá os tipos de dados do array portfólio, você concatena as listas company_dtype e days_dtype juntas. Em seguida, você converte o resultado em um objeto dtype usando a função np.dtype() conforme mostrado na linha 8.

O objeto dtype é então alimentado no função np.zeros() como seu parâmetro dtype . Você também configura sua matriz com uma forma de (6) para fornecer uma linha separada para os dados de cada compartilhamento. Isso produz uma matriz contendo seqüências vazias nos dois primeiros campos e zeros no restante, como mostrado na saída.

Preparando a matriz

Agora que você criou um array grande o suficiente para armazenar todos os dados necessários, a próxima etapa é começar a preenchê-lo. Para começar, você adicionará os detalhes das empresas armazenadas em 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')])

Mais uma vez, você usa a função loadtxt() , desta vez para adicionar dados do portfolio.csv em uma matriz estruturada chamada empresas . Observe que .rehape ((6,)) foi usado na linha 6 para fornecer à matriz a mesma forma que a matriz que você criou anteriormente. Isso é necessário para inserir empresas no portfólio .

Linha 8 é onde a inserção ocorre. Os dois campos de matriz são inseridos como os campos da empresa e saída.

Tudo o que resta a ser feito é a adição dos vários preços diários das ações. O código para fazer isso é mostrado abaixo:

>>> 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 começar, você cria uma lista definindo os dois campos dentro de cada um dos arquivos share_prices- diários. Para adicionar esses dados ao seu array portfolio principal, você mais uma vez cria um loop que itera sobre cada um deles, mas desta vez, você faz isso usando o zip() integrado do Python função. Os arquivos serão processados na ordem que você aprendeu anteriormente.

Nesse caso, você passa zip() a lista Days que você definiu anteriormente, bem como cada um dos arquivos que você está processando. Isso produz uma série de tuplas, uma para cada dia e par de arquivos encontrados. Todos os dias na tupla são atribuídos à variável dia do loop, enquanto cada arquivo é atribuído à variável csv_file .

Dentro do corpo do loop, cada arquivo é lido novamente usando np.loadtxt() , mas desta vez, em vez de manter o arquivo inteiro, apenas seu dia Os dados do campo são armazenados . Na primeira vez em torno do loop, esses dados são inseridos no campo portfólio mon e assim por diante. Você fez isso atribuindo os dados lidos no portfólio [dia] para cada dia diferente.

A versão final do array contém o nome da empresa, bem como o setor ao qual pertence. Os cinco números finais de cada registro são os preços das ações de segunda a sexta-feira em centavos.

Agora que combinou seus dados hierárquicos em uma matriz estruturada, você pode analisá-los usando os nomes dos campos para simplificar. Suponha que você queira extrair uma única empresa para ver mais de perto:

>>> 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')])

Aqui, você extrai uma única empresa localizando-a na coluna empresa. Para fazer isso, você usa portfolio["company"] == "Company_C", que seleciona as linhas cujos valores da coluna company correspondem a "Company_C". Este método é muito mais intuitivo do que selecionar linhas com indexação.

Da mesma forma, se você quiser ver como as empresas em seu portfólio se apresentam na sexta -feira, também pode selecionar esses números:

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

Para visualizar os registros de tecnologia, você usa portfolio["sector"] == "technology". Então, para ver apenas os registros de sexta-feira, você os filtra usando ["fri"].

Suponha que você possua 250 ações de cada uma de suas empresas de tecnologia. Você pode querer ver quanto dinheiro eles valem no final da semana:

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

Para fazer isso, você usa as técnicas que já aprendeu para selecionar os números de sexta-feira para cada uma das partes de tecnologia do seu portfólio. Em seguida, você multiplica os números por 250, que é o número de ações que você possui. Por fim, você multiplica o resultado por 0,01 para transformar os valores em dólares, lembrando que os preços das ações são cotados em centavos. Se você quiser o patrimônio líquido, tudo que você precisa fazer é usar sum():

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

A parte de tecnologia do seu portfólio vale US$525,00.

Como você pode ver, o uso de arrays estruturados permite acessar os dados de uma forma altamente intuitiva. Para ir mais longe, você também pode usar essa abordagem ao usar uma matriz estruturada como base para um gráfico Matplotlib. Isso é o que você fará a seguir.

Traçando os dados

Suponha que você queira exibir a análise da parte tecnologia do seu portfólio em um gráfico. Novamente, como você está trabalhando com um array estruturado, o código se torna 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()

Primeiro você cria o array auxiliar tech_mask. A seguir, você cria dois arrays que serão usados no gráfico. O array tech_sector, definido na linha 4, contém os nomes das empresas de cada empresa tech_sector. A matriz tech_value, definida na linha 5, contém as avaliações de sexta-feira para cada empresa tech_sector.

As linhas 7 a 10 criam o gráfico de barras. O eixo x contém os nomes das empresas tech_sector usadas para criar as barras, enquanto o parâmetro height contém suas avaliações. Os valores tech_value são os que são plotados. As linhas 11, 12, 13 e 14 produzem rótulos para os eixos e dão um título ao gráfico. Finalmente, o gráfico é exibido.

Se você executar o código acima em um notebook Jupyter, não precisará usar plt.show() . Se você o executar no Python Repl padrão, as referências de objeto serão mostradas após as linhas 11, 12 e 13. Você pode simplesmente ignorá -las e elas foram removidas da saída para maior clareza.

O gráfico resultante é mostrado abaixo:

Como você pode ver, Company_A parece estar melhor dos dois, embora apenas um pouco.

Testando suas habilidades: analisando e mapeando dados hierárquicos

Antes de seguir em frente, aqui está o seu terceiro desafio:

Você foi solicitado a agrupar as temperaturas médias mensais diárias de cada mês do ano para análise. Os dados são armazenados nos arquivos london_temperatures.csv, new_york_temperatures.csv e rome_temperatures.csv.

Usando as habilidades que você aprendeu, crie uma matriz weather_data estruturada com nomes de campos e tipos de dados sensatos que contenham quatro valores de dados para cada mês. O primeiro deles deverá conter o mês, enquanto os outros três deverão conter a temperatura do mês de cada cidade.

Use sua matriz estruturada para plotar a cada temperatura mensal para as três cidades em um gráfico de linha.

Uma solução possível para criar a matriz é:

>>> 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')])

Você começa criando antecipadamente um array de tamanho correto para aceitar os dados. Como antes, você usa listas e compreensões de lista para definir cada campo e seu tipo de dados antes de produzir a matriz weather_data nas linhas 9 e 10. Você pode ver o resultado nas linhas 11 a 14. Observe que cada < O campomês é inicializado com uma string vazia, enquanto cada campo inteiro é definido como 0.

Na linha 17, você começa a percorrer as cidades individuais. Para Londres, você lê o arquivo london_temperatures.csv em um array temps e, na linha 23, atribui seus dados ao mês e ao london em seu array weather_data. Os dados de Nova Iorque e Roma são lidos e adicionados da mesma maneira. Os rótulos dos meses são substituídos para cada cidade. Mas tudo bem, desde que sejam iguais em todos os conjuntos de dados.

As linhas 27 a 32 mostram o array completo. Uma possibilidade para o gráfico de linha poderia ser a seguinte:

>>> 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 o gráfico, você plota os meses ao longo do eixo x e cria uma linha separada para cada uma das temperaturas das três cidades ao longo do eixo y. Em seguida, você adiciona rótulos a ambos os eixos do gráfico e adiciona um título e uma legenda.

Sua saída final deve ser semelhante a esta:

Como você pode ver, das três cidades, Roma tem temperaturas consistentemente mais altas.

Na seção final, você aprenderá sobre uma das principais eficiências do NumPy e como escrever funções que tirem proveito dela.

Exemplo NumPy 4: Escrevendo suas próprias funções vetorizadas

Uma das eficiências do Numpy é sua capacidade de executar cálculos em matrizes inteiras sem que o programador tenha que escrever loops lentos que se voltam manualmente em cada linha ou elemento. Em vez disso, Numpy usa o idioma C subjacente para executar o cálculo em toda a matriz. Isso é conhecido como vetorização .

Nesta seção final, você trabalhará com o arquivo full_portfolio.csv mostrado abaixo:

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

Esses dados lhe parecerão familiares, pois são uma fusão dos arquivos usados na seção anterior. O título de cada coluna tem o mesmo significado de antes.

O código abaixo mostra a vetorização em ação:

>>> 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])

Depois de construir a matriz estruturada portfólio, você decide ver o desempenho de suas ações durante a semana. Para fazer isso, você escolhe duas matrizes – uma contendo os preços das ações de segunda-feira e outra contendo os de sexta-feira. Para visualizar a alteração semanal, subtraia os preços de segunda-feira dos preços de sexta-feira.

Observe na linha 21 que, embora você esteja subtraindo uma matriz do outro, Numpy subtraia cada elemento individual das matrizes sem que você precise escrever código que atravessa -os individualmente. Isso é vetorização.

Agora suponha que você receba um bônus extra de 10% sobre ações que subiram mais de 1% em valor durante a semana que você está analisando. Para saber o seu lucro incluindo o bônus, você precisa considerar dois casos: as ações que recebem o bônus e as que não recebem. Para fazer isso, você pode tentar o seguinte:

>>> 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()

Nas linhas 1 a 5, você definiu uma função chamada profit_with_bonus() que retornará seu lucro com base em como o preço de sua ação subiu durante a semana. Se o lucro for superior a 1%, você adiciona outros 10% como bônus.

Quando você chama sua função na linha 7, ela gera uma exceção valueError porque não pode interpretar as matrizes que você passou para ela. Só funcionará se você passar por dois números. Para fazer com que essa função funcione com as matrizes, continue lendo.

Adicionando funcionalidade de vetorização com np.vectorize()

Para fazer com que sua função fuct_with_bonus() funcione com as matrizes, você precisará transformá -lo em uma função vetorizada . Uma maneira de fazer isso é usar a função np.Vectorize() . Esta função retornará uma versão da sua função original, mas que leva as matrizes como entrada em vez de escalares.

O código mostrado abaixo transforma sua função em uma função vetorizada:

>>> 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 transformar sua função profit_with_bonus() em uma versão vetorizada, você a passa para a função np.vectorize() na linha 7. A versão vetorizada é então atribuída ao np.vectorize(). código>vectorized_profit_with_bonus nome.

Na linha 8, você chama a nova função e passa para ela os arrays que deseja analisar. Neste exemplo, o resultado mostra o seu lucro, incluindo o bônus adicionado. Se você comparar os números com o cálculo direto do lucro feito anteriormente, notará que recebeu um bônus por suas ações na primeira e na terceira empresa. Observe que você não precisou alterar a função original nas linhas 1 a 5.

Um último ponto sobre o uso de np.vectorize() que você deve observar é que a função original ainda está disponível caso você precise dela:

>>> in_profit(3, 5)
2.2

Desta vez, um único valor é retornado. Como 5 - 3 é 2 e 2 é mais que 1% de 3, você adiciona 10% a obtenha 2.2.

Adicionando funcionalidade de vetorização com @np.vectorize

Como você acabou de ver, a função np.vectorize() cria uma segunda função, o que significa que a versão original ainda estará disponível se você precisar usá-la. Como alternativa, você poderia 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 ])

Ao aplicar @np.vectorize à sua função na linha 1, a função profit_with_bonus() é transformada em uma versão vetorizada. Esta nova versão funciona da mesma maneira que a função vectorized_profit_with_bonus() que você viu anteriormente. Para usá-lo, chame profit_with_bonus() como faria normalmente, mas certifique-se de passar seus arrays para ele. Isso retornará a mesma matriz de lucro de antes.

Ao contrário da versão criada pela função np.Vectorize() , a versão escalar original do lucro_with_bonus() não existe mais. No entanto, se você passar por dois escalares, ainda funcionará, mas retornará o resultado como uma matriz:

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

Como você pode ver, a saída é um array desta vez.

Usando a funcionalidade de vetorização existente com np.where()

Muitas das funções NumPy disponíveis já suportam vetorização. É uma boa ideia consultar a documentação para ver se já existe uma função disponível para atender às suas necessidades. Para encontrar seu lucro com as ações, por exemplo, você poderia ter usado a função np.where() para contabilizar tanto o caso de bônus quanto o caso de lucro regular em vez de escrever sua própria função:

>>> 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 ])

Desta vez, você passa sua condição para np.where() . Diferentemente da sua função original fuct_with_bonus() , .where() suporta a vetorização nativamente. Nesse caso, você usa .where() Ao passar uma comparação entre duas matrizes. Você também passa por duas matrizes calculadas. O primeiro representa o lucro com um bônus adicionado de 10%, enquanto o segundo é o lucro claro.

Agora, np.where() avaliará a condição para cada elemento e escolherá o elemento correspondente de uma das duas matrizes. Se a condição for true , ele usará o elemento da primeira matriz e, se for false , ele escolhe o elemento do segundo.

Testando suas habilidades: escrevendo uma função vetorizada

É hora de mergulhar no desafio final do exercício. Você está quase terminando.

Você foi solicitado a criar dois arrays contendo as temperaturas mínima e máxima do array weather_data que você criou em seu último desafio. Se ainda não tiver feito isso, construa o array weather_data executando o primeiro bloco de código na solução do exercício Analisando e traçando gráficos de dados hierárquicos antes de prosseguir.

Em seguida, escreva uma função chamada find_min_max() que aceite três valores escalares, por exemplo, inteiros, e retorne os valores máximo e mínimo. Agora, use @np.vectorize ou np.vectorize() para aprimorar sua função para trabalhar com arrays NumPy. Certifique-se de poder chamar a versão vetorizada da sua função pelo nome original.

Como ponto de partida, sua função inicial poderia ser algo assim:

>>> 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)

Sua função find_min_max() inicial recebe três valores escalares e retorna os valores mais baixos e mais altos. Se você tentar usar dados do seu array weather_data, não 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 você pode ver, você gerou uma exceção ValueError. Você precisará atualizá-lo para funcionar com arrays.

Uma possível solução usando @np.vectorize pode 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 a versão vetorizada da sua função find_min_max(), você a decora com @np.vectorize. Para chamar a função vetorizada, você passa diretamente as três matrizes de dados meteorológicos. O decorador permite entender arrays como entradas e produzi-los como saídas. A função usa as funções integradas min() e max() para analisar cada um dos três elementos de cada mês e retorna o resultado em duas matrizes.

Uma solução alternativa poderia ser voltar à função original e usar 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 = 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 a versão vetorizada de sua função find_min_max(), você a passa para np.vectorize() e atribui a saída de volta à variável find_min_max, você pode então chamar a versão vetorizada usando seu nome original. Para chamar essa versão vetorizada, você passa diretamente as três matrizes de dados meteorológicos.