Skip to content

Tutorial

This tutorial collects a small set of federal appellate cases from CourtListener, turns them into a table, and analyzes it. Each step can be run on its own, and each saves its work.

The vocabulary

Part What it is In code
Case One decision: its sections of text (such as the parties, the judges, and the opinions) and information that needs no parsing (such as the date filed). courtpy.Case
Rule One row of a CSV file that finds something in a section of text. courtpy.Rule
Rulebook The rules of one or more CSV files, applied in order. courtpy.Rulebook
Case table A pandas.DataFrame with one row for each case. courtpy.parse
Coder A technique that adds variables to the case table, such as whether a decision reversed the court below. courtpy.coders
Project A study: the cases and an amos analysis of them. courtpy.Project

1. Collect cases

CourtListener offers its opinions in two ways. The bulk data needs no API key and has no limits, but its files are large (the opinions alone are more than 50 GB), so extracting cases takes a few hours. It is the best way to collect a large set of cases:

import courtpy

courtpy.BulkData().extract(
    courts = "ca1",
    start_date = "2019-01-01",
    end_date = "2019-12-31",
    folder = "court_listener")

The API is faster for a small or very recent set of cases, but it needs an API key and limits how many requests an account can make. Get a key from the "API" page of your CourtListener profile and store it once, in a terminal:

courtpy key set

Then download:

courtpy.CourtListener().download(
    "ca1", "2026-09-01", "2026-09-30", "court_listener", max_cases = 40)

Either way, each case is saved as a JSON file in a folder for its court, such as court_listener/ca1/4567890.json. Running a download again skips the cases that were already saved.

2. Parse the cases into a table

courtpy.parse reads every saved case in a folder and applies the rules in the "federal" rulebook (see Writing Rules):

table = courtpy.parse("court_listener")
print(table.shape)
print(table[["case_name", "court_num", "panel_judges", "disposition"]].head())

Each row is labeled by the case's CourtListener id. Among the columns are the parties (party1 and party2) and their roles (party1_appellant, party2_united_states, and so on), the judges (panel_judges, author, dissenting), the disposition (disposition_reverse, decision_reverse, and others), the issues the opinions discuss (criminal_firearm, civil_titlevii, general_amend4, and many more), and the citations in the opinions (references_case and others).

3. Code the outcomes

The coders add variables that combine others: whether each case is criminal, each party's role in the case, and the outcome of the appeal:

dataset = courtpy.code(table)
coded = dataset.data
print(coded["outcome_reversal"].mean())
print(coded[["type_criminal", "outcome_criminal_defendant_won"]].value_counts())

courtpy.code returns an amos.Dataset, whose history records each coder. Save the table so that the steps above need not be repeated:

courtpy.save_cases(coded, "cases.csv")
coded = courtpy.load_cases("cases.csv")

The CSV file can be opened in a spreadsheet. Lists (such as the judges) are written with "; " between the items, and load_cases turns them back into lists.

4. Analyze the table

courtpy.Project is amos.Project, so an analysis is described with amos settings. This one predicts reversals from a few features. (logit is scikit-learn's logistic regression; amos's logit_sm is the statsmodels model, with a table of coefficients, which needs pip install amos[statistics].)

settings = {
    "general": {"seed": 43, "label": "outcome_reversal"},
    "study_project": {"study_workers": "wrangler, explorer, analyst, critic"},
    "wrangler": {"techniques": "keep_columns, drop_missing"},
    "keep_columns_parameters": {
        "columns": ["year", "published", "type_criminal", "dissents",
                    "word_count", "criminal_firearm", "standard_de_novo"]},
    "explorer": {"techniques": "summarize, label_balance"},
    "analyst": {
        "design": "experiment",
        "criterion": "roc_auc",
        "steps": "split, model",
        "split_techniques": "stratified",
        "model_techniques": "baseline, logit"},
    "critic": {"techniques": "scorecard"},
}
project = courtpy.Project.create(settings, item = coded)
print(project.result.tables["scorecard"])
project.export()

export saves the report, the settings, the versions of every package, the history of every technique, and the tables and figures in a folder named for the run, so the study can be reported and reproduced.

5. Describe the whole study in one file

Steps 1 to 3 can be part of the study itself. CourtPy's loaders are amos techniques that collect, parse, and code cases, so a project whose wrangler starts with one needs no other data. Save this as study.ini:

[general]
seed = 43
label = outcome_reversal

[study_project]
study_workers = wrangler, analyst, critic

[wrangler]
techniques = load_court_listener, keep_columns, drop_missing

[load_court_listener_parameters]
source = court_listener
download = api
courts = ca1
start_date = 2026-09-01
end_date = 2026-09-30
max_cases = 40
save = cases.csv
reuse = true

[keep_columns_parameters]
columns = year, published, type_criminal, dissents, word_count

[analyst]
techniques = stratified, logit

[critic]
techniques = scorecard

and run it in a terminal:

courtpy run study.ini --export

The first run downloads, parses, and codes the cases and saves the table in cases.csv. Later runs reuse that file (because of reuse = true) and only repeat the analysis. The project's history records the loader and each coder, so the export shows exactly how the data was made. See examples/federal_appeals.ini for a fuller study.