Skip to content

Tableau croisé dynamique Pandas : résumer et restructurer les données comme dans Excel (Guide)

Updated on

Tous les analystes ayant travaillé avec Excel connaissent les tableaux croisés dynamiques. Vous faites glisser un champ dans la zone des lignes, un autre dans la zone des colonnes, choisissez une fonction de synthèse, et une feuille dense de transactions brutes se transforme en un résumé clair montrant des totaux par catégorie, des moyennes par région ou des décomptes par mois. Maintenant, vous devez faire la même chose en Python, mais écrire des appels groupby imbriqués avec unstack paraît maladroit et difficile à lire.

La vraie frustration arrive quand vos données ont plusieurs niveaux de regroupement, que vous avez besoin de sous-totaux, ou que vous voulez appliquer plusieurs fonctions d’agrégation à différentes colonnes en même temps. Enchaîner groupby, agg et unstack produit un code fragile qui casse dès que la forme des données change.

La fonction pandas pivot_table apporte toute la puissance des tableaux croisés dynamiques façon Excel dans Python, avec une API propre et déclarative. Un seul appel de fonction gère le regroupement, l’agrégation, l’indexation multi-niveaux, les sous-totaux et le traitement des valeurs manquantes. Ce guide couvre chaque paramètre, parcourt des exemples pratiques et compare pivot_table avec groupby, pivot et crosstab pour que vous sachiez exactement quel outil utiliser.

📚

Ce que fait pd.pivot_table()

pd.pivot_table() crée un tableau de synthèse de type tableur à partir d’un DataFrame. Vous indiquez quelles colonnes utiliser comme étiquettes de lignes (index), lesquelles utiliser comme en-têtes de colonnes, quelles valeurs agréger et quelle fonction d’agrégation appliquer. Le résultat est un nouveau DataFrame où chaque cellule contient une statistique de synthèse.

Voici un simple avant/après :

Données brutes :

regionproductrevenue
NorthWidget1200
NorthGadget800
SouthWidget1500
SouthGadget950
NorthWidget1400
SouthWidget1600

Tableau croisé (somme du revenue par région et produit) :

regionGadgetWidget
North8002600
South9503100

Chaque cellule contient la somme du revenue pour la combinaison région-produit correspondante.

Syntaxe et paramètres de pd.pivot_table()

pd.pivot_table(data, values=None, index=None, columns=None, aggfunc='mean',
               fill_value=None, margins=False, dropna=True, margins_name='All',
               observed=False, sort=True)

Référence des paramètres

ParamètreDescriptionPar défaut
dataLe DataFrame à synthétiserObligatoire
valuesColonne(s) à agrégerNone (toutes les colonnes numériques)
indexColonne(s) à utiliser comme étiquettes de lignesNone
columnsColonne(s) à utiliser comme en-têtes de colonnesNone
aggfuncFonction(s) d’agrégation : 'mean', 'sum', 'count', 'min', 'max', ou une fonction/un dict/une liste'mean'
fill_valueValeur pour remplacer les NaN dans le résultatNone
marginsAjouter les totaux lignes/colonnes (sous-totaux)False
margins_nameNom de la ligne/colonne de totaux'All'
dropnaExclure les colonnes dont toutes les entrées sont NaNTrue
observedN’afficher que les catégories observées pour les colonnes catégoriellesFalse
sortTrier le résultatTrue

Données d’exemple pour tous les exemples

Chaque exemple ci-dessous utilise ce dataset de ventes :

import pandas as pd
import numpy as np
 
sales = pd.DataFrame({
    'date': pd.to_datetime(['2025-01-15', '2025-01-20', '2025-02-10', '2025-02-18',
                            '2025-01-12', '2025-02-22', '2025-01-25', '2025-02-14',
                            '2025-01-30', '2025-02-05', '2025-01-18', '2025-02-28']),
    'region': ['North', 'North', 'North', 'North', 'South', 'South',
               'South', 'South', 'East', 'East', 'East', 'East'],
    'product': ['Widget', 'Gadget', 'Widget', 'Gadget',
                'Widget', 'Gadget', 'Widget', 'Gadget',
                'Widget', 'Gadget', 'Widget', 'Gadget'],
    'salesperson': ['Alice', 'Alice', 'Bob', 'Bob',
                    'Charlie', 'Charlie', 'Diana', 'Diana',
                    'Eve', 'Eve', 'Frank', 'Frank'],
    'revenue': [1200, 800, 1400, 850, 1500, 950, 1100, 780, 1300, 900, 1250, 870],
    'units': [10, 8, 12, 9, 15, 10, 11, 8, 13, 9, 12, 9]
})
 
