Skip to contents

What is the estimated trade matrix?

ct_get_data() gives you exactly what countries have reported to the UN Comtrade Database, and nothing else. The main database contains no estimates. That is usually what you want — but it means a country that filed nothing simply is not there, and a world total built from reported data is silently short by however much that country trades.

The Trade Matrix endpoint is a separate analytical product that fills those gaps. It is maintained by the UN Statistics Division, which complements reported figures with estimates so that the reporter × partner matrix covers world trade completely. ct_get_trade_matrix() queries it.

How much difference does that make? For food and live animals (SITC section "0") in 2022:

trade_matrix_coverage
#>   reporters_total reporters_not_reporting value_share_not_reporting
#> 1             209                      43                0.03137914

43 of those 209 reporters have no reported figure anywhere in this matrix — every one of their flows is a UN estimate. They account for only about 3% of world section-"0" exports by value, but that is not the point. The point is which countries they are (the ten largest are shown here):

trade_matrix_nonreporters
#>    reporter_iso      reporter_desc total_value
#> 1           RUS Russian Federation 30656396940
#> 2           BLR            Belarus  4769748693
#> 3           HND           Honduras  3206227436
#> 4           FRO         Faroe Isds  2005628144
#> 5           SYR              Syria   965707332
#> 6           GRL          Greenland   902167565
#> 7           SDN              Sudan   649557587
#> 8           BGD         Bangladesh   611261071
#> 9           VEN          Venezuela   545125342
#> 10          GNB      Guinea-Bissau   500206359

If your analysis involves Russia, Belarus or Honduras in 2022, ct_get_data() will not tell you they are missing. It will just return a smaller matrix. That is the case for reaching for this endpoint.

One caution, which the next-but-one section explains: “no reported figure in this matrix” is not quite the same as “filed nothing”. Iraq also appears on the full list, yet it did file 2022 data with Comtrade — the matrix estimated its food exports regardless. Read the list as countries whose figures here are entirely estimated, and check individual cases against ct_get_data() when the distinction matters.

The rest of this vignette covers the things about it that are easy to get wrong.

Authentication

Just like ct_get_data(), this function needs a Comtrade API token. If you have not set one up yet, head over to the main comtradr vignette, which walks through obtaining and storing your key.

A basic call

Let’s build the world export matrix for food and live animals in 2022. Leaving reporter and partner at their default "everything" returns the full matrix, estimates included.

matrix_0 <- ct_get_trade_matrix(
  commodity_code = "0",
  flow_direction = "export",
  start_date = 2022,
  end_date = 2022
)

To keep this vignette fast and network-free, we do not call the API when it is built. The objects shown throughout were computed from that same query — data-raw/DATASET.R runs it once with include_world = TRUE, so that the World rows are available for the next section, and derives everything else from the bilateral rows.

trade_matrix_sample is a small illustrative slice — Montenegro’s exports in 2022, a single reporter whose partner flows mix reported values with UN estimates. It keeps only a handful of display columns:

