Matplotlib & Seaborn
Theme
Palette
Download
Caption (tap to copy)
📖 Deep dive (full written explanation)
This is the hands-on post: a progressive build from a blank figure to a faceted Seaborn grid. Every block is self-contained and runs as-is on Seaborn's built-in datasets, so you can paste them straight into a notebook and watch each chart appear.
The cover sets the tone — less reading about plotting, more producing it. The ordering deliberately walks up the abstraction ladder: bare Matplotlib first, then object-oriented, then Seaborn, then faceting.
Step zero is setup. We install both libraries, import pyplot and seaborn under their conventional aliases, and call sns.set_theme once to apply Seaborn's styling globally — this affects Matplotlib charts too, since Seaborn just sets Matplotlib's rcParams. Then we load the built-in tips dataset, a small tidy table of restaurant bills and tips.
Using a built-in dataset matters for a tutorial: there's no file to download or path to fix, so every subsequent block runs without setup friction.
This is the simplest possible chart: the stateful pyplot API drawing a line from two Python lists. plt.plot with marker="o" draws the line and points, plt.title labels it, and plt.show renders it. No Figure or Axes is created explicitly — pyplot manages a 'current' one for you.
This is fine for a throwaway chart in a notebook. The next step shows why you'd graduate from it the moment you want real control.
Here is the same line, rebuilt in the object-oriented style. plt.subplots gives us explicit fig and ax objects with a set size. We call ax.plot with a color and label, then ax.set_title, ax.set_xlabel, ax.set_ylabel, and ax.legend — every command names its target. fig.tight_layout fixes spacing.
Compare it to the previous slide: more lines, zero ambiguity. This is the style to internalize, because it's the only one that stays sane once you have multiple subplots.
Now we switch to Seaborn for a statistical chart that would be tedious in raw Matplotlib. sns.histplot takes the DataFrame and a column name and draws a histogram; kde=True overlays a smooth kernel density estimate of the distribution. bins controls granularity and color sets the fill.
The point of this block is the leap in altitude: one line, a DataFrame, a column name, and you get a binned distribution plus a density curve — work that would be many lines of manual binning in pure Matplotlib.
This box plot shows Seaborn's grouping power. Passing x="day" and y="total_bill" draws one box per day, and hue="smoker" splits each day further into smoker and non-smoker boxes side by side. In one call you get medians, quartiles, spread, and outliers for every subgroup.
The automatic grouping is the headline feature: you describe the breakdown with column names and Seaborn does the aggregation and positioning. Reproducing this by hand in Matplotlib would take real effort.
This flow diagram is a quick reference mapping each chart type to the question it answers. A line plot shows a trend over an ordered sequence. A histogram shows the shape of one variable. A box plot compares groups. A relplot shows relationships and can facet across categories.
Keeping this mapping in mind is what stops you reaching for the wrong chart — a recurring theme that the Common Mistakes post returns to in depth.
relplot is a figure-level function, meaning it manages the whole figure and builds a grid for you. Here col="time" splits the data into separate panels for lunch and dinner, while hue="smoker" colors points within each panel. kind="scatter" picks the plot type. One call produces a multi-panel comparison.
This is faceting — small multiples that let you compare subgroups side by side — and it's where Seaborn's figure-level functions earn their keep. Doing this manually would mean looping over subsets and managing Axes yourself.
The final code block closes the loop back to the concept of using both libraries together. sns.scatterplot returns a Matplotlib Axes; we then finish the chart with pure Matplotlib calls — set_title with a font size, set_xlim to clamp the range — and save it via ax.figure.savefig with bbox_inches="tight" to trim whitespace.
This is the canonical real-world pattern: Seaborn to get most of the chart instantly, Matplotlib to polish and export it exactly how you want.
These patterns are the ones worth committing to memory because they recur in nearly every plotting session. Call set_theme once at the top. Always pass a tidy DataFrame plus column names rather than raw arrays. Use hue, col, and row to split data automatically. Grab the returned Axes to tweak. And save with bbox_inches='tight' to avoid clipped labels.
Internalize these five and most Seaborn work becomes nearly automatic.
This wraps the code post and sets up the finale. You can now build the core chart types in both libraries. The last post turns to the traps — the mistakes that still produce a chart but quietly mislead, which are the most dangerous kind because they don't error out.