Search code examples
pythonjupyteraltair

Altair mark_errorband ignoring conditional color?


I am still rather new to altair and was experimenting with interaction inside a Jupyter notebook. Suppose I have multiple values split over categories and I want to show them with an error band, and add selection on top of that. I came this far:

import altair as alt
import pandas as pd
import numpy as np
import random

alt.data_transformers.disable_max_rows()

# generate some data
data = pd.DataFrame(np.random.rand(1000,1),columns=["delta"])
data["time"] = np.random.rand(1000,1)
data["strategy"] = [random.choice(["some", "other", "foo"]) for x in range(0,1000)]
conditions = [data["strategy"] == "some", data["strategy"] == "other", data["strategy"] == "foo"]
offsets = [0, 2, 4]
data["delta"] = data["delta"] + np.select(conditions, offsets)

# parameters and interaction
my_bins = 50
op_slider = alt.binding_range(name="opacity", min=0, max=1, step=0.05)
my_op = alt.param(bind=op_slider, value=.7)
col_selection = alt.selection_point(fields=['strategy'])
my_col = alt.condition(col_selection, alt.Color("strategy:N").legend(None), alt.value("lightgray"))

my_x = alt.X("time:Q").bin(maxbins=my_bins)
my_y = alt.Y("delta:Q").aggregate("mean").scale(domain=(0,5),clamp=True)

# plot
band = alt.Chart(data).mark_errorband(extent="stdev", interpolate="linear", borders=False).encode(
    x=my_x, y=my_y,color=my_col,opacity=my_op
).add_params(col_selection, my_op)
line = alt.Chart(data).mark_line().encode(x=my_x,y=my_y,color=my_col).add_params(col_selection)

# clickable manual legend
selector = alt.Chart(data).mark_rect().encode(
    alt.Y("strategy:N").axis(orient="right"),
    color = my_col
).add_params(
    col_selection
)

left = band + line
left.width = 800
chart = (left | selector)
chart

This code allows me to gray out the lines that are not selected via the legend, however the error band completely ignores the conditional coloring. It has a fourth color and aggregates over all values. If I set the color statically to "strategy:N" inside the mark_errorband, the output is correct, but obviously ignores the selection interaction.

What a I doing wrong? Is this intentional behavior?

I know I could compute the error band myself via numpy, but that feels beside the point.


Solution

  • Welcome to the Altair community! =)

    If you add detail='strategy' the example seems to work the way you want. This encoding creates an explicit grouping without linking it to a visual channel. I'm not sure why it is required here, but it might be related to that errorband is an aggregate/composite mark of an area and lines (for the optional borders).