Python packages / tossd-reader / How-to guides
Filter by sector, purpose, channel, or modality
Filter activity records to a specific sector by passing a name or code to get_tossd(filters={"sector": ...}). The published data's sector vocabulary operates at a higher level of aggregation than the packaged sector codelist, with sub-sector codes folded into top-level groups.
Steps
-
Filter by sector name or code. The
filters=parameter resolves sector values identically toproviders=andrecipients=, attempting an exact code match first before falling back to case-insensitive name matching against the packaged codelist.import tossd_reader as tossd df = tossd.get_tossd( years=2024, columns="analysis", units="usd_million", filters={"sector": "I.2. Health"}, ) print(len(df)) print(round(df["usd_disbursement"].sum(), 1))53064 30794.2 -
Verify sector granularity before filtering on sub-codes. The published
sector_codecolumn contains 25 top-level groups (such as 110 Education, 120 Health, and 700 Humanitarian Assistance). The packaged codelist contains 50 entries, including sub-sector codes like 122 (I.2.b. Basic health) that reporting publishers fold into parent groups. Filtering directly on a folded sub-code returns an empty DataFrame with a warning.sub_health = tossd.get_tossd( years=2024, columns="analysis", units="usd_million", filters={"sector": "I.2.b. Basic health"}, )UserWarning: get_tossd's filters matched no rows; returning an empty (but correctly typed) frame. A codelist entry can sit at a finer granularity than the published data uses (sector sub-codes, for example, fold into their top-level group) -- compare against the column's own values, e.g. df['sector_code'].unique().Group-level name filtering requires the codelist's exact prefixed format. The DataFrame
sector_namecolumn contains"Health", whereas the codelist entry is prefixed as"I.2. Health".tossd.get_tossd(years=2024, filters={"sector": "Health"})UnknownCodeError: 'Health' did not match any sector code or name in the packaged codelist. Closest matches: I.2. Health, I.2.a. Health, general, I.2.b. Basic health, I.3. Population policies/programmes and reproductive health. -
Access sub-sector detail using purpose codes. Granular activity classifications are recorded in
purpose_code, which contains 303 distinct values in 2024 data. Filter onpurposeto query specific programme areas.basic_health = tossd.get_tossd( years=2024, columns="analysis", units="usd_million", filters={"purpose": "Basic health care"}, ) print(len(basic_health)) print(round(basic_health["usd_disbursement"].sum(), 1))5536 1883.1All matching rows retain
sector_code120 (Health). Purpose filtering refines the selection without altering the underlying sector code.print(sorted(basic_health["sector_code"].unique().tolist()))[120] -
Filter humanitarian assistance using the packaged name. Sector 700 is a DAC group heading omitted from the OECD source codelist. The packaged sector codelist includes it as a supplemental entry,
VIII. Humanitarian Aid, whichfilters=resolves like any standard sector.hum = tossd.get_tossd( years=2024, columns="analysis", units="usd_million", filters={"sector": "VIII. Humanitarian Aid"}, ) print(len(hum)) print(round(hum["usd_disbursement"].sum(), 1))40668 47179.8The name resolves to code 700 before filtering, making
filters={"sector": 700}equivalent. The plain-English label"Humanitarian Assistance"present insector_namedoes not resolve directly infilters=, which matches against the packaged codelist spelling.tossd.get_tossd(years=2024, filters={"sector": "Humanitarian Assistance"})UnknownCodeError: 'Humanitarian Assistance' did not match any sector code or name in the packaged codelist. Closest matches: VI.3. Other Commodity Assistance, VIII. Humanitarian Aid.Sector 700 contains sub-codes in the packaged codelist that fold into the parent group. Filtering on sub-code 720 (
VIII.1. Emergency Response) returns an empty frame.sub_emergency = tossd.get_tossd(years=2024, filters={"sector": 720})UserWarning: get_tossd's filters matched no rows; returning an empty (but correctly typed) frame. A codelist entry can sit at a finer granularity than the published data uses (sector sub-codes, for example, fold into their top-level group) -- compare against the column's own values, e.g. df['sector_code'].unique().The
codes.browse("sector")table includes anin_published_datacolumn that identifies active codes directly.sector = tossd.codes.browse("sector") print(sector[sector["code"].isin(["700", "720", "730", "740"])].to_string(index=False))code name tossd_only source in_published_data 700 VIII. Humanitarian Aid False dac-sector-classification True 720 VIII.1. Emergency Response False codelist False 730 VIII.2. Reconstruction and Rehabilitation False codelist False 740 VIII.3. Disaster Preparedness False codelist FalseCode 700 is the aggregate group used in published data. Sub-codes 720, 730, and 740 fold into code 700 and do not appear in
sector_codeindependently. See How to look up codes and names for inspectingin_published_dataacross other dimensions. -
Rank sectors within a filtered dataset. Use
df.tossd.rank_entities()to sum, rank, and count activities per sector in a single call.sen = tossd.get_tossd( years=2024, recipients="Senegal", columns="analysis", units="usd_million" ) print(sen.tossd.rank_entities(dimension="sector", top=5).to_string(index=False))sector_code sector_name usd_disbursement n_activities share_pct rank 320 Industry, Mining, Construction 343.687506 104 15.697118 1 230 Energy 225.801199 127 10.312938 2 210 Transport & Storage 213.414997 124 9.747228 3 110 Education 213.233284 528 9.738928 4 310 Agriculture, Forestry, Fishing 175.566222 539 8.018574 5The
n_activitiescolumn appears when the DataFrame containstossd_id. See How to rank providers by disbursement for details on activity counting and aggregate-row exclusion. -
Count distinct activities across rows. The
rank_entitieshelper calculatesn_activitiesautomatically. When filteringsector_codemanually, usinglen()alone overstates activity counts because activities spanning multiple sectors or purposes occupy multiple rows.edu = sen[(sen["sector_code"] == 110) & ~sen["is_aggregate"]] print(len(edu))553Count unique activities using distinct
tossd_idvalues, excluding the"0000"placeholder used by publishers for bundled records lacking individual identifiers.print(edu.loc[edu["tossd_id"] != "0000", "tossd_id"].nunique())528The difference between row counts and activity counts varies by sector. In Senegal's 2024 data, education spans 553 rows across 528 distinct activities, whereas basic health care occupies 72 rows for 72 activities.
health = sen[(sen["purpose_code"] == 12220) & ~sen["is_aggregate"]] print(len(health), health.loc[health["tossd_id"] != "0000", "tossd_id"].nunique())72 72
Verify it worked
The n_activities count from rank_entities matches the manual distinct tossd_id calculation.
ranked = sen.tossd.rank_entities(dimension="sector")
edu_activities = ranked.loc[ranked["sector_code"] == 110, "n_activities"].item()
print(edu_activities == edu.loc[edu["tossd_id"] != "0000", "tossd_id"].nunique())
True
Troubleshooting
KeyErroronsector_codeorpurpose_code. The DataFrame was queried withcolumns="minimal". Re-query withcolumns="analysis"or"all", or include the required column in an explicitcolumns=list.- A sector filter returns an empty frame with a
UserWarning. The requested codelist entry sits at a finer granularity than published data. Compare againstdf["sector_code"].unique(), or filter bypurposeinstead (step 3).
See also
- How to look up codes and names for browsing codelists and resolving tokens before filtering.
- How to rank providers by disbursement for
rank_entitiesmechanics,n_activities, and aggregate-row handling. - Columns, presets, and units for full column specifications.