{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# 2A.data - DataFrame et Graphes\n", "\n", "Les [Dataframe](https://fr.wikipedia.org/wiki/Pandas) se sont impos\u00e9s pour manipuler les donn\u00e9es. Avec cette fa\u00e7on de repr\u00e9senter les donn\u00e9es, associ\u00e9e \u00e0 des m\u00e9thodes couramment utilis\u00e9es, ce qu'on faisait en une ou deux boucles se fait maintenant en une seule fonction. Le module [pandas](http://pandas.pydata.org/) est tr\u00e8s utilis\u00e9, il existe de nombreux [tutoriels](http://pandas.pydata.org/pandas-docs/stable/tutorials.html), ou page de recettes pour les usages les plus fr\u00e9quents : [cookbook](http://pandas.pydata.org/pandas-docs/stable/cookbook.html#cookbook)."]}, {"cell_type": "code", "execution_count": 1, "metadata": {"collapsed": true}, "outputs": [], "source": ["%matplotlib inline\n", "# Cette premi\u00e8re instruction indique \u00e0 Jupyter d'ins\u00e9rer les graphiques\n", "# dans le notebook plut\u00f4t que dans une fen\u00eatre externe.\n", "import matplotlib.pyplot as plt\n", "# Ces deux lignes change le style des graphes."]}, {"cell_type": "markdown", "metadata": {}, "source": ["La premi\u00e8re instruction ``%matplotlib`` est sp\u00e9cifique aux notebooks. Sans cela, les graphiques ne sont pas ins\u00e9r\u00e9es dans la page elle-m\u00eame mais cela ne s'applique qu'aux notebooks et provoquera une erreur sur [Spyder](https://github.com/spyder-ide/spyder) par exemple."]}, {"cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [{"data": {"text/html": ["
\n", ""], "text/plain": [""]}, "execution_count": 3, "metadata": {}, "output_type": "execute_result"}], "source": ["from jyquickhelper import add_notebook_menu\n", "add_notebook_menu()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Le module [jyquickhelper](https://pypi.python.org/pypi/jyquickhelper) a \u00e9t\u00e9 d\u00e9velopp\u00e9 pour ins\u00e9rer quelques \u00e9l\u00e9ments de [javascript](https://en.wikipedia.org/wiki/JavaScript) dans le notebook de construire automatiquement un menu. Il n'est pas utile en dehors des notebooks. Pour l'installer : ``pip install jyquickhelper``."]}, {"cell_type": "markdown", "metadata": {}, "source": ["## DataFrame\n", "\n", "Pour faire court, c'est l'\u00e9quivalent d'une feuille Excel ou d'une table SQL.\n", "\n", "**Taille de DataFrame**\n", "\n", "Les DataFrame en Python sont assez rapides lorsqu'il y a moins de 10 millions d'observations et que le fichier texte qui d\u00e9crit les donn\u00e9es n'est pas plus gros que 10 Mo. Au del\u00e0, il faut soit \u00eatre patient, soit \u00eatre astucieux comme ici : [DataFrame et SQL](http://www.xavierdupre.fr/blog/2014-07-19_nojs.html), [Data Wrangling with Pandas](http://nbviewer.jupyter.org/urls/gist.github.com/fonnesbeck/5850413/raw/3a9406c73365480bc58d5e75bc80f7962243ba17/2.+Data+Wrangling+with+Pandas.ipynb). D'autres options seront propos\u00e9es plus tard durant ce cours.\n", "\n", "**Valeurs manquantes**\n", "\n", "Lorsqu'on r\u00e9cup\u00e8re des donn\u00e9es, il peut arriver qu'une valeur soit manquante ([Working with missing data](http://pandas.pydata.org/pandas-docs/dev/missing_data.html))."]}, {"cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [{"data": {"image/png": "iVBORw0KGgoAAAANSUhEUgAAAucAAAGNCAIAAAA94kg+AAAAAXNSR0IArs4c6QAAAARnQU1BAACx\njwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAD+ZSURBVHhe7d2xyjTPce9xXYgioatw4tQYYXQR\nxsEJFAikO3AiMDgUdm4wjpQ4McKJwQ6UKjBGBiNQJoOkQAgMOruz35qntnZnpmdnere76/dh4Lzb\n1bs7XTM9VWff1/p/448iIiIiPVDXIiIiIn1Q1yIiIiJ9UNciIiIifVDXIiIiIn1Q1yIiIiJ9UNci\nIiIifVDXIiIiIn1Q1yIiIiJ9UNciIiIifVDXIiIiIn1Q1yIiIiJ9UNciIiIifVDXItK3X3/z25sH\nU0VEOqeuRaRvoUEpP3i/iEg/1LWIdCw0Ii8cfJCISA/UtYj0aqX/CKHyg/eLiDRJXYtIr15uNeY3\nrhxMFRFpiboWkS4d7zD8J9Q7+DIRkTOoa5E7f/pnPzt+8FlSTaXOIHzs6QdfIyLyKnUtck6n8sLB\n18tOtfsA//lVD75PRKSYupakQgPR8sEZi+mi6s8nuXnwBhGRAupasgitwMrBGw4IH1jp4MuS6bHe\n+3PePHhPZeFeev/BeYjITupahhWekusH73mvcA5HDj5xdO+v7jX4VTw9mLdfuCu6ODh1ESmjrmUo\n4YG4cvCG5oXT3nXwEQM5XtebMi8nHIS3hMs93sE6RcRR19K98KRbOpg9irC69YP3dO6Fut6XpQWG\nq3nKwUd/TjifFw4+SCQZdS39CQ+vlYM3jC6setfBRzRvqaI3JeT2hWNeYxgvPDiProQlvHDwQSI5\nqGtpXXhCrR+8Rw4XAz6lGXM5vxwMvUVIS+3DLzOEHg9OcVxhvZ86OBv58vt/+H8/+9P/96v/4aW8\nlbqWFoWnxvrBe2RZyNjeg0/5nF21vPfjcbFkQe7NGXvnwXdnp67lk9S1tCU8I5YOZssxIaslB+98\nl9uXhire7MFJHzMv9nYwKsvCVfjUwdmkoK7lk9S1fF7Y/E8Ppsq7hPw/Hsw7VfiK+ZhLeBh/w8GZ\nvde8Xn8Qk/cK98PmwdsGp67lk9S1vFvY5CsHb5BPC9fl/UfOyj2vOuHa2xRuy5WDN/Ti33/xdfI/\n+l8GL375q7+cx//sZ3/97wyHruXffnSJ/uLfpj9P/vev/+xnf/mPv7+9uEYvM91H3T5netf1mGde\n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\n57/4LRvvFRERkVM96VoeSv7Uajz+EPL1Z99b3PcH1zK/1LVsfuzzPmMS33txPW3rh3Z2LUvnv/Et\nG2sXERGRUy10LfPPIRfTryB3PyG4XsS3GpP7Sr/StWx/rH/jvfsfZm78W3Z2LQvnv/UtG2sXERGR\nU5V2LdNfhfijrHJvdS2rH+vfeO+x47lw33Vi17LyLepaRERE3qngb4ieFm9zsGtZ/diNrkW/tYiI\niKRS8q9x1/65xkblXulatj92uWt51h/4n4jiWT3rP25Wz3/ntzybL9Kz//79n3/713/+9//HSxGR\nD3vStTy0Grfy7H4XuUworNyxXZiid+9d+diVriW+N36U/97pz5fo/q5l41vW3zulcbEtE6nq/378\n3V9/89scr7cd6lpEpC3Pupanv0xMNZjDeouLjcptlf56XAfvupartY9d61qu/HsfJn997/X0rt/7\nQtdytfwtG++d3vjVSIm8y9SyfPf3v/h6+Zsff+05EZF+PetaHnsL2e+hpxF5j5/+7pvf/vX3deuJ\nyICedi233wm2fuqQNdd/taMfWuQTpq5l5a91/uWHX3955Jqb6ReaH/7hF3//myk0/Tzz8DdEC+9l\nJqEf/oFBEZGTLXQty3+lIiWuP7Toxyr5kD98/7GrMFPb8bt/mf48NSjzXx7ZP4X5+qul2LUsvnea\nNn/dZZp+6RGROpa6FhHpGI3L5bj70eW+vZh/X/n681cTM/Fdy8p7r7/u6J/OiMgbqGsRGdXUZ0y9\nCy3F9AMJP5bcXH8+4ceVqQvxP7RcuK5l7b18kRoXEalNXYvI2KZ/5nJrL+zfrNwfO7qWpfdefP26\no3/XIiLVqGsRGd3871Eefy9xdv/W8hTNjRoXEalCXYvI4Fw7svYPUDa6lvJ/vOL+1klE5FzqWkTG\ncm0a3E8dtx8/7F/RTq2J+8nkMpmmZLNrWX6v+5DwFhGRc6lrERnOtXGZ/5XJww8kPurai82u5Wrt\nvU/GRUTOVbVrWf/f2C3/X+Ad8n+rd1qU/08HiIiIyCp1LZ+j/wFiERGRPd7YtbxepAftWqb/1X/9\nDxCLiIgUUtfyST/92ZjrEhERqeFJ10Ip/e0//cVPvvOt6bj9VwCv49NL//PANOj/y8b+94O525j+\nYJ92Pa7/nuOuF7H/cM/17bc57lseupZrA+Q/aoFbwuXw/y3D1dO+s3pik+Vv2Xzv9N+F1l8SiYiI\nFFnoWq4llqI+NyvU46ljmGtzWdcyib+1PHQt12+xCdO3LH+OncDUMTztNu4/Yddp37ETY/Lt5Vdr\nsvotG++9uJ8vIiIiK5a6FlfRQ2G+/XJgv3Cc27X4+n39ZKJ+5vRn9/vK9EZ/Ajdx2oX7wL1diz/t\nhwUuf8vqeycrXZeIiIjcK+hapsrq+om7Ur1a/n23UdK13DUfruS7mfFkHj928qwb8D3Ezq7Fz3TL\n3/qWtffeqGsREREp1mXXMv21iz+edy3xb17cCZzYtax8i7oWERGREzXetdxG3MynjcKjZ92Aa4PO\n7FpWvmXtvTfqWkRERIod7VpiYb4rw3d9SUHX4qPTaRD1Mxfbi3sP/cHdB66f9p3VzmPntzzOv+ak\noAkTqU7//SAR6cDRruWu3k9/vpTh511L7AzuolOBv7zXGpe7cv5k5tf5XGbOX+GEafFda6d9Z73z\nWP+W9fdeTBPuejWRD1HXIiIdONy1WKmejsu7rtHnXYufeX37XZQCbw3E5Yjf6FuTqafheNaywE97\naA6WT/sOJ8ari+lk3PJXvmXzvf6HGZGPUtciIh140rV8xEOBz6Dwb7tE3kBdi4h0QF3L51x/pNFf\nD0kd//LDX3/z2xzfdxvrF3//m3n8m9/+3b8w/KRrWZz5x//78XenmT/9nUV/8+OvXwzXo1dL5yYi\nskVdy6c8/E2TyFmmtmDuM/7wffvzNP7VQ9xNu+9a1mZOfcm15/jhH9zLwujdR02NUexpRESWtdK1\niMhJpv7jyW8YT8YvDY11Kr5rWZ95a0S++/tfTC+urj+rzM3HajR+8jSZ/kZEZJO6FpGxLP2A8Wzc\ndRiua9mY+dhq3PUia9Hpk79+d7m4/vTiWxwRkTXqWkTG8tgZ3Dwd/2oaYteyPPNo1zL9/ZE/1LWI\nSCl1LSJjmTqDbn5rERHZQ12LyGCm/+Md6yGcu97i5sC/a3mpa7n/FzAiInupaxEZzdQ3fP2kcWk4\nbo1CGL9/6buW9Zm3l691LfGjrn/xZF8qIrJJXYvIgG7NAYf/1WT6Pzy2w/+Dkruu5Wpx5qGu5cp/\nsloWEdlDXYuIiIj0QV2LiIiI9EFdi4iIiPRBXYuIiIj0QV2LiIiI9EFdi4iIiPRBXYuIiIj0QV2L\niIiI9OCPf/z/t6a8i8c03yIAAAAASUVORK5CYII=\n", "text/plain": [""]}, "execution_count": 4, "metadata": {}, "output_type": "execute_result"}], "source": ["from pyquickhelper.helpgen import NbImage\n", "NbImage(\"td2df.png\")"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Pour manipuler les dataframe, on utilise le module [pandas](http://pandas.pydata.org/). Il est pr\u00e9vu pour manipuler les donn\u00e9es d'une table par **bloc** (une sous-table). Tant qu'on manipule des blocs, le module est rapide."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Series\n", "\n", "Une [Series](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.html) est un objet uni-dimensionnel similaire \u00e0 un tableau, une liste ou une colonne d'une table. Chaque valeur est associ\u00e9e \u00e0 un **index** qui est par d\u00e9faut les entiers de 0 \u00e0 $N-1$ (avec $N$ la longueur de la [Series](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.html))."]}, {"cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [{"data": {"text/plain": ["0 42\n", "1 Hello World!\n", "2 3.14\n", "3 -5\n", "4 None\n", "dtype: object"]}, "execution_count": 5, "metadata": {}, "output_type": "execute_result"}], "source": ["import pandas\n", "from pandas import Series\n", "import numpy\n", "s = Series([42, 'Hello World!', 3.14, -5, None, numpy.nan])\n", "s.head()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On peut aussi pr\u00e9ciser les indices lors de la cr\u00e9ation, ou construire la [Series](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.html) \u00e0 partir d'un dictionnaire si on fournit un index avec un dictionnaire, les index qui ne sont pas des cl\u00e9s du dictionnaire seront des valeurs manquantes."]}, {"cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [{"data": {"text/plain": ["Malakoff 92240\n", "Nice 6300\n", "Paris14 75014\n", "Paris18 75018\n", "dtype: int64"]}, "execution_count": 6, "metadata": {}, "output_type": "execute_result"}], "source": ["s2 = Series([42, 'Hello World!', 3.14, -5, None, numpy.nan], \n", " index=['int', 'string', 'pi', 'neg', 'missing1', 'missing2'])\n", "city2cp_dict = {'Paris14': 75014, 'Paris18': 75018, 'Malakoff': 92240, 'Nice': 6300}\n", "cities = Series(city2cp_dict)\n", "cities"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Quelques liens pour comprendre le code suivant : [Series.isnull](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.isnull.html), [Series.notnull](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.notnull.html)."]}, {"cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [{"data": {"text/plain": ["Paris12 False\n", "Paris14 True\n", "Paris18 True\n", "Malakoff True\n", "Nice True\n", "Vanves False\n", "dtype: bool"]}, "execution_count": 7, "metadata": {}, "output_type": "execute_result"}], "source": ["cities_list = ['Paris12'] + list(city2cp_dict.keys()) + ['Vanves']\n", "cities2 = Series(city2cp_dict, index=cities_list)\n", "pandas.isnull(cities2) #same as cities2.isnull()\n", "pandas.notnull(cities2)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On peut se servir de l'index pour s\u00e9lectionner une ou plusieurs valeurs de la [Series](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.html), \u00e9ventuellement pour en changer la valeur. On peut aussi appliquer des op\u00e9rations math\u00e9matiques, filtrer avec un bool\u00e9en, ou encore tester la pr\u00e9sence d'un \u00e9lement."]}, {"cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [{"data": {"text/plain": ["Paris12 75.0\n", "Paris14 75.0\n", "Paris18 75.0\n", "Malakoff 92.0\n", "Nice 6.0\n", "Vanves NaN\n", "dtype: float64"]}, "execution_count": 8, "metadata": {}, "output_type": "execute_result"}], "source": ["cities2['Nice'] # renvoie un scalaire\n", "cities2[['Malakoff', 'Paris14']] # renvoie une Series\n", "cities2['Paris12'] = 75012\n", "dep = cities2 // 1000 # // pour une division enti\u00e8re\n", "dep"]}, {"cell_type": "markdown", "metadata": {}, "source": ["[pandas](http://pandas.pydata.org/) aligne automatiquement les donn\u00e9es en utilisant l'index des [Series](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.html) lorsqu'on fait une op\u00e9ration sur des series."]}, {"cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Paris14 True\n", "Paris13 False\n"]}], "source": ["cities2[dep==75]\n", "\n", "print(\"Paris14\",'Paris14' in cities2)\n", "print(\"Paris13\",'Paris13' in cities2)"]}, {"cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [{"data": {"text/plain": ["Malakoff 184480.0\n", "Nice 12600.0\n", "Paris12 NaN\n", "Paris14 150028.0\n", "Paris18 150036.0\n", "Vanves NaN\n", "dtype: float64"]}, "execution_count": 10, "metadata": {}, "output_type": "execute_result"}], "source": ["#print(cities)\n", "#print(cities2)\n", "cities + cities2"]}, {"cell_type": "markdown", "metadata": {}, "source": ["*pandas* garde les lignes communes aux deux tables et additionnent les colonnes portant le m\u00eame nom. On peut nommer la [Series](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.html), ses index et m\u00eame assigner un nouvel index \u00e0 une [Series](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.html) existante."]}, {"cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Ville\n", "Paris12 75012.0\n", "Paris14 75014.0\n", "Paris18 75018.0\n", "Malakoff 92240.0\n", "Nice 6300.0\n", "Vanves NaN\n", "Name: Code Postal, dtype: float64\n", "-------------\n", "0 42\n", "1 Hello World!\n", "2 3.14\n", "3 -5\n", "4 None\n", "5 NaN\n", "dtype: object\n"]}], "source": ["cities2.name = \"Code Postal\"\n", "cities2.index.name = \"Ville\"\n", "print(cities2)\n", "print(\"-------------\")\n", "s2.index = range(6)\n", "print(s2)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## DataFrame (pandas)\n", "\n", "**Quelques liens :** [An Introduction to Pandas](http://synesthesiam.com/posts/an-introduction-to-pandas.html)\n", "\n", "Un [DataFrame](http://en.wikipedia.org/wiki/Data_frame) est un objet qui est pr\u00e9sent dans la plupart des logiciels de traitements de donn\u00e9es, c'est une **matrice**, chaque colonne est une [Series](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.html) et est de m\u00eame type (nombre, date, texte), elle peut contenir des valeurs manquantes ([nan](http://docs.scipy.org/doc/numpy/user/misc.html)). On peut consid\u00e9rer chaque colonne comme les variables d'une table ([pandas.Dataframe](http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.html) - cette page contient toutes les m\u00e9thodes de la classe).\n", "\n", "Un [Dataframe](http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.html) repr\u00e9sente une table de donn\u00e9es, i.e. une collection ordonn\u00e9es de colonnes.\n", "Ces colonnes/lignes peuvent avoir des types diff\u00e9rents (num\u00e9rique, string, boolean).\n", "Cela est tr\u00e8s similaire aux [DataFrame](http://www.r-tutor.com/r-introduction/data-frame) du langage R (en apparence...), avec un traitement plus sym\u00e9trique des lignes et des colonnes."]}, {"cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " date | \n", " devise | \n", " prix | \n", "
\n", " \n", " \n", " \n", " 0 | \n", " 2014-06-22 | \n", " euros | \n", " 220.0 | \n", "
\n", " \n", " 1 | \n", " 2014-06-23 | \n", " euros | \n", " 221.0 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" date devise prix\n", "0 2014-06-22 euros 220.0\n", "1 2014-06-23 euros 221.0"]}, "execution_count": 12, "metadata": {}, "output_type": "execute_result"}], "source": ["import pandas\n", "l = [ { \"date\":\"2014-06-22\", \"prix\":220.0, \"devise\":\"euros\" }, \n", " { \"date\":\"2014-06-23\", \"prix\":221.0, \"devise\":\"euros\" },]\n", "df = pandas.DataFrame(l)\n", "df"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Avec une valeur manquante :"]}, {"cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " date | \n", " devise | \n", " prix | \n", "
\n", " \n", " \n", " \n", " 0 | \n", " 2014-06-22 | \n", " euros | \n", " 220.0 | \n", "
\n", " \n", " 1 | \n", " 2014-06-23 | \n", " euros | \n", " NaN | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" date devise prix\n", "0 2014-06-22 euros 220.0\n", "1 2014-06-23 euros NaN"]}, "execution_count": 13, "metadata": {}, "output_type": "execute_result"}], "source": ["l = [ { \"date\":\"2014-06-22\", \"prix\":220.0, \"devise\":\"euros\" }, \n", " { \"date\":\"2014-06-23\", \"devise\":\"euros\" },]\n", "df = pandas.DataFrame(l)\n", "df"]}, {"cell_type": "markdown", "metadata": {}, "source": ["[NaN](http://docs.scipy.org/doc/numpy/reference/generated/numpy.isnan.html#numpy.isnan) est une convention pour une valeur manquante. On extrait la variable ``prix`` :"]}, {"cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [{"data": {"text/plain": ["0 220.0\n", "1 NaN\n", "Name: prix, dtype: float64"]}, "execution_count": 14, "metadata": {}, "output_type": "execute_result"}], "source": ["df.prix"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Ou :"]}, {"cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [{"data": {"text/plain": ["0 220.0\n", "1 NaN\n", "Name: prix, dtype: float64"]}, "execution_count": 15, "metadata": {}, "output_type": "execute_result"}], "source": ["df[\"prix\"]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Pour extraire plusieurs colonnes :"]}, {"cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " date | \n", " prix | \n", "
\n", " \n", " \n", " \n", " 0 | \n", " 2014-06-22 | \n", " 220.0 | \n", "
\n", " \n", " 1 | \n", " 2014-06-23 | \n", " NaN | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" date prix\n", "0 2014-06-22 220.0\n", "1 2014-06-23 NaN"]}, "execution_count": 16, "metadata": {}, "output_type": "execute_result"}], "source": ["df[[\"date\",\"prix\"]]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Pour prendre la transpos\u00e9e (voir aussi [DataFrame.transpose](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Panel.transpose.html)) :"]}, {"cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " 0 | \n", " 1 | \n", "
\n", " \n", " \n", " \n", " date | \n", " 2014-06-22 | \n", " 2014-06-23 | \n", "
\n", " \n", " devise | \n", " euros | \n", " euros | \n", "
\n", " \n", " prix | \n", " 220 | \n", " NaN | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" 0 1\n", "date 2014-06-22 2014-06-23\n", "devise euros euros\n", "prix 220 NaN"]}, "execution_count": 17, "metadata": {}, "output_type": "execute_result"}], "source": ["df.T"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Lecture et \u00e9criture de DataFrame\n", "\n", "Aujourd'hui, on n'a plus besoin de r\u00e9\u00e9crire soi-m\u00eame une fonction de lecture ou d'\u00e9criture de donn\u00e9es pr\u00e9sent\u00e9es sous forme de tables. Il existe des fonctions plus g\u00e9n\u00e9riques qui g\u00e8re un grand nombre de cas.\n", "Cette section pr\u00e9sente bri\u00e8vement les fonctions qui permettent de lire/\u00e9crire un [DataFrame](http://en.wikipedia.org/wiki/Data_frame) aux formats [texte](http://fr.wikipedia.org/wiki/Fichier_texte)/[Excel](http://fr.wikipedia.org/wiki/Microsoft_Excel). On reprend l'exemple de section pr\u00e9c\u00e9dente. L'instruction ``encoding=utf-8`` n'est pas obligatoire mais conseill\u00e9e lorsque les donn\u00e9es contiennent des accents (voir [read_csv](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.io.parsers.read_csv.html))."]}, {"cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["date\tdevise\tprix\n", "2014-06-22\teuros\t220.0\n", "2014-06-23\teuros\t221.0\n", "\n"]}, {"data": {"text/plain": ["[]"]}, "execution_count": 18, "metadata": {}, "output_type": "execute_result"}], "source": ["import pandas\n", "l = [ { \"date\":\"2014-06-22\", \"prix\":220.0, \"devise\":\"euros\" }, \n", " { \"date\":\"2014-06-23\", \"prix\":221.0, \"devise\":\"euros\" },]\n", "df = pandas.DataFrame(l)\n", "\n", "# \u00e9criture au format texte\n", "df.to_csv(\"exemple.txt\",sep=\"\\t\",encoding=\"utf-8\", index=False)\n", "\n", "# on regarde ce qui a \u00e9t\u00e9 enregistr\u00e9\n", "with open(\"exemple.txt\", \"r\", encoding=\"utf-8\") as f: \n", " text = f.read()\n", "print(text)\n", "\n", "# on enregistre au format Excel\n", "df.to_excel(\"exemple.xlsx\", index=False)\n", "\n", "# special jupyter - notebook\n", "%system \"exemple.xlsx\""]}, {"cell_type": "markdown", "metadata": {}, "source": ["On peut r\u00e9cup\u00e9rer des donn\u00e9es directement depuis Internet ou une cha\u00eene de caract\u00e8res et afficher le d\u00e9but ([head](pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.head.html)) ou la fin ([tail](pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.tail.html)).\n", "\n", "**Apart\u00e9 :** lire [StringIO](http://www.xavierdupre.fr/app/ensae_teaching_cs/helpsphinx/all_FAQ.html#a-quoi-sert-un-stringio)\n", "**Donn\u00e9es :** [marathon.txt](https://raw.githubusercontent.com/sdpython/ensae_teaching_cs/master/src/ensae_teaching_cs/data/data_1a/marathon.txt).\n", "\n", "La fonction [marathon](http://www.xavierdupre.fr/app/ensae_teaching_cs/helpsphinx/ensae_teaching_cs/data/data1a.html?highlight=marathon#ensae_teaching_cs.data.data1a.marathon) fait partie du module [ensae_teaching_cs](https://pypi.python.org/pypi/ensae_teaching_cs). Ce module n'est pas n\u00e9cessaire si on t\u00e9l\u00e9charge directement les donn\u00e9es, il automatise certains op\u00e9rations qui sans cela seraient manuelles. Pour l'installer ``pip install ensae_teaching_cs``."]}, {"cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " ville | \n", " annee | \n", " temps | \n", " secondes | \n", "
\n", " \n", " \n", " \n", " 0 | \n", " PARIS | \n", " 2011 | \n", " 02:06:29 | \n", " 7589 | \n", "
\n", " \n", " 1 | \n", " PARIS | \n", " 2010 | \n", " 02:06:41 | \n", " 7601 | \n", "
\n", " \n", " 2 | \n", " PARIS | \n", " 2009 | \n", " 02:05:47 | \n", " 7547 | \n", "
\n", " \n", " 3 | \n", " PARIS | \n", " 2008 | \n", " 02:06:40 | \n", " 7600 | \n", "
\n", " \n", " 4 | \n", " PARIS | \n", " 2007 | \n", " 02:07:17 | \n", " 7637 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" ville annee temps secondes\n", "0 PARIS 2011 02:06:29 7589\n", "1 PARIS 2010 02:06:41 7601\n", "2 PARIS 2009 02:05:47 7547\n", "3 PARIS 2008 02:06:40 7600\n", "4 PARIS 2007 02:07:17 7637"]}, "execution_count": 19, "metadata": {}, "output_type": "execute_result"}], "source": ["from ensae_teaching_cs.data import marathon\n", "import pandas\n", "df = pandas.read_csv(marathon(filename=True), \n", " sep=\"\\t\", names=[\"ville\", \"annee\", \"temps\",\"secondes\"])\n", "df.head()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["La fonction [describe](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.describe.html) permet d'en savoir un peu plus sur les colonnes num\u00e9riques de cette table."]}, {"cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " annee | \n", " secondes | \n", "
\n", " \n", " \n", " \n", " count | \n", " 359.000000 | \n", " 359.000000 | \n", "
\n", " \n", " mean | \n", " 1989.754875 | \n", " 7933.660167 | \n", "
\n", " \n", " std | \n", " 14.028545 | \n", " 385.289830 | \n", "
\n", " \n", " min | \n", " 1947.000000 | \n", " 7382.000000 | \n", "
\n", " \n", " 25% | \n", " 1981.000000 | \n", " 7698.000000 | \n", "
\n", " \n", " 50% | \n", " 1991.000000 | \n", " 7820.000000 | \n", "
\n", " \n", " 75% | \n", " 2001.000000 | \n", " 8046.500000 | \n", "
\n", " \n", " max | \n", " 2011.000000 | \n", " 10028.000000 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" annee secondes\n", "count 359.000000 359.000000\n", "mean 1989.754875 7933.660167\n", "std 14.028545 385.289830\n", "min 1947.000000 7382.000000\n", "25% 1981.000000 7698.000000\n", "50% 1991.000000 7820.000000\n", "75% 2001.000000 8046.500000\n", "max 2011.000000 10028.000000"]}, "execution_count": 20, "metadata": {}, "output_type": "execute_result"}], "source": ["df.describe()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### DataFrame et Index\n", "\n", "On d\u00e9signe g\u00e9n\u00e9ralement une colonne ou *variable* par son nom. Les lignes peuvent \u00eatre d\u00e9sign\u00e9es par un entier."]}, {"cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " date | \n", " devise | \n", " prix | \n", "
\n", " \n", " \n", " \n", " 0 | \n", " 2014-06-22 | \n", " euros | \n", " 220.0 | \n", "
\n", " \n", " 1 | \n", " 2014-06-23 | \n", " euros | \n", " 221.0 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" date devise prix\n", "0 2014-06-22 euros 220.0\n", "1 2014-06-23 euros 221.0"]}, "execution_count": 21, "metadata": {}, "output_type": "execute_result"}], "source": ["import pandas\n", "l = [ { \"date\":\"2014-06-22\", \"prix\":220.0, \"devise\":\"euros\" }, \n", " { \"date\":\"2014-06-23\", \"prix\":221.0, \"devise\":\"euros\" },]\n", "df = pandas.DataFrame(l)\n", "df"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On extrait une ligne avec ([iloc](http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.iloc.html))."]}, {"cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [{"data": {"text/plain": ["date 2014-06-23\n", "devise euros\n", "prix 221\n", "Name: 1, dtype: object"]}, "execution_count": 22, "metadata": {}, "output_type": "execute_result"}], "source": ["df.iloc[1]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On extrait une colonne avec [loc]( ou [iloc](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.loc.html)."]}, {"cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [{"data": {"text/plain": ["date 2014-06-23\n", "devise euros\n", "prix 221\n", "Name: 1, dtype: object"]}, "execution_count": 23, "metadata": {}, "output_type": "execute_result"}], "source": ["df.loc[1]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On extrait une valeur en indiquant sa position dans la table avec des entiers :"]}, {"cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [{"data": {"text/plain": ["221.0"]}, "execution_count": 24, "metadata": {}, "output_type": "execute_result"}], "source": ["df.iloc[1,2]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Avec [loc](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.loc.html), il faut pr\u00e9ciser le nombre de la colonne."]}, {"cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [{"data": {"text/plain": ["Index(['date', 'devise', 'prix'], dtype='object')"]}, "execution_count": 25, "metadata": {}, "output_type": "execute_result"}], "source": ["df.columns"]}, {"cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [{"data": {"text/plain": ["221.0"]}, "execution_count": 26, "metadata": {}, "output_type": "execute_result"}], "source": ["df.loc[1,\"prix\"]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Mais il est possible d'utiliser une colonne ou plusieurs colonnes comme index ([set_index](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.set_index.html)) :"]}, {"cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " devise | \n", " prix | \n", "
\n", " \n", " date | \n", " | \n", " | \n", "
\n", " \n", " \n", " \n", " 2014-06-22 | \n", " euros | \n", " 220.0 | \n", "
\n", " \n", " 2014-06-23 | \n", " euros | \n", " 221.0 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" devise prix\n", "date \n", "2014-06-22 euros 220.0\n", "2014-06-23 euros 221.0"]}, "execution_count": 27, "metadata": {}, "output_type": "execute_result"}], "source": ["dfi = df.set_index(\"date\")\n", "dfi"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On peut maintenant d\u00e9signer une ligne par une date avec [loc](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.loc.html) (mais pas [iloc](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.iloc.html) car [iloc](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.iloc.html) n'accepte que des entiers qui se r\u00e9f\u00e8re aux index de chaque dimension)."]}, {"cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [{"data": {"text/plain": ["devise euros\n", "prix 221\n", "Name: 2014-06-23, dtype: object"]}, "execution_count": 28, "metadata": {}, "output_type": "execute_result"}], "source": ["dfi.loc[\"2014-06-23\"]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Il est possible d'utiliser plusieurs colonnes comme [index](http://pandas.pydata.org/pandas-docs/stable/indexing.html) :"]}, {"cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [{"data": {"text/plain": ["arrondissement 18\n", "Name: (dupr\u00e9, xavier), dtype: int64"]}, "execution_count": 29, "metadata": {}, "output_type": "execute_result"}], "source": ["df = pandas.DataFrame([ {\"pr\u00e9nom\":\"xavier\", \"nom\":\"dupr\u00e9\", \"arrondissement\":18}, \n", " {\"pr\u00e9nom\":\"cl\u00e9mence\", \"nom\":\"dupr\u00e9\", \"arrondissement\":15 } ])\n", "dfi = df.set_index([\"nom\",\"pr\u00e9nom\"])\n", "dfi.loc[\"dupr\u00e9\",\"xavier\"]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Si on veut changer l'index ou le supprimer ([reset_index](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.reset_index.html)) :"]}, {"cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " | \n", " pr\u00e9nom | \n", "
\n", " \n", " nom | \n", " arrondissement | \n", " | \n", "
\n", " \n", " \n", " \n", " dupr\u00e9 | \n", " 18 | \n", " xavier | \n", "
\n", " \n", " 15 | \n", " cl\u00e9mence | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" pr\u00e9nom\n", "nom arrondissement \n", "dupr\u00e9 18 xavier\n", " 15 cl\u00e9mence"]}, "execution_count": 30, "metadata": {}, "output_type": "execute_result"}], "source": ["dfi.reset_index(drop=False, inplace=True) \n", " # le mot-cl\u00e9 drop pour garder ou non les colonnes servant d'index\n", " # inplace signifie qu'on modifie l'instance et non qu'une copie est modifi\u00e9e\n", " # donc on peut aussi \u00e9crire dfi2 = dfi.reset_index(drop=False) \n", "dfi.set_index([\"nom\", \"arrondissement\"],inplace=True)\n", "dfi"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Les index sont particuli\u00e8rement 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."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Notation avec le symbole ``:``\n", "\n", "Le symbole ``:`` d\u00e9signe une plage de valeurs."]}, {"cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " ville | \n", " annee | \n", " temps | \n", " secondes | \n", "
\n", " \n", " \n", " \n", " 0 | \n", " PARIS | \n", " 2011 | \n", " 02:06:29 | \n", " 7589 | \n", "
\n", " \n", " 1 | \n", " PARIS | \n", " 2010 | \n", " 02:06:41 | \n", " 7601 | \n", "
\n", " \n", " 2 | \n", " PARIS | \n", " 2009 | \n", " 02:05:47 | \n", " 7547 | \n", "
\n", " \n", " 3 | \n", " PARIS | \n", " 2008 | \n", " 02:06:40 | \n", " 7600 | \n", "
\n", " \n", " 4 | \n", " PARIS | \n", " 2007 | \n", " 02:07:17 | \n", " 7637 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" ville annee temps secondes\n", "0 PARIS 2011 02:06:29 7589\n", "1 PARIS 2010 02:06:41 7601\n", "2 PARIS 2009 02:05:47 7547\n", "3 PARIS 2008 02:06:40 7600\n", "4 PARIS 2007 02:07:17 7637"]}, "execution_count": 31, "metadata": {}, "output_type": "execute_result"}], "source": ["import pandas, urllib.request\n", "from ensae_teaching_cs.data import marathon\n", "df = pandas.read_csv(marathon(), sep=\"\\t\", names=[\"ville\", \"annee\", \"temps\",\"secondes\"])\n", "df.head()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On peut s\u00e9lectionner un sous-ensemble de lignes :"]}, {"cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " ville | \n", " annee | \n", " temps | \n", " secondes | \n", "
\n", " \n", " \n", " \n", " 3 | \n", " PARIS | \n", " 2008 | \n", " 02:06:40 | \n", " 7600 | \n", "
\n", " \n", " 4 | \n", " PARIS | \n", " 2007 | \n", " 02:07:17 | \n", " 7637 | \n", "
\n", " \n", " 5 | \n", " PARIS | \n", " 2006 | \n", " 02:08:03 | \n", " 7683 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" ville annee temps secondes\n", "3 PARIS 2008 02:06:40 7600\n", "4 PARIS 2007 02:07:17 7637\n", "5 PARIS 2006 02:08:03 7683"]}, "execution_count": 32, "metadata": {}, "output_type": "execute_result"}], "source": ["df[3:6]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On extrait la m\u00eame plage mais avec deux colonnes seulement :"]}, {"cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " annee | \n", " temps | \n", "
\n", " \n", " \n", " \n", " 3 | \n", " 2008 | \n", " 02:06:40 | \n", "
\n", " \n", " 4 | \n", " 2007 | \n", " 02:07:17 | \n", "
\n", " \n", " 5 | \n", " 2006 | \n", " 02:08:03 | \n", "
\n", " \n", " 6 | \n", " 2005 | \n", " 02:08:02 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" annee temps\n", "3 2008 02:06:40\n", "4 2007 02:07:17\n", "5 2006 02:08:03\n", "6 2005 02:08:02"]}, "execution_count": 33, "metadata": {}, "output_type": "execute_result"}], "source": ["df.loc[3:6,[\"annee\",\"temps\"]]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Le m\u00eame code pour lequel on renomme les colonnes extraites :"]}, {"cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " year | \n", " time | \n", "
\n", " \n", " \n", " \n", " 3 | \n", " 2008 | \n", " 02:06:40 | \n", "
\n", " \n", " 4 | \n", " 2007 | \n", " 02:07:17 | \n", "
\n", " \n", " 5 | \n", " 2006 | \n", " 02:08:03 | \n", "
\n", " \n", " 6 | \n", " 2005 | \n", " 02:08:02 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" year time\n", "3 2008 02:06:40\n", "4 2007 02:07:17\n", "5 2006 02:08:03\n", "6 2005 02:08:02"]}, "execution_count": 34, "metadata": {}, "output_type": "execute_result"}], "source": ["sub = df.loc[3:6,[\"annee\",\"temps\"]]\n", "sub.columns = [\"year\",\"time\"]\n", "sub"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Exercice 1 : cr\u00e9er un fichier Excel\n", " \n", "On souhaite r\u00e9cup\u00e9rer les donn\u00e9es [donnees_enquete_2003_television.txt](http://www.xavierdupre.fr/enseignement/complements/donnees_enquete_2003_television.txt) (source : [INSEE](http://www.insee.fr/fr/themes/detail.asp?ref_id=fd-hdv03&page=fichiers_detail/HDV03/telechargement.htm)).\n", "\n", "* ``POIDSLOG`` : Pond\u00e9ration individuelle relative\n", "* ``POIDSF`` : Variable de pond\u00e9ration individuelle\n", "* ``cLT1FREQ`` : Nombre d'heures en moyenne pass\u00e9es \u00e0 regarder la t\u00e9l\u00e9vision \n", "* ``cLT2FREQ`` : Unit\u00e9 de temps utilis\u00e9e pour compter le nombre d'heures pass\u00e9es \u00e0 regarder la t\u00e9l\u00e9vision, cette unit\u00e9 est repr\u00e9sent\u00e9e par les quatre valeurs suivantes\n", " * 0 : non concern\u00e9\n", " * 1 : jour\n", " * 2 : semaine\n", " * 3 : mois \n", " \n", "Ensuite, on veut :\n", "\n", "1. Supprimer les colonnes vides\n", "2. Obtenir les valeurs distinctes pour la colonne ``cLT2FREQ``\n", "3. Modifier la matrice pour enlever les lignes pour lesquelles l'unit\u00e9 de temps (cLT2FREQ) n'est pas renseign\u00e9e ou \u00e9gale \u00e0 z\u00e9ro.\n", "4. Sauver le r\u00e9sultat au format Excel.\n", "\n", "Vous aurez peut-\u00eatre besoin des fonctions suivantes :\n", "\n", "* [numpy.isnan](http://docs.scipy.org/doc/numpy/reference/generated/numpy.isnan.html)\n", "* [DataFrame.apply](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.apply.html)\n", "* [DataFrame.fillna](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.fillna.html) ou [DataFrame.isnull](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.isnull.html) ou [DataFrame.notnull](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.notnull.html)\n", "* [DataFrame.copy](http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.copy.html)"]}, {"cell_type": "code", "execution_count": 34, "metadata": {"collapsed": true}, "outputs": [], "source": ["import pandas, io\n", "# ..."]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Manipuler un DataFrame\n", "\n", "Si la structure *DataFrame* s'est impos\u00e9e, c'est parce qu'on effectue toujours les m\u00eames op\u00e9rations. Chaque fonction cache une boucle ou deux dont le co\u00fbt est pr\u00e9cis\u00e9 en fin de ligne :\n", "\n", "- **filter** : on s\u00e9lectionne un sous-ensemble de lignes qui v\u00e9rifie une condition $\\rightarrow O(n)$\n", "- **union** : concat\u00e9nation de deux jeux de donn\u00e9es $\\rightarrow O(n_1 + n_2)$\n", "- **sort** : tri $\\rightarrow O(n \\ln n)$\n", "- **group by** : grouper des lignes qui partagent une valeur commune $\\rightarrow O(n)$\n", "- **join** : fusionner deux jeux de donn\u00e9es en associant les lignes qui partagent une valeur commune $\\rightarrow \\in [O(n_1 + n_2), O(n_1 n_2)]$\n", "- **pivot** : utiliser des valeurs pr\u00e9sentes dans colonne comme noms de colonnes $\\rightarrow O(n)$\n", "\n", "Les 5 premi\u00e8res op\u00e9rations sont issues de la logique de manipulation des donn\u00e9es avec le langage [SQL](http://fr.wikipedia.org/wiki/Structured_Query_Language) (ou le logiciel [SAS](http://www.sas.com)). La derni\u00e8re correspond \u00e0 un [tableau crois\u00e9 dynamique](http://fr.wikipedia.org/wiki/Tableau_crois%C3%A9_dynamique). Pour illustrer ces op\u00e9rations, on prendre le [DataFrame](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.html) suivant :"]}, {"cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Index(['ville', 'annee', 'temps', 'secondes'], dtype='object')\n", "villes {'FUKUOKA', 'AMSTERDAM', 'CHICAGO', 'BERLIN', 'PARIS', 'LONDON', 'BOSTON', 'NEW YORK', 'STOCKOLM'}\n", "annee [1947, 1948, 1949, 1950, 1951, 1952, 1953, 1954, 1955, 1956] ...\n"]}], "source": ["import pandas\n", "from ensae_teaching_cs.data import marathon\n", "filename = marathon()\n", "df = pandas.read_csv(filename, sep=\"\\t\", names=[\"ville\", \"annee\", \"temps\",\"secondes\"])\n", "print(df.columns)\n", "print(\"villes\",set(df.ville))\n", "print(\"annee\",list(set(df.annee))[:10],\"...\")"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### 6 op\u00e9rations : filtrer, union, sort, group by, join, pivot"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### filter\n", "\n", "Filter consiste \u00e0 s\u00e9lectionner un sous-ensemble de lignes du dataframe. Pour filter sur plusieurs conditions, il faut utiliser les op\u00e9rateurs logique & (et), | (ou), ~ (non) (voir [Mapping Operators to Functions](https://docs.python.org/3.4/library/operator.html#mapping-operators-to-functions)).\n", "\n", "* [filter](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.filter.html), [mask](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.mask.html),[where](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.where.html)\n", "* [pandas: filter rows of DataFrame with operator chaining](http://stackoverflow.com/questions/11869910/pandas-filter-rows-of-dataframe-with-operator-chaining)\n", "* [Indexing and Selecting Data](http://pandas.pydata.org/pandas-docs/stable/indexing.html)"]}, {"cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " ville | \n", " annee | \n", " temps | \n", " secondes | \n", "
\n", " \n", " \n", " \n", " 112 | \n", " FUKUOKA | \n", " 1971 | \n", " 02:12:51 | \n", " 7971 | \n", "
\n", " \n", " 204 | \n", " NEW YORK | \n", " 1971 | \n", " 02:22:54 | \n", " 8574 | \n", "
\n", " \n", " 285 | \n", " BOSTON | \n", " 1971 | \n", " 02:18:45 | \n", " 8325 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" ville annee temps secondes\n", "112 FUKUOKA 1971 02:12:51 7971\n", "204 NEW YORK 1971 02:22:54 8574\n", "285 BOSTON 1971 02:18:45 8325"]}, "execution_count": 37, "metadata": {}, "output_type": "execute_result"}], "source": ["subset = df [ df.annee == 1971 ]\n", "subset.head()"]}, {"cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " ville | \n", " annee | \n", " temps | \n", " secondes | \n", "
\n", " \n", " \n", " \n", " 285 | \n", " BOSTON | \n", " 1971 | \n", " 02:18:45 | \n", " 8325 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" ville annee temps secondes\n", "285 BOSTON 1971 02:18:45 8325"]}, "execution_count": 38, "metadata": {}, "output_type": "execute_result"}], "source": ["subset = df [ (df.annee == 1971) & (df.ville == \"BOSTON\") ]\n", "subset.head()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Les derni\u00e8res versions de pandas ont introduit la m\u00e9thode [query](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.query.html) qui permet de r\u00e9duire encore l'\u00e9criture :"]}, {"cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " ville | \n", " annee | \n", " temps | \n", " secondes | \n", "
\n", " \n", " \n", " \n", " 285 | \n", " BOSTON | \n", " 1971 | \n", " 02:18:45 | \n", " 8325 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" ville annee temps secondes\n", "285 BOSTON 1971 02:18:45 8325"]}, "execution_count": 39, "metadata": {}, "output_type": "execute_result"}], "source": ["subset = df.query( '(annee == 1971) & (ville == \"BOSTON\")')\n", "subset.head()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### union\n", "\n", "union = concat\u00e9nation de deux [DataFrame](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.html) (qui n'ont pas n\u00e9cessairement les m\u00eames colonnes). On peut concat\u00e9ner les lignes ou les colonnes.\n", "\n", "* [concat](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.concat.html)\n", "* [Merge, join, and concatenate](http://pandas.pydata.org/pandas-docs/stable/merging.html)"]}, {"cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [{"data": {"text/plain": ["((359, 4), (718, 4))"]}, "execution_count": 40, "metadata": {}, "output_type": "execute_result"}], "source": ["concat_ligne = pandas.concat((df,df))\n", "df.shape,concat_ligne.shape"]}, {"cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [{"data": {"text/plain": ["((359, 4), (359, 8))"]}, "execution_count": 41, "metadata": {}, "output_type": "execute_result"}], "source": ["concat_col = pandas.concat((df,df), axis=1)\n", "df.shape,concat_col.shape"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### sort\n", "\n", "Sort = trier\n", "\n", "* [sort](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.sort_values.html)"]}, {"cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " ville | \n", " annee | \n", " temps | \n", " secondes | \n", "
\n", " \n", " \n", " \n", " 35 | \n", " BERLIN | \n", " 2011 | \n", " 02:03:38 | \n", " 7418 | \n", "
\n", " \n", " 325 | \n", " BOSTON | \n", " 2011 | \n", " 02:03:02 | \n", " 7382 | \n", "
\n", " \n", " 202 | \n", " LONDON | \n", " 2011 | \n", " 02:04:40 | \n", " 7480 | \n", "
\n", " \n", " 0 | \n", " PARIS | \n", " 2011 | \n", " 02:06:29 | \n", " 7589 | \n", "
\n", " \n", " 276 | \n", " STOCKOLM | \n", " 2011 | \n", " 02:14:07 | \n", " 8047 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" ville annee temps secondes\n", "35 BERLIN 2011 02:03:38 7418\n", "325 BOSTON 2011 02:03:02 7382\n", "202 LONDON 2011 02:04:40 7480\n", "0 PARIS 2011 02:06:29 7589\n", "276 STOCKOLM 2011 02:14:07 8047"]}, "execution_count": 42, "metadata": {}, "output_type": "execute_result"}], "source": ["tri = df.sort_values( [\"annee\", \"ville\"], ascending=[0,1])\n", "tri.head()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### group by\n", "\n", "Cette op\u00e9ration consiste \u00e0 grouper les lignes qui partagent une caract\u00e9ristique commune (une valeur dans une colonne ou plusieurs valeurs dans plusieurs colonnes). On peut conserver chaque groupe, ou calculer une somme, une moyenne, prendre la ou meilleures valeurs (top $k$ per group)...\n", "\n", "* [groupby](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.groupby.html)\n", "* [sum](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.sum.html), [cumsum](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.cumsum.html), [mean](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.mean.html), [count](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.count.html)\n", "* [SQL GROUP BY](http://sql.sh/cours/group-by)\n", "* [Group By: split-apply-combine](http://pandas.pydata.org/pandas-docs/dev/groupby.html)\n", "* [group by customis\u00e9](http://stackoverflow.com/questions/15322632/python-pandas-df-groupby-agg-column-reference-in-agg)\n", "* [fast way to get index of top-k elements of every column in a pandas dataframe](http://stackoverflow.com/questions/32188867/fast-way-to-get-index-of-top-k-elements-of-every-column-in-a-pandas-dataframe)\n"]}, {"cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [{"data": {"text/plain": [""]}, "execution_count": 43, "metadata": {}, "output_type": "execute_result"}], "source": ["gr = df.groupby(\"annee\")\n", "gr"]}, {"cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " ville | \n", " temps | \n", " secondes | \n", "
\n", " \n", " annee | \n", " | \n", " | \n", " | \n", "
\n", " \n", " \n", " \n", " 2011 | \n", " 5 | \n", " 5 | \n", " 5 | \n", "
\n", " \n", " 2010 | \n", " 9 | \n", " 9 | \n", " 9 | \n", "
\n", " \n", " 2009 | \n", " 9 | \n", " 9 | \n", " 9 | \n", "
\n", " \n", " 2008 | \n", " 9 | \n", " 9 | \n", " 9 | \n", "
\n", " \n", " 2007 | \n", " 9 | \n", " 9 | \n", " 9 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" ville temps secondes\n", "annee \n", "2011 5 5 5\n", "2010 9 9 9\n", "2009 9 9 9\n", "2008 9 9 9\n", "2007 9 9 9"]}, "execution_count": 44, "metadata": {}, "output_type": "execute_result"}], "source": ["nb = gr.count()\n", "nb.sort_index(ascending=False).head()"]}, {"cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " secondes | \n", "
\n", " \n", " annee | \n", " | \n", "
\n", " \n", " \n", " \n", " 2011 | \n", " 37916 | \n", "
\n", " \n", " 2010 | \n", " 68673 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" secondes\n", "annee \n", "2011 37916\n", "2010 68673"]}, "execution_count": 45, "metadata": {}, "output_type": "execute_result"}], "source": ["nb = gr.sum()\n", "nb.sort_index(ascending=False).head(n=2)"]}, {"cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " secondes | \n", "
\n", " \n", " annee | \n", " | \n", "
\n", " \n", " \n", " \n", " 2011 | \n", " 7583.200000 | \n", "
\n", " \n", " 2010 | \n", " 7630.333333 | \n", "
\n", " \n", " 2009 | \n", " 7652.555556 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" secondes\n", "annee \n", "2011 7583.200000\n", "2010 7630.333333\n", "2009 7652.555556"]}, "execution_count": 46, "metadata": {}, "output_type": "execute_result"}], "source": ["nb = gr.mean()\n", "nb.sort_index(ascending=False).head(n=3)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Si les nom des colonnes utilis\u00e9es lors de l'op\u00e9ration ne sont pas mentionn\u00e9s, implicitement, c'est l'index qui sera choisi. On peut aussi aggr\u00e9ger les informations avec une fonction personnalis\u00e9e."]}, {"cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " annee | \n", " secondes | \n", "
\n", " \n", " \n", " \n", " 62 | \n", " 2009 | \n", " 8134 | \n", "
\n", " \n", " 63 | \n", " 2010 | \n", " 7968 | \n", "
\n", " \n", " 64 | \n", " 2011 | \n", " 8047 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" annee secondes\n", "62 2009 8134\n", "63 2010 7968\n", "64 2011 8047"]}, "execution_count": 47, "metadata": {}, "output_type": "execute_result"}], "source": ["def max_entier(x):\n", " return int(max(x))\n", "nb = df[[\"annee\",\"secondes\"]].groupby(\"annee\").agg(max_entier).reset_index()\n", "nb.tail(n=3)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Ou encore consid\u00e9rer des aggr\u00e9gations diff\u00e9rentes pour chaque colonne :"]}, {"cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " ville | \n", " secondes | \n", "
\n", " \n", " annee | \n", " | \n", " | \n", "
\n", " \n", " \n", " \n", " 2009 | \n", " 9 | \n", " 8134 | \n", "
\n", " \n", " 2010 | \n", " 9 | \n", " 7968 | \n", "
\n", " \n", " 2011 | \n", " 5 | \n", " 8047 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" ville secondes\n", "annee \n", "2009 9 8134\n", "2010 9 7968\n", "2011 5 8047"]}, "execution_count": 48, "metadata": {}, "output_type": "execute_result"}], "source": ["nb = df[[\"annee\",\"ville\",\"secondes\"]].groupby(\"annee\").agg({ \"ville\":len, \"secondes\":max_entier})\n", "nb.tail(n=3)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On veut extraire les deux meilleurs temps par ville :"]}, {"cell_type": "code", "execution_count": 48, "metadata": {"collapsed": true}, "outputs": [], "source": ["series = df.groupby([\"ville\"]).apply(lambda r: r[\"secondes\"].nsmallest(2))"]}, {"cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [{"data": {"text/plain": ["[171,\n", " 170,\n", " 35,\n", " 38,\n", " 325,\n", " 324,\n", " 357,\n", " 347,\n", " 74,\n", " 75,\n", " 202,\n", " 200,\n", " 234,\n", " 222,\n", " 2,\n", " 0,\n", " 248,\n", " 251]"]}, "execution_count": 50, "metadata": {}, "output_type": "execute_result"}], "source": ["indices = [t[1] for t in series.index]\n", "indices"]}, {"cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " ville | \n", " annee | \n", " temps | \n", " secondes | \n", "
\n", " \n", " \n", " \n", " 171 | \n", " AMSTERDAM | \n", " 2010 | \n", " 02:05:44 | \n", " 7544 | \n", "
\n", " \n", " 170 | \n", " AMSTERDAM | \n", " 2009 | \n", " 02:06:18 | \n", " 7578 | \n", "
\n", " \n", " 35 | \n", " BERLIN | \n", " 2011 | \n", " 02:03:38 | \n", " 7418 | \n", "
\n", " \n", " 38 | \n", " BERLIN | \n", " 2008 | \n", " 02:03:59 | \n", " 7439 | \n", "
\n", " \n", " 325 | \n", " BOSTON | \n", " 2011 | \n", " 02:03:02 | \n", " 7382 | \n", "
\n", " \n", " 324 | \n", " BOSTON | \n", " 2010 | \n", " 02:05:52 | \n", " 7552 | \n", "
\n", " \n", " 357 | \n", " CHICAGO | \n", " 2009 | \n", " 02:05:41 | \n", " 7541 | \n", "
\n", " \n", " 347 | \n", " CHICAGO | \n", " 1999 | \n", " 02:05:42 | \n", " 7542 | \n", "
\n", " \n", " 74 | \n", " FUKUOKA | \n", " 2009 | \n", " 02:05:18 | \n", " 7518 | \n", "
\n", " \n", " 75 | \n", " FUKUOKA | \n", " 2008 | \n", " 02:06:10 | \n", " 7570 | \n", "
\n", " \n", " 202 | \n", " LONDON | \n", " 2011 | \n", " 02:04:40 | \n", " 7480 | \n", "
\n", " \n", " 200 | \n", " LONDON | \n", " 2009 | \n", " 02:05:10 | \n", " 7510 | \n", "
\n", " \n", " 234 | \n", " NEW YORK | \n", " 2001 | \n", " 02:07:43 | \n", " 7663 | \n", "
\n", " \n", " 222 | \n", " NEW YORK | \n", " 1989 | \n", " 02:08:01 | \n", " 7681 | \n", "
\n", " \n", " 2 | \n", " PARIS | \n", " 2009 | \n", " 02:05:47 | \n", " 7547 | \n", "
\n", " \n", " 0 | \n", " PARIS | \n", " 2011 | \n", " 02:06:29 | \n", " 7589 | \n", "
\n", " \n", " 248 | \n", " STOCKOLM | \n", " 1983 | \n", " 02:11:37 | \n", " 7897 | \n", "
\n", " \n", " 251 | \n", " STOCKOLM | \n", " 1986 | \n", " 02:12:33 | \n", " 7953 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" ville annee temps secondes\n", "171 AMSTERDAM 2010 02:05:44 7544\n", "170 AMSTERDAM 2009 02:06:18 7578\n", "35 BERLIN 2011 02:03:38 7418\n", "38 BERLIN 2008 02:03:59 7439\n", "325 BOSTON 2011 02:03:02 7382\n", "324 BOSTON 2010 02:05:52 7552\n", "357 CHICAGO 2009 02:05:41 7541\n", "347 CHICAGO 1999 02:05:42 7542\n", "74 FUKUOKA 2009 02:05:18 7518\n", "75 FUKUOKA 2008 02:06:10 7570\n", "202 LONDON 2011 02:04:40 7480\n", "200 LONDON 2009 02:05:10 7510\n", "234 NEW YORK 2001 02:07:43 7663\n", "222 NEW YORK 1989 02:08:01 7681\n", "2 PARIS 2009 02:05:47 7547\n", "0 PARIS 2011 02:06:29 7589\n", "248 STOCKOLM 1983 02:11:37 7897\n", "251 STOCKOLM 1986 02:12:33 7953"]}, "execution_count": 51, "metadata": {}, "output_type": "execute_result"}], "source": ["df.loc[indices]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### join (_merge_ ou fusion)\n", "\n", "Fusionner deux tables consiste \u00e0 apparier les lignes de la premi\u00e8re table avec celle de la seconde si certaines colonnes de ces lignes partagent les m\u00eames valeurs. On distingue quatre cas :\n", "\n", "* ``INNER JOIN`` - **inner** : on garde tous les appariements r\u00e9ussis \n", "* ``LEFT OUTER JOIN`` - **left** : on garde tous les appariements r\u00e9ussis et les lignes non appari\u00e9es de la table de gauche\n", "* ``RIGHT OUTER JOIN`` - **right** : on garde tous les appariements r\u00e9ussis et les lignes non appari\u00e9es de la table de droite\n", "* ``FULL OUTER JOIN`` - **outer** : on garde tous les appariements r\u00e9ussis et les lignes non appari\u00e9es des deux tables\n", "\n", "Exemples et documentation :\n", "* [merging, joining](http://pandas.pydata.org/pandas-docs/stable/merging.html#database-style-dataframe-joining-merging)\n", "* [join](http://pandas.pydata.org/pandas-docs/stable/pandas.DataFrame.join.html)\n", "* [merge](http://pandas.pydata.org/pandas-docs/stable/pandas.merge.html) ou [DataFrame.merge](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.merge.html)\n", "* [jointures SQL](http://sql.sh/cours/jointures) - illustrations avec graphiques en patates\n", "\n", "Si les noms des colonnes utilis\u00e9es lors de la fusion ne sont pas mentionn\u00e9s, implicitement, c'est l'index qui sera choisi. Pour les grandes tables (> 1.000.000 lignes), il est fortement recommand\u00e9 d'ajouter un index s'il n'existe pas avant de fusionner. **A quoi correspondent les quatre cas suivants (LEFT ou FULL ou RIGHT ou INNER) ?**"]}, {"cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [{"data": {"image/png": "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\n", "text/plain": [""]}, "execution_count": 52, "metadata": {}, "output_type": "execute_result"}], "source": ["from IPython.display import Image\n", "Image(\"patates.png\")"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On souhaite ajouter une colonne pays aux marathons se d\u00e9roulant dans les villes suivantes."]}, {"cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " C | \n", " V | \n", "
\n", " \n", " \n", " \n", " 0 | \n", " USA | \n", " BOSTON | \n", "
\n", " \n", " 1 | \n", " USA | \n", " NEW YORK | \n", "
\n", " \n", " 2 | \n", " Germany | \n", " BERLIN | \n", "
\n", " \n", " 3 | \n", " UK | \n", " LONDON | \n", "
\n", " \n", " 4 | \n", " France | \n", " PARIS | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" C V\n", "0 USA BOSTON\n", "1 USA NEW YORK\n", "2 Germany BERLIN\n", "3 UK LONDON\n", "4 France PARIS"]}, "execution_count": 53, "metadata": {}, "output_type": "execute_result"}], "source": ["values = [ {\"V\":'BOSTON', \"C\":\"USA\"}, \n", " {\"V\":'NEW YORK', \"C\":\"USA\"}, \n", " {\"V\":'BERLIN', \"C\":\"Germany\"}, \n", " {\"V\":'LONDON', \"C\":\"UK\"}, \n", " {\"V\":'PARIS', \"C\":\"France\"}]\n", "pays = pandas.DataFrame(values)\n", "pays"]}, {"cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " ville | \n", " annee | \n", " temps | \n", " secondes | \n", " C | \n", " V | \n", "
\n", " \n", " \n", " \n", " 0 | \n", " PARIS | \n", " 2011 | \n", " 02:06:29 | \n", " 7589 | \n", " France | \n", " PARIS | \n", "
\n", " \n", " 1 | \n", " PARIS | \n", " 2010 | \n", " 02:06:41 | \n", " 7601 | \n", " France | \n", " PARIS | \n", "
\n", " \n", " 192 | \n", " BOSTON | \n", " 2010 | \n", " 02:05:52 | \n", " 7552 | \n", " USA | \n", " BOSTON | \n", "
\n", " \n", " 193 | \n", " BOSTON | \n", " 2011 | \n", " 02:03:02 | \n", " 7382 | \n", " USA | \n", " BOSTON | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" ville annee temps secondes C V\n", "0 PARIS 2011 02:06:29 7589 France PARIS\n", "1 PARIS 2010 02:06:41 7601 France PARIS\n", "192 BOSTON 2010 02:05:52 7552 USA BOSTON\n", "193 BOSTON 2011 02:03:02 7382 USA BOSTON"]}, "execution_count": 54, "metadata": {}, "output_type": "execute_result"}], "source": ["dfavecpays = df.merge(pays, left_on=\"ville\", right_on=\"V\")\n", "pandas.concat([dfavecpays.head(n=2),dfavecpays.tail(n=2)])"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**Question :** \n", "\n", "* **Que changerait l'ajout du param\u00e8tre ``how='outer'`` dans ce cas ?**\n", "* **On cherche \u00e0 joindre deux tables A,B qui ont chacune trois cl\u00e9s distinctes : $c_1, c_2, c_3$. Il y a respectivement dans chaque table $A_i$ et $B_i$ lignes pour la cl\u00e9 $c_i$. Combien la table finale issue de la fusion des deux tables contiendra-t-elle de lignes ?**"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### pivot (tableau crois\u00e9 dynamique)\n", "\n", "Cette op\u00e9ration consiste \u00e0 cr\u00e9er une seconde table en utilisant utiliser les valeurs d'une colonne comme nom de colonnes. \n", "\n", "| A | B | C |\n", "| --- | --- | --- |\n", "| A1 | B1 | C1 |\n", "| A1 | B2 | C2 |\n", "| A2 | B1 | C3 |\n", "| A2 | B2 | C4 |\n", "| A2 | B3 | C5 |\n", "\n", "L'op\u00e9ration ``pivot(A,B,C)`` donnera :\n", "\n", "| A | B1 | B2 | B3 |\n", "| --- | --- | --- | --- |\n", "| A1 | C1 | C2 | |\n", "| A2 | C3 | C4 | C5 |\n", "\n", "* [pivot](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.pivot.html), [pivot_table](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.tools.pivot.pivot_table.html)\n", "* [Reshaping and Pivot Tables](http://pandas.pydata.org/pandas-docs/stable/reshaping.html)\n", "* [Tableau crois\u00e9 dynamique - wikip\u00e9dia](http://fr.wikipedia.org/wiki/Tableau_crois%C3%A9_dynamique)\n", "\n", "On applique cela aux marathons o\u00f9 on veut avoir les villes comme noms de colonnes et une ann\u00e9e par ligne."]}, {"cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " ville | \n", " AMSTERDAM | \n", " BERLIN | \n", " BOSTON | \n", " CHICAGO | \n", " FUKUOKA | \n", " LONDON | \n", " NEW YORK | \n", " PARIS | \n", " STOCKOLM | \n", "
\n", " \n", " annee | \n", " | \n", " | \n", " | \n", " | \n", " | \n", " | \n", " | \n", " | \n", " | \n", "
\n", " \n", " \n", " \n", " 1967 | \n", " None | \n", " None | \n", " 02:15:45 | \n", " None | \n", " 02:09:37 | \n", " None | \n", " None | \n", " None | \n", " None | \n", "
\n", " \n", " 1968 | \n", " None | \n", " None | \n", " 02:22:17 | \n", " None | \n", " 02:10:48 | \n", " None | \n", " None | \n", " None | \n", " None | \n", "
\n", " \n", " 1969 | \n", " None | \n", " None | \n", " 02:13:49 | \n", " None | \n", " 02:11:13 | \n", " None | \n", " None | \n", " None | \n", " None | \n", "
\n", " \n", " 1987 | \n", " 02:12:40 | \n", " 02:11:11 | \n", " 02:11:50 | \n", " None | \n", " 02:08:18 | \n", " 02:09:50 | \n", " 02:11:01 | \n", " 02:11:09 | \n", " 02:13:52 | \n", "
\n", " \n", " 1988 | \n", " 02:12:38 | \n", " 02:11:45 | \n", " 02:08:43 | \n", " 02:08:57 | \n", " 02:11:04 | \n", " 02:10:20 | \n", " 02:08:20 | \n", " 02:13:53 | \n", " 02:14:26 | \n", "
\n", " \n", " 1989 | \n", " 02:13:52 | \n", " 02:10:11 | \n", " 02:09:06 | \n", " 02:11:25 | \n", " 02:12:54 | \n", " 02:09:03 | \n", " 02:08:01 | \n", " 02:13:03 | \n", " 02:13:34 | \n", "
\n", " \n", " 2009 | \n", " 02:06:18 | \n", " 02:06:08 | \n", " 02:08:42 | \n", " 02:05:41 | \n", " 02:05:18 | \n", " 02:05:10 | \n", " 02:09:15 | \n", " 02:05:47 | \n", " 02:15:34 | \n", "
\n", " \n", " 2010 | \n", " 02:05:44 | \n", " 02:05:08 | \n", " 02:05:52 | \n", " 02:06:23 | \n", " 02:08:24 | \n", " 02:05:19 | \n", " 02:08:14 | \n", " 02:06:41 | \n", " 02:12:48 | \n", "
\n", " \n", " 2011 | \n", " None | \n", " 02:03:38 | \n", " 02:03:02 | \n", " None | \n", " None | \n", " 02:04:40 | \n", " None | \n", " 02:06:29 | \n", " 02:14:07 | \n", "
\n", " \n", "
\n", "
"], "text/plain": ["ville AMSTERDAM BERLIN BOSTON CHICAGO FUKUOKA LONDON NEW YORK \\\n", "annee \n", "1967 None None 02:15:45 None 02:09:37 None None \n", "1968 None None 02:22:17 None 02:10:48 None None \n", "1969 None None 02:13:49 None 02:11:13 None None \n", "1987 02:12:40 02:11:11 02:11:50 None 02:08:18 02:09:50 02:11:01 \n", "1988 02:12:38 02:11:45 02:08:43 02:08:57 02:11:04 02:10:20 02:08:20 \n", "1989 02:13:52 02:10:11 02:09:06 02:11:25 02:12:54 02:09:03 02:08:01 \n", "2009 02:06:18 02:06:08 02:08:42 02:05:41 02:05:18 02:05:10 02:09:15 \n", "2010 02:05:44 02:05:08 02:05:52 02:06:23 02:08:24 02:05:19 02:08:14 \n", "2011 None 02:03:38 02:03:02 None None 02:04:40 None \n", "\n", "ville PARIS STOCKOLM \n", "annee \n", "1967 None None \n", "1968 None None \n", "1969 None None \n", "1987 02:11:09 02:13:52 \n", "1988 02:13:53 02:14:26 \n", "1989 02:13:03 02:13:34 \n", "2009 02:05:47 02:15:34 \n", "2010 02:06:41 02:12:48 \n", "2011 02:06:29 02:14:07 "]}, "execution_count": 55, "metadata": {}, "output_type": "execute_result"}], "source": ["piv = df.pivot(\"annee\",\"ville\",\"temps\")\n", "pandas.concat([piv[20:23],piv[40:43],piv.tail(n=3)])"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Il existe une m\u00e9thode qui effectue l'op\u00e9ration inverse : [Dataframe.stack](http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.stack.html)."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Lambda fonctions\n", "\n", "Les [lambda expressions](https://docs.python.org/3.4/tutorial/controlflow.html#lambda-expressions) permettent une syntaxe plus l\u00e9g\u00e8re (syntactic sugar) pour d\u00e9clarer une fonction simple.\n", "Cela est tr\u00e8s utile pour passer une fonction en argument notamment. \n", "Par exemple pour trier sur le 2\u00e8me element d'un tuple."]}, {"cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]\n"]}], "source": ["pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]\n", "pairs.sort(key=lambda pair: pair[1])\n", "print(pairs)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On peut r\u00e9\u00e9crire le groupby aggr\u00e9g\u00e9 par ``max_entier`` en utilisant une fonction lambda"]}, {"cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " annee | \n", " secondes | \n", "
\n", " \n", " \n", " \n", " 62 | \n", " 2009 | \n", " 8134 | \n", "
\n", " \n", " 63 | \n", " 2010 | \n", " 7968 | \n", "
\n", " \n", " 64 | \n", " 2011 | \n", " 8047 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" annee secondes\n", "62 2009 8134\n", "63 2010 7968\n", "64 2011 8047"]}, "execution_count": 57, "metadata": {}, "output_type": "execute_result"}], "source": ["def max_entier(x):\n", " return int(max(x))\n", "nb = df[[\"annee\",\"secondes\"]].groupby(\"annee\").agg(max_entier).reset_index()\n", "nb.tail(n=3)\n", "#same as:\n", "nb = df[[\"annee\",\"secondes\"]].groupby(\"annee\").agg(lambda x: int(max(x))).reset_index()\n", "nb.tail(n=3)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Exercice 2 : lambda fonction\n", "\n", "Ecrire une lambda fonction qui prend deux param\u00e8tres et qui est \u00e9quivalente \u00e0 la fonction suivante :"]}, {"cell_type": "code", "execution_count": 57, "metadata": {"collapsed": true}, "outputs": [], "source": ["def delta(x,y):\n", " return max(x,y)- min(x,y)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On utilise beaucoup les lambda fonctions lorsqu'une fonction prend une fonction en argument :"]}, {"cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [{"data": {"text/plain": ["0.3328334999999999"]}, "execution_count": 59, "metadata": {}, "output_type": "execute_result"}], "source": ["def riemann (a,b,f,n):\n", " return sum ( f(a + (b-a)*i/n) for i in range(0,n) ) / n\n", "riemann(0,1, lambda x : x**2, 1000)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Ensuite, il faut utiliser une lambda fonction et la fonction [apply](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.apply.html) pour tirer un \u00e9chantillon al\u00e9atoire"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Exercice 3 : moyennes par groupes\n", " \n", "Toujours avec le m\u00eame jeu de donn\u00e9es ([marathon.txt](http://www.xavierdupre.fr/enseignement/complements/marathon.txt)), on veut ajouter une ligne \u00e0 la fin du tableau crois\u00e9 dynamique contenant la moyenne en secondes des temps des marathons pour chaque ville."]}, {"cell_type": "code", "execution_count": 59, "metadata": {"collapsed": true}, "outputs": [], "source": []}, {"cell_type": "markdown", "metadata": {}, "source": ["### Avec ou sans index\n", "\n", "Une fa\u00e7on na\u00efve de faire une jointure entre deux tables de taille $m$ et $n$ et de regarder toutes les $mn$ combinaisons possibles. La taille de la table r\u00e9sultante d\u00e9pend du type de jointure (``inner``, ``outer``) et de l'unicit\u00e9 des cl\u00e9s utilis\u00e9es pour la jointure. Si les cl\u00e9s sont uniques, la table finale aura au plus $m+n$ lignes (une par cl\u00e9).\n", "\n", "Dans la plupart des cas, $O(mn)$ op\u00e9rations est beaucoup trop long. On peut faire plus rapide en triant chacune des tables d'abord et en les fusionnant : $O(n \\ln n) + O(m \\ln m) + O(n+m)$. Si $m=n$, il est \u00e9vident que cette fa\u00e7on de faire est plus rapide. C'est une des choses que fait [pandas (pr\u00e9sentation)](http://fr.slideshare.net/wesm/a-look-at-pandas-design-and-development) (voir aussi [klib](https://github.com/attractivechaos/klib)). \n", "\n", "On peut trier une table selon une cl\u00e9 ou encore utiliser une [table de hachage](http://fr.wikipedia.org/wiki/Table_de_hachage)), il est alors tr\u00e8s rapide de retrouver la ligne ou les lignes qui partagent cette cl\u00e9. On dit que la table est **index\u00e9e** selon cette cl\u00e9. Indexer selon une ou plusieurs colonnes une table acc\u00e9l\u00e8re toute op\u00e9ration s'appuyant sur ces colonnes comme la recherche d'un \u00e9l\u00e9ment.\n", "\n", "On veut comparer le temps n\u00e9cessaire pour une recherche. Pour cela on utilise la ``%magic`` function ``%timeit`` (ou ``%%timeit`` si on veut l'[appliquer \u00e0 la cellule](http://nbviewer.jupyter.org/github/jupyter/jupyter/blob/1.x/examples/notebooks/Cell%20Magics.ipynb)) de Jupyter."]}, {"cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [{"data": {"text/plain": ["(100000, 3)"]}, "execution_count": 61, "metadata": {}, "output_type": "execute_result"}], "source": ["import pandas, random\n", "big_df = pandas.DataFrame( {\"cle1\": random.randint(1,100), \n", " \"cle2\": random.randint(1,100), \n", " \"autre\":random.randint(1,10) } for i in range(0,100000) )\n", "big_df.shape"]}, {"cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["1.07 ms \u00b1 77.7 \u00b5s per loop (mean \u00b1 std. dev. of 7 runs, 1000 loops each)\n"]}], "source": ["%timeit big_df[(big_df.cle1 == 1) & (big_df.cle2 == 1)]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Et la version index\u00e9e :"]}, {"cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["374 \u00b5s \u00b1 22.6 \u00b5s per loop (mean \u00b1 std. dev. of 7 runs, 1000 loops each)\n"]}], "source": ["big_dfi = big_df.set_index([\"cle1\", \"cle2\"])\n", "big_dfi = big_dfi.sort_index() # Il ne faut oublier de trier.\n", "%timeit big_dfi.loc[(1,1), :]"]}, {"cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " | \n", " autre | \n", "
\n", " \n", " cle1 | \n", " cle2 | \n", " | \n", "
\n", " \n", " \n", " \n", " 1 | \n", " 1 | \n", " 6 | \n", "
\n", " \n", " 1 | \n", " 1 | \n", "
\n", " \n", " 1 | \n", " 2 | \n", "
\n", " \n", " 1 | \n", " 10 | \n", "
\n", " \n", " 1 | \n", " 5 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" autre\n", "cle1 cle2 \n", "1 1 6\n", " 1 1\n", " 1 2\n", " 1 10\n", " 1 5"]}, "execution_count": 64, "metadata": {}, "output_type": "execute_result"}], "source": ["big_dfi.head()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**Plus la table est grande, plus le gain est important.**"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Dates\n", "\n", "Les dates sont souvent compliqu\u00e9es \u00e0 g\u00e9rer car on n'utilise pas le m\u00eames format dans tous les pays. Pour faire simple, je recommande deux options :\n", "\n", "* Soit convertir les dates/heures au format cha\u00eenes de caract\u00e8res ``AAAA-MM-JJ hh:mm:ss:ms`` qui permet de trier les dates par ordre croissant.\n", "* Soit convertir les dates/heures au format [datetime](https://docs.python.org/3/library/datetime.html) (date) ou [timedelta](https://docs.python.org/3/library/datetime.html#timedelta-objects) (dur\u00e9e) (voir [Quelques notions sur les dates](http://www.xavierdupre.fr/blog/notebooks/example%20pyensae.html#date), [format de date/heure](https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior)).\n", "\n", "Par exemple, voici le code qui a permis de g\u00e9n\u00e9rer la colonne seconde de la table marathon :"]}, {"cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " ville | \n", " annee | \n", " temps | \n", " secondes | \n", "
\n", " \n", " \n", " \n", " 0 | \n", " PARIS | \n", " 2011 | \n", " 02:06:29 | \n", " 7589.0 | \n", "
\n", " \n", " 1 | \n", " PARIS | \n", " 2010 | \n", " 02:06:41 | \n", " 7601.0 | \n", "
\n", " \n", " 2 | \n", " PARIS | \n", " 2009 | \n", " 02:05:47 | \n", " 7547.0 | \n", "
\n", " \n", " 3 | \n", " PARIS | \n", " 2008 | \n", " 02:06:40 | \n", " 7600.0 | \n", "
\n", " \n", " 4 | \n", " PARIS | \n", " 2007 | \n", " 02:07:17 | \n", " 7637.0 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" ville annee temps secondes\n", "0 PARIS 2011 02:06:29 7589.0\n", "1 PARIS 2010 02:06:41 7601.0\n", "2 PARIS 2009 02:05:47 7547.0\n", "3 PARIS 2008 02:06:40 7600.0\n", "4 PARIS 2007 02:07:17 7637.0"]}, "execution_count": 65, "metadata": {}, "output_type": "execute_result"}], "source": ["from datetime import datetime, time\n", "df = pandas.read_csv(marathon(), sep=\"\\t\", names=[\"ville\", \"annee\", \"temps\",\"secondes\"])\n", "df = df [[\"ville\", \"annee\", \"temps\"]] # on enl\u00e8ve la colonne secondes pour la recr\u00e9er\n", "df[\"secondes\"] = df.apply( lambda r : (datetime.strptime(r.temps,\"%H:%M:%S\") - \\\n", " datetime(1900,1,1)).total_seconds(), axis=1)\n", "df.head()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Plot(s)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### R\u00e9cup\u00e9ration des donn\u00e9es\n", "\n", "On r\u00e9cup\u00e8re les donn\u00e9es disponibles sur le site de l'INSEE : [Naissance, d\u00e9c\u00e8s, mariages 2012](http://www.insee.fr/fr/themes/detail.asp?ref_id=fd-etatcivil2012&page=fichiers_detail/etatcivil2012/doc/documentation.htm). Il s'agit de r\u00e9cup\u00e9rer la liste des mariages de l'ann\u00e9e 2012. On souhaite repr\u00e9senter le graphe du nombre de mariages en fonction de l'\u00e9cart entre les mari\u00e9s."]}, {"cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Download of etatcivil2012_mar2012_dbase.zip: DONE!\n", "Download of etatcivil2012_nais2012_dbase.zip: DONE!\n", "Download of etatcivil2012_dec2012_dbase.zip: DONE!\n"]}], "source": ["import urllib.request\n", "import zipfile\n", "import http.client\n", "\n", "def download_and_save(name, root_url):\n", " try:\n", " response = urllib.request.urlopen(root_url+name)\n", " except (TimeoutError, urllib.request.URLError, http.client.BadStatusLine):\n", " # back up plan\n", " root_url = \"http://www.xavierdupre.fr/enseignement/complements/\"\n", " response = urllib.request.urlopen(root_url+name)\n", " with open(name, \"wb\") as outfile:\n", " outfile.write(response.read())\n", "\n", "def unzip(name):\n", " with zipfile.ZipFile(name, \"r\") as z:\n", " z.extractall(\".\")\n", "\n", "filenames = [\"etatcivil2012_mar2012_dbase.zip\", \n", " \"etatcivil2012_nais2012_dbase.zip\",\n", " \"etatcivil2012_dec2012_dbase.zip\", ]\n", "root_url = 'http://telechargement.insee.fr/fichiersdetail/etatcivil2012/dbase/'\n", "\n", "for filename in filenames:\n", " download_and_save(filename, root_url)\n", " unzip(filename)\n", " print(\"Download of {}: DONE!\".format(filename))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["L'exemple suivant pourrait ne pas marcher si le module [dbfread](https://github.com/olemb/dbfread/) n'est pas install\u00e9. Si tel est le cas, le programme utilisera une version des donn\u00e9es apr\u00e8s utilisation de ce module."]}, {"cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["use of dbfread\n"]}, {"data": {"text/plain": ["((246123, 16),\n", " Index(['ANAISH', 'DEPNAISH', 'INDNATH', 'ETAMATH', 'ANAISF', 'DEPNAISF',\n", " 'INDNATF', 'ETAMATF', 'AMAR', 'MMAR', 'JSEMAINE', 'DEPMAR', 'DEPDOM',\n", " 'TUDOM', 'TUCOM', 'NBENFCOM'],\n", " dtype='object'))"]}, "execution_count": 67, "metadata": {}, "output_type": "execute_result"}], "source": ["import pandas\n", "try:\n", " from dbfread import DBF\n", " use_dbfread = True\n", "except ImportError as e :\n", " use_dbfread = False\n", " \n", "if use_dbfread:\n", " print(\"use of dbfread\")\n", " def dBase2df(dbase_filename):\n", " table = DBF(dbase_filename, load=True, encoding=\"cp437\")\n", " return pandas.DataFrame(table.records)\n", "\n", " df = dBase2df('mar2012.dbf')\n", " #df.to_csv(\"mar2012.txt\", sep=\"\\t\", encoding=\"utf8\", index=False)\n", "else :\n", " print(\"use of zipped version\")\n", " import pyensae.datasource\n", " data = pyensae.datasource.download_data(\"mar2012.zip\")\n", " df = pandas.read_csv(data[0], sep=\"\\t\", encoding=\"utf8\", low_memory = False) \n", " \n", "df.shape, df.columns"]}, {"cell_type": "markdown", "metadata": {}, "source": ["L'[encoding](http://fr.wikipedia.org/wiki/Codage_des_caract%C3%A8res) est une fa\u00e7on de repr\u00e9senter les caract\u00e8res sp\u00e9ciaux (comme les caract\u00e8res accentu\u00e9es). L'encoding le plus r\u00e9pandu est ``utf-8``. Sans la mention ``encoding=\"cp437\"``, la fonction qui lit le fichier fait des erreurs lors de la lecture car elle ne sait pas comment interpr\u00e9ter certains caract\u00e8res sp\u00e9ciaux. On r\u00e9cup\u00e8re de la m\u00eame mani\u00e8re la signification des variables :"]}, {"cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["(16, 4) Index(['VARIABLE', 'LIBELLE', 'TYPE', 'LONGUEUR'], dtype='object')\n"]}, {"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " | \n", " VARIABLE | \n", " LIBELLE | \n", " TYPE | \n", " LONGUEUR | \n", "
\n", " \n", " \n", " \n", " 0 | \n", " AMAR | \n", " Ann\u00e9e du mariage | \n", " CHAR | \n", " 4 | \n", "
\n", " \n", " 1 | \n", " ANAISF | \n", " Ann\u00e9e de naissance de l'\u00e9pouse | \n", " CHAR | \n", " 4 | \n", "
\n", " \n", " 2 | \n", " ANAISH | \n", " Ann\u00e9e de naissance de l'\u00e9poux | \n", " CHAR | \n", " 4 | \n", "
\n", " \n", " 3 | \n", " DEPDOM | \n", " D\u00e9partement de domicile apr\u00e8s le mariage | \n", " CHAR | \n", " 3 | \n", "
\n", " \n", " 4 | \n", " DEPMAR | \n", " D\u00e9partement de mariage | \n", " CHAR | \n", " 3 | \n", "
\n", " \n", " 5 | \n", " DEPNAISF | \n", " D\u00e9partement de naissance de l'\u00e9pouse | \n", " CHAR | \n", " 3 | \n", "
\n", " \n", " 6 | \n", " DEPNAISH | \n", " D\u00e9partement de naissance de l'\u00e9poux | \n", " CHAR | \n", " 3 | \n", "
\n", " \n", " 7 | \n", " ETAMATF | \n", " \u00c9tat matrimonial ant\u00e9rieur de l'\u00e9pouse | \n", " CHAR | \n", " 1 | \n", "
\n", " \n", " 8 | \n", " ETAMATH | \n", " \u00c9tat matrimonial ant\u00e9rieur de l'\u00e9poux | \n", " CHAR | \n", " 1 | \n", "
\n", " \n", " 9 | \n", " INDNATF | \n", " Indicateur de nationalit\u00e9 de l'\u00e9pouse | \n", " CHAR | \n", " 1 | \n", "
\n", " \n", " 10 | \n", " INDNATH | \n", " Indicateur de nationalit\u00e9 de l'\u00e9poux | \n", " CHAR | \n", " 1 | \n", "
\n", " \n", " 11 | \n", " JSEMAINE | \n", " Jour du mariage dans la semaine | \n", " CHAR | \n", " 1 | \n", "
\n", " \n", " 12 | \n", " MMAR | \n", " Mois du mariage | \n", " CHAR | \n", " 2 | \n", "
\n", " \n", " 13 | \n", " NBENFCOM | \n", " Enfants en commun avant le mariage | \n", " CHAR | \n", " 1 | \n", "
\n", " \n", " 14 | \n", " TUCOM | \n", " Tranche de commune du lieu de domicile des \u00e9poux | \n", " CHAR | \n", " 1 | \n", "
\n", " \n", " 15 | \n", " TUDOM | \n", " Tranche d'unit\u00e9 urbaine du lieu de domicile de... | \n", " CHAR | \n", " 1 | \n", "
\n", " \n", "
\n", "
"], "text/plain": [" VARIABLE LIBELLE TYPE \\\n", "0 AMAR Ann\u00e9e du mariage CHAR \n", "1 ANAISF Ann\u00e9e de naissance de l'\u00e9pouse CHAR \n", "2 ANAISH Ann\u00e9e de naissance de l'\u00e9poux CHAR \n", "3 DEPDOM\u00a0 D\u00e9partement de domicile apr\u00e8s le mariage CHAR \n", "4 DEPMAR D\u00e9partement de mariage CHAR \n", "5 DEPNAISF D\u00e9partement de naissance de l'\u00e9pouse CHAR \n", "6 DEPNAISH D\u00e9partement de naissance de l'\u00e9poux CHAR \n", "7 ETAMATF \u00c9tat matrimonial ant\u00e9rieur de l'\u00e9pouse CHAR \n", "8 ETAMATH \u00c9tat matrimonial ant\u00e9rieur de l'\u00e9poux CHAR \n", "9 INDNATF Indicateur de nationalit\u00e9 de l'\u00e9pouse CHAR \n", "10 INDNATH Indicateur de nationalit\u00e9 de l'\u00e9poux CHAR \n", "11 JSEMAINE Jour du mariage dans la semaine CHAR \n", "12 MMAR Mois du mariage CHAR \n", "13 NBENFCOM Enfants en commun avant le mariage CHAR \n", "14 TUCOM Tranche de commune du lieu de domicile des \u00e9poux CHAR \n", "15 TUDOM Tranche d'unit\u00e9 urbaine du lieu de domicile de... CHAR \n", "\n", " LONGUEUR \n", "0 4 \n", "1 4 \n", "2 4 \n", "3 3 \n", "4 3 \n", "5 3 \n", "6 3 \n", "7 1 \n", "8 1 \n", "9 1 \n", "10 1 \n", "11 1 \n", "12 2 \n", "13 1 \n", "14 1 \n", "15 1 "]}, "execution_count": 68, "metadata": {}, "output_type": "execute_result"}], "source": ["vardf = dBase2df(\"varlist_mariages.dbf\")\n", "print(vardf.shape, vardf.columns)\n", "vardf"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Exercice 4 : nuage de points\n", "\n", "On veut tracer un nuage de points avec en abscisse l'\u00e2ge du mari, en ordonn\u00e9e, l'\u00e2ge de la femme. Il faudra peut-\u00eatre jeter un coup d'oeil sur la documentation de la m\u00e9thode [plot](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.plot.html). Etant donn\u00e9 le nombre d'observations, ce graphe risque d'\u00eatre moins lisible qu'une [heatmap](http://pandas.pydata.org/pandas-docs/stable/visualization.html#visualization-hexbin)."]}, {"cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [], "source": ["df.plot(...)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Exercice 5 : graphe d'une distribution avec pandas\n", "\n", "En ajoutant une colonne et en utilisant l'op\u00e9ration [group by](http://pandas.pydata.org/pandas-docs/stable/groupby.html), on veut obtenir la distribution du nombre de mariages en fonction de l'\u00e9cart entre les mari\u00e9s. Au besoin, on changera le type d'une colone ou deux. Le module ``pandas`` propose un panel de graphiques standard faciles \u00e0 obtenir. On souhaite repr\u00e9senter la distribution sous forme d'histogramme. A vous de choisir le meilleure graphique depuis la page [Visualization](http://pandas.pydata.org/pandas-docs/stable/visualization.html)."]}, {"cell_type": "code", "execution_count": 69, "metadata": {"collapsed": true}, "outputs": [], "source": ["df[\"colonne\"] = df.apply (lambda r: int(r[\"colonne\"]), axis=1) # pour changer de type\n", "df[\"difference\"] = ..."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### matplotlib\n", "\n", "[matplotlib](http://matplotlib.org/) est le module qu'utilise [pandas](http://pandas.pydata.org/). Ainsi, la m\u00e9thode [plot](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.plot.html) retourne un objet de type [Axes](http://matplotlib.org/api/axes_api.html#module-matplotlib.axes) qu'on peut modifier par la suite via les [m\u00e9thodes suivantes](http://matplotlib.org/api/pyplot_summary.html). On peut ajouter un titre avec [set_title](http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.set_title) ou ajouter une grille avec [grid](http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.grid). On peut \u00e9galement superposer [deux courbes sur le m\u00eame graphique](http://stackoverflow.com/questions/19941685/how-to-show-a-bar-and-line-graph-on-the-same-plot), ou [changer de taille de caract\u00e8res](http://stackoverflow.com/questions/12444716/how-do-i-set-figure-title-and-axes-labels-font-size-in-matplotlib). Le code suivant trace le nombre de mariages par d\u00e9partement."]}, {"cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [{"data": {"image/png": "iVBORw0KGgoAAAANSUhEUgAABCQAAAGRCAYAAAC9qWJoAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XlcVXX+x/H3ZVW5ILuI4hYquQUNjuiUINGqY2ZW0+SM\nmbaMTeUyTTb9prKZit/Mz9yzGS1p2tQcpbLUIASa1MIFxywVU1MbVyATsGT5/v7ox/1xBeVywQPq\n6/l4+Ji5Xz7nnM85d4H77nvOsRljjAAAAAAAACzk0dwNAAAAAACASw+BBAAAAAAAsByBBAAAAAAA\nsByBBAAAAAAAsByBBAAAAAAAsByBBAAAAAAAsByBBAAAQDM4evSo2rdvrz/+8Y/N3QoAAM2CQAIA\n0KJ16dJFo0ePbu42nOzbt082m01paWnN3UqTSEtLk81m0759+5q7lYtKUlKSkpKS6vyZMUZjxoxR\nYmKinnnmGWsbqyE9PV0vvPBCs22/qaWlpemVV15p7jYAAC4ikAAA4BI3dOhQrV+/Xu3bt2/uVi4Z\nL7zwgr799ltHGNRcCCQAAM2JQAIAcNH64YcfmruFFq28vFzGGIWFhSkhIUG+vr7N3VKL1dSvpSlT\npmj9+vVq1apVk67XVbw3AAAtAYEEAMDJ008/LZvNpoKCAg0dOlR2u12dO3fWM888o6qqKqfanTt3\n6pZbblFgYKBat26thIQErV69us717dixQ9dff738/PzUqVMnLVq0SJL02muvKSYmRna7XUOGDNFX\nX31VZ18LFixQdHS0WrVqpSuvvFJr1651+vndd9+tjh07av369Ro0aJBat26t3//+946f//3vf9cV\nV1yhVq1aKTQ0VOPGjVNRUVG9x6OsrEwTJkxQSEiI7Ha7hg8froMHD9ZZm5OTo2uuuUb+/v7y8/PT\n9ddfr88//7zebVT3vnHjRkfvPXv21Pvvvy/px/+a3qVLFwUEBOjmm2/WsWPHnJafO3euBg4cqODg\nYAUGBiohIcGxbLXq00xefPFF/f73v1dkZKR8fX2d/it9zVM2Fi9erOTkZIWFhclutysuLk6vvvpq\nrd6PHTumO++8UwEBAQoKCtLYsWP17rvvymazKTs726l2+fLlSkhIUJs2bRQYGKjbbrtN+/fvd6p5\n8803FRcXJ7vdroCAAPXt21d/+9vfznn8ql9j27Zt05AhQ9SmTRu1b99eTz75pNNr9vvvv9ekSZPU\np08f2e12RURE6Oc//7l27NjhtL7q45Gbm6vbbrtNgYGBGjBgwDl7WLx4sWJiYuTr66vevXtrxYoV\nddYdO3ZMDzzwgDp06CBfX1/FxMTo73//+1m3P2LECNntdoWEhOjBBx/UqVOnnGqfeuopXXnllQoI\nCFBoaKiSk5O1YcMGp5rs7GzZbDYtX75c9957r8LCwtSuXTvdfffdevXVV/XNN9/IZrPJZrOpS5cu\nbvW6bt063X777fL391e7du30/PPPS5JWr16tuLg4+fn5qX///tq0aVOtY+LK66L61K3Fixfr8ssv\nl5+fn+Lj4/Wvf/3LUZOUlKScnBx98sknjv2pPmXm8OHDGjNmjON13759ew0bNkxHjx6t83kCAFjE\nAABQw1NPPWUkmd69e5v/+Z//MRkZGebhhx82kswrr7ziqPvmm29MaGio6dq1q3nttdfMu+++a66/\n/nrj4eFhPvjgg1rr69Onj5k1a5b58MMPzYgRI4wk8/jjj5uBAweaFStWmKVLl5r27dubn/70p079\ndO7c2XTo0MHExMSYxYsXmxUrVpiEhATj6+trduzY4agbM2aMsdvtplOnTmb27Nlm7dq1ZsOGDcYY\nYx577DHj5eVlJk+ebNasWWNeeeUVExkZaX7605+aioqKcx6P0aNHG29vb/PnP//ZrFmzxvzud78z\nUVFRRpJZtGiRo27lypXG09PTDB8+3KSnp5v09HQzcOBAExgYaPbv33/ObYwZM8b4+/ubyy+/3Lz8\n8stm1apV5qqrrjK+vr5m8uTJZtiwYWblypXm5ZdfNv7+/ua2225zWn7KlClm4cKFJjMz06xevdo8\n+OCDRpJZtWqVo2bv3r1GkomMjDQ333yzee+990x6eropKyszixYtMpLM3r17HfXPPvusmTdvnlmz\nZo3JyMgwf/zjH42Xl5eZP3++07avuuoq07ZtWzN37lyzevVqc++995pOnToZSWbt2rWOuvnz5xtJ\nZuzYseb99983ixcvNjExMaZLly7mu+++M8YY8/HHHxubzWYeeeQRk5GRYdasWWNmzZplUlNTz3n8\nql9j3bp1czxPkydPNpLMU0895aj79ttvzbhx48xbb71lsrOzzfLly01KSooJDAw0hw4dctRVH4+O\nHTuaRx991GRkZDgdyzNlZGQYm83meJ4WLVpkoqKiTEREhElMTHTUnThxwvTo0cNERUWZv//97yYj\n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\np1DibMcnLS1NvXv3rrU9f39/x/8fNWqURo0apZKSEmVnZ+uxxx7TDTfcoIMHDzrNQKjL2Z6n6tfJ\n4sWLFR0drbS0NEdNeXn5WS9u6sqxCg0Nlbe391lfo507d3Y8DgkJUXh4uGbNmlXnunr27Flr+ZrH\n68z9+ec//ykvLy8tX77c6fgXFxfXCrhc3R93e3VHQ14XjRUeHq558+Zp3rx52rlzp1599VU99dRT\nCgsL029+85sm2w4AoGEIJAAAbklMTNTMmTO1b98+x9T0yspKLVmyRHFxcU6nYjTWhg0bdODAAcdp\nGydPntT7779f67SSulx77bXy8PDQ/v37de211zZouwMGDJCHh4eWLl2qqVOnOsYXL17sVNezZ091\n6dJF27dvd6qzQnXwUPML6a5du/TJJ584BRSNXWdxcbHeeecdp7qEhARVVlZqxYoVTqcXvP322051\ngwYNkr+/v3bv3q0xY8a41IPdbtewYcO0Z88ePfLIIyosLFRYWNg5l6nrebLb7Y6p/WVlZfLycv7T\n57XXXlNlZaVLPdXF09NT/fv317Jly/T00087QpNPP/1U+/btcwokbrjhBs2ZM0edOnVSeHh4vete\nunSpkpOTnfbHw8PDccePsrIyeXp6OgUNWVlZ2r9/v9OpEufi6+tb684d7vTqDndeF/Xx9fXVyZMn\nz1nTs2dPPffcc3rppZdcugsOAOD8IZAAALhl0qRJSktL07XXXqtp06YpICBAL774onbt2lXrlpON\n1a5dO1133XV6+umn5evrq//+7/9WaWmp/vjHP9a77GWXXabHHntMv/3tb7Vz504lJiaqVatWOnDg\ngDIyMjR+/HgNGTKkzmV79uypX/7yl47bR/bv318ffvihPvjgA6c6m82mefPm6eabb9bp06d1++23\nKzQ0VEeOHNG6devUqVMnTZ48uUmOxZlSUlLk5eWlX//615oyZYoOHTqkp556Sp06dXJ7OvqgQYMU\nEBCgBx98UNOmTVNpaan+/Oc/KzQ0VCdOnHDUXXfddfrZz36m++67T8ePH1d0dLSWLVumrVu3SpLj\ny3lAQID++te/6sEHH9SxY8d04403qm3btvrmm2+Uk5OjpKQkx3E+cuSIhgwZosjISB08eFCzZ89W\nbGxsvWGE9OOtYaufpzVr1mjhwoV6+umn1bZtW0k/fslOT0/XpEmTNGzYMG3cuFFz5sypczZBQ0yb\nNk3XXXedRowYofvvv1/Hjh3TU089pYiICKe6SZMmacmSJbr66qs1adIk9ezZU6WlpdqxY4c+/vjj\nWoHPBx98oEcffVTXXXedPvvsM02bNk2//vWv1b17d8f+zJw5U3fffbfGjh2rXbt26U9/+lOtmUPn\n0qtXLxUVFWn+/PmKj49Xq1at1Ldv3wb36g5XXxcN0atXL7344otasmSJLrvsMvn7+ysiIkIpKSm6\n6667FBMTI29vb73zzjsqLi7Wdddd1+j9AAA0QnNfVRMA0LJU37Gg+ir61caMGWM6d+7sNLZjxw5z\n8803m4CAAOPr62sGDBhQ6/aIZ1tf586dzV133eU0Vn03gIyMjFp1CxYsMN26dTM+Pj4mNjbWfPTR\nR7X6q+tuAdX+8Y9/mAEDBpg2bdoYPz8/ExMTYx588EFz4MCBcx6P0tJS88ADD5igoCDj5+dnfv7z\nnzvu0FHzLhvGGLNu3TozdOhQExgYaHx9fU3nzp3NHXfcYdatW3fObZytd0nmiSeecBqrvqtBzbub\nLFmyxPTs2dP4+vqaXr16mbfeeqvW81V9l40FCxbU2k5dd9n46KOPTGxsrGnVqpXp1q2bmTVrluO5\nrOno0aPmjjvuMHa73bRt29b86le/MmlpaUaSyc/Pd6p9//33TVJSkvH39zetW7c20dHRZuzYsY5b\nM65cudJcd911JiIiwvj4+JiOHTuae+65x3zzzTfnPH7VfW3bts0kJSWZVq1amXbt2pn/+q//crrz\nRWVlpXniiSdM+/btTevWrc3gwYPN5s2bTefOnZ1up1nXMa7Pm2++aXr06GF8fHxMr169zPLly01i\nYqLTXTaM+fEOMxMnTjRdunQx3t7eJiwszFx11VVOd5So3n5OTo4ZPny48fPzM0FBQWbChAmmrKzM\naX2zZ882Xbp0Ma1atTLx8fEmIyOj1nbrel9VKykpMb/4xS9MYGCgkeT0mmlIr2ceq7rudnG212B9\nrwtj6v68MMbUurXroUOHzI033mjsdrvjLifff/+9ue+++0yvXr2Mn5+f8ff3N/Hx8U535wAANA+b\nMVxmGAAANJ3f/va3WrRokYqKihwXHjyfnn76aU2bNk3l5eW1Tsm4EKWlpWns2LEqKChwuosNAAAX\nmwv/tzYAAGg2aWlpOnHihHr37q3Tp09r9erVmj9/vh599FFLwggAAHDhIpAAAABu8/Pz08yZM/XV\n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\nUElJiQICAhxjBQUFCgkJqbXdcz33e/fuVVBQkE6fPq1Vq1YpOztbBw4cUFRU1FnfC+d6rhqqvnUV\nFxerdevWji8Kn332mYqKitShQwd5ePw4OS8tLU1fffWVOnfurIiICHl7ezuOdVxc3Dm3X9d7q6qq\nSp9++qmysrK0fv16ffHFFzp16pTat2/v2Kar6zobV973damqqpIxpta/F198UYmJiWddrq7XUUPe\n9/W9js7lzH115b1QXl6uvLw8VVZWytfXVxkZGfr666/VsWPHWl8QXXnfu/KcuvNeOBdX98GV17gr\nNQ05vq5+drnzudXQz4czf5829fPgyuvjL3/5i5KSkpSWlqYTJ06oZ8+e+s9//qNVq1Zp4MCBjrrG\n/q6sua//+Mc/lJ+fr4KCAn300Ufq16+ffH19NXfuXCUlJTVoXZL0xhtvaPfu3QoKCtLu3btVXFys\nL7/8UiUlJerSpUuD9mHatGnKy8vTpk2bVF5ertDQULeOfTVX3wtNeXylpvtsqObq793qbZ/tfeDq\n+9nV96krf/dKjfscr0t9x6Mhn1uucHU/XTV//nzt3r1bO3bsUHZ2tk6ePKkTJ07ok08+UXx8fIO2\nOX36dHl6eurrr79Wenq6goKC5Ofnp7fffluDBw921D355JPKzs5Wdna21q5dq7Vr12rdunXasmWL\nhgwZcs5+z/wbw92/Vaqd+bu5IX/TNuS9cKYz36fu/n1clwsykPj+++/1z3/+U6+88orefPNNrVy5\nUp999pk8PT2dPrzz8/Mdb9iysjItXLhQS5Ys0ZdffqmYmBi1atWqybdZVlamt99+W9nZ2SovL1en\nTp0cP1u4cKGuvPLKBtV9++23eu2117RlyxZ1795d77zzjl555RVt375dMTExat26tX744Qe9//77\n2rVrl7p27aqPP/5YS5Ys0b59+9SjRw/HL4jHHntM3333nYKDg2W328+6r2+88Ya6dOkiX19fffXV\nV3ryySeVlZWllStXqmvXrgoLC6tzuUceeUQ33nhjrfHf/OY3+vTTT5WTk+N4E2/evLnWm/gvf/mL\nevbsqdLSUi1YsEADBw5Uly5dlJaW5vgl7+oHh6t//JeWljrerJs2bdJnn32m4uJidezYUTabTZJ0\n5MgRlZaWOv0rKSnR66+/7tS/qx9Wa9euVVJSkt544w098cQTio6O1oABA7R06VIlJyc76lz5wHX1\nj48PPvhAAQEB53zeG/JcuRKquPpl09U/xlzdB2OMNm3apOzsbEdPvr6+Th+Srr6OXN0HV4+bq3/I\nuvLcz549W0lJSZo/f77Cw8N11VVXqby8XEuXLtXVV1/doOdKcu290JAwzZUvCitWrNCUKVPUsWNH\ntWvXTh06dNCVV16p9PR0t95bc+fOlbe3t+Lj49W3b19FRkZq7969ysjI0IABAxq0Llff98YYbdmy\nRUeOHFF4eLjjWOXl5Tn919Nf/epX+vzzz5Wbm+v0b9++fbr55psb9Dpy9X3vyuvI1X119b1Q/d46\nePCgVqxYocjISHl7e+u9995z+oLo6vvelefU1feCq8+Vq/vgymvc1S/MDT2+5/rsakjQ58p72tXf\npw15Hur7jHb19fHxxx8rMTFRK1eu1G9/+1tFREQoJiZGH374odNnqqvvGVf2dfny5Zo6dar69++v\nqKgozZ8/X1FRUfr3v//ttE1Xj1t6eromTpyorl27asCAAVq1apUmT56s1157zanOlX3YvHmznnnm\nGXXv3l179uzRP//5T33yySc6deqU0xciV3+fuvpeaMrj25SfDZJrv3dd/d3m6vvZ1b8xXPm719XP\ncVd+h7t6PFzdT1fey67uZ/X6XPmMXrnyf9s787Cojuzvf7tpBBpkFwVaQEBAQAQV3FBxAeOWEKNR\nE8doRhOjY5JR4zLGldEYY8ZEB+KSqElc4kZcEXVco+IKqIAoCIqsggqNLA009/2Dt+vXlwatdng0\nOOfzPDwPfft0VZ1aTp1bt07dQ5g9eza6dOmCvXv3Yu7cufD19cXhw4dFOvDk+Z///AeffPIJ/Pz8\ncODAAUyfPh0uLi7MV9eu35qaGgwdOhSjR49Gv379kJKSgoULF4p05fExePsurx/C2994fVCecfrf\nPFBqiGYZsrFmzRoEBQVh/vz5iIuLQ2VlJXr16oW9e/ciNzcX7733HgBgx44d8Pf3B1DXCFZWVpgz\nZw4uXbqEDRs2YPbs2QDqFgf27duHR48eISAgAMHBwSyvH3/8EZMmTeLOMyoqCvb29ujWrRtOnTqF\nixcv4rPPPoOhoSHS0tJYurxykZGR6Ny5M1QqFZYsWYLg4GDMmzcPV65cwcaNGzF79mxERkbC1tYW\nVVVVWLFiBRwdHfHmm2/i6tWr2LhxI6ZPnw6gbrWwrKwMS5YsgaWlJXr16oWePXvqGI74+Hi8//77\n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\n", "text/plain": [""]}, "metadata": {}, "output_type": "display_data"}], "source": ["df[\"nb\"] = 1\n", "dep = df[[\"DEPMAR\",\"nb\"]].groupby(\"DEPMAR\", as_index=False).sum().sort_values(\"nb\",ascending=False)\n", "ax = dep.plot(kind = \"bar\", figsize=(18,6))\n", "ax.set_xlabel(\"d\u00e9partements\", fontsize=16)\n", "ax.set_title(\"nombre de mariages par d\u00e9partements\", fontsize=16)\n", "ax.legend().set_visible(False) # on supprime la l\u00e9gende\n", "\n", "# on change la taille de police de certains labels\n", "for i,tick in enumerate(ax.xaxis.get_major_ticks()):\n", " if i > 10 :\n", " tick.label.set_fontsize(8) "]}, {"cell_type": "markdown", "metadata": {}, "source": ["Quand on ne sait pas, le plus simple est d'utiliser un moteur de recherche avec un requ\u00eate du type : ``matplotlib + requ\u00eate``. Pour cr\u00e9er un graphique, le plus courant est de choisir le graphique le plus ressemblant d'une [gallerie de graphes](http://matplotlib.org/gallery.html) puis de l'adapter \u00e0 vos donn\u00e9es. On peut aussi changer le [style](http://matplotlib.org/users/style_sheets.html) des graphes. Un style populaire est celui de [ggplot2](http://www.statmethods.net/advgraphs/ggplot2.html) :"]}, {"cell_type": "code", "execution_count": 71, "metadata": {"collapsed": true}, "outputs": [], "source": ["import matplotlib.pyplot as plt\n", "plt.style.use('ggplot')"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Exercice 6 : distribution des mariages par jour\n", " \n", "On veut obtenir un graphe qui contient l'histogramme de la distribution du nombre de mariages par jour de la semaine et d'ajouter une seconde courbe correspond avec un second axe \u00e0 la r\u00e9partition cumul\u00e9e.\n"]}, {"cell_type": "code", "execution_count": 72, "metadata": {"collapsed": true}, "outputs": [], "source": []}, {"cell_type": "markdown", "metadata": {}, "source": ["## Annexes\n", "\n", "\n", "### Cr\u00e9er un fichier Excel avec plusieurs feuilles\n", "\n", "La page [Allow ExcelWriter() to add sheets to existing workbook](https://github.com/pydata/pandas/issues/3441) donne plusieurs exemples d'\u00e9criture. On diminue la taille du document Excel \u00e0 \u00e9crire."]}, {"cell_type": "code", "execution_count": 73, "metadata": {"collapsed": true}, "outputs": [], "source": ["df1000 = df[:1000]"]}, {"cell_type": "code", "execution_count": 74, "metadata": {"collapsed": true}, "outputs": [], "source": ["import pandas\n", "writer = pandas.ExcelWriter('ton_example100.xlsx')\n", "df1000.to_excel(writer, 'Data 0')\n", "df1000.to_excel(writer, 'Data 1')\n", "writer.save()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**FIN**"]}, {"cell_type": "code", "execution_count": 75, "metadata": {"collapsed": true}, "outputs": [], "source": []}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.3"}}, "nbformat": 4, "nbformat_minor": 2}