diff --git a/_unittests/ut_helpgen/data/td1a_cenonce_session_10.rst b/_unittests/ut_helpgen/data/td1a_cenonce_session_10.rst new file mode 100644 index 000000000..5c44c2216 --- /dev/null +++ b/_unittests/ut_helpgen/data/td1a_cenonce_session_10.rst @@ -0,0 +1,1186 @@ + +.. _td1acenoncesession10rst: + +TD 10 : DataFrame et Matrice +============================ + + +**Links:** + + * :download:`notebook ` + * :download:`html ` + * :download:`PDF ` + * :download:`python ` + +**Notebook:** + +Les `DataFrame `__ se sont +imposés pour manipuler les données. Avec cette façon de représenter les +données, associée à des un ensemble de méthodes couramment utilisées, ce +qu'on faisait en une ou deux boucles se fait maintenant en une seule +fonction. Cette séance contient beaucoup d'exemples et peu d'exercices. +Il est conseillé de supprimer toutes les sorties et de les exécuter une +à une. +**Plan** + +- `Trouver chaussure à ses stats <#intro>`__ +- `DataFrame (pandas) <#df>`__ + + - `création / lecture / écriture <#io>`__ + - `index <#index>`__ + - `Notation avec le symbole : <#ix>`__ + - `Exercice 1 : créer un fichier Excel <#exo1>`__ + +- `Manipuler un DataFrame <#df2>`__ + + - `6 opérations : filtrer, union, sort, group by, join, + pivot <#op>`__ + + - `Exercice 2 <#exo2>`__ + +- `Dates <#date>`__ +- `Matrix, Array (numpy) <#mat>`__ + + - `création / lecture / écriture <#mat>`__ + - `DataFrame/Matrix/Array <#diff>`__ + +- `Calcul matriciel <#mat2>`__ + + - `Exercice 3 <#exo3>`__ + +- `Annexes <#annex>`__ + + - `Créer un fichier Excel avec plusieurs feuilles <#excel>`__ + +L'introduction ne contient pas d'éléments nécessaires à la réalisation +du TD. + +.. raw:: html + +

+ +Trouver chaussure à ses stats + +.. raw:: html + +

