AlgoMaster Logo

Data Visualization

Low Priority21 min readUpdated September 21, 2026
Listen to this chapter
Unlock Audio

Knowing matplotlib's API is half the job; the other half is knowing what chart to draw, what colors to use, and what mistakes to avoid. This lesson is about visualization as a discipline: how to pick the right chart for the data, how to make charts readable for everyone (including colorblind viewers), how to annotate for storytelling, what other tools exist beyond matplotlib, and the common mistakes that turn a chart into a lie. The lesson ends with the standard workflow: explore freely, refine the most useful chart, present it cleanly.

Choosing the Right Chart

Most data-visualization problems collapse into one of five questions, and each has a small set of correct chart types. Pick the question first, then the chart.

QuestionGood chartsAvoid
What's the distribution of one variable?Histogram, KDE, box plot, violin plotPie chart
How do groups compare on one measure?Bar chart, grouped bar, dot plotStacked area, pie
Are two variables related?Scatter, line (if x is ordered), hexbinBar, pie
How is something composed of parts?Stacked bar (few categories), treemapPie (more than 3 slices), 3D anything
How does a measure trend over time?Line, area, slope chartBar (loses continuity), pie

The wrong chart isn't just ugly; it actively misleads. A pie chart for ten categories asks the reader to compare angles, which humans are bad at. A bar chart of the same data uses lengths, which are easy to compare precisely. Same data, very different reader experience.

Distribution: Histogram, KDE, Box, Violin

A histogram bins values and shows counts. It's the right starting point for one numeric variable. A kernel density estimate (KDE) smooths the histogram into a continuous curve, which works better with enough data to make the smoothing meaningful when comparing distributions on the same chart.

The histogram shows the full shape, including the lower mode in the "before" data. The box plot shows the summary: median (line), interquartile range (box), whiskers (typical extent), outliers (dots). For two groups, the box plot is more compact and makes the median shift visible. For more than two groups, box plots scale better than overlapping histograms.

A violin plot is a box plot crossed with a KDE: it shows both the summary statistics and the full shape of the distribution. Seaborn's violinplot is the standard way to draw one.

Comparison: Bar, Grouped Bar, Dot

A bar chart compares one measure across discrete categories. This section is about when to escalate beyond the basic bar.

Use a grouped bar when each category has two or three sub-measures. Side by side bars, one color per sub-measure, one position per category.

Use a dot plot when many categories make the bar's full length more visual noise than information. A dot plot puts a single dot at each category's value, sometimes with a thin connector to a baseline. Cleaner than a tall stack of long bars.

The dot plot shows each category's revenue as a horizontal line with a dot at the end. Sorting by value makes the ranking readable at a glance. invert_yaxis() puts the highest-revenue category at the top, matching top-to-bottom scanning order. The result is far less ink than a full bar chart and reads faster.

Always sort categorical bars or dots by value (descending), not alphabetically. Alphabetical order conveys nothing about the data; value order makes the ranking the visual story.

Relationship: Scatter, Line, Hexbin

For two numeric variables, a scatter plot is the default. If the x-axis is ordered (time, distance), a line connecting the points implies the sequence.

Hexbin fits when a scatter would have so many points they overlap into one black smear. Each hexagon counts how many observations fall into it, and the color encodes the count.

The scatter, even with alpha=0.1 to soften overlap, still saturates in the dense center. The hexbin shows the density structure clearly: a dense ridge along the diagonal correlation, fading out at the edges. For dense scatters, switch.

Composition: Stacked Bar, Treemap

A stacked bar shows how each total is composed of parts. It works for a small number of parts (3-5) when the totals themselves are meaningful.

bottom= shifts each next layer to start where the previous one ended. The result shows total revenue per month (bar heights) and the contribution of each category (segment heights). Stacked bars work here; pie charts for the same data would force the reader to compare angles between months separately, which is much harder.

The limit is the number of categories. With more than 5-6 segments per bar, the smaller ones become unreadable. Switch to a treemap (seaborn or plotly have decent ones) or a small-multiples grid of separate bar charts.

A line chart for time series is the default. Area charts add a filled region between the line and the x-axis, useful when the magnitude (not just the trajectory) is the point.

Slope charts show the change between two points in time for several groups: one short line per group, two x positions ("before" and "after"), with the lines colored by direction. Compact, ranking-friendly, and effective when the changes are mixed (some up, some down).

Each product gets one slope line: green if it grew, red if it shrank. The category labels sit on the left at the starting position; final values sit on the right at the ending position. The result is a dense summary of "what changed" that a stacked or grouped bar chart can't match for impact.

Color and Accessibility

Color does a lot of work in a chart: it distinguishes categories, encodes magnitude, draws attention. Used badly, it makes the chart unreadable for the 8% of men and 0.5% of women with some form of red-green colorblindness, or anyone viewing the chart on a low-contrast display.

Categorical vs Sequential vs Diverging

Three kinds of color schemes, each for a different job.

