... | ... | @@ -5,70 +5,95 @@ De toutes façons, il faut savoir lire les données dans un fichier, donc c'est |
|
|
|
|
|
### B2) lire un fichier texte et extraire l'information
|
|
|
|
|
|
Vous trouverez du code déjà écrit dans le fichier Python visu_chronique.py, avec la fonction lecture_qj0_to_ts écrite pour lire des fichiers de débit journalier dans un des formats de la base Hydro2.
|
|
|
Vous êtes invités à vous approprier ce code, vous pouvez tester les instructions dans la console comme on a fait précédemment, et essayer de réécrire votre propre fonction.
|
|
|
Vous trouverez du code déjà écrit dans le fichier Python visu_chronique.py, avec la fonction lecteur_qj0_to_ts écrite pour lire des fichiers de débit journalier dans un des formats de la base Hydro2.
|
|
|
Normalement, le code est commenté, et vous êtes invités à vous approprier ce code. N'hésitez pas à tester les instructions dans la console comme on a fait précédemment, et essayer de réécrire votre propre fonction.
|
|
|
|
|
|
Il existe des méthodes performantes pour lire des fichiers structurés (colonnes nommées et lignes), notamment dans pandas et seaborn (read_csv). Ici, le fichier à un format qui se prête moyennement bien aux lectures en bloc, et on va donc travailler à l'ancienne.
|
|
|
Au passage, on va manipuler plusieurs notions utiles de Python. .
|
|
|
|
|
|
#### a) Définir un nom de fichier et lire ce fichier
|
|
|
Pratique recommandée pour ouvrir un fichier : "with open(nom_fichier, 'r') as alias_du_fichier"
|
|
|
=> notion de "context manager" qui garantit que le fichier se fermera à la fin du bloc d'instructions, quoi qu'il arrive
|
|
|
Pour mieux comprendre les instructions de "découpage" des lignes (split) et leur transcription en dates et valeurs, manipulez dans la console la chaîne " QJO;B2220010;19680627;7330.000;C;9;"
|
|
|
#### a) Définir le chemin vers le fichier
|
|
|
La fonction **lecteur_qj0_to_ts** prend comme argument un chemin complet, répertoire+fichier.
|
|
|
|
|
|
``` python
|
|
|
ligne_lue_dans_le_fichier = "QJO;B2220010;19680627;7330.000;C;9;"
|
|
|
```
|
|
|
Plusieurs solutions pour définir ce chemin :
|
|
|
**1) je tape le chemin en clair dans le code**, avec des slash / et pas de backslash, qui est un code d'échappement.
|
|
|
|
|
|
Si besoin, on fera un topo sur les dictionnaires, un type de conteneur central en python.
|
|
|
|
|
|
Une fois la fonction écrite, il faut l'exécuter avec un chemin comme argument.
|
|
|
|
|
|
Plusieurs solutions :
|
|
|
1) je tape le chemin en clair dans le code, avec des slash / et pas de backslash :
|
|
|
``` python
|
|
|
# j'entre directement le chemin sous forme de chaîne de caractère comme argument
|
|
|
ts = lecteur_qj0_to_ts("C:/WorkSpace/2020-BaO_MultiDSpa/Migrateurs/Meuse/B2220010_qj_hydro2_2019.txt")
|
|
|
éventuellement :
|
|
|
# deuxième solution, je passe par une variable
|
|
|
chemin_complet = "C:/WorkSpace/2020-BaO_MultiDSpa/Migrateurs/Meuse/B2220010_qj_hydro2_2019.txt"
|
|
|
ts = lecteur_qj0_to_ts(chemin_complet)
|
|
|
2) je demande à l'utilisateur de saisir le nom au clavier
|
|
|
chemin_complet= input("Entrez le chemin complet vers le fichier de données")
|
|
|
|
|
|
```
|
|
|
|
|
|
**2) je demande à l'utilisateur de saisir le nom au clavier**
|
|
|
La commande **input** permet de demander à l'utilisateur de saisir une réponse au clavier
|
|
|
``` python
|
|
|
chemin_complet= input("Entrez le chemin complet vers le fichier de données :")
|
|
|
# j'ai intérêt à vérifier si le fichier existe
|
|
|
from pathlib import Path
|
|
|
# explication : j'utilise la méthode is_file de Path sur l'objet nom_fichier converti en Path
|
|
|
if Path.is_file(Path(chemin_complet)):
|
|
|
ts = lecteur_qj0_to_ts(chemin_complet)
|
|
|
else:
|
|
|
print("chemine ", chemin_complet, " non valide")
|
|
|
3) j'utilise une fonction de Tkinter
|
|
|
from tkinter.filedialog import askopenfilename
|
|
|
print("chemin ", chemin_complet, " non valide")
|
|
|
```
|
|
|
On peut écrire une boucle **while** qui repose la question tant que le chemin n'est pas valide.
|
|
|
|
|
|
**3) j'utilise une fonction de Tkinter**
|
|
|
``` python
|
|
|
from tkinter.filedialog import askopenfilename
|
|
|
chemin_complet= askopenfilename() # ouverture d'une fenêtre explorateur
|
|
|
```
|
|
|
C'est une utilisation "vite fait sur le gaz" d'une fonnctionnalité qui s'utilise normalement avec une interface graphique. En l'absence de cette interface, Tkinter va se débrouiller et ouvrir quand même une interface dans un coin, qu'il faudra refermer.
|
|
|
On verra si nécessaire comment définir proprement une interface, mais cela sort du cadre de ce premier atelier.
|
|
|
|
|
|
#### b) Ouvrir le fichier et le lire ligne par ligne
|
|
|
|
|
|
Un code vous est déjà proposé.
|
|
|
Pratique recommandée pour ouvrir un fichier : "**with** open(nom_fichier, 'r') as alias_du_fichier"
|
|
|
=> **with** introduit un **"context manager"** qui garantit que le fichier se fermera à la fin du bloc d'instructions, quoi qu'il arrive.
|
|
|
Ensuite, on va lire les lignes avec alias_du_fichier.readline().
|
|
|
Dans le code proposé, on va d'abord lire les lignes une à une jusqu'à en trouver une qui contienne un mot-clé.
|
|
|
Ensuite, on lit toutes les autres grâce à une simple boucle **for ligne_lue in alias_du_fichier**, qui lira les lignes une par une jusqu'à la fin. Grâce au context manager, le fichier sera fermé proprement à la fin du bloc de code.
|
|
|
|
|
|
=> figure selon 2 modalités, une fois à partir de listes issues d'un dictionnaire, et une autre fois avec le wrapper de pandas (mon_objet_pandas.plot)
|
|
|
Chaque ligne récupérée sous forme de chaîne de caractères, **string**, et stockée dans une variable que nous avons appelée **ligne_lue**.
|
|
|
|
|
|
b) Extraire l'information utile : "parser" les lignes
|
|
|
#### c) Extraire l'information utile : "parser" les lignes
|
|
|
Il nous faut maintenant extraire l'information des lignes lues.
|
|
|
On a supposé que nos données ne sont pas assez bien bien structurées pour utiliser les instructions modernes de type "read_csv" (du module pandas ou seaborn par exempe).
|
|
|
|
|
|
Ceci est un parsing "à l'ancienne", ligne par ligne. Un peu pénible, certes, cette étape permet de réaliser des vérifications et de décomposer des éléments, ainsi que de manipuler des notions de liste et string.
|
|
|
Pour une lecture "en bloc", avec des données bien structurées, voir les instructions modernes de type "read_csv" (du module pandas ou seaborn par exempe).
|
|
|
On réalise ici un parsing "à l'ancienne", ligne par ligne. Un peu pénible, certes, mais cette étape permet de réaliser des vérifications et de décomposer des éléments, ainsi que de manipuler des notions de liste et string.
|
|
|
|
|
|
Les commentaires du code + la diapo doivent vous guider.
|
|
|
Pour comprendre, définissez une chaîne de caractères (string) nommée ligne dans la console
|
|
|
ligne = "QJO;B2220010;19680705;5280.000;C;9;"
|
|
|
# Effectuez ensuite des tests, par exemple :
|
|
|
"QJO" in ligne #retourne un boléen, vrai/faux (en Python, True.False, avec une majuscule)
|
|
|
ligne.splitee = ligne.split(";") # ligne_splitee est une LISTE de STRINGS
|
|
|
ligne.splitee[1] # élément d'indice 1, est-ce le premier ???
|
|
|
ligne.splitee[2][0:4] # string composé des caractères d'indices 0 à 4 EXCLU de l'élément d'indice 2
|
|
|
valeur_numerique = float(ligne.splitee[3] )
|
|
|
Pour mieux comprendre les instructions de "découpage" des lignes (split) et leur transcription en dates et valeurs, manipulez dans la console la chaîne " QJO;B2220010;19680627;7330.000;C;9;"
|
|
|
|
|
|
# Création d'un dictionnaire
|
|
|
``` python
|
|
|
ligne_lue_dans_le_fichier = "QJO;B2220010;19680627;7330.000;C;9;"
|
|
|
```
|
|
|
Effectuez ensuite des tests, par exemple
|
|
|
``` python
|
|
|
"QJO" in ligne_lue_dans_le_fichier # retourne un booléen, vrai/faux (en Python, True ou False, avec une majuscule)
|
|
|
ligne_splitee = ligne_lue_dans_le_fichier.split(";") # ligne_splitee est une LISTE de STRINGS
|
|
|
ligne_splitee[1] # élément d'indice 1, est-ce le premier ???
|
|
|
ligne_splitee[2][0:4] # string composé des caractères d'indices 0 à 4 EXCLU de l'élément d'indice 2
|
|
|
valeur_numerique = float(ligne_splitee[3] )
|
|
|
```
|
|
|
|
|
|
#### d) stockage de l'information dans un dictionnaire
|
|
|
|
|
|
Si besoin, on fera un topo sur les dictionnaires, un type de conteneur central en python.
|
|
|
Le dictionnaire associe des clés à des valeurs.
|
|
|
On utilisera plusieurs fois des dictionnaires, on va donc découvrir ici une manière de créer un dictionnaire, et les principales instructions utiles.
|
|
|
La **clé** doit être d'un type NON-MUTABLE* (voir notes) ; évidemment, si la clé changeait on perdrait la relation clé-valeur. Les types string, datetime, chiffre... conviennent. Une liste ne convient pas.
|
|
|
La **valeur associée** peut être de n'importe quel type, y compris des conteneurs, et peut en théorie varier d'une clé à l'autre (mais en pratique il vaut mieux se tenir à un même type, ou au minimum à des types qui partagent les méthodes que l'on va devoir leur appliquer dans la suite...
|
|
|
|
|
|
``` python
|
|
|
# définition d'un dictionnaire ; entre accolades
|
|
|
dictionnaire_main = {1:"un", 2:"deux", 3: "trois", 4:"quatre", 5: "cinq"} # dictionnaire avec couples clé: valeur
|
|
|
mon_dictionnaire = dict() # dictionnaire vide
|
|
|
mon_dictionnaire [clef] = valeur # j'ajoute une entrée (j'ajoute des entrées comme je veux)
|
|
|
# la variable clef doit être d'un type NON-MUTABLE (voir notes) ; les types string, datetime, chiffre... conviennent
|
|
|
# la variable valeur peut être de n'importe quel type, y compris des conteneurs
|
|
|
mon_dictionnaire_vide = dict() # dictionnaire vide
|
|
|
mon_dictionnaire[clef] = valeur # j'ajoute une entrée à un dictionnaire existant (j'ajoute des entrées comme je veux)
|
|
|
|
|
|
mon_dictionnaire[clef] #renvoie la valeur
|
|
|
mon_dictionnaire.keys() # vue des clés ; ce n'est pas une liste mais c'est un itérable
|
|
|
for clef in mon_dictionnaire.keys() :
|
... | ... | @@ -78,27 +103,47 @@ for clef in mon_dictionnaire: |
|
|
print("clé = ", clef)
|
|
|
cle_de_sol in mon_dictionnaire.keys() # booléen, vrai si cle_de_sol est une ces clés
|
|
|
mon_dictionnaire.values() # vue des valeurs ; ce n'est pas une liste mais c'est un itérable
|
|
|
mon_dictionnaire.items() # vue des couples (clé, valeur) ; c'est un iterable dont les éléments sont des tuples
|
|
|
mon_dictionnaire.items() # vue des couples (clé, valeur) ; c'est un itérable* dont les éléments sont des tuples
|
|
|
for clef, valeur in mon_dictionnaire.items() :
|
|
|
print("clé = ", clef, " et valeur = ", valeur)
|
|
|
```
|
|
|
|
|
|
#### e) tracé à partir du dictionnaire, puis à partir d'une structure pandas Series
|
|
|
|
|
|
# tracé à partir du dictionnaire, puis à partir d'une structure pandas Series
|
|
|
Ensuite, on compare 2 manière de tracer le graphique q(t) :
|
|
|
Ensuite, on compare 2 manière de tracer le graphique q(t) : d'abord à partir du dictionnaire ; pour cela il faut extraire du dictionnaire la liste_des_dates et la liste_des_q.
|
|
|
La doc indique que les dictionnaires n'existaient pas quand matplotlib a été développé, et il n'a pas été conçu au départ pour accepter directement un dictionnaire comme argument, ni même les vues keys et values d'un dictionnaire : il faut les convertir en **liste** d'abord pour les utiliser comme arguments.
|
|
|
|
|
|
``` python
|
|
|
liste_des_dates= list(mon_dictionnaire.keys() ) # on convertit la vue en liste
|
|
|
liste_des_q = list(mon_dictionnaire.values() )
|
|
|
plt.plot(liste_des_dates, liste_des_q)
|
|
|
|
|
|
plt.plot(liste_des_dates, liste_des_q), avec liste_des_dates = clés du dictionnaire et liste_des_a = valeurs
|
|
|
# ou directement
|
|
|
plt.plot(list(mon_dictionnaire.keys()),list(mon_dictionnaire.values())
|
|
|
```
|
|
|
On va tester le wrapper de pandas (méthode **mon_objet_pandas.plot()**)
|
|
|
Pour cela, on doit d'abord créer un objet de type pandas Series avec les données, sur lequel on appliquera la méthode plot : ma_series.plot().
|
|
|
|
|
|
on convertit le dictionnaire en objet pandas Series et on applique la méthode plot : ma_series.plot()
|
|
|
La conversion est facile, puisque le module pandas, lui, accepte un dictionnaire comme argument.
|
|
|
|
|
|
On introduit dans ce 2e cas un type de courbe "en créneaux" qui convient mieux à des valeurs moyennes journalières.
|
|
|
|
|
|
Comment exécuter la fonction ?
|
|
|
Deux solutions :
|
|
|
Remarque : comment exécuter une fonction à la fois pour suivre le déroulement de l'atelier ?
|
|
|
C'est vrai qu'un notebook est bien pratique pour avancer étape par étape.
|
|
|
Ici, on va mettre en oeuvre l'une ou l'autre de ces deux techniques :
|
|
|
|
|
|
1) écrire les instructions dans le "corps du programme", précédé ici d'un test pour activer certaines sous-parties au fur et à mesure du déroulé
|
|
|
2) travailler dans la console et utiliser le fichier comme un module :
|
|
|
2) travailler dans la console et utiliser le fichier comme un module. La ligne après les définitions de fonctions, donc en début de corps de programme, et qui vous intrigue peut-être est justement là pour permettre d'utiliser le fichier comme module sans déclencher l'exécution des instructions.
|
|
|
``` python
|
|
|
if __name__ == '__main__':
|
|
|
# bloc d'instructions qui ne s'exécute que si le fichier est exécuté en temps que programme principal
|
|
|
#donc qui ne s'exécutera pas si le fichier est importé comme module d'un autre programme
|
|
|
```
|
|
|
Voilà ce qui doit se passer dans la console :
|
|
|
``` python
|
|
|
from Atelier_MatPlotLib_2021_part1 import lecteur_qj0_to_ts
|
|
|
from datetime import datetime, timedelta
|
|
|
from tkinter.filedialog import askopenfilename
|
|
|
import matplotlib.pyplot as plt
|
|
|
from tkinter.filedialog import askopenfilename # parce qu'on en a besoin dans la console
|
|
|
chemin_complet = askopenfilename()
|
|
|
ts = lecteur_qj0_to_ts(chemin_complet)
|
|
|
```
|
|
|
|