str(trade_matrix_sample)
#> 'data.frame':    46 obs. of  11 variables:
#>  $ reporter_iso       : chr  "MNE" "MNE" "MNE" "MNE" ...
#>  $ reporter_desc      : chr  "Montenegro" "Montenegro" "Montenegro" "Montenegro" ...
#>  $ partner_iso        : chr  "SRB" "BIH" "_X " "ITA" ...
#>  $ partner_desc       : chr  "Serbia" "Bosnia Herzegovina" "Areas, nes" "Italy" ...
#>  $ flow_desc          : chr  "Export" "Export" "Export" "Export" ...
#>  $ cmd_code           : chr  "0" "0" "0" "0" ...
#>  $ cmd_desc           : chr  "Food and live animals" "Food and live animals" "Food and live animals" "Food and live animals" ...
#>  $ primary_value      : num  24883516 4757266 3567222 3031961 2064945 ...
#>  $ ref_year           : int  2022 2022 2022 2022 2022 2022 2022 2022 2022 2022 ...
#>  $ classification_code: chr  "SS" "SS" "SS" "SS" ...
#>  $ is_reported        : logi  FALSE FALSE TRUE TRUE FALSE FALSE ...
head(trade_matrix_sample)
#>   reporter_iso reporter_desc partner_iso       partner_desc flow_desc cmd_code
#> 1          MNE    Montenegro         SRB             Serbia    Export        0
#> 2          MNE    Montenegro         BIH Bosnia Herzegovina    Export        0
#> 3          MNE    Montenegro         _X          Areas, nes    Export        0
#> 4          MNE    Montenegro         ITA              Italy    Export        0
#> 5          MNE    Montenegro         DEU            Germany    Export        0
#> 6          MNE    Montenegro         MKD    North Macedonia    Export        0
#>                cmd_desc primary_value ref_year classification_code is_reported
#> 1 Food and live animals      24883516     2022                  SS       FALSE
#> 2 Food and live animals       4757266     2022                  SS       FALSE
#> 3 Food and live animals       3567222     2022                  SS        TRUE
#> 4 Food and live animals       3031961     2022                  SS        TRUE
#> 5 Food and live animals       2064945     2022                  SS       FALSE
#> 6 Food and live animals       1496952     2022                  SS       FALSE

The World rows, and why they will ruin your totals

This is the one that bites hardest, so it comes first.

The endpoint returns aggregate World rows — reporter code 0, partner code 0, ISO W00 — interleaved with the bilateral flows. They are the row margins, the column margins and the grand total of the matrix. They are labeled World in reporter_desc / partner_desc, but no flag column marks them as aggregates, so nothing stops you summing straight over them.

Here is what the full 2022 section-"0" export response actually contains:

trade_matrix_blocks
#>                              block n_rows  total_value
#> 1      World / World (grand total)      1 1.511925e+12
#> 2                  bilateral flows  18237 1.511925e+12
#> 3     partner = World (row margin)    209 1.511925e+12
#> 4 reporter = World (column margin)    243 1.511925e+12

Four blocks, each summing to the same world total. Add up primary_value over the response as it arrives from the API and you get four times the right answer.

ct_get_trade_matrix() therefore drops these rows by default. If you want the margins or the grand total, ask for them explicitly. (One exception: if you request partner = "World", that margin is the thing you asked for and is kept — the reporter margin and grand total are still dropped, so the result is still safe to sum.)

# bilateral flows only -- safe to aggregate (the default)
bilateral <- ct_get_trade_matrix(
  commodity_code = "0", flow_direction = "export",
  start_date = 2022, end_date = 2022
)

# margins and grand total included -- do NOT sum across the whole frame
with_margins <- ct_get_trade_matrix(
  commodity_code = "0", flow_direction = "export",
  start_date = 2022, end_date = 2022,
  include_world = TRUE
)

Do not try to filter these rows using is_aggregate. The column exists in the response, but this endpoint never populates it: it is FALSE on every row, World rows included. Reporter or partner code 0 is the only reliable marker.

What is_reported actually means

The obvious reading of is_reported is “TRUE if the country reported this, FALSE if the UN estimated it because the country did not report”. That reading is wrong, and it will lead you astray.

is_reported is a per-cell provenance flag:

  • TRUE — the value is the reporting country’s own figure.
  • FALSE — the cell was produced or adjusted by the UNSD estimation pipeline: extrapolated from a nearby reported year, derived by mirror inversion from partner data, manually adjusted for under-reported or confidential trade, or produced by redistributing non-specified partners.

Three consequences worth internalizing:

A country that reports fully still has FALSE cells. Germany reported 2022 in full, yet 77 of its 233 partner cells are flagged FALSE — and 60 of those agree with the reported figure to within rounding (a median difference of about $31,000 on values in the millions). Only 17 are revised materially, almost all upward. The flag marks passage through the estimation pipeline, not necessarily a changed number.

Aggregates inherit the flag. A TOTAL row is FALSE if any underlying section was estimated, even where every other section was reported as filed.

