Projet Architecture Logicielle
Fait par : Carina Faria Leitao, Emma Bulone et Rachid Boulkhir
Enseignant : Matthieu Falce
import plotly.express as px
import pandas as pd
import numpy as np
import plotly.graph_objects as go
import warnings
warnings.filterwarnings('ignore')
df = pd.read_csv("https://raw.githubusercontent.com/datasets/covid-19/master/data/countries-aggregated.csv")
df.head()
Sur le tableau de données ci-dessus , on peut voir les différentes variables utilisées dans notre dashboard :
- Date : Date à laquelle la personne a été déclarée positive au Covid-19
- Country : Le pays dans lequel la personne habite
- Confirmed : Nombre de cas positifs
- Recovered : Nombre de cas guéris
- Deaths : Nombre de décés
On a décidé de réaliser un dashboard permettent de visualiser ces données par pays et par période d'étude souhaitée. La page est composée de 3 éléments :
Un tableau des données groupé par pays sur lequel on peut :
Filtrer les données mais aussi sélectionner un pays et une colonne entre "Deaths","Confirmed" et "Recoverd" et le graphique en dessous se met alors à jour en conséquence Ce tableau possède également 2 colonnes réprésentants les taux de décés et de guérisons groupés par pays toujours.
Un graphique changeant selon la sélection évoquée au niveau du tableau dans lequel on retrouve la ou les courbes correspondantes à l'évolution du nombre de décés ou de guérisions pour le ou les pays sélectionnés dans le tableau. On peut également choisir la période d'étude grâce aux deux cases situées près du graphique dans lesquelles on peut mettre une date de début et une date de fin. Le graphique se mettra à jour en conséquence des dates choisies, de la variable choisie (guérisons ou décés) au sein du tableau ainsi que du/des pays choisis au niveau de la carte.
- Une carte sur laquelle on peut voir le nombre de décés avec une échelle selon laquelle au plus la couleur est foncée au plus le nombre de décés est important. Sur cette carte il est possible de selectionner un ou plusieurs pays ce qui permet d'avoir une vision sur une liste restreinte de pays avec le graphique du nombre de décés/gueris qui se mettra alors à jour.
Au cours du projet, nous avons rencontré quelques difficultés:
Tout d'abord, dans la réalisation de la carte du monde afin que tous les pays soient bien reconnus, on a du trouver pour chaque pays son code ISO3. Pour cela on a créé un dictionnaire python qui permet d'associer le libellé du pays à son code ISO3.
De plus, dans la carte, au niveau de l'échelle, il a fallu l'adapter puisqu'elle n'était pas représentative du nombre réel de décés. Par exemple, lors de l'affichage du nombre de décés dans chaque pays, le nombre de décés aux Etats-Unis par rapport à sa population totale est complétement différents du nombre de décés en France par rapport à sa population totale. Pour corriger cette mauvaise représentation, nous avons decidé de passer au log. Dans la carte les données sont alors passées au log par contre l'échelle est quand à elle est basée sur les valeurs réelles.
Nous avons eu des difficultés pour garder une échelle en valeurs réelles alors que la carte était passée au log. En effet, par défaut la librairie python met des valeurs passées au log comme sur la carte. Les manipulations pour corriger l'échelle et la remettre en valeur réelle a été assez complexe à cause du manque d'information dans la documentation de la librairie python (plotly).
Une autre solution à ce problème d'échelle serait de réaliser une échelle sur laquelle on retrouve plutôt un taux de décés(nombre de décés/population totale du pays).
Pour la fonctionnalité "sélection des pays sur la carte", nous avons constaté un disfonctionnement qui pour nous provient de la librairie plotly. En effet, lorsque nous essayons de selectionner un pays et tout de suite après on souhaite le déselectionner, cela ne fonctionne pas. La seule manière de désélectionner un pays sélectionné est de sélectionner un autre pays et ensuite de recliquer sur la pays que l'on souhaitait désélectionner.
Nous avons rencontrés également une petite difficulté lorsque nous sélectionnons une période à filtrer avec les input de dates nous ne pouvons plus sélectionner une ligne du tableau
df_table=(df.groupby('Country')
.agg({'Confirmed':'sum', 'Recovered': 'sum','Deaths':'sum'})
.reset_index())
df_table['% Recovered']=(100*(df_table['Recovered']/df_table['Confirmed'])).map('{:,.2f} %'.format)
df_table['% Deaths']=(100*(df_table['Deaths']/df_table['Confirmed'])).map('{:,.2f} %'.format)
df_table
Il était pour nous nécéssaire de créer les deux indicateurs taux de décés et taux de guérisons. En effet, en voyant uniquement les volumes de décés ou de guérisons, on n'a du mal à se rendre compte de l'ampleur du phénomène et il est difficile de comparer deux pays entre eux sans ce rapporter à la taille de population de chacun des deux pays.
Sur le tableau dans l'application, en haut de chaque colonne il est possible de filter et trier les données.
Le graphique
fig_default = px.line(df, x='Date', y=df["Deaths"], color=df["Country"] )
fig_default.show()
Il nous a semblait interressant de laisser libre choix à l'utlisateur de la vision qu'il souhaite étudiée : vision décés ou vision guérisons, quel pays il souhaite étudié et enfin sur quelle période il choisi d'observer l'évolution.
Vous pouvez voir ci dessous, les deux input qui permettent la selection de la période:
Lorsque vous entrez une date de début et de fin dans ces input le graphique se met à jour mais la carte se met elle aussi à jour en conséquence. Par exemple, entre le 01/01/20 et 31/01/20 on ne retrouve que des données sur la Chine.
En effet, la Covid-19 n'était présente qu'en Chine sur cette période.
Dans notre application, il est possible de selectionner un ou plusieurs pays comme évoqué précédemment avec le graphique qui se mettra alors à jour. Nous expliciterons comment nous avons développé cette partie intéractive dans la partie intéractivité du blog.
class smart_dict(dict):
def __missing__(self, key):
return key
dict_to_iso_alpha = {'Afghanistan': 'AFG', 'Albania': 'ALB', 'Algeria': 'DZA','Andorra':'AND', 'Angola': 'AGO', 'Antigua and Barbuda':'ATG',
'Argentina': 'ARG','Armenia':'ARM', 'Australia': 'AUS',
'Austria': 'AUT', 'Azerbaijan':'AZE','Bahamas':'BHS', 'Bahrain': 'BHR', 'Bangladesh': 'BGD','Barbados':'BRB',
'Belarus':'BLR','Belgium': 'BEL','Belize':'BLZ', 'Benin': 'BEN','Bhutan':'BTN', 'Bolivia': 'BOL',
'Bosnia and Herzegovina': 'BIH', 'Botswana': 'BWA', 'Brazil': 'BRA','Brunei':'BRN','Bulgaria': 'BGR',
'Burma':'MMR','Burkina Faso': 'BFA',
'Burundi': 'BDI','Cabo Verde':'CPV', 'Cambodia': 'KHM', 'Cameroon': 'CMR', 'Canada': 'CAN', 'Central African Republic': 'CAF',
'Chad': 'TCD', 'Chile': 'CHL', 'China': 'CHN', 'Colombia': 'COL', 'Comoros': 'COM', 'Congo (Kinshasa)': 'COD',
'Congo (Brazzaville)': 'COG', 'Costa Rica': 'CRI', "Cote d'Ivoire": 'CIV', 'Croatia': 'HRV', 'Cuba': 'CUB','Cyprus':'CYP',
'Czechia': 'CZE', 'Denmark': 'DNK', 'Dominica':'DMA','Djibouti': 'DJI', 'Dominican Republic': 'DOM', 'Ecuador': 'ECU',
'Egypt': 'EGY', 'El Salvador': 'SLV', 'Equatorial Guinea': 'GNQ', 'Eritrea': 'ERI', 'Estonia':'EST','Ethiopia': 'ETH',
'Eswatini':'SWZ','Fiji':'FJI','Georgia':'GEO','Grenada':'GRD','Guyana':'GUY',
'Finland': 'FIN', 'France': 'FRA', 'Gabon': 'GAB', 'Gambia': 'GMB', 'Germany': 'DEU', 'Ghana': 'GHA',
'Greece': 'GRC', 'Guatemala': 'GTM', 'Guinea': 'GIN', 'Guinea-Bissau': 'GNB', 'Haiti': 'HTI', 'Holy See':'VAT',
'Honduras': 'HND',
'Hong Kong, China': 'HKG', 'Hungary': 'HUN', 'Iceland': 'ISL', 'India': 'IND', 'Indonesia': 'IDN', 'Iran': 'IRN',
'Iraq': 'IRQ', 'Ireland': 'IRL', 'Israel': 'ISR', 'Italy': 'ITA', 'Jamaica': 'JAM', 'Japan': 'JPN', 'Jordan': 'JOR',
'Kazakhstan':'KAZ',
'Kenya': 'KEN', 'Kiribati':'KIR','Korea, Dem. Rep.': 'PRK', 'Korea, South': 'KOR', 'Kosovo':'XXK','Kyrgyzstan':'KGZ',
'Kuwait': 'KWT','Laos':'LAO' ,'Latvia':'LVA','Lebanon': 'LBN', 'Lesotho': 'LSO',
'Liberia': 'LBR', 'Libya': 'LBY','Liechtenstein':'LIE','Lithuania':'LTU', 'Luxembourg':'LUX',
'Madagascar': 'MDG', 'Malawi': 'MWI', 'Malaysia': 'MYS', 'Maldives':'MDV','Mali': 'MLI','Malta':'MLT','Marshall Islands':'MHL',
'Mauritania': 'MRT', 'Mauritius': 'MUS', 'Mexico': 'MEX', 'Mongolia': 'MNG', 'Montenegro': 'MNE', 'Morocco': 'MAR',
'Mozambique': 'MOZ', 'Myanmar': 'MMR', 'Namibia': 'NAM', 'Nepal': 'NPL', 'Netherlands': 'NLD', 'New Zealand': 'NZL',
'Nicaragua': 'NIC', 'Niger': 'NER', 'Nigeria': 'NGA', 'Norway': 'NOR', 'Oman': 'OMN', 'Pakistan': 'PAK', 'Panama': 'PAN',
'Paraguay': 'PRY', 'Peru': 'PER', 'Philippines': 'PHL', 'Poland': 'POL', 'Portugal': 'PRT', 'Puerto Rico': 'PRI','Qatar':'QAT',
'Reunion': 'REU', 'Romania': 'ROU', 'Russia':'RUS','Rwanda': 'RWA', 'Sao Tome and Principe': 'STP', 'Saudi Arabia': 'SAU',
'Senegal': 'SEN', 'Serbia': 'SRB', 'Sierra Leone': 'SLE', 'Singapore': 'SGP', 'Slovak Republic': 'SVK',
'Slovenia': 'SVN', 'Somalia': 'SOM', 'South Africa': 'ZAF', 'Spain': 'ESP', 'Sri Lanka': 'LKA', 'Sudan': 'SDN',
'Swaziland': 'SWZ', 'Sweden': 'SWE', 'Switzerland': 'CHE', 'Syria': 'SYR', 'Taiwan': 'TWN', 'Tanzania': 'TZA',
'Thailand': 'THA', 'Togo': 'TGO', 'Trinidad and Tobago': 'TTO', 'Tunisia': 'TUN', 'Turkey': 'TUR', 'Uganda': 'UGA',
'United Kingdom': 'GBR', 'United Arab Emirates': 'ARE','US': 'USA', 'Uruguay': 'URY', 'Venezuela': 'VEN', 'Vietnam': 'VNM',
'West Bank and Gaza': 'PSE', 'Yemen': 'YEM', 'Zambia': 'ZMB', 'Zimbabwe': 'ZWE',
'Micronesia':'FSM','Moldova':'MDA','Monaco':'MCO','North Macedonia':'MKD','Papua New Guinea':'PNG',
'Saint Kitts and Nevis':'KNA','Saint Lucia':'LCA','Saint Vincent and the Grenadines':'VCT',
'Samoa':'WSM','Vanuatu':'VUT','San Marino':'SMR', 'Seychelles':'SYC',
'Slovakia':'SVK', 'Solomon Islands':'SLB', 'South Sudan':'SSD', 'Suriname':'SUR',
'Taiwan*':'TWN', 'Tajikistan':'TJK', 'Timor-Leste':'TLS', 'Ukraine':'UKR',
'Uzbekistan':'UZB', 'Vanuatu':'VUT'
}
df__ = df.groupby(["Country"]).sum()
df__["Country"] = df__.index
df__["ISO alpha-3"] = df__["Country"].map(smart_dict(dict_to_iso_alpha))
df__["Deaths in log10"] = np.log10(df__["Deaths"])
ticks = np.arange(2, 8.1, 1)
fig_choropleth = go.Figure(data=go.Choropleth(
locations=df__["Country"].map(dict_to_iso_alpha),
text=df__["Country"],
hoverinfo='text',
z=df__["Deaths in log10"],
colorbar_tickvals=ticks,
colorbar_title="Décès",
colorscale="reds",
colorbar_ticktext=[ "100" if p<3 else "{:d}k".format( int((10**(p-3))) ) if p<6 else "{:d}M".format( int((10**(p-6))) ) for p in ticks] ) )
fig_choropleth.update_geos(projection_type="natural earth")
fig_choropleth.show()
Pour avoir le code plus détaillé sur cette partie intéractivité, nous vous invitions à vous rendre dans le code source de l'applicaton.
@app.callback(
Output('datatable-interactivity', 'selected_rows'),
Output('map', 'figure'),
[Input("map", "clickData")]
)
def update_map(clickData):
...
return list(selections), fig_choropleth
La fonction ci-dessus prend en entrée les pays sélectionnés sur la carte qui se trouvent dans l'objet 'clickData' . Ensuite, en sortie on a la carte mise à jour ainsi qu'une sélection des pays sélectionnées sur la carte qui est faite au niveau du tableau.
def update_graph(selected_rows, selected_columns):
...
return px.line(_df, x='Date', y=_df[selected_column], color=_df["Country"], title="Evolution du nombre de décés")
Cette fonction prend en paramètre les lignes (correspondantes au pays) sélectionnées dans le tableau ou sur la carte.
En effet, l'utilisateur a la possibilité de sélectionner un pays directement sur le tableau ou bien de choisir des pays sur le map. Dés lors où l'utilisateur clique sur un pays du map, ce pays est alors coché au niveau du tableau.
Enfin, l'utilisateur peut également sélectionner une des 2 colonnes (Deaths et Recovered) qu'il souhaite observer sur le graphique.
Ensuite, la fonction renvoie le graphique mis à jour avec les paramètres (pays et variables ) demandés par l'utilisateur.