Anton Golubev
Anton Golubev

Reputation: 1433

How to add layers to a faceted chart in Altair?

Altair is a lovely visualization lib, with nice intuitive visual grammar API. I am however struggling adding a layer with rule marks to a faceted chart.

Imagine you have a simple dataset:

print(df[['Year', 'Profile', 'Saison', 'Pos']].to_csv())
,Year,Profile,Saison,Pos
0,2017,6.0,Sommer,VL
1,2017,6.0,Winter,VL
13,2017,6.0,Winter,HL
12,2017,6.0,Sommer,HL
18,2017,6.0,Sommer,HR
6,2017,6.0,Sommer,VR
7,2017,6.0,Winter,VR
19,2017,6.0,Winter,HR
14,2018,5.5,Winter,HL
8,2018,5.5,Winter,VR
15,2018,5.5,Sommer,HL
20,2018,4.3,Winter,HR
21,2018,5.0,Sommer,HR
3,2018,5.5,Sommer,VL
2,2018,6.2,Winter,VL
9,2018,4.5,Sommer,VR
17,2019,4.5,Sommer,HL
11,2019,4.2,Sommer,VR
22,2019,3.5,Winter,HR
10,2019,5.28,Winter,VR
5,2019,4.6,Sommer,VL
4,2019,4.9,Winter,VL
16,2019,4.0,Winter,HL
23,2019,4.5,Sommer,HR

Than you can simply display it with:

base = alt.Chart(df[df.Saison=='Winter']).mark_bar().encode(x='Year:O', y='Profile:Q', column='Pos:N')
base

enter image description here

Than, let’s say I want to add horizontal marks to visualize some limits:

enter image description here

For that I define the DataSet:

print(Limits.to_csv())
,Profil
0,3.0
1,1.5

And add it to the collection of charts:

limits = alt.Chart(Limits).mark_rule(color='red').encode(y='Profil')
base + limits

This does not work and generates and error:

ValueError: Faceted charts cannot be layered.

How can I overcome this limitation? Underlying vega-light grammar apparently supports such complex composition of layers, but I cannot figure out the way how to express it in Altair.

Upvotes: 3

Views: 2215

Answers (1)

jakevdp
jakevdp

Reputation: 86310

You cannot layer a facteted chart, because in general there is no guarantee that the contents of each layer contains compatible faceting.

However, you can facet a layered chart. It might look something like this:

import altair as alt
import pandas as pd
import io

df = pd.read_csv(io.StringIO("""
,Year,Profile,Saison,Pos
0,2017,6.0,Sommer,VL
1,2017,6.0,Winter,VL
13,2017,6.0,Winter,HL
12,2017,6.0,Sommer,HL
18,2017,6.0,Sommer,HR
6,2017,6.0,Sommer,VR
7,2017,6.0,Winter,VR
19,2017,6.0,Winter,HR
14,2018,5.5,Winter,HL
8,2018,5.5,Winter,VR
15,2018,5.5,Sommer,HL
20,2018,4.3,Winter,HR
21,2018,5.0,Sommer,HR
3,2018,5.5,Sommer,VL
2,2018,6.2,Winter,VL
9,2018,4.5,Sommer,VR
17,2019,4.5,Sommer,HL
11,2019,4.2,Sommer,VR
22,2019,3.5,Winter,HR
10,2019,5.28,Winter,VR
5,2019,4.6,Sommer,VL
4,2019,4.9,Winter,VL
16,2019,4.0,Winter,HL
23,2019,4.5,Sommer,HR
"""))

bars = alt.Chart().mark_bar().encode(
    x='Year:O',
    y='Profile:Q',
)

limits = alt.Chart(
  pd.DataFrame({'Profil': [3, 1.5]})
).mark_rule(
  color='red'
).encode(y='Profil')

alt.layer(
    bars,
    limits,
    data=df[df.Saison=='Winter']
).facet(
    'Pos:N',
)

enter image description here

Specifying the data here is a little tricky: the facet() method keys-off the top-level data in the chart it's called on, so when you're faceting layers built from different datasets, you need to specify the relevant data at the top level.

Upvotes: 9

Related Questions