Seaborn

Chapter 7 ยท Think Data Science & Management

Seaborn Foundations

7.1 ยท Think Data Science & Management

Today's goals

  • Read and write the Seaborn call pattern
  • Choose between pandas, Matplotlib, and Seaborn
  • Map columns to color, style, and size
  • Recognize tidy data and reshape wide data with melt
  • Tell figure-level from axes-level functions

Seaborn: statistical charts from a DataFrame

  • Built on top of Matplotlib: every Seaborn plot is a Matplotlib figure
  • You pass a DataFrame and column names
  • Seaborn handles grouping, colors, legends, and statistics
import seaborn as sns
sns.set_theme(style='whitegrid')
tips = sns.load_dataset('tips')

The tips data

tips[['total_bill', 'tip', 'size', 'day', 'time', 'sex']].head(3)
   total_bill   tip  size  day    time     sex
0       16.99  1.01     2  Sun  Dinner  Female
1       10.34  1.66     3  Sun  Dinner    Male
2       21.01  3.50     3  Sun  Dinner    Male
  • One row per bill; numeric and categorical columns

One call pattern

sns.PLOT_TYPE(data=df, x='col_a', y='col_b', hue='col_c')
     โ†‘             โ†‘        โ†‘          โ†‘           โ†‘
 plot type    DataFrame   x-axis     y-axis    color by
  • x, y, hue take column names as strings
  • Add ax= to draw on a Matplotlib axes you created

A first look: three plots, one pattern

Three Seaborn plots: a histogram of total bill, a scatter of tip against bill colored by meal, and box plots of bill by day

histplot(data=tips, x='total_bill') ยท scatterplot(..., y='tip', hue='time') ยท boxplot(..., x='day', y='total_bill')

Three libraries, three jobs

Library Strength Typical call
pandas Fastest plot from a DataFrame df.plot(kind='hist')
Matplotlib Full control of every element ax.hist(values)
Seaborn Grouped, statistical charts sns.histplot(data=df, x='col')

Quick check: pandas. Grouped comparison: Seaborn. Exact layout: Matplotlib.

Scatter: pandas vs. Seaborn

Left: a plain pandas scatter of tip against bill. Right: the Seaborn version with color for meal, marker shape for sex, and point size for party size

sns.scatterplot(data=tips, x='total_bill', y='tip', hue='time', style='sex', size='size')

Histograms: why do they look different?

Three histograms of bill and tip: pandas and Matplotlib with 10 bins, Seaborn with more bins and transparent overlapping bars

  • pandas and Matplotlib: 10 bins by default
  • Seaborn: picks the number of bins from the data, and overlaps groups with transparency

A manager wants tips compared for lunch and dinner on one chart. Fastest good choice?

  1. tips['tip'].plot(kind='hist')
  2. sns.histplot(data=tips, x='tip', hue='time')
  3. A Matplotlib loop over meals with ax.hist()
  4. sns.histplot(data=tips, x='tip')

The Seaborn data model

Semantic mappings

Argument Maps a column to Example
hue= color hue='time'
style= marker or line style style='sex'
size= marker size size='size'
  • size='size' maps a column; s=80 sets one constant size

Tidy data

  • Each row is one observation
  • Each column is one variable
  • Each cell holds one value
  student  subject  score
0   Alice     math     90
1   Alice  english     85
2     Bob     math     78
3     Bob  english     88

Seaborn calls this long-form data.

Wide data: easy to read, hard to plot

  student gender  grade  math  english  science
0   Alice      F     10    90       85       92
1     Bob      M     10    78       88       80
  • One row per student; one column per subject
  • There is no subject column, so x='subject' fails

Which table is tidy?

  1. Columns: region, Q1, Q2, Q3, Q4
  2. Columns: region, quarter, revenue
  3. Columns: quarter, North, South
  4. Columns: region, value (holding both revenue and cost)

Wide to tidy with melt

students_tidy = pd.melt(students,
    id_vars=['student', 'gender', 'grade'],
    var_name='subject', value_name='score')
  student gender  grade  subject  score
0   Alice      F     10     math     90
1     Bob      M     10     math     78
...
5   Alice      F     10  english     85
  • 5 students ร— 3 subjects = 15 rows

Tidy data plots in one line

sns.barplot(data=students_tidy,
            x='subject', y='score',
            hue='gender',
            errorbar=None)
  • subject on the x-axis
  • gender as color

Bars of average score by subject, split by gender

Figure-level vs. axes-level

Two kinds of functions

