DataLens

Axis titles, labels, and hover text

Replace raw column names with human labels, format numbers as Rupiah and percentages, and make hover boxes say exactly what matters.

Level: Foundations · 7 min read

Practise this module

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

The idea

A default chart speaks in column names: revenue, order_date, qty_sold. Your audience speaks in "Revenue (Rp)", "Order date" and "Units sold". The difference between a chart people glance at and one they trust is often just labels, number formats and a good hover box. All three take a line or two.

import pandas as pd
import plotly.express as px

sales = pd.read_csv("sales.csv")
completed = sales[sales["status"] == "completed"]
monthly = (completed.assign(month=completed["order_date"].str[:7])
           .groupby("month", as_index=False)
           .agg(revenue=("revenue", "sum"), orders=("order_id", "nunique")))
print(monthly)
     month  revenue  orders
0  2024-01  1693000       6
1  2024-02  1752000       4
2  2024-03  1027000       2
3  2024-04  3987000       7
4  2024-05  2552000       5
5  2024-06   825000       2

labels=: rename columns for the chart

Express uses column names as axis titles, legend titles and hover labels. labels= maps column names to display names, everywhere at once:

fig = px.bar(monthly, x="month", y="revenue",
             labels={"month": "Month", "revenue": "Revenue (Rp)"},
             title="Completed revenue per month")
fig.show()
print(fig.layout.xaxis.title.text, "|", fig.layout.yaxis.title.text)
Month | Revenue (Rp)

Prefer labels= over renaming DataFrame columns just for a chart: your data keeps code-friendly names, and the chart gets human ones.

You can also set titles after creating the figure, which works for Graph Objects figures too:

fig.update_xaxes(title_text="Month (2024)")
fig.update_yaxes(title_text="Revenue (Rupiah)")

Number formats on the axis

Plotly writes 4,000,000 as 4M by default. For a Rupiah audience, you might want separators and a prefix. tickformat uses the same mini-language as the text_auto formats in the previous module:

Format3987000 shows asUse for
","3,987,000full numbers with separators
".2s"4.0Mcompact
".1%"(of 0.237) 23.7%shares stored as fractions
"d"3987000plain integers
fig = px.bar(monthly, x="month", y="revenue",
             labels={"month": "Month", "revenue": "Revenue"})
fig.update_yaxes(tickprefix="Rp ", tickformat=",")
fig.show()
print(repr(fig.layout.yaxis.tickprefix), repr(fig.layout.yaxis.tickformat))
'Rp ' ','

tickprefix and ticksuffix add text before or after each tick label, like "Rp " or " units".

Tip

Plotly uses an English-style comma as the thousands separator. Indonesian convention is a dot (Rp 3.987.000). To switch the whole chart, set fig.update_layout(separators=",."): the first character is the decimal mark, the second the thousands separator.

Category order

Axis categories appear in the order of the data, or alphabetically. Force an order with category_orders:

by_channel = completed.groupby("channel", as_index=False)["revenue"].sum()
fig = px.bar(by_channel, x="channel", y="revenue",
             category_orders={"channel": ["web", "app"]})
fig.show()

Useful for natural orders that aren't alphabetical: weekdays, sizes (S, M, L), ratings, or "Q1, Q2, Q3, Q4".

Hover text: what appears on mouse-over

The hover box is where interactive charts beat static ones. Express shows the x and y values by default. Add more context with hover_name (bold title) and hover_data (extra rows):

fig = px.scatter(monthly, x="orders", y="revenue", hover_name="month",
                 hover_data={"orders": True, "revenue": ":,"},
                 labels={"orders": "Orders", "revenue": "Revenue (Rp)"})
fig.show()

hovertemplate: full control

For complete control, write the hover text yourself with hovertemplate. Values are inserted with %{...}:

fig = px.bar(monthly, x="month", y="revenue")
fig.update_traces(hovertemplate="<b>%{x}</b><br>Revenue: Rp %{y:,}<extra></extra>")
fig.show()
print(fig.data[0].hovertemplate)
<b>%{x}</b><br>Revenue: Rp %{y:,}<extra></extra>

To show a column that isn't on an axis, pass it as custom_data in Express and reference it as %{customdata[0]}:

fig = px.bar(monthly, x="month", y="revenue", custom_data=["orders"])
fig.update_traces(hovertemplate="%{x}: Rp %{y:,} from %{customdata[0]} orders<extra></extra>")
fig.show()

Why this matters for clean code

Labels are the user interface of your analysis. A chart titled "revenue by month" with an axis reading 4M invites "4 million what?" A clean chart answers the questions before they're asked: what is measured, in which unit, over what period. Keep the formatting in a few explicit lines after the px call, so the "what" (data) and the "how it looks" (labels) stay separate and easy to change.

Recap

Practise this module

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

Next: Color, size, and symbol encoding →