Nous proposons une série de quelques articles où nous allons analyser les licences sportives en France à l’aide de Pandas, et nous réaliserons une interface utilisateur avec des widgets.
Pandas, Plotly et Jupyter : De l'analyse de données à l'application en ligne (1/3)
Pandas, Plotly et Jupyter : De l’analyse de données à l’application en ligne (2/3)
400 mots - Temps de lecture 2 min ![](https://www.logilab.fr/file/16368263/raw/748bab1cd694a1a4fa0703996_200px.png) Le 10 mars 2022 a eu lieu le lancement de la « boite-à-outils Resourcecode » devant plus d’une centaine de partenaires du projet. Logilab est fière d’avoir pu participer à ce projet. [Resourcecode](http://resourcecode.info/) est un projet visant à soutenir les investissements et la croissance dans le secteur de l’énergie houlomotrice et maréomotrice par la création d’une boîte à outils intégrée de données marines. Concrètement, des données décrivant l’état de la mer (vitesse du vent, hauteur des vagues, direction du courant, etc) sont enregistrées par des bouées de l’[IFREMER](https://ifremer.fr/) (*Institut Français de Recherche pour l'Exploitation de la Mer*) et de ses partenaires. Des données de 1994 à 2020 sont disponibles pour des milliers de points de l’océan Atlantique et de la mer du Nord avec une résolution temporelle de l’ordre de l’heure. Une fois ces données enregistrées, elles peuvent être interpolées sur les points d’un maillage triangulaire. Logilab a remporté un appel d’offre, déposé par l’Ifremer, dans le cadre de ce projet. Nous avons eu la charge de réaliser : - une application web [resourcecode.ifremer.fr](https://resourcecode.ifremer.fr) permettant la visualisation des points où les données sont accessibles et proposant des outils statiques ou interactifs basés sur des calepins [Jupyter](jupyter.org/) afin d’étudier la mer au point considéré. - produire une bibliothèque python [resourcecode](https://pypi.org/project/resourcecode) permettant de télécharger localement les données d’un point sous forme de `DataFrame` Pandas. L'intégration continue de la forge GitLab de l'IFREMER génère avec Sphinx la [documentation](https://resourcecode.gitlab-pages.ifremer.fr/resourcecode/) de cette bibliothèque. - intégrer à cette bibliothèque des codes de calculs écrits par l’IFREMER et ses partenaires (codes écrits en R, MATLAB ou Python) - mettre en place une architecture permettant à l’IFREMER et ses partenaires de construire des nouveaux outils (statiques ou interactifs). Ces outils sont développés et maintenus par l’IFREMER et ses partenaires, et *automatiquement* intégré à l’application web. Ils sont développés sur l’instance [GitLab de l’Ifremer](https://gitlab.ifremer.fr/resourcecode/tools). Lors de cet événement de lancement de Resourcecode, une démonstration en direct a pu être effectuée auprès du public : la bibliothèque a été installée et un dépôt de code contenant un calepin Jupyter a été cloné puis exécuté. Cela a permis de démontrer la facilité d'utilisation de cet outil, ainsi que la répétabilité offerte par ce type d’architecture, qui correspond aux attentes actuelles en matière de science ouverte (*Open Science*). ![](https://www.logilab.fr/file/16367129/raw/748bab1cd694a1a4fa0703995.png)
_Temps de lecture estimé 10 minutes._ Nous proposons une série de quelques articles où nous allons utiliser la bibliothèque Pandas pour analyser les licences sportives en France. En chemin, nous réaliserons une interface utilisateur avec des widgets. Cette série sera découpée en trois articles. Dans le premier, nous allons explorer le jeu de données à notre disposition en utilisant la bibliothèque Pandas. Dans le second, nous introduirons *Jupyter* et les *ipywidgets* qui nous permettront de faire une interface utilisateur. Nous terminerons la série en présentant *Voilà* ainsi que le thème *jupyter-flex*. # *Pandas, jupyter, ipywidgets, voilà* ? De quoi parle-t-on ? - [Pandas](https://pandas.pydata.org/) est une bibliothèque Python très connue, qui permet d’analyser et d’étudier des jeux de données. Elle est conçue pour traiter des jeux de données tabulaires (ceux pouvant être lus par un tableur). Les données peuvent être de différents types (nombres, dates, chaînes de caractères, etc). Pandas est, comme nous le verrons, très efficace. Les fonctions coûteuses de Pandas sont généralement écrites en C, et Python est utilisé pour manipuler et appeler ces fonctions. - [Jupyter](https://jupyter.org/) est une plateforme, utilisable dans un navigateur web qui permet d’exécuter des calepins (*notebooks*). Un calepin est un fichier qui combine des cellules de différents types : du code exécutable, du texte, des visualisations, etc. - Les [Ipywidgets](https://ipywidgets.readthedocs.io/en/latest/) sont des éléments graphiques interactifs que l’on peut ajouter à des calepins *Jupyter*. Ils vont nous permettre de proposer aux utilisateurs de choisir un fichier, choisir un élément dans une liste, cliquer sur un bouton, etc. Chacune des actions de l’utilisateur peut être associée à une fonction Python, et donc rendre le calepin interactif. - [Voilà](https://github.com/voila-dashboards/voila) est une application qui permet d’exécuter des calepins, mais sans afficher le code source − qui est visible par défaut dans *Jupyter*. L’énorme intérêt à cela est qu’un calepin *Jupyter* devient alors une application web à part entière, utilisable dans le navigateur, et seuls les éléments indispensables à son utilisation sont visibles. Après cette petite phase de présentation, découvrons les données que nous allons manipuler aujourd’hui. # Présentation des données Dans cette série d’articles nous utilisons des données issues de [https://data.gouv.fr](https://data.gouv.fr). Il s’agit du nombre de licences sportives, par sexe, par catégorie d’âges, par municipalité pour les années 2012 à 2018. Les données brutes peuvent être téléchargées [ici](https://www.data.gouv.fr/fr/datasets/donnees-geocodees-issues-du-recensement-des-licences-et-clubs-aupres-des-federations-sportives-agreees-par-le-ministere-charge-des-sports/). Nous avons réalisé une opération de nettoyage sur ces données, afin de nous assurer d’avoir une structure cohérente pour chaque année. Nous avons également remplacé les municipalités par leur département, ce qui permet d’alléger les données à manipuler. Au final, nous obtenons six fichiers csv, un par année, dont la structure est la suivante : ```csv dep_code,dep_name,fed_code,fed_name,gender,age,lic_holders 01,Ain,101,Fédération Française d'athlétisme,F,00-04,0 01,Ain,101,Fédération Française d'athlétisme,F,05-09,75 01,Ain,101,Fédération Française d'athlétisme,F,10-14,251 01,Ain,101,Fédération Française d'athlétisme,F,15-19,130 01,Ain,101,Fédération Française d'athlétisme,F,20-29,39 01,Ain,101,Fédération Française d'athlétisme,F,30-44,105 01,Ain,101,Fédération Française d'athlétisme,F,45-59,105 01,Ain,101,Fédération Française d'athlétisme,F,60-74,23 01,Ain,101,Fédération Française d'athlétisme,F,75+,0 01,Ain,101,Fédération Française d'athlétisme,M,00-04,0 01,Ain,101,Fédération Française d'athlétisme,M,05-09,106 01,Ain,101,Fédération Française d'athlétisme,M,10-14,278 […] ``` | Nom de colonne | Description | | --------: | -------- | | `dep_code` | Code (unique) du département | | `dep_name` | Nom du département | | `fed_code` | Code (unique) de la fédération sportive | | `fed_name` | Nom de la fédération sportive | | `gender` | Genre (peut être `M` ou `F`) | | `age` | La tranche d’âge considérée (peut être `00-04`, `05-09`, `10-14`, `15-19`, `20-29`, `30-44`, `44-59`, `60-74`, `75+`) | | `lic_holders` | Le nombre de licenciés dans le département, enregistrés dans cette fédération, de ce genre et de cette tranche d’âge. | # Chargement de données pour une année Pandas offre un nombre important de fonctions permettant de charger des données depuis différents formats: CSV, Excel, tableaux HTML, JSON, bases SQL, HDF5, etc. Nous allons utiliser la fonction `read_csv`. Cette fonction utilise les éléments de la première ligne comme noms de colonnes. Pandas essaie également de détecter les types de colonnes à utiliser (nombre, date, chaîne de caractères) en se basant sur les premiers éléments lus. Nous spécifions donc à Pandas que la colonne `dep_code` est de type `str`, pour prendre en compte les départements Corse (`2A` et `2B`), sans quoi Pandas émettra un avertissement. ```python from pathlib import Path import pandas as pd DATA_DIR = Path().resolve() / "data" # en supposant que les données sont dans le dossier data d2012 = pd.read_csv( DATA_DIR / "sport_license_holders_2012.csv", dtype={"dep_code": str} ) ``` Nous obtenons alors la *DataFrame* suivante : ![](https://www.logilab.fr/file/13252396/raw/748bab1cd694a1a4fa0703927.png) # Premières analyses À partir de là, nous pouvons commencer à étudier le jeu de données. Par exemple, en demandant le nom de chaque fédération : ```python >>> d2012["fed_name"].unique() array(["Fédération Française d'athlétisme", "Fédération Française des sociétés d'aviron", 'Fédération Française de badminton', 'Fédération Française de basketball', 'Fédération Française de boxe', 'Fédération Française de canoë-kayak', 'Fédération Française de cyclisme', "Fédération Française d'équitation", "Fédération Française d'escrime", […], 'Fédération française de pentathlon moderne', 'Fédération Française de javelot tir sur cible', 'Fédération Flying Disc France', 'Fédération Française Maccabi', 'Fédération Française de la course camarguaise', 'Fédération Française de la course landaise', 'Fédération Française de ballon au poing'], dtype=object) ``` Nous pouvons facilement compter le nombre total, toutes catégories confondues, de licenciés : ```python >>> d2012["lic_holders"].sum() 12356101 ``` Une des forces de Pandas réside dans la possibilité de créer des filtres, ou des groupes simplement. Par exemple, pour compter le nombre de licenciés hommes, nous pouvons créer un masque (`True` si le genre est `M` et `False` sinon), puis appliquer ce masque à notre *DataFrame*. ```python >>> mask_male = d2012["gender"] == "M" >>> d2012[mask_male]["lic_holders"].sum() 7806235 ``` Ainsi, en 2012, il y avait 7 806 235 licenciés masculins de sport en France. Combien y a-t-il de licenciés, en 2012, par tranche d’âge ? Pour répondre à cette question, nous utilisons la méthode `groupby` de Pandas, en donnant le nom de la colonne sur laquelle nous souhaitons faire le groupe : ```python >>> d2012.groupby("age")["lic_holders"].sum() ``` ![](https://www.logilab.fr/file/13252442/raw/748bab1cd694a1a4fa0703921.png) Cette méthode permet de constituer des groupes, selon une clé (généralement le nom d’une ou plusieurs colonnes), puis d’appliquer sur chaque groupe partageant la même clé une fonction d’agrégation. Dans notre exemple, la clé de chaque groupe est l’âge, et la fonction d’agrégation la somme sur la colonne `lic_holders`. Nous pouvons effectuer le même type de calcul, mais en groupant cette fois-ci sur le genre et l’âge, ce qui donne le résultat suivant : ```python >>> d2012.groupby(["gender", "age"])["lic_holders"].sum() ``` ![](https://www.logilab.fr/file/13252463/raw/748bab1cd694a1a4fa0703920.png) Les deux résultats que nous venons d’obtenir sont ce qu’on appelle des `Series`. C’est-à-dire, des *DataFrames* mais constituées d’une seule colonne. On observe que les groupes sont directement constitués par l’index. Dans le cas d’un `groupby()` avec une seule colonne, nous avons un *index simple* et dans le cas où plusieurs colonnes sont utilisées, nous obtenons ce qu’on appelle un *index multiple* ou un *index hiérarchique*. Nous allons étudier cela un peu plus en profondeur dans la suite. # Créer un index sur mesure Dans la *DataFrame* que nous avons chargée, de très nombreuses données sont répétées et ne sont utilisées que pour définir des groupes (`dep_code`, `dep_name`, `gender`, `age` etc). Nous allons mettre toutes ces données dans l’index de la *DataFrame*. Cela permet d’avoir dans l’index les données de chaque groupe, et dans la *DataFrame* les données desdits groupes (ici le nombre de licenciés sportifs). Pour ce faire, nous utilisons la méthode `set_index` : ```python >>> d2012.set_index( ["dep_code", "dep_name", "fed_code", "fed_name", "gender", "age"], inplace=True ) >>> d2012 ``` ![](https://www.logilab.fr/file/13252487/raw/748bab1cd694a1a4fa070391f.png) Nous avons ainsi une *DataFrame* à une seule colonne, et avec un index à six niveaux. Nous pouvons toujours grouper par genre et par âge, en utilisant le mot-clé `level`, indiquant qu’il faut grouper en utilisant l’index : ```python >>> d2012.groupby(level=["gender", "age"]).sum() ``` ![](https://www.logilab.fr/file/13252506/raw/748bab1cd694a1a4fa070391e.png) # Dans quels départements la course camarguaise est-elle pratiquée ? La course camarguaise est un sport traditionnel dans lequel les participants tentent d'attraper des attributs primés fixés au frontal et aux cornes d'un bœuf. Pour savoir dans quels départements ce sport est le plus pratiqué, nous allons : 1. Filtrer sur l’index pour n’avoir que les enregistrements correspondant à ce sport (le code de la fédération est 215) ; 2. Grouper par code et nom de département, et compter le nombre de licenciés ; 3. Afficher les groupes triés par ordre décroissant de licenciés. ```python >>> d2012_camarg = d2012.xs( 215, level="fed_code" ) # Only keep the rows with index equal to 215 at level ``fed_code`` >>> d2012_camarg_depts = d2012_camarg.groupby( ["dep_code", "dep_name"] ).sum() # Group the data by department (only keep departments with non-null values) >>> d2012_camarg_depts.sort_values( by="lic_holders", ascending=False ) # Sort the data in decreasing order ``` ![](https://www.logilab.fr/file/13252526/raw/748bab1cd694a1a4fa070391d.png) Sans trop de surprise, on observe que c’est le Gard (où est la Camargue), les Bouches-du-Rhône, l’Hérault et le Vaucluse (départements qui entourent le Gard) qui ont le plus de licenciés dans ce sport. # Quels sont les sports les plus pratiqués par les femmes ? Nous allons : 1. Sélectionner les enregistrements correspondant à `gender = 'F'` ; 2. Grouper par fédération et compter le nombre de licenciées ; 3. Afficher les dix sports avec le plus de licenciées. ```python >>> d2012_females_top_10 = d2012.xs("F", level="gender") .groupby(["fed_code", "fed_name"]) .sum() .nlargest(10, "lic_holders") >>> d2012_females_top_10 ``` ![](https://www.logilab.fr/file/13252554/raw/748bab1cd694a1a4fa070391c.png) Pandas permet également de faire des graphiques. Par défaut c’est la bibliothèque [matplotlib](https://matplotlib.org/) qui est utilisée. Nous pouvons par exemple utiliser un diagramme en bâtons pour afficher le top 10 des sports pratiqués par les femmes : ```python >>> d2012_females_top_10.plot( kind="bar", legend=False, xlabel="Sport federation", ylabel="Number of license holders", color="#CC0066", title="Female sport license holders in 2012 (top 10)", ) ``` ![](https://www.logilab.fr/file/13252574/raw/748bab1cd694a1a4fa070391a.png) # Charger les données pour toutes les années Dans la section précédente, nous avons chargé uniquement les données de l’année 2012. Mais nous avons bien plus de données que cela. Nous allons donc charger chaque fichier, puis renommer la colonne `lic_holders` en fonction de l’année en cours. Nous aurons ainsi une *DataFrame*, avec en colonne le nombre de licenciés par année, et en index les différents groupes. Nous allons faire une liste `years_dfs` qui va contenir toutes les *DataFrames*, une par année, puis nous allons simplement les concaténer. Cela donne donc : ```python >>> years_dfs = [] >>> for year in range(2012, 2019): ... fname = f"sport_license_holders_{year}.csv" ... yr_df = pd.read_csv( ... DATA_DIR / fname, ... dtype={"dep_code": str}, ... index_col=["dep_code", "dep_name", "fed_code", "fed_name", "gender", "age"], ... ) ... yr_df.rename(columns={"lic_holders": str(year)}, inplace=True) ... year_dfs.append(yr_df) >>> ``` On concatène toutes les *DataFrames*, en fonction de l’index (`axis=1`) : ```python >>> data = pd.concat(years_df, axis=1) >>> data ``` ![](https://www.logilab.fr/file/13252596/raw/748bab1cd694a1a4fa070391b.png) Nous avons ainsi une *DataFrame* avec plus de 1.6 million de lignes, et 7 colonnes. On peut maintenant afficher, par exemple, les 10 sports les plus pratiqués en fonction des années : ```python >>> data_sport = data.groupby(level=["fed_code", "fed_name"]).sum() >>> data_sport.nlargest(10, "2012") ``` ![](https://www.logilab.fr/file/13252618/raw/748bab1cd694a1a4fa0703928.png) Nous avons ainsi le nombre de licenciés, par fédération et par année pour les 10 plus grosses fédérations de 2012. Le tri est effectué par rapport aux données de 2012. On notera qu’en 2018 il y a 0 licencié de Karaté. Cela est probablement une erreur dans les données, ce qui peut arriver. # Tracer l'évolution du nombre de licenciés avec Plotly Nous pouvons maintenant suivre l’évolution du nombre de licenciés dans certaines disciplines. Nous sélectionnons les sports dont le code de fédération est 109, 115, 242, 117. ```python >>> sel_data_sports = data_sports.loc[ ... [109, 115, 242, 117] ... ] # Select the rows whose value at the first level of the index (``fed_code``) ... # is one of the list items >>> # Drop the first level of the index (``fed_code``) >>> sel_data_sports = sel_data_sports.droplevel(0) >>> # Will be used as the title of the legend >>> sel_data_sports.index.name = "Federations" >>> sel_data_sports.transpose().plot( ... title="Sport license holders", xlabel="year", ylabel="number of license holders" ...) # Transpose to have the years as the index (will be the X axis) ``` ![](https://www.logilab.fr/file/13252640/raw/748bab1cd694a1a4fa0703929.png) Comme nous le disions, par défaut Pandas utilise la bibliothèque *matplotlib* pour générer les graphiques. La figure produite ici est statique, elle peut facilement être insérée dans un rapport par exemple, mais cela présente des difficultés lors de la phase d’exploration. Depuis quelque temps maintenant, Pandas est compatible avec plusieurs bibliothèques de visualisation. Il y a notamment [Plotly](https://plotly.com/python/), qui permet de faire des graphiques interactifs utilisables dans le navigateur web. Pour utiliser *Plotly*, il est nécessaire de changer la bibliothèque utilisée par défaut. ```python # Choose Plotly as the plotting back-end # this has to be done only once, usually at the begining of the code >>> pd.options.plotting.backend = "plotly" ``` Une fois *Plotly* configurée, nous pouvons retracer le graphique, comme précédemment : ```python >>> fig = sel_data_sports.transpose().plot(title="Sport license holders") >>> fig.update_layout(xaxis_title="year", yaxis_title="number of license holders") >>> fig ``` Dans un environnement *Jupyter*, la figure produite est celle-ci, et il est possible de sélectionner/désélectionner les courbes à afficher : ![](https://www.logilab.fr/file/13252662/raw/748bab1cd694a1a4fa070392a.png) # Quelle est la prochaine étape ? Nous avons dans ce premier article, chargé avec Pandas des données textuelles au format CSV. Nous avons vu comment et pourquoi utiliser un index multiple. Cela nous a permis de calculer quelques statistiques simples sur des groupes d’individus. Nous avons également produit des visualisations avec *matplotlib* et avec *Plotly*. Dans le prochain article, nous utiliserons des widgets *Jupyter* pour manipuler dynamiquement les données à afficher sur les graphiques.
Dans un article prédécent nous vous proposions une analyse de données à l’aide de la bibliothèque Pandas. Nous y avions construit une série de graphiques simples pour réaliser cette analyse. Dans cet épisode, nous allons aborder les widgets qui vont nous permettre de rendre ces graphiques dynamiques
Le 10 mars 2022, l'IFREMER a présenté les résultats du projet http://resourcecode.info/ devant plus d’une centaine de parties prenantes.
Les AFPYRo reprennent et le prochain aura lieu dans nos locaux parisiens entre Denfert Rochereau et la Place d'Italie ! Un AFPYRo est un événement organisé par l’[AFPy](https://www.afpy.org/) − Association Francophone Python − pour regrouper des personnes souhaitant discuter du langage de programmation Python dans un cadre convivial. Après une ou deux présentations (vous pouvez [proposer la vôtre](https://discuss.afpy.org/t/pour-les-parisiens-afpyro-chez-logilab-le-16-mars/1384)), nous échangerons autour de quelques pizzas. Le prochain AFPYRo sera donc à Logilab, au 104 Boulevard Auguste Blanqui 75013 Paris, le 16 mars 2023 de 19h à 21h et nous offrirons les pizzas. N’hésitez pas à [vous inscrire](https://discuss.afpy.org/t/pour-les-parisiens-afpyro-chez-logilab-le-16-mars/1384) et à passer nous voir !
Temps de lecture estimé 10 minutes. Dans un [article précédent](https://www.logilab.fr/blogentry/13252264) nous vous proposions une analyse de données à l’aide de la bibliothèque Pandas. Nous y avions construit une série de graphiques simples pour réaliser cette analyse. Dans cet épisode, nous allons aborder les *widgets* qui vont nous permettre de rendre ces graphiques dynamiques. Il est conseillé d’avoir lu l’article précédent qui détaille la structure des données utilisées. ## Qu’est-ce qu’un *widget* ? Dans un calepin jupyter, le code peut facilement être édité et rejoué. Il est donc assez simple d’effectuer des changements. Il est toutefois possible que les utilisateurs finaux de l’application ne sachent pas programmer ou simplement qu’on préfère avoir un moyen simple d'interagir (sans avoir à relire le code Python et à le modifier). Dans de tels cas, les *widgets* constituent une bonne solution. Les *widgets* sont des objets qui sont rendus dynamiquement dans les calepins Jupyter, et avec lesquels il est possible d’interagir. La bibliothèque de base pour construire ces *widgets* est [`ipywidgets`](https://ipywidgets.readthedocs.io/en/latest/). Dans l’exemple ci-dessous, la bibliothèque est importée puis un curseur glissant est construit. ```ipython >>> import ipywidgets as ipw >>> ipw.IntSlider(min=0, max=20, step=2) ``` À l’exécution de la cellule Jupyter, le *widget* est affiché. ![](https://www.logilab.fr/file/19793781/raw/e852cfbe7238489c4ae60251c.png) La connexion entre le *widget* affiché dans la page Web et l’objet python a été automatiquement définie. Cela signifie que si l’objet python est modifié, le rendu du *widget* est modifié et vice-versa. Dans le cas présent, l’attribut ``value`` du *widget* vaut ``6``. À titre d’exemple, on peut construire un curseur glissant comme ceci : ```ipython >>> slider = ipw.IntSlider(min=0, max=20, step=2) >>> slider ``` puis modifier dynamiquement la valeur de cet objet. Le rendu sera alors mis à jour. ```ipython >>> from time import sleep >>> for i in range(0, 22, 2): ... sleep(1) ... slider.value = i ``` ![](https://www.logilab.fr/file/19793850/raw/e852cfbe7238489c4ae602520.20.15.gif) Les *widgets* deviennent très intéressants dès lors que l’on associe des fonctions python à des évènements. Dans l’exemple ci-dessous, nous avons défini deux *widgets* de type “curseur glissant” et un *widget* d’affichage. Nous voulons afficher dans ce dernier *widget* la somme des deux curseurs. On construit un *widget* de type Bouton, et on associe le clic sur ce bouton à l’appel de la fonction `compute_add` qui somme les valeurs des deux curseurs et met à jour l’affichage. ```ipython >>> from IPython.display import clear_output >>> sld1 = ipw.IntSlider(min=0, max=20) >>> sld2 = ipw.IntSlider(min=0, max=20) >>> >>> out = ipw.Output() >>> with out: ... print("0 + 0 = 0") ... >>> def compute_add(evt): ... with out: ... clear_output() ... res = sld1.value + sld2.value ... print(f"{sld1.value} + {sld2.value} = {res}") ... >>> btn = ipw.Button(description="Sum") >>> btn.on_click(compute_add) >>> ipw.HBox([ipw.VBox([sld1, sld2, btn]), out]) ``` Le rendu est alors le suivant : ![](https://www.logilab.fr/file/19793893/raw/e852cfbe7238489c4ae602520.20.15.gif) ## Utiliser un *widget* pour sélectionner les données à afficher Dans l’épisode précédent, nous avions écrit une fonction pour charger toutes les données des licenciés inscrits dans les fédérations sportives pour les années 2012 à 2019. La fonction est la suivante : ```ipython >>> from pathlib import Path >>> import pandas as pd >>> DATA_DIR = Path().resolve() / "data" >>> def load_data(): ... year_dfs = [] ... for year in range(2012, 2019): ... fname = f"sport_license_holders_{year}.csv" ... yr_df = pd.read_csv( ... DATA_DIR / fname, ... dtype={"dep_code": str}, ... index_col=["dep_code", "dep_name", "fed_code", "fed_name", "gender", "age"], ... ) ... yr_df.rename(columns={"lic_holders": str(year)}, inplace=True) ... year_dfs.append(yr_df) ... data = pd.concat(year_dfs, axis=1) ... return data ... >>> d = load_data() ``` ![](https://www.logilab.fr/file/19793929/raw/e852cfbe7238489c4ae60251c.png) Le `DataFrame` résultant contient plus de 1.6 millions de lignes et 7 colonnes. Nous pouvons maintenant écrire une fonction très simple qui affiche l’évolution du nombre de licenciés de 2012 à 2019 pour les fédérations qui sont données en paramètre. ```ipython >>> pd.options.plotting.backend = "plotly" # Choose Plotly as the plotting back-end >>> def plot_license_holders_evolution_by_sport(data, fed_codes): ... data_sports = data.groupby(level=["fed_code", "fed_name"]).sum() ... sel_data_sports = data_sports.loc[list(fed_codes)] ... sel_data_sports = sel_data_sports.droplevel(0) ... sel_data_sports.index.name = "Federations" ... fig = sel_data_sports.transpose().plot(title="Sport license holders") ... fig.update_layout(xaxis_title="year", yaxis_title="number of license holders") ... return fig ... >>> plot_license_holders_evolution_by_sport(d, [109, 115, 242, 117]) ``` ![](https://www.logilab.fr/file/19793957/raw/e852cfbe7238489c4ae60251c.png) Nous souhaitons utiliser un *widget* proposant de sélectionner une ou plusieurs disciplines, puis afficher le graphique correspondant lorsque la sélection est validée. La première chose que nous réalisons est un dictionnaire contenant en clef le nom des fédérations sportives et en valeur leur numéro associé. Ce dictionnaire pourra être fourni à un *widget* de type `SelectMultiple`. Nous utilisons le code suivant pour obtenir le dictionnaire de correspondance : ```ipython >>> def extract_federation_names_codes(data): ... codes = data.index.get_level_values( ... "fed_code" ... ) # Extract all the values from the level ``fed_codes`` of the index ... names = data.index.get_level_values( ... "fed_name" ... ) # Extract all the values from the level ``fed_names`` of the index ... dic = {name: code for code, name in zip(codes, names)} ... return dic ... ``` Et finalement, la fonction suivante permet de construire l’interface souhaitée : ```ipython >>> from IPython.display import display >>> def build_gui(data): ... fed_values = extract_federation_names_codes(data) ... fed_wdg = ipw.SelectMultiple( ... options=fed_values, description="Sport federations", rows=20 ... ) ... plt_btn = ipw.Button(description="Plot") ... out_wdg = ipw.Output() ... # Define the hook function that will be called each time the button is clicked ... def refresh_plot(evt): ... fed_codes = fed_wdg.value ... with out_wdg: ... clear_output() ... display(plot_license_holders_evolution_by_sport(data, fed_codes)) ... ... plt_btn.on_click(refresh_plot) ... gui_wdg = ipw.HBox([ipw.VBox([fed_wdg, plt_btn]), out_wdg]) ... return gui_wdg ... >>> build_gui(d) ``` ![](https://www.logilab.fr/file/19793992/raw/e852cfbe7238489c4ae602520.20.15.gif) Nous venons ainsi de faire une fonction qui construit une interface utilisateur, composée d’un *widget* permettant de faire une sélection multiple. Lorsque la sélection est validée, la fonction d’affichage du graphique est rappelée, mettant ainsi le composant à jour. Le développement de cette interface utilisateur est bien plus simple que ce que nous aurions eu à faire avec d'autres solutions comme Qt, Tkinter ou même Flask + Javascript. On voit que cela permet à tous les utilisateurs de faire leur propre analyse sans avoir à changer une seule ligne de code. Dans le prochain épisode, nous présenterons *Voila* qui permet de transformer un calepin Jupyter en une petite application Web, utilisable sans aucune connaissance de Python. Nous utiliserons également *jupyter-flex* pour obtenir une jolie application Web dotée de bulles d’aides, d’onglets et d’un menu latéral.
Les AFPYRo reprennent et le prochain aura lieu dans nos locaux parisiens entre Denfert Rochereau et la Place d'Italie !