Chapter 9 — Data Visualization with Matplotlib, Seaborn, and Plotly

Visualization reveals patterns numbers hide. This chapter tours the three main Python plotting libraries: Matplotlib for fine control, Seaborn for statistical plots, and Plotly for interactive graphics.

Learning Objectives

Prerequisites / Imports

In [1]:
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
sns.set_theme(style='whitegrid')

1 The Grammar of Matplotlib

A Figure holds one or more Axes. Plot, then label, then show.

In [1]:
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(7,4))
ax.plot(x, np.sin(x), label='sin')
ax.plot(x, np.cos(x), label='cos')
ax.set_title('Sine and Cosine')
ax.set_xlabel('x'); ax.set_ylabel('y')
ax.legend()
plt.show()

2 Bar Charts

Compare categorical quantities.

In [1]:
revenue = pd.DataFrame({'product':['A','B','C','D'], 'sales':[250, 180, 320, 140]})
fig, ax = plt.subplots(figsize=(7,4))
ax.bar(revenue['product'], revenue['sales'], color='steelblue')
ax.set_title('Sales by Product')
ax.set_ylabel('Sales ($)')
plt.show()

3 Histograms

Show the distribution of a numeric variable.

In [1]:
tips = sns.load_dataset('tips')
plt.figure(figsize=(7,4))
plt.hist(tips['total_bill'], bins=20, color='coral', edgecolor='black')
plt.title('Distribution of Total Bill')
plt.xlabel('Total bill ($)'); plt.ylabel('Frequency')
plt.show()

4 Scatter Plots

Reveal relationships between two numeric variables.

In [1]:
plt.figure(figsize=(7,4))
plt.scatter(tips['total_bill'], tips['tip'], alpha=0.6)
plt.title('Tip vs Total Bill')
plt.xlabel('Total bill ($)'); plt.ylabel('Tip ($)')
plt.show()

5 Seaborn Statistical Plots

Seaborn makes attractive statistical graphics with little code.

In [1]:
plt.figure(figsize=(7,4))
sns.boxplot(data=tips, x='day', y='total_bill')
plt.title('Total bill by day')
plt.show()
In [1]:
penguins = sns.load_dataset('penguins').dropna()
plt.figure(figsize=(7,5))
sns.scatterplot(data=penguins, x='flipper_length_mm', y='body_mass_g', hue='species')
plt.title('Penguin flipper length vs body mass')
plt.show()

6 Correlation Heatmap

A heatmap of the correlation matrix spotlights linear relationships.

In [1]:
num = tips.select_dtypes('number').corr()
plt.figure(figsize=(6,5))
sns.heatmap(num, annot=True, cmap='coolwarm', vmin=-1, vmax=1)
plt.title('Correlation matrix (tips)')
plt.show()

7 Interactive Plots with Plotly

Plotly figures are interactive (zoom, hover). We render a static PNG here; in a live notebook the figure is fully interactive.

In [1]:
import plotly.express as px
tips = sns.load_dataset('tips')
fig = px.scatter(tips, x='total_bill', y='tip', color='time',
                 size='size', hover_data=['day'],
                 title='Tip vs Total Bill (interactive)')