print(sales)

Tableau croisé de base : somme du revenue par région

table = pd.pivot_table(sales, values='revenue', index='region', aggfunc='sum')
print(table)

Sortie :

        revenue
region
East       4320
North      4250
South      4330

C’est équivalent à sales.groupby('region')['revenue'].sum(), mais renvoie un DataFrame au lieu d’une Series.

Ajouter des en-têtes de colonnes

Ajoutez le paramètre columns pour ventiler le résumé par une seconde variable :

table = pd.pivot_table(sales, values='revenue', index='region',
                       columns='product', aggfunc='sum')
print(table)

Sortie :

product  Gadget  Widget
region
East       1770    2550
North      1650    2600
South      1730    2600

Désormais, chaque cellule montre le revenue total pour une paire région-produit spécifique.

Fonctions d’agrégation multiples

Passez une liste de fonctions à aggfunc pour calculer plusieurs statistiques à la fois :

table = pd.pivot_table(sales, values='revenue', index='region',
                       columns='product', aggfunc=['sum', 'mean', 'count'])
print(table)

Sortie :

          sum          mean                count
product Gadget Widget Gadget  Widget      Gadget Widget
region
East      1770   2550  885.0  1275.0           2      2
North     1650   2600  825.0  1300.0           2      2
South     1730   2600  865.0  1300.0           2      2

Le résultat a un MultiIndex sur les colonnes, avec la fonction d’agrégation au premier niveau et le produit au second niveau.

Fonctions d’agrégation différentes par colonne

Utilisez un dictionnaire pour aggfunc afin d’appliquer différentes fonctions à différentes colonnes de valeurs :

table = pd.pivot_table(sales, values=['revenue', 'units'], index='region',
                       columns='product',
                       aggfunc={'revenue': 'sum', 'units': 'mean'})
print(table)

Sortie :

       revenue         units
product Gadget Widget Gadget Widget
region
East      1770   2550    9.0   12.5
North     1650   2600    8.5   11.0
South     1730   2600    9.0   13.0

Le revenue est additionné tandis que les units sont moyennées — exactement ce que vous feriez dans un tableau croisé dynamique Excel avec des fonctions de synthèse différentes selon le champ.

Ajouter des sous-totaux avec margins

Le paramètre margins ajoute une ligne et une colonne montrant les totaux généraux :

table = pd.pivot_table(sales, values='revenue', index='region',
                       columns='product', aggfunc='sum', margins=True)
print(table)

Sortie :

product  Gadget  Widget    All
region
East       1770    2550   4320
North      1650    2600   4250
South      1730    2600   4330
All        5150    7750  12900

La ligne All montre le revenue total par produit. La colonne All montre le revenue total par région. La cellule en bas à droite est le total général.

Vous pouvez personnaliser l’étiquette avec margins_name :

table = pd.pivot_table(sales, values='revenue', index='region',
                       columns='product', aggfunc='sum',
                       margins=True, margins_name='Total')
print(table)

Gérer les valeurs manquantes avec fill_value

Quand certaines combinaisons n’existent pas dans vos données, le tableau croisé contient des NaN. Utilisez fill_value pour les remplacer :

# Remove one row to create a missing combination
sales_missing = sales.drop(index=0)
 
table = pd.pivot_table(sales_missing, values='revenue', index='region',
                       columns='product', aggfunc='sum', fill_value=0)
print(table)

Cela remplace les NaN par 0, ce qui est plus propre à l’affichage et évite des problèmes dans les calculs en aval.

Index multi-niveaux (regroupement de lignes)

Passez une liste de colonnes à index pour créer des étiquettes de lignes hiérarchiques :

table = pd.pivot_table(sales, values='revenue',
                       index=['region', 'salesperson'],
                       columns='product', aggfunc='sum')
print(table)

Sortie :

product                Gadget  Widget
region salesperson
East   Eve              900.0  1300.0
       Frank            870.0  1250.0
North  Alice            800.0  1200.0
       Bob              850.0  1400.0
South  Charlie          950.0  1500.0
       Diana            780.0  1100.0

Chaque salesperson est imbriqué sous sa région, donnant une vue “drill-down” des données.

Colonnes multi-niveaux

