Diverging Stacked Bar Chart#
This example shows a diverging stacked bar chart for sentiments towards a set of eight questions, displayed as percentages with neutral responses straddling the 0% mark.
import altair as alt
import polars as pl
import polars.selectors as cs
records = [
{
"question": "Question 1",
"type": "Strongly disagree",
"value": 24,
},
{
"question": "Question 1",
"type": "Disagree",
"value": 294,
},
{
"question": "Question 1",
"type": "Neither agree nor disagree",
"value": 594,
},
{
"question": "Question 1",
"type": "Agree",
"value": 1927,
},
{
"question": "Question 1",
"type": "Strongly agree",
"value": 376,
},
{
"question": "Question 2",
"type": "Strongly disagree",
"value": 2,
},
{
"question": "Question 2",
"type": "Disagree",
"value": 2,
},
{
"question": "Question 2",
"type": "Neither agree nor disagree",
"value": 0,
},
{
"question": "Question 2",
"type": "Agree",
"value": 7,
},
{
"question": "Question 2",
"type": "Strongly agree",
"value": 11,
},
{
"question": "Question 3",
"type": "Strongly disagree",
"value": 2,
},
{
"question": "Question 3",
"type": "Disagree",
"value": 0,
},
{
"question": "Question 3",
"type": "Neither agree nor disagree",
"value": 2,
},
{
"question": "Question 3",
"type": "Agree",
"value": 4,
},
{
"question": "Question 3",
"type": "Strongly agree",
"value": 2,
},
{
"question": "Question 4",
"type": "Strongly disagree",
"value": 0,
},
{
"question": "Question 4",
"type": "Disagree",
"value": 2,
},
{
"question": "Question 4",
"type": "Neither agree nor disagree",
"value": 1,
},
{
"question": "Question 4",
"type": "Agree",
"value": 7,
},
{
"question": "Question 4",
"type": "Strongly agree",
"value": 6,
},
{
"question": "Question 5",
"type": "Strongly disagree",
"value": 0,
},
{
"question": "Question 5",
"type": "Disagree",
"value": 1,
},
{
"question": "Question 5",
"type": "Neither agree nor disagree",
"value": 3,
},
{
"question": "Question 5",
"type": "Agree",
"value": 16,
},
{
"question": "Question 5",
"type": "Strongly agree",
"value": 4,
},
{
"question": "Question 6",
"type": "Strongly disagree",
"value": 1,
},
{
"question": "Question 6",
"type": "Disagree",
"value": 1,
},
{
"question": "Question 6",
"type": "Neither agree nor disagree",
"value": 2,
},
{
"question": "Question 6",
"type": "Agree",
"value": 9,
},
{
"question": "Question 6",
"type": "Strongly agree",
"value": 3,
},
{
"question": "Question 7",
"type": "Strongly disagree",
"value": 0,
},
{
"question": "Question 7",
"type": "Disagree",
"value": 0,
},
{
"question": "Question 7",
"type": "Neither agree nor disagree",
"value": 1,
},
{
"question": "Question 7",
"type": "Agree",
"value": 4,
},
{
"question": "Question 7",
"type": "Strongly agree",
"value": 0,
},
{
"question": "Question 8",
"type": "Strongly disagree",
"value": 0,
},
{
"question": "Question 8",
"type": "Disagree",
"value": 0,
},
{
"question": "Question 8",
"type": "Neither agree nor disagree",
"value": 0,
},
{
"question": "Question 8",
"type": "Agree",
"value": 0,
},
{
"question": "Question 8",
"type": "Strongly agree",
"value": 2,
},
]
source = pl.DataFrame(records)
type_codes = {
"Strongly disagree": -2,
"Disagree": -1,
"Neither agree nor disagree": 0,
"Agree": 1,
"Strongly agree": 2,
}
value = pl.col("value")
perc = pl.col("percentage")
sum_disagree = perc.filter(pl.col.type.is_in(["Strongly disagree", "Disagree"])).sum()
percentage_end = perc.cum_sum() - (
sum_disagree + perc.filter(type="Neither agree nor disagree").item() / 2
)
source = (
pl.DataFrame(records)
.with_columns(
type_code=pl.col.type.replace_strict(type_codes),
percentage=((value / value.sum()) * 100).over("question"),
)
.group_by("question")
.agg(
pl.all(),
percentage_end=percentage_end,
percentage_start=(percentage_end - perc),
)
.explode(cs.list())
.sort("question", "type_code")
)
alt.Chart(source).mark_bar().encode(
x=alt.X("percentage_start").title("Percentage"),
x2="percentage_end",
y=alt.Y("question:N").axis(
title=None, offset=5, ticks=False, minExtent=60, domain=False
),
color=alt.Color("type:N")
.title("Response")
.scale(
domain=list(type_codes),
range=["#c30d24", "#f3a583", "#cccccc", "#94c6da", "#1770ab"],
),
)
import altair as alt
import polars as pl
import polars.selectors as cs
records = [
{
"question": "Question 1",
"type": "Strongly disagree",
"value": 24,
},
{
"question": "Question 1",
"type": "Disagree",
"value": 294,
},
{
"question": "Question 1",
"type": "Neither agree nor disagree",
"value": 594,
},
{
"question": "Question 1",
"type": "Agree",
"value": 1927,
},
{
"question": "Question 1",
"type": "Strongly agree",
"value": 376,
},
{
"question": "Question 2",
"type": "Strongly disagree",
"value": 2,
},
{
"question": "Question 2",
"type": "Disagree",
"value": 2,
},
{
"question": "Question 2",
"type": "Neither agree nor disagree",
"value": 0,
},
{
"question": "Question 2",
"type": "Agree",
"value": 7,
},
{
"question": "Question 2",
"type": "Strongly agree",
"value": 11,
},
{
"question": "Question 3",
"type": "Strongly disagree",
"value": 2,
},
{
"question": "Question 3",
"type": "Disagree",
"value": 0,
},
{
"question": "Question 3",
"type": "Neither agree nor disagree",
"value": 2,
},
{
"question": "Question 3",
"type": "Agree",
"value": 4,
},
{
"question": "Question 3",
"type": "Strongly agree",
"value": 2,
},
{
"question": "Question 4",
"type": "Strongly disagree",
"value": 0,
},
{
"question": "Question 4",
"type": "Disagree",
"value": 2,
},
{
"question": "Question 4",
"type": "Neither agree nor disagree",
"value": 1,
},
{
"question": "Question 4",
"type": "Agree",
"value": 7,
},
{
"question": "Question 4",
"type": "Strongly agree",
"value": 6,
},
{
"question": "Question 5",
"type": "Strongly disagree",
"value": 0,
},
{
"question": "Question 5",
"type": "Disagree",
"value": 1,
},
{
"question": "Question 5",
"type": "Neither agree nor disagree",
"value": 3,
},
{
"question": "Question 5",
"type": "Agree",
"value": 16,
},
{
"question": "Question 5",
"type": "Strongly agree",
"value": 4,
},
{
"question": "Question 6",
"type": "Strongly disagree",
"value": 1,
},
{
"question": "Question 6",
"type": "Disagree",
"value": 1,
},
{
"question": "Question 6",
"type": "Neither agree nor disagree",
"value": 2,
},
{
"question": "Question 6",
"type": "Agree",
"value": 9,
},
{
"question": "Question 6",
"type": "Strongly agree",
"value": 3,
},
{
"question": "Question 7",
"type": "Strongly disagree",
"value": 0,
},
{
"question": "Question 7",
"type": "Disagree",
"value": 0,
},
{
"question": "Question 7",
"type": "Neither agree nor disagree",
"value": 1,
},
{
"question": "Question 7",
"type": "Agree",
"value": 4,
},
{
"question": "Question 7",
"type": "Strongly agree",
"value": 0,
},
{
"question": "Question 8",
"type": "Strongly disagree",
"value": 0,
},
{
"question": "Question 8",
"type": "Disagree",
"value": 0,
},
{
"question": "Question 8",
"type": "Neither agree nor disagree",
"value": 0,
},
{
"question": "Question 8",
"type": "Agree",
"value": 0,
},
{
"question": "Question 8",
"type": "Strongly agree",
"value": 2,
},
]
source = pl.DataFrame(records)
type_codes = {
"Strongly disagree": -2,
"Disagree": -1,
"Neither agree nor disagree": 0,
"Agree": 1,
"Strongly agree": 2,
}
value = pl.col("value")
perc = pl.col("percentage")
sum_disagree = perc.filter(pl.col.type.is_in(["Strongly disagree", "Disagree"])).sum()
percentage_end = perc.cum_sum() - (
sum_disagree + perc.filter(type="Neither agree nor disagree").item() / 2
)
source = (
pl.DataFrame(records)
.with_columns(
type_code=pl.col.type.replace_strict(type_codes),
percentage=((value / value.sum()) * 100).over("question"),
)
.group_by("question")
.agg(
pl.all(),
percentage_end=percentage_end,
percentage_start=(percentage_end - perc),
)
.explode(cs.list())
.sort("question", "type_code")
)
color_scale = alt.Scale(
domain=[
"Strongly disagree",
"Disagree",
"Neither agree nor disagree",
"Agree",
"Strongly agree",
],
range=["#c30d24", "#f3a583", "#cccccc", "#94c6da", "#1770ab"],
)
y_axis = alt.Axis(title=None, offset=5, ticks=False, minExtent=60, domain=False)
alt.Chart(source).mark_bar().encode(
x=alt.X("percentage_start:Q", title='Percentage'),
x2="percentage_end:Q",
y=alt.Y("question:N", axis=y_axis),
color=alt.Color("type:N", title="Response", scale=color_scale),
)