Week 3 · Who matters, and why
Corridor Control
Global migration and flight networks
Two networks. One world. Migration is the UN count of where people born in one country now live, and the twin is a network of international flight routes, aggregated from airports to countries.
Thick corridors are not friendship. They are people.
What we asked: which countries broker global migration and asylum, and is that the same as which countries simply have the most people moving through them?
Drag to spin. Click a country to inspect it.
Loading the corridor data…
Heavy tails in migration
Three of the four tails on this page fail the test for a A pattern where the share of items with a value of at least k falls off as a fixed power of k, so most items have small values and a few have huge ones. It draws a straight line on log–log axes, but other patterns can look nearly straight too..
(2020 · follows the slider)
The fourth, flight partners, is not ruled out on its own terms, but the A spread whose logarithm follows the bell curve. Its tail is long, but it bends down on log–log axes where a power law runs straight. and A spread whose share falls by the same factor for each step up, so its tail dies off fast. still describe it better wherever the three can be told apart. The charts below show none of that, which is the reason for fitting rather than looking.
A. Distribution of partners
How many countries have how many partners. A partner is another country on the other end of a link, not a person. Partner counts are binned by doubling and each bar is countries per partner value, so a wide bin is not tall merely for being wide. Click a bar to select the largest country in it.
B. CCDF of partners
\htmlClass{x-deg-share}{P}(\htmlClass{x-deg-K}{K} \geq \htmlClass{x-deg-k}{k}), the share of countries with at least k partners. Click a point to select it.
Is it a power law? We fitted it
A straight-ish line on log–log axes is not evidence of a power law, and with 232 countries the axis only spans about two Factors of ten: two decades span a hundredfold range, such as 2 to 200.. So we fitted the tails: Choosing the model's numbers that make the data actually seen as likely as possible. for the exponent, the lower bound chosen by minimising the The largest gap between the share of real values below a point and the share the model predicts, over every point., a A test of whether data could plausibly have come from a model, by comparing it with fake datasets drawn from that model. against 500 synthetic datasets drawn from the fitted model, and a A test of which of two models makes the data more likely, and whether the difference is larger than chance. against the two distributions that also look straight here. The method is Clauset, Shalizi and Newman (2009); the code is analysis/week03_tails.py. The fits are for 2020, the null-model year, and stay there while the charts above follow the slider.
| Tail | \htmlClass{x-xmin}{x_{\min}} | \htmlClass{x-alpha-tail}{\alpha} | In tail | Fit p | Against the rivals |
|---|---|---|---|---|---|
| Origins per destination | 33 | 2.32 | 85 | 0.00 | lognormal fits better (p = 0.072) |
| People on a corridor | 126,665 | 2.05 | 393 | 0.01 | lognormal fits better (p = 0.026) |
| Destinations per origin | 33 | 2.95 | 124 | 0.03 | both fit better (p = 0.004, 0.000) |
| Flight partners | 27 | 2.93 | 57 | 0.21 | both fit better (p = 0.082, 0.077) |
Fit p is the share of synthetic datasets from the fitted power law that fit it worse than the real data does. Below 0.1 the power law is ruled out; above it the power law survives, which is not the same as winning. Three of the four are below it: origins per destination and people on a corridor solidly, destinations per origin more narrowly at 0.03. Flight partners alone survives, at 0.21, and still loses to both rivals. Where a rival can be told apart at all it is one of the two, and on this page's tails that is usually the lognormal.
Popular ≠ bridge. Big ≠ prestigious.
(2020 only · the null-model year)
Betweenness vs origins (in-degree), log–log
One point per country. Migration graph is Each link points one way, from one node to another. and Each link carries a number, such as how many people it stands for, not just whether it exists.; distance along a corridor is \htmlClass{x-inverse}{1} / \htmlClass{x-people}{\text{people}}, so a heavy corridor is a short step. Click any point to select that country; every chart and panel on the page follows.
A "path" here is a chain of migrant-stock corridors, A to B to C, not a route anyone travels. How often a node lies on the shortest paths between other pairs of nodes. counts how often a country sits on the strongest such chains linking other countries: a structural position in the stock network, not a measure of people passing through.
Big ≠ prestigious: rank by people, rank by PageRank
A score that a node earns from the nodes linking to it, each passing on a share of its own score, so a link from a high-scoring node counts for more. on the weighted migration graph, The chance that PageRank follows a link at each step rather than jumping to a random node. \htmlClass{x-alpha-damping}{\alpha} = \htmlClass{x-damping-value}{0.85}: a country's score is mostly what its senders hand it, and only a small constant share comes from anywhere at random.
Compared to what?
10 countries out of 232 broker more than their number of partners predicts when the corridor sizes are dealt out at random.
(null model) (analysis year: 2020)
That takes both a How far a value lies from what chance would give, measured in standard deviations, the typical size of chance's swings. The farther from 0, the less likely chance alone produced it. of 2 or more against the null and excess betweenness above a twentieth of the leader's (the United States). Either test alone is looser: 12 countries clear \htmlClass{x-z}{z} = 2 on its own, but two of them, Guam and Réunion, do it on an excess so small it is noise around a near-zero base, which is what the second test catches. The rest sit inside what a shuffled network produces, which is why the scatter carries a null model beside it rather than a ranking. The null keeps each country's number of partners fixed but not its total corridor weight, so a country with large corridors can still score high partly because of their size: 7 of the top 10 by z sit at or above the 86th percentile of mean corridor weight.
The null-model scatter and the twin map share the same country selection: click either one and the other follows.
Betweenness z-score vs origins (in-degree), log x
The null keeps every country's A node's number of incoming links (in-degree) and of outgoing links (out-degree). and deals the observed corridor weights back out at random. Click any point to select that country.
Reading the map, and flights as a proxy for people
Migration shows where people live. Flights show where you can go. Use the toggle to compare them, and click a country to select it. The scatter on the left highlights the same one. Net drops the corridors and colours each country instead: green where more people live there than have left it, red where more have left than arrived. It is a balance of two stocks, not a count of anybody moving this year, and it follows the year slider.
How good is a route as a proxy for people? Measured, for the one country where it can be. US BTS publishes passengers on every international segment with an American airport at one end, and joining it to this same route table for 2019 gives 87 countries to compare. Route count ranks them well (A correlation computed on ranks: 1 when two orderings agree exactly, 0 when they are unrelated. 0.87, A 95% confidence interval: the range that would hold the true value in 95 of 100 repeats of the study. 0.81 to 0.92) and is poor at size: among the 58 countries with five routes or more, excluding Venezuela, one route carries anywhere from 25,014 people a year (Nigeria) to 362,869 (Hong Kong), a factor of 14.5; the middle half sit between 60,125 and 186,353. Read this network as a ranking of access and never as a volume, and treat the bottom of it carefully: Venezuela sits lower still, at 14 routes for 8,769 passengers each, but it is excluded from that factor because the snapshot still lists services that had stopped flying. analysis/week03_passengers.py.
Roles inside the communities
42 countries changed the part they play between 1990 and 2024.
Venezuela moved from A role: not a hub, with most of its links inside its own community. to A role: a hub, large inside its own community, with most of its links staying inside it.: large inside one A group of nodes joined by more and heavier links among themselves than to the rest of the network., barely present in the others. Colombia moved from A role: not a hub, with nearly all of its links inside its own community. to peripheral, keeping most of its corridors inside one community but gaining a few outside it. The chart below gives every country two coordinates, and the year slider moves them.
Section 8 splits the world into communities once, with a single seed. Roles are steadier than that one partition: each year is repartitioned 100 times and a country's \htmlClass{x-z-within}{z} and \htmlClass{x-p-part}{P} are averaged over the runs that agreed on its role, from Guimerà and Amaral (Nature 433, 2005): \htmlClass{x-z-within}{z}, how large a country is among the other members of its own community, and \htmlClass{x-p-part}{P}, how evenly its corridors are spread across all of them. A country with \htmlClass{x-p-part}{P} near zero keeps everyone inside one community; near one, it is split evenly between them. The seven names and the cut-offs between them are theirs, drawn on the chart rather than applied out of sight.
How steady the roles are, and what they replaced
This replaces six labels of our own that compared a 2020 migration ranking against a flight snapshot last refreshed around 2014. When we labelled them, 30 of the 228 countries then in the data carried a label that mixed the two vintages, and because the flight side cannot move, the section could only ever show one year. Nothing here touches the flight network, so the slider moves it.
Edge inspector
(one link at a time)
Let's analyse Denmark
(2020 only · the null-model year)
Denmark is where this starts because it is where we are, but the dropdown takes any of the 238 countries and territories in the table, and it follows whatever you have selected elsewhere on the page. Six of them have no migration figures in the selected year and say so instead of drawing.
A. Denmark, in and out
Migrant stock, 2020
B. Denmark through time
Incoming and outgoing people, 1990–2024
C. Bridge rank, 1990–2024
Where Denmark sits in the world's betweenness ranking, year by year · 1 = top bridge
D. Nearest neighbours
2020 · the four closest countries on the ground · origins, z-score, flight partners · click a bar
More ways to look at this
Six questions, answered from the same two files
The six questions
2 · Where do migrants live, and where were they born?
3 · Do migrants move far?
4 · Does wealth pull people, and how far?
5 · Is this the highly skilled, or everybody, and was it a choice?
6 · Who moves?
What is left after gravity
Size and distance predict some of the world's migration, and the corridors they miss are the interesting ones. Russia to Ukraine carries 158 times what the model expects. The list of corridors like it is the finding here, not the fit.
The corridors gravity cannot explain
The fit is not the finding. The What is left of an observation once the model's prediction is taken out. is. These carry the most people relative to what size and distance predict, among corridors of at least 50,000 people.
| Corridor | People | Gravity says | Ratio |
|---|---|---|---|
| Russia → Ukraine | 3,375,095 | 21,311 | ×158 |
| United Kingdom → New Zealand | 254,524 | 2,697 | ×94 |
| United Kingdom → Australia | 1,107,102 | 13,082 | ×85 |
| Sudan → Chad | 1,071,034 | 15,121 | ×71 |
| Russia → Tajikistan | 235,636 | 3,427 | ×69 |
| Palestine → Libya | 322,909 | 5,987 | ×54 |
| Russia → Belarus | 664,610 | 12,715 | ×52 |
| Russia → Uzbekistan | 865,767 | 17,190 | ×50 |
| Suriname → Kingdom of the Netherlands | 177,165 | 3,746 | ×47 |
| Venezuela → Peru | 1,596,667 | 36,830 | ×43 |
| Ukraine → Russia | 2,872,612 | 82,559 | ×35 |
| New Zealand → Australia | 588,088 | 17,116 | ×34 |
How the model is fitted, and the full table
Question 4 showed two things separately: rich countries hold more foreign-born people, and the people in them came from further away. Two charts is not a model, so neither one holds the other fixed. A model in which the flow between two places rises with the size of both and falls with the distance between them, as gravity does with mass and distance. does. It is the standard baseline in the migration literature and it is almost embarrassingly good: people on a corridor rise with the size of both ends and fall with the distance between them.
Fitted here by A way to fit a model to counts that keeps the zeros and does not take logs, so observations of zero still count. on all 8,487 corridors with population and income at both ends, in analysis/week03_gravity.py. That is the same 8,238 non-zero corridors question 5 uses, needing income at both ends and nothing else, plus 249 more: pairs that held people in an earlier snapshot, show zero for 2024, and still have the population and income data gravity needs. PPML keeps those zeros deliberately (see below). Counts rather than log counts, because logging drops every corridor carrying nobody and biases the rest. Each distance and population coefficient is an The per cent change in one quantity for a one per cent change in another.: a one per cent rise in the term moves the corridor by that many per cent. The under-1,000 km term is a yes/no shift, not an elasticity: it moves the corridor by a fixed share when the two centres are that close. Intervals are the An interval that stays honest when the scatter around the model is larger for some observations than for others. the method calls for, which statsmodels fits directly and scikit-learn could not report at all.
| Term | Elasticity | 95% interval |
|---|---|---|
| Distance between the two | −1.20 | −1.44 to −0.95 |
| Destination income per head | +0.82 | +0.66 to +0.98 |
| Destination population | +0.68 | +0.60 to +0.76 |
| Origin population | +0.50 | +0.42 to +0.58 |
| Origin income per head | −0.27 | −0.35 to −0.18 |
| Under 1,000 km apart | −0.30 | −0.79 to +0.20 |
The fit drops every corridor touching a country the World Bank does not price: Syria, South Sudan, Yemen, Eritrea, North Korea and Cuba among others. Those are war zones and sanctioned states, which is to say several of the origins whose corridors would have had the largest residuals. The model is fitted on the part of the world calm enough to be measured, and the missing part is 7.3% of the world's migrants.
Every term is measured in the same year as the outcome, so nothing here identifies a cause: a corridor thirty times over its prediction is a corridor worth explaining, not an explanation. The fit reaches \htmlClass{x-r2}{R^2} 0.45 across the range of the data and 0.17 on the raw counts, where a handful of enormous corridors dominate.
The bottom of the residual list, where gravity expects a crowd and DESA reports five people, is a map of which statistics offices file small origins under "other", not of where nobody went.
Does the world split into groups?
68.7% of the world's migrants move inside one of nine groups. Shuffle the network keeping every country's partner count and Louvain still returns almost the same A score for a split into groups: how many more links fall inside the groups than a random network with the same link counts would put there., so the amount of grouping says nothing on its own. Which countries land together does, and even that is a matter of degree: run Louvain from 100 more starting points and it agrees with the partition below on the group count more often than not, but rarely on the exact groups.
| Group | Countries | Largest members |
|---|---|---|
| Latin America, Iberia and the western Mediterranean | 37 | France, Spain, Venezuela, Colombia, Morocco |
| Germany, Russia and the post-Soviet belt | 35 | Germany, Russia, Ukraine, Italy, Poland |
| North America and the Pacific rim | 35 | United States, Mexico, People's Republic of China, Canada, Philippines |
| The former British settler network | 24 | United Kingdom, Australia, South Africa, New Zealand, Zimbabwe |
| South Asia and the Gulf | 24 | India, Saudi Arabia, Bangladesh, Pakistan, United Arab Emirates |
| The Horn and central Africa | 18 | Sudan, South Sudan, Democratic Republic of the Congo, Uganda, Ethiopia |
| West Africa, and ECOWAS free movement | 17 | Ivory Coast, Nigeria, Burkina Faso, Mali, Ghana |
| Turkey, the Levant and the Netherlands | 16 | Turkey, Syria, Jordan, Palestine, Kingdom of the Netherlands |
| Afghanistan and Iran | 2 | Afghanistan, Iran |
How the groups were found, and refugees against migrants
A course on social graphs asks this in week three and this post had not. Louvain on the undirected network, weighted by people moving in both directions and cut at corridors of 10,000 or more, in analysis/week03_communities.py. It returns nine groups over 208 countries, and they name themselves.
Migrants and refugees are not the same network
The same countries, two different graphs: UN DESA's stock against UNHCR's refugee counts, both 2024, from analysis/week03_country_networks.py. This was computed for the project weeks ago and had never reached the page.
| Measure | Migrants | Refugees |
|---|---|---|
| Countries | 232 | 204 |
| Corridors | 9,035 | 4,792 |
| People | 282m | 31m |
| Reciprocity | 0.41 (null 0.26 ± 0.004, z = +36.2) | 0.11 (null 0.07 ± 0.003, z = +10.9) |
| Clustering | 0.71 (null 0.67 ± 0.002, z = +17.2) | 0.60 (null 0.61 ± 0.004, z = −4.1) |
| Degree assortativity | −0.26 (null −0.33 ± 0.003, z = +22.0) | −0.34 (null −0.31 ± 0.004, z = −8.7) |
| Average distance | 1.75 (null 1.73 ± 0.001, z = +8.1) | 1.87 (null 1.84 ± 0.009, z = +3.2) |
| Diameter | 3 (null 3, no spread) | 4 (null 3.94 ± 0.237, z = +0.3) |
| Largest clique | 43 (null 27.31 ± 1.172, z = +13.4) | 20 (null 21.10 ± 0.985, z = −1.1) |
How often two neighbours of a node are also linked to each other, closing a triangle., Whether well-linked nodes tend to link to other well-linked nodes (above 0) or to sparsely linked ones (below 0, called disassortative)., average distance, The longest shortest path: the most steps needed to get between any two nodes. and the largest A group in which every pair shares a link. are all computed on the undirected, unweighted core: whether two countries share any corridor, not how many people or how many corridors. The null is 100 degree-preservingA shuffle step: take links A–B and C–D and rewire them to A–D and C–B, so every node keeps its number of partners. shuffles of that same core, from analysis/week03_country_networks.py; \htmlClass{x-z}{z} is (\htmlClass{x-z-real}{\text{real}} - \htmlClass{x-null-mean}{\text{null mean}}) / \htmlClass{x-null-sd}{\text{null standard deviation}}. The share of one-way links whose reverse also exists. needs the direction, so it uses the directed network and 100 directed shuffles that keep every country's number of origins and of destinations, from analysis/week03_reciprocity.py. The migrant network's diameter never moved across 100 shuffles, so it has no z to report.
The two largest cliques, by name
Migrants (43 countries): Argentina, Australia, Austria, Belgium, Bolivia, Brazil, Bulgaria, Canada, Chile, Colombia, Costa Rica, Croatia, Cyprus, Denmark, Estonia, Finland, France, Germany, Greece, Hungary, Iceland, Israel, Italy, Kingdom of the Netherlands, Latvia, Lithuania, Luxembourg, Mexico, Norway, People's Republic of China, Peru, Poland, Portugal, Romania, Russia, Slovakia, Slovenia, South Africa, Spain, Switzerland, United Kingdom, United States, Venezuela: every pair among them shares at least one migration corridor in either direction.
Refugees (20 countries): Brazil, Canada, Egypt, Eritrea, Ethiopia, Germany, Iraq, Italy, Ivory Coast, Kingdom of the Netherlands, Lebanon, Morocco, Norway, Somalia, Sudan, Sweden, Syria, United Kingdom, United States, Yemen: a mix of asylum countries and the origins that send them refugees, each pair connected by a corridor.
| # | Where migrants live | Where refugees are |
|---|---|---|
| 1 | United States | Iran |
| 2 | Germany | Turkey |
| 3 | Saudi Arabia | Germany |
| 4 | Canada | Uganda |
| 5 | United Kingdom | Pakistan |
| 6 | Spain | Chad |
| 7 | France | Poland |
| 8 | Australia | Ethiopia |
| 9 | United Arab Emirates | Bangladesh |
| 10 | Russia | Sudan |
Ranked by people. Of the top ten in each, only Germany appears in both. The migrant list is the rich world; the refugee list is the countries next door to a war, and eight of its ten sit below the unweighted average GDP per head of the 199 countries with World Bank figures, $22,441.
Why this page weights its betweenness
The methods note says that betweenness on the raw matrix ranks statistics offices rather than countries. Here is the evidence, from the same script: unweighted, undirected betweenness on the 2024 network, the same measure and the same year, with a rising floor on how big a corridor has to be to count. The bottom row switches to the measure the rest of this page actually uses: weighted, directed, 2020.
| Corridor floor | Corridors left | Top brokers |
|---|---|---|
| 0 | 9,035 | Australia, Norway, United States, Denmark, Greece |
| 1,000 | 3,767 | Australia, Canada, France, United States, Italy |
| 10,000 | 1,780 | Canada, France, United States, Australia, Italy |
| 50,000 | 769 | France, United States, United Kingdom, Spain, Russia |
| 100,000 | 503 | United States, France, Germany, United Kingdom, Russia |
| weighted, directed, 2020 | 9,031 | United States, United Kingdom, Germany, France, Spain |
What surprised us
Betweenness on the raw, unweighted matrix does not rank countries at all: it ranks statistics offices. Australia, Norway and Denmark top it at a corridor floor of zero because their population registers name every origin however small, so hundreds of one-person corridors count as paths that a survey-based country would never report. Raise the floor and two of the three, Norway and Denmark, vanish immediately; Australia holds on two floors longer. Big ≠ prestigious carried the same lesson a different way: a country's PageRank tracks who sends it people far more closely than it tracks how many people it holds in total, so size and structural importance are not the same question.
Four extra views, two of them from files the rest of the page does not load
The four extra views
1 · The network with the geography taken away
Both sliders change this chart only. Drag to pan, scroll to zoom, drag a country out of the pile, click one to select it.
2 · Who grew, 1990 to 2024
3 · A war, month by month
First-time asylum applications in 34 European countries, from Eurostat. One row is a year, one cell a month. Temporary protection is a different legal route and is not counted here, so most Ukrainians after February 2022 are missing. Two entries in the picker are not countries: Eurostat counts stateless applicants and applicants of unknown citizenship in their own rows, and they are kept because leaving them out would drop real people.
4 · The day the borders shut
One cell is one day, and the darker it is, the more countries were closed to arrivals from everywhere. Oxford tracker, top level of travel restriction.
Methods, and what this cannot tell you
Sources, methods and limits
Migration. UN DESA International Migrant Stock, revision 2024. An arc origin → destination carries the people born in the origin who live in the destination. Subtract two years and you get a change in where people live, not a count of who moved.
Betweenness. Weighted and directed, \htmlClass{x-distance}{\text{distance}} = \htmlClass{x-inverse}{1} / \htmlClass{x-people}{\text{people}}, so a heavy corridor is a short step. Run it unweighted and the top brokers come out as Australia, Norway and Denmark, which ranks statistics offices: a register names hundreds of tiny origins, a survey buckets them into "other".
Flights. OpenFlights routes, aggregated from airport pairs to country pairs and counted as distinct routes. One undated snapshot, last refreshed around 2014, so it sits still while the year slider moves. A route existing says nothing about seats or passengers, and week03_passengers.py measures what that costs: against US BTS passenger counts for 2019, route count ranks countries at Spearman 0.87 and misses volume by up to a factor of 14.5 (see the fineprint below). Degree and betweenness use the same formulas, \htmlClass{x-distance}{\text{distance}} = \htmlClass{x-inverse}{1} / \htmlClass{x-routes}{\text{routes}}, so the scatter compares one quantity across two networks.
Forced displacement. UNHCR Refugee Data Finder, end-2024: refugees plus asylum seekers on the same origin → destination pairs. It answers question 5's second half. The two counts do not nest perfectly (DESA estimates in the middle of 2024, UNHCR counts at the end of it), so on 281 fast-moving corridors, Sudan's most of all, the refugee count exceeds the stock and we cap it there. That makes a crisis corridor read low, never high. A further 2,811 UNHCR pairs, 1.7 million people with Eritrea to Germany among them, have no DESA stock row to attach to and are not on this page at all. Internal displacement is in the source and left out too: these are border crossings.
Roles. Guimerà and Amaral's cartography (Nature 433, 2005) in analysis/week03_cartography.py, on the same graph section 8 partitions: undirected, weighted by people both ways, corridors under 10,000 people dropped. Two coordinates per country, both computed on A node's total weight across its links, rather than its number of links. rather than on a count of partners, because a corridor here runs from a dozen people to four million. Louvain is stochastic, so each year is partitioned 100 times, every run votes for a role, and a country carries the role most runs gave it. The coordinates printed beside a role are averaged over the runs that produced that role and not over all of them: pooled, four countries came out with a participation coefficient on the wrong side of their own name. The thresholds are Guimerà and Amaral's own and are not tuned to this network, which is much denser than the ones they were set on, so peripheral stays the largest group. Tuning them to balance our own chart would be seven arbitrary numbers, which is what this replaced.
What the roles replaced. Six labels of our own, from a cascade of rank tests, three of which compared a 2020 migration ranking against the undated flight snapshot. When we labelled them, 30 of the 228 countries then in the data carried one, the section could only ever show a single year, and 141 of 228 came out "mixed". Better flight data would have fixed it and there is none: ICAO sells a dated global country-pair table, Sabre licenses one, Eurostat publishes a free dated one that only Europe reports into, and OpenSky is free, dated and CC BY but its coverage follows the ADS-B receivers, which sit in Europe and North America. Each trades a vintage problem for a coverage problem, so the roles now use no flight data at all.
Heavy tails. The Clauset, Shalizi and Newman procedure in analysis/week03_tails.py: maximum likelihood for the exponent, the lower bound by minimising the KS distance, 500 synthetic datasets for the goodness-of-fit p, and Vuong likelihood-ratio tests against lognormal and exponential on the same tail. Both rivals are fitted with the continuous approximation, and so is the power law inside the comparison, because a ratio between a probability and a probability density means nothing. Full output in analysis/week03_tails.json.
Why these four numbers changed. The fitting used to be our own code and is now the powerlaw package, which is the reference implementation of that procedure, and the swap found a bug we had been publishing. Our KS distance walked the sorted tail one observation at a time, so a degree value appearing ten times produced ten steps in the empirical curve against one flat stretch of the model, and that gap counted as distance. Degree sequences are nothing but ties: 230 countries share 86 distinct in-degrees. The exponent was never wrong, and corridor weights, which barely tie, were right to the digit. But the inflated distance moved the\htmlClass{x-xmin}{x_{\min}} that minimises it, so all three degree tails had been fitted above the wrong cut. Destinations per origin is the one that changes its mind: it used to read \htmlClass{x-p-fit}{p} = 0.88, a power law surviving comfortably, and it reads 0.03.
Which countries, and how many. Nine different counts appear on this page and all of them are right, so here they are in one place. 238 countries and territories are in the payload, the union of the migration and flight networks. 232 have migration figures and sit on at least one corridor. 208 have a flight route. 214 have any World Bank indicator and 199 have income per head, which is why the wealth charts say 175 once a population of 200,000 or more is also required. 208 survive the 10,000-person floor in the community graph and so carry a role in section 5, where 196 did in 1990. 185 report to the Oxford tracker, and 87 have both a flight route to the United States and passengers on it.
The countries without a price. Of the 39 with no income per head, the list is not random: Syria, South Sudan, Yemen, Eritrea, North Korea and Cuba, alongside two dozen small territories. Those are war zones, sanctioned states and dependencies, and the war zones are among the largest refugee origins on the page. Every income-based result here therefore excludes them: the gravity model, the wealth scatter and the income tiers all drop corridors touching a country the World Bank does not price, which is 7.3% of the world's migrants. It biases those results toward the parts of the world that are calm enough to be measured.
Reporting coverage. DESA's table is ragged. The United States names 59 origins in every year of the table; Denmark rises from 167 in 1990 to 192 by 2024, because one runs a survey with an "other" bucket and the other keeps a population register that adds countries as they appear. Every partner count on this page inherits that gap.
Monthly applications. Eurostat migr_asyappctzm, first-time asylum applicants of all ages and both sexes, by citizenship and reporting country, monthly since 2013. Reuse with attribution. Eurostat ships aggregate rows inside both dimensions (EU27 sits beside its own members, TOTAL beside every citizenship), and summing over them counts most of Europe twice, so both are dropped. Applications are not arrivals, temporary protection is a different route and is not counted, and Eurostat suppresses small cells. The origins shown here clear a 3,000-application floor, and pooled across 34 reporting countries a month that clears that floor always has at least one reporting country behind it, so no cell in this grid is actually a hole; the code keeps a month with no figure as null rather than zero in case a thinner origin ever needs it, but on this floor that case has not come up.
Border closures. Oxford Covid-19 Government Response Tracker, CC BY 4.0, indicator C8: policy toward foreign travellers on a 0 to 4 scale, one value per country per day, national rows only. Level 4 is a ban on all regions or a total closure. It is policy on paper rather than traffic, the panel is 185 countries and the number reporting moves from day to day, and the tracker stops at the end of 2022.
The map. Natural Earth 1:110m admin-0 boundaries, public domain. scripts/migration/build_world_outline.py keeps the ISO code and the name, rounds coordinates to two decimals and gets the file to 169 KB. Borders are here so you can see where a corridor lands, and claim nothing about any dispute.
Render variants. Six renderers and eleven style dimensions over one dataset. Only the drawing changes: a variant overrides the visuals it improves and inherits the rest. The libraries sit in the repository, so the page fetches no third-party JavaScript. Section 8 is the one exception to the renderer choice: its four extra views are ECharts whichever renderer you picked, and the megabyte that costs is fetched when you open the section and not before.
The basemap. NASA Blue Marble, public domain, offered in every renderer. On the 2D canvas we resample it through the orthographic projection at two thirds of the drawn size and scale it up, which keeps a drag smooth at no visible cost.
Statistics. Every interval on this page is computed rather than assumed. Correlations carry aA confidence interval for a correlation, computed after a change of scale that makes its spread close to a bell curve.; the weighted median of distance carries a percentile interval over 400 Copies of the data made by drawing from it at random, with repeats allowed; how a result varies across them shows its uncertainty. of the corridors, resampled by corridor because a corridor is the thing that was observed. The null model runs 100 shuffles, so the finest p it can express is one in a hundred and a z above about 2.5 is a floor rather than a measurement. Eleven invariants across the five data files run in tests/week03-data.test.mjs: totals agreeing between the two migration files, no aggregate codes leaking into a country list, and the forced-displacement cap still covering the number of corridors this page says it does.
Rebuilding this. scripts/rebuild_week03.py downloads the sources and recreates every file the page loads; --check lists what is committed and what made it. The raw inputs stay out of the repository. A style guide draws every component under every skin.
AI use. We built the harvest, the analysis and this page with Claude. Every number on the page comes out of a script in analysis/: week03_corridor_control.py for the networks, then _tails, _gravity, _communities, _country_networks, _asylum and _closures. The prose is typed by hand: tests/week03-prose.test.mjs pins the figures most likely to drift against the analysis output, so a rebuild that changes one of those gets caught before a reader does. It does not cover every number on the page.