Les packages sont des ensembles de fonctions qui nous permettent de faire simplement des opérations complexes. Par exemple des graphiques, des calculs ou encore des cartes avec une syntaxe simplifiée (plus simple que s'il fallait le faire avec les fonctions de base de python).
import pandas as pd # package pour la gestion des données
from matplotlib import pyplot as plt # Pour les graphiques
import seaborn as sns # Pour des graphiques plus complexes (avec plusieurs catégories)
import folium # Pour faire des cartes
Pour toute analyse il faut importer les données nécessaires. Nous travaillerons ici sur les données du spipoll. Elle sont stockées à l'adresse suivante : https://depot.vigienature-ecole.fr/datasets/papers/spipoll.csv
Nous vous proposons une ligne de commande pour importer ce ficher depuis internet mais vous pouvez également travailler à partir d'un fichier téléchargé localement.
Attention, le fichier fait environ : 600000 lignes et 20 colonnes, le télécharement peut être un peu long.
# importer les données
# le chargement peut être long
spipoll_data = pd.read_csv('/home/simon/github/datasets/papers/spipoll.csv', low_memory=False)
spipoll_data # afficher l'objet importé (ici le tableau de données)
Numero_observation | Ordre | Espece | Nombre_individus | Latitude | Longitude | Heure | Mois | Annee | Temperature | Vent | Nebulosite | Plante | Famille_plante | Pollinisation_plante | Fleur_ombragee | Type_plante | Departement | Date_observation | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2 | Hymenoptera | Bourdons noirs à bande(s) jaune(s) et cul blanc | je n'ai pas l'information | 48.844975 | 2.358313 | 10 | 6 | 2019 | 03_20-30ºC | 02_faible, irrégulier | 02_25-50% | Acanthe molle | Acanthaceae | entomogame | Oui | plantée | 75 | 20/06/2019 |
1 | 2 | Diptera | Mouches à damier | entre 2 et 5 | 48.844975 | 2.358313 | 10 | 6 | 2019 | 03_20-30ºC | 02_faible, irrégulier | 02_25-50% | Acanthe molle | Acanthaceae | entomogame | Oui | plantée | 75 | 20/06/2019 |
2 | 2 | Hymenoptera | Abeille mellifère | entre 2 et 5 | 48.844975 | 2.358313 | 10 | 6 | 2019 | 03_20-30ºC | 02_faible, irrégulier | 02_25-50% | Acanthe molle | Acanthaceae | entomogame | Oui | plantée | 75 | 20/06/2019 |
3 | 17 | Hemiptera | Pucerons | plus de 5 | 44.707981 | 4.667456 | 11 | 1 | 2019 | 02_10-20ºC | 03_faible, continu | 01_0-25% | Le Sénéçon jacobée | NaN | NaN | Non | spontanée | 07 | 03/01/2019 |
4 | 17 | Hemiptera | Pucerons | plus de 5 | 44.707981 | 4.667456 | 11 | 1 | 2019 | 02_10-20ºC | 03_faible, continu | 01_0-25% | Le Sénéçon jacobée | NaN | NaN | Non | spontanée | 07 | 03/01/2019 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
583445 | 76214 | Diptera | Mouches pâles | 1 | 43.555448 | 0.207342 | 16 | 12 | 2023 | 02_10-20ºC | 01_nul | 04_75-100% | Euryops (Euryops chrysanthemoides) | NaN | NaN | Non | plantée | NaN | 10/12/2023 |
583446 | 76214 | Diptera | Moustiques, Tipules et autres diptères Nématoc... | 1 | 43.555448 | 0.207342 | 16 | 12 | 2023 | 02_10-20ºC | 01_nul | 04_75-100% | Euryops (Euryops chrysanthemoides) | NaN | NaN | Non | plantée | NaN | 10/12/2023 |
583447 | 76214 | Hemiptera | Pentatomes | 1 | 43.555448 | 0.207342 | 16 | 12 | 2023 | 02_10-20ºC | 01_nul | 04_75-100% | Euryops (Euryops chrysanthemoides) | NaN | NaN | Non | plantée | NaN | 10/12/2023 |
583448 | 76214 | Hemiptera | Homoptères autres | plus de 5 | 43.555448 | 0.207342 | 16 | 12 | 2023 | 02_10-20ºC | 01_nul | 04_75-100% | Euryops (Euryops chrysanthemoides) | NaN | NaN | Non | plantée | NaN | 10/12/2023 |
583449 | 76214 | Hymenoptera | Terebrants Chalcidiens et autres | 1 | 43.555448 | 0.207342 | 16 | 12 | 2023 | 02_10-20ºC | 01_nul | 04_75-100% | Euryops (Euryops chrysanthemoides) | NaN | NaN | Non | plantée | NaN | 10/12/2023 |
583450 rows × 19 columns
Pour commencer, nous allons essayer de comprendre comment les données sont réparties dans la base de données. Dans le Spipoll, les participants et participantes choisissent librement les plantes sur lesquelles ils font leurs observations. Ils doivent noter les plantes sur lesquelles ils font des mesures. Il est donc possible de regarder les plantes les plus fréquentes pour avoir les données les plus fiables possibles.
Ici, nous allons compter le nombre de valeurs uniques de la variable 'Numero_observation' qui permet d'identifier chaque réalisation du protocole grâce à la fonction nunique(), en fonction de la plante et de la famille de la plante grâce à la fonction groupby() et de la liste des colonnes que l'on souhaite garder entre crochet.
Nous allons ensuite les ranger dans l'ordre décroissant avec la fonction sort_values() pour connaitre les plantes les plus vues.
La fonction head() nous permet de ne garder que les premières lignes du jeu de données.
# regrouper les résultats par plante et famille de plante et compter le nombre de valeurs unique de numero d'observation
result = spipoll_data.groupby(['Plante','Famille_plante'], as_index=False)['Numero_observation'].nunique()
# renommer la colonne résultat de la ligne précédente
result = result.rename(columns={'Numero_observation': 'Nombre_observation_unique'})
# ranger les résultats dans l'ordre décroissant pour choisir des plantes avec beaucoup de données
result_sorted = result.sort_values(by='Nombre_observation_unique', ascending=False)
# afficher les 10 premiers résultats
result_sorted.head(10)
Plante | Famille_plante | Nombre_observation_unique | |
---|---|---|---|
751 | Le Romarin | Lamiaceae | 918 |
739 | Le Lierre grimpant | Araliaceae | 839 |
232 | Carotte sauvage | Apiaceae | 826 |
6 | Achillée millefeuille | Asteraceae | 610 |
723 | Le Buddléia de David | Scrophulariaceae | 594 |
1074 | Pâquerette | Asteraceae | 526 |
714 | Laurier tin | Adoxaceae | 404 |
716 | Lavande | Lamiaceae | 389 |
145 | Berce des prés | Apiaceae | 387 |
749 | Le Prunier épineux | Rosaceae | 355 |
Pour cet exemple, nous allons travailler sur le Romarin, le Buddléia de David et la carotte sauvage respectivement 1ère, 3éme et 5ème plantes les plus étudiées dans le spipoll (vous pourrez changer facilement les plantes en faisant des rechercher/remplacer dans votre propre document).
Nous allons créer un nouveau jeu de données en utilisant une technique de filtre classique pour les data.frame créés avec le package pandas.
# Filtrer le DataFrame pour ne conserver que les lignes où 'Plante' est égale à 'Le Buddléia de David' ou 'Carotte sauvage' ou 'Le Romarin'
spipoll_plante_data = spipoll_data[(spipoll_data['Plante'] == 'Le Buddléia de David') | (spipoll_data['Plante'] == 'Carotte sauvage') | (spipoll_data['Plante'] == 'Le Romarin')]
Avant de nous lancer dans une analyse plus poussée, nous allons vérifier que les données sont comparables. Il faut qu'elles soient comparables dans le temps (au sein d'une année et entre année) et au niveau spatial au moins partiellement.
Pour cela, nous allons effectuer une série de graphiques et de cartes.
Commençons par le mois :
Nous allons utiliser à nouveau la fonction groupby
# observation des données pour savoir si elles sont comparables
# dans le temps
# en fonction du mois
res_mois = spipoll_plante_data.groupby(['Plante','Mois'], as_index=False)['Numero_observation'].nunique()
# renommer la colonne résultat de la ligne précédente
res_mois = res_mois.rename(columns={'Numero_observation': 'Nombre_observation_unique'})
plt.figure(figsize=(8, 6)) # Définir la taille du graphique (largeur et hauteur)
sns.lineplot(x='Mois', # colonne à utiliser pour l'axe des x
y='Nombre_observation_unique', # colonne à utiliser pour l'axe des y
hue='Plante', # Créer une ligne par plante
data=res_mois, # nom du tableau de données à utiliser
marker='o', # code du style
palette={'Le Buddléia de David':'purple',
'Carotte sauvage':'grey',
'Le Romarin':'blue'}, # couleurs des lignes en fonction des noms des plantes
)
plt.xlabel('Mois') # Nom de l'axe des x
plt.ylabel('Nombre d\'observations') # Nom de l'axe des y
plt.title('Nombre d\'observations en fonction du mois') # Ajouter un titre au graphique
plt.show() # Afficher le graphique
Nous pouvons voir sur ce graphique que les données du Romarin sont très différentes des données de la carotte sauvage et du buddléia. Il faudra donc en tenir compte dans la suite de notre analyse. En effet les résultats seront peut-être le résultat de la différence de phénologie des groupes d'espèces (les papillons par exemple sont plus nombreux en été).
Nous pouvons donc maintenant passer à la comparaison des années.
# dans le temps
# en fonction de l'année
res_annee = spipoll_plante_data.groupby(['Plante','Annee'], as_index=False)['Numero_observation'].nunique()
res_annee
plt.figure(figsize=(8, 6)) # Width and Height of the chart
sns.lineplot(x='Annee',
y='Numero_observation',
hue='Plante', # Create 2 line plots according to labels in 'z'
data=res_annee,
marker='o', # Style used to mark the join between 2 points
palette={'Le Buddléia de David':'purple',
'Carotte sauvage':'grey',
'Le Romarin':'blue'}, # Colors of the lines
)
plt.xlabel('Mois') # x-axis name
plt.ylabel('Nombre d\'observations') # y-axis name
plt.title('Nombre d\'observations en fonction de l\'année') # Add a title
# dans l'espace
Text(0.5, 1.0, "Nombre d'observations en fonction de l'année")
Ici les résultats sont beaucoup plus proche pour les 3 plantes. La carotte sauvage est un peu différente des deux autres avec moins d'occurence au début de la période. ATTENTION : cette brusque augmentation est majoritairement due à un changement de referentiel pour les plantes (maintenant les noms des plantes sont issus d'une base de données, ils étaient auparavant saisis manuellement par les utilisateurs qui pouvaient utiliser des noms différents pour nommer la même plante).
Nous allons désormais passer à une comparaison spatiale et il faudra donc réaliser une carte.
Pour rendre cette étape un peu moins répétitive, nous allons utiliser une fonction qui prendra en entrée le jeu de donnée et le nom de la plante que nous souhaitons comparer.
# construction du jeu de donnée pour les carte
res_spacial = spipoll_plante_data.groupby(['Numero_observation','Plante','Latitude', 'Longitude'], as_index=False)['Numero_observation'].nunique()
# fonction pour représenter les points dans des couleurs différentes en fonction des espèces
def get_color(type):
# si le nom de la plante, renvoyer la couleur correspondante
if type == 'Le Buddléia de David':
return 'purple'
elif type == 'Le Romarin':
return 'blue'
else:
return 'gray'
# fonction pour créer la carte
def make_map (data, plante):
# sélectionner uniquement les données pour la plante qui nous intéresse
res_spacial = data[data['Plante'] == plante]
# créer la carte initiale à la moyenne des coordonnées de tous les points
carte = folium.Map(location=[res_spacial['Latitude'].mean(), res_spacial['Longitude'].mean()], zoom_start=6)
# Ajouter des marqueurs à la carte pour chaque participation
for index, row in res_spacial.iterrows():
folium.CircleMarker(
location=[row['Latitude'], row['Longitude']], # les coordonnées
radius=5, # Taille du cercle
color=get_color(row['Plante']), # couleur en fonction de la colonne plante
fill_color=get_color(row['Plante']), # appliquer la fonction couleur proposée plus haut
fill_opacity=0.6, # ajouter de la transparence (0 = totalement transparent 1 = totalement opaque)
).add_to(carte)
return(carte)
make_map(res_spacial, 'Le Buddléia de David')
make_map(res_spacial, 'Carotte sauvage')
make_map(res_spacial, 'Le Romarin')
Ici aussi, le romarin semble avoir une répartition plus dense au sud même si cela reste assez équilibré à première vue.
Nous allons donc regarder les résultats sur ces trois plantes en gardant à l'esprit les résultats observés. Nous allons compter le nombre de fois où l'on observe un insecte selon son ordre et en fonction de l'espèce de plante.
Nombre_observations=spipoll_plante_data.groupby(['Plante', 'Ordre'], as_index = False ).size()
# renommer la colonne résultat de la ligne précédente
Nombre_observations = Nombre_observations.rename(columns={'size': 'Nombre_insectes'})
# Set the figure size
plt.figure(figsize=(8, 6))
# grouped barplot
ax = sns.barplot(x="Ordre", y="Nombre_insectes", hue="Plante", data=Nombre_observations, errorbar=None);
ax.set(xlabel='Ordre', ylabel='Nombre d\'observations')
plt.show()
Ce premier graphique n'est pas satisfaisant. On ne peut pas lire les ordres d'insectes. Mais plus important, on représente le nombre de protocoles réalisés. Comme ce nombre varie, une comparaison est donc une mauvaise idée. Nous allons donc travailler sur des proportions.
La première étape est de calculer le nombre de protocole réalisés.
res_plante = Nombre_observations.groupby('Plante', as_index=False).sum('Nombre_insectes')
# renommer la colonne résultat de la ligne précédente
res_plante = res_plante.rename(columns={'Nombre_insectes': 'Nombre_insectes_plante'})
res_plante
Plante | Nombre_insectes_plante | |
---|---|---|
0 | Carotte sauvage | 25111 |
1 | Le Buddléia de David | 8656 |
2 | Le Romarin | 6187 |
Il faut ensuite fusionner les résultats (faire une jointure) et diviser les nombre d'insectes observés par ordre par le nombre total d'insectes observés par plante.
res_proportion = Nombre_observations.merge(res_plante, on='Plante')
res_proportion['proportion'] = res_proportion['Nombre_insectes'] / res_proportion['Nombre_insectes_plante']
# Set the figure size
plt.figure(figsize=(8, 6))
# grouped barplot
ax = sns.barplot(x="Ordre", y="proportion", hue="Plante", data=res_proportion, errorbar=None);
ax.set(xlabel='Ordre', ylabel='Proportion')
plt.xticks(rotation=30, ha='right', rotation_mode='anchor')
plt.tight_layout()
plt.show()
Pour un graphique plus lisible, nous n'allons garder que les 4 groupes les plus abondants.
# garder les pollinisateurs les plus courants
res_proportion_red = res_proportion[res_proportion['Ordre'].isin(['Coleoptera', 'Diptera', 'Hymenoptera', 'Lepidoptera'])]
# Set the figure size
plt.figure(figsize=(8, 6))
# grouped barplot
ax = sns.barplot(x="Ordre", y="proportion", hue="Plante", data=res_proportion_red, errorbar=None);
ax.set(xlabel='Ordre', ylabel='Proportion')
plt.tight_layout()
plt.show()
# Evolution des proportions des insectes en fonction du mois de l\année
def plot_mois(insect_order):
# filtrer pour une plante
data_carotte = spipoll_data
# garder les pollinisateurs les plus courants
data_carotte_poll = data_carotte[data_carotte['Ordre'].isin(['Coleoptera', 'Diptera', 'Hymenoptera', 'Lepidoptera'])]
data_carotte_poll_mois = data_carotte_poll.groupby(['Ordre', 'Mois'], as_index = False).size()
data_carotte_poll_mois_sum = data_carotte_poll_mois.groupby('Mois', as_index=False).sum('size')
# passage en proportion
data_carotte_poll_mois_proportion = data_carotte_poll_mois.merge(data_carotte_poll_mois_sum, on='Mois')
data_carotte_poll_mois_proportion['proportion'] = data_carotte_poll_mois_proportion['size_x'] / data_carotte_poll_mois_proportion['size_y']
data_carotte_poll_mois_proportion = data_carotte_poll_mois_proportion[data_carotte_poll_mois_proportion['Ordre'].isin(insect_order)]
# grouped barplot
ax = sns.barplot(x="Mois", y="proportion", hue="Ordre", data=data_carotte_poll_mois_proportion, errorbar=None);
ax.set(xlabel='Mois', ylabel='Nombre d\'observations')
plt.xticks(rotation=30, ha='right', rotation_mode='anchor')
plt.tight_layout()
plt.show()
plot_mois(['Coleoptera'])
plot_mois(['Diptera'])
plot_mois(['Hymenoptera'])
plot_mois(['Lepidoptera'])
# Adjust layout
plt.tight_layout()
<Figure size 640x480 with 0 Axes>