DataLens

Plotly Express vs Graph Objects: when to use which

Understand Plotly's two layers, what a figure really is, and the tidy data shape that makes Express charts one-liners.

Level: Foundations · 8 min read

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

The idea

Plotly is the charting library this course uses for interactive charts: every chart can be hovered, zoomed and downloaded, in a notebook, a Streamlit app or a web page. It comes with two ways to build a chart:

They aren't rivals. Express is built on top of Graph Objects: every px function returns a Graph Objects figure. So the real skill is knowing when Express is enough, and how to reach into the figure when it isn't.

The same chart, twice

Revenue per category, the Express way:

import pandas as pd
import plotly.express as px

by_category = pd.DataFrame({
    "category": ["Electronics", "Kitchen", "Coffee", "Home", "Stationery"],
    "revenue": [5113000, 2610000, 1805000, 1560000, 748000],
})

fig = px.bar(by_category, x="category", y="revenue", title="Completed revenue by category")
fig.show()

The Graph Objects way:

import plotly.graph_objects as go

fig = go.Figure(
    data=[go.Bar(x=by_category["category"], y=by_category["revenue"])],
    layout=go.Layout(title="Completed revenue by category"),
)
fig.show()

Press Try it on both: the charts look nearly identical. The difference is in how you describe them:

What a figure is made of

Every Plotly figure, however it was made, has the same two parts:

fig = px.bar(by_category, x="category", y="revenue")
print(type(fig).__name__)
print(len(fig.data), fig.data[0].type)
print(fig.layout.xaxis.title.text, "|", fig.layout.yaxis.title.text)
Figure
1 bar
category | revenue
PartHoldsExample
fig.dataa list of traces: the marks (bars, lines, dots) and their valuesone bar trace
fig.layouteverything around the marks: title, axes, legend, colours, sizeaxis titles category, revenue
Key idea

A Plotly figure is just a structured description (data + layout) that the browser draws. Express writes that description for you; Graph Objects lets you write it yourself. Either way, you can change any part afterwards.

That last point is the most practical one. Start with Express, then adjust the figure with methods like update_layout, update_traces and add_trace:

fig = px.bar(by_category, x="category", y="revenue", title="Completed revenue by category")
fig.add_trace(go.Scatter(x=by_category["category"], y=[2000000] * 5,
                         mode="lines", name="Target"))
fig.update_layout(yaxis_title="Revenue (Rp)")
fig.show()

An Express bar chart, plus a Graph Objects line for the target, plus a layout tweak. This "Express first, then customise" style is how most professional Plotly code looks.

Express loves tidy data

Express is quickest when your DataFrame is tidy (also called long): one row per observation, one column per variable. Compare two shapes of the same numbers:

wide = pd.DataFrame({
    "month": ["Jan", "Feb", "Mar"],
    "web": [1200, 1100, 900],
    "app": [500, 650, 640],
})
long = wide.melt(id_vars="month", var_name="channel", value_name="revenue")
print(wide)
print(long)
  month   web  app
0   Jan  1200  500
1   Feb  1100  650
2   Mar   900  640
  month channel  revenue
0   Jan     web     1200
1   Feb     web     1100
2   Mar     web      900
3   Jan     app      500
4   Feb     app      650
5   Mar     app      640

With the long version, one Express call draws a line per channel, coloured and labelled automatically:

fig = px.line(long, x="month", y="revenue", color="channel", markers=True)
fig.show()

color="channel" says "split the data by this column". That only works when "channel" is a column, which is why melt (from the pandas reshaping module) is the most common preparation step before an Express chart.

When to use which

SituationUse
Exploring data, standard charts (bar, line, scatter, histogram, box)Express
Data is in a tidy DataFrameExpress
A standard chart that needs a few tweaksExpress, then update_* / add_trace
Mixing unusual traces, building a chart trace by traceGraph Objects
Values aren't in a DataFrame, or need precise control of every traceGraph Objects
Subplots of different chart types, dropdowns, custom interactivityGraph Objects (later modules)

Start with Express nearly every time. Reach for Graph Objects when Express can't express it.

Why this matters for clean code

A chart is code too, and the same rules apply: the shortest clear version wins. One px call on a tidy DataFrame is easier to read and change than forty lines of trace building. Keep data preparation (filtering, melting, aggregating) separate from charting, so each step does one thing.

Recap

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

Next: Your first chart: line and scatter with px →