Axes-level Figure-level
Examples histplot, scatterplot, boxplot displot, relplot, catplot
Draws on one ax you pass its own new figure
Panels plt.subplots + ax= col=, row=
Size figsize= height=, aspect=

Faceting: figure-level. Custom layouts and layering: axes-level.

Same plot, two ways

sns.relplot(data=tips,
    x='total_bill', y='tip',
    hue='sex', col='time')
  • Figure-level: one call
  • col='time' builds the panels
fig, axes = plt.subplots(1, 2)
for ax, meal in zip(axes,
        ['Lunch', 'Dinner']):
    sns.scatterplot(
        data=tips[tips['time'] == meal],
        x='total_bill', y='tip',
        hue='sex', ax=ax)
  • Axes-level: you build the panels
  • Same two scatter plots

You need box plots of bill by day, one panel per meal. Which call?

  1. sns.boxplot(data=tips, x='day', y='total_bill', col='time')
  2. sns.catplot(data=tips, x='day', y='total_bill', col='time', kind='box')
  3. sns.catplot(data=tips, x='day', y='total_bill', ax='time')
  4. sns.boxplot(data=tips, x='day', y='total_bill', hue='time')

Your turn: reshape regional sales

sales = pd.DataFrame({'region': ['North', 'South', 'East', 'West'],
                      'Q1': [240, 215, 260, 230], 'Q2': [255, 220, 250, 245],
                      'Q3': [270, 210, 275, 250], 'Q4': [290, 230, 280, 265]})
  1. pd.melt() into sales_tidy with columns region, quarter, revenue
  2. Bar plot: quarter on x, revenue on y, colored by region

Recap

  • Pattern: sns.PLOT(data=df, x='col', y='col', hue='col')
  • Libraries: pandas for quick looks, Seaborn for groups, Matplotlib for control
  • Mappings: hue, style, size; s= is a constant
  • Tidy data: one row per observation; melt reshapes wide data
  • Two kinds: axes-level takes ax=; figure-level makes panels

Seaborn Plot Families

7.2 ยท Think Data Science & Management

Today's goals

  • Pick a plot family from the question you are asking
  • Show a distribution with a histogram or KDE, and tune bins, alpha, and kde
  • Show a relationship with a scatter or line plot, and map hue and size
  • Compare groups with box, bar, and count plots through catplot

Three families, three questions

Family Question it answers Functions
Distribution How are the values spread out? histplot, kdeplot
Relational How do two variables move together? scatterplot, lineplot
Categorical How do groups compare? boxplot, barplot, countplot

Start from the question, then pick the family.

One call pattern for every plot

sns.histplot(data=tips, x='total_bill')
sns.scatterplot(data=tips, x='total_bill', y='tip', hue='time')
sns.boxplot(data=tips, x='day', y='total_bill')
  • data= the DataFrame
  • x=, y= column names, not arrays
  • hue=, size= map more columns to color and size
  • ax= puts the plot on an axes you created

Distribution plots

Histogram: count values in bins

fig, ax = plt.subplots()
sns.histplot(data=tips,
             x='total_bill',
             ax=ax)
ax.set_title('Total Bill')
  • Bins split the range into intervals
  • Bar height = number of bills in each bin

Histogram of total bill: most bills fall between 10 and 25 dollars, with a long tail to the right

The number of bins changes the story

Two histograms of total bill: bins=10 looks smooth; bins=30 shows more detail and gaps

  • Too few bins hide detail; too many show noise
  • Try a few values before you draw a conclusion

alpha and kde

sns.histplot(data=tips, x='tip',
             bins=30, alpha=0.4,
             kde=True)
  • alpha=0.4: bars 40% opaque
  • kde=True: adds a smooth curve on top

Histogram of tips with transparent bars and a smooth density curve on top

Which argument adds the smooth curve?

  1. alpha=0.5
  2. bins=30
  3. kde=True
  4. fill=True

KDE: a smooth distribution

fig, axes = plt.subplots(1, 2, figsize=(9, 3.2))
sns.kdeplot(data=tips, x='total_bill', fill=True, ax=axes[0])
sns.kdeplot(data=tips, x='tip', fill=True, ax=axes[1])

Two filled density curves, for total bill and for tip, both skewed to the right

Two variables: 2D KDE vs. scatter

Left: contour lines of a 2D density for bill and tip. Right: the same data as a scatter plot

  • sns.kdeplot(data=tips, x='total_bill', y='tip')
  • Contours show where points are dense

Relational plots

Scatter plot with hue

sns.scatterplot(data=tips,
                x='total_bill',
                y='tip',
                hue='time')
  • Each point is one bill
  • hue='time' colors lunch and dinner