Categorical palettes distinguish unrelated groups. Each color is distinct; no implied ordering. Use these for "category", "department", "country". Five to ten distinct hues works; beyond that, viewers can't keep them straight.

Sequential palettes encode a numeric magnitude. Light to dark, or one hue at varying lightness. Use for "revenue", "population density", "score". viridis, plasma, inferno, and cividis are matplotlib's perceptually uniform sequential palettes (equal data differences look like equal color differences, including for colorblind viewers).

Diverging palettes encode a value with a meaningful midpoint. Color drifts from one hue at the low end, through a neutral midpoint, to another hue at the high end. Use for "change from baseline", "z-score", "profit/loss". RdBu, coolwarm, BrBG are common diverging palettes.

The three palettes encode different things. Sequential (left) shows magnitude growing in one direction. Diverging (middle) shows departure from zero in two directions, with white as the neutral midpoint. Categorical (right) gives each integer value an unrelated distinct color.

Using a sequential palette for unrelated categories (or a categorical palette for ordered data) is a common mistake. The reader's eye reads "darker means higher" from a sequential palette regardless of intent.

Colorblind-Friendly Choices

Roughly 1 in 12 men and 1 in 200 women have some form of color vision deficiency, most commonly red-green. Two design choices help:

Pick palettes that don't rely on red vs green. viridis, cividis, and plasma are designed to be perceptually uniform and to remain distinguishable when simulated under deuteranopia (the common red-green deficiency). The tab10 categorical palette is also reasonably accessible.

Use redundancy. Don't rely on color alone. In a multi-line chart, use different line styles (solid, dashed, dotted) in addition to colors. In a scatter plot, use different markers (circle, square, triangle). The chart should still be readable in grayscale.

Each series has a unique combination of color, line style, and marker. Print the chart in grayscale and the three series remain distinguishable by the line style and marker.

Test by converting to grayscale (any image editor) or by using a colorblind simulator (browser extensions exist). If two series collapse into the same gray or look identical to a colorblind simulation, change the styling.

Annotations and Storytelling

A chart shows data; an annotated chart tells a story. The key data points are pointed to directly. Three annotation techniques carry most of the weight: highlight, text, and reference lines.

Highlighting

Color one element to draw attention, mute the rest. Sometimes called the "spotlight" or "preattentive attention" technique.

Every bar is gray except the top one. The reader's eye goes to the colored bar first. The title states the takeaway explicitly. This is the simplest and most reliable storytelling technique: most things gray, one thing bright.

Text Annotations

ax.annotate(text, xy=(x, y), ...) puts text on the chart at a specific data point, often with an arrow. Use for callouts: "spike caused by Black Friday", "outlier: faulty sensor", "average baseline".

xy= is the data point the annotation refers to. xytext= is where the label text goes. arrowprops= styles the arrow that connects them. The reader sees the spike, sees the label, and knows why November is different.

Reference Lines

A horizontal or vertical line at a meaningful value: a target, a budget, a regulatory limit, the mean. ax.axhline(y) and ax.axvline(x) are the calls.

The horizontal dashed line is the target. fill_between shades the region between the line and the target, with one color for above-target days and another for below. The reader sees the trend, the target, and the overall performance at a glance.

Each annotation adds visual weight. Stick to one or two per chart for the main takeaway; more than that and the chart starts to look like a textbook diagram, which slows the reader down rather than helping.

Tools Tour

Matplotlib is the foundation. Most other Python visualization libraries either build on it or compete with it.

Seaborn

Seaborn is a high-level wrapper around matplotlib focused on statistical graphics. It fits when polished defaults for distributions, categorical comparisons, and faceted layouts are wanted.

One call (sns.boxplot(...)) handles the entire box plot including the per-category coloring. Seaborn understands DataFrames natively: column names go as strings for x, y, and hue, and it figures out the rest. The result is a matplotlib axes, so further customization through the usual ax.set_xxx(...) methods works as expected.

Seaborn is well-suited to:

  • Statistical plots: kdeplot, histplot, displot, boxplot, violinplot, swarmplot.
  • Categorical comparisons: barplot, pointplot, countplot.
  • Multi-panel "facets": relplot, catplot, displot with col= and row= parameters automatically create one panel per category.
  • Heatmaps: heatmap with annotations.

The downside is reduced control over fine details and a different idiom (named arguments instead of separate function calls). For a publication figure where every tick mark matters, drop to matplotlib.

Plotly

Plotly produces interactive HTML output: hover tooltips, zoom, pan, click-to-toggle series. Use it for notebook exploration and web dashboards.

Plotly Express (plotly.express as px) is the high-level API: one function call per chart type. The result is an interactive HTML chart that renders in notebooks, dashboards, or standalone HTML files. The full plotly API (plotly.graph_objects) gives finer control at the cost of more code.

Plotly fits for:

  • Interactive exploration in notebooks (hover to see values).
  • Dashboards built with Dash, Streamlit, or plain HTML.
  • Plots that benefit from zooming into dense regions.

