learn-python-data-analysis-with-phoebe / Session 7 of 8
Learn Python Data Analysis with Phoebe · Session 7 of 8

Seaborn and the polish

You can hand-build a labeled chart in matplotlib now. Seaborn does in one line what that took ten - and it understands DataFrames and categories natively. This session turns your job-market frame into publication-grade statistical charts: distributions split by group, a correlation heatmap, and a cohesive themed set ready for the Session 8 report.

🟠 Getting real Builders Statistical charts 45 min live + self-study
0-3 · Welcome 3-18 · Concepts 18-40 · Build-along demos 40-45 · Q&A
Part 0

One line, not ten

Matplotlib gave you the engine and the control. Seaborn gives you the shortcut. Where matplotlib made you count values, bin them, and place bars by hand, seaborn takes data=df plus the column names and does the statistics for you - it groups, computes confidence intervals, builds the legend, and picks sensible defaults. It's not a replacement for matplotlib; it sits on top of it. That means everything you learned last session - fig, ax, set_title, savefig - still works, and now the hard parts get one-liners.

Live - presented in session Self-study - read after class ★ Build-along demo The project: the data job market
★ What you walk out with today Publication-grade statistical charts of your frame - salary distributions split by experience level, a correlation heatmap that reveals what relates to what, and a faceted set of small multiples - all wearing one cohesive theme and palette, saved as PNG and SVG for the report.
Part 1 · covers the seaborn interface model

Why seaborn, and figure- vs axes-level 5 min live

Seaborn has two families of functions, and knowing which family you're calling is the one concept that saves the most confusion. Learn it now.

Axes-level draws onto an ax you already have scatterplot() histplot() · kdeplot() boxplot() · violinplot() barplot() · countplot() fig, ax = plt.subplots() sns.boxplot(data=df, x=.., y=.., ax=ax) Figure-level manages its own figure + faceting relplot() → scatter / line displot() → hist / kde catplot() → box / violin / bar facet with col= and row= sns.displot(data=df, x=.., col="role_bucket") Axes-level fits into your own figure; figure-level owns the figure and does the faceting.
🔍 Click to zoom - axes-level functions draw onto your ax; figure-level functions own the whole figure and facet for you
LiveData-aware, and two function families3 min

Two things make seaborn feel like a level-up:

  • It speaks DataFrame. You pass data=df and the column names as strings - x="experience_level", y="salary_year", hue="remote". Seaborn does the grouping, the aggregation, the confidence intervals, and the legend. No more counting and binning by hand.
  • It has two function families. Axes-level functions (scatterplot, histplot, boxplot, barplot) draw onto an ax you pass in - they slot into the matplotlib workflow you already know. Figure-level functions (relplot, displot, catplot) create and manage their own figure, and can split your data into a grid of small multiples with col= and row=.

Rule of thumb: reach for axes-level when you're placing one chart into a layout you control; reach for figure-level when you want faceting done for you.

Self-studyWhich family, when2 min read
You want...FamilyFunction
one chart inside my own fig, axaxes-levelboxplot, histplot, scatterplot, barplot
the same chart split across categoriesfigure-levelcatplot, displot, relplot with col= / row=
fine control over a multi-panel layoutaxes-leveldraw each panel onto its own ax
The gotcha Figure-level functions don't take an ax= argument - they own the figure. If you try to drop relplot into a subplot grid it won't fit. That single fact explains most "why won't my seaborn chart go where I put it" confusion.
Part 2 · covers the core statistical plots

The core plots on real data 5 min live

A handful of seaborn plots answer most job-market questions. Each maps to a question - pick the plot by what you're asking.

histplot / kdeplot Q: how does salary spread? boxplot / violinplot Q: salary by experience level? barplot (with CI) Q: mean salary by role? scatterplot (hue) Q: salary vs size, by remote? hue adds a categorical split; the legend is built for you.
🔍 Click to zoom - four seaborn plots, each labeled with the job-market question it answers
LiveThe plot-to-question map3 min
  • histplot / kdeplot - the spread of one number (salary_year), smooth curve optional.
  • boxplot / violinplot - a numeric column split by a category (salary by experience_level). Box shows median and quartiles; violin adds the full shape.
  • barplot - the mean of a value per category, with a confidence-interval whisker seaborn computes for free (mean salary by role).
  • scatterplot - two numbers, with an optional hue= to color a third categorical dimension (salary vs company size, colored by remote).
  • countplot - just how many rows per category, the seaborn one-liner for a frequency bar.