Scatter of tip against total bill, colored by lunch and dinner; tips rise with the bill

Add size for a fourth variable

ax = sns.scatterplot(data=tips,
    x='total_bill', y='tip',
    hue='time', size='size',
    sizes=(20, 200))
ax.legend(bbox_to_anchor=(1, 1))
  • size= maps party size to point size
  • bbox_to_anchor moves the legend outside

Scatter where larger parties are drawn as larger points; they sit at higher bills

Line plot: average and variability

sns.lineplot(data=tips,
             x='day', y='tip',
             estimator='mean',
             errorbar='sd')
  • Line = mean tip each day
  • Band = mean plus or minus one standard deviation

Line of average tip by day, rising from about 2.75 dollars on Thursday to 3.26 dollars on Sunday, with a shaded band

What does the shaded band show?

  1. The highest and lowest tip each day
  2. One standard deviation above and below the mean tip
  3. The tips of individual customers
  4. The number of bills each day

Categorical plots

Box plot with catplot

g = sns.catplot(data=tips,
    x='day', y='total_bill',
    hue='sex', kind='box')
g.set_axis_labels('Day',
                  'Total Bill')
  • catplot is figure-level; kind= picks the plot
  • Box = middle 50%; line = median

Box plots of total bill by day, split by sex; Saturday and Sunday medians are higher

Bar plot: a mean, not the data

sns.barplot(data=tips,
            x='day',
            y='total_bill',
            errorbar='sd')
  • Bar height = mean total bill
  • Error bar = mean plus or minus one standard deviation

Bars of average total bill by day, about 17 to 21 dollars, with error bars

Count plot: how many in each group

sns.countplot(data=tips, x='day')
  • Counts rows per category
  • No y=: there is nothing to average
  • Histogram bins numbers; count plot counts categories

Bar chart of the number of bills by day: Saturday 87, Sunday 76, Thursday 62, Friday 19

A manager asks: "How many orders did we get each day?"

  1. sns.histplot(data=orders, x='day')
  2. sns.countplot(data=orders, x='day')
  3. sns.barplot(data=orders, x='day', y='amount')
  4. sns.scatterplot(data=orders, x='day', y='amount')

Choosing a plot

Question Plot
What does a typical bill look like? histplot or kdeplot
Do bigger bills get bigger tips? scatterplot
How does the average change over time? lineplot
How do groups differ in spread? boxplot
How do group averages compare? barplot
How many in each group? countplot

Your turn: bill vs. tip by time and party size

  1. Draw a scatter plot of total_bill vs. tip from tips
  2. Color points by time with hue
  3. Map size to marker size with sizes=(30, 180)
  4. Title it Tips: Bill vs Tip by Time and Party Size

Recap

  • Distribution: histplot and kdeplot; tune bins, alpha, kde
  • Relational: scatterplot and lineplot; add hue and size
  • Categorical: catplot with kind='box', 'bar', or 'count'
  • One pattern: data=, column names for x/y, ax= for axes-level plots
  • Pick the plot from the question

Faceting, Multivariate Views, and Style

7.3 ยท Think Data Science & Management

Today's goals

  • Compare groups side by side with faceting: col= and row=
  • Use figure-level wrappers for core faceting practice
  • Read selected pair-plot panels and heatmap entries
  • Set themes, palettes, and sizes on purpose

Small multiples: one panel per group

  • Same chart, repeated for each group
  • Shared axes make the panels directly comparable
  • Easier to read than one crowded chart with many colors

"Is the pattern the same for lunch and dinner?" is a faceting question.

Three figure-level wrappers

Figure-level Makes panels of kind options
displot distributions 'hist', 'kde'
relplot relationships 'scatter', 'line'
catplot group comparisons 'box', 'bar', 'count', ...
  • Add col= and row= to split into panels
  • height= and aspect= size each panel
  • They create their own figure: no ax=

displot: one histogram per meal

sns.displot(data=tips, x='total_bill', col='time',
            kde=True, height=3.6, aspect=1.3)

Two histograms of total bill, Lunch and Dinner, on the same axes scale; dinner bills run higher

relplot: rows and columns

sns.relplot(data=tips, x='total_bill', y='tip', hue='sex',
            col='time', row='smoker', height=2, aspect=2.8)

A two-by-two grid of scatter plots of tip against bill, split by meal across and smoker status down

catplot: compare groups within groups

sns.catplot(data=tips, x='day', y='total_bill', hue='sex',
            col='time', kind='box', height=3.6, aspect=1.1)

