ONS Trade Data

All queries run in the browser from pre-generated JSON files — no server required.

Query Builder

Compose any multidimensional query. Change the controls and press Run.

Code
engine
  .query('country', 'DE')
  .groupBy('commodity_code', 'commodity_name')
  .aggregate({ value_gbp: 'sum' })
  .sortBy('value_gbp_sum', 'desc')
  .limit(20)
  .run()

Direct File Access

Every data file is a plain JSON array served as a static asset. You can fetch it directly without the query engine — useful for loading all records for a country or commodity into your own code.

Code
const res = await fetch('/data/trade-by-country/de.json');
const records = await res.json();
// records is a TradeRecord[]

Example Query Recipes

These are four analytical patterns examples — shown here as query engine code you can copy and adapt.

Partner Reliance

Which countries supply a given commodity? Identifies concentration risk.

engine
  .query('commodity', '28')
  .filter({ flow: 'import', year: 2024 })
  .groupBy('country_code', 'country_name')
  .aggregate({ value_gbp: 'sum' })
  .compute('share_pct', (row, all) => {
    const total = all.reduce((s, r) =>
      s + (r.value_gbp_sum as number), 0);
    return total > 0
      ? Math.round(((row.value_gbp_sum as number)
          / total) * 10000) / 100
      : 0;
  })
  .sortBy('value_gbp_sum', 'desc')
  .limit(20)
  .run()

Export Growth Discovery

Which commodities are growing fastest by value with a given partner? Use two queries — one for recent periods, one for the previous window — then join on commodity_code.

// Recent window (last 12 periods)
const recent = await engine
  .query('country', 'US')
  .filter({ flow: 'export', dateFrom: '2024-01-01' })
  .groupBy('commodity_code', 'commodity_name')
  .aggregate({ value_gbp: 'sum' })
  .run();

// Previous window
const prev = await engine
  .query('country', 'US')
  .filter({ flow: 'export', dateFrom: '2023-01-01', dateTo: '2023-12-31' })
  .groupBy('commodity_code')
  .aggregate({ value_gbp: 'sum' })
  .run();

// Join + compute growth client-side
const prevMap = new Map(
  prev.map(r => [r.commodity_code, r.value_gbp_sum])
);
const growth = recent.map(r => {
  const p = (prevMap.get(r.commodity_code) as number) ?? 0;
  return {
    ...r,
    growth_pct: p > 0
      ? ((r.value_gbp_sum as number) - p) / p * 100
      : null
  };
}).sort((a, b) =>
  (b.growth_pct as number) - (a.growth_pct as number));

Trade Balance Breakdown

Net trade (exports − imports) with a country, broken down by commodity.

// Group by commodity + flow, then pivot
const rows = await engine
  .query('country', 'DE')
  .filter({ year: 2024 })
  .groupBy('commodity_code', 'commodity_name', 'flow')
  .aggregate({ value_gbp: 'sum' })
  .run();

const map = new Map();
for (const r of rows) {
  const e = map.get(r.commodity_code) ?? {
    commodity_code: r.commodity_code,
    commodity_name: r.commodity_name,
    imports: 0, exports: 0
  };
  if (r.flow === 'import') e.imports += r.value_gbp_sum;
  else e.exports += r.value_gbp_sum;
  e.net = e.exports - e.imports;
  map.set(r.commodity_code, e);
}

const balance = [...map.values()]
  .sort((a, b) => a.net - b.net); // deficit-first

Anomaly Detection

Flag statistically unusual trade values using z-score. Records where |z| ≥ threshold are outliers.

const rows = await engine
  .query('country', 'CN')
  .filter({ flow: 'export' })
  .compute('z_score', (row, all) => {
    const vals = all.map(r => r.value_gbp as number);
    const mean = vals.reduce((s, v) => s + v, 0) / vals.length;
    const std = Math.sqrt(
      vals.reduce((s, v) => s + (v - mean) ** 2, 0) / vals.length
    );
    return std > 0
      ? Math.round(
          ((row.value_gbp as number) - mean) / std * 100
        ) / 100
      : 0;
  })
  .sortBy('z_score', 'desc')
  .run();

const outliers = rows.filter(r =>
  Math.abs(r.z_score as number) >= 2.5);