Skip to content

Instantly share code, notes, and snippets.

@mtorchiano
Created March 17, 2026 13:22
Show Gist options
  • Select an option

  • Save mtorchiano/c490f4b7b91255c12c4aa9f15733570f to your computer and use it in GitHub Desktop.

Select an option

Save mtorchiano/c490f4b7b91255c12c4aa9f15733570f to your computer and use it in GitHub Desktop.
Plotnine extension for likert-like plotting using divergent bar charts
from plotnine import *
import pandas as pd
class geom_likert(geom_col):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def __radd__(self, other):
if isinstance(other, ggplot):
df = other.data
mapping = other.mapping
item = mapping["x"]
response = mapping["fill"]
df_counts = df.groupby(item)[response].value_counts().reset_index(name="count")
likert_options = df[response].cat.categories.tolist()
if len(likert_options)%2 == 1:
neutral_option = likert_options[len(likert_options) // 2]
neutral_mask = df_counts[response] == neutral_option
df_counts["count"] = df_counts["count"].astype(float)
df_counts.loc[neutral_mask, "count"] = df_counts.loc[neutral_mask, "count"] / 2
df_counts = pd.concat([df_counts, df_counts[neutral_mask].assign(count=lambda d: -d["count"])], ignore_index=True)
negative_threshold = likert_options[len(likert_options) // 2 -1]
df_counts.loc[df_counts[response] <= negative_threshold, "count"] = -df_counts["count"]
df_counts["prop"] = df_counts["count"] / df_counts.groupby(item)["count"].transform(lambda x: x.abs().sum())
other += geom_col(aes(y="prop"),position=position_stack(reverse=True),
data=df_counts[df_counts["count"] > 0])
other += geom_col(aes(y="prop"),position=position_stack(),
data=df_counts[df_counts["count"] < 0])
other += scale_y_continuous(labels=lambda l: ["{:.0f}%".format(abs(v) * 100) for v in l], limits=(-1, 1))
other += geom_hline(yintercept=0, color="gray", size=0.5, linetype="dashed")
other += coord_flip()
return other
return NotImplemented
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment