Search code examples
pythonvisualizationaltair

Remove gap between two graphs with shared axis


Objective

I want to generate a 'double ended' barchart, showing gained points (of some metric) vis-a-vis missed points, something like this:

graph I want to make

Result so far

I managed to do this

import altair as alt
import pandas as pd

source = pd.DataFrame(
    {
        "cohort": ["A", "B", "C", "D", "E", "F", "G", "H", "I"],
        "gained": [28, 55, 43, 91, 81, 53, 19, 87, 52],
        "missed": [5, 8, 34, 21, 16, 22, 9, 7, 11],
    }
)

up = (
    alt.Chart(source)
    .mark_bar(color="blue")
    .encode(
        x=alt.X("cohort:N").axis(labels=False, title=None, ticks=False),
        y=alt.Y("gained:Q"),
    )
)
down = (
    alt.Chart(source)
    .mark_bar(color="red")
    .encode(
        x=alt.X("cohort:N").axis(labelAngle=0),
        y=alt.Y("missed:Q", scale=alt.Scale(reverse=True)),
    )
)

alt.vconcat(up, down).resolve_scale(x="shared")

which generates this: results so far

Is there any way I can remove the gap? Or perhaps go about it completely differently with Vega-Altair?


Solution

  • @r-beginners pointed me in the right direction, this does the trick:

    import altair as alt
    import pandas as pd
    
    source = pd.DataFrame(
        {
            "cohort": [
                "start",
                "1st trimester",
                "2nd trimester",
                "3rd trimester",
                "delivery",
            ],
            "gained": [28, 55, 43, 11, 41],
            "missed": [5, 8, 34, 21, 16],
        }
    )
    
    WIDTH = 500
    TEXT_OFFSET = 9
    
    base_up = alt.Chart(source).encode(
        x=alt.X("cohort:N").axis(None), y=alt.Y("gained:Q").axis(None), text="gained:Q"
    )
    up = base_up.mark_bar() + base_up.mark_text(align="center", dy=-TEXT_OFFSET).properties(
        width=WIDTH
    )
    
    middle = (
        alt.Chart(source)
        .encode(
            alt.X("cohort:N").axis(None),
            alt.Text("cohort:N"),
        )
        .mark_text()
        .properties(height=15, width=WIDTH)
    )
    
    base_down = alt.Chart(source).encode(
        x=alt.X("cohort:N").axis(None),
        y=alt.Y("missed:Q", scale=alt.Scale(reverse=True)).axis(None),
        text="missed:Q",
    )
    down = base_down.mark_bar(color="red") + base_down.mark_text(
        align="center", dy=TEXT_OFFSET
    ).properties(width=WIDTH)
    
    alt.vconcat(up, down, spacing=0).configure_view(strokeOpacity=0)
    

    enter image description here