Search code examples
pythonaltairvega-lite

How do I color conditionally in a density plot?


sampleI am trying to make a density plot and mark part of the region based on the X value. The following snippet mostly works but has a weird angled cut-off between regions. How do I fix it?

np.random.seed(0)
df = pd.DataFrame({'x': np.random.normal(size=1000)})

(alt.Chart(df).
 transform_density('x', as_=['val', 'density']).
 transform_calculate(crit='(datum.val < -1.96) | (datum.val > 1.96)').
 mark_area().encode(
    x='val:Q',
    y=alt.Y('density:Q', impute={'value': 0}),
    color='crit:N'
 )
)

Solution

  • This happens because the area mark tries to interpolate linearly from where the blue and orange region ends to the bottom of the x-axis for each section of the area. If you set interpolate='step' you will see that the border is sharp (removing impute will improve it for the area). You can try to upsample the data to have the interpolation happen over such a small distance that it appears to be a straight vertical line and the areas look connected, more details in the answer to this question How to correctly shift the baseline in an area plot to a particular y location and change the fill color correspondingly, in Altair? .

    Another solution is to layer the middle portion on top of the entire density plot:

    import numpy as np
    import pandas as pd
    import altair as alt
    
    
    np.random.seed(0)
    df = pd.DataFrame({'x': np.random.normal(size=1000)})
    
    chart = (
        alt.Chart(df)
        .transform_density('x', as_=['x', 'density'])
        .mark_area(color='coral')
        .encode(x='x', y=alt.Y('density:Q')))
    
    chart + (
        chart
        .mark_area(color='steelblue')
        .transform_filter('(datum.x > -1.96) & (datum.x < 1.96)'))
    

    enter image description here