Python packages / tossd-reader / How-to guides
Split Pillar II into sub-pillars
Filter to Pillar II.A or II.B with pillars=, then split Pillar II by sub-pillar and year with df.tossd.subpillar_breakdown(). Review sub-pillar coverage when comparing years, distinguishing between row-share and value-share metrics.
Steps
-
Filter Pillar II activities with
pillars=. Pillar II.A accepts21,"21", or"II.A". Pillar II.B accepts22,"22", or"II.B". Matching is case-insensitive, so"ii.a"also works. All three forms of II.A return identical rows.import tossd_reader as tossd a1 = tossd.get_tossd(years=2024, pillars=21, columns="minimal", units="usd_million") a2 = tossd.get_tossd(years=2024, pillars="21", columns="minimal", units="usd_million") a3 = tossd.get_tossd(years=2024, pillars="II.A", columns="minimal", units="usd_million") print(a1.shape, a2.shape, a3.shape) print(a1.equals(a2), a1.equals(a3))(59746, 19) (59746, 19) (59746, 19) True TrueQuerying both sub-pillars for 2024 provides the disbursement breakdown between II.A and II.B.
b = tossd.get_tossd(years=2024, pillars="II.B", columns="minimal", units="usd_million") print(round(a1["usd_disbursement"].sum(), 1), round(b["usd_disbursement"].sum(), 1))76510.1 54782.7get_tossd()keeps the publisher's aggregate rows (is_aggregate) by default, so these totals exceed the II.A and II.B amounts in step 5, wheresubpillar_breakdown()drops them. -
Check sub-pillar coverage by row before comparing years. Sub-pillar tagging first appears as trace data in 2022 and rolled out from 2023. The
tossd_subpillarcolumn isNAon any Pillar II row left untagged by the reporting provider, so.notna()calculates row-share coverage directly. Thedf.tossd.subpillar_breakdown()method (step 5) reports value-share coverage. Distinguish between these two metrics when citing coverage.p2 = tossd.get_tossd(years=2023, pillars=2, columns="minimal") print(round(p2["tossd_subpillar"].notna().mean() * 100, 1))50.6p2 = tossd.get_tossd(years=2024, pillars=2, columns="minimal") print(round(p2["tossd_subpillar"].notna().mean() * 100, 1))99.1 -
Review warnings emitted by default-years sub-pillar queries. When
years=None(the default), a sub-pillar filter narrows automatically to 2023 onward, and a secondary warning notes the 2023 coverage gap.ii_a = tossd.get_tossd(pillars=21, columns="minimal")UserWarning: Sub-pillar filters are only meaningful from 2023 onward; narrowing the default years [2019, 2020, 2021, 2022, 2023, 2024] to [2023, 2024]. Pass years= explicitly to request years before 2023 (raises InvalidPillarError for a sub-pillar filter). UserWarning: 2023 sub-pillar tagging is incomplete: roughly 49% of 2023 pillar-2 rows carry no sub-pillar tag (the rollout wasn't yet complete that year). Treat 2023 sub-pillar splits as indicative, not reliable; 2024 onward is complete. -
Handle pre-2023 queries with sub-pillar filters. Automatic narrowing applies only when
years=None. Specifying an explicit year prior to 2023 alongside a sub-pillar filter raises anInvalidPillarError.tossd.get_tossd(years=2021, pillars="II.A", columns="minimal")InvalidPillarError: Sub-pillar filters (pillars=21/'II.A' or 22/'II.B') are not meaningful before 2023; requested year(s) [2021] predate that. -
Split Pillar II across years with
df.tossd.subpillar_breakdown(). This method isolates thetossd_pillar == 2subset internally, so query without apillars=filter. Every year appears in the result across tagged and untagged categories.p = tossd.get_tossd(years=[2023, 2024], columns="analysis", units="usd_million") p.tossd.subpillar_breakdown()year subpillar usd_disbursement share_pct coverage_pct 0 2023 II.A 60465.333985 48.168490 66.553006 1 2023 II.B 23077.865394 18.384516 66.553006 2 2023 Untagged 41985.614871 33.446994 66.553006 3 2024 II.A 66272.006313 54.441323 98.135964 4 2024 II.B 53189.954160 43.694641 98.135964 5 2024 Untagged 2269.110984 1.864036 98.135964The
share_pctcolumn reports each row's share of its annual Pillar II total. Thecoverage_pctcolumn reports the combinedII.A+II.Bshare of the same total, repeated across all three rows for that year as an annual summary. For 2023, value-share coverage is 66.6%, whereas row-share coverage in step 2 is 50.6%. Value-share coverage places greater weight on large tagged activities. State which metric you are citing.
Verify it worked
The three subpillar rows from subpillar_breakdown() partition every Pillar II row, so the sum of II.A, II.B, and Untagged disbursements reconstructs each year's Pillar II total (with aggregate rows excluded on both sides).
breakdown = p.tossd.subpillar_breakdown()
year_totals = breakdown.groupby("year")["usd_disbursement"].sum()
pillar_2 = p.loc[~p["is_aggregate"] & (p["tossd_pillar"] == 2)]
year_totals.round(1).equals(pillar_2.groupby("year")["usd_disbursement"].sum().round(1))
True
Troubleshooting
InvalidPillarError naming a year. You passed pillars=21/22 (or "II.A"/"II.B") with an explicit years= argument containing years before 2023. Omit years= to use the default 2023-onward window, or use pillars=2 to query all Pillar II rows across any year without sub-pillar splits.
See also
- Pillars and aggregate rows for sub-pillar rollout context and reporting instructions.
- Query reference for
get_tossdfilter options. - Verbs reference for
subpillar_breakdown()parameters and return structures.