Real world

A boxplot of salary by experience_level tells the whole seniority-pay story in one glance: you see the median step up from entry to senior, and the boxes show how much the ranges overlap. That single chart often answers the loudest question in any "state of the job market" post - "does seniority actually pay?" - with evidence instead of a vibe.

Self-studyhue, and how seaborn does stats for you2 min read

Two things seaborn quietly handles that you'd hand-code in matplotlib:

  • hue= splits any plot by a categorical column and builds the color legend automatically. One argument, and a boxplot of salary by experience becomes salary by experience and remote status.
  • Confidence intervals - barplot and lineplot aggregate and draw the CI whisker by default, so a "mean salary by role" chart honestly shows how uncertain each mean is. That's statistics baked into the plotting call.
Part 3 · covers heatmaps and theming

Heatmaps, themes, and polish 4 min live

Two finishing moves: the correlation heatmap that scans your whole numeric frame at once, and the theme that makes every chart in the report look like one family.

Correlation heatmap - df.corr() salary size views applies salary size views applies 1.0 .42 .18 .21 .42 1.0 .09 .05 .18 .09 1.0 .66 .21 .05 .66 1.0 weak strong Darker = stronger correlation; one glance shows what moves with what.
🔍 Click to zoom - a correlation heatmap: each shaded, annotated cell is how strongly two numeric columns move together
LiveThe heatmap, and one theme to rule them2 min
★ Correlation heatmap + a set themeimport seaborn as sns # one cohesive look for every chart in the report sns.set_theme(style="whitegrid", palette="Blues", context="talk") corr = jobs[["salary_year", "company_size", "views", "applies"]].corr() ax = sns.heatmap(corr, annot=True, cmap="Blues", fmt=".2f") ax.set_title("What relates to what - numeric columns")

df.corr() gives you the pairwise correlation matrix; sns.heatmap(..., annot=True) shades and numbers every cell so a reader sees strength and sign at a glance. And sns.set_theme() at the top of the notebook sets style, palette, and context (label sizing) for every chart after it - one line, one consistent look.

Self-studyPalette discipline and despine2 min read
  • One meaning per color. If blue means "remote" in one chart, it means "remote" in every chart - and you add a legend so the reader never guesses. This is the workspace encoding rule, and seaborn's palette= makes it easy to keep.
  • set_theme(style, palette, context) - style is the background (whitegrid, ticks...), palette is the color set, context scales fonts for a deck vs a paper.
  • sns.despine() removes the top and right spines for a cleaner, more modern frame.
  • Saving is unchanged from Session 6 - grab the figure and savefig(dpi=300) as PNG and SVG.
Real world

A correlation heatmap of your numeric columns is the fastest "what relates to what" scan there is. On the job data it might show salary moving with company size but barely with view count - a finding you'd never spot scrolling rows, and one that earns a line in the Session 8 report.

Demo 1 of 3

Salary by experience, then split by remote ★ 7 min · everyone codes

Set the theme once so the whole session looks consistent.

Draw a boxplot of salary_year across experience_level - read the seniority-pay story.

Add hue="remote" and watch seaborn split each box and build the legend for free.

★ Boxplot, then add a hueimport seaborn as sns import matplotlib.pyplot as plt sns.set_theme(style="whitegrid", palette="Blues", context="talk") salaried = jobs.dropna(subset=["salary_year"]) # 1 - salary by experience level fig, ax = plt.subplots(figsize=(10, 6)) sns.boxplot(data=salaried, x="experience_level", y="salary_year", ax=ax) ax.set_title("Advertised salary by experience level") ax.set_xlabel("experience level") ax.set_ylabel("salary_year (USD)") # 2 - now split each box by remote status - one extra argument fig2, ax2 = plt.subplots(figsize=(11, 6)) sns.boxplot(data=salaried, x="experience_level", y="salary_year", hue="remote", ax=ax2) ax2.set_title("Salary by experience level, split by remote")
Read it out loud "Median salary steps up entry -> mid -> senior, and remote roles sit a little higher at each level." If your chart lets you say a sentence like that with a straight face, it's working. If the boxes are unreadable, try violinplot or drop the smallest categories.
Demo 2 of 3

