↖︎ Vishal Singh

CFPB Consumer Complaints

Crisis Monitor

A case study in treating consumer narratives as an early-warning system: find the spike, read the words that explain it, then decide whether the signal is an incident, a mix shift, or a structural change.

Snapshot Loading official CFPB data... Consumer Complaint Database
The teaching point: complaint volume is not the same as customer pain, and text data is not just sentiment. The useful move is to connect timing, product mix, company concentration, and the actual words consumers used.
Narratives shown here are consumers' words published by the CFPB after opt-in consent and personal-information scrubbing. CFPB redactions such as XXXX are intentionally left visible.
Loading the CFPB snapshot...

View 1

Complaint volume changed shape after credit reporting took over

The overall line gives the first warning: the database becomes a different instrument after 2022. Pins mark incidents that students can use as anchors, but the biggest story is the denominator changing under every share metric.

View 2

Not every spike means the same thing

Choose a story. The chart changes from a single company-product spike, to a bureau concentration view, to a policy-transition workload, to a small but severe fintech narrative signal.

View 3

The structural-shift trap is visible only when share and volume are paired

Credit reporting becomes the dominant complaint category. The switch below shows why "mortgage share fell" is a weak conclusion unless the denominator is handled first.

View 4

The narrative field explains what the count cannot

Use the filter like a lightweight ILIKE search. These are bounded examples from consented public narratives, not a representative sample of every consumer experience.

View 5

The same questions can be asked with plain SQL

The case is built to move from aggregates, to keyword search, to richer text representations. These snippets are the bridge from the page to a DuckDB exercise.

Pin a spike

SELECT company, year_month, n, z
FROM zscores_company_month
WHERE z > 3
ORDER BY z DESC;

Search the words

SELECT company, COUNT(*) AS n
FROM complaints
WHERE narrative_text ILIKE '%identity theft%'
  AND date_received BETWEEN '2017-09-01' AND '2018-03-01'
GROUP BY company
ORDER BY n DESC;

Measure theme density

SELECT date_trunc('month', date_received) AS month,
       COUNT(*) AS n_total,
       COUNT_IF(narrative_text ILIKE '%forbearance%'
             OR narrative_text ILIKE '%CARES%') AS n_forbearance
FROM complaints
WHERE product = 'Mortgage'
GROUP BY 1
ORDER BY 1;