+ +La programmation est omni-présente lorsqu'on manipule des données. On +leur applique des traitements parfois standards, souvent adaptés pour la +circonstance. On souhaite toujours programmer le moins possible mais +aussi ne pas avoir à réapprendre un langage à chaque fois qu'on doit +manipuler les données. +Le logiciel `MATLAB `__ a +proposé voici 30 ans un premier environnement de travail facilitant le +calcul matriciel et ce standard s'est imposé depuis. Comme *MATLAB* est +un logiciel payant, des équivalents open source et gratuits ont été +développés. Ils proposent tous le calcul matriciel, la possibilité de +visualiser, un environnement de développement. Ils différent pas des +performances différentes et des éventails d'extensions différentes. +- `R `__ : la référence pour les + statisticiens, il est utilisé par tous les chercheurs dans ce + domaine. +- `SciLab `__ : développé par + l'\ `INRIA `__. +- `Octave `__ : clone open source + de *MATLAB*, il n'inclut pas autant de librairies mais il est + gratuit. +- `Julia `__ : c'est le plus jeune, il est plus + rapide mais ses librairies sont moins nombreuses. + +Ils sont tous performants en qui concerne le calcul numérique, ils le +sont beaucoup moins lorsqu'il s'agit de faire des traitements qui ne +sont pas numériques (traiter du texte par exemple) car ils n'ont pas été +prévus pour cela à la base (à l'exception de Julia peut être qui est +plus jeune `Python v. Clojure v. +Julia `__). +Le langage Python est devenu depuis 2012 une alternative intéressante +pour ces raisons (voir également `Why +Python? `__) : +- Il propose les même fonctionnalités de base (calcul matriciel, + graphiques, environnement). +- Python est plus pratique pour tout ce qui n'est pas numérique + (fichiers, web, server web, SQL, ...). +- La plupart des librairies connues et écrites en C++ ont été portée + sous Python. +- Il est plus facile de changer un composant important en Python (numpy + par exemple) si le nouveau est plus efficace. + +Un inconvénient peut-être est qu'il faut installer plusieurs extensions +avant de pouvoir commencer à travailler. Vous pouvez soit utiliser la +distribution `WinPython `__ ou +installer les modules suivants (avant leurs dépendances) : +- `numpy `__ : calcul matriciel +- `pandas `__ : DataFrame +- `ipython `__ : notebooks (comme celui-ci) +- `matplotlib `__ : graphiques +- `scikit-learn `__ : machine + learning, statistique descriptive + +Optionnels : +- `ggplot `__ : extension très populaire sur + *R* et portée sur Python +- `Spyder `__ : environnement + type R, MATLAB, ... +- `scipy `__ : autres traitements numériques + (voir `NumPy vs. SciPy vs. other + packages `__) +- `cvxopt `__ : optimisation quadratique sous + contrainte +- `Pillow `__ : traitement d'image +- `PyQt4 `__ : interface graphique +- `openpyxl `__ : lecture/écriture + de fichir Excel + +Les environnements Python évoluent très vite, les modules mentionnés ici +sont tous maintenus mais il eut en surgir de nouveau très rapidement. +Quelques environnements à suivre : +- `PyTools `__ : environnement de + développement pour Visual Studio +- `PyCharm `__ : n'inclut pas les + graphiques mais est assez agréable pour programmer +- `IEP `__ : écrit en Python +- `PyDev `__ : extension pour + `Eclipse `__ + +Si vous ne voulez pas programmer, il existe des alternatives. C'est +assez performant sur de petits jeux de données mais cela devient plus +complexe dès qu'on veut programmer car le code doit tenir compte des +spécificités de l'outil. +- `Orange `__ : écrit en Python +- `Weka `__ : écrit en Java (le + pionnier) +- `RapidMiner `__ : version gratuite et payante + +C'est parfois plus pratique pour commencer mais mal commode si on veut +automatiser un traitrment pour répéter la même tâche de façon régulière. +Pour les travaux pratiques à l'ENSAE, jai choisi les +`notebook `__ : c'est une page blanche +où on peut mélanger texte, équations, graphiques, code et exécution de +code. + +.. raw:: html + +

+ +DataFrame (pandas) + +.. raw:: html + +

+ +Un `Data Frame `__ est un objet +qui est présent dans la plupart des logiciels de traitements de données, +c'est une **matrice**, chaque colonne est de même type (nombre, dates, +texte), elle peut contenir des valeurs manquantes. On peut considérer +chaque colonne comme les variables d'une table +(`pandas.Dataframe `__) + +:: + + import pandas + + l = [ { "date":"2014-06-22", "prix":220.0, "devise":"euros" }, + { "date":"2014-06-23", "prix":221.0, "devise":"euros" },] + + df = pandas.DataFrame(l) + df + + + + +.. raw:: html + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
datedeviseprix
0 2014-06-22 euros 220
1 2014-06-23 euros 221
+ +

2 rows × 3 columns

+
+ + + + +Avec une valeur manquante : + +:: + + l = [ { "date":"2014-06-22", "prix":220.0, "devise":"euros" }, + + { "date":"2014-06-23", "devise":"euros" },] + df = pandas.DataFrame(l) + + df + + + +.. raw:: html + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
datedeviseprix
0 2014-06-22 euros 220
1 2014-06-23 euros NaN
+ +

2 rows × 3 columns

+
+ + + + +`NaN `__ +est une convention pour une valeur manquante. On extrait la variable +``prix`` : + +:: + + df.prix + + + + +.. parsed-literal:: + + 0 220 + + 1 NaN + Name: prix, dtype: float64 + + + + +Ou : + +:: + + df["prix"] + + + + +.. parsed-literal:: + + 0 220 + + 1 NaN + Name: prix, dtype: float64 + + + + +Pour extraire plusieurs colonnes : + +:: + + df [["date","prix"]] + + + + +.. raw:: html + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dateprix
0 2014-06-22 220
1 2014-06-23 NaN
+

2 rows × 2 columns

+ +
+ + + +.. raw:: html + +

+ +Lecture et écriture de DataFrame + +.. raw:: html + +

+ +Aujourd'hui, on n'a plus besoin de réécrire soi-même une fonction de +lecture ou d'écriture de données présentées sous forme de tables. Il +existe des fonctions plus génériques qui gère un grand nombre de cas. +Cette section présente brièvement les fonctions qui permettent de +lire/écrire un DataFrame aux formats texte/Excel. On reprend l'exemple +de section précédente. L'instruction ``encoding=utf-8`` n'est pas +obligatoire mais conseillée lorsque les données contiennent des accents +(voir +`read\_csv `__). + +:: + + import pandas + + l = [ { "date":"2014-06-22", "prix":220.0, "devise":"euros" }, + { "date":"2014-06-23", "prix":221.0, "devise":"euros" },] + + df = pandas.DataFrame(l) + + + # écriture au format texte + df.to_csv("exemple.txt",sep="\t",encoding="utf-8", index=False) + + + # on regarde ce qui a été enregistré + + with open("exemple.txt", "r", encoding="utf-8") as f : text = f.read() + print(text) + + + # on enregistre au format Excel + + df.to_excel("exemple.xlsx", index=False) + + + # on ouvre Excel sur ce fichier (sous Windows) + from pyquickhelper import run_cmd + + out,err = run_cmd("exemple.xlsx", wait = False) + +.. parsed-literal:: + + date devise prix + + 2014-06-22 euros 220.0 + 2014-06-23 euros 221.0 + + + + + +On peut récupérer des données directement depuis Internet ou une chaîne +de caractères et afficher le début +(`head `__) +ou la fin +(`tail `__) +: + +:: + + import pandas, io + + from pyquickhelper import get_url_content + text = get_url_content("http://www.xavierdupre.fr/enseignement/complements/marathon.txt") + + df = pandas.read_csv(io.StringIO(text), sep="\t", + names=["ville", "annee", "temps","secondes"]) + + df.head() + + + +.. raw:: html + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
villeanneetempssecondes
0 PARIS 2011 02:06:29 7589
1 PARIS 2010 02:06:41 7601
2 PARIS 2009 02:05:47 7547
3 PARIS 2008 02:06:40 7600
4 PARIS 2007 02:07:17 7637
+ +

5 rows × 4 columns

+
+ + + + +.. raw:: html + +

+ +DataFrame et Index + +.. raw:: html + +

+ +On désigne généralement une colonne ou *variable* par son nom. Les +lignes peuvent être désignées par un entier. + +:: + + import pandas + + l = [ { "date":"2014-06-22", "prix":220.0, "devise":"euros" }, + { "date":"2014-06-23", "prix":221.0, "devise":"euros" },] + + df = pandas.DataFrame(l) + df + + + + +.. raw:: html + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
datedeviseprix
0 2014-06-22 euros 220
1 2014-06-23 euros 221
+ +

2 rows × 3 columns

+
+ + + + +On extrait une ligne +(`ix `__) +: + +:: + + df.ix[1] + + + + +.. parsed-literal:: + + date 2014-06-23 + + devise euros + prix 221 + + Name: 1, dtype: object + + + +Mais il est possible d'utiliser une colonne ou plusieurs colonnes comme +index +(`set\_index `__) +: + +:: + + dfi = df.set_index("date") + + dfi + + + +.. raw:: html + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
deviseprix
date
2014-06-22 euros 220
2014-06-23 euros 221
+ +

2 rows × 2 columns

+
+ + + + +On peut maintenant désigner une ligne par une date : + +:: + + dfi.ix["2014-06-23"] + + + + +.. parsed-literal:: + + devise euros + + prix 221 + Name: 2014-06-23, dtype: object + + + + +Il est possible d'utiliser plusieurs colonnes comme index : + +:: + + df = pandas.DataFrame([ {"prénom":"xavier", "nom":"dupré", "arrondissement":18}, + + {"prénom":"clémence", "nom":"dupré", "arrondissement":15 } ]) + dfi = df.set_index(["nom","prénom"]) + + dfi.ix["dupré","xavier"] + + + +.. parsed-literal:: + + arrondissement 18 + + Name: (dupré, xavier), dtype: int64 + + + +Si on veut changer l'index ou le supprimer +(`reset\_index `__) +: + +:: + + dfi.reset_index(drop=False, inplace=True) + + # le mot-clé drop pour garder ou non les colonnes servant d'index + # inplace signifie qu'on modifie l'instance et non qu'une copie est modifiée + + # donc on peut aussi écrire dfi2 = dfi.reset_index(drop=False) + dfi.set_index(["nom", "arrondissement"],inplace=True) + + dfi + + + +.. raw:: html + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
prénom
nomarrondissement
dupré18 xavier
15 clémence
+

2 rows × 1 columns

+ +
+ + + +.. raw:: html + +

+ +Notation avec le symbole ``:`` + +.. raw:: html + +

+ +Le symbole ``:`` désigne une plage de valeur. + +:: + + import pandas, io + + from pyquickhelper import get_url_content + text = get_url_content("http://www.xavierdupre.fr/enseignement/complements/marathon.txt") + + df = pandas.read_csv(io.StringIO(text), sep="\t", + names=["ville", "annee", "temps","secondes"]) + + df.head() + + + +.. raw:: html + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
villeanneetempssecondes
0 PARIS 2011 02:06:29 7589
1 PARIS 2010 02:06:41 7601
2 PARIS 2009 02:05:47 7547
3 PARIS 2008 02:06:40 7600
4 PARIS 2007 02:07:17 7637
+ +

5 rows × 4 columns

+
+ + + + +On peut sélectionner un sous-ensemble de lignes : + +:: + + df[3:6] + + + + +.. raw:: html + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
villeanneetempssecondes
3 PARIS 2008 02:06:40 7600
4 PARIS 2007 02:07:17 7637
5 PARIS 2006 02:08:03 7683
+ +

3 rows × 4 columns

+
+ + + + +On extrait la même plage mais avec deux colonnes seulement : + +:: + + df.ix[3:6,["annee","temps"]] + + + + +.. raw:: html + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
anneetemps
3 2008 02:06:40
4 2007 02:07:17
5 2006 02:08:03
6 2005 02:08:02
+

4 rows × 2 columns

+ +
+ + + +Le même code pour lequel on renomme les colonnes extraites : + +:: + + sub = df.ix[3:6,["annee","temps"]] + + sub.columns = ["year","time"] + sub + + + + +.. raw:: html + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
yeartime
3 2008 02:06:40
4 2007 02:07:17
5 2006 02:08:03
6 2005 02:08:02
+

4 rows × 2 columns

+ +
+ + + +.. raw:: html + +

+ +Exercice 1 : créer un fichier Excel + +.. raw:: html + +

+ + + +On souhaite récupérer les données +`marathon.txt `__, +les indexer selon la ville et l'année puis sauve le tout au format +Excel. + +:: + + import pandas, io + + # ... +.. raw:: html + +

+ +Manipuler un DataFrame + +.. raw:: html + +

+ +Si la structure *DataFrame* s'est imposée, c'est parce qu'on effectue +toujours les mêmes opérations. Chaque fonction cache une boucle ou deux +dont le coût est précisé en fin de ligne : +- **filter** : on sélectionne un sous-ensemble de lignes qui vérifie + une condition :math:`\rightarrow O(n)` +- **union** : concaténation de deux jeux de données + :math:`\rightarrow O(n_1 + n_2)` +- **sort** : tri :math:`\rightarrow O(n \ln n)` +- **group by** : grouper des lignes qui partagent une valeur commune + :math:`\rightarrow O(n)` +- **join** : fusionner deux jeux de données en associant les lignes qui + partagent une valeur commune + :math:`\rightarrow \in [O(n_1 + n_2), O(n_1 n_2)]` +- **pivot** : utiliser des valeurs présent dans colonne comme noms de + colonnes :math:`\rightarrow O(n)` + +Les 5 premières opérations sont issues de la logique de manipulation des +données avec le langage +`SQL `__ (ou le +logiciel `SAS `__). La dernière correspond à un +`tableau croisé +dynamique `__. + +.. raw:: html + +

+ +6 opérations : filtrer, union, sort, group by, join, pivot + +.. raw:: html + +

diff --git a/_unittests/ut_helpgen/test_rst.py b/_unittests/ut_helpgen/test_rst.py new file mode 100644 index 000000000..12a664aa0 --- /dev/null +++ b/_unittests/ut_helpgen/test_rst.py @@ -0,0 +1,34 @@ +""" +@brief test log(time=8s) +@author Xavier Dupre +""" + +import sys, os, unittest, shutil + + +try : + import src +except ImportError : + path = os.path.normpath(os.path.abspath(os.path.join(os.path.split(__file__)[0], "..", ".."))) + if path not in sys.path : sys.path.append(path) + import src + +from src.pyquickhelper.loghelper.flog import fLOG +from src.pyquickhelper.helpgen.sphinx_main import post_process_rst_output + +class TestRst(unittest.TestCase): + + def test_rst(self) : + fLOG (__file__, self._testMethodName, OutputPrint = __name__ == "__main__") + path = os.path.abspath(os.path.split(__file__)[0]) + file = os.path.join(path, "data", "td1a_cenonce_session_10.rst") + temp = os.path.join(path,"temp_rst") + if not os.path.exists(temp) : os.mkdir(temp) + dest = os.path.join(temp, os.path.split(file)[-1]) + if os.path.exists(dest) : os.remove(dest) + shutil.copy(file,temp) + post_process_rst_output(dest, False, False, False) + + +if __name__ == "__main__" : + unittest.main () diff --git a/src/pyquickhelper/helpgen/sphinx_main.py b/src/pyquickhelper/helpgen/sphinx_main.py index f83759f23..83f99f3a6 100644 --- a/src/pyquickhelper/helpgen/sphinx_main.py +++ b/src/pyquickhelper/helpgen/sphinx_main.py @@ -680,13 +680,18 @@ def post_process_rst_output(file, html, pdf, python): # bullets for pos,line in enumerate(lines): + if pos == 0 : continue if len(line) > 0 and (line.startswith("- ") or line.startswith("* ")) \ and pos < len(lines) : next = lines[pos+1] prev = lines[pos-1] - if (next.startswith("- ") or next.startswith("* ")) and \ - not (prev.startswith("- ") or prev.startswith("* ")): - lines[pos] = "\n" + lines[pos] + if (next.startswith("- ") or next.startswith("* ")) \ + and not (prev.startswith("- ") or prev.startswith("* ")) \ + and not prev.startswith(" "): + lines[pos-1] += "\n" + elif line.startswith("- ") and next.startswith(" ") \ + and not prev.startswith(" ") and not prev.startswith("- "): + lines[pos-1] += "\n" # we remove some empty lines rem=[]