A correlation heatmap of the numbers ★ 8 min · everyone codes

Pick your numeric columns - salary, company size, applies, views.

Compute .corr(), then heatmap it with annotations on.

Interpret: which pairs move together, which don't, and what that means for the market.

★ corr() -> heatmapnum_cols = ["salary_year", "company_size", "applies", "views"] corr = jobs[num_cols].corr() fig, ax = plt.subplots(figsize=(7, 6)) sns.heatmap(corr, annot=True, fmt=".2f", cmap="Blues", vmin=-1, vmax=1, ax=ax) ax.set_title("Correlation between numeric columns") fig.tight_layout() fig.savefig("corr-heatmap.png", dpi=300, bbox_inches="tight") fig.savefig("corr-heatmap.svg", bbox_inches="tight")
Real world

Reading it: a strong positive cell (dark) between views and applies just says popular postings get more clicks - not surprising. The interesting cell is salary vs company_size: if it's moderate and positive, "bigger companies advertise higher pay" becomes a defensible line in your report. The heatmap turned four columns into one scannable claim.

Demo 3 of 3

Faceted small multiples with displot ★ 7 min · everyone codes

Use a figure-level function to split the salary distribution across role buckets in one call.

col= makes a panel per category - small multiples without a subplot loop.

Apply the theme, then save the figure for the report.

★ displot faceted by role bucketg = sns.displot( data=salaried, x="salary_year", col="role_bucket", # one panel per role group col_wrap=3, # wrap into a tidy grid bins=30, height=3.5, ) g.set_titles("{col_name}") g.set_axis_labels("salary_year (USD)", "count") g.figure.suptitle("Salary distribution by role bucket", y=1.03) # figure-level object -> save via .figure g.figure.savefig("salary-by-role.png", dpi=300, bbox_inches="tight") g.figure.savefig("salary-by-role.svg", bbox_inches="tight")
Figure-level saving Figure-level calls return a grid object (a FacetGrid), not an Axes. Reach its figure with g.figure to set a super-title or save. That's the practical payoff of the Part 1 distinction - now you know exactly why the save line looks different here.
After the session

This week ◐ 40 min total

Check yourself

Three questions before you go 🎯 ◐ 90 seconds

1 · What's the main advantage of seaborn over raw matplotlib?

Seaborn sits on top of matplotlib. You pass data=df plus column names and it handles the grouping, confidence intervals, legend, and defaults - the statistics come baked in.

2 · What's the difference between figure-level and axes-level functions?

Figure-level functions own the whole figure and do faceting for you - so they don't take ax=. Axes-level functions slot into a fig, ax layout you control.

3 · What does sns.heatmap(df.corr(), annot=True) show?

df.corr() builds the correlation matrix; the heatmap shades and labels each cell so you see the strength and sign of every pair at a glance.

Source material

Official sources covered

This session teaches the working content of the official seaborn tutorial and Kaggle's free Data Visualization micro-course. Certificates and the graded final project stay on those platforms - links provided.

seaborn tutorial - relational, distribution, categorical + themesfunction families, boxplot / histplot / heatmap, set_theme - Parts 1-3
Kaggle Data Visualization L1-L6Hello Seaborn, Line, Bar & Heatmap, Scatter, Distributions, Choosing plots - all demos
jointplot / PairGrid + custom palettesself-study - powerful, but beyond today's core set
Kaggle Data Visualization - Final Projectstays on Kaggle for the certificate

Session 7 cheat sheet · pin this

Set the look oncesns.set_theme(style="whitegrid", palette="Blues", context="talk") at the top - every chart inherits it.
Speak DataFramePass data=df plus column names: x="experience_level", y="salary_year", hue="remote".
Two familiesAxes-level (scatterplot/histplot/boxplot) draw on an ax; figure-level (relplot/displot/catplot) own the figure.
Group distributionsboxplot / violinplot for a numeric column split by a category - the seniority-pay story in one glance.
What relates to whatsns.heatmap(df.corr(), annot=True, cmap="Blues") - the fastest correlation scan.
Small multiplesdisplot / catplot with col= / row= facet automatically; save via g.figure.savefig(dpi=300).