So the reported/estimated split is not “extra coverage”. Over the bilateral rows of our 2022 section-"0" matrix:

trade_matrix_totals
#>   is_reported  total_value n_flows
#> 1       FALSE 1.022290e+11    2476
#> 2        TRUE 1.409696e+12   15761

That FALSE bucket — about 7% of the total — is not “trade the UN estimated for countries that had not reported”. It is every cell that passed through the pipeline for any reason. Do not present it as the coverage gap; the coverage gap is the 43 countries shown at the top of this vignette.

To find those countries, group by reporter and ask whether any row is reported, rather than reading the flag row by row:

library(dplyr)

non_reporters <- matrix_0 |>
  group_by(reporter_iso, reporter_desc) |>
  summarise(any_reported = any(is_reported), .groups = "drop") |>
  filter(!any_reported)

Commodities

Commodities are classified by SITC, and classification_code is "SS" on every row — the “combined” SITC list that spans revisions. (The TM you see in the request URL is the endpoint’s dataset selector, not a classification.) The estimation itself is done on SITC Rev.3.

commodity_code accepts:

  • "TOTAL" — all commodities combined.
  • One-, two- and three-digit SITC codes, e.g. "0", "01", "011".
  • The five four-/five-digit codes the UN estimates in addition: "7812", "7841", "7851", "7852", "78531".
  • The level selectors "ag1" through "ag5", which return every code with that many digits. everything is a synonym for "ag1".
  • "all_levels", which returns the entire hierarchy at once.

The one-digit sections are:

Code SITC section
0 Food and live animals
1 Beverages and tobacco
2 Crude materials, inedible, except fuels
3 Mineral fuels, lubricants and related materials
4 Animal and vegetable oils, fats and waxes
5 Chemicals and related products
6 Manufactured goods classified chiefly by material
7 Machinery and transport equipment
8 Miscellaneous manufactured articles
9 Commodities not classified elsewhere

A second double-counting trap lurks here. "all_levels" returns TOTAL, the sections, and the two- and three-digit codes nested inside them — so summing across that result over-counts just as the World rows do. The function warns you when you ask for it. If you want “all ten sections”, use everything or "ag1", which return one level only.

Values, dates and flows

Only primary_value is populated, in current US dollars, following the usual Comtrade convention: CIF-type for imports, FOB-type for exports. The cifvalue, fobvalue, qty and net_wgt columns are always NA here. That is by design — the estimation is applied to trade value only, and quantity information is stripped from the source data.

Annual goods only. start_date and end_date must be plain years (yyyy), as a number or a string; 2022 and "2022" both work. Passing "2022-01" is rejected rather than quietly interpreted as a year. The API accepts at most 12 periods per query.

Imports and exports only. The trade matrix carries no re-exports or other flows, so flow_direction accepts "import", "export" or "everything". Other values from ct_get_ref_table('flow_direction') are rejected up front, rather than returning a confusingly empty result.

Caveats worth carrying into your analysis

These come from the UN’s own Note on the Trade Estimation:

  • Mirror-derived estimates “may not fully reflect the data collected and compiled by the countries”, because trade flows reported by partners do not match those reported by the compiling country.
  • The manual adjustment of under-reported and confidential trade “is based on the best judgment of the staff” at UNSD’s Business and Trade Statistics Section.
  • Aggregate totals from this matrix usually run slightly higher than the reported series.
  • Because the main database holds no estimates, ct_get_data() and ct_get_trade_matrix() are not interchangeable. Figures from the trade matrix should not be presented as reported statistics.

Finally, note that this endpoint is not part of the UN Comtrade public API documentation, which is why ct_get_trade_matrix() is marked experimental. Its field semantics may change without notice.

When publishing, cite the data source as “UN Comtrade”.

See also

  • ct_get_data() — the standard endpoint, which returns only reported values. Reach for it when you want exactly what countries filed, and for ct_get_trade_matrix() when a missing country would distort your answer.
  • The main comtradr vignette for token setup, lookup helpers and general usage.