If you've ever clicked through an "[INDEPENDENT EXPENDITURE REPORT]" on the FEC website, you've seen the schedule E rows: dozens of entries tagged SUPPORT or OPPOSE, each one naming a candidate, a dollar amount, a purpose, and a date. The format looks simple. It isn't.
This post is a field guide to the underlying data — the same rows that populate LobbyVault's independent expenditure pages — and the three analytical mistakes that turn otherwise good campaign-finance reporting into misleading numbers.
The Key Fields
The FEC's bulk independent expenditure file (independent_expenditure.csv) has a wide schema, but the columns that matter for most reporting are:
| Field | What it actually means |
|---|---|
cmte_id |
The spender (always a committee, usually a Super PAC) |
cand_id |
The targeted candidate — not who benefits, see below |
sup_opp |
S = supports candidate; O = opposes candidate |
exp_amo |
Amount of this expenditure (gross, not net) |
exp_dat |
Date the expenditure was disseminated |
pur |
Free-text purpose — "media", "direct mail", etc. |
pay |
Payee — who actually got paid (the vendor) |
file_num |
The parent filing, useful for amendments |
The sup_opp column is the single most important field and the most commonly misread.
Mistake 1: Treating "Oppose" as Hostile to the Candidate
An O (oppose) expenditure against Candidate X often helps Candidate X's opponent. When you aggregate by the cand_id field and call that number "spent on Candidate X," you can badly mislead readers.
The correct aggregation for "money that helped Candidate X" is:
SUM(exp_amo) WHERE cand_id = X AND sup_opp = 'S'
+ SUM(exp_amo) WHERE cand_id = Y AND sup_opp = 'O' -- for each opponent Y
LobbyVault's candidate pages try to surface both views separately: money for (supportive IE + opposition IE against opponents) and money against (opposition IE against the candidate + supportive IE for opponents). The raw FEC export only gives you one side; the flip has to be reconstructed.
Mistake 2: Summing Amendments Without Deduplication
FEC filings are amended frequently. When a committee files an amendment, it does not update the original rows — it files a new report, and the bulk data keeps both. If you simply SUM(exp_amo) across all filings you will double-count every amended row.
The standard defensive move is to partition by file_num and keep only the latest filing per transaction_id. A more conservative approach is to join against the form_type column and exclude rows from superseded reports.
If the numbers you're computing look too high compared to press coverage of a race, duplication from amendments is the first thing to check.
Mistake 3: Trusting Free-Text pur Fields
The pur (purpose) column is free-text. Committees write things like "media", "television", "TV", "MEDIA BUY", "ADVERTISING - CABLE", and "consulting" interchangeably. Building a "spending by category" chart by grouping on the raw pur string produces a long tail of near-duplicates.
A workable normalization is:
- Uppercase everything.
- Collapse any value containing
MEDIA,TV,CABLE,BROADCAST,RADIOintoMEDIA. - Collapse
MAIL,PRINT,POSTCARDintoDIRECT MAIL. - Collapse
DIGITAL,ONLINE,WEB,SOCIALintoDIGITAL. - Bucket
CONSULTING,STRATEGY,RESEARCHintoCONSULTING. - Everything else →
OTHER.
This is crude but survives contact with real FEC data.
Timing: When Do These Rows Appear?
Most independent expenditures are disclosed via 24-hour notices (filed within 24 hours of the expenditure) during the 20 days before an election, and 48-hour notices before that. Outside the pre-election window, they appear in quarterly reports.
This means the exp_dat field and the filing date can drift. If you're producing a time-series chart of IE activity, use exp_dat (the dissemination date) for the story-telling axis but sanity-check against file_num's filing date when looking for reporting anomalies.
Putting It Together
A defensible "top IE spenders of the cycle" query looks roughly like:
SELECT cmte_id,
SUM(CASE WHEN sup_opp='S' THEN exp_amo ELSE 0 END) AS supporting,
SUM(CASE WHEN sup_opp='O' THEN exp_amo ELSE 0 END) AS opposing,
SUM(exp_amo) AS total
FROM fec_independent_expenditure
WHERE exp_dat BETWEEN '2026-01-01' AND '2026-12-31'
AND is_superseded = 0 -- amendment handling
GROUP BY cmte_id
ORDER BY total DESC
LIMIT 100;
Most of LobbyVault's committee pages run a version of this query, then layer on the support/oppose breakdown per targeted candidate.
The Bottom Line
The FEC publishes enough data to answer almost any question about independent-expenditure spending — but the schema rewards readers who understand the distinction between cmte_id, cand_id, and the sup_opp flag, and who deduplicate amendments. Most of the headline numbers you see in campaign-finance coverage are a rough approximation; the interesting story is usually one layer deeper in the raw rows.