{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# Inspection roll call votes / namentliche Abstimmungen data for \"Fraktionszwang\"\n",
    "\n",
    "> Get insight into [\"Fraktionszwang\"](https://de.wikipedia.org/wiki/Fraktionsdisziplin) using voting behavior data from the bundestag and abgeordnetenwatch."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1",
   "metadata": {},
   "source": [
    "## TL;DR\n",
    "\n",
    "* Measured diversity of votes by party using entropy.\n",
    "* All parties have their members largely voting along party line, but not deviation is the norm. The only group voting close to radically random is those of the factionless."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2",
   "metadata": {},
   "source": [
    "## Setup"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3",
   "metadata": {},
   "source": [
    "Fraktionszwang should become evident by how diverse the votes are by one party across different polls."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4",
   "metadata": {},
   "source": [
    "To collect data, if not already present in `../data/preprocessed` run\n",
    "\n",
    "    uv run bundestag download huggingface"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "import polars as pl\n",
    "from plotnine import (\n",
    "    ggplot,\n",
    "    aes,\n",
    "    geom_point,\n",
    "    labs,\n",
    "    scale_y_continuous,\n",
    "    facet_wrap,\n",
    "    theme,\n",
    "    geom_line,\n",
    "    scale_color_manual,\n",
    ")\n",
    "import os\n",
    "from pathlib import Path\n",
    "from bundestag.fine_logging import setup_logging\n",
    "import logging\n",
    "from bundestag.paths import get_paths\n",
    "import math\n",
    "from bundestag.data.transform.abgeordnetenwatch.transform import (\n",
    "    get_polls_parquet_path,\n",
    "    get_votes_parquet_path,\n",
    "    get_mandates_parquet_path,\n",
    ")\n",
    "\n",
    "logger = logging.getLogger(__name__)\n",
    "\n",
    "\n",
    "def plot_poll_counts_over_time(df: pl.dataframe, x: str, y: str) -> ggplot:\n",
    "    return (\n",
    "        ggplot(df, aes(\"date\", \"n\"))\n",
    "        + geom_point()\n",
    "        + labs(title=\"# polls per day over time\", x=\"Date\", y=\"# unique polls\")\n",
    "        + scale_y_continuous(breaks=[0, 2, 4, 6, 8, 10])\n",
    "    )\n",
    "\n",
    "\n",
    "def plot_voting_members_per_poll_over_time(\n",
    "    df: pl.DataFrame, x: str, poll: str, member: str\n",
    ") -> ggplot:\n",
    "    members_per_poll_per_day_over_time = df.group_by([x, poll]).agg(\n",
    "        pl.col(member).n_unique().alias(\"n\")\n",
    "    )\n",
    "    return (\n",
    "        ggplot(members_per_poll_per_day_over_time, aes(x, \"n\"))\n",
    "        + geom_point()\n",
    "        + labs(\n",
    "            title=\"# Members voting per poll per day over time\", x=\"Date\", y=\"# members\"\n",
    "        )\n",
    "    )\n",
    "\n",
    "\n",
    "def compute_vote_shares(\n",
    "    df: pl.DataFrame, x: str, poll: str, party: str, vote: str, member: str\n",
    ") -> pl.DataFrame:\n",
    "    member_votes_per_faction_per_poll_per_day_over_time = (\n",
    "        df.group_by([x, poll, party, vote])\n",
    "        .agg(pl.col(member).n_unique().alias(\"n\"))\n",
    "        .sort(x, poll, party, vote)\n",
    "    )\n",
    "    return member_votes_per_faction_per_poll_per_day_over_time.with_columns(\n",
    "        (pl.col(\"n\") / pl.sum(\"n\").over([x, poll, party])).alias(\"vote share\")\n",
    "    )\n",
    "\n",
    "\n",
    "def plot_voting_shares_over_time(\n",
    "    df: pl.DataFrame, x: str, party: str, colors: scale_color_manual\n",
    ") -> ggplot:\n",
    "    return (\n",
    "        ggplot(\n",
    "            df,\n",
    "            aes(x, \"vote share\", color=\"vote\"),\n",
    "        )\n",
    "        + geom_point(alpha=0.3)\n",
    "        + labs(\n",
    "            title=\"Voting shares per poll per day over time\",\n",
    "            x=\"Date\",\n",
    "            y=\"Vote fraction\",\n",
    "        )\n",
    "        + facet_wrap(party, ncol=1)\n",
    "        + scale_y_continuous(limits=(0, 1), breaks=[0, 0.25, 0.5, 0.75, 1.0])\n",
    "        + theme(figure_size=(10, 16), subplots_adjust={\"hspace\": 0.35})\n",
    "        + colors\n",
    "    )\n",
    "\n",
    "\n",
    "def get_max_entropy(df: pl.DataFrame, col: str) -> float:\n",
    "    return -math.log2(1 / df[col].n_unique())\n",
    "\n",
    "\n",
    "def compute_entropies(\n",
    "    df: pl.DataFrame, t: str, poll: str, party: str, vote: str\n",
    ") -> pl.DataFrame:\n",
    "    max_entropy = get_max_entropy(df, vote)\n",
    "    return (\n",
    "        df.with_columns(**{\"log p\": pl.col(\"vote share\").log(base=2)})\n",
    "        .group_by([t, poll, party])\n",
    "        .agg(\n",
    "            **{\n",
    "                \"shannon entropy\": -pl.when(pl.col(\"vote share\") > 0)\n",
    "                .then(pl.col(\"vote share\") * pl.col(\"log p\"))\n",
    "                .otherwise(0)\n",
    "                .sum()\n",
    "            }\n",
    "        )\n",
    "        .with_columns(\n",
    "            **{\n",
    "                \"share of max shannon entropy [%]\": pl.col(\"shannon entropy\")\n",
    "                / max_entropy\n",
    "            }\n",
    "        )\n",
    "    )\n",
    "\n",
    "\n",
    "def compute_rolling_median(\n",
    "    df: pl.DataFrame,\n",
    "    n: int,\n",
    "    x: str,\n",
    "    party: str,\n",
    "    y: str = \"share of max shannon entropy [%]\",\n",
    ") -> pl.DataFrame:\n",
    "    return df.sort(x).with_columns(\n",
    "        pl.col(y).rolling_median(window_size=n).over(party).alias(\"rolling_median\")\n",
    "    )\n",
    "\n",
    "\n",
    "def plot_entropy_over_time(\n",
    "    df: pl.DataFrame,\n",
    "    party_colors: scale_color_manual,\n",
    "    x: str = \"date\",\n",
    "    y: str = \"share of max shannon entropy [%]\",\n",
    "    color: str = \"party\",\n",
    ") -> ggplot:\n",
    "    return (\n",
    "        ggplot(df, aes(x, y, color=color))\n",
    "        + geom_point(alpha=0.3)\n",
    "        + labs(\n",
    "            title=\"Voting entropy per poll per day over time\",\n",
    "            x=\"Date\",\n",
    "            y=\"Shannon entropy (smaller = more Fraktionszwang)\",\n",
    "        )\n",
    "        + facet_wrap(color, ncol=1)\n",
    "        + theme(figure_size=(10, 16), subplots_adjust={\"hspace\": 0.35})\n",
    "        + party_colors\n",
    "        + scale_y_continuous(labels=lambda v: [f\"{x * 100:.0f}%\" for x in v])\n",
    "    )\n",
    "\n",
    "\n",
    "def plot_rolling_entropy_over_time(\n",
    "    df: pl.DataFrame,\n",
    "    party_colors: scale_color_manual,\n",
    "    n_polls_to_average: int,\n",
    "    x: str = \"date\",\n",
    "    y: str = \"rolling_median\",\n",
    "    color: str = \"party\",\n",
    ") -> ggplot:\n",
    "    return (\n",
    "        ggplot(df, aes(x=x, color=color))\n",
    "        + geom_line(aes(y=y))\n",
    "        + labs(\n",
    "            title=f\"Relative voting entropy per poll per day over time with rolling median (n={n_polls_to_average})\",\n",
    "            x=\"Date\",\n",
    "            y=\"Share of Shannon entropy relative to maximum (smaller = more Fraktionszwang)\",\n",
    "        )\n",
    "        + theme(figure_size=(8, 6), subplots_adjust={\"hspace\": 0.35})\n",
    "        + party_colors\n",
    "        + scale_y_continuous(\n",
    "            labels=lambda v: [f\"{x * 100:.0f}%\" for x in v], limits=(0, 1)\n",
    "        )\n",
    "    )\n",
    "\n",
    "\n",
    "# if this notebook is run via `make docs` then the environment variable is set\n",
    "makedocs = os.getenv(\"MAKEDOCS\") is not None\n",
    "logger.info(f\"Running nb with {makedocs=}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7",
   "metadata": {},
   "outputs": [],
   "source": [
    "setup_logging(logging.INFO)\n",
    "\n",
    "_fig_path = Path(\"./images\")\n",
    "paths = get_paths(\"../data\")\n",
    "paths"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8",
   "metadata": {},
   "source": [
    "## Bundestag sheet data"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9",
   "metadata": {},
   "source": [
    "### Collect data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {},
   "outputs": [],
   "source": [
    "file = paths.preprocessed_bundestag / \"bundestag.de_votes.parquet\"\n",
    "file"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11",
   "metadata": {},
   "outputs": [],
   "source": [
    "data_bundestag = pl.read_parquet(file)\n",
    "\n",
    "data_bundestag.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12",
   "metadata": {},
   "source": [
    "### Clean the data"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13",
   "metadata": {},
   "source": [
    "Use a single name for \"Die Linke\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "14",
   "metadata": {},
   "outputs": [],
   "source": [
    "data_bundestag[\"Fraktion/Gruppe\"].value_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15",
   "metadata": {},
   "outputs": [],
   "source": [
    "data_bundestag = data_bundestag.with_columns(\n",
    "    **{\n",
    "        \"Fraktion/Gruppe\": pl.when(pl.col(\"Fraktion/Gruppe\").eq(pl.lit(\"DIE LINKE.\")))\n",
    "        .then(pl.lit(\"Die Linke\"))\n",
    "        .otherwise(pl.col(\"Fraktion/Gruppe\"))\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16",
   "metadata": {},
   "outputs": [],
   "source": [
    "data_bundestag[\"Fraktion/Gruppe\"].value_counts()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17",
   "metadata": {},
   "source": [
    "### Plot metrics over time"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "18",
   "metadata": {},
   "source": [
    "How many things are voted on per day over time?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19",
   "metadata": {},
   "outputs": [],
   "source": [
    "things_per_day_over_time = data_bundestag.group_by(\"date\").agg(\n",
    "    pl.col(\"Abstimmnr\").n_unique().alias(\"n\")\n",
    ")\n",
    "things_per_day_over_time.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = plot_poll_counts_over_time(things_per_day_over_time, \"date\", \"n\")\n",
    "p.show()\n",
    "if makedocs:\n",
    "    p.save(_fig_path / \"bundestag_sheets_polls_over_time.png\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21",
   "metadata": {},
   "source": [
    "How many members vote per poll over time?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = plot_voting_members_per_poll_over_time(\n",
    "    data_bundestag, \"date\", \"Abstimmnr\", \"Bezeichnung\"\n",
    ")\n",
    "p.show()\n",
    "if makedocs:\n",
    "    p.save(_fig_path / \"bundestag_sheets_members_per_poll_over_time.png\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23",
   "metadata": {},
   "source": [
    "Count of vote types by date, poll and party over time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24",
   "metadata": {},
   "outputs": [],
   "source": [
    "member_votes_per_faction_per_poll_per_day_over_time = compute_vote_shares(\n",
    "    data_bundestag, \"date\", \"Abstimmnr\", \"Fraktion/Gruppe\", \"vote\", \"Bezeichnung\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25",
   "metadata": {},
   "outputs": [],
   "source": [
    "member_votes_per_faction_per_poll_per_day_over_time.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26",
   "metadata": {},
   "outputs": [],
   "source": [
    "colors = scale_color_manual(\n",
    "    breaks=[\"ja\", \"nein\", \"nichtabgegeben\", \"Enthaltung\"],\n",
    "    values=[\"green\", \"red\", \"grey\", \"orange\"],\n",
    ")\n",
    "p = plot_voting_shares_over_time(\n",
    "    member_votes_per_faction_per_poll_per_day_over_time,\n",
    "    \"date\",\n",
    "    \"Fraktion/Gruppe\",\n",
    "    colors,\n",
    ")\n",
    "p.show()\n",
    "if makedocs:\n",
    "    p.save(_fig_path / \"bundestag_sheets_voting_shares_over_time.png\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "27",
   "metadata": {},
   "outputs": [],
   "source": [
    "entropy_per_poll_faction = compute_entropies(\n",
    "    member_votes_per_faction_per_poll_per_day_over_time,\n",
    "    \"date\",\n",
    "    \"Abstimmnr\",\n",
    "    \"Fraktion/Gruppe\",\n",
    "    \"vote\",\n",
    ")\n",
    "entropy_per_poll_faction.head(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28",
   "metadata": {},
   "outputs": [],
   "source": [
    "party_colors = scale_color_manual(\n",
    "    breaks=[\n",
    "        \"AfD\",\n",
    "        \"BSW\",\n",
    "        \"BÜ90/GR\",\n",
    "        \"CDU/CSU\",\n",
    "        \"Die Linke\",\n",
    "        \"FDP\",\n",
    "        \"Fraktionslos\",\n",
    "        \"SPD\",\n",
    "    ],\n",
    "    values=[\"blue\", \"purple\", \"green\", \"black\", \"red\", \"yellow\", \"grey\", \"salmon\"],\n",
    ")\n",
    "p = plot_entropy_over_time(\n",
    "    entropy_per_poll_faction, party_colors, x=\"date\", color=\"Fraktion/Gruppe\"\n",
    ")\n",
    "p.show()\n",
    "if makedocs:\n",
    "    p.save(_fig_path / \"bundestag_sheets_voting_entropy_over_time.png\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29",
   "metadata": {},
   "source": [
    "Now we compute the rolling median of `shannon entropy` over `n_polls_to_average` polls for each `Fraktion/Gruppe`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "30",
   "metadata": {},
   "outputs": [],
   "source": [
    "n_polls_to_average = 30\n",
    "entropy_per_poll_faction = compute_rolling_median(\n",
    "    entropy_per_poll_faction, n_polls_to_average, \"date\", \"Fraktion/Gruppe\"\n",
    ")\n",
    "entropy_per_poll_faction.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31",
   "metadata": {},
   "source": [
    "Now let's plot the original `shannon entropy` and the `shannon_entropy_rolling_median` to see the effect of the rolling median."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = plot_rolling_entropy_over_time(\n",
    "    entropy_per_poll_faction,\n",
    "    party_colors,\n",
    "    n_polls_to_average,\n",
    "    \"date\",\n",
    "    color=\"Fraktion/Gruppe\",\n",
    ")\n",
    "p.show()\n",
    "if makedocs:\n",
    "    p.save(_fig_path / \"bundestag_sheets_rolling_voting_entropy_over_time.png\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33",
   "metadata": {},
   "source": [
    "## Abgeordnetenwatch.de data"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "34",
   "metadata": {},
   "source": [
    "### Collect data"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35",
   "metadata": {},
   "source": [
    "First, collect data for the individual legislative periods"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36",
   "metadata": {},
   "outputs": [],
   "source": [
    "legislature_ids = [67, 83, 97, 111, 132, 161]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "37",
   "metadata": {},
   "outputs": [],
   "source": [
    "tmp = []\n",
    "for legislature_id in legislature_ids:\n",
    "    p = get_polls_parquet_path(legislature_id, paths.preprocessed_abgeordnetenwatch)\n",
    "    if not p.exists():\n",
    "        continue\n",
    "    _mandates = pl.read_parquet(p)\n",
    "    _mandates = _mandates.with_columns(**{\"legislature_id\": legislature_id})\n",
    "    tmp.append(_mandates)\n",
    "\n",
    "polls = pl.concat(tmp, how=\"diagonal_relaxed\")\n",
    "polls.head(2), polls.tail(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38",
   "metadata": {},
   "outputs": [],
   "source": [
    "tmp = []\n",
    "for legislature_id in legislature_ids:\n",
    "    p = get_votes_parquet_path(legislature_id, paths.preprocessed_abgeordnetenwatch)\n",
    "    if not p.exists():\n",
    "        continue\n",
    "    _mandates = pl.read_parquet(p)\n",
    "    _mandates = _mandates.with_columns(**{\"legislature_id\": legislature_id})\n",
    "    tmp.append(_mandates)\n",
    "\n",
    "votes = pl.concat(tmp, how=\"diagonal_relaxed\")\n",
    "votes.head(2), votes.tail(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39",
   "metadata": {},
   "outputs": [],
   "source": [
    "tmp = []\n",
    "for legislature_id in legislature_ids:\n",
    "    p = get_mandates_parquet_path(legislature_id, paths.preprocessed_abgeordnetenwatch)\n",
    "    if not p.exists():\n",
    "        continue\n",
    "    _mandates = pl.read_parquet(p)\n",
    "    print(len(_mandates))\n",
    "    _mandates = _mandates.with_columns(**{\"legislature_id\": legislature_id})\n",
    "    tmp.append(_mandates)\n",
    "\n",
    "mandates = pl.concat(tmp, how=\"diagonal_relaxed\")\n",
    "mandates.head(2), mandates.tail(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "40",
   "metadata": {},
   "outputs": [],
   "source": [
    "data_abgeordnetenwatch = polls.join(\n",
    "    votes, on=[\"legislature_id\", \"poll_id\"], how=\"left\"\n",
    ").join(mandates, on=[\"legislature_id\", \"mandate_id\"], how=\"left\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "41",
   "metadata": {},
   "outputs": [],
   "source": [
    "data_abgeordnetenwatch = data_abgeordnetenwatch.with_columns(\n",
    "    **{\"date\": pl.col(\"poll_date\").str.to_date(format=\"%Y-%m-%d\")}\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42",
   "metadata": {},
   "outputs": [],
   "source": [
    "with pl.Config(tbl_rows=15):\n",
    "    display(data_abgeordnetenwatch[\"party\"].value_counts(sort=True))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43",
   "metadata": {},
   "source": [
    "### Clean the data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44",
   "metadata": {},
   "outputs": [],
   "source": [
    "data_abgeordnetenwatch = data_abgeordnetenwatch.with_columns(\n",
    "    **{\n",
    "        \"party\": pl.when(\n",
    "            pl.col(\"party\").is_in(pl.lit([\"DIE LINKE\", \"Die Linke. (Gruppe)\"]))\n",
    "        )\n",
    "        .then(pl.lit(\"Die Linke\"))\n",
    "        .otherwise(pl.col(\"party\"))\n",
    "    }\n",
    ").with_columns(\n",
    "    **{\n",
    "        \"party\": pl.when(pl.col(\"party\").is_in(pl.lit([\"DIE GRÜNEN\"])))\n",
    "        .then(pl.lit(\"BÜNDNIS 90/DIE GRÜNEN\"))\n",
    "        .otherwise(pl.col(\"party\"))\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "45",
   "metadata": {},
   "outputs": [],
   "source": [
    "with pl.Config(tbl_rows=15):\n",
    "    display(data_abgeordnetenwatch[\"party\"].value_counts(sort=True))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "46",
   "metadata": {},
   "outputs": [],
   "source": [
    "data_abgeordnetenwatch.filter(pl.col(\"party\").is_null()).group_by(\"legislature_id\").agg(\n",
    "    **{\"mandates\": pl.col(\"mandate_id\").n_unique()}\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47",
   "metadata": {},
   "source": [
    "### Plot metrics over time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "48",
   "metadata": {},
   "outputs": [],
   "source": [
    "things_per_day_over_time = data_abgeordnetenwatch.group_by(\"date\").agg(\n",
    "    pl.col(\"poll_id\").n_unique().alias(\"n\")\n",
    ")\n",
    "things_per_day_over_time.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "49",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = plot_poll_counts_over_time(things_per_day_over_time, \"date\", \"n\")\n",
    "p.show()\n",
    "if makedocs:\n",
    "    p.save(_fig_path / \"abgeordnetenwatch_polls_over_time.png\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = plot_voting_members_per_poll_over_time(\n",
    "    data_abgeordnetenwatch, \"date\", \"poll_id\", \"mandate_id\"\n",
    ")\n",
    "p.show()\n",
    "if makedocs:\n",
    "    p.save(_fig_path / \"abgeordnetenwatch_members_per_poll_over_time.png\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "51",
   "metadata": {},
   "outputs": [],
   "source": [
    "member_votes_per_faction_per_poll_per_day_over_time = compute_vote_shares(\n",
    "    data_abgeordnetenwatch, \"date\", \"poll_id\", \"party\", \"vote\", \"mandate_id\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "52",
   "metadata": {},
   "outputs": [],
   "source": [
    "colors = scale_color_manual(\n",
    "    breaks=[\"yes\", \"no\", \"no_show\", \"abstain\"],\n",
    "    values=[\"green\", \"red\", \"grey\", \"orange\"],\n",
    ")\n",
    "p = plot_voting_shares_over_time(\n",
    "    member_votes_per_faction_per_poll_per_day_over_time, \"date\", \"party\", colors\n",
    ")\n",
    "p.show()\n",
    "if makedocs:\n",
    "    p.save(_fig_path / \"abgeordnetenwatch_voting_shares_over_time.png\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "53",
   "metadata": {},
   "outputs": [],
   "source": [
    "entropy_per_poll_faction = compute_entropies(\n",
    "    member_votes_per_faction_per_poll_per_day_over_time,\n",
    "    \"date\",\n",
    "    \"poll_id\",\n",
    "    \"party\",\n",
    "    \"vote\",\n",
    ")\n",
    "entropy_per_poll_faction.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "54",
   "metadata": {},
   "outputs": [],
   "source": [
    "party_colors = scale_color_manual(\n",
    "    breaks=[\n",
    "        \"AfD\",\n",
    "        \"BSW (Gruppe)\",\n",
    "        \"BÜNDNIS 90/DIE GRÜNEN\",\n",
    "        \"CDU/CSU\",\n",
    "        \"Die Linke\",\n",
    "        \"FDP\",\n",
    "        \"fraktionslos\",\n",
    "        \"SPD\",\n",
    "    ],\n",
    "    values=[\"blue\", \"purple\", \"green\", \"black\", \"red\", \"yellow\", \"grey\", \"salmon\"],\n",
    ")\n",
    "p = plot_entropy_over_time(entropy_per_poll_faction, party_colors)\n",
    "p.show()\n",
    "if makedocs:\n",
    "    p.save(_fig_path / \"abgeordnetenwatch_voting_entropy_over_time.png\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "55",
   "metadata": {},
   "outputs": [],
   "source": [
    "n_polls_to_average = 30\n",
    "entropy_per_poll_faction = compute_rolling_median(\n",
    "    entropy_per_poll_faction,\n",
    "    n_polls_to_average,\n",
    "    \"date\",\n",
    "    \"party\",\n",
    "    y=\"share of max shannon entropy [%]\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "56",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = plot_rolling_entropy_over_time(\n",
    "    entropy_per_poll_faction.filter(pl.col(\"party\").is_not_null()),\n",
    "    party_colors,\n",
    "    n_polls_to_average,\n",
    ")\n",
    "p.show()\n",
    "if makedocs:\n",
    "    p.save(_fig_path / \"abgeordnetenwatch_rolling_voting_entropy_over_time.png\")"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