Box plots of total bill by day and sex, in two panels for Lunch and Dinner

Which call puts lunch and dinner side by side?

  1. sns.histplot(data=tips, x='tip', hue='time')
  2. sns.displot(data=tips, x='tip', col='time')
  3. sns.histplot(data=tips, x='tip', col='time')
  4. sns.displot(data=tips, x='tip', ax='time')

Optional: direct FacetGrid construction

tips['tip_pct'] = (100 * tips['tip']
                   / tips['total_bill'])

grid = sns.FacetGrid(tips,
    row='sex', col='time',
    margin_titles=True)
grid.map(plt.hist, 'tip_pct',
         bins=np.linspace(0, 40, 15))

A two-by-two grid of histograms of tip percentage by sex and meal; all centered around 15 percent

Figure-level function or FacetGrid?

Use displot, relplot, catplot

  • Standard plot types
  • One call, sensible defaults
  • Most of the time

Use FacetGrid

  • A custom or Matplotlib function
  • Several map() calls layered
  • Fine control of each panel

Multivariate views

Pair plot: every pair at once

sns.pairplot(tips,
    vars=['total_bill',
          'tip', 'size'],
    hue='time')
  • Off-diagonal: scatter of each pair
  • Diagonal: each variable's distribution

A three-by-three grid: scatter plots for each pair of bill, tip, and party size, with distributions on the diagonal

Optional: joint plot and margins

sns.jointplot(data=tips,
    x='total_bill', y='tip',
    kind='reg')
  • Center: scatter + regression line
  • Edges: each variable's distribution
  • kind=: 'scatter', 'kde', 'hex', 'reg'

Scatter of tip against bill with a fitted line and confidence band, and histograms along the top and right edges

What happens here?

fig, ax = plt.subplots(1, 2)
sns.jointplot(data=tips, x='total_bill', y='tip', ax=ax[0])
  1. The joint plot appears in the left panel
  2. Seaborn ignores ax= and draws a separate new figure
  3. Python raises an error: tips has no total_bill column
  4. The joint plot fills both panels

Heatmap: a correlation matrix

corr = tips[['total_bill', 'tip',
             'size']].corr()
sns.heatmap(corr, annot=True,
            cmap='coolwarm', center=0,
            vmin=-1, vmax=1)
  • Color and number show each correlation
  • vmin=-1, vmax=1 fixes the scale

Heatmap of correlations: bill and tip 0.68, bill and size 0.6, tip and size 0.49

Themes and style

Themes set the look for every plot

sns.set_theme(style='whitegrid', context='talk', palette='deep')
Setting Controls Common values
style background and grid 'whitegrid', 'ticks', 'white'
context text and line size 'notebook', 'talk', 'paper'
palette colors for groups 'deep', 'colorblind', 'Blues'

with sns.axes_style('ticks'): changes the style for one block only.

Palettes: match the data, and everyone's eyes

Two scatter plots colored by meal: one with the deep palette, one with the colorblind palette

  • Qualitative ('deep', 'colorblind') for categories
  • Sequential ('Blues') for low-to-high values
  • Diverging ('coolwarm') for values around a midpoint

s= sets one size; size= maps a column

Left: all points the same size with s=80. Right: point size mapped to party size

  • s=80: every point the same size
  • size='size': point size from the size column; sizes=(20, 220) sets the range

Overlay: several layers on one axes

fig, ax = plt.subplots()
sns.scatterplot(data=tips,
    x='total_bill', y='tip', ax=ax)
sns.rugplot(data=tips,
    x='total_bill', ax=ax)
ax.axhline(y=tips['tip'].mean(),
    color='red', linestyle='--')

Scatter of tip against bill with rug marks along the x-axis and a dashed red line at the mean tip of 3 dollars

You want every point drawn larger. Which argument?

  1. size='size'
  2. s=100
  3. sizes=(20, 200)
  4. height=4

Your turn: correlation heatmap

  1. Compute the correlation matrix of total_bill, tip, and size
  2. Draw it with annot=True, cmap='coolwarm', and center=0
  3. Fix the color scale with vmin=-1, vmax=1
  4. Title it Tips Correlation Heatmap
  5. Explain the strongest off-diagonal association and one limitation

Recap

  • Faceting: col= and row= on displot, relplot, catplot
  • Optional: direct FacetGrid, joint plots, and KDE construction
  • Interpretation: selected pair-plot panels and off-diagonal correlations
  • Figure-level functions make their own figure: no ax=
  • Style: set_theme, palettes by data type, s= vs. size=