Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Who files the most bills? This chapter ranks legislators by authored bills in a chosen congress. It uses the authors attached to each /documents record, so a co-authored bill counts for every listed author.

Source
# --- COLLAPSIBLE SETUP BLOCK ---
from collections import Counter

import pandas as pd
import plotly.express as px
import sys
from pathlib import Path

cwd = Path.cwd().resolve()
sys.path[:0] = [str(cwd), str(cwd.parent)]

from book_utils import api_get, fetch_all, full_name, wrap_label, searchable_table
Source
# --- COLLAPSIBLE PARAMETER BLOCK ---
# Pick the congress to analyze (8-20). Author linkage improves sharply from the
# 13th Congress onward, so the most recent congress is the default.
stats, _ = api_get("stats")
available = sorted(int(row["congress"]) for row in stats["bills_by_congress"])
SELECTED_CONGRESS = available[-1]          # <-- edit this (e.g. 17, 18, 19)
SELECTED_CONGRESS = min(max(SELECTED_CONGRESS, available[0]), available[-1])
print(f"Analyzing the {SELECTED_CONGRESS}th Congress")
Analyzing the 20th Congress
Source
# --- COLLAPSIBLE INGESTION BLOCK ---
documents = pd.DataFrame(fetch_all("documents", congress=SELECTED_CONGRESS))
documents[["name", "subtype", "date_filed", "authors"]].head()
Loading...
Source
# --- COLLAPSIBLE AUTHOR-TALLY BLOCK ---
# Walk every bill's author list. Each author gets credit for the bill (co-authors
# included). We keep two counters: overall bills per author, and bills per author
# split by chamber (subtype), keyed by a stable (id, name) tuple.
author_counts = Counter()
author_chamber_counts = Counter()

for _, row in documents.iterrows():
    for author in row.get("authors") or []:
        author_id = author.get("id")
        name = full_name(author)
        if author_id and name:
            key = (author_id, name)
            author_counts[key] += 1
            author_chamber_counts[(author_id, name, row.get("subtype"))] += 1

top_authors = pd.DataFrame(
    [{"author_id": key[0], "author": key[1], "authored_bills": value} for key, value in author_counts.items()]
).sort_values("authored_bills", ascending=False)

print(f"Distinct authors with at least one linked bill: {len(top_authors)}")
top_authors.head(20)
Distinct authors with at least one linked bill: 315
Loading...

If top_authors is nearly empty here, you have almost certainly selected an early congress where author links were never digitized — H3 in action.

Search Every Author

The table below is fully searchable and sortable in your browser. Type a surname to find a legislator, or sort by authored_bills to see the leaders.

Source
# Client-side searchable roster of every author and their bill count.
roster = top_authors.rename(
    columns={"author": "Legislator", "authored_bills": "Authored bills", "author_id": "ID"}
)[["Legislator", "Authored bills", "ID"]]
searchable_table(roster, caption=f"All bill authors in the {SELECTED_CONGRESS}th Congress (searchable)")
Loading...

Top Authors

Long Philippine names — compound surnames, quoted nicknames, and suffixes such as Jr. or III — are wrapped onto two lines so they stay readable on the axis; the full name is preserved on hover.

Source
# --- COLLAPSIBLE TOP-AUTHOR CHART BLOCK ---
# wrap_label breaks long names across lines so they do not overflow the y-axis;
# we keep the untruncated name available for the hover tooltip.
top25 = top_authors.head(25).sort_values("authored_bills").copy()
top25["label"] = top25["author"].map(lambda n: wrap_label(n, width=24))

fig = px.bar(
    top25,
    x="authored_bills",
    y="label",
    orientation="h",
    custom_data=["author"],
    title=f"Top Bill Authors in the {SELECTED_CONGRESS}th Congress",
    labels={"authored_bills": "Authored bills", "label": "Legislator"},
)
# Show the full, unwrapped name in the tooltip.
fig.update_traces(hovertemplate="%{customdata[0]}<br>Authored bills: %{x}<extra></extra>")
# Extra left margin gives the wrapped names room to breathe.
fig.update_layout(margin={"l": 160}, yaxis_title="")
fig
Loading...

The steep drop-off from the top author downward is the concentration predicted by H1.

Top Authors by Chamber

Do the leaders sit in one chamber? Color splits each author’s output into House and Senate bills.

Source
# --- COLLAPSIBLE CHAMBER-SPLIT CHART BLOCK ---
# Expand the (author, chamber) counter into a tidy frame, keep only the top 15
# authors, and wrap their names for the axis.
by_type = pd.DataFrame(
    [
        {"author_id": author_id, "author": author, "bill_type": subtype, "bills": count}
        for (author_id, author, subtype), count in author_chamber_counts.items()
    ]
)
top_author_ids = top_authors.head(15)["author_id"]
by_type = by_type[by_type["author_id"].isin(top_author_ids)].copy()
by_type["label"] = by_type["author"].map(lambda n: wrap_label(n, width=24))

fig = px.bar(
    by_type,
    x="bills",
    y="label",
    color="bill_type",
    orientation="h",
    custom_data=["author"],
    title=f"Top Authors by Bill Type ({SELECTED_CONGRESS}th Congress)",
    labels={"bills": "Authored bills", "label": "Legislator", "bill_type": "Bill type"},
)
fig.update_traces(hovertemplate="%{customdata[0]}<br>Bills: %{x}<extra></extra>")
fig.update_layout(margin={"l": 160}, yaxis_title="")
fig
Loading...

If nearly all the top bars are a single color, that is H2 confirmed for this congress.

Questions to Investigate