.. _td1acenoncesession10rst: ============================== 1A.data - DataFrame et Matrice ============================== .. only:: html **Links:** :download:`notebook `, :downloadlink:`html `, :download:`python `, :downloadlink:`slides `, :githublink:`GitHub|_doc/notebooks/td1a_dfnp/td1a_cenonce_session_10.ipynb|*` Les `DataFrame `__ se sont imposés pour manipuler les données avec le module `pandas `__. Le module va de la manipulation des données jusqu’au calcul d’une régresion linéaire. 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. .. code:: ipython3 from jyquickhelper import add_notebook_menu add_notebook_menu() .. contents:: :local: L’introduction ne contient pas d’éléments nécessaires à la réalisation du TD. Trouver chaussure à ses stats ----------------------------- 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 (voir `Installation de Python `__) : - `numpy `__ : calcul matriciel - `pandas `__ : DataFrame - `jupyter `__ : notebooks (comme celui-ci) - `matplotlib `__ : graphiques - `scikit-learn `__ : machine learning, statistique descriptive - `statsmodels `__ : statistiques descriptives Optionnels : - `Spyder `__ : environnement type R, MATLAB, … - `scipy `__ : autres traitements numériques (voir `NumPy vs. SciPy vs. other packages `__) - `dask `__ : dataframe distribué et capables de gérer des gros volumes de données (> 5Go) 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 : - `Python Tools for Visual Studio `__ : 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 `__ - `WingIDE `__ 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) - `dataiku `__ : startup française - `RapidMiner `__ : version gratuite et payante - `AzureML `__ : solution Microsoft de workflow de données 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, j’ai choisi les `notebooks `__ : c’est une page blanche où on peut mélanger texte, équations, graphiques, code et exécution de code. **Taille de DataFrame** Les DataFrame en Python sont assez rapides lorsqu’il y a moins de 10 millions d’observations et que le fichier texte qui décrit les données n’est pas plus gros que 10 Mo. Au delà, il faut soit être patient, soit être astucieux comme ici : `DataFrame et SQL `__, `Data Wrangling with Pandas `__. **Valeurs manquantes** Lorsqu’on récupère des données, il peut arriver qu’une valeur soit manquante. - `Missing Data `__ - `Working with missing data `__ DataFrame (pandas) ------------------ **Quelques liens :** `An Introduction to Pandas `__ 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 `__ - cette page contient toutes les méthodes de la classe). .. code:: ipython3 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
date devise prix
0 2014-06-22 euros 220
1 2014-06-23 euros 221

2 rows × 3 columns

Avec une valeur manquante : .. code:: ipython3 l = [ { "date":"2014-06-22", "prix":220.0, "devise":"euros" }, { "date":"2014-06-23", "devise":"euros" },] df = pandas.DataFrame(l) df .. raw:: html
date devise prix
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`` : .. code:: ipython3 df.prix .. parsed-literal:: 0 220 1 NaN Name: prix, dtype: float64 Ou : .. code:: ipython3 df["prix"] .. parsed-literal:: 0 220 1 NaN Name: prix, dtype: float64 Pour extraire plusieurs colonnes : .. code:: ipython3 df [["date","prix"]] .. raw:: html
date prix
0 2014-06-22 220
1 2014-06-23 NaN

2 rows × 2 columns

Pour prendre la transposée (voir aussi `DataFrame.transpose `__) : .. code:: ipython3 df.T .. raw:: html
0 1
date 2014-06-22 2014-06-23
devise euros euros
prix 220 NaN

3 rows × 2 columns

Lecture et écriture de DataFrame ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 `__). .. code:: ipython3 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.loghelper import run_cmd from pyquickhelper.loghelper.run_cmd import skip_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 `__). Le code qui suit est ce qu’on écrirait d’habitude : .. code:: ipython3 if False: import pandas, urllib.request furl = urllib.request.urlopen("http://www.xavierdupre.fr/enseignement/complements/marathon.txt") df = pandas.read_csv(furl, sep="\t", names=["ville", "annee", "temps","secondes"]) df.head() Et pout éviter les erreurs de connexion internet, les données font partie intégrante du module : .. code:: ipython3 from ensae_teaching_cs.data import marathon import pandas df = pandas.read_csv(marathon(filename=True), sep="\t", names=["ville", "annee", "temps","secondes"]) df.head() .. raw:: html
ville annee temps secondes
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
La fonction `describe `__ permet d’en savoir un peu plus sur les colonnes numériques de cette table. .. code:: ipython3 df.describe() .. raw:: html
annee secondes
count 359.000000 359.000000
mean 1989.754875 7933.660167
std 14.028545 385.289830
min 1947.000000 7382.000000
25% 1981.000000 7698.000000
50% 1991.000000 7820.000000
75% 2001.000000 8046.500000
max 2011.000000 10028.000000

