Exploratory data analysis (EDA) means understanding a dataset before modeling it or drawing conclusions from it: its structure, quality, distributions and relationships. This is the step where you discover the pitfalls… and the first leads.
Start with the questions and the grain
Before coding, two questions:
- What do we want to know? “Which regions and categories drive revenue? Is there seasonality?”
- What does a row represent? An order, an order line, a customer, a day? This grain determines every calculation: summing order line amounts does not mean the same thing as counting orders.
The basic review
import pandas as pd
ventes = pd.DataFrame({
"commande_id": [101, 102, 103, 104],
"date": ["2026-01-03", "2026-01-04", "2026-01-04", "2026-01-10"],
"region": ["Nord", "Sud", "Nord", "Est"],
"quantite": [2, 1, 3, 1],
"montant": ["39,90", "12,50", "59,85", "24,00"], # decimal comma: read as text
})
print(ventes.shape) # (4, 5): 4 rows, 5 columns
print(ventes.head()) # preview of the first rows
print(ventes.dtypes) # type of each column
ventes.info() # types, non-null values, memory
print(ventes.describe()) # statistics of the numeric columns
print(ventes.nunique()) # number of distinct values per column
Reading the results carefully:
montantis of type str: the decimal comma prevented it from being read as a number. Since pandas 3, text has thestrtype by default (previouslyobject).dateis also text: you cannot sort or group by month correctly.commande_idis numeric, but it is an identifier: its mean indescribe()makes no sense. Checking thatnunique()== number of rows confirms that it does identify each row.
Fixing the types
ventes["montant"] = pd.to_numeric(ventes["montant"].str.replace(",", ".", regex=False))
ventes["date"] = pd.to_datetime(ventes["date"], errors="coerce")
print(ventes.dtypes) # montant float64, date datetime64
errors="coerce" turns values that cannot be converted into missing values (NaT for dates) instead of raising an error. Always check how many values were lost:
print(ventes["date"].isna().sum())
Selecting by type
numeriques = ventes.select_dtypes(include="number").columns.tolist()
autres = ventes.select_dtypes(exclude="number").columns.tolist()
print(ventes.describe(include="all")) # summary of all columns
Keeping track of your observations
Keep a running list: “montant imported as text”, “only 3 regions”, “period from January to March”. This log will become the basis of your final summary.
Key takeaways
- Business questions and grain first.
- shape, dtypes, info, describe, nunique: the starting profile.
- Fix the types, then check the values lost in conversion.
Exercises
Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.
Profile function
Write
profil(df)that returns a dictionary with the keysn_lignes,n_colonnes,numeriques(list of numeric columns) andcle_unique(True if thecommande_idcolumn has as many distinct values as there are rows).import pandas as pd ventes = pd.DataFrame({ 'commande_id': [101, 102, 103, 104, 105, 106, 107, 108], 'date': ['2026-01-03', '2026-01-04', '2026-01-04', '2026-01-10', '2026-02-02', '2026-02-03', '2026-02-15', '2026-03-01'], 'region': ['Nord', 'Sud', 'Nord', 'Est', 'Sud', 'Nord', 'Est', 'Sud'], 'categorie': ['jeux', 'livres', 'jeux', 'deco', 'livres', 'deco', 'jeux', 'livres'], 'quantite': [2, 1, 3, 1, 2, 4, 1, 2], 'montant': ['39,90', '12,50', '59,85', '24,00', '25,00', '96,00', '19,95', '25,00'], }) def profil(df): passFix the types
Convert
montantto a number (replace the comma with a period) anddateto datetime witherrors='coerce'. Then store innb_dates_invalidesthe number of dates that were not converted and inca_totalthe sum of the amounts.import pandas as pd ventes = pd.DataFrame({ 'date': ['2026-01-03', '2026-01-04', '2026-02-30', '2026-03-01'], 'montant': ['39,90', '12,50', '59,85', '24,00'], }) # conversions puis nb_dates_invalides et ca_totalThe grain of the data
A file contains one row per ordered product: order 12 appears 3 times (3 products). To count the number of orders, what should you use?
- len(df)
- df['commande_id'].nunique()
- df['commande_id'].sum()
- df.shape[1]
End-of-lesson quiz
Since pandas 3, what is the default type of a text column?
- object
- str
- category
What does pd.to_datetime(s, errors=‘coerce’) do with an invalid date?
- It raises an error
- It replaces it with NaT
- It removes it from the series
By default, describe() summarizes:
- All columns
- The numeric columns
- The text columns
The rest of the course
- Missing values and duplicates18 min
- Univariate analysis: distributions and categories18 min
- Bivariate analysis: crossing variables20 min
- Anomalies, change over time and the EDA summary20 min