Revenue Tree Gallery¶
The revenue tree decomposes a period-on-period change in revenue into the multiplicative drivers that produced it: customers, visits per customer, items per visit, and price per item. Each node shows the value in both periods, the absolute and percentage change, and the contribution that driver made to the total revenue change.
Revenue trees excel at:
- Performance diagnostics: pinpoint which lever moved when revenue shifts year-on-year
- Strategic planning: identify the largest leverage points before setting next year's targets
- Store and region comparisons: spot whether two units share a story or differ in what's driving them
- Pricing vs. volume attribution: separate how much of a revenue lift came from charging more vs. selling more units
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from openretailscience.analysis.revenue_tree import RevenueTree
Default Tree¶
When unit_quantity is present in the data, draw_tree() renders the full eight-node tree, splitting the spend-per-visit branch further into units per visit and price per unit. Cells whose driver helped revenue render green, cells that hurt revenue render red, and cells with sub-1% movement render gray, so the eye lands on the levers that actually moved.
Pass value_labels=(current, previous) to label the two periods. The first entry labels the period 2 column, the second labels period 1.
rt = RevenueTree(
df=transactions,
period_col="period",
p1_value="2023",
p2_value="2024",
)
rt.draw_tree(value_labels=("2024", "2023"))
plt.show()
Custom Node Labels¶
Each node label is a separate parameter on draw_tree(), so you can rename any subset to match your team's vocabulary without touching the underlying analysis. The shape of the tree stays the same. Useful when "Revenue" should read as "Sales", "Customers" should read as "Shoppers", or "Visit" maps to operational language like "Trip", "Order", or "Session".
rt.draw_tree(
value_labels=("This Year", "Last Year"),
unit_spend_label="Sales",
customer_id_label="Shoppers",
spend_per_customer_label="Sales / Shopper",
transactions_per_customer_label="Trips / Shopper",
spend_per_transaction_label="Sales / Trip",
units_per_transaction_label="Items / Trip",
price_per_unit_label="Price / Item",
)
plt.show()
Five-Node Tree¶
When unit_quantity isn't in the input data, draw_tree() renders a five-node tree that stops at spend per visit. Use this shape when the upstream system only tracks transaction totals (no item-level data), or when the units-versus-price split isn't relevant to the audience and a simpler tree communicates faster.
rt_no_quantity = RevenueTree(
df=transactions_no_quantity,
period_col="period",
p1_value="2023",
p2_value="2024",
)
rt_no_quantity.draw_tree(value_labels=("2024", "2023"))
plt.show()
Grouped Trees with group_col and row_index¶
Pass group_col to compute one tree per group (region, store, segment, channel). The class still holds a single DataFrame, but each row corresponds to one group's KPIs. Render any group's tree by passing row_index=N to draw_tree(): index 0 is the first group, 1 is the second, and so on. Useful for side-by-side comparisons where two units of the business have moved in opposite directions.
rt_regional = RevenueTree(
df=regional_transactions,
period_col="period",
p1_value="2023",
p2_value="2024",
group_col="region",
)
# Index 0 -> North (sorted alphabetically). Revenue +15.8%, driven by new shoppers and higher prices.
rt_regional.draw_tree(row_index=0, value_labels=("North 2024", "North 2023"))
plt.show()
Switching to row_index=1 renders the same tree shape for the next group, surfacing a different story without any change to the analysis. South customers held flat but visited less often, and prices didn't move to compensate.
rt_regional.draw_tree(row_index=1, value_labels=("South 2024", "South 2023"))
plt.show()