De la même manière, passez une liste à columns pour obtenir des en-têtes de colonnes hiérarchiques :

sales['month'] = sales['date'].dt.month_name()
 
table = pd.pivot_table(sales, values='revenue', index='region',
                       columns=['product', 'month'], aggfunc='sum', fill_value=0)
print(table)

Cela crée un en-tête de colonnes à deux niveaux, avec le produit au premier niveau et le mois au second.

Utiliser des fonctions d’agrégation personnalisées

Vous pouvez passer n’importe quel callable à aggfunc, y compris des fonctions lambda et des fonctions NumPy :

# Range (max - min) of revenue by region
table = pd.pivot_table(sales, values='revenue', index='region',
                       columns='product', aggfunc=lambda x: x.max() - x.min())
print(table)

Sortie :

product  Gadget  Widget
region
East         30      50
North        50     200
South       170     400

D’autres agrégations personnalisées utiles :

# Coefficient of variation
table = pd.pivot_table(sales, values='revenue', index='region',
                       aggfunc=lambda x: x.std() / x.mean() * 100)

Exemple concret : analyse des notes d’étudiants

students = pd.DataFrame({
    'student': ['Alice', 'Alice', 'Alice', 'Bob', 'Bob', 'Bob',
                'Charlie', 'Charlie', 'Charlie', 'Diana', 'Diana', 'Diana'],
    'subject': ['Math', 'Science', 'English'] * 4,
    'semester': ['Fall', 'Fall', 'Fall', 'Fall', 'Fall', 'Fall',
                 'Spring', 'Spring', 'Spring', 'Spring', 'Spring', 'Spring'],
    'score': [92, 88, 95, 78, 85, 72, 90, 93, 88, 85, 79, 91]
})
 
# Average score by subject and semester
table = pd.pivot_table(students, values='score', index='subject',
                       columns='semester', aggfunc='mean', margins=True)
print(table.round(1))

Sortie :

semester   Fall  Spring   All
subject
English    83.5    89.5  86.5
Math       85.0    87.5  86.2
Science    86.5    86.0  86.2
All        85.0    87.7  86.3

On voit immédiatement que les moyennes du semestre Spring sont légèrement plus élevées au global, et que English montre la plus forte progression.

Exemple concret : rapport mensuel des ventes

# Create a monthly sales summary
sales['month'] = sales['date'].dt.strftime('%Y-%m')
 
report = pd.pivot_table(sales, values=['revenue', 'units'],
                        index='region', columns='month',
                        aggfunc={'revenue': 'sum', 'units': 'sum'},
                        margins=True, fill_value=0)
print(report)

Cela produit le même type de rapport de synthèse mensuel que vous créeriez dans Excel, avec des totaux pour chaque région et chaque mois.

pivot_table vs pivot vs groupby vs crosstab

Ces quatre fonctions se recoupent partiellement, mais chacune a un cas d’usage distinct :

Fonctionnalitépivot_table()pivot()groupby()crosstab()
AgrégationOui (toute fonction)Non (erreur en cas de doublons)Oui (toute fonction)Oui (limité)
Gère les doublonsOui, via aggfuncNonOuiOui
Sous-totaux (margins)OuiNonNon (manuel)Oui
Remplir les valeurs manquantesOui (fill_value)NonNonOui (fill_value)
EntréeDataFrameDataFrameDataFrameSeries/tableaux
SortieDataFrameDataFrameDataFrame/SeriesDataFrame
Index multi-niveauxOuiOuiOuiOui
Idéal pourRésumer des données avec agrégationRestructurer des données clé-valeur à clés uniquesAnalyse groupée flexibleTables de fréquence
Fonction par défautmeanN/AN/Acount

Quand utiliser chacune

Utilisez pivot_table() quand vous avez besoin de résumés façon Excel avec agrégation, sous-totaux ou plusieurs fonctions d’agrégation. C’est l’option la plus puissante et la plus lisible pour créer des tables de synthèse.

Utilisez pivot() lorsque vos données ont des combinaisons uniques de valeurs d’index et de colonnes, et que vous souhaitez simplement les restructurer sans agrégation. C’est plus rapide que pivot_table() car cela saute l’étape d’agrégation.

Utilisez groupby() lorsque vous avez besoin de calculs par groupes mais sans résultat au format “wide”. groupby renvoie par défaut une sortie au format “long”. Vous pouvez obtenir un résultat similaire à pivot_table via groupby().unstack(), mais pivot_table est plus lisible.

