> [!note] L'écosystème Python pour la Data Science > `pandas` fait partie d'un écosystème puissant comprenant d'autres bibliothèques clés : > - **`NumPy`** : Pour le calcul numérique haute performance, notamment avec des tableaux multidimensionnels. `pandas` l'utilise en interne. > - **`Matplotlib` / `Seaborn`** : Pour la visualisation de données. > - **`Scikit-learn`** : Pour l'apprentissage automatique. > - **`SciPy`** : Pour le calcul scientifique et technique. > Maîtriser `pandas` est une porte d'entrée vers l'exploitation de toutes ces bibliothèques. # Détails sur pandas pour manipuler de la donnée ## Pourquoi `pandas` ? `pandas` est une bibliothèque open source Python qui fournit des structures de données rapides, flexibles et expressives, conçues pour rendre le travail avec des données "relationnelles" ou "étiquetées" à la fois facile et intuitif. Elle est construite sur la bibliothèque `NumPy` et excelle dans la gestion de données tabulaires, similaires à ce que l'on trouve dans des feuilles de calcul ou des bases de données SQL. ## Pré-requis et Objectifs Ce chapitre suppose que vous avez des bases solides en Python (variables, types de données, listes, dictionnaires, boucles, fonctions). Si ce n'est pas le cas, un rappel sur ces concepts est fortement recommandé. À la fin de ce chapitre, vous serez capable de : * Comprendre les structures de données `Series` et `DataFrame` de `pandas`. * Créer et inspecter ces structures de données. * Sélectionner et filtrer des données de manière efficace. * Manipuler les données (ajouter/supprimer des colonnes, gérer les valeurs manquantes, appliquer des fonctions). * Réaliser des opérations de groupement et d'agrégation. * Fusionner et concaténer différentes sources de données. * Lire et écrire des données depuis/vers des fichiers courants. > [!tip] Installation de pandas > Si `pandas` n'est pas déjà installé dans votre environnement Python, vous pouvez le faire via `pip` : > ```bash > pip install pandas numpy > ``` > Il est également souvent inclus dans les distributions Data Science comme Anaconda. Commençons sans plus attendre par les structures de données fondamentales de `pandas`. # Les Structures de Données Fondamentales de `pandas` `pandas` introduit deux structures de données principales : le `Series` et le `DataFrame`. Elles sont les piliers de toute manipulation de données avec cette bibliothèque. ## Series > [!definition] Series > Un `Series` est un tableau unidimensionnel étiqueté capable de contenir n'importe quel type de données (entiers, chaînes de caractères, nombres flottants, objets Python, etc.). Il peut être vu comme une colonne unique d'une feuille de calcul ou un dictionnaire à une dimension avec un index ordonné. Chaque élément d'un `Series` est associé à un *index*, qui peut être numérique (par défaut, de 0 à $N-1$) ou défini par des étiquettes personnalisées. ### Création d'un Series Un `Series` peut être créé à partir de diverses sources : * **À partir d'une liste Python ou d'un tableau NumPy :** ```python import pandas as pd import numpy as np # À partir d'une liste s_list = pd.Series([10, 20, 30, 40, 50]) print("Series depuis une liste:\n", s_list) # À partir d'un tableau NumPy arr = np.array([1.1, 2.2, 3.3, 4.4]) s_numpy = pd.Series(arr) print("\nSeries depuis un tableau NumPy:\n", s_numpy) ``` ``` Series depuis une liste: 0 10 1 20 2 30 3 40 4 50 dtype: int64 Series depuis un tableau NumPy: 0 1.1 1 2.2 2 3.3 3 4.4 dtype: float64 ``` On observe que `pandas` attribue un index numérique par défaut (0, 1, 2, ...) et infère le type de données (`dtype`). * **Avec un index personnalisé :** ```python s_indexed = pd.Series([10, 20, 30], index=['a', 'b', 'c']) print("\nSeries avec index personnalisé:\n", s_indexed) ``` ``` Series avec index personnalisé: a 10 b 20 c 30 dtype: int64 ``` * **À partir d'un dictionnaire Python :** Les clés du dictionnaire deviennent l'index, et les valeurs deviennent les données. ```python data_dict = {'Paris': 2.14, 'Marseille': 0.86, 'Lyon': 0.52} s_dict = pd.Series(data_dict) print("\nSeries depuis un dictionnaire:\n", s_dict) ``` ``` Series depuis un dictionnaire: Paris 2.14 Marseille 0.86 Lyon 0.52 dtype: float64 ``` ### Accès aux Éléments d'un Series Vous pouvez accéder aux éléments d'un `Series` de plusieurs manières : * **Par index numérique (position) :** ```python print("Élément à la position 1 (s_list):", s_list[1]) ``` ``` Élément à la position 1 (s_list): 20 ``` * **Par label d'index :** ```python print("Population de Paris (s_dict):", s_dict['Paris']) ``` ``` Population de Paris (s_dict): 2.14 ``` * **Par tranche (slicing) :** ```python print("\nTranche de s_list:\n", s_list[1:4]) print("\nTranche de s_dict (par label):\n", s_dict['Marseille':'Lyon']) ``` ``` Tranche de s_list: 1 20 2 30 3 40 dtype: int64 Tranche de s_dict (par label): Marseille 0.86 Lyon 0.52 dtype: float64 ``` > [!warning] Slicing avec labels > Contrairement au slicing Python standard où l'élément final est exclus, le slicing avec des labels d'index dans `pandas` *inclut* l'élément final. ### Opérations Vectorisées sur un Series Un des grands avantages de `pandas` est sa capacité à effectuer des opérations vectorisées (élément par élément) de manière très efficace, comme avec `NumPy`. ```python print("s_list multiplié par 2:\n", s_list * 2) print("\nRacine carrée de s_numpy:\n", np.sqrt(s_numpy)) ``` ``` s_list multiplié par 2: 0 20 1 40 2 60 3 80 4 100 dtype: int64 Racine carrée de s_numpy: 0 1.048809 1 1.483240 2 1.816590 3 2.097618 dtype: float64 ``` ## DataFrame > [!definition] DataFrame > Un `DataFrame` est une structure de données tabulaire à deux dimensions, avec des lignes et des colonnes étiquetées. Il peut être vu comme une feuille de calcul Excel, une table de base de données SQL, ou un dictionnaire de `Series` partageant le même index. C'est la structure de données la plus couramment utilisée en Data Science avec `pandas`. Chaque colonne d'un `DataFrame` est un `Series` en soi. ### Création d'un DataFrame * **À partir d'un dictionnaire de listes ou de tableaux NumPy :** C'est la méthode la plus courante. Les clés du dictionnaire deviennent les noms de colonnes. ```python data = { 'Nom': ['Alice', 'Bob', 'Charlie', 'David'], 'Age': [25, 30, 35, 28], 'Ville': ['Paris', 'Lyon', 'Marseille', 'Paris'], 'Salaire': [50000, 60000, 75000, 52000] } df = pd.DataFrame(data) print("DataFrame depuis un dictionnaire de listes:\n", df) ``` ``` DataFrame depuis un dictionnaire de listes: Nom Age Ville Salaire 0 Alice 25 Paris 50000 1 Bob 30 Lyon 60000 2-Charlie 35 Marseille 75000 3 David 28 Paris 52000 ``` * **Avec un index personnalisé :** ```python df_indexed = pd.DataFrame(data, index=['A', 'B', 'C', 'D']) print("\nDataFrame avec index personnalisé:\n", df_indexed) ``` ``` DataFrame avec index personnalisé: Nom Age Ville Salaire A Alice 25 Paris 50000 B Bob 30 Lyon 60000 C-Charlie 35 Marseille 75000 D David 28 Paris 52000 ``` * **À partir d'une liste de dictionnaires :** Chaque dictionnaire représente une ligne. ```python data_list_dict = [ {'Nom': 'Eve', 'Age': 22, 'Ville': 'Nantes'}, {'Nom': 'Frank', 'Age': 40, 'Ville': 'Bordeaux', 'Salaire': 80000} ] df_from_list_dict = pd.DataFrame(data_list_dict) print("\nDataFrame depuis une liste de dictionnaires:\n", df_from_list_dict) ``` ``` DataFrame depuis une liste de dictionnaires: Nom Age Ville Salaire 0 Eve 22 Nantes NaN 1 Frank 40 Bordeaux 80000.0 ``` > [!note] Valeurs manquantes (NaN) > `pandas` utilise `NaN` (Not a Number) pour représenter les valeurs manquantes. C'est un concept important que nous aborderons plus en détail. ### Inspection d'un DataFrame Une fois un `DataFrame` créé, il est essentiel de pouvoir l'inspecter pour comprendre sa structure et son contenu. * **`.head(n=5)` et `.tail(n=5)` :** Affichent les $n$ premières/dernières lignes. Utile pour un aperçu rapide. ```python print("Premières 3 lignes:\n", df.head(3)) print("\nDernières 2 lignes:\n", df.tail(2)) ``` * **`.info()` :** Fournit un résumé concis du DataFrame, y compris le nombre d'entrées, les types de données de chaque colonne, le nombre de valeurs non nulles et l'utilisation de la mémoire. ```python df.info() ``` ``` <class 'pandas.core.frame.DataFrame'> RangeIndex: 4 entries, 0 to 3 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Nom 4 non-null object 1 Age 4 non-null int64 2 Ville 4 non-null object 3 Salaire 4 non-null int64 dtypes: int64(2), object(2) memory usage: 256.0+ bytes ``` * **`.describe()` :** Génère des statistiques descriptives pour les colonnes numériques (nombre, moyenne, écart-type, min, max, quartiles). ```python print("\nStatistiques descriptives:\n", df.describe()) ``` ``` Statistiques descriptives: Age Salaire count 4.000000 4.000000 mean 29.500000 59250.000000 std 4.358899 11634.303916 min 25.000000 50000.000000 25% 27.250000 51500.000000 50% 29.000000 56000.000000 75% 31.250000 63750.000000 max 35.000000 75000.000000 ``` * **`.shape` :** Retourne un tuple représentant les dimensions (lignes, colonnes) du DataFrame. ```python print("\nForme du DataFrame (lignes, colonnes):", df.shape) # (4, 4) ``` * **`.columns` et `.index` :** Affichent les noms des colonnes et l'index des lignes, respectivement. ```python print("\nNoms des colonnes:", df.columns) print("Index des lignes:", df.index) ``` * **`.dtypes` :** Affiche les types de données de chaque colonne. ```python print("\nTypes de données des colonnes:\n", df.dtypes) ``` # Indexation et Sélection Avancées Accéder et sélectionner des sous-ensembles de données est une opération fondamentale. `pandas` offre des outils puissants et flexibles pour cela. ## Sélection de Colonnes Vous pouvez sélectionner une ou plusieurs colonnes d'un `DataFrame`. * **Sélection d'une seule colonne :** Retourne un `Series`. ```python age_column = df['Age'] print("Colonne 'Age' (Series):\n", age_column) print("Type de age_column:", type(age_column)) ``` ``` Colonne 'Age' (Series): 0 25 1 30 2 35 3 28 Name: Age, dtype: int64 Type de age_column: <class 'pandas.core.series.Series'> ``` Vous pouvez aussi utiliser la notation par attribut (`df.colonne`), mais c'est moins robuste si le nom de colonne contient des espaces ou est un mot-clé Python. ```python # age_column_attr = df.Age # Fonctionne aussi ``` * **Sélection de plusieurs colonnes :** Retourne un `DataFrame`. ```python subset_df = df[['Nom', 'Ville']] print("\nSous-DataFrame avec 'Nom' et 'Ville':\n", subset_df) print("Type de subset_df:", type(subset_df)) ``` ``` Sous-DataFrame avec 'Nom' et 'Ville': Nom Ville 0 Alice Paris 1 Bob Lyon 2-Charlie Marseille 3 David Paris Type de subset_df: <class 'pandas.core.frame.DataFrame'> ``` ## Sélection de Lignes La sélection de lignes se fait principalement via les attributs `.loc` et `.iloc`. ### `.loc` (Sélection par Label) > [!definition] `.loc` > `.loc` est utilisé pour la sélection basée sur les **labels** (noms) des lignes et des colonnes. Il peut prendre un label unique, une liste de labels, une tranche de labels, ou une expression booléenne. * **Sélection d'une ligne par son label d'index :** ```python # Si l'index est numérique par défaut (0, 1, 2, ...) print("Ligne avec index 0 (via .loc):\n", df.loc[0]) # Si l'index est personnalisé (ex: 'A', 'B', 'C', ...) print("\nLigne avec index 'A' (df_indexed):\n", df_indexed.loc['A']) ``` * **Sélection de plusieurs lignes par labels :** ```python print("\nLignes avec index 0 et 2:\n", df.loc[[0, 2]]) print("\nLignes de 'A' à 'C' (inclus) dans df_indexed:\n", df_indexed.loc['A':'C']) ``` * **Sélection de lignes et de colonnes spécifiques par labels :** La syntaxe est `df.loc[lignes, colonnes]`. ```python # Ligne 0, colonnes 'Nom' et 'Ville' print("\nNom et Ville de la ligne 0:\n", df.loc[0, ['Nom', 'Ville']]) # Lignes 0 à 2, toutes les colonnes print("\nLignes 0 à 2, toutes colonnes:\n", df.loc[0:2, :]) ``` ### `.iloc` (Sélection par Position Entière) > [!definition] `.iloc` > `.iloc` est utilisé pour la sélection basée sur les **positions entières** (numériques) des lignes et des colonnes, de manière similaire au slicing de listes Python. L'indexation commence à 0. * **Sélection d'une ligne par sa position :** ```python print("Ligne à la position 1 (via .iloc):\n", df.iloc[1]) ``` * **Sélection de plusieurs lignes par positions :** ```python print("\nLignes aux positions 0 et 2:\n", df.iloc[[0, 2]]) print("\nLignes des positions 0 à 2 (exclusif pour la fin):\n", df.iloc[0:2]) ``` * **Sélection de lignes et de colonnes spécifiques par positions :** La syntaxe est `df.iloc[positions_lignes, positions_colonnes]`. ```python # Ligne à la position 0, colonnes aux positions 0 et 2 print("\nÉléments à la position (0,0) et (0,2):\n", df.iloc[0, [0, 2]]) # Lignes aux positions 0 à 2, toutes les colonnes print("\nLignes 0 à 2, toutes colonnes (via .iloc):\n", df.iloc[0:3, :]) ``` > [!warning] `.loc` vs `.iloc` > - `.loc` utilise les **labels** (noms) des lignes et des colonnes. > - `.iloc` utilise les **positions entières** (numériques) des lignes et des colonnes. > - Pour `.loc` avec slicing, l'élément final est **inclus**. > - Pour `.iloc` avec slicing, l'élément final est **exclus** (comme en Python standard). > Il est crucial de bien comprendre cette distinction pour éviter des erreurs subtiles. ## Sélection Booléenne / Filtrage Le filtrage est une technique puissante pour sélectionner des lignes basées sur une ou plusieurs conditions. ```python # Sélectionner les personnes de Paris paris_people = df[df['Ville'] == 'Paris'] print("Personnes de Paris:\n", paris_people) ``` ``` Personnes de Paris: Nom Age Ville Salaire 0 Alice 25 Paris 50000 3 David 28 Paris 52000 ``` Vous pouvez combiner plusieurs conditions avec les opérateurs logiques bitwise : * `&` (ET logique) * `|` (OU logique) * `~` (NON logique) > [!warning] Parenthèses obligatoires > Lors de la combinaison de conditions, chaque condition doit être entre parenthèses pour éviter les erreurs de précédence des opérateurs. ```python # Sélectionner les personnes de Paris ET ayant un salaire > 50000 paris_high_salary = df[(df['Ville'] == 'Paris') & (df['Salaire'] > 50000)] print("\nPersonnes de Paris avec salaire > 50000:\n", paris_high_salary) # Sélectionner les personnes de Lyon OU de Marseille lyon_marseille = df[(df['Ville'] == 'Lyon') | (df['Ville'] == 'Marseille')] print("\nPersonnes de Lyon ou Marseille:\n", lyon_marseille) ``` ### Méthodes utiles pour le filtrage * **`.isin()` :** Pour vérifier si une valeur est présente dans une liste de valeurs. ```python # Sélectionner les personnes dont la ville est dans une liste spécifique selected_cities = ['Lyon', 'Paris'] filtered_by_isin = df[df['Ville'].isin(selected_cities)] print("\nPersonnes de Lyon ou Paris (avec isin):\n", filtered_by_isin) ``` * **`.isnull()` et `.notnull()` :** Pour identifier les valeurs manquantes. ```python df_with_nan = pd.DataFrame({'A': [1, 2, np.nan], 'B': [4, np.nan, 6]}) print("\nDataFrame avec NaN:\n", df_with_nan) print("\nValeurs nulles:\n", df_with_nan.isnull()) print("\nLignes avec des valeurs nulles dans la colonne 'A':\n", df_with_nan[df_with_nan['A'].isnull()]) ``` # Manipulation des Données Une fois les données sélectionnées, il est souvent nécessaire de les modifier, d'ajouter de nouvelles informations ou de gérer les problèmes de qualité. ## Ajout, Suppression, Modification de Colonnes * **Ajout d'une nouvelle colonne :** ```python df['Anciennete'] = [3, 8, 12, 5] # Ajout d'une colonne avec une liste print("DataFrame avec colonne 'Anciennete':\n", df) # Calculer une nouvelle colonne basée sur d'autres colonnes df['Bonus'] = df['Salaire'] * 0.10 print("\nDataFrame avec colonne 'Bonus':\n", df) ``` * **Modification de valeurs :** ```python df.loc[0, 'Salaire'] = 55000 # Modifier le salaire d'Alice print("\nDataFrame après modification du salaire d'Alice:\n", df) df['Age'] = df['Age'] + 1 # Augmenter l'âge de toutes les personnes print("\nDataFrame après augmentation de l'âge:\n", df) ``` * **Suppression de colonnes ou de lignes :** ```python # Supprimer une colonne (axis=1 pour les colonnes) df_no_bonus = df.drop('Bonus', axis=1) print("\nDataFrame sans la colonne 'Bonus':\n", df_no_bonus) # Supprimer des lignes (axis=0 pour les lignes) df_no_first_row = df.drop(0, axis=0) print("\nDataFrame sans la première ligne:\n", df_no_first_row) # Supprimer plusieurs lignes df_no_multiple_rows = df.drop([1, 3], axis=0) print("\nDataFrame sans les lignes 1 et 3:\n", df_no_multiple_rows) ``` > [!note] `inplace=True` > Par défaut, `drop()` retourne un nouveau DataFrame. Pour modifier le DataFrame original directement, utilisez `inplace=True`. > `df.drop('Bonus', axis=1, inplace=True)` ## Gestion des Valeurs Manquantes (NaN) Les valeurs manquantes (`NaN`) sont courantes dans les jeux de données réels et nécessitent une attention particulière. * **Identification des valeurs manquantes :** ```python print("\nValeurs manquantes dans df_from_list_dict:\n", df_from_list_dict.isnull()) print("\nNombre de valeurs manquantes par colonne:\n", df_from_list_dict.isnull().sum()) ``` * **Suppression des lignes/colonnes avec des valeurs manquantes :** ```python # Supprimer les lignes contenant au moins une valeur NaN df_dropped_rows = df_from_list_dict.dropna(axis=0) print("\nDataFrame sans lignes avec NaN:\n", df_dropped_rows) # Supprimer les colonnes contenant au moins une valeur NaN df_dropped_cols = df_from_list_dict.dropna(axis=1) print("\nDataFrame sans colonnes avec NaN:\n", df_dropped_cols) ``` * **Imputation des valeurs manquantes :** Remplacer les `NaN` par une valeur. ```python # Remplacer les NaN par 0 df_filled_zero = df_from_list_dict.fillna(0) print("\nDataFrame avec NaN remplacés par 0:\n", df_filled_zero) # Remplacer les NaN par la moyenne de la colonne 'Salaire' mean_salaire = df_from_list_dict['Salaire'].mean() df_filled_mean = df_from_list_dict['Salaire'].fillna(mean_salaire) print("\nColonne 'Salaire' avec NaN remplacés par la moyenne:\n", df_filled_mean) ``` > [!note] Préparation au futur > La gestion des valeurs manquantes est un sujet complexe et crucial en pré-traitement des données. Nous n'avons abordé ici que les méthodes les plus simples. Des techniques plus avancées (imputation par la médiane, mode, régression, etc.) seront vues dans le chapitre sur le pré-traitement. ## Application de Fonctions Vous pouvez appliquer des fonctions personnalisées ou des fonctions `NumPy` sur des `Series` ou `DataFrames`. * **`.apply()` :** Pour appliquer une fonction le long d'un axe d'un DataFrame (lignes ou colonnes) ou sur un Series. ```python # Appliquer une fonction lambda pour calculer l'ancienneté en décennies df['Anciennete_Decennies'] = df['Anciennete'].apply(lambda x: x / 10) print("\nDataFrame avec ancienneté en décennies:\n", df) # Appliquer une fonction sur chaque ligne pour créer un résumé def get_description(row): return f"{row['Nom']} a {row['Age']} ans et vit à {row['Ville']}." df['Description'] = df.apply(get_description, axis=1) # axis=1 pour appliquer sur les lignes print("\nDataFrame avec colonne 'Description':\n", df[['Nom', 'Age', 'Ville', 'Description']]) ``` * **`.map()` :** Pour substituer des valeurs dans un `Series` en utilisant un dictionnaire ou une fonction. ```python city_mapping = {'Paris': 'IDF', 'Lyon': 'Auvergne-Rhône-Alpes', 'Marseille': 'PACA', 'Nantes': 'Pays de la Loire', 'Bordeaux': 'Nouvelle-Aquitaine'} df['Region'] = df['Ville'].map(city_mapping) print("\nDataFrame avec colonne 'Region' (via map):\n", df[['Nom', 'Ville', 'Region']]) ``` * **`.applymap()` :** Pour appliquer une fonction élément par élément sur un `DataFrame` entier. (Moins courant, souvent remplacé par des opérations vectorisées ou `apply` sur des colonnes spécifiques). ## Tri des Données * **`sort_values()` :** Pour trier un DataFrame par les valeurs d'une ou plusieurs colonnes. ```python # Trier par 'Age' croissant df_sorted_age = df.sort_values(by='Age') print("\nDataFrame trié par Age (croissant):\n", df_sorted_age) # Trier par 'Ville' puis par 'Salaire' décroissant df_sorted_multi = df.sort_values(by=['Ville', 'Salaire'], ascending=[True, False]) print("\nDataFrame trié par Ville (croissant) et Salaire (décroissant):\n", df_sorted_multi) ``` * **`sort_index()` :** Pour trier un DataFrame par son index. ```python # Créons un DataFrame avec un index désordonné df_desordonne = df.loc[[2, 0, 3, 1]] print("\nDataFrame avec index désordonné:\n", df_desordonne) df_sorted_index = df_desordonne.sort_index() print("\nDataFrame trié par index:\n", df_sorted_index) ``` # Opérations de Groupement et d'Agrégation (`groupby`) L'opération `groupby` est l'une des fonctionnalités les plus puissantes de `pandas`. Elle permet de diviser les données en groupes basés sur une ou plusieurs clés, d'appliquer une fonction à chaque groupe, puis de combiner les résultats. C'est le paradigme "Split-Apply-Combine". > [!theorem] Le paradigme "Split-Apply-Combine" > 1. **Split (Division)** : Les données sont divisées en groupes basés sur une ou plusieurs clés. > 2. **Apply (Application)** : Une fonction est appliquée indépendamment à chaque groupe (agrégation, transformation, filtrage). > 3. **Combine (Combinaison)** : Les résultats de ces opérations sont combinés en une nouvelle structure de données `pandas`. ```python # Exemple de DataFrame pour groupby data_ventes = { 'Produit': ['A', 'B', 'A', 'C', 'B', 'A', 'C'], 'Region': ['Est', 'Ouest', 'Est', 'Nord', 'Ouest', 'Sud', 'Nord'], 'Ventes': [100, 150, 120, 80, 200, 90, 110], 'Quantite': [10, 15, 12, 8, 20, 9, 11] } df_ventes = pd.DataFrame(data_ventes) print("DataFrame des ventes:\n", df_ventes) ``` ## Utilisation de `.groupby()` ```python # Grouper par 'Produit' et calculer la somme des 'Ventes' ventes_par_produit = df_ventes.groupby('Produit')['Ventes'].sum() print("\nSomme des ventes par produit:\n", ventes_par_produit) ``` ``` Somme des ventes par produit: Produit A 310 B 350 C 190 Name: Ventes, dtype: int64 ``` ## Fonctions d'Agrégation Courantes Après `groupby`, vous pouvez appliquer diverses fonctions d'agrégation : * `.sum()` : Somme des valeurs * `.mean()` : Moyenne des valeurs * `.median()` : Médiane des valeurs * `.min()` / `.max()` : Minimum / Maximum des valeurs * `.count()` : Nombre de valeurs non nulles * `.std()` / `.var()` : Écart-type / Variance * `.size()` : Nombre total d'éléments (y compris NaN) ```python # Moyenne des ventes par région mean_ventes_region = df_ventes.groupby('Region')['Ventes'].mean() print("\nMoyenne des ventes par région:\n", mean_ventes_region) # Nombre de transactions par produit count_transactions_produit = df_ventes.groupby('Produit')['Quantite'].count() print("\nNombre de transactions par produit:\n", count_transactions_produit) ``` ## Agrégations Multiples Vous pouvez regrouper par plusieurs colonnes et appliquer plusieurs fonctions d'agrégation. ```python # Grouper par 'Produit' et 'Region', puis calculer la somme et la moyenne des ventes multi_agg = df_ventes.groupby(['Produit', 'Region'])['Ventes'].agg(['sum', 'mean']) print("\nSomme et moyenne des ventes par Produit et Région:\n", multi_agg) ``` ``` Somme et moyenne des ventes par Produit et Région: sum mean Produit Region A Est 220 110.0 Sud 90 90.0 B Ouest 350 175.0 C Nord 190 95.0 ``` Vous pouvez également spécifier différentes fonctions d'agrégation pour différentes colonnes : ```python custom_agg = df_ventes.groupby('Produit').agg( Total_Ventes=('Ventes', 'sum'), Moyenne_Quantite=('Quantite', 'mean'), Nombre_Transactions=('Ventes', 'count') ) print("\nAgrégation personnalisée par Produit:\n", custom_agg) ``` # Fusion et Concaténation de DataFrames Souvent, les données sont réparties sur plusieurs fichiers ou tables. `pandas` offre des fonctions pour combiner ces `DataFrames`. ## Concaténation (`pd.concat()`) La concaténation empile des `DataFrames` les uns après les autres, soit par les lignes (par défaut, `axis=0`), soit par les colonnes (`axis=1`). ```python df1 = pd.DataFrame({'A': ['A0', 'A1'], 'B': ['B0', 'B1']}, index=[0, 1]) df2 = pd.DataFrame({'A': ['A2', 'A3'], 'B': ['B2', 'B3']}, index=[2, 3]) df3 = pd.DataFrame({'C': ['C0', 'C1'], 'D': ['D0', 'D1']}, index=[0, 1]) print("df1:\n", df1) print("\ndf2:\n", df2) print("\ndf3:\n", df3) # Concaténation par lignes (par défaut) df_concat_rows = pd.concat([df1, df2]) print("\nConcaténation par lignes (df1, df2):\n", df_concat_rows) # Concaténation par colonnes df_concat_cols = pd.concat([df1, df3], axis=1) print("\nConcaténation par colonnes (df1, df3):\n", df_concat_cols) ``` ``` df1: A B 0 A0 B0 1 A1 B1 df2: A B 2 A2 B2 3 A3 B3 df3: C D 0 C0 D0 1 C1 D1 Concaténation par lignes (df1, df2): A B 0 A0 B0 1 A1 B1 2 A2 B2 3 A3 B3 Concaténation par colonnes (df1, df3): A B C D 0 A0 B0 C0 D0 1 A1 B1 C1 D1 ``` > [!note] Gestion des index et des colonnes > - Si les DataFrames concaténés par lignes ont des index en commun, vous pouvez utiliser `ignore_index=True` pour créer un nouvel index numérique. > - Si les DataFrames concaténés par lignes n'ont pas les mêmes colonnes, les colonnes manquantes seront remplies par `NaN`. > - Si les DataFrames concaténés par colonnes n'ont pas les mêmes index, les lignes manquantes seront remplies par `NaN`. ## Fusion (`pd.merge()`) La fusion est similaire aux jointures SQL. Elle combine des `DataFrames` basés sur une ou plusieurs clés communes. ```python df_clients = pd.DataFrame({ 'ClientID': [1, 2, 3, 4], 'NomClient': ['Alice', 'Bob', 'Charlie', 'David'] }) df_commandes = pd.DataFrame({ 'CommandeID': [101, 102, 103, 104, 105], 'ClientID': [2, 1, 3, 2, 5], # ClientID 5 n'existe pas dans df_clients 'Produit': ['Pomme', 'Poire', 'Banane', 'Orange', 'Kiwi'], 'Quantite': [2, 1, 3, 2, 1] }) print("df_clients:\n", df_clients) print("\ndf_commandes:\n", df_commandes) ``` ``` df_clients: ClientID NomClient 0 1 Alice 1 2 Bob 2 3 Charlie 3 4 David df_commandes: CommandeID ClientID Produit Quantite 0 101 2 Pomme 2 1 102 1 Poire 1 2 103 3 Banane 3 3 104 2 Orange 2 4 105 5 Kiwi 1 ``` * **Types de jointures (`how`) :** * `'inner'` (par défaut) : Ne conserve que les lignes où les clés sont présentes dans les deux DataFrames. * `'left'` : Conserve toutes les lignes du DataFrame de gauche, et ajoute les correspondances du DataFrame de droite (NaN si pas de correspondance). * `'right'` : Conserve toutes les lignes du DataFrame de droite, et ajoute les correspondances du DataFrame de gauche (NaN si pas de correspondance). * `'outer'` : Conserve toutes les lignes des deux DataFrames, remplissant les valeurs manquantes avec NaN. * **Clés de jointure (`on`, `left_on`, `right_on`) :** * `on='colonne'` : Si la clé de jointure a le même nom dans les deux DataFrames. * `left_on='col_gauche'`, `right_on='col_droite'` : Si les clés ont des noms différents. ``` # Inner join (par défaut) : Seuls les clients ayant des commandes et les commandes ayant des clients existants sont inclus merged_inner = pd.merge(df_clients, df_commandes, on='ClientID', how='inner') print("\nInner Join (clients et commandes correspondantes):\n", merged_inner) # Left join : Tous les clients sont conservés, même s'ils n'ont pas de commandes merged_left = pd.merge(df_clients, df_commandes, on='ClientID', how='left') print("\nLeft Join (tous les clients, avec leurs commandes):\n", merged_left) # Right join : Toutes les commandes sont conservées, même si le ClientID n'existe pas dans df_clients merged_right = pd.merge(df_clients, df_commandes, on='ClientID', how='right') print("\nRight Join (toutes les commandes, avec leurs clients):\n", merged_right) # Outer join : Toutes les lignes des deux DataFrames sont incluses merged_outer = pd.merge(df_clients, df_commandes, on='ClientID', how='outer') print("\nOuter Join (tous les clients et toutes les commandes):\n", merged_outer) ``` # Lecture et Écriture de Données `pandas` facilite l'importation et l'exportation de données depuis et vers divers formats de fichiers. ## Fichiers CSV Les fichiers CSV (Comma Separated Values) sont l'un des formats les plus courants. * **Lecture : `pd.read_csv()`** ```python # Supposons que vous ayez un fichier 'data.csv' dans le même répertoire # data.csv: # Nom,Age,Ville,Salaire # Alice,25,Paris,50000 # Bob,30,Lyon,60000 # Pour l'exemple, nous allons créer un fichier CSV temporaire df.to_csv('data_temp.csv', index=False) # index=False pour ne pas écrire l'index du DataFrame df_from_csv = pd.read_csv('data_temp.csv') print("\nDataFrame lu depuis 'data_temp.csv':\n", df_from_csv.head()) ``` * **Écriture : `.to_csv()`** ```python # Écrire le DataFrame dans un fichier CSV df_ventes.to_csv('ventes_agg.csv', index=False) # index=False pour ne pas inclure l'index du DataFrame print("\nDataFrame 'df_ventes' écrit dans 'ventes_agg.csv'.") ``` ## Fichiers Excel * **Lecture : `pd.read_excel()`** ```python # Pour l'exemple, nous allons créer un fichier Excel temporaire df.to_excel('data_temp.xlsx', index=False, sheet_name='Personnes') df_from_excel = pd.read_excel('data_temp.xlsx', sheet_name='Personnes') print("\nDataFrame lu depuis 'data_temp.xlsx':\n", df_from_excel.head()) ``` * **Écriture : `.to_excel()`** ```python df_clients.to_excel('clients.xlsx', index=False) print("\nDataFrame 'df_clients' écrit dans 'clients.xlsx'.") ``` > [!tip] Autres formats > `pandas` supporte de nombreux autres formats, notamment : > - JSON : `pd.read_json()`, `.to_json()` > - SQL : `pd.read_sql()`, `.to_sql()` (nécessite des moteurs de base de données) > - Parquet, HDF5, Feather, Pickle, etc. > Ces fonctions sont très utiles pour interagir avec différentes sources de données. # ➡️ C'est la fin ! - Cours précèdent: [[Cours 1 - Python et Data]] - Prochain cours: [[Exercices - Python et Data]] - Page d'accueil de la compétence: [[Python et Data]] # 🗓️ Historique - Dernière MAJ: `13-Octobre-2025` - Rédigé par: [[Hamilton DE ARAUJO]]