The downside: HTML output is heavier than a PNG, doesn't print well, and isn't great for static reports. Static export to PNG is possible (fig.write_image) but requires the kaleido package.

Bokeh

Similar idea to plotly: interactive, web-targeted. Bokeh's API is slightly more verbose and gives more control over building custom interactive applications. Used in some BI tools and dashboards; in pure data-exploration notebooks, plotly is usually picked first.

Altair

Altair is a declarative wrapper around the Vega-Lite specification. The chart is described in terms of data columns and encodings: "this column to x, that to y, that to color." Altair figures out scales, axes, and legends from the data types.

The declarative style is concise for standard statistical charts and forces a clean separation between data, encoding, and styling. Less flexible for highly custom layouts (where matplotlib's procedural API wins).

Plotnine (ggplot for Python)

Plotnine ports R's ggplot2 API to Python: the grammar-of-graphics approach where geoms layer on top of mapped aesthetics. Familiar to anyone who has used ggplot2 in R. Concise for statistical visualizations; the learning curve is real for first-timers.

Choosing

Use caseFirst choice
Static publication figure with precise controlmatplotlib
Statistical summary with sensible defaultsseaborn
Interactive exploration in a notebookplotly
Web dashboards (custom widgets, callbacks)plotly + Dash or bokeh
Grammar-of-graphics fans coming from Rplotnine
Declarative, JSON-spec-based outputaltair

Most working data scientists use matplotlib and seaborn together for static work and plotly for interactive. The other libraries fill niches but aren't required.

Common Mistakes

A chart can lie even with truthful data. These are the mistakes that show up most often.

Truncated Y-Axis

Cutting off the y-axis exaggerates differences. A bar chart of revenues [100, 102, 104, 106] with the y-axis starting at 99 looks like dramatic growth; with the y-axis starting at 0 it looks like a flat line, which is the truth.

Same data, two stories. The rule for bar charts is always start the y-axis at zero, because the bar lengths are what the reader compares, and length is only meaningful from a zero baseline. Line charts are slightly more forgiving (the slope matters more than absolute height), but starting from zero is the safer default unless there's a specific reason not to.

Pie Chart with Many Slices

Already covered. Pie charts work for 2-3 slices and an "X is a majority" story. For anything else, a bar chart is faster and more precise.

Too Much Information on One Chart

The classic "dashboard in a single chart" mistake: ten lines, three y-axes, color encoding plus marker encoding plus size encoding. The reader gives up. The fix is small multiples: a grid of simpler charts, each showing one thing clearly. Pandas, seaborn, plotly, and matplotlib all support faceted layouts.

col="category" produces one panel per category. Each panel is simple, focused, and easy to read. Together they show the same data as a multi-line chart but without the visual collision.

3D When It Isn't Needed

3D bar charts and 3D pie charts almost always reduce readability. The third dimension adds perspective distortion that makes values harder to compare. Unless the data has three meaningful axes that all need to be shown at once, stay in 2D.

Wrong Chart Type

Using a line chart when the x-axis is unordered categorical: the line implies a sequence that doesn't exist. Using a bar chart when the x-axis is continuous: the gaps between bars hide the real spacing. Always check that the chart type matches the data type of the axis.

Misleading Annotations

Annotating only the points that support the story while ignoring contrary evidence. Showing a trend line on noisy data that doesn't actually have a trend. Cherry-picking the time range to hide the parts that don't fit. These are dishonest but common; spotting them in a draft means redrawing the chart with the full data.

The Workflow: Explore, Refine, Present

Visualization is a workflow with three phases, each with different goals and chart styles.

Explore

The exploration phase is about understanding what's in the data. Speed matters; polish doesn't. Default colors are fine, axis labels can be sloppy, titles are often missing. The goal is to look at many views quickly: distributions of each column, scatters between pairs, group-by aggregates, time trends. df.describe(), df.hist(), sns.pairplot(), and df.plot(kind="...") are the tools. Dozens of charts get made and most get thrown away.

One line, three histograms. Not pretty, but immediately informative.

Refine

When a chart tells a story worth keeping, refine it. Fix the title, label the axes properly, pick deliberate colors (brand palette, accessible choices), set the figure size, hide the spines, add annotations for the takeaway. This is where most of the matplotlib customization comes in. The chart now has to communicate to others, not just to the author.

Present

A presented chart is built around a specific audience and message. The title states the takeaway, not just the topic ("Revenue grew 40% in Q4, driven by holiday electronics" instead of "Monthly Revenue"). Annotations highlight the key data points. Colors match the document or brand. Output format matches the medium (PNG for slides, PDF for reports, SVG for web, HTML for interactive dashboards).

The same data goes through all three phases and looks different at the end of each. A scatter drawn during exploration in 30 seconds becomes a polished, annotated chart with a takeaway-driven title for the final report.

A practical pattern: keep the exploration code messy and disposable. When a chart is worth refining, copy it to a new cell or script and start the polish there. A presentation-quality chart on the first attempt isn't realistic because the story isn't clear yet.

Quiz

Data Visualization Quiz

10 quizzes