Data visualisation is the bridge between analysis and insight. A perfect model with poor visualisation fails to communicate its value; a flawed analysis made compelling with excellent visuals can mislead stakeholders. The best data scientists combine technical chart-building proficiency with design principles and storytelling ability — they know not just how to plot data but how to make the audience understand and act on it. This guide covers the full data visualisation stack used in production data science: Matplotlib fundamentals, Seaborn statistical graphics, Plotly interactive charts, and the principles that make visualisations effective.
Visualisation is central to exploratory data analysis (covered in our Feature Engineering guide), communicating model results (see Model Evaluation and Hyperparameter Tuning), and presenting findings in data science case study interviews. For building interactive dashboards from ML models, see our Streamlit ML App Deployment guide. The visualisation of time series data specifically is covered in our Time Series Forecasting Interview Q&A.
Choosing the Right Chart — The Most Important Visualisation Decision
The most common visualisation mistake is choosing a chart based on aesthetics rather than the data relationship being communicated. Every chart type answers a specific analytical question. Before writing a single line of plotting code, ask: what relationship am I trying to show?
| Relationship to Show | Best Chart | Avoid |
|---|---|---|
| Distribution of one variable | Histogram, KDE, violin plot, box plot | Bar chart (hides shape) |
| Comparison across categories | Bar chart (sorted), dot plot | Pie chart (>5 slices), 3D bar |
| Trend over time | Line chart | Bar chart (obscures trend), scatter |
| Correlation between two numeric vars | Scatter plot, hexbin (large n) | Line chart (implies order) |
| Composition (part of whole) | Stacked bar, treemap, waffle chart | Pie chart (poor for comparison), donut |
| Correlation matrix / many pairs | Heatmap, pair plot | Too many scatter plots |
| Geographic data | Choropleth map, bubble map | Tables (hides spatial patterns) |
| Ranking | Horizontal sorted bar, lollipop chart | Vertical bar (hard to read labels) |
| Uncertainty / confidence intervals | Error bars, ribbon/band on line chart | Single line (implies precision) |
Why pie charts are almost always wrong: Humans compare lengths far more accurately than angles or areas. A bar chart representing the same data as a pie chart almost always communicates more clearly. Pie charts are acceptable only when: there are 2-3 categories, the proportions are dramatically different (95%/5%), and the “part of whole” concept is the primary message. For any other situation, use a bar chart.
Matplotlib — The Foundation Layer
Matplotlib is the foundational plotting library that Seaborn, pandas .plot(), and many other libraries are built on. Understanding its object model — Figure (the canvas), Axes (individual plot areas), and Artist (everything drawn on a figure) — is essential for customisation and for fixing the inevitable layout issues.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
# --- Object-oriented API (preferred over pyplot state machine) ---
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
fig.suptitle('Sales Dashboard Q3 2026', fontsize=14, fontweight='bold')
# Revenue trend
ax = axes[0, 0]
dates = pd.date_range('2026-07-01', periods=90, freq='D')
revenue = np.cumsum(np.random.randn(90)) + 100
ax.plot(dates, revenue, color='#2563EB', linewidth=2)
ax.fill_between(dates, revenue, alpha=0.1, color='#2563EB')
ax.set_title('Daily Revenue', fontweight='bold')
ax.set_xlabel('Date'); ax.set_ylabel('Revenue (INR Lakhs)')
ax.xaxis.set_major_formatter(plt.matplotlib.dates.DateFormatter('%b %d'))
ax.tick_params(axis='x', rotation=45)
# Category comparison (sorted horizontal bar)
ax = axes[0, 1]
categories = ['Electronics', 'Clothing', 'Home', 'Books', 'Sports']
values = [450, 320, 280, 190, 150]
colors = ['#2563EB' if v == max(values) else '#93C5FD' for v in values]
ax.barh(categories, values, color=colors)
ax.set_title('Revenue by Category', fontweight='bold')
for i, v in enumerate(values):
ax.text(v + 5, i, f'{v:,}', va='center', fontsize=9)
ax.set_xlabel('Revenue (INR Lakhs)')
plt.tight_layout()
plt.savefig('dashboard.png', dpi=150, bbox_inches='tight')
plt.show()
Matplotlib style best practices: Always use the object-oriented API (fig, ax = plt.subplots()) rather than the pyplot state machine (plt.plot()) for anything beyond a quick exploratory chart — it is more explicit, easier to debug, and composable. Set figure size before plotting: figsize=(width_inches, height_inches). Use bbox_inches='tight' when saving to avoid clipped labels. Remove chart junk: eliminate unnecessary gridlines (ax.grid(axis=’y’, alpha=0.3) — only horizontal), remove top and right spines (ax.spines[[‘top’, ‘right’]].set_visible(False)), and use direct labels instead of legends where possible.
Seaborn — Statistical Visualisation
Seaborn is built on Matplotlib and provides high-level functions for statistical graphics. It handles the tedious details — colour palettes, grouping by hue, confidence intervals, faceting — that would require significant Matplotlib boilerplate. Seaborn is the standard for EDA in data science notebooks.
import seaborn as sns
import pandas as pd
sns.set_theme(style='whitegrid', palette='Blues_d', font_scale=1.1)
# --- Distribution plots ---
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# Histogram with KDE overlay
sns.histplot(df['revenue'], kde=True, bins=30, ax=axes[0], color='steelblue')
axes[0].set_title('Revenue Distribution')
# Box plot — shows median, IQR, outliers
sns.boxplot(data=df, x='category', y='revenue', ax=axes[1], palette='Blues')
axes[1].tick_params(axis='x', rotation=45)
axes[1].set_title('Revenue by Category')
# Violin plot — shows full distribution shape
sns.violinplot(data=df, x='tier', y='revenue', ax=axes[2],
palette='Blues', inner='quartile')
axes[2].set_title('Revenue Distribution by Tier')
plt.tight_layout(); plt.show()
# --- Pair plot for EDA ---
# Plots every pair of numeric columns + diagonal distributions
pair_grid = sns.pairplot(df[['revenue', 'quantity', 'discount', 'profit']],
hue='category', diag_kind='kde', plot_kws={'alpha': 0.4})
pair_grid.fig.suptitle('Feature Relationships', y=1.02)
# --- Heatmap for correlation matrix ---
fig, ax = plt.subplots(figsize=(10, 8))
corr = df.select_dtypes('number').corr()
mask = np.triu(np.ones_like(corr, dtype=bool)) # upper triangle mask
sns.heatmap(corr, mask=mask, annot=True, fmt='.2f', cmap='RdYlBu_r',
center=0, vmin=-1, vmax=1, ax=ax,
cbar_kws={'shrink': 0.8})
ax.set_title('Feature Correlation Matrix', fontweight='bold')
plt.tight_layout(); plt.show()
# --- FacetGrid: same plot across groups ---
g = sns.FacetGrid(df, col='region', row='year', height=3, aspect=1.3)
g.map_dataframe(sns.lineplot, x='month', y='revenue', hue='category')
g.add_legend(); g.set_titles(col_template='{col_name}', row_template='{row_name}')
Plotly — Interactive Visualisations
Plotly produces interactive charts that run in browsers — users can hover for tooltips, zoom, pan, filter by legend click, and download as PNG. This interactivity is essential for dashboards and stakeholder presentations where exploration is needed. Plotly Express provides a high-level API with defaults tuned for data science; Plotly Graph Objects provides full control for custom charts.
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# --- Interactive scatter with hover ---
fig = px.scatter(df, x='marketing_spend', y='revenue',
color='category', size='profit',
hover_data=['region', 'quarter'],
trendline='ols', # add OLS regression line
title='Revenue vs Marketing Spend',
labels={'marketing_spend': 'Marketing Spend (INR L)',
'revenue': 'Revenue (INR L)'})
fig.update_layout(font_family='Inter', title_x=0.5)
fig.show()
# --- Interactive line chart with range selector ---
fig = px.line(df_monthly, x='date', y='revenue',
color='category', title='Monthly Revenue Trend')
fig.update_xaxes(
rangeslider_visible=True,
rangeselector=dict(buttons=[
dict(count=3, label='3M', step='month', stepmode='backward'),
dict(count=6, label='6M', step='month', stepmode='backward'),
dict(count=1, label='1Y', step='year', stepmode='backward'),
dict(step='all', label='All')
])
)
fig.show()
# --- Choropleth map ---
fig = px.choropleth(df_states, locations='state_code',
locationmode='USA-states', color='revenue',
scope='usa', color_continuous_scale='Blues',
title='Revenue by State')
fig.show()
Visualisation Design Principles
Data-ink ratio (Tufte): Every pixel of ink in a chart should be justified by the data it represents. Eliminate: 3D effects (distort perception), excessive gridlines, heavy borders, decorative patterns, gradient fills, redundant legends (label directly instead). Maximise the ratio of data-ink to total ink — the simplest chart that communicates the message is the best chart.
Pre-attentive attributes — what the eye sees first: Colour (hue and saturation), position, size, and orientation are processed before conscious attention. Use them to direct the audience’s eye: make the most important bar a different colour; position the most important trend line at the top; use size to encode a third variable in scatter plots. Use at most 2-3 pre-attentive attributes per chart — using all of them simultaneously creates visual noise.
Colour choice principles: Use sequential palettes (Blues, Greens) for ordered numeric data. Use diverging palettes (RdYlBu, PiYG) when data has a meaningful midpoint (positive/negative, above/below average). Use qualitative palettes (Set1, Paired) for categorical data with no ordering. Always test for colour-blind accessibility — approximately 8% of men have red-green colour blindness. Use the viridis/plasma palettes (perceptually uniform, colour-blind friendly) for continuous data by default.
Storytelling with data: A visualisation in a presentation or report should have: a headline that states the finding (not just the variable name) — “Revenue grew 34% YoY driven by Electronics” not “Revenue by Category”; annotations that call out the key insight directly on the chart; a clear hierarchy (what should the eye see first?); and context (what is the baseline, target, or comparison?). For communicating model results in interviews, our Data Science Case Study Interview guide covers how to present A/B test results and metric analyses clearly.
The choice between Matplotlib, Seaborn, and Plotly should be context-driven: Matplotlib for precise publication-quality static figures; Seaborn for fast statistical EDA in notebooks; Plotly for interactive dashboards and stakeholder presentations. Combined with the Matplotlib and Seaborn fundamentals covered in our earlier guide and the Streamlit deployment guide for building interactive ML apps, these three libraries cover the full data visualisation needs of a production data science team.



