- Learn
- Computer Science & IT
- Data Science & AI
- Data Analysis with Python
- Matplotlib and Seaborn
Visualization with Matplotlib and Seaborn
IntermediateLesson 7 of 8 in Data Analysis with Python Course
What you will learn
- Create a figure and its axes with Matplotlib's object-oriented interface, draw line, scatter and bar charts on them, and control titles, labels, ticks, limits, legends and styles.
- Set up a figure and its axesUnderstand how a Matplotlib chart is built from a figure, one or more axes and the elements drawn on them, and choose between the pyplot and object-oriented ways of writing plotting code.
- Draw line and scatter plotsDraw values along an ordered axis with
ax.plotand individual observations withax.scatter, and control color, line style, markers, marker size and transparency. - Compare categories with bar chartsDraw vertical, horizontal, grouped and stacked bar charts with
ax.barandax.barh, and label the bars with their values. - Label, scale and style a plotSet titles, axis labels, limits, ticks, tick formats, scales, legends and grids, and change the overall look of plots with style sheets and settings.
- Set up a figure and its axes
- Draw charts straight from Series and DataFrames with the pandas
plotmethod, and aggregate, sort and reshape tables first so that each chart shows the intended values.- Use the pandas plot methodCreate line, bar, histogram, box, area, pie and scatter plots from a Series or DataFrame, and customize the Matplotlib axes they return.
- Shape the data before plottingAggregate, count, sort, pivot and melt a table so that it holds exactly the values a chart should show, in the format pandas or seaborn expects.
- Use the pandas plot method
- Use seaborn to map DataFrame columns to positions, colors and styles, show relationships between numeric variables with trend summaries, and compare groups with categorical plots.
- Map columns to plot features in seabornName DataFrame columns for position, color, style and size, tell axes-level from figure-level functions, and set seaborn themes and palettes.
- Show relationships between numeric variablesDraw scatter plots, aggregated line plots with uncertainty bands, regression lines and pair plots with seaborn, and read what the bands and lines do and do not show.
- Compare groups with categorical plotsCompare a numeric variable across categories with bar, count, point, strip and swarm plots, and choose their estimator, error bars, order and orientation.
- Map columns to plot features in seaborn
- Show how a numeric variable is distributed with histograms, density curves, box plots and violin plots, and display a table of values or correlations as a heatmap.
- Show a distribution with histograms and density curvesBuild histograms in Matplotlib, pandas and seaborn, choose bins and scales, and add or compare smoothed density curves.
- Summarize and compare distributions with box plotsRead and draw box plots from quartiles and the 1.5 × IQR rule, compare groups with them, and use violin and strip plots to show what a box hides.
- Display a grid of values as a heatmapTurn a pivot table or a correlation matrix into a heatmap with seaborn or Matplotlib, and control annotations, number formats, color scales and color limits.
- Show a distribution with histograms and density curves
- Arrange small multiples with Matplotlib subplots and seaborn facets, point out what matters with text, arrows and reference lines, and save figures in the right format and size for reports and the web.
- Arrange several plots in one figureCreate grids of Axes with
plt.subplots, share scales between panels, fill the panels in a loop, fix spacing and titles, and build panels of unequal size. - Facet data with seabornSplit a plot into panels by one or two categorical variables with the
col,rowandcol_wraparguments of figure-level functions or withFacetGrid, and customize the resulting grid. - Add annotations and reference linesPlace text, arrows, reference lines, shaded ranges and value labels on a plot, in data coordinates or in coordinates relative to the axes.
- Save figures for reports and the webExport figures with
savefigin raster or vector formats, control size, resolution, margins and background, and save at the right moment in scripts, notebooks and loops.
- Arrange several plots in one figure
Matplotlib and Seaborn study guide
1Build plots with Matplotlib figures and axes
A plotting command needs somewhere to draw. Matplotlib makes that place explicit: a figure holds one or more plotting areas, and each area holds the marks and labels. Start with one area, then use the same objects to add lines, points, bars, and useful settings. Keeping a name for the figure and each plotting area makes it easier to change the right chart when a notebook grows or a figure contains several panels.
1.1 Set up a figure and its axes
Matplotlib is the standard plotting library for Python, and pandas and seaborn both draw with it. It is imported by convention as import matplotlib.pyplot as plt. In a Jupyter notebook a chart appears below the cell that draws it; in a script, plt.show() displays the open figures and, in a normal script, waits until their windows are closed.
A Figure is the whole canvas: the image that is shown or saved. An Axes is one plotting area inside the figure, with its own data, title, axis labels and limits; one figure can hold several. An Axis (singular) is one number line of an Axes, the x-axis or the y-axis, with its ticks and tick labels. Every visible item, such as a line, a marker, a text or a bar, is called an artist.
fig, ax = plt.subplots() creates a figure with one Axes and returns both objects. figsize=(width, height) sets the figure size in inches. The default is 6.4 × 4.8 inches at 100 dots per inch, so a default figure is 640 × 480 pixels on screen; plt.subplots(figsize=(8, 4)) makes a wider, flatter figure.
Matplotlib offers two styles. In the object-oriented style you call methods on explicit objects: ax.plot(...), ax.set_title(...). In the pyplot style, functions such as plt.plot(...) and plt.title(...) act on the "current" Axes, usually the one created last. Both draw the same charts; the object-oriented style is clearer as soon as a figure has more than one Axes or plotting code goes into a function, so this course uses it.
The two styles use different names for the same settings: plt.title, plt.xlabel and plt.xlim correspond to ax.set_title, ax.set_xlabel and ax.set_xlim. A frequent error is ax.title("Rainfall"): on an Axes, title is an attribute that holds a text object, so calling it raises TypeError: 'Text' object is not callable. Use ax.set_title("Rainfall").
months = [1, 2, 3, 4, 5, 6]; temp_c = [3, 5, 9, 13, 17, 20], then
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(months, temp_c)
ax.set_title("Mean monthly temperature")
ax.set_xlabel("Month")
ax.set_ylabel("Temperature (°C)")
.
Drawing calls add to an Axes; they do not replace what is there. A second ax.plot(...) on the same ax adds a second line to the same chart. To get a separate chart, create a new figure with another plt.subplots() call, or draw on a different Axes of a multi-panel figure.
pyplot keeps every figure it created open until it is closed with plt.close(fig) or plt.close("all"). Open figures use memory, and once more than 20 are open Matplotlib warns about it. Code that creates figures in a loop should close each figure after saving it.
Key Takeaways
- A figure is the canvas; an Axes is a plotting area.
- Call methods on the Axes that should receive the change.
- Drawing again on the same Axes adds artists.
- Close saved figures in loops.
1.2 Draw line and scatter plots
ax.plot(x, y) draws markers or straight line segments that join the points in the order they are given. A line plot suits a quantity measured along an ordered variable such as time or distance. With one argument, ax.plot(y), the x-values default to 0, 1, 2, ….
Because points are joined in the order given, unsorted x-values produce a zigzag that runs back and forth across the chart. For x = [3, 1, 2] the line goes from x = 3 back to x = 1 and then forward to x = 2. Sort the data by x first, for example with df.sort_values("x").
Style arguments: color (a name such as "black" or "tab:orange", or a hex code such as "#1f77b4"), linestyle ("-" solid, "--" dashed, ":" dotted, "-." dash-dot), linewidth, marker ("o" circle, "s" square, "^" triangle) and markersize. A short format string combines marker and line style: ax.plot(x, y, "o--") draws circles joined by a dashed line. Without a color, each new line takes the next color of the default cycle, which starts with "tab:blue" and "tab:orange".
Give each line a label and call ax.legend() to show a legend:
ax.plot(months, north, marker="o", label="North")
ax.plot(months, coast, linestyle="--", label="Coast")
ax.legend()
. If no artist has a label, ax.legend() draws nothing and Matplotlib warns that no artists with labels were found.
ax.scatter(x, y) draws one unconnected marker per observation and suits the relationship between two numeric variables. Example: daily temperature [12, 15, 18, 21, 24, 27, 30, 22] °C and bike rentals [180, 220, 260, 310, 330, 360, 340, 250]; ax.scatter(temp_c, rentals) shows rentals mostly rising with temperature, and the correlation of the two lists is about 0.92.
In ax.scatter, s sets each marker's area in points squared; the default is 36, the area of a marker 6 points wide. Doubling s doubles the area, so the marker's width grows only by a factor of about 1.4. s can be one number or one value per point, which lets marker size show a third variable.
c colors the points: one color for all, or one number per point mapped through a colormap such as cmap="viridis". Keep the returned object and add a color key: sc = ax.scatter(temp_c, rentals, c=rain_mm, cmap="viridis"); fig.colorbar(sc, ax=ax, label="Rain (mm)").
When many points overlap (overplotting), dense regions turn into solid blobs and hide how many points they contain. Lower the opacity with alpha=0.3, use smaller markers, or show density instead, for example with a hexagonal binning plot (df.plot.hexbin) or a two-dimensional histogram.
ax.plot(x, y, "o") with a marker and no line also draws points, and it is faster for very many points because all markers share one size and color. Use ax.scatter when size or color must vary from point to point.
Key Takeaways
- Lines connect points in their supplied order.
- Scatter size is area in points squared.
- A numeric color mapping needs a color bar.
- Use labels and a legend to identify series.
- Transparency helps reveal overlapping observations.
1.3 Compare categories with bar charts
ax.bar(categories, heights) draws one vertical bar per category; ax.barh(categories, widths) draws horizontal bars, which leave room for long category names. When the categories are strings, Matplotlib places them at equally spaced positions in the order given and uses them as tick labels. When they are numbers, the bars stand at those numeric positions on an ordinary numeric axis.
Bars start at a baseline of 0 by default, so a bar's length shows its value. The width argument is measured in x-axis units and is 0.8 by default. String categories are placed one unit apart, so the default leaves a gap of 0.2 between bars, and width=1 makes neighboring bars touch.
200 commuters were asked how they usually travel: car 84, bus 52, bike 38, walk 26. ax.barh(["Car", "Bus", "Bike", "Walk"], [84, 52, 38, 26]) draws four horizontal bars. The shares are 42%, 26%, 19% and 13%, which add up to 100%.
ax.barh puts the first category at the bottom of the axis, because the y-axis grows upward. To read a ranked list from the top, as in the commuting example, call ax.invert_yaxis(), or reverse both the categories and their values before plotting, so that each value stays with its category.
Grouped bars place several series side by side in each category. Use numeric positions and shift each series by half a bar width:
x = np.arange(3)
ax.bar(x - 0.2, north, width=0.4, label="North")
ax.bar(x + 0.2, coast, width=0.4, label="Coast")
ax.set_xticks(x, labels=["Jan", "Feb", "Mar"])
ax.legend()
. The North bars stand at −0.2, 0.8 and 1.8, the Coast bars at 0.2, 1.2 and 2.2, centered on each tick.
Stacked bars draw a second series on top of the first with bottom=: ax.bar(days, car); ax.bar(days, bus, bottom=car). If a day has 40 car trips and 25 bus trips, the bus segment runs from 40 to 65, and the total bar height is 65.
bars = ax.bar(...) returns a container of bars; ax.bar_label(bars) writes each value at the end of its bar. fmt controls the text: fmt="%.1f" writes 3.14 as "3.1", and fmt="{:.0f} km" writes it as "3 km".
Matplotlib draws bars in the order of the data, not in order of size. For a ranked chart, sort the values first, for example with sorted() on pairs or sort_values() in pandas. The yerr= argument adds error bars of a given length to each bar, and ax.errorbar(x, y, yerr=...) does the same for points.
A bar chart compares values of separate categories, so its bars stand apart with gaps. A histogram, by contrast, shows how one numeric variable is distributed over adjacent intervals, and its bars touch. Drawing measurements as one bar per row of data is a bar chart of raw values, not a histogram.
Key Takeaways
- Keep categories paired with their values when sorting.
- Grouped bars use offsets around category ticks.
- Stacked bars use cumulative bottoms.
- Bar labels belong to the returned container.
1.4 Label, scale and style a plot
Set text with ax.set_title("..."), ax.set_xlabel("...") and ax.set_ylabel("..."), or with one call: ax.set(title="Monthly rainfall", xlabel="Month", ylabel="Rainfall (mm)"). Put the unit in the axis label, for example "Rainfall (mm)" or "Distance (km)", so each tick value has a meaning.
Matplotlib chooses axis limits from the data plus a small margin. Set them yourself with ax.set_xlim(1, 12) and ax.set_ylim(0, 120), or fix one end only with ax.set_ylim(bottom=0). ax.set(xlim=(1, 12), ylim=(0, 120)) does the same in one call.
ax.set_xticks([1, 4, 7, 10]) chooses tick positions, and ax.set_xticks([1, 2, 3], labels=["Jan", "Feb", "Mar"]) also replaces their labels. Rotate crowded labels with ax.tick_params(axis="x", labelrotation=45). Tick formatters change how values are written: ax.yaxis.set_major_formatter(PercentFormatter(xmax=1, decimals=0)), with PercentFormatter imported from matplotlib.ticker, labels the value 0.25 as "25%".
ax.set_yscale("log") puts the y-axis on a logarithmic scale, where equal distances stand for equal ratios: the step from 10 to 100 is as long as the step from 100 to 1,000. It suits values that span several orders of magnitude, such as city populations. A logarithmic axis cannot show zero or negative values.
ax.legend() places the legend where it covers the fewest points (loc="best", the default for an Axes legend). Choose a spot with loc="upper left", add a heading with title="Station" and spread entries over columns with ncols=2. ax.legend(bbox_to_anchor=(1.02, 1), loc="upper left") puts the legend just outside the right edge of the plotting area.
ax.grid(True, alpha=0.3) adds light grid lines at the ticks; ax.grid(axis="y") draws only horizontal ones, which helps reading bar heights.
Style sheets change the default look of every later chart: plt.style.use("ggplot"). plt.style.available lists the installed styles; the seaborn-like styles that ship with Matplotlib are named, for example, "seaborn-v0_8-whitegrid". To apply a style to one block of code only, use with plt.style.context("ggplot"):.
Single default settings live in plt.rcParams, for example plt.rcParams["font.size"] = 12 or plt.rcParams["figure.figsize"] = (8, 4). They apply to figures created afterwards, and plt.rcdefaults() restores Matplotlib's defaults.
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot([1, 2, 3, 4], [62, 48, 55, 40], marker="o", label="North")
ax.set(title="Monthly rainfall, January to April", xlabel="Month", ylabel="Rainfall (mm)", ylim=(0, 70))
ax.set_xticks([1, 2, 3, 4], labels=["Jan", "Feb", "Mar", "Apr"])
ax.legend()
ax.grid(axis="y", alpha=0.3)
.
In a figure with several Axes, pyplot functions such as plt.title or plt.xlabel change only the current Axes, usually the one created last, so a title can land on the wrong panel. Calling the methods of the intended Axes, such as axes[0].set_title(...), avoids this.
Key Takeaways
- Tick formatting changes labels without changing values.
- Log scales show equal ratios as equal distances.
- Styles and rcParams set defaults for later charts.
2Plot data directly from pandas
A pandas table often already holds the labels and values a chart needs. Its plotting methods use those labels to build a Matplotlib chart, which you can then finish with the same methods as a hand-built plot. The important work often happens before drawing: deciding what one row represents, calculating the right summary, and arranging the table so that groups, series, and their order match the question.
2.1 Use the pandas plot method
Build the wide rainfall table with one column per station:
import pandas as pd
wide = pd.DataFrame({
"North": [62, 48, 55, 40],
"Coast": [110, 95, 80, 60],
"Valley": [20, 15, 30, 45]
}, index=pd.Index(["Jan", "Feb", "Mar", "Apr"], name="month"))
Every pandas Series and DataFrame has a .plot method that draws with Matplotlib and returns the Matplotlib Axes it drew on. df.plot(kind="bar") and df.plot.bar() are the same call. The kinds are "line" (the default), "bar", "barh", "hist", "box", "kde" or "density", "area", "pie", "scatter" and "hexbin".
By default the index becomes the x-axis and every numeric column becomes one line, with a legend built from the column names. A Series plots its values against its index. To choose columns, pass x="month" and y="rain_mm", or a list such as y=["North", "Coast"].
a table wide with the index Jan, Feb, Mar, Apr and the columns North [62, 48, 55, 40], Coast [110, 95, 80, 60] and Valley [20, 15, 30, 45], all in millimeters of rain. ax = wide.plot(marker="o", figsize=(8, 4), title="Monthly rainfall"); ax.set_ylabel("Rainfall (mm)") draws three labeled lines in one Axes and then sets the y-label with an ordinary Matplotlib method.
Common arguments of .plot: title, xlabel, ylabel, xlim, ylim, figsize, color, legend=False, rot (tick-label rotation in degrees), grid=True and logy=True. ax= draws into an existing Axes, for example one panel of plt.subplots(1, 2), instead of a new figure.
subplots=True draws each column in its own Axes and returns a NumPy array of Axes instead of a single one; layout=(2, 2) arranges them in a grid and sharey=True gives them one y-scale. For the three-station table, wide.plot(subplots=True, layout=(1, 3), sharey=True) gives one panel per station.
Scatter and hexbin plots exist only for DataFrames and need both x and y: days.plot.scatter(x="temp_c", y="rentals", c="rain_mm", colormap="viridis") colors the points by rain. A Series has no scatter plot, so s.plot.scatter() raises an error. df.plot.hist(bins=5) and df.plot.box() give a histogram and a box plot of each numeric column.
s.plot.pie() draws one wedge per value, sized by its share of the total. Since pie charts need a Series of nonnegative parts of one whole, a column of counts such as s.value_counts() is a typical input.
Bar plots in pandas treat the index as categories and place the bars at positions 0, 1, 2, … whatever the index values are. Line plots use the actual index values when they are numbers or dates. A pandas line and a pandas bar drawn on the same Axes therefore line up only when the line's x-values match the bar positions, as with a default index 0, 1, 2, …; for years such as 2020 to 2023 the line sits far to the right of the bars. Plot both against the same positions when combining them.
With a DatetimeIndex, .plot() puts dates on the x-axis and labels them automatically. Daily data are often clearer after resampling, for example to monthly means with s.resample("MS").mean().plot().
.plot draws numeric columns only. If numbers were read as text, for example because a column contains "12 mm", the column is skipped, and when no numeric data is left pandas raises TypeError: no numeric data to plot. Convert the column first, for example with pd.to_numeric(...).
Key Takeaways
- Pandas plots numeric data and uses the index by default.
- Pass an Axes with
ax=to reuse a panel. - Separate subplots return an array of Axes.
- Pandas bars and numeric lines use different position rules.
2.2 Shape the data before plotting
A chart draws the values that are in the table, so summarize first. Example: a long table rain with 12 rows, one per station and month, and the columns month, station and rain_mm. rain.groupby("station")["rain_mm"].mean() gives one mean per station: Coast 86.25 mm, North 51.25 mm and Valley 27.5 mm. Adding .plot.bar() draws three bars.
Plotting raw rows by mistake gives individual readings instead of the requested means: rain.plot.bar(x="station", y="rain_mm") draws 12 bars with repeated station names instead of three means.
Sort before plotting to get ranked bars: means.sort_values(ascending=False).plot.bar() puts the largest mean on the left. For a horizontal chart, means.sort_values().plot.barh() puts the largest at the top, because the first value is drawn at the bottom.
s.value_counts() counts how often each category occurs and lists the counts from most to least frequent. For the travel modes "bus", "car", "bus", "bike", "bus", "car" it gives bus 3, car 2, bike 1, ready for .plot.bar(). Use .sort_index() or .reindex([...]) when the categories have a natural order, such as days of the week.
A DataFrame in wide format, with one column per series, gives one line or bar color per column in pandas. Reshape a long table with rain.pivot(index="month", columns="station", values="rain_mm"). When some index and column pair occurs more than once, pivot raises an error; use pivot_table(..., aggfunc="mean") instead, or groupby(["month", "station"])["rain_mm"].mean().unstack().
pivot and pivot_table sort the new index, so string months come out in alphabetical order, Apr, Feb, Jan, Mar, and a line plot then runs through the months in the wrong order. Restore the real order with wide.reindex(["Jan", "Feb", "Mar", "Apr"]), or store the month as an ordered categorical or as a month number before pivoting.
On a wide table, wide.plot.bar() draws grouped bars, one group per index label and one color per column, while wide.plot.bar(stacked=True) stacks the columns. The transpose wide.T.plot.bar() swaps the roles: one group per station, one color per month.
For shares within each group, normalize a cross-tabulation: pd.crosstab(trips["region"], trips["mode"], normalize="index") divides every count by its row total, so each row sums to 1. If region N has two car trips and one bus trip, its row is car 0.67 and bus 0.33 (rounded). .plot.bar(stacked=True) then draws bars of equal total height that show each region's mix.
seaborn works best with long (tidy) data: one row per observation and one column per variable. wide.reset_index().melt(id_vars="month", var_name="station", value_name="rain_mm") turns the 4 × 3 wide table back into 12 rows with three columns, so station can be used for color or facets.
Key Takeaways
- Aggregate rows before plotting the required summary.
- A duplicate pivot pair requires an aggregation rule.
- Wide data suit series columns; long data suit semantic mappings.
- Restore meaningful category order after reshaping.
- Normalize each row to compare group shares.
3Make statistical plots with seaborn
Statistical charts connect table columns to visual features and often calculate a summary while drawing. Seaborn handles both jobs through named arguments. A short call can show groups in different colors, average repeated observations, or split a table into panels. Read these defaults carefully: a chart of individual observations and a chart of group means can start from the same rows but answer different questions.
3.1 Map columns to plot features in seaborn
seaborn is a plotting library built on Matplotlib that works directly with pandas DataFrames and computes common statistical summaries itself. It is imported by convention as import seaborn as sns. A typical call names the table and its columns: sns.scatterplot(data=days, x="temp_c", y="rentals").
Besides x and y, columns can be mapped to other features (semantic mappings): hue sets the color, style the marker or dash pattern, and size the marker size or line width. seaborn picks the colors and builds the legend itself. A categorical hue column gets distinct colors; a numeric one gets a gradient.
Each mapping needs a column in long format. With the long rainfall table, sns.lineplot(data=rain, x="month", y="rain_mm", hue="station", marker="o") draws one colored line per station with a legend. If hue names a column that is not in the table, seaborn raises a ValueError saying it could not interpret the value.
When x holds strings, seaborn places the categories in the order they first appear in the data, so months stored as "Jan", "Feb", "Mar" in that row order stay in calendar order. The categorical functions, such as barplot, boxplot and countplot, accept order=["Mon", "Tue", "Wed"] to set the category order, and hue_order sets the order of the hue groups in every function that has hue. lineplot has no order argument: to fix the order of month names there, make the x-column an ordered categorical, for example pd.Categorical(rain["month"], categories=["Jan", "Feb", "Mar", "Apr"], ordered=True).
Axes-level functions, such as scatterplot, lineplot, histplot, boxplot, barplot and heatmap, draw into one Matplotlib Axes, accept ax= to choose that Axes and return it. Figure-level functions, relplot, displot, catplot and lmplot, create their own figure, can split it into panels with col and row, and return a seaborn FacetGrid object.
Figure-level functions are sized per panel: height is each panel's height in inches and aspect its width-to-height ratio, so each panel is height × aspect inches wide; the defaults are 5 and 1. Because they create their own figure, they cannot draw into an existing Axes: relplot, displot and catplot warn and ignore ax=, and lmplot rejects it with a TypeError. They place the legend outside the panels.
sns.set_theme() switches all later charts, including plain Matplotlib ones, to seaborn's look: the "darkgrid" style, the "notebook" context and seaborn's default palette. style can be "darkgrid", "whitegrid", "dark", "white" or "ticks"; context can be "paper", "notebook", "talk" or "poster" and scales fonts and lines, for example from a 12-point base font for "notebook" to 18 points for "talk".
palette chooses the colors for hue, for example palette="colorblind", "Set2" or "viridis". In seaborn 0.13, passing palette without hue is deprecated; to color bars by category, set hue to the same column as x and add legend=False: sns.barplot(data=rain, x="station", y="rain_mm", hue="station", legend=False).
The result can be finished with Matplotlib:
ax = sns.scatterplot(...)
ax.set(xlabel="Temperature (°C)", ylabel="Rentals per day", title="Rentals and temperature")
. For a figure-level result, use the grid's methods, such as g.set_axis_labels("Temperature (°C)", "Rentals per day"), and g.figure.suptitle("...") for an overall title.
Key Takeaways
- Semantic mappings connect columns to visual features.
- Axes-level functions accept an existing Axes.
- Figure-level functions create and size their own panels.
- Themes affect later charts across both libraries.
3.2 Show relationships between numeric variables
sns.scatterplot(data=days, x="temp_c", y="rentals", hue="weekday", size="rain_mm") draws one point per row, colored and sized by further columns. Combining hue and style with the same column gives each group a distinct color and marker, which still reads in grayscale.
sns.lineplot aggregates by default: when several rows share an x-value, it draws the line through the mean of their y-values and shades a band for the 95% confidence interval of that mean, computed by bootstrapping. Worked example: at x = 1 the y-values are 10, 12 and 14, at x = 2 they are 20, 21 and 25; the line passes through 12 and 22.
The default band shows the uncertainty of each mean, not the range of the data. errorbar="sd" shades one standard deviation above and below the mean instead, errorbar=None removes the band, and estimator=None (together with units=) draws each individual series without averaging. With one row per x-value there is nothing to average and no band.
sns.regplot(data=days, x="temp_c", y="rentals") draws a scatter plot with a least-squares straight line and a shaded 95% confidence band for that line; order=2 fits a quadratic curve instead. sns.lmplot does the same as a figure-level function, so it can also split the data by hue, col or row.
A fitted line summarizes the data with the model you chose. A straight line can be drawn through curved data, so look at the points around the line before trusting it. A strong trend shows that the variables are associated in this data, without establishing a cause.
sns.pairplot(df) draws a grid with a scatter plot for every pair of numeric columns and each column's distribution on the diagonal: histograms by default, density curves when hue is set. Four numeric columns give a 4 × 4 grid of 16 panels. corner=True keeps only the lower triangle, and vars=[...] limits the columns. It returns a PairGrid.
sns.jointplot(data=days, x="temp_c", y="rentals") draws a scatter plot with each variable's histogram along its edges; kind="hex" replaces the points with hexagonal bins, which helps with large data sets.
sns.relplot(..., kind="scatter") and sns.relplot(..., kind="line") are the figure-level versions of scatterplot and lineplot; scatter is the default kind. Use them when the same relationship should be shown in separate panels for each group.
sns.regplot(data=days, x="temp_c", y="rentals") shows rentals rising from 180 at 12 °C toward about 350 at the warmest days, with a correlation of about 0.92. The 30 °C day has fewer rentals (340) than the 27 °C day (360), so the points flatten at the top; a quadratic fit (order=2) can be compared with the straight line.
Key Takeaways
- Default seaborn lines average repeated observations.
- A confidence band describes uncertainty in an estimate.
- Use units and no estimator to preserve individual trajectories.
- Inspect points as well as fitted lines.
- Pair plots compare variables and their distributions.
3.3 Compare groups with categorical plots
seaborn's categorical functions place each category at positions 0, 1, 2, … on the categorical axis. Even numbers are treated as labels: years 2020, 2021 and 2023 are drawn equally spaced, with no gap for 2022. native_scale=True keeps a numeric axis with real spacing.
sns.barplot(data=rain, x="station", y="rain_mm") draws one bar per station whose height is the mean of its rows, with an error bar for the 95% confidence interval of that mean. With the rainfall table the bars are Coast 86.25, North 51.25 and Valley 27.5 mm. estimator="median" or estimator="sum" changes the summary; errorbar="sd" or errorbar=None changes or removes the error bars.
sns.countplot(data=trips, x="mode") counts the rows in each category and needs no numeric column; sns.barplot summarizes a numeric column. Use countplot for "how many", barplot for "how much on average".
sns.pointplot shows each group's mean as a point with an error bar and joins the points of each hue group with a line. It makes changes across categories easy to compare, for example the mean rainfall of three stations in each of four months.
A bar of means hides the spread and the number of observations: a mean of 3 rows looks as solid as a mean of 300. Showing the data is often better: sns.stripplot draws every observation as a point with a little random jitter, and sns.swarmplot arranges the points so that they do not overlap. A strip plot can be drawn over a box plot or bar chart on the same Axes.
With hue, bar, box and violin plots place the groups side by side within each category (dodging) by default. Point, strip and swarm plots draw the groups at the same position unless dodge=True is set, so their points can overlap. Set the category order with order=[...] and the group order with hue_order=[...].
Orientation follows the data: put the categories on the y-axis and the numbers on the x-axis, sns.barplot(data=rain, x="rain_mm", y="station"), to get horizontal bars with readable long labels. orient="y" (or "h") states this explicitly.
sns.catplot is the figure-level function for all categorical plots. Its kind can be "strip" (the default), "swarm", "box", "violin", "boxen", "point", "bar" or "count", and col or row splits the plot into panels.
Key Takeaways
- Count plots count rows; bar plots summarize numeric values.
- Strip and swarm plots reveal observations.
- Dodging separates hue groups within a category.
4Visualize distributions and grids of values
A distribution chart describes a collection of measurements, while a heatmap describes a table through color. These plots compress many values, so their settings affect what you can read. Bin edges decide which observations are counted together; quartiles and whiskers summarize different parts of a sample; a color scale maps numbers to shades. Work through those calculations before interpreting the finished image.
4.1 Show a distribution with histograms and density curves
A histogram splits the range of a numeric variable into adjacent intervals called bins and draws a bar over each bin whose height is the number of observations in it. It shows the shape of a distribution: where values are centered, how spread out they are, whether they are skewed, and gaps or unusual values.
counts, edges, patches = ax.hist(data, bins=5) draws the histogram and returns the count in each bin and the bin edges. With an integer, the bins have equal width from the minimum to the maximum; the default is 10 bins. Each bin includes its left edge and excludes its right edge, except the last bin, which includes both.
20 commute times in minutes: 12, 15, 18, 20, 22, 22, 25, 27, 28, 30, 31, 33, 35, 38, 40, 42, 45, 50, 55, 62. With bins=5 the edges are 12, 22, 32, 42, 52 and 62, and the counts are 4, 7, 4, 3 and 2. The value 22 falls in the second bin because bins exclude their right edge, and 62 counts in the last bin because the last bin includes it.
The number and position of bins change the picture. The same 20 times with edges [10, 20, 30, 40, 50, 60, 70] give the counts 3, 6, 5, 3, 2 and 1. Too few bins hide the shape; too many make a jagged chart with many near-empty bins. Try several settings, and prefer round bin edges that readers can interpret, such as 10-minute steps.
density=True in Matplotlib, or stat="density" in seaborn, scales the bars so that their total area is 1: each height is count ÷ (n × bin width). For the commute data with five 10-minute bins, the second bar's height is 7 ÷ (20 × 10) = 0.035 per minute. A density height is not a probability; height × bin width is the proportion of observations in the bin.
sns.histplot(data=commutes, x="minutes") draws a histogram with seaborn; bins and binwidth control the bins. stat chooses the height: "count" (default), "probability" or "proportion" (heights sum to 1), "percent" (heights sum to 100) and "density" (area 1). For the five commute bins, "probability" gives 0.2, 0.35, 0.2, 0.15 and 0.1.
To compare groups, sns.histplot(data=commutes, x="minutes", hue="mode") overlays one histogram per group; multiple="stack" stacks them, multiple="dodge" places them side by side, and element="step" draws outlines that overlap more readably. Groups of different sizes are compared fairly with stat="density", common_norm=False, which scales each group to an area of 1.
A kernel density estimate (KDE) is a smooth curve that estimates the shape of the distribution; the area under it is 1. sns.kdeplot(data=commutes, x="minutes") draws it, and sns.histplot(..., kde=True) adds it to a histogram. bw_adjust above 1 makes the curve smoother, below 1 more detailed.
A KDE curve extends beyond the smallest and largest values, so it can show density below 0 minutes for times that cannot be negative. cut=0 stops the curve at the data limits. For small samples, the curve can suggest a shape that the data do not support.
In pandas, commutes["minutes"].plot.hist(bins=5, edgecolor="white") draws a histogram (default 10 bins), and df.hist() draws one histogram per numeric column in a grid. In seaborn, sns.displot is the figure-level version: kind="hist" (default), kind="kde" or kind="ecdf", where an ECDF shows for every value the proportion of observations at or below it.
Key Takeaways
- Histogram boundaries determine bin membership.
- Density uses area to represent a share.
- A KDE smooths a distribution and depends on bandwidth.
- Normalize groups independently to compare shapes.
4.2 Summarize and compare distributions with box plots
A box plot summarizes a distribution: the box runs from the first quartile (Q1) to the third quartile (Q3), a line inside marks the median, whiskers extend toward the smallest and largest typical values, and points beyond the whiskers are drawn one by one as possible outliers (fliers). The interquartile range IQR = Q3 − Q1 is the box's length and spans the middle half of the data.
By default Matplotlib, pandas and seaborn use whis=1.5: the fences are Q1 − 1.5 × IQR and Q3 + 1.5 × IQR. Each whisker ends at the most extreme data value inside its fence, not at the fence itself, and every value outside the fences is drawn as a separate point.
11 delivery times in minutes: 14, 16, 17, 18, 19, 20, 21, 22, 24, 25, 41. Matplotlib computes Q1 = 17.5, median = 20 and Q3 = 23, so IQR = 5.5. The fences are 17.5 − 8.25 = 9.25 and 23 + 8.25 = 31.25. The lower whisker ends at 14, the upper whisker at 25, and 41 is drawn as an outlier point.
Quartiles depend on the method. The plotting libraries use linear interpolation between sorted values (the NumPy and pandas default), which gives 17.5 and 23 above. The hand method of taking the medians of the lower and upper halves without the overall median gives Q1 = 17 and Q3 = 24 for the same data. Both are valid; small differences between a hand calculation and a plot come from this.
Draw the summary with the library you are using: ax.boxplot([north, coast], tick_labels=["North", "Coast"]) in Matplotlib (the argument was called labels before version 3.9); rain.boxplot(column="rain_mm", by="station") in pandas, which also adds the overall title "Boxplot grouped by station"; sns.boxplot(data=rain, x="station", y="rain_mm") in seaborn, with hue for a second grouping. whis=(0, 100) draws the whiskers to the minimum and maximum, and showfliers=False hides the outlier points.
Side-by-side box plots compare groups at a glance: compare the medians, the box lengths (spread of the middle half) and the overlap of the boxes. A median close to one end of the box, or one whisker much longer than the other, points to a skewed distribution.
A box shows only five numbers and the outliers. It does not show the number of observations, and very different shapes can have the same box: a distribution with two separate peaks and one with a single peak can share Q1, the median and Q3. Overlaying the points, sns.stripplot(data=rain, x="station", y="rain_mm", color="black", alpha=0.5), or using a violin plot reveals this.
sns.violinplot(data=rain, x="station", y="rain_mm") draws a mirrored density curve for each group with a small box plot inside (inner="box", the default); inner="quart" marks the quartiles with lines instead. With a two-level hue, split=True puts one group on each side of the violin. Like a KDE, the violin can extend beyond the data; cut=0 stops it at the observed minimum and maximum.
A point flagged by the 1.5 × IQR rule is unusual relative to the rest of the data, a reason to investigate the value. Check it: it may be a data-entry mistake or a real, important value, such as one delivery delayed by a road closure. Removing points only because the plot marks them distorts the analysis.
Key Takeaways
- Box length is the IQR.
- Whiskers end at observations inside the fences.
- Quartile methods can differ.
- Hidden fliers remain in the underlying data.
- Points and violins reveal information a box hides.
4.3 Display a grid of values as a heatmap
A heatmap shows a table of numbers as a grid of colored cells: rows and columns are two categories (or binned variables), and each cell's color stands for its value. It suits patterns in two-way tables, such as visitors by day and time of day, and correlation matrices.
sns.heatmap(table) takes a wide table, uses its index as row labels and its columns as column labels, and adds a color bar as the key. The table usually comes from pivot_table: visits.pivot_table(index="day", columns="slot", values="visitors", aggfunc="mean").
a museum counts visitors per half-day. Saturday morning 120 and 140, Saturday afternoon 200; Sunday morning 90, Sunday afternoon 160; Monday morning 40, Monday afternoon 70 and 90. The pivot table of means is Mon 40 and 80, Sat 130 and 200, Sun 90 and 160 (AM and PM). sns.heatmap(pt, annot=True, fmt=".0f", cmap="Blues") shows the busiest cell, Saturday afternoon, as the darkest. The pivot sorts the days alphabetically (Mon, Sat, Sun); pt.reindex(["Sat", "Sun", "Mon"]) would restore a chosen order.
annot=True writes each value in its cell, and fmt sets the number format. The default ".2g" keeps two significant digits, so 43.25 is shown as "43" and 1234 as "1.2e+03". Use fmt=".0f" for whole numbers or fmt=".2f" for two decimals; fmt="d" works only for integer data and raises a ValueError for floats.
A correlation heatmap starts from corr = df.corr(numeric_only=True), a square, symmetric table whose defined coefficients range from −1 to 1. Nonconstant numeric columns have 1 on the diagonal; constant columns have undefined correlations. In pandas 2 and later, numeric_only defaults to False, so df.corr() raises an error when the table has a text column. Draw it with sns.heatmap(corr, annot=True, fmt=".2f", cmap="vlag", vmin=-1, vmax=1, center=0).
eight days with temperature (°C), bike rentals, rain (mm) and wind (km/h). The correlation matrix gives temperature and rentals 0.92, rentals and rain −0.82, rain and wind 0.75, and temperature and wind −0.38. With the "vlag" colormap, the strong positive pair shows deep red, the strong negative pair deep blue, and the weak temperature–wind pair a pale tint close to the white center.
Without vmin and vmax, the colors stretch from the smallest to the largest value in that table, so the same color can mean different values in two heatmaps. Fix the limits to compare heatmaps, and use vmin=-1, vmax=1 for correlations. A sequential colormap such as "rocket" (seaborn's default), "viridis" or "Blues" suits values that run from low to high; a diverging colormap such as "vlag", "coolwarm" or "RdBu_r", with center at the meaningful midpoint, suits values above and below a reference such as 0. With center set and no cmap, seaborn uses the diverging "icefire".
Useful options: linewidths=0.5 draws thin lines between cells, square=True makes the cells square, cbar_kws={"label": "Visitors"} labels the color bar, and missing values (NaN) are left blank. mask=np.triu(np.ones_like(corr, dtype=bool)) hides the upper triangle and the diagonal of a correlation matrix, which repeat the lower triangle.
In Matplotlib, im = ax.imshow(values, cmap="viridis"); fig.colorbar(im, ax=ax) draws a heatmap of a 2-D array, with row 0 at the top; "viridis" is Matplotlib's default colormap. imshow labels the axes with row and column numbers, so set names yourself: ax.set_xticks(range(len(cols)), labels=cols). ax.pcolormesh is an alternative when cells have unequal sizes.
A correlation coefficient measures the strength of a linear association only. A correlation near 0 does not rule out a strong curved relationship, and one outlier can raise or lower a coefficient a lot. Check the matching scatter plots, for example with a pair plot, before reading a correlation heatmap.
Key Takeaways
- Heatmap colors represent table values.
- Fix color limits for comparisons across plots.
- Correlation scales span −1 to 1.
- Check scatter plots before interpreting correlations.
5Build multi-panel figures, annotate them and export them
A useful figure often needs several views of the same data. Panels let you compare groups, annotations connect a claim to a particular value, and export settings turn the result into an image that fits its destination. Keep control of the figure throughout this process. The object that holds your plots also holds their overall size and is the most reliable way to save exactly the result you have built.
5.1 Arrange several plots in one figure
Small multiples are a set of charts of the same kind, one for each subset of the data, drawn with the same scales and placed side by side so that the subsets can be compared. In Matplotlib they are built as a grid of Axes in one figure.
fig, axes = plt.subplots(2, 3, figsize=(12, 6)) returns a figure and a 2 × 3 NumPy array of Axes. axes[0, 0] is the top-left panel and axes[1, 2] the bottom-right. With one row or one column the array is one-dimensional, so plt.subplots(1, 3) gives axes[0], axes[1] and axes[2]. axes.flat runs through all panels row by row.
The array itself cannot draw: axes.plot(...) raises AttributeError: 'numpy.ndarray' object has no attribute 'plot'. Index one panel first, as in axes[0, 1].plot(...).
sharex=True and sharey=True give all panels the same x- or y-limits, so equal heights mean equal values in every panel and only the outer panels show tick labels. Without shared axes, each panel scales to its own data, and a small range can look as large as a big one.
fig, axes = plt.subplots(2, 2, sharey=True, layout="constrained"), then for ax, (name, grp) in zip(axes.flat, rain.groupby("station")): ax.plot(grp["month_num"], grp["rain_mm"], marker="o"); ax.set_title(name). The three stations fill three of the four panels; axes[1, 1].axis("off") hides the empty fourth one.
Labels of neighboring panels can overlap. layout="constrained" in plt.subplots or plt.figure adjusts the spacing automatically; fig.tight_layout(), called after drawing, is the older alternative. fig.suptitle("Monthly rainfall by station") adds one title for the whole figure, and fig.supxlabel(...) and fig.supylabel(...) add shared axis labels.
Panels of different sizes: plt.subplots(1, 2, gridspec_kw={"width_ratios": [2, 1]}) makes the left panel twice as wide as the right. fig, axd = plt.subplot_mosaic([["top", "top"], ["left", "right"]]) returns a dictionary of Axes, here a wide panel axd["top"] above two smaller ones.
pandas and seaborn axes-level plots can fill chosen panels with ax=: wide["North"].plot(ax=axes[0, 0]) or sns.histplot(data=commutes, x="minutes", ax=axes[0, 1]). This is how plots from all three libraries are combined in one figure.
Key Takeaways
- Index an Axes array before drawing.
- Shared scales support panel comparisons.
- Constrained layout makes room for labels.
5.2 Facet data with seaborn
Faceting draws the same plot once for each level of a categorical variable, each in its own panel, with shared axes. seaborn's figure-level functions do this with col= (panels side by side), row= (panels stacked) and col_wrap= (start a new row after that many columns).
sns.relplot(data=rain, x="month_num", y="rain_mm", col="station", col_wrap=2, kind="line", height=3, aspect=1.5) draws three panels, two in the first row and one in the second. Each panel is 3 inches tall and 3 × 1.5 = 4.5 inches wide, so the figure is about 9 × 6 inches.
row and col together make a grid with one panel per combination of levels: two seasons as rows and three stations as columns give 2 × 3 = 6 panels. col_wrap cannot be combined with row.
The same arguments work for every kind of plot: sns.displot(data=commutes, x="minutes", col="mode") gives one histogram per travel mode, and sns.catplot(data=rain, x="station", y="rain_mm", col="season", kind="box") gives box plots per season.
Panels share their x- and y-axes by default, so heights can be compared across panels. Letting each panel scale to its own data shows detail within small groups but makes comparisons across panels misleading at a glance. With relplot and displot, pass facet_kws={"sharey": False}; catplot takes sharey=False directly, and passing it in facet_kws as well raises a TypeError.
sns.FacetGrid builds the grid directly and then draws into every panel: g = sns.FacetGrid(commutes, col="mode", height=3); g.map_dataframe(sns.histplot, x="minutes"); g.add_legend(). g.map(plt.scatter, "temp_c", "rentals") passes the named columns as positional arguments to a plotting function.
Customize the grid with its methods: g.set_titles("{col_name}") sets panel titles from the level names, g.set_axis_labels("Month", "Rainfall (mm)") labels all axes, and g.set(ylim=(0, 120)) applies an Axes setting to every panel. g.axes_dict["Coast"] or g.axes.flat reaches single Axes, and g.figure.suptitle("Rainfall by station", y=1.02) adds an overall title above the panels.
hue and col both split by a variable. hue overlays the groups in one Axes, which makes direct comparison easy for a few groups; col separates them, which stays readable when lines or points would overlap or when there are many groups. Both can be used at once, for example col="station", hue="season".
Figure-level functions do not draw into an existing Axes (relplot, displot and catplot ignore ax= with a warning, lmplot rejects it) and do not take Matplotlib's figsize (passing it raises an error): size the grid with height and aspect. To place a faceted plot next to other plots in one Matplotlib figure, build the panels with plt.subplots and axes-level functions instead.
Key Takeaways
- Facets split rows into panels by group.
- Panel dimensions use height and aspect.
- Grid methods customize the whole display.
- Figure-level charts cannot fill an existing Axes.
5.3 Add annotations and reference lines
ax.text(x, y, "text") writes text at a point given in data coordinates, the same units as the plotted values. ha ("left", "center", "right") and va ("top", "center", "bottom") align the text to that point; fontsize and color style it.
With transform=ax.transAxes, positions are fractions of the Axes instead: (0, 0) is the lower-left corner and (1, 1) the upper-right. ax.text(0.02, 0.95, "n = 20", transform=ax.transAxes, va="top") keeps a note in the top-left corner whatever the data and limits are. Text in data coordinates moves when the limits change and can end up outside the plotting area.
ax.annotate("Wettest month", xy=(1, 110), xytext=(2, 115), arrowprops=dict(arrowstyle="->")) writes the text at xytext and draws an arrow to the point xy; both are in data coordinates by default. textcoords="offset points" makes xytext an offset from xy in points, for example xytext=(10, 10) for 10 points right and up.
Reference lines span the whole Axes: ax.axhline(50, color="gray", linestyle="--", label="Target") draws a horizontal line at y = 50, and ax.axvline(...) a vertical one. ax.axvspan(6, 8, alpha=0.2) shades the range from x = 6 to x = 8, for example a holiday period. ax.hlines(y, xmin, xmax) draws horizontal lines over a limited range.
on the histogram of the 20 commute times, the mean is 650 ÷ 20 = 32.5 minutes and the median 30.5 minutes. ax.axvline(32.5, color="black", linestyle="--"); ax.text(33.5, 6.5, "mean 32.5 min") marks the mean, which lies to the right of the median because a few long commutes pull it up.
Bar values: ax.bar_label(bars, fmt="%.0f") labels the bars returned by ax.bar. For bars drawn by pandas or seaborn, the bars are stored in ax.containers, so ax.bar_label(ax.containers[0]) labels the first series. padding=3 moves the labels 3 points away from the bar ends.
Shapes come from matplotlib.patches: ax.add_patch(Rectangle((x, y), width, height, fill=False, edgecolor="red")) outlines a region in data coordinates, and Circle and Polygon work the same way.
Text can be computed from the data with f-strings, so labels stay correct when the data change: ax.text(..., f"mean {commutes['minutes'].mean():.1f} min"). Annotations are drawn on top of the data; place them in empty areas and keep them few, so they point out what matters.
Key Takeaways
- Data coordinates anchor text to measurements.
- Axes coordinates anchor text to the panel.
- An annotation separates the target point from the text position.
- Compute changing labels from the data.
5.4 Save figures for reports and the web
fig.savefig("rainfall.png") saves the figure; the file extension sets the format: PNG and JPEG are raster images made of pixels, while SVG and PDF are vector graphics made of shapes. format="pdf" sets the format explicitly.
Raster images have a fixed number of pixels and blur when enlarged; vector files stay sharp at any size and suit reports, print and documents. PNG is a good raster format for charts on the web and in slides. JPEG compression is lossy and blurs thin lines and text, so it is a poor choice for charts.
Pixel size = figure size in inches × dpi (dots per inch). fig, ax = plt.subplots(figsize=(8, 5)) saved with dpi=150 gives 8 × 150 by 5 × 150 = 1200 × 750 pixels. Without dpi, savefig uses the figure's own dpi, 100 by default, so the same figure becomes 800 × 500 pixels. For vector formats, dpi affects only embedded raster parts.
Text and line widths are set in points (1 point = 1/72 inch), relative to the figure's inches. Raising dpi adds pixels without changing how large the text looks relative to the chart; enlarging figsize instead makes the text relatively smaller. Choose figsize for the final display size and dpi for the sharpness.
bbox_inches="tight" trims empty margins and includes items that stick out of the figure, such as a legend placed outside the Axes; the saved image is then a little smaller or larger than figure size × dpi. pad_inches=0.1 sets the margin kept around it.
transparent=True saves without a background, which suits slides with a colored background; facecolor="white" forces a white background, which matters when a dark notebook theme would otherwise leak into the file.
plt.savefig saves the current figure, so call it before a blocking plt.show(): once the window is closed, pyplot no longer tracks that figure, and a later plt.savefig(...) saves a new, empty one. In a Jupyter notebook, pyplot also lets go of the figure at the end of the cell, so plt.savefig in a later cell saves an empty image. Keeping the figure in a variable avoids both problems: fig.savefig(...) still works after plt.show() or in a later notebook cell.
seaborn results are saved the same way: an axes-level plot through its figure, ax.figure.savefig("box.png", dpi=200), and a figure-level grid with g.savefig("facets.png", dpi=200), which uses tight bounding boxes by default.
for name, grp in rain.groupby("station"):
grp = grp.sort_values("month_num")
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(grp["month_num"], grp["rain_mm"], marker="o")
ax.set(title=name, xlabel="Month", ylabel="Rainfall (mm)")
fig.savefig(f"rain_{name}.png", dpi=150, bbox_inches="tight")
plt.close(fig)
. Closing each figure keeps memory use low and avoids the warning about more than 20 open figures.
Key Takeaways
- Save the figure object you built.
- Inches times dpi gives uncropped raster dimensions.
- Tight bounding boxes change the exported boundary.
- Vector output preserves shapes.
- Close each figure after batch export.
Matplotlib and Seaborn examples
1Build plots with Matplotlib figures and axes
15 steps
- 1IntroductionBuild one chart at a time
A plotting command needs somewhere to draw. Matplotlib makes that place explicit: a figure holds one or more plotting areas, and each area holds the marks and labels. Start with one area, then use the same objects to add lines, points, bars, and useful settings. Keeping a name for the figure and each plotting area makes it easier to change the right chart when a notebook grows or a figure contains several panels.
- 2ExplanationKeep hold of the canvas
A Figure holds the full image. An Axes holds one chart and its coordinate axes. Create both explicitly so later commands have a clear destination.
import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(7, 4))The width and height are in inches. The variable
axpoints to the plotting area insidefig; it is the object that will receive the line and its labels. - 3ExampleDraw into that area
Add a short temperature series to the Axes you just created. Each pair becomes a point on the line.
ax.plot([1, 2, 3], [8, 11, 10]) ax.set(title="Daily temperature", xlabel="Day", ylabel="Temperature (°C)")Calling
ax.plotagain would add another line to this same area. In a script,plt.show()displays the figure; a notebook normally displays it below the cell. - 4ChallengeFix a title call
The call
ax.title("Daily temperature")raises a "Text object is not callable" error. How should it be changed?---reveal---
Use
ax.set_title("Daily temperature"). The Axes attributetitleis already a text object;set_titleis the method that changes it. The pyplot equivalent isplt.title(...), but it works on whichever Axes is current. - 5ExplanationKeep the observations paired
A line connects points in input order. If the distance column runs 4, 1, 3 km, the line moves backward before it moves forward. Sort complete observations by distance, preserving each elevation beside its original distance.
route = route.sort_values("distance_km") ax.plot(route["distance_km"], route["elevation_m"])A scatter plot needs no connecting order because each marker is a separate observation.
- 6ExampleGive each series a key
Line styles and marker styles can identify series as well as color. Add a label when drawing, then ask the Axes to build its legend.
ax.plot([1, 2, 3], [4, 8, 7], "o--", label="East") ax.plot([1, 2, 3], [6, 7, 9], "s-", label="West") ax.legend(title="Site")For a scatter plot, numeric
cvalues need a color bar to explain their gradient. Loweralphawhen overlapping points hide the sample. - 7ChallengePredict a size change
A scatter marker uses
s=25. You change it tos=100with the same marker shape. How does its width change?---reveal---
The width doubles. The area has grown by a factor of four, and linear dimensions grow by the square root of that factor. Scatter
sis measured in points squared; reading it as a width would incorrectly predict a fourfold increase. - 8ExplanationPlace categories and values
Bars use separate category positions. Start with one number per category; strings become equally spaced labels in the supplied order. A horizontal chart gives long names room.
fig, ax = plt.subplots() bars = ax.barh(["Bus", "Rail", "Walk"], [48, 37, 25]) ax.invert_yaxis() ax.bar_label(bars)The inversion puts the first category at the top. Reversing names alone would attach the values to the wrong categories.
- 9ExampleBuild a stack from its base
Suppose weekday trips consist of 32 bus trips and 18 rail trips. The second bar segment must start at the top of the first.
ax.bar(["Weekday"], [32], label="Bus") ax.bar(["Weekday"], [18], bottom=[32], label="Rail") ax.legend()The rail segment has height 18 and ends at 50. If a third mode is added, its baseline is the cumulative total of the two segments already drawn.
- 10ChallengeLocate the grouped bars
Two bars each have width 0.4 and should be centered together at category position 1. Where should their centers be?
---reveal---
Use 0.8 and 1.2, half a width either side of the tick. Their inner edges meet at 1, and the pair is centered on the category. Using positions 1 and 1.4 would make a neighboring pair but shift its overall center away from the category tick.
- 11ExplanationSeparate positions from labels
Ticks mark coordinate positions; their labels are the text shown there. Supply both together when replacing numeric month positions with names.
ax.set_xticks([1, 2, 3], labels=["Jan", "Feb", "Mar"]) ax.set_ylabel("Rainfall (mm)") ax.grid(axis="y", alpha=0.3)A formatter changes how existing numbers are displayed. It does not rescale the data, so check whether percentages are stored as fractions or as numbers from 0 to 100.
- 12ExampleChoose a scale with meaning
Equal steps on a linear scale mean equal differences. Equal steps on a logarithmic scale mean equal ratios.
ax.set_yscale("log")Values 5, 50, and 500 have equal spacing because each is ten times the previous value. Zero and negative values cannot appear on an ordinary log scale. Styles control appearance separately: use a style context when a different look should apply to only one block of code.
- 13ChallengeLabel the intended panel
A two-panel figure needs a title on its first panel, but
plt.title(...)changes the second one. How can the code target the first panel reliably?---reveal---
Call
axes[0].set_title(...). The named Axes is explicit, so the current pyplot state no longer decides the destination. This matters whenever a figure has several panels or a helper function creates or selects another Axes. - 14ChallengeExplain a numeric color key
A scatter chart uses point color for rain depth and marker area for rental volume. Its legend names stations but gives no numeric color key. What does the figure still need?
---reveal---
Add a color bar attached to the scatter result and label it "Rainfall (mm)". The station legend explains groups, while the color bar explains numeric shades. Keeping the returned scatter object lets
fig.colorbaruse the same mapping as the plotted points. - 15RecapCheck the result you built
A chart has a canvas, a plotting area, and artists attached to that area. Check that each series keeps the intended observation pairs, that the labels identify variables and units, and that numeric color mappings have a key. When a change affects the wrong place, inspect which Axes received the method call. When a shape looks wrong, inspect the order and coordinates of the data before changing its style.
2Plot data directly from pandas
9 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionLet the table supply the chart
- 2ExplanationUse the table labels
- 3ExampleChoose numeric columns
- 4ChallengeAlign lines and bars
- 5ExplanationSummarize before drawing
- 6ExampleChoose a shape for the next library
- 7ChallengeRepair a repeated pivot pair
- 8ChallengeSummarize and reshape together
- 9RecapCheck the result you built
3Make statistical plots with seaborn
12 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionRead what the function computes
- 2ExplanationMap variables to features
- 3ExamplePick the owner of the layout
- 4ChallengeFit the function to the layout
- 5ExplanationRead the line estimator
- 6ExamplePreserve each trajectory
- 7ChallengeInterpret a narrow band
- 8ExplanationCount rows or summarize values
- 9ExampleShow the observations
- 10ChallengeChoose the summary
- 11ChallengeExplain the changing bars
- 12RecapCheck the result you built
4Visualize distributions and grids of values
12 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionRead compressed data carefully
- 2ExplanationCount a bin correctly
- 3ExampleConvert count to density
- 4ChallengeCompare unequal-width bins
- 5ExplanationBuild fences from the box
- 6ExampleFind the observed whisker end
- 7ChallengeLook inside an identical box
- 8ExplanationStart from the numeric grid
- 9ExampleFix the color meaning
- 10ChallengeDiagnose matching shades
- 11ChallengeCheck two summaries together
- 12RecapCheck the result you built
5Build multi-panel figures, annotate them and export them
16 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionPrepare a figure for sharing
- 2ExplanationCreate addressable panels
- 3ExampleFill the grid with groups
- 4ChallengeKeep scale comparisons fair
- 5ExplanationLet seaborn split the table
- 6ExampleCustomize the grid as a grid
- 7ChallengeChange sharing through the right argument
- 8ExplanationChoose what anchors the text
- 9ExampleAttach a label to a peak
- 10ChallengeKeep a filtered label accurate
- 11ExplanationChoose dimensions before pixels
- 12ExampleSave the retained figure
- 13ChallengeCombine layout and export decisions
- 14ChallengeFinish a reproducible batch
- 15TipInspect the saved result
- 16RecapCheck the result you built
Matplotlib and Seaborn practice questions
Set 1: Read plots and check the basics10 Practice Questions
Question 1 of 10
Show answerHide answer
Question 2 of 10
df.plot(y="depth_mm") return when it draws one panel?Show answerHide answer
set_ylabel. It does not return the whole Figure; reach that through the Axes' figure attribute when you need to save the image.Question 3 of 10
plt.subplots(2, 3), how do you select the bottom-right panel?Show answerHide answer
axes[1, 2]. Using 2 and 3 counts from one and points beyond this array's bounds.Question 4 of 10
Show answerHide answer
xmax=1, one whole unit means 100%, so 0.32 is formatted as 32%. Setting xmax=100 would be appropriate for values already stored on a 0–100 scale; with fractions it would label 0.32 as 0.32% instead.Question 5 of 10
Show answerHide answer
errorbar="sd"; it has a different interpretation.Question 6 of 10
Show answerHide answer
Question 7 of 10
Show answerHide answer
catplot for categorical facets and pass sharey=False directly. Unlike relplot and displot, catplot supplies this argument to its grid itself, so also providing it through facet_kws creates a duplicate-argument error. A single axes-level box plot cannot create facets through col.Question 8 of 10
Show answerHide answer
Question 9 of 10
Show answerHide answer
.0f displays zero decimal places and works with means stored as floats. The integer format d requires integer data and can raise an error even for 26.0. Formatting the displayed text does not change the table's values.Question 10 of 10
Show answerHide answer
Set 2: Apply plotting choices to new data10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
ax.scatter(x, y, s=64), what does 64 specify?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
[0, 5, 10, 15], which bin contains the value 5?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
[4, 1, 3] km and paired elevations [90, 40, 75] m. What order should a forward-moving line use?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 3: Connect tables, summaries, and charts10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
ax.plot(...) call do to an existing Axes?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
fig in one cell. A later cell needs to save that exact chart after pyplot has released it. What call is reliable?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
plt.title("Depth") after creating a second figure. The title should belong to the first figure's Axes, stored as first_ax. How should the title be set?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
xy and xytext be with textcoords="offset points"?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 4: Diagnose chart settings and comparisons10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
mode column. What produces the number of trips per mode?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
height_cm, mass_kg, and species. How can a scatter plot identify species by both color and marker shape?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 5: Solve plotting and export problems10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
Aggregation
Annotation
Artist
Aspect ratio
Axes
Axes array
Axes coordinates
Axes-level function
Axis
B
Bandwidth
Bar container
Baseline
Bin edge
C
Color bar
Color limits
Column wrapping
Confidence interval
Constrained layout
Correlation matrix
Count plot
D
Data coordinates
DatetimeIndex
Diverging colormap
Dodging
Dpi
E
ECDF
Estimator
F
Facet
FacetGrid
Fence
Figure
Figure reference
Figure-level function
Flier
G
Grouped bar chart
H
Heatmap
Histogram
Hue
I
Index
Interquartile range
K
Kernel density estimate
L
Line plot
Logarithmic scale
Long format
M
Marker area
Melt
N
Native scale
Numeric column
O
Object-oriented interface
Overplotting
P
Pair plot
Pivot
Plot accessor
Probability density
Q
Quartile
R
Raster image
Reference line
Regression line
Row normalization
S
Scatter plot
Semantic mapping
Sequential colormap
Shared axis
Small multiples
Stacked bar chart
Standard deviation band
Strip plot
Style sheet
Subplot mosaic
Swarm plot
T
Theme
Tick
Tick formatter
Tight bounding box
V
Vector graphic
Violin plot
W
Whisker
Wide format
About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.