# TD3 : Numpy
## Partie 1 : Approche naïve du calcul scientifique en Python
Le fichier `basic_func.py` vous est fourni et il contient une fonction ainsi qu'un `main` :
```python
import numpy as np
import time
def average(a):
"""
Compute the average value of ``a``
Parameters
----------
a : ndarray
Input array.
Returns
-------
avg : int
The average value of ``a``
"""
average = 0
for i in range(a.shape[0]):
average += a[i]
return average/a.shape[0]
if __name__ == "__main__":
rand_array_1 = np.random.randint(0, 10000, 1000000)
t0 = time.time()
average_value = average(rand_array_1)
print(time.time() - t0)
t0 = time.time()
average_value = np.mean(rand_array_1)
print(time.time() - t0)
```
Questions :
- Que fait la fonction `average` ?
- Que font les lignes du `main` ?
- Quels résultats obtient-on ?
- Qu'en déduire ?
## Partie 2 : Généralités sur les tableaux `numpy`
### Exercice 1
Dans la suite de cet exercice, le tableau de données `foo` sera manipulé. Il est créé comme suit :
```python
foo = np.random.randint(
0,
10,
(np.random.randint(50, 60), np.random.randint(42, 69))
)
```
1. Que fait cette ligne ?
**Réponse :** Cette ligne crée une matrice d'entiers entre 0 et 10 de taille aléatoire : entre 50 et 59 lignes et entre 42 et 68 colonnes.
2. Comment connaître le **nombre de lignes** d'un tableau ?
**Réponse :** L'attribut `foo.shape` est un tuple (nb de lignes, nb de colonnes). `foo.shape[0]` renvoie donc le nombre de lignes.
3. Comment connaître le **nombre de colonnes** d'un tableau ?
**Réponse :** `foo.shape[1]` renvoie donc le nombre de colonnes.
4. Comment connaître le **nombre d'éléments** d'un tableau ?
**Réponse :**`foo.size` renvoie la taille d'un tableau (son nombre d'éléments)
5. Comment connaître le **type des éléments** d'un tableau ? Et le changer ?
**Réponse :** L'attribut `foo.dtype` renvoie le type des éléments de `foo`. `foo.astype('float64')` ou `foo.astype(np.float)` permet de convertir les éléments vers un autre type.
6. Que renvoie `foo > 8` ?
**Réponse :** Un tableau de booléens qui est obtenu en appliquant la condition à chaque élément.
7. Comment **extraire les coordonnées** des éléments strictement inférieurs à 5 ?
**Réponse :** La fonction `where(condition)` permet d'extraire les coordonnées des éléments qui vérifient cette condition.
8. Comment **multiplier ces éléments par 2** ?
**Réponse :** On peut accéder aux éléments étant donné une liste de coordonnées `foo[np.where(foo < 5)]`. On peut donc les multiplier en faisant `foo[np.where(foo > 5)] *= 2`.
9. Que font les lignes suivantes ?
```python
bar = foo[:, 0:13:2]
bar_copy = foo[:, 0:13:2].copy()
bar_float = bar.astype(np.float)
baz = foo[4:14,(1,6,11,26)]
```
**Réponses :**
- *ligne 1 :* Récupération de toutes les lignes et une colonne sur deux de 0 à 13.
- *ligne 2 :* Récupération des mêmes informations mais force la copie des données (modifier `bar` ne modifiera pas `foo`).
- *ligne 3 :* Conversion en flottants.
- *ligne 4 :* Récupération de la ligne 4 à 14 et les colonnes 1, 6, 11 et 26.
### Exercice 2
1. Calculer l'hypothénuse de chaque triangle en utilisant le théorème de Pythagore.
**Solution :**
```python
hypothenuse = np.sqrt(short_sides[:,1]**2+short_sides[:,0]**2)
```
2. Créer `triangles` qui rassemble `short_sides` et les hypothénuses.
**Solution :**
```python
triangles = np.column_stack((short_sides, hypothenuse))
```
3. Calculer un des deux angles non droits via **l'arc cosinus**, **l'arc sinus** et **l'arc tangente**.
**Solution :**
```python
acos_tr = np.arccos(triangles[:,0]/triangles[:,2])
asin_tr = np.arcsin(triangles[:,1]/triangles[:,2])
atan_tr = np.arctan2(triangles[:,1], triangles[:,0])
```
4. Vérifier si les valeurs trouvées sont **toutes** égales ?
**Solution :**
```python
np.all(acos_tr==asin_tr)
np.all(acos_tr==atan_tr)
```
5. Vérifier si **quelques** valeurs trouvées sont égales ?
**Solution :**
```python
np.any(acos_tr==asin_tr)
np.any(acos_tr==atan_tr)
```
6. Vérifier si les valeurs trouvées sont **à peu près** égales ?
**Solution :**
```python
np.allclose(acos_tr, asin_tr)
np.allclose(acos_tr, atan_tr)
```
7. Que signifie **à peu près** ?
**Réponse :** Par défaut, la tolérance relative est de 1e-5 et la tolérance absolue de 1e-8 mais peut être modifiée en utilisant la fonction comme suit :
```python
numpy.allclose(acos_tr, asin_tr, rtol=1e-8, atol=1e-10)
```
La fonction vérifie la condition suivante :
```python
numpy.allclose(a, b, rtol=1e-5, atol=1e-8)
# absolute(a - b) <= (atol + rtol * absolute(b))
```
## Partie 3 : Millions de mesures de coquilles
### Préliminaire : Chargement de données
Pour charger vos données :
- Utilisez tout d'abord `np.loadtxt` pour charger un fichier texte dans un tableau numpy.
- Puis `np.save` permet de sauver les données déjà chargées dans tableau dans un format intermédiaire.
- Enfin, vous pouvez charger vos données avec `np.load` pour gagner du temps par rapport à la première étape.
```python
# /!\ A NE FAIRE QU'UNE FOIS
data=np.loadtxt("coquilles.txt")
# /!\ A NE FAIRE QU'UNE FOIS
np.save("coquilles.npy", data)
# A répéter au lancement du programme
data = np.load("coquilles.npy")
```
Le tableau contient les données formattées comme suit :
- Colonne 1 : Identifiant de la coquille (0 à 13400)
- Colonne 2 : Époque d'enregistrement (0 à 196)
- Colonne 3 : Latitude de la coquille
- Colonne 4 : Longitude de la coquille
- Colonne 5 : Stade d'évolution (1 ou 5)
Le début du fichier ressemble à :
```python
[ 0. 0. -0.19542887 49.89834643 1. ]
[ 0. 1. -0.17596363 49.91657752 1. ]
...
```
### Migration de coquilles
---
Pour tracer la trajectoire de la 42e coquille :
- Créer un masque d'indices qui permette de déterminer les positions de la 42e coquille
- Extraire la lattitude et la longitude
- Afficher avec `matplotlib`
**Solution :**
```python
# Extraction des données
mask_42 = data[:, 0] == 41
longi_42 = data[mask_42][:, 2]
lat_42 = data[mask_42][:, 3]
# Affichage
plt.figure()
plt.plot(longi_42, lat_42)
plt.xlabel("Longitude")
plt.ylabel("Latitude")
plt.title("42th scallop trajectory")
```
---
Pour retrouver les coquilles en stage 5 avant le 60e enregistrement il faut recouper deux conditions. On peut recouper deux valeurs booléennes avec un `*` (opération `AND`) ou `+` (opération `OR`).
```python
a = np.array([True, True, True])
b = np.array([True, False, False])
print(a*b)
>>> np.array([True, False, False])
print(a+b)
>>> np.array([True, True, True])
```
**Solution :**
```python
mask_early = (data[:, -1] == 5) * (data[:, 1] < 60)
early = np.unique(data[mask_early][:, 0])
print(early)
```
---
Le point de départ de chaque coquille correspond aux coordonnées à l'étape 0.
**Solution :**
```python
start = data[data[:, 1] == 0][:, 2:4]
```
---
Pour trouver le changement de stage 1 au stage 5 on peut :
- Extraire les stages des coquilles
- Copier en décalant d'un indice les stages (`np.roll`)
- Chercher quand la différence des deux vaut `-4` (donc un changement de stage)
```python
x = np.arange(10)
np.roll(x, 2)
>>> array([8, 9, 0, 1, 2, 3, 4, 5, 6, 7])
np.roll(x, -2)
>>> array([2, 3, 4, 5, 6, 7, 8, 9, 0, 1])
```
Pour pouvoir les trier, la fonction `argsort()` renvoie les indices de manière à ce que le tableau résultant soit trié.
```python
x = np.array([3, 1, 2])
np.argsort(x)
>>> array([1, 2, 0])
```
**Solution :**
```python
stage_col = data[:, -1]
stage_col_roll = np.roll(stage_col, 1)
mask_stage = (stage_col_roll - stage_col) == -4 # !!! parenthèses !!!
stage_change = data[mask_stage]
# trier les coquilles en fonction de leur date de changement d'étape
stage_sorted = stage_change[stage_change[:, 1].argsort()]
print(stage_sorted[:, 0].astype(np.int))
```
---
###### tags: `python`