fig
Figure({
    'data': [{'customdata': array([['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sun'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Sat'],
                                   ['Thur']], dtype=object),
              'hovertemplate': ('time=Dinner<br>total_bill=%{x}' ... '{customdata[0]}<extra></extra>'),
              'legendgroup': 'Dinner',
              'marker': {'color': '#636efa',
                         'size': array([2, 3, 3, 2, 4, 4, 2, 4, 2, 2, 2, 4, 2, 4, 2, 2, 3, 3, 3, 3, 2, 2, 2, 4,
                                        2, 4, 2, 2, 2, 2, 2, 4, 2, 4, 2, 3, 3, 3, 3, 3, 3, 2, 2, 2, 4, 2, 2, 4,
                                        3, 2, 2, 2, 4, 2, 4, 2, 4, 2, 2, 4, 2, 2, 2, 4, 3, 3, 2, 1, 2, 2, 2, 3,
                                        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2,
                                        2, 2, 1, 3, 2, 3, 2, 4, 2, 2, 3, 4, 4, 5, 6, 4, 2, 4, 4, 2, 3, 2, 2, 3,
                                        2, 4, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 3, 4, 2, 5, 3, 5, 3, 3,
                                        2, 3, 4, 2, 2, 3, 4, 4, 2, 3, 2, 5, 2, 2, 4, 4, 2, 2, 4, 3, 2, 2, 2, 2,
                                        2, 2, 3, 3, 2, 2, 2, 2], dtype=int64),
                         'sizemode': 'area',
                         'sizeref': 0.015,
                         'symbol': 'circle'},
              'mode': 'markers',
              'name': 'Dinner',
              'orientation': 'v',
              'showlegend': True,
              'type': 'scatter',
              'x': array([16.99, 10.34, 21.01, 23.68, 24.59, 25.29,  8.77, 26.88, 15.04, 14.78,
                          10.27, 35.26, 15.42, 18.43, 14.83, 21.58, 10.33, 16.29, 16.97, 20.65,
                          17.92, 20.29, 15.77, 39.42, 19.82, 17.81, 13.37, 12.69, 21.7 , 19.65,
                           9.55, 18.35, 15.06, 20.69, 17.78, 24.06, 16.31, 16.93, 18.69, 31.27,
                          16.04, 17.46, 13.94,  9.68, 30.4 , 18.29, 22.23, 32.4 , 28.55, 18.04,
                          12.54, 10.29, 34.81,  9.94, 25.56, 19.49, 38.01, 26.41, 11.24, 48.27,
                          20.29, 13.81, 11.02, 18.29, 17.59, 20.08, 16.45,  3.07, 20.23, 15.01,
                          12.02, 17.07, 26.86, 25.28, 14.73, 10.51, 17.92, 28.97, 22.49,  5.75,
                          16.32, 22.75, 40.17, 27.28, 12.03, 21.01, 12.46, 11.35, 15.38, 44.3 ,
                          22.42, 20.92, 15.36, 20.49, 25.21, 18.24, 14.31, 14.  ,  7.25, 38.07,
                          23.95, 25.71, 17.31, 29.93, 14.07, 13.13, 17.26, 24.55, 19.77, 29.85,
                          48.17, 25.  , 13.39, 16.49, 21.5 , 12.66, 16.21, 13.81, 17.51, 24.52,
                          20.76, 31.71, 10.59, 10.63, 50.81, 15.81,  7.25, 31.85, 16.82, 32.9 ,
                          17.89, 14.48,  9.6 , 34.63, 34.65, 23.33, 45.35, 23.17, 40.55, 20.69,
                          20.9 , 30.46, 18.15, 23.1 , 15.69, 26.59, 38.73, 24.27, 12.76, 30.06,
                          25.89, 48.33, 13.27, 28.17, 12.9 , 28.15, 11.59,  7.74, 30.14, 20.45,
                          13.28, 22.12, 24.01, 15.69, 11.61, 10.77, 15.53, 10.07, 12.6 , 32.83,
                          35.83, 29.03, 27.18, 22.67, 17.82, 18.78]),
              'xaxis': 'x',
              'y': array([ 1.01,  1.66,  3.5 ,  3.31,  3.61,  4.71,  2.  ,  3.12,  1.96,  3.23,
                           1.71,  5.  ,  1.57,  3.  ,  3.02,  3.92,  1.67,  3.71,  3.5 ,  3.35,
                           4.08,  2.75,  2.23,  7.58,  3.18,  2.34,  2.  ,  2.  ,  4.3 ,  3.  ,
                           1.45,  2.5 ,  3.  ,  2.45,  3.27,  3.6 ,  2.  ,  3.07,  2.31,  5.  ,
                           2.24,  2.54,  3.06,  1.32,  5.6 ,  3.  ,  5.  ,  6.  ,  2.05,  3.  ,
                           2.5 ,  2.6 ,  5.2 ,  1.56,  4.34,  3.51,  3.  ,  1.5 ,  1.76,  6.73,
                           3.21,  2.  ,  1.98,  3.76,  2.64,  3.15,  2.47,  1.  ,  2.01,  2.09,
                           1.97,  3.  ,  3.14,  5.  ,  2.2 ,  1.25,  3.08,  3.  ,  3.5 ,  1.  ,
                           4.3 ,  3.25,  4.73,  4.  ,  1.5 ,  3.  ,  1.5 ,  2.5 ,  3.  ,  2.5 ,
                           3.48,  4.08,  1.64,  4.06,  4.29,  3.76,  4.  ,  3.  ,  1.  ,  4.  ,
                           2.55,  4.  ,  3.5 ,  5.07,  2.5 ,  2.  ,  2.74,  2.  ,  2.  ,  5.14,
                           5.  ,  3.75,  2.61,  2.  ,  3.5 ,  2.5 ,  2.  ,  2.  ,  3.  ,  3.48,
                           2.24,  4.5 ,  1.61,  2.  , 10.  ,  3.16,  5.15,  3.18,  4.  ,  3.11,
                           2.  ,  2.  ,  4.  ,  3.55,  3.68,  5.65,  3.5 ,  6.5 ,  3.  ,  5.  ,
                           3.5 ,  2.  ,  3.5 ,  4.  ,  1.5 ,  3.41,  3.  ,  2.03,  2.23,  2.  ,
                           5.16,  9.  ,  2.5 ,  6.5 ,  1.1 ,  3.  ,  1.5 ,  1.44,  3.09,  3.  ,
                           2.72,  2.88,  2.  ,  3.  ,  3.39,  1.47,  3.  ,  1.25,  1.  ,  1.17,
                           4.67,  5.92,  2.  ,  2.  ,  1.75,  3.  ]),
              'yaxis': 'y'},
             {'customdata': array([['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Thur'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri'],
                                   ['Fri']], dtype=object),
              'hovertemplate': ('time=Lunch<br>total_bill=%{x}<' ... '{customdata[0]}<extra></extra>'),
              'legendgroup': 'Lunch',
              'marker': {'color': '#EF553B',
                         'size': array([4, 2, 2, 2, 2, 1, 2, 2, 4, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 6, 2, 2,
                                        2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 5, 6, 2, 2, 3, 2, 2, 2, 2, 2,
                                        2, 2, 2, 2, 4, 2, 2, 3, 2, 2, 2, 4, 3, 2, 2, 1, 3, 2, 2, 2], dtype=int64),
                         'sizemode': 'area',
                         'sizeref': 0.015,
                         'symbol': 'circle'},
              'mode': 'markers',
              'name': 'Lunch',
              'orientation': 'v',
              'showlegend': True,
              'type': 'scatter',
              'x': array([27.2 , 22.76, 17.29, 19.44, 16.66, 10.07, 32.68, 15.98, 34.83, 13.03,
                          18.28, 24.71, 21.16, 10.65, 12.43, 24.08, 11.69, 13.42, 14.26, 15.95,
                          12.48, 29.8 ,  8.52, 14.52, 11.38, 22.82, 19.08, 20.27, 11.17, 12.26,
                          18.26,  8.51, 10.33, 14.15, 16.  , 13.16, 17.47, 34.3 , 41.19, 27.05,
                          16.43,  8.35, 18.64, 11.87,  9.78,  7.51, 19.81, 28.44, 15.48, 16.58,
                           7.56, 10.34, 43.11, 13.  , 13.51, 18.71, 12.74, 13.  , 16.4 , 20.53,
                          16.47, 12.16, 13.42,  8.58, 15.98, 13.42, 16.27, 10.09]),
              'xaxis': 'x',
              'y': array([4.  , 3.  , 2.71, 3.  , 3.4 , 1.83, 5.  , 2.03, 5.17, 2.  , 4.  , 5.85,
                          3.  , 1.5 , 1.8 , 2.92, 2.31, 1.68, 2.5 , 2.  , 2.52, 4.2 , 1.48, 2.  ,
                          2.  , 2.18, 1.5 , 2.83, 1.5 , 2.  , 3.25, 1.25, 2.  , 2.  , 2.  , 2.75,
                          3.5 , 6.7 , 5.  , 5.  , 2.3 , 1.5 , 1.36, 1.63, 1.73, 2.  , 4.19, 2.56,
                          2.02, 4.  , 1.44, 2.  , 5.  , 2.  , 2.  , 4.  , 2.01, 2.  , 2.5 , 4.  ,
                          3.23, 2.2 , 3.48, 1.92, 3.  , 1.58, 2.5 , 2.  ]),
              'yaxis': 'y'}],
    'layout': {'legend': {'itemsizing': 'constant', 'title': {'text': 'time'}, 'tracegroupgap': 0},
               'template': '...',
               'title': {'text': 'Tip vs Total Bill (interactive)'},
               'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'total_bill'}},
               'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'tip'}}}
})
In [1]:
fig2 = px.bar(tips.groupby('day', as_index=False)['total_bill'].mean(),
                 x='day', y='total_bill', title='Average total bill by day')
fig2
<cell-prefix>:1: FutureWarning:

The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.

Figure({
    'data': [{'alignmentgroup': 'True',
              'hovertemplate': 'day=%{x}<br>total_bill=%{y}<extra></extra>',
              'legendgroup': '',
              'marker': {'color': '#636efa', 'pattern': {'shape': ''}},
              'name': '',
              'offsetgroup': '',
              'orientation': 'v',
              'showlegend': False,
              'textposition': 'auto',
              'type': 'bar',
              'x': array(['Thur', 'Fri', 'Sat', 'Sun'], dtype=object),
              'xaxis': 'x',
              'y': array([17.68274194, 17.15157895, 20.44137931, 21.41      ]),
              'yaxis': 'y'}],
    'layout': {'barmode': 'relative',
               'legend': {'tracegroupgap': 0},
               'template': '...',
               'title': {'text': 'Average total bill by day'},
               'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'title': {'text': 'day'}},
               'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'total_bill'}}}
})

8 Choosing the Right Chart

Question Chart
Distribution of one variable histogram, box plot
Relationship of two variables scatter, line
Comparison across categories bar chart
Part of a whole stacked bar, pie (sparingly)
Correlation among many variables heatmap

Case Study: Sales Dashboard Elements

Combine a bar chart of sales by category and a line chart of monthly sales — the building blocks of a business dashboard.

In [1]:
rng = np.random.default_rng(1)
cats = ['Electronics','Clothing','Books','Home']
sales_by_cat = pd.DataFrame({'category':cats, 'sales':rng.integers(1000,5000,size=4)})
months = pd.date_range('2026-01-01', periods=12, freq='MS')
monthly = pd.DataFrame({'month':months, 'sales':np.cumsum(rng.normal(200, 40, size=12)).round(0)})

fig, axes = plt.subplots(1, 2, figsize=(12,4))
axes[0].bar(sales_by_cat['category'], sales_by_cat['sales'], color='teal')
axes[0].set_title('Sales by category'); axes[0].set_ylabel('Sales ($)')
axes[1].plot(monthly['month'], monthly['sales'], marker='o', color='darkorange')
axes[1].set_title('Cumulative monthly sales'); axes[1].tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()
<cell-expr>:1: UserWarning:


Exercises

  1. Plot the functions $y=x^2$ and $y=x^3$ on the same axes for $x\in[-2,2]$.
  2. Make a bar chart of five cities and their populations.
  3. Draw a histogram with 30 bins of a normal random sample of 1000 points.
  4. Create a scatter plot of two numeric columns and color by a category.
  5. Use Seaborn to make a box plot of total bill by smoker status.
  6. Produce a correlation heatmap of the penguins numeric columns.
  7. Build an interactive Plotly scatter of penguin flipper vs body mass.
  8. Save a Matplotlib figure to a PNG file with plt.savefig.
  9. Create a 2x1 subplot with a line chart and a bar chart.
  10. Explain when you would choose a heatmap over a scatter plot.

Python Data Science: From Foundations to Applications — Chapter 9