EDA is the disciplined version of "poking at the data": shape and types first, then missingness, then each variable's distribution, then relationships between variables. The goal is not pretty plots — it is a list of surprises and decisions: columns to fix, outliers to investigate, transformations to apply, hypotheses worth testing.
| Step | pandas | You're looking for |
|---|---|---|
| Shape & types | df.shape, df.info() | Row count, wrong dtypes, IDs read as numbers |
| Missingness | df.isna().mean() | Columns to drop, impute, or flag — see missing data |
| Numeric profile | df.describe() | Impossible values, skew (mean ≠ median), scale |
| Categorical profile | value_counts() | Cardinality, typos ("NY" vs "N.Y."), rare levels |
| Distributions | df.hist(), boxplot | Shape, outliers — see distribution shape |
| Relationships | df.corr(), scatter_matrix | Redundancy, leakage candidates, target signal |
pip install pandas seaborn matplotlib
# ────────────────────────────────────────
import seaborn as sns
import matplotlib.pyplot as plt
df = sns.load_dataset('titanic')
print(df.shape)
print(df.describe(include='all').T)
print(df.isna().mean().sort_values(ascending=False).head())
df.hist(bins=40, figsize=(12, 6))
sns.heatmap(df.isna(), cbar=False) # missingness at a glance
plt.show()
Five rows that look harmless. Ages stored as text become numbers only after conversion, with 1 entry (“n/a”) turning into a missing value. Incomes use −999 for “unknown”: the naive mean is 31,800, the mean without the placeholders 53,667. The first hour of EDA is mostly about finding what the numbers really are.
- Summary statistics hide placeholders, duplicated rows and unit changes; look at value counts and the extremes.
- Every chart you look at during EDA is a test; the patterns you find need confirming on fresh data.
- Cleaning rules decided after seeing results can quietly favour the answer you wanted.
df = pd.DataFrame({'age': ['34', '51', '29', 'n/a', '45'], # numbers stored as text
'income': [52_000, -999, 61_000, 48_000, -999]}) # -999 means "unknown"
age = pd.to_numeric(df.age, errors='coerce')
naive_mean = df.income.mean()
clean_mean = df.income.replace(-999, np.nan).mean()Assumes import numpy as np and import pandas as pd (and SciPy where imported). Each snippet simulates or makes up its own data, as the comments say.
The same idea with a library
# Python — the 10-line EDA opener import pandas as pd df = pd.read_csv('data.csv') print(df.shape) print(df.info()) # dtypes + non-null counts print(df.describe(include='all').T) # numeric + categorical profile print(df.isna().mean().sort_values(ascending=False).head(10)) for col in df.select_dtypes('object'): print(col, df[col].nunique(), df[col].value_counts().head(3).to_dict()) df.hist(bins=40, figsize=(12, 8)) # every numeric at once
- Exploratory Data Analysis, J. W. Tukey, Addison-Wesley, 1977
- K. W. Broman & K. H. Woo, “Data Organization in Spreadsheets”, The American Statistician 72(1), 2018
- H. Wickham, “Tidy Data”, Journal of Statistical Software 59(10), 2014
Last reviewed October 2026 · How this site checks its content · Report a mistake
- Technical IndicatorsDonchian ChannelsThe highest high and lowest low of the last N bars; leaving that range is the signal
- Technical IndicatorsSimple Moving AverageAveraging cancels noise: the longer the window, the calmer the line and the later it reacts
- Technical IndicatorsStandard DeviationHow widely values spread around their mean, the single number most of risk measurement is built on
- Technical IndicatorsStochastic OscillatorWhere the close sits inside its recent range: near the top in strength, near the bottom in weakness
- Technical IndicatorsWilliams %RThe stochastic seen from the top: how far the close sits below the recent high