Utilisez crosstab() lorsque vous calculez des tables de fréquence ou des tableaux croisés de variables catégorielles. crosstab() accepte directement des Series ou des tableaux (pas uniquement des DataFrames) et, par défaut, compte les occurrences.

Exemple d’équivalence

Ces trois lignes produisent le même résultat :

# pivot_table approach
result1 = pd.pivot_table(sales, values='revenue', index='region',
                         columns='product', aggfunc='sum')
 
# groupby + unstack approach
result2 = sales.groupby(['region', 'product'])['revenue'].sum().unstack()
 
# Both produce the same table
print(result1.equals(result2))  # True

La version pivot_table est plus lisible, surtout lorsque vous ajoutez des totaux, des valeurs de remplissage, ou plusieurs fonctions d’agrégation.

Aplatir des colonnes MultiIndex

Après avoir créé un tableau croisé avec plusieurs fonctions d’agrégation, vous obtenez souvent des colonnes MultiIndex difficiles à manipuler :

table = pd.pivot_table(sales, values='revenue', index='region',
                       columns='product', aggfunc=['sum', 'mean'])
 
# Flatten the column MultiIndex
table.columns = ['_'.join(col).strip() for col in table.columns.values]
print(table)

Sortie :

        sum_Gadget  sum_Widget  mean_Gadget  mean_Widget
region
East          1770        2550        885.0       1275.0
North         1650        2600        825.0       1300.0
South         1730        2600        865.0       1300.0

Désormais, les colonnes sont des chaînes “plates”, plus simples à référencer.

Trier et filtrer des tableaux croisés

Les tableaux croisés sont des DataFrames ordinaires : vous pouvez donc les trier et les filtrer :

table = pd.pivot_table(sales, values='revenue', index='region',
                       columns='product', aggfunc='sum', margins=True)
 
# Sort by total revenue (All column), descending
sorted_table = table.sort_values('All', ascending=False)
print(sorted_table)
 
# Filter to show only regions with Widget revenue > 2500
filtered = table[table['Widget'] > 2500]
print(filtered)

Exporter des tableaux croisés

Enregistrez votre tableau croisé vers Excel (là où les parties prenantes l’attendent) ou vers CSV :

table = pd.pivot_table(sales, values='revenue', index='region',
                       columns='product', aggfunc='sum', margins=True)
 
# Export to Excel
table.to_excel('sales_pivot.xlsx', sheet_name='Revenue Summary')
 
# Export to CSV
table.to_csv('sales_pivot.csv')

Conseils de performance

pivot_table() appelle groupby en interne, donc les performances sont similaires. Pour de gros datasets :

Taille du datasetTemps attendu
100K lignes, 2 colonnes de group~5 ms
1M lignes, 2 colonnes de group~50 ms
10M lignes, 3 colonnes de group~500 ms

Stratégies d’optimisation :

  1. Réduire les données avant de pivoter — filtrez les lignes et ne gardez que les colonnes nécessaires avant d’appeler pivot_table().
  2. Utiliser des dtypes category — convertissez les colonnes de chaînes en dtype category pour accélérer le regroupement.
  3. Éviter les aggfuncs en lambda — les noms intégrés ('sum', 'mean') utilisent du code C optimisé. Les fonctions lambda retombent sur des boucles Python plus lentes.
# Faster: use categorical dtypes
sales['region'] = sales['region'].astype('category')
sales['product'] = sales['product'].astype('category')
 
# Faster: use string name instead of lambda
table = pd.pivot_table(sales, values='revenue', index='region',
                       columns='product', aggfunc='sum')  # 'sum' is optimized

Visualiser des tableaux croisés avec PyGWalker

Même si pd.pivot_table() est excellent pour les synthèses numériques, il arrive que vous deviez visualiser les tendances de manière interactive. PyGWalker (opens in a new tab) est une bibliothèque Python open-source qui vous permet de créer des tableaux croisés, des bar charts, des heatmaps, et plus encore via une interface visuelle de type drag-and-drop — aucun code requis après la configuration initiale.

import pandas as pd
import pygwalker as pyg
 
sales = pd.DataFrame({
    'region': ['North', 'North', 'South', 'South', 'East', 'East'] * 2,
    'product': ['Widget', 'Gadget'] * 6,
    'revenue': [1200, 800, 1500, 950, 1300, 900, 1400, 850, 1100, 780, 1250, 870],
    'units': [10, 8, 15, 10, 13, 9, 12, 9, 11, 8, 12, 9]
})
 
