Data and licences¶
This page documents where the bundled data and the FAOSTAT store come from, how they are transformed, and how to cite them.
FAOSTAT Detailed Trade Matrix¶
The trade data come from the FAOSTAT Detailed Trade Matrix (domain TM), published by the Food and Agriculture Organization of the United Nations (FAO): bilateral trade in about 560 agricultural products between about 220 countries and territories, from 1986. The library uses the bulk "normalized" file.
| Dataset page | https://www.fao.org/faostat/en/#data/TM |
| Bulk file | https://bulks-faostat.fao.org/production/Trade_DetailedTradeMatrix_E_All_Data_(Normalized).zip |
| Pinned release | data file dated 2025-12-18 |
| Retrieved | 2026-09-27 |
| Size | 420,650,070 bytes |
| SHA-256 | 75b7ff8de04e7ae01f4b68150353cc0961fcbd5b0497c5f81338844345e348ec |
| Licence | CC BY 4.0 |
The pinned values are in netviz_tools.datasets._registry.
Licence¶
FAO publishes its statistical databases under the Creative Commons Attribution 4.0 International licence (CC BY 4.0), as set out in the FAO database terms of use. You may copy, redistribute and adapt the data, including commercially, provided you give attribution in the required form.
The terms also say that you may not represent that FAO participated in, sponsored, approved or endorsed the way you use the data, or use the data in a way that suggests FAO endorses a company, product or service. netviz-tools is an independent project and is not affiliated with or endorsed by FAO. FAO databases may also contain third-party data under different terms; check the dataset notes on the FAOSTAT site if you redistribute the data.
Citing FAOSTAT¶
The terms of use require this citation format:
FAO. [year]. [dataset]. Accessed on [date]. [URL]. Licence: CC-BY-4.0.
where the year is the year of the dataset's last update. For the release pinned in netviz-tools 1.0.0 (data file dated 2025-12-18, retrieved 2026-09-27):
FAO. 2025. FAOSTAT: Detailed trade matrix. Accessed on 27 September 2026. https://www.fao.org/faostat/en/#data/TM. Licence: CC-BY-4.0.
netviz_tools.datasets.faostat.CITATION holds the template with {year} and {accessed} placeholders:
from netviz_tools.datasets import faostat
faostat.CITATION.format(year=2025, accessed="27 September 2026")
If you build your own store from a newer file, cite that file's update year and your own access date; both are recorded in the store manifest (see below).
The bundled sample¶
faostat.load_sample() reads a 0.6 MB Parquet file inside the package, cut from the pinned release by scripts/make_sample.py. It keeps quantity flows in tonnes for Wheat (item code 15), Maize (corn) (56) and Soya beans (236) from 2010 to 2024, reported by both importers and exporters. The file has 140,935 rows (72,012 importer-reported and 68,923 exporter-reported), of which 65 are self-loops that load_sample drops by default. It has the same columns as the store, so load_sample and load run the same query.
How the store is built¶
faostat.build_store() converts the bulk CSV into a Parquet dataset partitioned by item code, using DuckDB. The rules:
- Exporter is always the sender. FAOSTAT rows are written from the reporter's point of view ("Import quantity" reported by Egypt from partner Russian Federation, "Export quantity" reported by the Russian Federation to partner Egypt). Every row is re-oriented so that
exporteris the country the goods leave andimporterthe country they arrive in, whoever reported it. Areported_bycolumn keeps the reporter's side ("importer"or"exporter"). - Units are normalized. FAOSTAT's
An(animals) becomeshead;1000 Anis multiplied by 1000 and becomeshead;Nobecomesnumber. Tonnes staytand trade values stay1000 USD. Quantity and value rows are kept apart in ameasurecolumn. - Zero values are dropped. Rows with a value of zero or no value are not written.
- Self-loops are kept in the store (flows reported between a country and itself, such as re-imports) but
load()andload_sample()drop them unless you passself_loops=True. - Flags are kept. The FAOSTAT observation flag is stored and returned by
load(..., details=True).
load() then selects the reporting perspective (reporter="importer", "exporter" or "combined"), the measure, the items and the years. With "combined", the importer's report is used where one exists and the exporter's otherwise, decided per exporter, importer, item and year.
The store keeps FAOSTAT's own column names. load() and load_sample() return them under the library's generic flow-frame names:
| Store column | Returned as |
|---|---|
exporter, importer |
source, target |
year |
time |
item |
category |
value (for the chosen measure) |
weight |
unit |
unit |
exporter_code, importer_code (with details=True) |
source_code, target_code |
item_code, reported_by and flag keep their names. Each returned frame carries a copy of faostat.LABELS in flows.attrs["labels"] ("source": "Exporter", "target": "Importer", "time": "Year", "category": "Item", "weight": "Quantity", or "Value" with measure="value"), which graphs and plots use as display labels.
Why the reporter perspective matters¶
The two reports of the same flow often disagree, and some countries stop reporting. In the bundled sample, the Russian Federation's wheat exports look like this (million tonnes):
| Year | Importer-reported | Exporter-reported | Combined |
|---|---|---|---|
| 2020 | 26.82 | 37.27 | 32.87 |
| 2021 | 22.39 | 27.26 | 26.62 |
| 2022 | 22.44 | no rows | 22.44 |
| 2023 | 33.96 | no rows | 33.96 |
The Russian Federation reports no wheat exports after 2021, so a graph built from exporter-reported data shows no Russian wheat exports from 2022 on. The importer-reported default keeps them. For wheat in 2021, 155 importing countries report against 99 exporting countries.
Stale hashes and the manifest¶
FAO replaces the bulk file in place when it publishes an update: the URL stays the same and the content changes. The SHA-256 pinned in each netviz-tools release identifies the exact file it was built and tested against. When the file you download (or pass as zip_path=) does not match, build_store() and download() raise SourceHashMismatchError, which names the expected and actual hashes.
To build from the newer file anyway, opt in explicitly:
Every build writes manifest.json next to the data. faostat.store_info() returns it. It records:
source_urland whether the zip was downloaded or given as a local file;source_sha256, the SHA-256 actually used, withpinned_sha256andmatches_pinnedfor comparison;retrieved_at(the zip's modification time) andbuilt_at;rowsandrows_by_measure(row counts and year ranges per measure and reporter);items, the item codes and names found in the file;netviz_tools_versionand the licence.
So a store built from an unpinned file is still traceable to the exact bytes it came from.
Country metadata¶
faostat.countries() returns one row per FAOSTAT area that appears in the trade matrix (221 areas), built by scripts/build_metadata.py from three sources:
| Source | Used for | Licence |
|---|---|---|
| FAOSTAT Detailed Trade Matrix (pinned release above) | area codes, names, M49 codes; item catalogue | CC BY 4.0 |
| UNSD Standard country or area codes for statistical use (M49) | ISO3 codes, region, sub-region; the continent column |
see the UNSD site |
| Natural Earth 1:10m Admin 0 Countries v5.1.2 | label points (lon, lat) used as node positions in flow maps |
public domain |
continent is the M49 region, except that the Americas are split into Northern America, Central America, the Caribbean and South America. The item catalogue (faostat.items(), 558 items) also comes from the FAOSTAT file.
Manual overrides¶
Some FAOSTAT areas have no entry in the current M49 table or no admin-0 feature in Natural Earth: historical states, overseas departments, small islands. Each gap is filled by hand, and every override is listed in datasets/_meta/sources.json with its reason. There are 31:
| M49 | Area | Field | Reason |
|---|---|---|---|
| 058 | Belgium-Luxembourg | region | Belgium-Luxembourg (to 1999) is not in current M49; region of Belgium. |
| 058 | Belgium-Luxembourg | lon/lat | Historical area; label point of Belgium. |
| 074 | Bouvet Island | lon/lat | Not a separate Natural Earth admin-0 feature; approximate location. |
| 128 | Canton and Enderbury Islands | region | Canton and Enderbury Islands (historical) is not in current M49; now Kiribati. |
| 128 | Canton and Enderbury Islands | lon/lat | Historical area; label point of Kiribati. |
| 200 | Czechoslovakia | region | Czechoslovakia (to 1992) is not in current M49; region of Czechia. |
| 200 | Czechoslovakia | lon/lat | Historical area; label point of Czechia. |
| 230 | Ethiopia PDR | region | Ethiopia PDR (to 1992) is not in current M49; region of Ethiopia. |
| 230 | Ethiopia PDR | lon/lat | Historical area; label point of Ethiopia. |
| 254 | French Guiana | lon/lat | Part of France in Natural Earth admin-0; approximate centroid. |
| 312 | Guadeloupe | lon/lat | Part of France in Natural Earth admin-0; approximate centroid. |
| 396 | Johnston Island | region | Johnston Island (historical code) is part of US Minor Outlying Islands. |
| 396 | Johnston Island | lon/lat | Not a separate Natural Earth feature; approximate atoll location. |
| 474 | Martinique | lon/lat | Part of France in Natural Earth admin-0; approximate centroid. |
| 488 | Midway Island | region | Midway Island (historical code) is part of US Minor Outlying Islands. |
| 488 | Midway Island | lon/lat | Not a separate Natural Earth feature; approximate atoll location. |
| 638 | Réunion | lon/lat | Part of France in Natural Earth admin-0; approximate centroid. |
| 891 | Serbia and Montenegro | region | Serbia and Montenegro (1992-2005) is not in current M49; region of Serbia. |
| 891 | Serbia and Montenegro | lon/lat | Historical area; label point of Serbia. |
| 736 | Sudan (former) | region | Sudan (former, to 2011) is not in current M49; region of Sudan. |
| 736 | Sudan (former) | lon/lat | Historical area; label point of Sudan. |
| 158 | China, Taiwan Province of | region | M49 158 is not listed by UNSD; FAOSTAT places it in Eastern Asia with China. |
| 158 | China, Taiwan Province of | iso3 | ISO 3166-1 alpha-3 code; M49 158 is not listed in the UNSD table. |
| 772 | Tokelau | lon/lat | Not a separate Natural Earth admin-0 feature; approximate location. |
| 810 | USSR | region | USSR (to 1991) is not in current M49; region of the Russian Federation. |
| 810 | USSR | lon/lat | Historical area; label point of the Russian Federation. |
| 872 | Wake Island | region | Wake Island (historical code) is part of US Minor Outlying Islands. |
| 872 | Wake Island | lon/lat | Not a separate Natural Earth feature; approximate atoll location. |
| 890 | Yugoslav SFR | region | Yugoslav SFR (to 1991) is not in current M49; region of Serbia. |
| 890 | Yugoslav SFR | lon/lat | Historical area; label point of Serbia. |
| 744 | Svalbard and Jan Mayen Islands | lon/lat | Part of Norway in Natural Earth admin-0; approximate centroid. |
"Region of X" means the area borrows the M49 region and sub-region of X. "Label point of X" means it borrows X's Natural Earth label point. "Approximate" locations are hand-entered coordinates.