Chapter 2. Introduction to NumPy Flashcards
Em linhas gerais, o que é a biblioteca NumPy e para que serve?
A biblioteca NumPy(Numerical Python) é uma interface para lidar de modo eficiente com armazenamento e tratamento de buffers de dados densos. Os NumPy Arrays formam o core do ecossistema inteiro das ferramentas de data science em python.
a) O que significa ser dinamicamente tipada? Qual o oposto disso?
b) O que significa ser fortemente tipada? Qual o posto disso?
Classifique as linguagens a seguir conforme as definições anteriores: C, Java, GO, Kotlin, Python, JavaScript, Rust
Fonte: ChatGPT
a) Dinamicamente tipada e o oposto:
Dinamicamente tipada: Em uma linguagem dinamicamente tipada, o tipo de uma variável é associado ao valor em tempo de execução, e essa associação pode ser alterada durante a execução do programa. Não é necessário declarar explicitamente o tipo de variável ao criá-la.
Exemplo: Python, JavaScript
Estaticamente tipada: Em uma linguagem estaticamente tipada, o tipo de uma variável é definido em tempo de compilação e não pode ser alterado durante a execução do programa. É necessário declarar explicitamente o tipo de uma variável ao criá-la.
Exemplo: C, C++, C#, Java, Go, Kotlin, Rust.
b) Fortemente tipada e o oposto:
Fortemente tipada: Em uma linguagem fortemente tipada, as operações entre tipos incompatíveis são restritas, e o compilador (ou interpretador) impõe regras rigorosas sobre como os diferentes tipos podem ser combinados e operados. Conversões explícitas geralmente são necessárias para operar entre tipos diferentes.
Exemplo: C, C++, C#, Java, Go, Kotlin, Python, Rust.
Fracamente tipada: não tem um tipo fixo, você pode atribuir valores de diferentes tipos a uma variável durante a execução do programa e as operações podem ser realizadas entre valores de tipos diferentes sem a necessidade de conversões explícitas.
Exemplo: JavaScript.
Classificação:
C: Estaticamente tipada, Fortemente tipada.
C++: Estaticamente tipada, Fortemente tipada.
Java: Estaticamente tipada, Fortemente tipada.
Go: Estaticamente tipada, Fortemente tipada.
Kotlin: Estaticamente tipada, Fortemente tipada.
Python: Dinamicamente tipada, Fortemente tipada.
JavaScript: Dinamicamente tipada, Fracamente tipada.
Rust: Estaticamente tipada, Fortemente tipada.
Obs: muitas linguagens possuem inferência de tipos. Isso não faz dela uma linguagem dinâmica ou fracamente tipada!
Por que um Integer em Python não possui a mesma performance que um Integer em C? Qual o trade-off?
Porque para que você tenha flexibilidade e liberdade (programação dinâmica), é necessário criar estruturas intermediárias entre a declaração da variável e a alocação dos bytes em memória. Nesse sentido, um Integer em Python armazena diversas informações além do ponteiro para a struct de integer em C, que irá alocar os bytes em memória efetivamente. O Trade-Off é performance por flexibilidade.
Qual a diferença de uma lista para um NumPy array?
Uma lista em python, essencialmente, armazena um bloco de objetos python, como o explicado anteriormente, para permitir a flexibilidade de tipos. Isso significa que cada elemento da lista armazena diversas informações além do ponteiro para a estrutura em C que aloca na memória.
Os NumPy Arrays corrigem isto através de tipos fixos, onde cada posição na lista é um ponteiro para a estrutura em C de alocação na memória.
Portanto, NumPy Arrays só operam com um tipo de estrutura de dados. Se você tentar realizar operações com estruturas de dados distintas no mesmo array, ele irá realizar um Upcasting e converterá todos os elementos para um tipo em comum.
Listas em Python podem ser multidimensionais? Qual a diferença para NumPy Arrays?
Listas em Python podem armazenar outras listas em python, simulando um vetor multidimensional. Entretanto, não é possível realizar operações vetorizadas sem iterar por todos os elementos. Em um NumPy array, por exemplo, para somar 1 a cada elemento, basta instruir:
novo_array = antigo_array + 1
Enquanto que, para as listas default, seria necessário iterar em cada elemento para somar 1.
Quais os 3 principais atributos de um NumPy array?
ndim, shape e size
Exemplo:
x3 = np.random.randint(10, size=(3, 4, 5))
x3 ndim: 3
x3 shape: (3, 4, 5)
x3 size: 60
Ao realizar uma operação de slice de array, como meu_array[:3], qual a diferença entre listas default e ndarray?
Em listas default, será retornado uma copia da lista original, enquanto nos ndarrays será retornado uma view do array original.
Portanto, ao alterar o valor de um slice ndarray, isto afetará o array original.
Como funciona join e split de ndarrays multidimensionais?
Para concatenar ndarrays de mesma dimensão, basta utilizar nd.concatenate(). Para concatenar ndarrays de diferentes dimensões, será necessário escolher nd.vstack() quando for necessário implementar dimensões na vertical(linhas), ou nd.hstack quando for necessário implementar dimensões na horizontal(colunas).
Para splittar um ndarray de única dimensão, basta utilizar o nd.split() e passar os pontos de quebra. Para splittar um ndarray multidimensional, pode-se utilizar nd.vsplit (para quebrar um array verticalmente) ou nd.hsplit(para quebrar um array horizontalmente).
Defina em linhas gerais o que são operações vetorizadas em NumPy, por que e como são utilizadas.
Operações Vetorizadas são, literalmente, operações aritméticas que podem ser realizadas entre vetores com uma performance muito superior à abordagem de iterar com “for” padrão do python. O NumPy “joga” a execução desse loop para a compilação.
Exemplos de operações vetorizadas:
x = np.arange(4)
print(“x =”, x)
print(“x + 5 =”, x + 5)
print(“x - 5 =”, x - 5)
print(“x * 2 =”, x * 2)
print(“x / 2 =”, x / 2)
print(“x // 2 =”, x // 2) # floor division
print(“-x = “, -x)
print(“x ** 2 = “, x ** 2)
print(“x % 2 = “, x % 2)
out:
x = [0 1 2 3]
x + 5 = [5 6 7 8]
x - 5 = [-5 -4 -3 -2]
x * 2 = [0 2 4 6]
x / 2 = [ 0. 0.5 1. 1.5]
x // 2 = [0 0 1 1]
-x = [ 0 -1 -2 -3]
x ** 2 = [0 1 4 9]
x % 2 = [0 1 0 1]
Também é possível somar, subtrair, etc entre dois ou mais vetores.
x = [0,1,2]
y = [4,5,6]
print(x+y)
out:
[4,6,8]
O que são UFuncs ?
UFuncs (Universal Functions) são funções do universo da matemática implementadas por diversas bibliotecas python, como Numpy, SciPy, Pandas, etc. (verificar se é apenas NumPy e SciPy que implementam Ufuncs)
O que é uma operação de reduce?
Uma operação de reduce consiste em aplicar, reiteradamente, alguma ação em um array até chegar em um resultado final. Um exemplo simples de reduce seria uma somatória de todos os elementos de um array.
Uma outra maneira de realizar operações entre vetores é através de broadcasting. Explique o que é broadcasting e por que leva esse nome.
Broadcasting é a habilidade do numpy de realizar operações matemáticas entre vetores e matrizes de diferentes dimensões, operações essas que, por definição matemática, exigem que os vetores ou matrizes envolvidos sejam transformados em novos durante o cálculo (por isso há “casting” no nome do conceito). NumPy consegue fazer essas operações com muita eficiência porque não cria de fato esses vetores intermediários, apenas segue as regras matemáticas e aloca o resultado final na memória.
*Obs: as vezes é necessário fazer alguns reshapes na mão antes de realizar a operação para não dar erro. Exemplo: ao somar uma matriz (3,2) com uma (1,3), normalmente “giramos” a matriz (1,3) para se transformar em (3,1) e continuamos a partir daí. O broadcasting não faz esse “giro” automaticamente, portanto, é necessário realizar um a[:, np.newaxis].shape antes.
O livro cita uma operação comum de broadcasting que é a centralização de arrays, utilizando a função mean. Mas não consegui compreender matematicamente o que isso significa.
“Imagine you have an array of 10 observations, each of which consists of 3 values. Using the standard convention (see “Data Representation in Scikit-Learn” on page 343), we’ll store this in a 10×3 array:
In[17]: X = np.random.random((10, 3))
We can compute the mean of each feature using the mean aggregate across the first dimension:
In[18]: Xmean = X.mean(0)
Xmean
Out[18]: array([ 0.53514715, 0.66567217, 0.44385899])
And now we can center the X array by subtracting the mean (this is a broadcasting operation):
In[19]: X_centered = X - Xmean
To double-check that we’ve done this correctly, we can check that the centered array has near zero mean:
In[20]: X_centered.mean(0)
Out[20]: array([ 2.22044605e-17, -7.77156117e-17, -1.66533454e-17])
To within-machine precision, the mean is now zero. “
Para que serve a biblioteca matplotlib?
A biblioteca Matplotlib oferece funções para representar funções bidimensionais visualmente. O livro, nesse capítulo, cita um exemplo realizando broadcastings e operações vetorizadas e plota no gráfico através do matplotlib.
*a saber:
In[21]: # x and y have 50 steps from 0 to 5
x = np.linspace(0, 5, 50)
y = np.linspace(0, 5, 50)[:, np.newaxis]
z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)
In[22]: %matplotlib inline
import matplotlib.pyplot as plt
In[23]: plt.imshow(z, origin=’lower’, extent=[0, 5, 0, 5],
cmap=’viridis’)
plt.colorbar();
O que são operações de comparação vetorizadas e máscaras booleanas?
*OBS: isso caiu bastante em provas recentes
Operações de comparação são UFuncs que comparam elemento-a-elemento(com eficiência superior ao “default for’) e retornam um array booleano. Máscaras boolenas é a utilização desses arrays booleanos para filtrar um array quando se quer extrair, modificar ou manipular dados baseado em critérios.
Antes de exemplificar máscaras booleanas, vamos às operações de comparação com vetores:
In[5]: x < 3 # less than
Out[5]: array([ True, True, False, False, False], dtype=bool)
In[11]: (2 * x) == (x ** 2)
Out[11]: array([False, True, False, False, False], dtype=bool
In[17]: np.sum(x < 6, axis=1) # how many values less than 6 in each row?
Out[17]: array([4, 2, 2])
In[18]: np.any(x > 8) # are there any values greater than 8?
Out[18]: True
In[22]: np.all(x < 8, axis=1) # are all values in each row less than 8?
Out[22]: array([ True, False, True], dtype=bool)
In[23]: np.sum((inches > 0.5) & (inches < 1))
Out[23]: 29
Agora, exemplos de máscaras booleanas:
In[28]: x[x < 5]
Out[28]: array([0, 3, 3, 3, 2, 4])
Note que ao utilizar a máscara, o vetor retornado não é mais um vetor booleano, e sim um array de dados filtrados pelo critério da máscara.