# Launch interactive pivot table and visualization
walker = pyg.walk(sales)

PyGWalker vous offre une interface à la Tableau où vous pouvez faire glisser region vers les lignes, product vers les colonnes et revenue vers les valeurs pour créer un tableau croisé visuellement. Vous pouvez passer instantanément de la vue tableau à la vue graphique, tester différentes fonctions d’agrégation et exporter le résultat — le tout sans écrire de code supplémentaire.

Essayez PyGWalker dans Google Colab (opens in a new tab), Kaggle (opens in a new tab), ou installez avec pip install pygwalker.

FAQ

Quelle est la différence entre pivot et pivot_table dans pandas ?

pivot() restructure les données sans agrégation — il exige des combinaisons uniques de valeurs d’index et de colonnes et lève une erreur si des doublons existent. pivot_table() gère les doublons en les agrégeant via une fonction comme sum ou mean. Utilisez pivot() pour un simple reshape et pivot_table() lorsque vous avez besoin d’agrégation ou de sous-totaux.

Comment ajouter des totaux (margins) à un pivot table pandas ?

Définissez margins=True dans l’appel à pivot_table() : pd.pivot_table(df, values='revenue', index='region', columns='product', aggfunc='sum', margins=True). Cela ajoute une ligne et une colonne All avec des sous-totaux. Personnalisez le libellé avec margins_name='Total'.

Puis-je utiliser plusieurs fonctions d’agrégation dans un pivot table ?

Oui. Passez une liste à aggfunc : aggfunc=['sum', 'mean', 'count']. Cela crée un en-tête de colonnes MultiIndex avec un niveau pour la fonction et un niveau pour les colonnes de valeurs. Vous pouvez aussi passer un dictionnaire pour appliquer des fonctions différentes selon la colonne : aggfunc={'revenue': 'sum', 'units': 'mean'}.

En quoi pivot_table est-il différent de groupby dans pandas ?

Les deux agrègent des données, mais pivot_table() produit un résultat au format “wide” (avec des en-têtes de colonnes issus d’une des variables de regroupement), tandis que groupby() produit par défaut un résultat au format “long”. pivot_table() prend aussi en charge nativement les totals (margins) et le remplissage de valeurs. En interne, pivot_table() utilise groupby().

Comment gérer les valeurs NaN dans un pivot table ?

Utilisez le paramètre fill_value pour remplacer les NaN par une valeur spécifique : pd.pivot_table(df, ..., fill_value=0). Le paramètre dropna=True (par défaut) exclut les colonnes dont toutes les entrées sont NaN.

Puis-je créer un pivot table avec des pourcentages ?

Oui. Créez d’abord le pivot table avec des comptes ou des sommes, puis divisez par le total. Vous pouvez aussi utiliser normalize dans pd.crosstab() pour des tableaux croisés en pourcentage. Pour les pivot tables, calculez manuellement les pourcentages : table = table.div(table.sum(axis=1), axis=0) * 100.

Conclusion

La fonction pivot_table() de pandas est l’outil le plus polyvalent pour créer des tables de synthèse en Python. Voici les points clés à retenir :

  • Utilisez values, index et columns pour définir quoi résumer, comment regrouper les lignes et comment regrouper les colonnes.
  • Utilisez aggfunc pour spécifier la fonction d’agrégation. Passez une liste pour plusieurs fonctions, ou un dictionnaire pour des fonctions par colonne.
  • Utilisez margins=True pour ajouter des sous-totaux — l’équivalent de “Grand Total” dans Excel.
  • Utilisez fill_value pour remplacer les combinaisons manquantes par une valeur par défaut (souvent 0).
  • Préférez pivot_table() à groupby().unstack() pour la lisibilité, surtout quand vous avez besoin de totals ou de valeurs de remplissage.
  • Préférez pivot() à pivot_table() lorsque vos données ont des combinaisons de clés uniques et que vous n’avez pas besoin d’agrégation.
  • Préférez crosstab() pour de simples tables de fréquence sur des variables catégorielles.

Pour explorer vos tableaux croisés de façon interactive, PyGWalker (opens in a new tab) fournit une interface visuelle drag-and-drop qui reproduit l’expérience des tableaux croisés dynamiques Excel dans Jupyter Notebook.

📚