8 rows × 2 columns

DataFrame et Index ~~~~~~~~~~~~~~~~~~ On désigne généralement une colonne ou *variable* par son nom. Les lignes peuvent être désignées par un entier. .. code:: ipython3 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
date devise prix
0 2014-06-22 euros 220
1 2014-06-23 euros 221

2 rows × 3 columns

On extrait une ligne (`loc `__) : .. code:: ipython3 df.iloc[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 `__) : .. code:: ipython3 dfi = df.set_index("date") dfi .. raw:: html
devise prix
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 : .. code:: ipython3 dfi.loc["2014-06-23"] .. parsed-literal:: devise euros prix 221 Name: 2014-06-23, dtype: object Il est possible d’utiliser plusieurs colonnes comme index : .. code:: ipython3 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.loc["dupré","xavier"] .. parsed-literal:: arrondissement 18 Name: (dupré, xavier), dtype: int64 Si on veut changer l’index ou le supprimer (`reset_index `__) : .. code:: ipython3 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
nom arrondissement
dupré 18 xavier
15 clémence

2 rows × 1 columns

Les index sont particulièrement utiles lorsqu’il s’agit de fusionner deux tables. Pour des petites tables, la plupart du temps, il est plus facile de s’en passer. Notation avec le symbole ``:`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Le symbole ``:`` désigne une plage de valeurs. .. code:: ipython3 from ensae_teaching_cs.data import marathon import pandas df = pandas.read_csv(marathon(filename=True), sep="\t", names=["ville", "annee", "temps","secondes"]) df.head() .. raw:: html
ville annee temps secondes
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
On peut sélectionner un sous-ensemble de lignes : .. code:: ipython3 df[3:6] .. raw:: html
ville annee temps secondes
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 : .. code:: ipython3 df.loc[3:6,["annee","temps"]] .. raw:: html
annee temps
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 : .. code:: ipython3 sub = df.loc[3:6,["annee","temps"]] sub.columns = ["year","time"] sub .. raw:: html
year time
3 2008 02:06:40
4 2007 02:07:17
5 2006 02:08:03
6 2005 02:08:02

4 rows × 2 columns

Exercice 1 : créer un fichier Excel ----------------------------------- On souhaite récupérer les données `donnees_enquete_2003_television.txt `__ (source : `INSEE `__). - ``POIDSLOG`` : Pondération individuelle relative - ``POIDSF`` : Variable de pondération individuelle - ``cLT1FREQ`` : Nombre d’heures en moyenne passées à regarder la télévision - ``cLT2FREQ`` : Unité de temps utilisée pour compter le nombre d’heures passées à regarder la télévision, cette unité est représentée par les quatre valeurs suivantes - 0 : non concerné - 1 : jour - 2 : semaine - 3 : mois Ensuite, on veut : 1. Supprimer les colonnes vides 2. Obtenir les valeurs distinctes pour la colonne ``cLT2FREQ`` 3. Modifier la matrice pour enlever les lignes pour lesquelles l’unité de temps (cLT2FREQ) n’est pas renseignée ou égale à zéro. 4. Sauver le résultat au format Excel. Vous aurez peut-être besoin des fonctions suivantes : - `numpy.isnan `__ - `DataFrame.apply `__ - `DataFrame.fillna `__ ou `DataFrame.isnull `__ - `DataFrame.copy `__ .. code:: ipython3 import pandas, io # ... Manipuler un DataFrame : filtrer, union, sort, group by, join, pivot -------------------------------------------------------------------- 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ésentes 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 `__. Pour illustrer ces opérations, on prendre le DataFrame suivant : .. code:: ipython3 from ensae_teaching_cs.data import marathon import pandas df = pandas.read_csv(marathon(), sep="\t", names=["ville", "annee", "temps","secondes"]) print(df.columns) print("villes",set(df.ville)) print("annee",list(set(df.annee))[:10],"...") .. parsed-literal:: Index(['ville', 'annee', 'temps', 'secondes'], dtype='object') villes {'FUKUOKA', 'STOCKOLM', 'PARIS', 'CHICAGO', 'AMSTERDAM', 'BOSTON', 'BERLIN', 'LONDON', 'NEW YORK'} annee [1947, 1948, 1949, 1950, 1951, 1952, 1953, 1954, 1955, 1956] ... filter ~~~~~~ Filter consiste à sélectionner un sous-ensemble de lignes du dataframe. Pour filter sur plusieurs conditions, il faut utiliser les opérateurs logique & (et), \| (ou), ~ (non) (voir `Mapping Operators to Functions `__). - `filter `__, `mask `__,\ `where `__ - `pandas: filter rows of DataFrame with operator chaining `__ - `Indexing and Selecting Data `__ .. code:: ipython3 subset = df [ df.annee == 1971 ] subset.head() .. raw:: html
ville annee temps secondes
112 FUKUOKA 1971 02:12:51 7971
204 NEW YORK 1971 02:22:54 8574
285 BOSTON 1971 02:18:45 8325
.. code:: ipython3 subset = df [ (df.annee == 1971) & (df.ville == "BOSTON") ] subset.head() .. raw:: html
ville annee temps secondes
285 BOSTON 1971 02:18:45 8325
union ~~~~~ union = concaténation de deux DataFrame (qui n’ont pas nécessaire les mêmes colonnes). On peut concaténer les lignes ou les colonnes. - `concat `__ - `Merge, join, and concatenate `__ .. code:: ipython3 concat_ligne = pandas.concat((df,df)) df.shape,concat_ligne.shape .. parsed-literal:: ((360, 4), (720, 4)) .. code:: ipython3 concat_col = pandas.concat((df,df), axis=1) df.shape,concat_col.shape .. parsed-literal:: ((360, 4), (360, 8)) sort ~~~~ Sort = trier - `sort `__ .. code:: ipython3 tri = df.sort_values( ["annee", "ville"], ascending=[0,1]) tri.head() .. raw:: html
ville annee temps secondes
35 BERLIN 2011 02:03:38 7418
326 BOSTON 2011 02:03:02 7382
203 LONDON 2011 02:04:40 7480
0 PARIS 2011 02:06:29 7589
277 STOCKOLM 2011 02:14:07 8047

5 rows × 4 columns

group by ~~~~~~~~ Cette opération consiste à grouper les lignes qui partagent une caractéristique commune (une ou ou plusieurs valeurs par exemple). Sur chaque groupe, on peut calculer une somme, une moyenne… - `groupby `__ - `sum `__, `cumsum `__, `mean `__, `count `__ - `SQL GROUP BY `__ - `Group By: split-apply-combine `__ - `group by customisé `__ .. code:: ipython3 gr = df.groupby("annee") gr .. parsed-literal:: .. code:: ipython3 nb = gr.count() nb.sort_index(ascending=False).head() .. raw:: html
ville annee temps secondes
annee
2011 5 5 5 5
2010 9 9 9 9
2009 9 9 9 9
2008 9 9 9 9
2007 9 9 9 9

5 rows × 4 columns

.. code:: ipython3 nb = gr.sum() nb.sort_index(ascending=False).head(n=2) .. raw:: html
secondes
annee
2011 37916
2010 68673

2 rows × 1 columns

.. code:: ipython3 nb = gr.mean() nb.sort_index(ascending=False).head(n=3) .. raw:: html
secondes
annee
2011 7583.200000
2010 7630.333333
2009 7652.555556

3 rows × 1 columns

Si les nom des colonnes utilisées lors de l’opération ne sont pas mentionnés, implicitement, c’est l’index qui sera choisi. On peut aussi aggréger les informations avec une fonction personnalisée. .. code:: ipython3 def max_entier(x): return int(max(x)) nb = df[["annee","secondes"]].groupby("annee").agg(max_entier).reset_index() nb.tail(n=3) .. raw:: html
annee secondes
62 2009 8134
63 2010 7968
64 2011 8047

3 rows × 2 columns

Ou encore considérer des aggrégations différentes pour chaque colonne : .. code:: ipython3 nb = df[["annee","ville","secondes"]].groupby("annee").agg({ "ville":len, "secondes":max_entier}) nb.tail(n=3) .. raw:: html
secondes ville
annee
2009 8134 9
2010 7968 9
2011 8047 5

3 rows × 2 columns

join (*merge* ou fusion) ~~~~~~~~~~~~~~~~~~~~~~~~ Fusionner deux tables consiste à apparier les lignes de la première table avec celle de la seconde si certaines colonnes de ces lignes partagent les mêmes valeurs. On distingue quatre cas : - ``INNER JOIN`` - **inner** : on garde tous les appariements réussis - ``LEFT OUTER JOIN`` - **left** : on garde tous les appariements réussis et les lignes non appariées de la table de gauche - ``RIGHT OUTER JOIN`` - **right** : on garde tous les appariements réussis et les lignes non appariées de la table de droite - ``FULL OUTER JOIN`` - **outer** : on garde tous les appariements réussis et les lignes non appariées des deux tables Exemples et documentation : \* `merging, joining `__ \* `join `__ \* `merge `__ ou `DataFrame.merge `__ \* `jointures SQL `__ - illustrations avec graphiques en patates Si les noms des colonnes utilisées lors de la fusion ne sont pas mentionnés, implicitement, c’est l’index qui sera choisi. Pour les grandes tables (> 100.000 lignes), il est fortement recommandés d’ajouter un index s’il n’existe pas avant de fusionner. A quoi correspondent les quatre cas suivants : .. code:: ipython3 from IPython.display import Image Image("patates.png") .. image:: td1a_cenonce_session_10_68_0.png On souhaite ajouter une colonne pays aux marathons se déroulant dans les villes suivanes. .. code:: ipython3 values = [ {"V":'BOSTON', "C":"USA"}, {"V":'NEW YORK', "C":"USA"}, {"V":'BERLIN', "C":"Germany"}, {"V":'LONDON', "C":"UK"}, {"V":'PARIS', "C":"France"}] pays = pandas.DataFrame(values) pays .. raw:: html
C V
0 USA BOSTON
1 USA NEW YORK
2 Germany BERLIN
3 UK LONDON
4 France PARIS

5 rows × 2 columns

.. code:: ipython3 dfavecpays = df.merge(pays, left_on="ville", right_on="V") pandas.concat([dfavecpays.head(n=2),dfavecpays.tail(n=2)]) .. raw:: html
ville annee temps secondes C V
0 PARIS 2011 02:06:29 7589 France PARIS
1 PARIS 2010 02:06:41 7601 France PARIS
193 BOSTON 2010 02:05:52 7552 USA BOSTON
194 BOSTON 2011 02:03:02 7382 USA BOSTON

4 rows × 6 columns

pivot (tableau croisé dynamique) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Cette opération consiste à créer une seconde table en utilisant utiliser les valeurs d’une colonne comme nom de colonnes. +----+----+----+ | A | B | C | +====+====+====+ | A1 | B1 | C1 | +----+----+----+ | A1 | B2 | C2 | +----+----+----+ | A2 | B1 | C3 | +----+----+----+ | A2 | B2 | C4 | +----+----+----+ | A2 | B3 | C5 | +----+----+----+ L’opération ``pivot(A,B,C)`` donnera : +----+----+----+----+ | A | B1 | B2 | B3 | +====+====+====+====+ | A1 | C1 | C2 | | +----+----+----+----+ | A2 | C3 | C4 | C5 | +----+----+----+----+ - `pivot `__ - `Reshaping and Pivot Tables `__ - `Tableau croisé dynamique - wikipédia `__ On applique cela aux marathons où on veut avoir les villes comme noms de colonnes et une année par lignes. .. code:: ipython3 piv = df.pivot("annee","ville","temps") pandas.concat([piv[20:23],piv[40:43],piv.tail(n=3)]) .. raw:: html
ville AMSTERDAM BERLIN BOSTON CHICAGO FUKUOKA LONDON NEW YORK PARIS STOCKOLM
annee
1967 NaN NaN 02:15:45 NaN 02:09:37 NaN NaN NaN NaN
1968 NaN NaN 02:22:17 NaN 02:10:48 NaN NaN NaN NaN
1969 NaN NaN 02:13:49 NaN 02:11:13 NaN NaN NaN NaN
1987 02:12:40 02:11:11 02:11:50 NaN 02:08:18 02:09:50 02:11:01 02:11:09 02:13:52
1988 02:12:38 02:11:45 02:08:43 02:08:57 02:11:04 02:10:20 02:08:20 02:13:53 02:14:26
1989 02:13:52 02:10:11 02:09:06 02:11:25 02:12:54 02:09:03 02:08:01 02:13:03 02:13:34
2009 02:06:18 02:06:08 02:08:42 02:05:41 02:05:18 02:05:10 02:09:15 02:05:47 02:15:34
2010 02:05:44 02:05:08 02:05:52 02:06:23 02:08:24 02:05:19 02:08:14 02:06:41 02:12:48
2011 NaN 02:03:38 02:03:02 NaN NaN 02:04:40 NaN 02:06:29 02:14:07

9 rows × 9 columns

Il existe une méthode qui effectue l’opération inverse : `Dataframe.stack `__. Exercice 2 : moyennes par groupes --------------------------------- Toujours avec le même jeu de données (`marathon.txt `__), on veut ajouter une ligne à la fin du tableau croisé dynamique contenant la moyenne en secondes des temps des marathons pour chaque ville. Dates ----- Les dates sont souvent compliquées à gérer car on n’utilise pas le mêmes format dans tous les pays. Pour faire simple, je recommande deux options : - Soit convertir les dates/heures au format chaînes de caractères ``AAAA-MM-JJ hh:mm:ss:ms`` qui permet de trier les dates par ordre croissant. - Soit convertir les dates/heures au format `datetime `__ (date) ou `timedelta `__ (durée) (voir `Quelques notions sur les dates `__, `format de date/heure `__). Par exemple, voici le code qui a permis de générer la colonne seconde de la table marathon : .. code:: ipython3 from datetime import datetime, time from ensae_teaching_cs.data import marathon import pandas df = pandas.read_csv(marathon(), sep="\t", names=["ville", "annee", "temps","secondes"]) df = df [["ville", "annee", "temps"]] # on enlève la colonne secondes pour la recréer df["secondes"] = df.apply( lambda r : (datetime.strptime(r.temps,"%H:%M:%S") - \ datetime(1900,1,1)).total_seconds(), axis=1) df.head() .. raw:: html
ville annee temps secondes
0 PARIS 2011 02:06:29 7589.0
1 PARIS 2010 02:06:41 7601.0
2 PARIS 2009 02:05:47 7547.0
3 PARIS 2008 02:06:40 7600.0
4 PARIS 2007 02:07:17 7637.0
Matrix, Array (numpy) --------------------- Le module le plus populaire sous Python est `numpy `__. Il propose deux containers `Matrix `__ et `Array `__ qui facilitent le calcul matriciel. Ce module est écrit en C++, Fortran. Il sera plus rapide que tout code écrit en Python. De nombreuses modules Python s’appuient sur numpy : `SciPy `__, `pandas `__, `scikit-learn `__, `matplotlib `__, … Il y a deux différences entre un ``DataFrame`` et un tableau ``numpy`` : - Il n’y a pas d’index sur les lignes autre que l’index entier de la ligne. - Tous les types doivent être identiques (tous entier, tous réels, tous str). Il n’y a pas de mélange possible. C’est à cette condition que les calculs sont aussi rapides. .. code:: ipython3 import numpy print("int","\n",numpy.matrix([[1, 2], [3, 4,]])) print("float","\n",numpy.matrix([[1, 2], [3, 4.1]])) print("str","\n",numpy.matrix([[1, 2], [3, '4']])) .. parsed-literal:: int [[1 2] [3 4]] float [[ 1. 2. ] [ 3. 4.1]] str [['1' '2'] ['3' '4']] Il y a deux types d’objets, ``array`` et ``matrix``. Le type ``matrix`` se comporte comme on peut l’attendre d’une matrice. Le type ``array`` est plus générique et autorise plus de deux dimensions. Les opérateurs qui s’y appliquent ne comportent pas comme ceux d’une matrice, en particulier la multiplication qui se fait terme à terme pour un tableau. .. code:: ipython3 m1 = numpy.matrix( [[0.0,1.0],[1.0,0.0]]) print("multiplication de matrices\n",m1 * m1) m2 = numpy.array([[0.0,1.0],[1.0,0.0]]) print("multiplication de tableaux (terme à terme)\n",m2 * m2) .. parsed-literal:: multiplication de matrices [[ 1. 0.] [ 0. 1.]] multiplication de tableaux (terme à terme) [[ 0. 1.] [ 1. 0.]] Un tableau en plusieurs dimensions : .. code:: ipython3 cube = numpy.array( [ [[0.0,1.0],[1.0,0.0]], [[0.0,1.0],[1.0,0.0]] ] ) print(cube.shape) cube .. parsed-literal:: (2, 2, 2) .. parsed-literal:: array([[[ 0., 1.], [ 1., 0.]], [[ 0., 1.], [ 1., 0.]]]) Quelques liens pour apprendre à manipuler ces objets : - `opérations avec numpy.matrix `__ - `Numpy - multidimensional data arrays `__ - `NUmpy Tutorial `__ - `classe numpy.matrix `__ - `classe numpy.array `__ matrices nulle, identité, aléatoire ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ On utilise beaucoup les fonctions suivantes pour créer une matrice ou un tableau particulier. .. code:: ipython3 # la matrice nulle numpy.zeros( (3,4) ) .. parsed-literal:: array([[ 0., 0., 0., 0.], [ 0., 0., 0., 0.], [ 0., 0., 0., 0.]]) .. code:: ipython3 # la matrice de 1 numpy.ones( (3,4) ) .. parsed-literal:: array([[ 1., 1., 1., 1.], [ 1., 1., 1., 1.], [ 1., 1., 1., 1.]]) .. code:: ipython3 # la matrice identité numpy.identity( 3 ) .. parsed-literal:: array([[ 1., 0., 0.], [ 0., 1., 0.], [ 0., 0., 1.]]) .. code:: ipython3 # la matrice aléatoire numpy.random.random( (3,4)) .. parsed-literal:: array([[ 0.56295296, 0.77545561, 0.56041393, 0.90371888], [ 0.09984123, 0.59781939, 0.09845057, 0.30856921], [ 0.37161512, 0.5630934 , 0.6359542 , 0.13298039]]) Pour d’autres fonctionnalités aléatoires : `numpy.random `__. Quelques fonctions fréquemment utilisées ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - `column_stack `__ : pour assembler des colonnes les unes à côté des autres - `vstack `__ : pour assembler des lignes les unes à la suite des autres de DataFrame à numpy ~~~~~~~~~~~~~~~~~~~~ Le plus simple est sans doute d’utiliser ``pandas`` pour lire un fichier texte et d’utiliser la propriété `values `__ pour convertir tout ou partie du ``DataFrame`` en ``numpy.matrix``. .. code:: ipython3 from pandas import read_csv import numpy from datetime import datetime, time from ensae_teaching_cs.data import marathon df = read_csv(marathon(filename=True), sep="\t", names=["ville", "annee", "temps","secondes"]) arr = df[["annee","secondes"]].values # retourne un array (et non un matrix) mat = numpy.matrix(arr) print(type(arr),type(mat)) arr[:2,:] .. parsed-literal:: .. parsed-literal:: array([[2011, 7589], [2010, 7601]], dtype=int64) La conversion réciproque est aussi simple mais il faut préciser les noms des colonnes qui ne sont pas mémorisées dans l’objet ``numpy.array`` : .. code:: ipython3 import pandas df2 = pandas.DataFrame(arr, columns=["annee", "secondes"]) df2.head(n=2) .. raw:: html
annee secondes
0 2011 7589
1 2010 7601

2 rows × 2 columns

Exercice 3 : régression linéaire -------------------------------- On souhaite implémenter une `régression `__ qui se traduit par le problème suivant : :math:`Y=XA+\epsilon`. La solution est donnée par la formule matricielle : :math:`A^*=(X'X)^{-1}X'Y`. On prépare les données suivantes. .. code:: ipython3 from pandas import read_csv from datetime import datetime, time from ensae_teaching_cs.data import marathon df = read_csv(marathon(filename=True), sep="\t", names=["ville", "annee", "temps","secondes"]) df = df [ (df["ville"] == "BERLIN") | (df["ville"] == "PARIS") ] for v in ["PARIS","BERLIN"]: df["est" + v] = df.apply( lambda r : 1 if r["ville"] == v else 0, axis=1) df.head(n = 3) .. raw:: html
ville annee temps secondes estPARIS estBERLIN
0 PARIS 2011 02:06:29 7589 1 0
1 PARIS 2010 02:06:41 7601 1 0
2 PARIS 2009 02:05:47 7547 1 0
On veut construire le modèle : :math:`secondes = a_0 \; annee + a_1 \; stPARIS + a_2 \; estBERLIN`. En appliquant la formule ci-dessus, déterminer les coefficients :math:`a_0,a_1,a_2`. Annexes ------- Créer un fichier Excel avec plusieurs feuilles ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ La page `Allow ExcelWriter() to add sheets to existing workbook `__ donne plusieurs exemples d’écriture. .. code:: ipython3 import pandas writer = pandas.ExcelWriter('tou_example.xlsx') df.to_excel(writer, 'Data 0') df.to_excel(writer, 'Data 1') writer.save()