Em um post anterior, apresentamos como a vetorização de códigos Python é uma estratégia excelente para melhorar seus desempenhos e também para torná-los mais claros. A vetorização em Python pode substituir os loops tradicionais em inúmeras situações. Isso proporciona um ganho considerável de velocidade nos códigos Python. A vetorização é particularmente apropriada para situações com quantidades de dados grandes. Nessas situações, loops tradicionais tendem a ser opções lentas e computacionalmente caras.
NumPy para Vetorizar Códigos Python
NumPy é a biblioteca tradicional para vetorização em Python. Mas, para quem não tem familiaridade com ela, a mudança pode ser desafiadora. Por isso, nesse post, nos concentraremos em alguns exemplos de aplicações tradicionais de loops e como substituí-los por comandos NumPy.
Para quem não possui NumPy instalado, a instalação com pip é feita com o comando:
pip install numpy
Para instalação através do conda:
conda install anaconda::numpy
Criação de Listas e Vetores
Quando se fala em vetorizar um código Python, o primeiro passo é transformar listas e outras estruturas de dados em vetores ou, mais precisamente, em arrays NumPy. O processo é bem simples:
minha_lista = [1, 2, 3, 4, 5, 'a', 'b', 'c']
meu_array = np.array(minha_lista) # transforma lista em array
Evidentemente, um array também pode ser facilmente convertido em lista com a função tolist().
meu_array = np.array([1, 2, 3, 4, 5])
minha_lista = meu_array.tolist()
Operações Aritméticas Tradicionais
Uma das substituições mais eficientes que pode ser feita com NumPy é a geração de arrays através de operações aritméticas simples como somas ou exponenciações. Nos exemplos abaixo, comparamos as implementações tradicionais com listas com os comandos NumPy que podem ser usados para substituí-las.
# exemplo de lista formada for 10 valores de x ao quadrado
# lista criada com loop
minha_lista = [x**2 for x in range(10)]
# lista criada de forma vetorizada com NumPy
meu_array = np.arange(10)**2
# exemplo de lista criada com operação de soma - note que o intervalo tem passo igual a 2
# lista criada com loop
minha_lista = [x + 10 for x in range(0, 10000, 2)]
# lista criada de forma vetorizada com NumPy
meu_array = np.arange(0, 10000, 2) + 10
# exemplo de lista criada com multiplicação
# lista criada com loop
minha_lista = [2*i for i in range(1000000)]
# lista criada de forma vetorizada com NumPy
meu_array = np.multiply(np.arange(1000000), 2)
Listas de Números Randômicos
Listas de números randômicos são facilmente geradas em NumPy através de várias funções. Todas elas tendem a ser mais eficazes em comparação com as listas tradicionais criadas com loops. Mas essa eficácia é maior para grandes conjuntos de dados. Abaixo, mostramos um exemplo com a função np.random.randint(). Ela é específica para a geração de números randômicos inteiros.
import random # para lista tradicional
# lista criada com loop - no exemplo, geramos 1000 valores randômicos num intervalo de 1 a 100
random_nb = [random.randint(1, 100) for _ in range(1000)]
# lista criada de forma vetorizada com NumPy
np_random_nb = np.random.randint(1, 100, size=1000)
Operações com Duas Listas
Frequentemente, em códigos com muitos cálculos numéricos, operações com mais de uma lista são executadas repetidas vezes. Essas operações podem ser facilmente substituídas por formas vetorizadas mais eficazes. Veja alguns exemplos.
# operações com duas listas - elemento a elemento
# Exemplo de multiplicação
# forma tradicional com loop
res = sum(x * y for x, y in zip(lista1, lista2))
# forma vetorizada
np_res = np.dot(array1, array2)
# Exemplo de soma
# forma tradicional com loop
res = sum(x + y for x, y in zip(lista1, lista2))
# forma vetorizada
np_res = array1 + array2
Um exemplo com multiplicação matricial.
# operações com duas listas - multiplicação matricial
# forma tradicional com loop
matriz_a = [[1, 2], [3, 4]]
matriz_b = [[5, 6], [7, 8]]
rows_a, cols_a = len(matriz_a), len(matriz_a[0])
rows_b, cols_b = len(matriz_b), len(matriz_b[0])
# Precisa verificar se os tamanhos das matrizes permitem multiplicação matricial
if cols_a != rows_b:
print("Tamanho inapropriado para multiplicação")
else:
res = [[0 for _ in range(cols_b)] for _ in range(rows_a)]
# forma vetorizada com NumPy
matriz_a = np.array([[1, 2], [3, 4]])
matriz_b = np.array([[5, 6], [7, 8]])
np_res = np.multiply(matriz_a, matriz_b)
Condicionais
Arrays NumPy são facilmente usados com condicionais. Condicionais do tipo if são implementados em NumPy de forma compacta, como mostrado abaixo.
# Condicional do tipo if
# forma tradicional com loop
arr = [-1, 3, -5, 2, -4, 6]
res = [x for x in arr if x > 0]
# forma vetorizada
array = np.array(arr)
np_res = array[array > 0]
O código acima retorna os valores de um array especificados por uma condição. No exemplo acima, a condição é ser maior do que zero. Em certas situações, não queremos exatamente os valores dos elementos, mas seus índices. No NumPy, isso é feito com o comando np.where(). Veja um exemplo.
# Retorna indices de uma lista a partir de um condicional
# forma tradicional - exemplo: retorna os indices dos valores maiores do que 3
arr = [1, 3, 5, 2, 4, 6]
indices = [i for i, x in enumerate(arr) if x > 3]
# forma vetorizada com NumPy
arr = np.array(arr)
indices = np.where(arr > 3)
O comando np.where() pode ser empregado eficazmente para retornar os índices de um array especificados por um condicional. Tradicionalmente, a mesma operação pode ser feita em códigos Python com enumerate.
Condicionais do Tipo if else
O comando np.where() do NumPy é uma forma concisa e eficiente de realizar condicionais do tipo if else em arrays. O NumPy aproveita as operações vetorizadas para realizar a comparação e a atribuição em elementos de maneira mais otimizada do que as listas tradicionais em códigos Python.
# Exemplo If-else - verifica se um valor é par ou impar
# forma tradicional
numeros = [1, 2, 3, 4, 5]
res = ['Par' if num % 2 == 0 else 'Impar' for num in numeros]
# forma vetorizada
numeros = np.array([1, 2, 3, 4, 5])
np_res = np.where(numeros % 2 == 0, 'Par', 'Impar')
Concluindo
A vetorização é uma forma eficaz de melhorar o desempenho de códigos Python. Ela é particularmente adequada para códigos com operações numéricas repetitivas tradicionalmente executadas com loops. NumPy é a biblioteca mais popular do Python para realizar vetorização. Várias de suas funções podem substituir comandos tradicionais do Python. Portanto, se você deseja melhorar o desempenho de seus códigos, aprender alguns comandos NumPy é uma excelente ideia.