Search code examples
pythonplotaxesaltairvega-lite

Dual Axis on Altair - filtering source


Altair charting is brilliant in Python because it's easy to add hover-over data annotations (unlike Seaborn) and adding additional 'marks' (lines, bars etc) is cleaner than Plotly, but one thing am struggling with is dual axis. This generally works fine (similar to documentation https://altair-viz.github.io/gallery/layered_chart_with_dual_axis.html):

import altair as alt
from vega_datasets import data
source = data.seattle_weather()

chart_base = alt.Chart(source.query("weather != 'snow'")).encode(
    alt.X('date:T',
    axis=alt.Axis(),
    scale=alt.Scale(zero=False)
    )
).properties(width=800, height=400)

line_prec = chart_base.mark_line(color='blue', size=1).encode(y='mean(precipitation)')
line_temp = chart_base.mark_line(color='red', size=1).encode(y='mean(temp_max)')
alt.layer(line_prec, line_temp).resolve_scale(y='independent')

enter image description here

... so all I need to do is filter the individual marks, similar to the source.query but say we want to show precipitation only where "weather == 'rain'" and temp_max where "weather != 'rain'" (yes not analytically insightful but just to illustrate working example of double filter).

Solution could have been Altair combining multiple data sets but the DRYer code is not dual axis and more promising but the multiple layer method doesn't seem to apply to dual axis, as we get error trying to add (rather desperately) the .resolve_scale(y='independent'):

chart_precip = alt.Chart(source.query("weather == 'rain'")).mark_line(color='blue', size=1).encode(
    y='mean(precipitation)', 
    x=('date:T')
).properties(width=500, height=300) #.resolve_scale(y='independent') # can't add resolve_scale

chart_temp = alt.Chart(source.query("weather != 'rain'")).mark_line(color='red', size=1).encode(
    y='mean(temp_max)', 
    x=('date:T')
).properties(width=500, height=300) #.resolve_scale(y='independent') # can't add resolve_scale

chart_precip + chart_temp

It works but does not label the dual axis, which is no good:

enter image description here

So... is there a way to simply have dual axis marks filtered individually?


Solution

  • You can achieve dual axes by setting resolve_scale(y='independent') on the layered chart rather than the individual layers:

    (chart_precip + chart_temp).resolve_scale(y='independent')
    

    enter image description here

    Note that chart_precip + chart_temp is just a shorthand for alt.layer(chart_precip, chart_temp), so this is quite similar to your first example.