Dual-Axis Effect with ggdeck()¶
The ggdeck() function allows for the overlaying of multiple plots into a single view.
By combining a primary plot with one or more blank plots—each configured with its own scale—you can achieve a dual-axis (or multi-axis) effect.
In [1]:
from lets_plot import *
import pandas as pd
In [2]:
LetsPlot.setup_html()
In [3]:
data = pd.read_csv("https://raw.githubusercontent.com/JetBrains/lets-plot-docs/refs/heads/master/data/delhi_climate.csv")
data['date'] = pd.to_datetime(data['date'])
data.head()
Out[3]:
| date | meantemp | humidity | wind_speed | meanpressure | |
|---|---|---|---|---|---|
| 0 | 2013-01-01 | 10.000000 | 84.500000 | 0.000000 | 1015.666667 |
| 1 | 2013-01-02 | 7.400000 | 92.000000 | 2.980000 | 1017.800000 |
| 2 | 2013-01-03 | 7.166667 | 87.000000 | 4.633333 | 1018.666667 |
| 3 | 2013-01-04 | 8.666667 | 71.333333 | 1.233333 | 1017.166667 |
| 4 | 2013-01-05 | 6.000000 | 86.833333 | 3.700000 | 1016.500000 |
In [4]:
base_plot = (
ggplot(data) + geom_line(aes('date', 'meantemp', color='meantemp'))
+ scale_color_gradient2(low='#0571b0', mid='#f7f7f7', high='#ca0020', midpoint=30)
+ labs(y='°C', color='°C')
)
base_plot
Out[4]:
1. Secondary Axis - Fahrenheits¶
In [5]:
# Add Fahrenheit column via linear transformation
data['meantemp_f'] = (data['meantemp'] * 9/5) + 32
data.head(3)
Out[5]:
| date | meantemp | humidity | wind_speed | meanpressure | meantemp_f | |
|---|---|---|---|---|---|---|
| 0 | 2013-01-01 | 10.000000 | 84.5 | 0.000000 | 1015.666667 | 50.00 |
| 1 | 2013-01-02 | 7.400000 | 92.0 | 2.980000 | 1017.800000 | 45.32 |
| 2 | 2013-01-03 | 7.166667 | 87.0 | 4.633333 | 1018.666667 | 44.90 |
In [6]:
# Helper to synchronize the Y-axis aesthetic (line, ticks, and text)
def axis_y_color(color):
return theme(
axis_line_y=element_line(color=color),
axis_ticks_y=element_line(color=color),
axis_text_y=element_text(color=color),
axis_title_y=element_text(color=color),
)
In [7]:
# Create an empty plot with the y-axis on the right
fahrenheit_plot = (
ggplot(data)
+ geom_blank(aes('date', 'meantemp_f')) # <-- Scale mapping without rendering data
+ labs(y='°F')
+ axis_y_color('blue')
+ scale_y_continuous(position='right')
)
fahrenheit_plot + ggsize(400, 200)
Out[7]:
In [8]:
# Compose the 'base' plot and the 'Fahrenheit' plot in a deck
ggdeck(
[
base_plot,
fahrenheit_plot
]
) + \
ggsize(800, 400) + \
ggtitle('Delhi Mean Temperature') + \
theme_classic() + \
theme(plot_title=element_text(hjust=0.5, size=21))
Out[8]:
2. Show More Temperature Scales - Kelvin and Rankine¶
This section demonstrates an alternative approach by anchoring secondary scales using boundary coordinates to ensure precise axis synchronization with minimal data overhead.
In [9]:
x_min = data['date'].min()
x_max = data['date'].max()
c_min = data['meantemp'].min()
c_max = data['meantemp'].max()
# Perform linear transformations for Kelvin and Rankine boundaries
f_min, f_max = (c_min * 9 / 5) + 32, (c_max * 9 / 5) + 32
k_min, k_max = (c_min + 273.15), (c_max + 273.15)
r_min, r_max = (f_min + 459.67), (f_max + 459.67)
In [10]:
# Option 1: create a lightweight dictionary for scale mapping.
bounds_data = dict(
x = [x_min, x_max],
y_kelvin = [k_min, k_max],
)
kelvin_plot = (
ggplot(bounds_data)
+ geom_blank(aes('x', 'y_kelvin'))
+ labs(y='K')
+ axis_y_color('green')
+ scale_y_continuous(position='right')
)
kelvin_plot + ggsize(400, 200)
Out[10]:
In [11]:
# Option 2: set the scale limits.
rankine_plot = (
ggplot()
+ geom_blank()
+ labs(y='°R')
+ axis_y_color('red')
+ scale_y_continuous(position='right', limits=[r_min, r_max])
+ scale_x_datetime(limits=[x_min, x_max])
)
rankine_plot + ggsize(400, 200)
Out[11]:
In [12]:
# Assemble the multi-axis deck.
# Use 'panel_inset' to create horizontal spacing between adjacent Y-axes.
ggdeck(
[
base_plot,
fahrenheit_plot,
kelvin_plot
+ theme(panel_inset=[0, 3, 0, 0]), # <-- 3px offset for K axis
rankine_plot
+ theme(panel_inset=[0, 3, 0, 0]), # <-- 3px offset for °R axis
]
) + \
ggsize(800, 400) + \
ggtitle('Delhi Mean Temperature') + \
theme_classic() + \
theme(
plot_title=element_text(hjust=0.5, size=21),
panel_grid=element_line(), # <-- Add gridlines to the Classic theme
) + ggtb()
Out[12]:
3. Layout Optimization — Plot Subtitles Instead of Y-Axis Titles¶
In [13]:
ggdeck(
[
base_plot,
fahrenheit_plot
+ labs(subtitle='°F') # <-- Subtitle labeling the Y-axis
+ theme(
axis_title_y='blank',
plot_subtitle=element_text(hjust=1, color='blue') # <-- Subtitle right-justification and color
),
kelvin_plot
+ labs(subtitle='K')
+ theme(
axis_title_y='blank',
plot_subtitle=element_text(hjust=1, color='green'),
panel_inset=[0, 3, 0, 0],
),
rankine_plot
+ labs(subtitle='°R')
+ theme(
axis_title_y='blank',
plot_subtitle=element_text(hjust=1, color='red'),
panel_inset=[0, 3, 0, 0],
),
]
) + \
ggsize(800, 400) + \
ggtitle('Delhi Mean Temperature') + \
theme_classic() + \
theme(
plot_title=element_text(hjust=0.5, size=21),
panel_grid=element_line(),
) + ggtb()
Out[13]: