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]: