Maailmankartta on jakauma

Miksi jokainen tasokartta valehtelee, mistä Equal Earth luopuu ja miksi Mercator aikanaan voitti

YK:n yleiskokous suositti 4.9.2026 luopumista Mercatorin projektiosta. Mutta kartan vääristymä ei ole yksi luku — se on jakauma maapallon pinnan yli. Lasketaan se auki: pinta-alat, kulmat, väestöpainotus, vastaskenaariot ja se, mitä Mercator itse asiassa teki oikein.

matematiikka
geometria
avoin data
visualisointi
päätöksenteko
Author

Kristian Vepsäläinen

Published

6.9.2026

1 Uutinen, joka on oikeasti matematiikkaa

Yhdistyneiden kansakuntien yleiskokous hyväksyi 4. syyskuuta 2026 päätöslauselman, jossa jäsenvaltioita kehotetaan siirtymään pois Gerardus Mercatorin vuonna 1569 esittelemästä karttaprojektiosta ja käyttämään sen sijaan karttoja, jotka esittävät mantereiden pinta-alat oikeassa suhteessa. Päätöstä kannatti 164 maata, vastaan äänesti yksi (Yhdysvallat) ja kuusi pidättyi. Käytännössä suositus osoittaa vuonna 2018 julkaistuun Equal Earth -projektioon. Taustalla oli Afrikan unionin ja kansalaisjärjestöjen Correct the Map -kampanja, joka oli käynnistynyt jo elokuussa 2025.

Julkisessa keskustelussa asia on tiivistetty yhteen lukuun: “Afrikka on 14 kertaa Grönlantia suurempi, mutta kartalla ne näyttävät samankokoisilta.”

Tämä on tyypillinen esimerkki siitä, mistä tässä blogissa jatkuvasti puhutaan. Yksi luku on aina jonkin jakauman tiivistelmä, ja tiivistelmä hukkaa sen, mikä oikeasti on kiinnostavaa. Karttaprojektion vääristymä ei ole yksi luku. Se on funktio, joka saa eri arvon jokaisessa maapallon pisteessä — eli jakauma pallon pinnan yli. Ja kuten kohta nähdään, tuosta jakaumasta voi laskea useita eri “keskilukuja” riippuen siitä, painotetaanko maapinta-alaa, ihmisiä vai merialueita — ja vastaus siihen “kuinka paha Mercator on” muuttuu radikaalisti painotuksen mukana.

Tämä postaus on tarkoituksella ennenkaikkea yleissivistävä. Aihe ansaitsee sen. Mutta lopussa kerron silti, miksi täsmälleen sama matemaattinen ilmiö tulee vastaan yritysdatassa joka ikinen viikko — ja miksi useimmat organisaatiot eivät huomaa sitä.


2 Miksi tasokartta on aina kompromissi

Otetaan appelsiini. Kuori se yhtenä palana ja yritä litistää kuori pöydälle niin, ettei se repeä eikä veny. Se ei onnistu. Sinun on joko revittävä kuori tai venytettävä sitä.

Tämä ei ole kömpelyyttä. Se on todistettu matemaattinen mahdottomuus.

NoteFaktalaatikko 1: Gaussin Theorema Egregium (1827)

Carl Friedrich Gauss osoitti tutkielmassaan Disquisitiones generales circa superficies curvas (1827) tuloksen, jota hän itse kutsui nimellä Theorema Egregium — “merkittävä lause”.

Lause sanoo, että pinnalla on ominaisuus nimeltä Gaussin kaarevuus, joka säilyy muuttumattomana kaikissa muunnoksissa, jotka eivät veny eivätkä purista pintaa. Kaarevuus on siis pinnan sisäinen ominaisuus: se voidaan mitata pinnan päällä ryömimällä, ilman että pintaa katsotaan ulkopuolelta.

  • Pallon pinnan Gaussin kaarevuus on positiivinen: \(K = 1/R^2\), missä \(R\) on säde.
  • Tason Gaussin kaarevuus on nolla: \(K = 0\).

Koska \(1/R^2 \neq 0\), pallon pintaa ei voi kuvata tasolle venyttämättä. Ei nyt, ei koskaan, ei millään nerokkuudella. Jokainen maailmankartta on siis välttämättä väärässä jossakin — kysymys on vain siitä, missä ja miten.

Arkinen todiste samasta lauseesta: kun taivutat pizzapalan U:n muotoon poikkisuunnassa, se ei enää notku pituussuunnassa. Kaarevuuden säilyminen pakottaa sen jäykäksi.

Vastaskenaario: entä jos Maa olisi lieriö tai kartio? Silloin Gaussin kaarevuus olisi nolla, ja kartta voitaisiin tehdä täysin vääristymättä — sen voisi kirjaimellisesti leikata auki ja rullata tasoksi. Karttaprojektioiden koko ongelmakenttä on siis seurausta yhdestä ainoasta asiasta: Maa on likimain pallo.

Lähde: Gauss, C. F. (1827). Disquisitiones generales circa superficies curvas. Commentationes Societatis Regiae Scientiarum Gottingensis Recentiores 6, 99–146.

Koska kaikkea ei voi säilyttää, jokainen projektio valitsee mitä se säilyttää ja mistä se luopuu. Kolme klassista vaihtoehtoa:

  1. Konformisuus (kulmien säilyminen): pienet muodot pysyvät oikeina, mutta pinta-alat vääristyvät. Mercator on tätä.
  2. Ekvivalenssi (pinta-alojen säilyminen): pinta-alat ovat oikein, mutta muodot venyvät. Equal Earth ja Gall–Peters ovat tätä.
  3. Kompromissi: ei kumpaakaan täsmälleen, mutta kumpikaan ei myöskään mene pahasti pieleen. Robinson ja Winkel Tripel ovat tätä.

Ja tässä on olennainen asia, joka lähes aina jää uutisoinnista pois: konformisuus ja ekvivalenssi ovat toisensa poissulkevia. Ei ole olemassa karttaa, joka olisi molempia. Se ei ole tekninen puute vaan Theorema Egregiumin suora seuraus. Kun YK suosittelee siirtymistä pinta-alatarkkaan karttaan, se suosittelee samalla — väistämättä — siirtymistä karttaan, jossa muodot ovat väärin. Siitä tässä postauksessa tehdään numerot näkyviksi.


3 Miten vääristymää mitataan: kaksi lukua per piste

Tässä kohtaa on pakko selittää yksi käsite, koska koko loppu postaus nojaa siihen. Yritän tehdä sen ilman yhtään kaavaa ensin.

Kuvittele, että piirrät maapallon pinnalle hyvin pienen ympyrän — vaikka sadan kilometrin säteisen. Piirrä se sitten kartalle. Ympyrästä tulee kartalla lähes aina ellipsi: soikio, jolla on pitkä ja lyhyt akseli. Tämä soikio on nimeltään Tissot’n indikatriisi (ranskalaisen matemaatikon Nicolas Auguste Tissot’n mukaan, 1859).

Soikiosta luetaan kaksi lukua:

  • \(a\) = kuinka moninkertaiseksi etäisyydet venyvät soikion pisimmässä suunnassa
  • \(b\) = kuinka moninkertaiseksi etäisyydet venyvät soikion lyhimmässä suunnassa

Näistä johdetaan kaksi ymmärrettävää mittaria:

Mittari Kaava Mitä se tarkoittaa arkikielellä
Pinta-alakerroin \(s = a \cdot b\) Kuinka moninkertaiselta alue näyttää kartalla suhteessa todelliseen kokoonsa. Arvo 1 = oikein, 4 = näyttää nelinkertaiselta.
Suurin kulmavääristymä \(\omega = 2\arcsin\frac{a-b}{a+b}\) Kuinka monta astetta pahimmillaan kulmat vääristyvät. Arvo 0° = muodot oikein, 40° = muodot pahasti litistyneet tai venyneet.

Näiden kahden mittarin logiikka on suoraviivainen:

  • Jos \(a = b\) (soikio on ympyrä), kulmavääristymä on nolla. Kartta on siinä kohdassa konforminen — muodot ovat oikein.
  • Jos \(a \cdot b = 1\) (soikio on venynyt yhteen suuntaan täsmälleen sen verran kuin se on kutistunut toiseen), pinta-ala on oikein. Kartta on siinä kohdassa ekvivalentti.
  • Molemmat yhtä aikaa (\(a = b\) ja \(ab = 1\)) tarkoittaisi \(a = b = 1\), eli ei vääristymää lainkaan. Gaussin lauseen mukaan tämä on mahdotonta kaikkialla yhtä aikaa.

Nyt jokaisessa maapallon pisteessä on siis kaksi lukua. Miljardeja pisteitä, kaksi lukua kummassakin. Siitä syntyy jakauma.


4 Data ja menetelmät

4.1 Mitä dataa käytetään

Aineisto Lähde Käyttö
Maiden rajat (vektori) GISCO / Eurostat giscoR-paketilla Maa-alueiden rajaus, maittaiset pinta-alat
Maiden väkiluvut Maailmanpankin avoin API (SP.POP.TOTL) Väestöpainotettu vääristymäjakauma
Mantereet ja aluejaot giscoR::gisco_countrycode Ryhmävertailut
Projektiokaavat PROJ-kirjasto sf-paketin kautta Vääristymien laskenta

Kaikki lähteet ovat avointa dataa. Mitään lukuja ei ole simuloitu tai keksitty.

4.2 Miksi Monte Carlo -otanta

Vääristymä pitää laskea “koko maapallon yli”. Mutta maapallon pinnalla on äärettömän monta pistettä, joten integraali pitää approksimoida. Teen sen tasa-aluepainotteisella satunnaisotannalla: arvon pisteitä maapallon pinnalta niin, että jokainen neliökilometri on yhtä todennäköinen.

Tässä on yksi klassinen sudenkuoppa. Jos arpoisi leveyspiirin tasajakaumasta välillä \(-90^\circ \ldots 90^\circ\), napa-alueet ylipainottuisivat rajusti, koska leveyspiirit ovat siellä lyhyempiä. Oikea tapa on arpoa leveyspiirin sini tasajakaumasta:

\[\varphi = \arcsin(u), \quad u \sim \text{Tasajakauma}(-1, 1)\]

Tämä antaa tasaisen tiheyden pallon pinnalla. Koodissa tämä tarkistetaan erikseen.

4.3 Miten vääristymä lasketaan numeerisesti

En johda kaavoja käsin jokaiselle projektiolle. Sen sijaan lasken numeerisen derivaatan: siirrän pistettä hiukan itään, hiukan länteen, hiukan pohjoiseen ja hiukan etelään, katson kuinka paljon kartan koordinaatit muuttuvat, ja rakennan niistä ns. Jacobin matriisin. Matriisin singulaariarvot ovat täsmälleen Tissot’n soikion akselit \(a\) ja \(b\). Menetelmä toimii mille tahansa projektiolle, myös sellaisille (kuten Robinson), joita ei edes ole määritelty suljetussa muodossa vaan interpoloimalla taulukosta.

4.4 Yksi kriittinen normalisointi

Pinta-alakerroin \(s\) sisältää mielivaltaisen globaalin vakion: jos kartta piirretään kahta kertaa isommaksi, kaikki kertoimet nelinkertaistuvat, mutta mikään ei muutu suhteellisesti. Siksi vertailen aina suhteellista pinta-alavääristymää:

\[r(x) = \frac{s(x)}{g}, \qquad g = \exp\left(\overline{\log s}\right) \text{ maa-alueiden yli}\]

Toisin sanoen: kuinka moninkertaiselta tämä paikka näyttää verrattuna kartan “keskimääräiseen” maa-alueeseen. Arvo 3 tarkoittaa, että alue näyttää kolme kertaa liian isolta suhteessa muihin maa-alueisiin samalla kartalla. Pinta-alan säilyttävillä projektioilla \(r = 1\) kaikkialla. Tämä on normalisointivapaa suure: kahden paikan välinen suhde ei riipu siitä, miten kartta on skaalattu.

ImportantMitä ei voi tehdä ilman terra-pakettia

Kaikki tämän postauksen laskenta tehdään sf-paketilla ja PROJ-kirjastolla, ilman rasteripaketteja. Se on itse asiassa etu: rasteripohjainen vääristymäanalyysi olisi itsessään projektioriippuvainen, koska rasterin solut olisivat jossakin projektiossa. Piste-otanta pallon pinnalta on projektiovapaa lähtökohta.


5 Ympäristö ja apufunktiot

Code
library(here)
library(sf)
library(dplyr)
library(tidyr)
library(purrr)
library(ggplot2)
library(ggdist)
library(scales)
library(patchwork)
library(giscoR)
library(httr2)
library(jsonlite)
library(qs2)

# Puolustuksellinen maskauksenesto (MASS, igraph yms. voivat varastaa nämä)
select <- dplyr::select
filter <- dplyr::filter

source(here("R", "theme_kristian.R"))

DATA_DIR <- here("data", "karttaprojektiot")
if (!dir.exists(DATA_DIR)) dir.create(DATA_DIR, recursive = TRUE)

set.seed(20260909)

# Maapallon keskimääräinen säde metreinä (IUGG:n auktorisoitu keskisäde R_1).
# Käytetään palloa, ei ellipsoidia: projektioiden vertailussa pallo on oikea
# vertailukohta, ja osa projektioista (esim. Winkel Tripel) on määritelty
# vain pallolle.
R_MAA <- 6371007

CRS_PALLO <- "+proj=longlat +R=6371007 +no_defs"

# Vertailtavat projektiot. Kaikissa sama säde ja sama keskimeridiaani (Greenwich),
# jotta vertailu on reilu.
PROJEKTIOT <- c(
  "Mercator"        = "+proj=merc     +lon_0=0 +R=6371007 +no_defs",
  "Gall-Peters"     = "+proj=cea +lat_ts=45 +lon_0=0 +R=6371007 +no_defs",
  "Mollweide"       = "+proj=moll     +lon_0=0 +R=6371007 +no_defs",
  "Eckert IV"       = "+proj=eck4     +lon_0=0 +R=6371007 +no_defs",
  "Equal Earth"     = "+proj=eqearth  +lon_0=0 +R=6371007 +no_defs",
  "Robinson"        = "+proj=robin    +lon_0=0 +R=6371007 +no_defs",
  "Winkel Tripel"   = "+proj=wintri   +lon_0=0 +R=6371007 +no_defs",
  "Natural Earth"   = "+proj=natearth +lon_0=0 +R=6371007 +no_defs",
  "Plate carree"    = "+proj=eqc      +lon_0=0 +R=6371007 +no_defs"
)

# Luokittelu myöhempää tulkintaa varten
PROJ_TYYPPI <- c(
  "Mercator"      = "konforminen",
  "Gall-Peters"   = "pinta-alatarkka",
  "Mollweide"     = "pinta-alatarkka",
  "Eckert IV"     = "pinta-alatarkka",
  "Equal Earth"   = "pinta-alatarkka",
  "Robinson"      = "kompromissi",
  "Winkel Tripel" = "kompromissi",
  "Natural Earth" = "kompromissi",
  "Plate carree"  = "kompromissi"
)

stopifnot(setequal(names(PROJEKTIOT), names(PROJ_TYYPPI)))

Vääristymien laskentafunktio. Tämä on postauksen matemaattinen ydin, joten se on kommentoitu rivi riviltä.

Code
laske_vaaristymat <- function(lonlat, crs_kohde, R = R_MAA, delta_deg = 1e-4) {

  stopifnot(is.matrix(lonlat), ncol(lonlat) == 2)
  # Reunaehdot: numeerinen derivaatta ei saa ylittää päivämäärärajaa eikä napoja,
  # koska PROJ kiertää pituusasteen takaisin ja derivaatta hyppäisi.
  stopifnot(all(abs(lonlat[, 1]) <= 179.9), all(abs(lonlat[, 2]) <= 89.5))

  d <- delta_deg
  proj_apu <- function(m) sf::sf_project(CRS_PALLO, crs_kohde, m, keep = TRUE)

  pI <- proj_apu(cbind(lonlat[, 1] + d, lonlat[, 2]))   # itään
  pL <- proj_apu(cbind(lonlat[, 1] - d, lonlat[, 2]))   # länteen
  pP <- proj_apu(cbind(lonlat[, 1], lonlat[, 2] + d))   # pohjoiseen
  pE <- proj_apu(cbind(lonlat[, 1], lonlat[, 2] - d))   # etelään

  # Askel radiaaneina (keskeisdifferenssi -> 2 * delta)
  askel <- 2 * d * pi / 180
  phi   <- lonlat[, 2] * pi / 180
  cphi  <- cos(phi)

  # Osittaisderivaatat kartan koordinaateille
  dx_dlam <- (pI[, 1] - pL[, 1]) / askel
  dy_dlam <- (pI[, 2] - pL[, 2]) / askel
  dx_dphi <- (pP[, 1] - pE[, 1]) / askel
  dy_dphi <- (pP[, 2] - pE[, 2]) / askel

  # Jacobin matriisi suhteessa pallon PAIKALLISEEN ortonormaaliin kehykseen:
  # itä-suunnan kaarenpituus on R*cos(phi)*dlambda, pohjois-suunnan R*dphi.
  J11 <- dx_dlam / (R * cphi); J21 <- dy_dlam / (R * cphi)
  J12 <- dx_dphi / R;          J22 <- dy_dphi / R

  # J^T J:n alkiot -> singulaariarvot analyyttisesti (2x2, ei tarvita svd-silmukkaa)
  Ee <- J11^2 + J21^2
  Gg <- J12^2 + J22^2
  Ff <- J11 * J12 + J21 * J22
  jalki <- Ee + Gg
  det2  <- Ee * Gg - Ff^2

  diskr <- pmax(jalki^2 - 4 * det2, 0)          # numeerinen suoja
  lam1  <- (jalki + sqrt(diskr)) / 2
  lam2  <- pmax((jalki - sqrt(diskr)) / 2, 0)

  a <- sqrt(lam1)                                # Tissot'n soikion pitkä puoliakseli
  b <- sqrt(lam2)                                # lyhyt puoliakseli

  tibble(
    lon        = lonlat[, 1],
    lat        = lonlat[, 2],
    a          = a,
    b          = b,
    pinta_ala  = sqrt(pmax(det2, 0)),            # a * b
    omega      = 2 * asin(pmin((a - b) / (a + b), 1)) * 180 / pi
  )
}

5.1 Funktion validointi tunnetuilla arvoilla

Ennen kuin luotan tuloksiin, tarkistan funktion kolmella tapauksella, joiden oikea vastaus tiedetään käsin laskemalla. Tämä on se kohta, jossa pilkkuvirheet karsiutuvat.

Code
testipisteet <- cbind(
  lon = c(0, 25, -70, 120, 10),
  lat = c(0, 60, -33, 45, 78)
)

# 1) Mercator: pinta-alakerroin = 1/cos^2(phi), kulmavaaristyma = 0
v_merc <- laske_vaaristymat(testipisteet, PROJEKTIOT[["Mercator"]])
odotus_merc <- 1 / cos(testipisteet[, "lat"] * pi / 180)^2

stopifnot(max(abs(v_merc$pinta_ala - odotus_merc) / odotus_merc) < 1e-5)
stopifnot(max(v_merc$omega) < 1e-4)

# 2) Pinta-alatarkat projektiot: pinta-alakerroin = 1 kaikkialla
for (nimi in c("Equal Earth", "Mollweide", "Eckert IV", "Gall-Peters")) {
  v <- laske_vaaristymat(testipisteet, PROJEKTIOT[[nimi]])
  stopifnot(max(abs(v$pinta_ala - 1)) < 1e-4)
}

# 3) Plate carree: pinta-alakerroin = 1/cos(phi)
v_eqc <- laske_vaaristymat(testipisteet, PROJEKTIOT[["Plate carree"]])
stopifnot(max(abs(v_eqc$pinta_ala - 1 / cos(testipisteet[, "lat"] * pi / 180))) < 1e-5)

cat("Kaikki kolme validointitestiä läpäisty.\n")
Kaikki kolme validointitestiä läpäisty.
Code
cat("Mercator pinta-alakerroin 60. leveyspiirilla:",
    round(v_merc$pinta_ala[2], 3), "(käsin laskettu:",
    round(1 / cos(60 * pi / 180)^2, 3),")\n")
Mercator pinta-alakerroin 60. leveyspiirilla: 4 (käsin laskettu: 4 )

Funktio siis tuottaa täsmälleen ne arvot, jotka teoriasta seuraavat. Nyt sitä voi käyttää projektioihin, joille käsin laskeminen olisi työlästä.


6 Aineiston haku

Code
polku_maat <- file.path(DATA_DIR, "maat.qs")

if (!file.exists(polku_maat)) {
  maat_raaka <- giscoR::gisco_get_countries(
    year       = "2020",
    resolution = "20",
    epsg       = "4326"
  )
  qs2::qs_save(maat_raaka, polku_maat)
}
maat_raaka <- qs2::qs_read(polku_maat)

stopifnot(all(c("ISO3_CODE", "NAME_ENGL") %in% names(maat_raaka)))
stopifnot(nrow(maat_raaka) > 200)

# Mannerluokitus giscoR:n omasta koodikirjasta
koodit <- giscoR::gisco_countrycode |>
  select(ISO3_CODE, continent, un.region.name) |>
  distinct(ISO3_CODE, .keep_all = TRUE)

MANNER_FI <- c(
  "Africa" = "Afrikka", "Asia" = "Aasia", "Europe" = "Eurooppa",
  "Americas" = "Amerikat", "Oceania" = "Oseania", "Antarctica" = "Antarktis"
)

maat <- maat_raaka |>
  left_join(koodit, by = "ISO3_CODE") |>
  mutate(
    manner_en = coalesce(continent, un.region.name),
    manner    = unname(MANNER_FI[manner_en])
  ) |>
  filter(!is.na(manner), ISO3_CODE != "ATA")   # Antarktis erikseen, ks. alla

# Antarktis on poistettu, koska Mercatorin projektio ei kykene esittamään sitä
# lainkaan: kerroin kasvaa rajatta navalla. Tama on itsessään tulos, ei puute.
cat("Maita mukana:", nrow(maat), "\n")
Maita mukana: 240 
Code
cat("Ilman mannerluokitusta jääneet:",
    sum(is.na(unname(MANNER_FI[coalesce(maat_raaka$ISO3_CODE, "")]))), "\n")
Ilman mannerluokitusta jääneet: 257 
Code
print(count(st_drop_geometry(maat), manner, sort = TRUE))
# A tibble: 5 × 2
  manner       n
  <chr>    <int>
1 Afrikka     58
2 Amerikat    53
3 Aasia       50
4 Eurooppa    50
5 Oseania     29
Code
polku_vaesto <- file.path(DATA_DIR, "vaesto_worldbank.qs")

if (!file.exists(polku_vaesto)) {
  vastaus <- request("https://api.worldbank.org/v2/country/all/indicator/SP.POP.TOTL") |>
    req_url_query(format = "json", per_page = 20000, date = "2015:2024") |>
    req_retry(max_tries = 3) |>
    req_perform()

  raaka <- jsonlite::fromJSON(resp_body_string(vastaus), flatten = TRUE)

  stopifnot(length(raaka) == 2, is.data.frame(raaka[[2]]))

  vaesto_raaka <- raaka[[2]] |>
    as_tibble() |>
    select(ISO3_CODE = countryiso3code, vuosi = date, vakiluku = value) |>
    filter(!is.na(vakiluku), nchar(ISO3_CODE) == 3) |>
    mutate(vuosi = as.integer(vuosi)) |>
    group_by(ISO3_CODE) |>
    slice_max(vuosi, n = 1, with_ties = FALSE) |>
    ungroup()

  qs2::qd_save(vaesto_raaka, polku_vaesto)
}
vaesto_raaka <- qs2::qd_read(polku_vaesto)

stopifnot(nrow(vaesto_raaka) > 180)
stopifnot(all(vaesto_raaka$vakiluku > 0))

cat("Vakilukuja maittain:", nrow(vaesto_raaka),
    "| viimeisin vuosi:", max(vaesto_raaka$vuosi),
    "| yhteensa:", format(sum(vaesto_raaka$vakiluku), big.mark = " "), "\n")
Vakilukuja maittain: 260 | viimeisin vuosi: 2024 | yhteensa: 79 234 389 292 

6.1 Otannan tuottaminen

Code
polku_otos <- file.path(DATA_DIR, "maapisteet.qs")
N_TAVOITE  <- 60000    # maa-alueille osuvien pisteiden tavoitemaara

if (!file.exists(polku_otos)) {

  sf::sf_use_s2(TRUE)
  maat_yhd <- maat |> select(ISO3_CODE, NAME_ENGL, manner)

  arvo_pisteita <- function(n) {
    tibble(
      lon = runif(n, -179.9, 179.9),
      lat = asin(runif(n, -1, 1)) * 180 / pi   # tasa-alueotanta pallolla
    ) |>
      filter(abs(lat) <= 89.5)
  }

  kertyma <- list()
  saatu   <- 0
  kierros <- 0

  while (saatu < N_TAVOITE && kierros < 40) {
    kierros <- kierros + 1
    ehdokas <- arvo_pisteita(250000)

    ehdokas_sf <- st_as_sf(ehdokas, coords = c("lon", "lat"), crs = 4326, remove = FALSE)
    osuma      <- st_join(ehdokas_sf, maat_yhd, join = st_intersects, left = FALSE)

    osa <- osuma |> st_drop_geometry() |> as_tibble()
    kertyma[[kierros]] <- osa
    saatu <- saatu + nrow(osa)
  }

  maapisteet <- bind_rows(kertyma) |>
    distinct(lon, lat, .keep_all = TRUE) |>
    slice_head(n = N_TAVOITE)

  qs2::qd_save(maapisteet, polku_otos)
}
maapisteet <- qs2::qd_read(polku_otos)

stopifnot(nrow(maapisteet) > 50000)
stopifnot(all(!is.na(maapisteet$manner)))
stopifnot(all(abs(maapisteet$lat) <= 89.5), all(abs(maapisteet$lon) <= 179.9))

# Tarkistus: osumaosuuden pitaisi vastata maapinta-alan osuutta (n. 29 % ilman
# Antarktista noin 26-27 %). Jos ei, otanta on vinoutunut.
cat("Maapisteita:", nrow(maapisteet), "\n")
Maapisteita: 60000 
Code
print(maapisteet |> count(manner, sort = TRUE) |> mutate(osuus = n / sum(n)))
# A tibble: 5 × 3
  manner       n  osuus
  <chr>    <int>  <dbl>
1 Amerikat 18781 0.313 
2 Aasia    13935 0.232 
3 Afrikka  13291 0.222 
4 Eurooppa 10158 0.169 
5 Oseania   3835 0.0639
TipMiksi tämä tarkistus on tärkeä

Yllä oleva mannerjakauma on samalla otannan laadunvarmistus. Koska otanta on tasa-aluepainotteinen, pisteiden osuuksien pitää vastata mantereiden todellisia pinta-alaosuuksia. Jos Eurooppa saisi 20 % pisteistä, tietäisin heti, että leveyspiiriotanta on väärin — ja koko postauksen tulokset olisivat roskaa. Tällaiset “pitäisi olla suunnilleen X” -tarkistukset ovat halpoja kirjoittaa ja säästävät nolouksilta.


7 Tulos 1: Mercatorin vääristymä ei ole luku vaan jakauma

Lasketaan nyt vääristymä jokaiselle otospisteelle jokaisessa projektiossa.

Code
polku_vaar <- file.path(DATA_DIR, "vaaristymat.qs")

if (!file.exists(polku_vaar)) {
  koord <- as.matrix(maapisteet[, c("lon", "lat")])

  vaaristymat <- imap_dfr(PROJEKTIOT, function(crs, nimi) {
    laske_vaaristymat(koord, crs) |>
      mutate(
        projektio = nimi,
        ISO3_CODE = maapisteet$ISO3_CODE,
        manner    = maapisteet$manner
      )
  })

  # Normalisointi: suhteellinen pinta-alavaaristyma r = s / geometrinen keskiarvo
  vaaristymat <- vaaristymat |>
    group_by(projektio) |>
    mutate(r_pinta_ala = pinta_ala / exp(mean(log(pinta_ala)))) |>
    ungroup() |>
    mutate(
      log2_r = log2(r_pinta_ala),
      tyyppi = unname(PROJ_TYYPPI[projektio])
    )

  qs2::qd_save(vaaristymat, polku_vaar)
}
vaaristymat <- qs2::qd_read(polku_vaar)

stopifnot(all(is.finite(vaaristymat$r_pinta_ala)))
stopifnot(all(vaaristymat$omega >= -1e-9))

# Sanity: pinta-alatarkoilla r pitaa olla 1
tarkistus <- vaaristymat |>
  filter(tyyppi == "pinta-alatarkka") |>
  summarise(poikkeama = max(abs(r_pinta_ala - 1)))
stopifnot(tarkistus$poikkeama < 1e-3)

cat("Rivi\u00e4 yhteens\u00e4:", nrow(vaaristymat), "\n")
Riviä yhteensä: 540000 
Code
merc <- vaaristymat |> filter(projektio == "Mercator")

kvantiilit <- quantile(merc$r_pinta_ala, c(0.05, 0.25, 0.5, 0.75, 0.95, 0.99))

ggplot(merc, aes(x = r_pinta_ala)) +
  geom_histogram(bins = 90, fill = "#457b9d", colour = NA, alpha = 0.9) +
  geom_vline(xintercept = 1, colour = "#e63946", linewidth = 0.9) +
  geom_vline(xintercept = kvantiilit[["50%"]], colour = "#f4a261",
             linetype = "dashed", linewidth = 0.9) +
  scale_x_continuous(
    trans  = "log2",
    breaks = c(0.5, 1, 2, 4, 8, 16, 32),
    labels = c("0,5\u00d7", "1\u00d7", "2\u00d7", "4\u00d7", "8\u00d7", "16\u00d7", "32\u00d7")
  ) +
  labs(
    title    = "Mercatorin pinta-alavääristymä on jakauma, ei luku",
    subtitle = paste0(
      "Punainen viiva = kartan keskimääräinen maa-alue. Katkoviiva = mediaani (",
      formatC(kvantiilit[["50%"]], digits = 2, format = "f"), "\u00d7)."
    ),
    x = "Kuinka moninkertaiselta alue näyttää kartan keskimääräiseen maa-alueeseen verrattuna",
    y = "Otospisteiden lukumäärä",
    caption = "Aineisto: GISCO/Eurostat maarajat, 60 000 tasa-aluepainotettua otospistettä"
  ) +
  theme_kristian()
Figure 1: Mercatorin projektion suhteellinen pinta-alavääristymä maa-alueilla. Vaaka-akseli on logaritminen: jokainen askel oikealle tarkoittaa kaksinkertaistumista.

Miten tätä kuvaa luetaan. Vaaka-akselilla on se, kuinka monikertaiselta maapallon eri kohdat näyttävät Mercatorin kartalla verrattuna kartan keskimääräiseen maa-alueeseen. Pystyakselilla on, kuinka moni otospiste osui kuhunkin kohtaan. Punainen pystyviiva kohdassa “1×” on se kohta, jossa alue näyttää täsmälleen keskimääräiseltä. Sen vasemmalla puolella alueet näyttävät liian pieniltä, oikealla puolella liian isoilta. Akseli on logaritminen: joka kerta kun siirryt yhden merkinnän oikealle, luku kaksinkertaistuu. Se on tarpeen, koska muuten oikea häntä olisi niin pitkä, ettei kuvasta näkisi mitään.

Numerot:

Code
merc_yhteenveto <- tibble(
  Tunnusluku = c("5. persentiili", "Alakvartiili", "Mediaani", "Yläkvartiili",
                 "95. persentiili", "99. persentiili", "Maksimi",
                 "Ylin / alin (5% vs 95%)"),
  Arvo = c(
    kvantiilit[["5%"]], kvantiilit[["25%"]], kvantiilit[["50%"]],
    kvantiilit[["75%"]], kvantiilit[["95%"]], kvantiilit[["99%"]],
    max(merc$r_pinta_ala),
    kvantiilit[["95%"]] / kvantiilit[["5%"]]
  )
) |>
  mutate(Arvo = paste0(formatC(Arvo, digits = 2, format = "f"), "\u00d7"))

knitr::kable(merc_yhteenveto,
             caption = "Mercatorin suhteellinen pinta-alavääristymä maa-alueilla")
Mercatorin suhteellinen pinta-alavääristymä maa-alueilla
Tunnusluku Arvo
5. persentiili 0.57×
Alakvartiili 0.62×
Mediaani 0.76×
Yläkvartiili 1.34×
95. persentiili 3.85×
99. persentiili 8.98×
Maksimi 44.31×
Ylin / alin (5% vs 95%) 6.76×

Tässä on postauksen ensimmäinen olennainen havainto. Mercatorin “pahuudelle” ei ole yhtä lukua. Puolet maapallon maa-alueesta on melko lähellä oikeaa suhdetta, mutta jakauman oikea häntä on hyvin pitkä. Ero jakauman ylä- ja alapään välillä on monikymmenkertainen. Jos joku sanoo “Mercator liioittelee noin kaksinkertaisesti”, hän puhuu mediaanista ja jättää kertomatta, että osalle maapalloa kerroin on kymmeniä.

Ja tässä on sama asia karttana:

Code
hila <- expand.grid(
  lon = seq(-179.5, 179.5, by = 1),
  lat = seq(-84.5, 84.5, by = 1)
) |> as.matrix()

hila_v <- laske_vaaristymat(hila, PROJEKTIOT[["Mercator"]]) |>
  mutate(r = pinta_ala / exp(mean(log(merc$pinta_ala))))

rajat_sf <- maat |> st_geometry()

ggplot() +
  geom_tile(data = hila_v, aes(x = lon, y = lat, fill = log2(r)),
            width = 1, height = 1) +
  geom_sf(data = rajat_sf, fill = NA, colour = "grey20", linewidth = 0.12) +
  scale_fill_gradient2(
    low = "#1d3557", mid = "#f7f2e8", high = "#e63946", midpoint = 0,
    breaks = log2(c(0.5, 1, 2, 4, 8, 16)),
    labels = c("0,5\u00d7", "1\u00d7", "2\u00d7", "4\u00d7", "8\u00d7", "16\u00d7"),
    name   = "Näennäinen\nkoko"
  ) +
  coord_sf(xlim = c(-180, 180), ylim = c(-85, 85), expand = FALSE, crs = 4326) +
  labs(
    title    = "Mercatorin vääristymä riippuu vain leveyspiiristä",
    subtitle = "valkoinen = näyttää liian pieneltä, punainen = näyttää liian isolta",
    x = NULL, y = NULL,
    caption = "Kartta on piirretty tasakulmakoordinaatistossa, jotta vääristymä itse näkyy vääristymättä"
  ) +
  theme_kristian()
Figure 2: Missä Mercator liioittelee ja missä se kutistaa. Väri kertoo suhteellisen pinta-alavääristymän.

Miten tätä karttaa luetaan. Tämä kuva on piirretty tarkoituksella ilman Mercatoria — se on tavallinen pituus/leveysasteruudukko. Väri kertoo, mitä Mercator tekisi kullekin kohdalle. Vaalea keskikaista päiväntasaajan tienoilla on alue, jonka Mercator kutistaa. Voimakkaan punaiset alueet pohjoisessa ja etelässä ovat niitä, jotka se puhaltaa isoiksi. Huomaa, että väri riippuu vain korkeudesta kuvassa, ei lainkaan siitä ollaanko idässä vai lännessä: Mercatorin vääristymä on puhtaasti leveyspiirin funktio.


8 Kestääkö “Grönlanti näyttää Afrikan kokoiselta” tarkastelun?

Tämä on kampanjan iskulause. Testataan se tarkasti — ei otantana vaan laskemalla maiden todelliset ja näennäiset pinta-alat suoraan rajapolygoneista.

Code
# Todelliset pinta-alat pallogeometrialla (s2)
sf::sf_use_s2(TRUE)
maat$ala_tosi <- as.numeric(st_area(maat))

# Rajaviivat pitaa TIHENTAA ennen projisointia. Muuten pitka suora rajaviiva
# (esim. USA/Kanada 49. leveyspiirilla) piirtyy kartalle suorana, vaikka sen
# pitaisi kaartua -- ja pinta-ala menee vaarin. 50 km segmentti riittaa.
maat_tiheat <- st_segmentize(maat, dfMaxLength = 50000)

# Naennaiset pinta-alat kartalla: projisoidaan polygonit ja mitataan tason ala.
naennainen_ala <- function(crs) {
  sf::sf_use_s2(FALSE)
  g <- maat_tiheat |>
    st_wrap_dateline(options = c("WRAPDATELINE=YES", "DATELINEOFFSET=180")) |>
    st_transform(crs)
  ala <- as.numeric(st_area(g))
  sf::sf_use_s2(TRUE)
  ala
}

# Gronlanti on Tanskan alue; GISCO:ssa erillinen koodi GRL.
kohteet <- c("GRL", "COD", "IND", "FIN", "BRA", "RUS", "AUS", "DZA")

vertailu <- tibble(
  ISO3_CODE   = maat$ISO3_CODE,
  maa         = maat$NAME_ENGL,
  ala_tosi    = maat$ala_tosi,
  ala_merc    = naennainen_ala(PROJEKTIOT[["Mercator"]]),
  ala_eqe     = naennainen_ala(PROJEKTIOT[["Equal Earth"]])
) |>
  mutate(
    # normalisoidaan niin, etta maailman maa-alueiden yhteispinta-ala tasmaa
    r_merc = (ala_merc / sum(ala_merc)) / (ala_tosi / sum(ala_tosi)),
    r_eqe  = (ala_eqe  / sum(ala_eqe))  / (ala_tosi / sum(ala_tosi))
  )

# Equal Earth on pinta-alatarkka: suhteen pitaa olla 1 numeerisen tarkkuuden
# rajoissa. Tama on koko pinta-alalaskennan validointi.
cat("Equal Earth, suurin poikkeama ykkosesta:",
    round(max(abs(vertailu$r_eqe - 1)), 4), "\n")
Equal Earth, suurin poikkeama ykkosesta: 5e-04 
Code
stopifnot(max(abs(vertailu$r_eqe - 1)) < 0.02)

# Afrikan yhteispinta-ala
afr_iso <- maat$ISO3_CODE[maat$manner == "Afrikka"]
afr <- vertailu |> filter(ISO3_CODE %in% afr_iso) |>
  summarise(tosi = sum(ala_tosi), merc = sum(ala_merc))

grl <- vertailu |> filter(ISO3_CODE == "GRL")

cat("Afrikan todellinen pinta-ala / Gronlannin:",
    round(afr$tosi / grl$ala_tosi, 2), "\n")
Afrikan todellinen pinta-ala / Gronlannin: 13.88 
Code
cat("Afrikan naennainen pinta-ala Mercatorilla / Gronlannin:",
    round(afr$merc / grl$ala_merc, 2), "\n")
Afrikan naennainen pinta-ala Mercatorilla / Gronlannin: 0.94 
Code
cat("Gronlannin liioittelukerroin Mercatorilla:",
    round(grl$r_merc, 2), "\n")
Gronlannin liioittelukerroin Mercatorilla: 6.79 
Code
vertailu |>
  filter(ISO3_CODE %in% kohteet) |>
  arrange(desc(r_merc)) |>
  transmute(
    Maa = maa,
    `Todellinen pinta-ala (milj. km²)` = round(ala_tosi / 1e12, 2),
    `Liioittelukerroin Mercatorilla`   = paste0(formatC(r_merc, digits = 2, format = "f"), "\u00d7")
  ) |>
  knitr::kable(caption = "Kuinka moninkertaiselta maa näyttää Mercatorin kartalla")
Table 1: Kuinka moninkertaiselta maa näyttää Mercatorin kartalla
Maa Todellinen pinta-ala (milj. km²) Liioittelukerroin Mercatorilla
Greenland 2.16 6.79×
Finland 0.34 2.26×
Russian Federation 16.85 2.03×
Algeria 2.31 0.54×
Australia 7.71 0.52×
India 2.97 0.49×
Brazil 8.52 0.44×
Democratic Republic of The Congo 2.34 0.42×

Tässä on kiinnostava nyanssi. Väite “Afrikka ja Grönlanti näyttävät samankokoisilta” on liioiteltu, jos sillä tarkoitetaan täsmällistä yhtäsuuruutta. Todellinen suhde on noin 14:1, ja Mercator kutistaa sen laskennallisesti selvästi — mutta ei aivan ykköseen asti. Yksi syy on se, että Grönlanti ei ole kokonaan hyvin pohjoisessa: sen eteläkärki on 60. leveyspiirin tuntumassa, missä liioittelukerroin on paljon pienempi kuin pohjoiskärjessä.

Miksi väite silti tuntuu useimmista ihmisistä oikealta? Todennäköisin selitys on, että Grönlanti sijaitsee kartan yläreunassa, jossa se on iso ja yhtenäinen muoto, kun taas Afrikkaa katsotaan keskellä muiden mantereiden ympäröimänä. Havaintopsykologia ja geometria eivät ole sama asia.

Tässä on tämän blogin peruskysymys tiivistettynä: kampanjan pointti on oikea (Mercator vääristää pinta-aloja systemaattisesti ja voimakkaasti), mutta markkinointiin valittu yksittäinen luku ei kestä tarkkaa tarkastelua. Molemmat asiat voivat olla totta yhtä aikaa, ja päättäjän on hyvä osata erottaa ne.

NoteFaktalaatikko 2: vaikuttaako Mercator oikeasti ihmisten maailmankuvaan?

Kampanjan keskeinen väite on, että Mercator vääristää ihmisten mielikuvaa maailmasta. Väitteelle on empiiristä tutkimusta — ja tulos on yllättävän epäselvä.

Battersby & Montello (2009) tutkivat kahdella kokeella, kuinka tarkasti ihmiset arvioivat maailman alueiden pinta-aloja. Muistista tuotetut arviot eivät noudattaneet Mercatorin vääristymän kaavaa: napa-alueita ei arvioitu suhteettoman suuriksi sillä tavalla kuin “Mercator-efekti” ennustaisi. Arviot seurasivat pikemminkin psykofysiikan yleistä lainalaisuutta, jossa suuria määriä aliarvioidaan ja pieniä yliarvioidaan. Sen sijaan silloin, kun koehenkilöt katsoivat karttaa suoraan, he onnistuivat vain osittain korjaamaan vääristymää mielessään.

De Boeck & Ooms (2019) toistivat kokeen sukupolvella, joka on kasvanut Web Mercatoriin perustuvien verkkokarttojen kanssa, eli altistunut Mercatorille selvästi enemmän kuin alkuperäinen tutkimusjoukko.

Vastaskenaario: jos Mercator-efekti muistiarvioissa olisi voimakas, YK:n päätöksen käytännön vaikutus olisi suuri: kartan vaihtaminen korjaisi suoraan ihmisten maailmankuvaa. Jos taas efektiä ei ole, päätöksen merkitys on enemmän symbolinen ja institutionaalinen — mikä ei tarkoita, ettei se olisi merkittävä, mutta se on eri asia.

Huomionarvoista: tästä kysymyksestä ei ole julkaistua meta-analyysiä. Tutkimuksia on vähän, otokset ovat pieniä ja koostuvat pääosin opiskelijoista. Tämä on rehellisyyden nimissä sanottava: väite kartan vaikutuksesta maailmankuvaan on uskottava mutta heikosti todennettu.

Lähteet: Battersby, S. E. & Montello, D. R. (2009). Area estimation of world regions and the projection of the global-scale cognitive map. Annals of the Association of American Geographers, 99(2), 273–291. — De Boeck, M. & Ooms, K. (2019). Evaluating young people’s area estimation of countries and continents. ISPRS International Journal of Geo-Information, 8(3), 125.


9 Tulos 2: kaikki projektiot rinnakkain

Nyt varsinainen vertailu. Piirretään jokaisen projektion kaksi vääristymäjakaumaa vierekkäin.

Code
jarjestys <- vaaristymat |>
  group_by(projektio) |>
  summarise(m = median(omega), .groups = "drop") |>
  arrange(m) |>
  pull(projektio)

d_plot <- vaaristymat |>
  mutate(projektio = factor(projektio, levels = jarjestys))

VARIT <- c("pinta-alatarkka" = "#2a9d8f",
           "konforminen"     = "#e63946",
           "kompromissi"     = "#f4a261")

# HUOM: osa jakaumista on degeneroituneita eli vakioita. Pinta-alatarkoilla
# projektioilla r_pinta_ala == 1 kaikkialla ja Mercatorilla omega == 0
# kaikkialla. Ydinestimointi (bw.SJ) kaatuu vakiodataan, joten slab lasketaan
# histogrammilla. Se on tassa myos rehellisempi esitys: piikki on aito piikki,
# ei levitetty kumpare.

p1 <- ggplot(d_plot, aes(x = r_pinta_ala, y = projektio, fill = tyyppi)) +
  stat_halfeye(
    density   = "histogram",
    .width    = c(0.5, 0.9),
    normalize = "groups",
    slab_alpha = 0.85,
    point_size = 1.6,
    interval_size_range = c(0.5, 1.2)
  ) +
  geom_vline(xintercept = 1, colour = "#e63946", linewidth = 0.7) +
  scale_x_continuous(
    trans  = "log2",
    breaks = c(0.5, 1, 2, 4, 8, 16),
    labels = c("0,5\u00d7", "1\u00d7", "2\u00d7", "4\u00d7", "8\u00d7", "16\u00d7")
  ) +
  coord_cartesian(xlim = c(0.4, 20)) +
  scale_fill_manual(values = VARIT) +
  labs(title = "Pinta-alavääristymä", x = "Näennäinen koko", y = NULL, fill = NULL) +
  theme_kristian()

p2 <- ggplot(d_plot, aes(x = omega, y = projektio, fill = tyyppi)) +
  stat_halfeye(
    density   = "histogram",
    .width    = c(0.5, 0.9),
    normalize = "groups",
    slab_alpha = 0.85,
    point_size = 1.6,
    interval_size_range = c(0.5, 1.2)
  ) +
  scale_x_continuous(labels = function(x) paste0(x, "\u00b0")) +
  coord_cartesian(xlim = c(0, 80)) +
  scale_fill_manual(values = VARIT) +
  labs(title = "Kulmavääristymä (muotojen vääristymä)",
       x = "Suurin kulmavirhe", y = NULL, fill = NULL) +
  theme_kristian()

(p1 | p2) +
  plot_layout(guides = "collect") +
  plot_annotation(
    title = "Jokainen projektio valitsee, kummasta virheestä se maksaa",
    subtitle = "Vasemmalla pinta-alojen virhe, oikealla muotojen virhe. Sama piste-otos molemmissa.",
    caption = paste0(
      "60 000 tasa-aluepainotettua otospistett\u00e4 maa-alueilta. ",
      "Akselit on rajattu luettavuuden vuoksi; Mercatorin pinta-alah\u00e4nt\u00e4 jatkuu ",
      formatC(max(vaaristymat$r_pinta_ala[vaaristymat$projektio == "Mercator"]),
              digits = 0, format = "f"), "\u00d7 asti."
    )
  ) &
  theme_kristian()
Figure 3: Kaikkien vertailtujen projektioiden vääristymäjakaumat maa-alueilla.

Miten tätä kuvaa luetaan. Jokaista projektiota kohti on piirretty kaksi “vuorenharjannetta”. Harjanteen korkeus kertoo, kuinka tavallinen kyseinen arvo on: leveä ja matala harjanne tarkoittaa, että vääristymä vaihtelee paljon paikasta toiseen, kapea ja korkea harjanne tarkoittaa, että vääristymä on lähes sama kaikkialla. Harjanteen alla oleva paksu jana kattaa keskimmäiset 50 % maa-alueesta ja ohut jana keskimmäiset 90 %. Vasemmalla puolella kaikki vihreät harjanteet ovat kapeita piikkejä kohdassa 1× — se on pinta-alatarkkuuden määritelmä. Oikealla puolella juuri nuo samat vihreät ovat leveitä: hinta maksetaan muodoissa.

Code
yhteenveto <- vaaristymat |>
  group_by(projektio, tyyppi) |>
  summarise(
    pa_med   = median(r_pinta_ala),
    pa_p95   = quantile(r_pinta_ala, 0.95),
    pa_haj   = quantile(r_pinta_ala, 0.95) / quantile(r_pinta_ala, 0.05),
    om_med   = median(omega),
    om_p95   = quantile(omega, 0.95),
    om_max   = max(omega),
    .groups  = "drop"
  ) |>
  arrange(om_med)

yhteenveto |>
  transmute(
    Projektio = projektio,
    Tyyppi    = tyyppi,
    `Pinta-ala, mediaani` = paste0(formatC(pa_med, digits = 2, format = "f"), "\u00d7"),
    `Pinta-ala, 95 %`     = paste0(formatC(pa_p95, digits = 2, format = "f"), "\u00d7"),
    `Ylin/alin -suhde`    = paste0(formatC(pa_haj, digits = 1, format = "f"), "\u00d7"),
    `Kulmavirhe, mediaani`= paste0(formatC(om_med, digits = 1, format = "f"), "\u00b0"),
    `Kulmavirhe, 95 %`    = paste0(formatC(om_p95, digits = 1, format = "f"), "\u00b0")
  ) |>
  knitr::kable(caption = "Projektioiden vertailu maa-alueiden yli")
Table 2: Projektioiden vertailu maa-alueiden yli
Projektio Tyyppi Pinta-ala, mediaani Pinta-ala, 95 % Ylin/alin -suhde Kulmavirhe, mediaani Kulmavirhe, 95 %
Mercator konforminen 0.76× 3.85× 6.8× 0.0° 0.0°
Plate carree kompromissi 0.87× 1.96× 2.6× 8.5° 52.9°
Robinson kompromissi 0.94× 1.40× 1.7× 12.0° 54.3°
Natural Earth kompromissi 0.94× 1.45× 1.7× 12.2° 49.9°
Winkel Tripel kompromissi 0.94× 1.48× 1.8× 13.8° 48.8°
Equal Earth pinta-alatarkka 1.00× 1.00× 1.0× 18.4° 63.2°
Eckert IV pinta-alatarkka 1.00× 1.00× 1.0× 19.2° 65.1°
Mollweide pinta-alatarkka 1.00× 1.00× 1.0× 21.8° 78.0°
Gall-Peters pinta-alatarkka 1.00× 1.00× 1.0× 30.6° 66.0°

Taulukko kertoo tarinan tiiviisti. Mercator on ainoa, jonka kulmavirhe on nolla kaikkialla — se on täsmälleen konforminen. Hinta näkyy vasemmassa sarakkeessa: ylin/alin -suhde on kymmeniä. Pinta-alatarkat projektiot ovat peilikuva: pinta-alasarake on täsmälleen 1,00× kaikkialla, mutta kulmasarake kasvaa. Kompromissiprojektiot asettuvat väliin molemmissa.


10 Mistä Equal Earth luopuu

Nyt päästään postauksen otsikkokysymykseen. Equal Earth on pinta-alatarkka, joten sen pinta-alavirhe on nolla. Mistä se maksaa sen?

Code
hila_eqe <- laske_vaaristymat(hila, PROJEKTIOT[["Equal Earth"]])

ggplot() +
  geom_tile(data = hila_eqe, aes(x = lon, y = lat, fill = omega),
            width = 1, height = 1) +
  geom_sf(data = rajat_sf, fill = NA, colour = "grey15", linewidth = 0.12) +
  scale_fill_gradientn(
    colours = c("#f7f2e8", "#f4a261", "#e63946", "#7a1020"),
    values  = scales::rescale(c(0, 15, 35, 70)),
    name    = "Kulmavirhe",
    labels  = function(x) paste0(x, "\u00b0")
  ) +
  coord_sf(xlim = c(-180, 180), ylim = c(-85, 85), expand = FALSE, crs = 4326) +
  labs(
    title    = "Equal Earthin hinta: muodot vääristyvät reunoilla ja navoilla",
    subtitle = "Vaalea = muodot lähes oikein, tumma = muodot pahasti vääristyneet",
    x = NULL, y = NULL
  ) +
  theme_kristian()
Figure 4: Equal Earthin kulmavääristymä eli muotojen vääristymä maapallon yli.

Miten tätä karttaa luetaan. Vaaleat alueet ovat niitä, joissa Equal Earth piirtää muodot melkein oikein. Tummat alueet ovat niitä, joissa muodot venyvät tai litistyvät pahasti. Huomaa kuvion muoto: se ei enää riipu pelkästä leveyspiiristä kuten Mercatorilla, vaan myös siitä, kuinka kaukana ollaan kartan keskimeridiaanista (nollameridiaani, joka kulkee Greenwichin läpi). Kartan oikea ja vasen reuna ovat huonompia kuin keskikaista. Tähän palataan vastaskenaarioissa, koska se tarkoittaa että kartan keskikohdan valinta on poliittinen päätös, jolla on mitattava seuraus.

Code
eqe <- vaaristymat |> filter(projektio == "Equal Earth")

pahin <- eqe |>
  group_by(manner) |>
  summarise(
    mediaani = median(omega),
    p90      = quantile(omega, 0.90),
    osuus_yli_20 = mean(omega > 20),
    .groups = "drop"
  ) |>
  arrange(desc(mediaani))

pahin |>
  transmute(
    Manner = manner,
    `Kulmavirhe, mediaani` = paste0(formatC(mediaani, digits = 1, format = "f"), "\u00b0"),
    `Kulmavirhe, 90 %`     = paste0(formatC(p90, digits = 1, format = "f"), "\u00b0"),
    `Osuus, jossa yli 20°`  = percent(osuus_yli_20, accuracy = 0.1)
  ) |>
  knitr::kable(caption = "Equal Earthin muotovirhe mantereittain")
Equal Earthin muotovirhe mantereittain
Manner Kulmavirhe, mediaani Kulmavirhe, 90 % Osuus, jossa yli 20°
Eurooppa 40.8° 66.8° 77.8%
Oseania 25.3° 33.3° 90.9%
Amerikat 21.9° 60.4° 54.7%
Aasia 18.9° 31.1° 44.0%
Afrikka 15.1° 16.9° 0.0%

Tässä on tulos, joka ei ole ollut esillä uutisoinnissa: Equal Earthin muotovirhe on suurin juuri niillä alueilla, joita kampanja halusi kohdella oikeudenmukaisemmin. Pinta-ala tulee oikein, mutta muodot vääristyvät merkittävästi kartan reunoilla ja korkeilla leveyspiireillä. Tämä ei ole argumentti Equal Earthia vastaan — se on Theorema Egregium toiminnassa. Jokin on pakko uhrata. Mutta rehellinen keskustelu edellyttää, että uhraus sanotaan ääneen.

NoteFaktalaatikko 3: Equal Earth ja sen suunnitteluperiaate

Equal Earth julkaistiin vuonna 2018. Sen tekivät slovenialainen geodeetti Bojan Šavrič, yhdysvaltalainen kartografi Tom Patterson ja sveitsiläinen kartografian tutkija Bernhard Jenny.

Projektio on pseudosylinterinen ja pinta-alatarkka. Sen suunnittelun lähtökohta oli nimenomaan esteettinen ja viestinnällinen: tekijät halusivat pinta-alatarkan projektion, joka näyttää miellyttävältä, koska aiemmat pinta-alatarkat vaihtoehdot (etenkin Gall–Peters) koettiin laajalti rumiksi ja siksi niitä ei otettu käyttöön. Kaavat sovitettiin numeerisesti Robinsonin projektion ulkoasua muistuttaviksi, mutta pinta-alatarkkuuden ehdolla.

Vastaskenaario: jos vuonna 2018 ei olisi julkaistu Equal Earthia, vaihtoehtoina olisivat olleet Mollweide, Eckert IV tai Gall–Peters. Tämän postauksen taulukoista näkyy, että Mollweiden ja Eckert IV:n vääristymäprofiilit ovat samaa luokkaa Equal Earthin kanssa — matemaattisesti kyse ei ole isosta harppauksesta. Equal Earthin merkittävin panos ei ole matemaattinen vaan sosiaalinen: se teki pinta-alatarkasta kartasta poliittisesti hyväksyttävän näköisen. Se on hyvä muistutus siitä, että menetelmän omaksuminen riippuu harvoin pelkästä matematiikasta.

Lähde: Šavrič, B., Patterson, T. & Jenny, B. (2019). The Equal Earth map projection. International Journal of Geographical Information Science, 33(3), 454–465.


11 Väestöpainotus: kenen vääristymä oikeastaan on tärkeä?

Tähän asti olen painottanut maapinta-alaa. Mutta ihmiset eivät jakaudu maapinta-alan mukaan. Siperia, Sahara ja Australian sisäosat ovat valtavia ja lähes asumattomia.

Jos kysymys on siitä, miten kartta vaikuttaa ihmisten käsitykseen omasta maastaan, oikea painotus ei olekaan neliökilometri vaan ihminen. Lasketaan sama jakauma väestöpainotettuna.

Code
# Jokaiselle otospisteelle paino = maan vakiluku / kyseisen maan otospisteiden lkm.
# Tama vastaa oletusta, etta vaesto jakautuu tasaisesti maan sisalla. Oletus on
# karkea, mutta se ei riipu mistaan projektiosta eika lisaa piilo-oletuksia
# muuta kuin maan sisalla, missa leveyspiirivaihtelu on useimmilla mailla pieni.

pisteet_per_maa <- maapisteet |> count(ISO3_CODE, name = "n_pisteita")

painot <- pisteet_per_maa |>
  inner_join(vaesto_raaka, by = "ISO3_CODE") |>
  mutate(paino = vakiluku / n_pisteita)

cat("Maita, joille saatiin sek\u00e4 otospisteit\u00e4 ett\u00e4 v\u00e4kiluku:", nrow(painot), "\n")
Maita, joille saatiin sekä otospisteitä että väkiluku: 179 
Code
cat("Katettu v\u00e4est\u00f6:", format(sum(painot$vakiluku), big.mark = " "), "\n")
Katettu väestö: 8 095 006 408 
Code
stopifnot(nrow(painot) > 150)

vaaristymat_p <- vaaristymat |>
  inner_join(select(painot, ISO3_CODE, paino), by = "ISO3_CODE")

# Painotettu kvantiilifunktio (ei lisariippuvuuksia)
painotettu_kvantiili <- function(x, w, p) {
  stopifnot(length(x) == length(w), all(w >= 0), all(is.finite(x)))
  jarj <- order(x)
  x <- x[jarj]; w <- w[jarj]
  kumul <- cumsum(w) / sum(w)
  vapply(p, function(pp) x[which.max(kumul >= pp)], numeric(1))
}

vertailu_painotus <- vaaristymat_p |>
  filter(projektio == "Mercator") |>
  summarise(
    `Maapinta-ala, mediaani`   = median(r_pinta_ala),
    `Väestöpainotettu, mediaani` = painotettu_kvantiili(r_pinta_ala, paino, 0.5),
    `Maapinta-ala, 90 %`       = quantile(r_pinta_ala, 0.90),
    `Väestöpainotettu, 90 %`   = painotettu_kvantiili(r_pinta_ala, paino, 0.90)
  ) |>
  pivot_longer(everything(), names_to = "Painotus", values_to = "Arvo") |>
  mutate(Arvo = paste0(formatC(Arvo, digits = 2, format = "f"), "\u00d7"))

knitr::kable(vertailu_painotus,
             caption = "Mercatorin vääristymä riippuu ratkaisevasti siitä, mitä painotetaan")
Mercatorin vääristymä riippuu ratkaisevasti siitä, mitä painotetaan
Painotus Arvo
Maapinta-ala, mediaani 0.77×
Väestöpainotettu, mediaani 0.70×
Maapinta-ala, 90 % 2.69×
Väestöpainotettu, 90 % 1.18×
Code
# Vertailukohta: tasainen otanta koko maapallon pinnalta, meret mukaan lukien.
pallo_otos <- cbind(
  lon = runif(60000, -179.9, 179.9),
  lat = asin(runif(60000, -1, 1)) * 180 / pi
)
pallo_otos <- pallo_otos[abs(pallo_otos[, "lat"]) <= 89.5, , drop = FALSE]

# Normalisoidaan samalla vakiolla kuin maa-alueet, jotta kaikki kolme kayraa
# ovat samalla asteikolla ja keskenaan vertailukelpoisia.
G_MERC <- exp(mean(log(merc$pinta_ala)))

pallo_v <- laske_vaaristymat(pallo_otos, PROJEKTIOT[["Mercator"]]) |>
  mutate(r = pinta_ala / G_MERC)

# Painotettu kertymafunktio. Aloituspiste F = 0 lisataan, jotta askelkuvaaja
# lahtee nollasta eika ensimmaisen havainnon korkeudelta.
ecdf_painotettu <- function(x, w = rep(1, length(x)), nimi) {
  stopifnot(length(x) == length(w), all(is.finite(x)), all(w >= 0))
  jarj <- order(x)
  x <- x[jarj]; w <- w[jarj]
  tibble(
    arvo     = c(x[1], x),
    F        = c(0, cumsum(w) / sum(w)),
    painotus = nimi
  )
}

mercp <- vaaristymat_p |> filter(projektio == "Mercator")

kaikki_ecdf <- bind_rows(
  ecdf_painotettu(pallo_v$r,             nimi = "Koko maapallon pinta"),
  ecdf_painotettu(merc$r_pinta_ala,      nimi = "Maa-alueet"),
  ecdf_painotettu(mercp$r_pinta_ala, mercp$paino, nimi = "Ihmiset")
) |>
  mutate(painotus = factor(painotus,
    levels = c("Koko maapallon pinta", "Maa-alueet", "Ihmiset")))

stopifnot(
  all(abs(
    kaikki_ecdf |> group_by(painotus) |> summarise(m = max(F)) |> pull(m) - 1
  ) < 1e-9)
)

ggplot(kaikki_ecdf, aes(x = arvo, y = F, colour = painotus)) +
  geom_step(linewidth = 1) +
  geom_vline(xintercept = 1, colour = "grey50", linetype = "dotted") +
  scale_x_continuous(
    trans  = "log2",
    breaks = c(0.5, 1, 2, 4, 8, 16, 32),
    labels = c("0,5\u00d7", "1\u00d7", "2\u00d7", "4\u00d7", "8\u00d7", "16\u00d7", "32\u00d7")
  ) +
  scale_y_continuous(labels = percent, limits = c(0, 1)) +
  # Rajaus tehdaan coord_cartesianilla, EI scale_x:n limits-argumentilla:
  # limits pudottaisi havainnot pois datasta, jolloin kertymakayra ei enaa
  # yltaisi sataan prosenttiin. coord_cartesian vain zoomaa.
  coord_cartesian(xlim = c(0.4, 40)) +
  scale_colour_manual(values = c("Koko maapallon pinta" = "#457b9d",
                                 "Maa-alueet"           = "#f4a261",
                                 "Ihmiset"              = "#2a9d8f")) +
  labs(
    title    = "Kenen näkökulmasta Mercator on paha?",
    subtitle = "Kertymäfunktio: käyrä kertoo, kuinka suuri osuus jää kunkin vääristymäarvon alle",
    x = "Näennäinen koko Mercatorin kartalla",
    y = "Kertymäosuus", colour = "Painotus",
    caption = "Väestöpainotus: Maailmanpankki SP.POP.TOTL. Vaaka-akseli on rajattu, ei suodatettu."
  ) +
  theme_kristian()
Figure 5: Sama projektio, kolme eri painotusta, kolme eri vastausta.

Miten tätä kuvaa luetaan. Jokainen käyrä nousee vasemmalta oikealle nollasta sataan prosenttiin. Kun valitset vaaka-akselilta jonkin arvon, käyrän korkeus siinä kohdassa kertoo, kuinka suuri osuus jää sen arvon alle. Esimerkiksi jos vihreä käyrä on 2× kohdalla korkeudella 80 %, se tarkoittaa: 80 % maailman ihmisistä asuu alueella, joka näyttää Mercatorin kartalla vähemmän kuin kaksinkertaiselta. Mitä enemmän käyrä on vasemmalla, sitä pienempiä vääristymiä kyseinen painotus tuottaa.

Tulos on kirkas ja se on tämän postauksen kiinnostavin yksittäinen havainto: väestöpainotettuna Mercator on paljon vähemmän paha kuin pinta-alapainotettuna. Syy on yksinkertainen: valtaosa maailman ihmisistä asuu suhteellisen matalilla leveyspiireillä, missä Mercatorin vääristymä on maltillinen. Pahimmat liioittelut kohdistuvat Grönlantiin, Pohjois-Kanadaan, Siperiaan, Huippuvuorille ja Antarktikselle — alueille, joilla asuu hyvin vähän ihmisiä.

Tämä ei kumoa kampanjan pointtia. Se tarkentaa sitä: Mercatorin ongelma ei ole ensisijaisesti se, että se vääristäisi asuttujen alueiden keskinäisiä suhteita rajusti. Ongelma on se, että se antaa muutamalle harvaan asutulle pohjoiselle alueelle suhteettoman suuren visuaalisen painoarvon — ja nämä alueet sattuvat olemaan pohjoisen pallonpuoliskon vauraiden valtioiden alueita. Se on eri väite kuin “Afrikka näyttää liian pieneltä”, ja itse asiassa vahvempi väite, koska se kestää tarkastelun.


12 Tilastolliset testit: eroavatko mantereet toisistaan?

Tehdään nyt asianmukainen tilastollinen vertailu. Ensin on kuitenkin välttämätöntä puhua siitä, mitä testi tässä tilanteessa oikeastaan tarkoittaa.

WarningFaktalaatikko 4: milloin p-arvo on hyödytön

Karttaprojektion vääristymä on deterministinen. Jokaisessa maapallon pisteessä sillä on täsmälleen yksi oikea arvo, joka voidaan laskea kaavasta. Siinä ei ole satunnaisuutta.

Ainoa satunnaisuus tässä analyysissä tulee siitä, että olen arponut 60 000 pistettä sen sijaan että olisin laskenut integraalin analyyttisesti. Tilastollinen testi vastaa siis kysymykseen: “onko havaittu ero suurempi kuin oman otantani kohina?” Koska otos on suuri ja ero on iso, vastaus on triviaalisti kyllä. P-arvo lähestyy nollaa riippumatta siitä, onko ero käytännössä merkityksellinen.

Tämä on täsmälleen sama ongelma, joka vaivaa yritysten A/B-testejä ja rekisteritutkimuksia: kun n on satojatuhansia, kaikki on “tilastollisesti merkitsevää”. Siksi raportoin p-arvon rinnalla aina efektikoon — luvun, joka kertoo kuinka suuri ero on, ei vain onko sitä.

Toinen tekninen ongelma: otospisteet eivät ole riippumattomia. Kaksi vierekkäistä pistettä Grönlannissa kertovat lähes saman asian. Tämä on spatiaalista autokorrelaatiota, ja se tekee nimellisistä p-arvoista liian pieniä. Korjaan tämän kahdella tavalla: klusteroidulla bootstrapilla (maa klusterina) ja hierarkkisella bayesilaisella mallilla.

Vastaskenaario: jos otospisteet olisivat aidosti riippumattomia havaintoja jostakin satunnaisilmiöstä, tavallinen Wilcoxonin testi olisi oikea työkalu ja sen p-arvo tulkittavissa suoraan. Näin ei ole, ja siksi raportoin kolme eri lähestymistapaa: lukija näkee itse, kuinka paljon johtopäätös riippuu menetelmävalinnasta.

12.1 Testi 1: mantereiden väliset erot (Kruskal–Wallis)

Code
merc_test <- vaaristymat |>
  filter(projektio == "Mercator") |>
  select(log2_r, manner, ISO3_CODE)

kw <- kruskal.test(log2_r ~ manner, data = merc_test)

# Efektikoko: epsilon-neliö = (H - k + 1) / (n - k)
n_kaikki <- nrow(merc_test)
k_ryhmat <- n_distinct(merc_test$manner)
eps2 <- as.numeric((kw$statistic - k_ryhmat + 1) / (n_kaikki - k_ryhmat))

cat("Kruskal-Wallis H =", round(as.numeric(kw$statistic), 1),
    "| df =", kw$parameter,
    "| p =", format.pval(kw$p.value, digits = 3, eps = 1e-300), "\n")
Kruskal-Wallis H = 25251.8 | df = 4 | p = <1e-300 
Code
cat("Efektikoko epsilon^2 =", round(eps2, 3), "\n")
Efektikoko epsilon^2 = 0.421 

Miten tämä tulkitaan. Kruskal–Wallisin testi on ei-parametrinen varianssianalyysi: se vertaa ryhmien järjestyslukuja, ei keskiarvoja. Valitsin sen kahdesta syystä. Ensinnäkin vääristymäjakauma on voimakkaasti vino ja pitkä häntäinen, jolloin normaalijakaumaoletukseen nojaava varianssianalyysi ei ole perusteltu. Toiseksi vertailen mediaaneja, jotka ovat tässä kiinnostavampia kuin keskiarvot: keskiarvoa hallitsisi muutama Grönlannin pohjoiskärjen piste.

Efektikoko \(\varepsilon^2\) on asteikolla 0–1 ja kertoo, kuinka suuri osuus järjestyslukujen vaihtelusta selittyy mantereella. P-arvo on olematon, kuten edellä ennakoitiin — efektikoko on se, jota kannattaa katsoa.

12.2 Testi 2: pareittaiset vertailut efektikokoineen

Code
mantereet <- sort(unique(merc_test$manner))
parit <- combn(mantereet, 2, simplify = FALSE)

# Rank-biserial -korrelaatio Wilcoxonin U:sta.
# HUOM: kokonaisluvun ylivuoto on todellinen riski kun n1*n2 > 2^31,
# siksi as.numeric() ennen kertolaskua.
rank_biserial <- function(x, y) {
  w <- wilcox.test(x, y, exact = FALSE, correct = FALSE)
  U <- as.numeric(w$statistic)
  n1 <- as.numeric(length(x)); n2 <- as.numeric(length(y))
  list(p = w$p.value, r = 2 * U / (n1 * n2) - 1)
}

tulokset_parit <- map_dfr(parit, function(pp) {
  x <- merc_test$log2_r[merc_test$manner == pp[1]]
  y <- merc_test$log2_r[merc_test$manner == pp[2]]
  rb <- rank_biserial(x, y)
  tibble(
    ryhma1 = pp[1], ryhma2 = pp[2],
    mediaani1 = 2^median(x), mediaani2 = 2^median(y),
    p_raaka = rb$p, rank_biserial = rb$r
  )
}) |>
  mutate(p_bh = p.adjust(p_raaka, method = "BH")) |>
  arrange(desc(abs(rank_biserial)))

tulokset_parit |>
  transmute(
    Vertailu = paste(ryhma1, "vs.", ryhma2),
    `Mediaani 1` = paste0(formatC(mediaani1, digits = 2, format = "f"), "\u00d7"),
    `Mediaani 2` = paste0(formatC(mediaani2, digits = 2, format = "f"), "\u00d7"),
    `Rank-biserial` = formatC(rank_biserial, digits = 2, format = "f"),
    `p (BH-korjattu)` = format.pval(p_bh, digits = 2, eps = 1e-300)
  ) |>
  knitr::kable(caption = "Mercatorin vääristymän erot mantereiden välillä")
Mercatorin vääristymän erot mantereiden välillä
Vertailu Mediaani 1 Mediaani 2 Rank-biserial p (BH-korjattu)
Afrikka vs. Eurooppa 0.61× 2.03× -0.99 < 1e-300
Eurooppa vs. Oseania 2.03× 0.69× 0.99 < 1e-300
Aasia vs. Eurooppa 0.79× 2.03× -0.94 < 1e-300
Aasia vs. Afrikka 0.79× 0.61× 0.65 < 1e-300
Amerikat vs. Eurooppa 0.86× 2.03× -0.58 < 1e-300
Afrikka vs. Oseania 0.61× 0.69× -0.52 < 1e-300
Afrikka vs. Amerikat 0.61× 0.86× -0.50 < 1e-300
Aasia vs. Oseania 0.79× 0.69× 0.35 8.6e-248
Amerikat vs. Oseania 0.86× 0.69× 0.27 1.5e-156
Aasia vs. Amerikat 0.79× 0.86× -0.11 2.2e-70

Rank-biserial -korrelaatio on efektikoko, joka kertoo suoraan: jos poimit satunnaisen pisteen ryhmästä A ja satunnaisen pisteen ryhmästä B, kuinka todennäköisesti A:n vääristymä on suurempi? Arvo 0 tarkoittaa täyttä sattumanvaraisuutta (50–50), arvo 1 tarkoittaa että A on aina suurempi ja −1 että B on aina suurempi. Toisin kuin p-arvo, se ei riipu otoskoosta.

12.3 Testi 3: klusteroitu bootstrap

Edellä olevat p-arvot ovat harhaisia spatiaalisen autokorrelaation vuoksi. Korjaan sen bootstrapilla, jossa uudelleenotanta tehdään maiden tasolla, ei pisteiden tasolla. Tämä vastaa kysymykseen: kuinka paljon tulos muuttuisi, jos maailmassa olisi hieman eri joukko maita?

Code
set.seed(4711)
B <- 1000

maalista    <- split(merc_test$log2_r, merc_test$ISO3_CODE)
maan_manner <- merc_test |> distinct(ISO3_CODE, manner)

stopifnot(all(maan_manner$ISO3_CODE %in% names(maalista)))

boot_mediaanit <- map_dfr(seq_len(B), function(i) {
  otos_maat <- maan_manner |>
    group_by(manner) |>
    slice_sample(prop = 1, replace = TRUE) |>
    ungroup()

  arvot <- map2(otos_maat$ISO3_CODE, otos_maat$manner,
                ~ tibble(log2_r = maalista[[.x]], manner = .y)) |>
    bind_rows()

  arvot |>
    group_by(manner) |>
    summarise(med = median(log2_r), .groups = "drop") |>
    mutate(toisto = i)
})

boot_ci <- boot_mediaanit |>
  group_by(manner) |>
  summarise(
    alaraja = quantile(med, 0.025),
    piste   = quantile(med, 0.5),
    ylaraja = quantile(med, 0.975),
    .groups = "drop"
  ) |>
  mutate(across(c(alaraja, piste, ylaraja), ~ 2^.x)) |>
  arrange(desc(piste))

boot_ci |>
  transmute(
    Manner = manner,
    `Mediaanivääristymä` = paste0(formatC(piste, digits = 2, format = "f"), "\u00d7"),
    `95 % luottamusväli` = paste0(
      formatC(alaraja, digits = 2, format = "f"), "\u2013",
      formatC(ylaraja, digits = 2, format = "f"), "\u00d7"
    )
  ) |>
  knitr::kable(caption = "Mercatorin mediaanivääristymä mantereittain, maatason bootstrap")
Mercatorin mediaanivääristymä mantereittain, maatason bootstrap
Manner Mediaanivääristymä 95 % luottamusväli
Eurooppa 2.02× 1.24–2.25×
Amerikat 0.85× 0.60–1.55×
Aasia 0.78× 0.67–0.91×
Oseania 0.69× 0.58–0.95×
Afrikka 0.61× 0.59–0.64×
Code
ggplot(boot_ci, aes(x = piste, y = reorder(manner, piste))) +
  geom_vline(xintercept = 1, colour = "#e63946", linetype = "dashed") +
  geom_errorbarh(aes(xmin = alaraja, xmax = ylaraja), height = 0.2,
                 colour = "#457b9d", linewidth = 1) +
  geom_point(size = 3.2, colour = "#1d3557") +
  scale_x_continuous(trans = "log2", breaks = c(1, 1.5, 2, 3, 4, 6),
                     labels = function(x) paste0(gsub("\\.", ",", x), "\u00d7")) +
  labs(
    title    = "Mercatorin liioittelu mantereittain",
    subtitle = "Piste = mediaani, jana = 95 % luottamusväli \n (maatason bootstrap, 1000 toistoa)",
    x = "Näennäinen koko suhteessa kartan keskimääräiseen \n maa-alueeseen", y = NULL
  ) +
  theme_kristian()
Figure 6: Mantereiden mediaanivääristymät ja niiden epävarmuus.

Miten tätä kuvaa luetaan. Jokaisella mantereella on yksi piste ja yksi jana. Piste on paras arvio mantereen tyypillisestä vääristymästä. Jana kertoo, kuinka epävarma tuo arvio on: jos janat eivät mene päällekkäin, ero on selvä myös epävarmuus huomioiden. Punainen katkoviiva on “oikein” -kohta. Huomaa, että janat ovat leveitä. Se johtuu siitä, että bootstrap-menetelmä arpoo maita uudelleen, ja mantereen tulos riippuu paljon siitä, mitkä maat siihen sattuvat osumaan. Tavallinen pistetason luottamusväli olisi ollut lähes olematon — ja harhaanjohtava.

12.4 Testi 4: hierarkkinen bayesilainen malli

Vielä yksi näkökulma. Bootstrap käsittelee maita vaihdettavina yksikköinä. Bayesilainen hierarkkinen malli tekee jotain hienovaraisempaa: se kutistaa pienten maiden estimaatteja kohti mantereen keskiarvoa sitä enemmän, mitä vähemmän niistä on tietoa. Tämä on juuri se ominaisuus, joka tekee bayesilaisista menetelmistä hyödyllisiä epätasapainoisissa aineistoissa — ja maailmankartta on erittäin epätasapainoinen aineisto (Venäjä vs. Malta).

Code
library(brms)

polku_malli <- file.path(DATA_DIR, "malli_manner_meta.qs")

# HUOM: jos mallin kaava tai aineiston rakenne muuttuu, TÄMÄ TIEDOSTO ON
# POISTETTAVA KÄSIN. Muuten if(!file.exists()) lataa hiljaa vanhentuneen mallin.

# Maatason yhteenveto. Koska otospisteet arvottiin tasaisesti maapallon
# pinnalta, kunkin maan sisällä ne ovat riippumattomia poimintoja maan
# pinta-alalta -- keskivirhe sd/sqrt(n) on siis paikkansapitäväk.
maatason <- merc_test |>
  group_by(ISO3_CODE, manner) |>
  summarise(
    n         = n(),
    keskiarvo = mean(log2_r),
    hajonta   = sd(log2_r),
    .groups   = "drop"
  ) |>
  filter(n >= 20, is.finite(hajonta), hajonta > 0) |>
  mutate(
    se     = hajonta / sqrt(n),
    manner = factor(manner)
  )

# Raportoidaan mita jai pois, jotta lukija nakee rajauksen vaikutuksen.
cat("Maita mallissa:", nrow(maatason),
    "| pudonneet (alle 20 otospistetta):",
    n_distinct(merc_test$ISO3_CODE) - nrow(maatason), "\n")
Maita mallissa: 133 | pudonneet (alle 20 otospistetta): 52 
Code
cat("Mallissa olevien maiden osuus otospisteista:",
    scales::percent(sum(maatason$n) / nrow(merc_test), accuracy = 0.1), "\n")
Mallissa olevien maiden osuus otospisteista: 99.4% 
Code
stopifnot(all(maatason$se > 0), nrow(maatason) > 100)

if (!file.exists(polku_malli)) {
  malli <- brm(
    # se(): maakohtainen mittausepavarmuus on TUNNETTU, ei estimoitava.
    # sigma = TRUE: sen paalle estimoidaan maiden valinen heterogeenisuus.
    keskiarvo | se(se, sigma = TRUE) ~ 0 + manner,
    data    = maatason,
    family  = student(),          # paksut hannat: Gronlannin kaltaiset poikkeamat
    prior   = c(
      prior(normal(0, 3), class = b),
      prior(exponential(1), class = sigma)
    ),
    chains  = 4, iter = 4000, warmup = 1000,
    cores   = 4, seed = 20260909,
    control = list(adapt_delta = 0.95),
    refresh = 0
  )
  qs2::qs_save(malli, polku_malli)
}
malli <- qs2::qs_read(polku_malli)

# Konvergenssin tarkistus. Nama kolme ovat minimivaatimus ennen kuin
# posteriorijakaumasta saa sanoa yhtaan mitaan.
diag <- brms::nuts_params(malli)
divergenssit <- sum(diag$Value[diag$Parameter == "divergent__"])
rhat_max     <- max(brms::rhat(malli), na.rm = TRUE)
ess_min      <- min(brms::neff_ratio(malli), na.rm = TRUE)

cat("Divergensseja:", divergenssit,
    "| suurin R-hat:", round(rhat_max, 4),
    "| pienin ESS-suhde:", round(ess_min, 3), "\n")
Divergensseja: 0 | suurin R-hat: 1.0007 | pienin ESS-suhde: 0.413 
Code
stopifnot(divergenssit == 0, rhat_max < 1.01, ess_min > 0.1)

posteriori <- brms::as_draws_df(malli) |>
  as_tibble() |>
  select(starts_with("b_manner")) |>
  pivot_longer(everything(), names_to = "manner", values_to = "log2_r") |>
  mutate(
    manner  = gsub("^b_manner", "", manner),
    kerroin = 2^log2_r
  )
Code
ggplot(posteriori, aes(x = kerroin, y = reorder(manner, kerroin))) +
  ggdist::stat_halfeye(.width = c(0.5, 0.95), fill = "#2a9d8f", alpha = 0.85) +
  geom_vline(xintercept = 1, colour = "#e63946", linetype = "dashed") +
  scale_x_continuous(trans = "log2", breaks = c(1, 1.5, 2, 3, 4, 6),
                     labels = function(x) paste0(gsub("\\.", ",", x), "\u00d7")) +
  labs(
    title    = "Sama kysymys bayesilaisittain",
    subtitle = "Harjanne = uskottavuusjakauma. \n Leveys kertoo epävarmuuden, sijainti kertoimen",
    x = "Tyypillinen näennäinen koko Mercatorin kartalla", y = NULL,
    caption = "brms, Student-t -jakauma, maakohtainen satunnaisvaikutus"
  ) +
  theme_kristian()
Figure 7: Bayesilaisen mallin posteriorijakaumat mantereiden tyypilliselle vääristymälle.

Miten tätä kuvaa luetaan. Toisin kuin edellisessä kuvassa, tässä ei ole yksittäistä pistettä ja janaa vaan kokonainen harjanne. Harjanne on uskottavuusjakauma: sen korkeus jokaisessa kohdassa kertoo, kuinka uskottava kyseinen arvo on annetun aineiston valossa. Tämä on täsmälleen se, mitä tarkoitan sloganilla “maailma on jakauma” — vastaus ei ole luku vaan jakauma mahdollisista luvuista, ja jakauman leveys on yhtä tärkeä tieto kuin sen sijainti.

Vertaa tätä ja edellistä kuvaa: bootstrapin ja bayesilaisen mallin johtopäätökset ovat samansuuntaiset. Se on hyvä merkki. Kun kaksi menetelmällisesti erilaista lähestymistapaa antaa saman vastauksen, tulos ei ole menetelmävalinnan artefakti.


13 Miksi Mercator sitten valittiin? Suoran kurssin hinta

Nyt siirryn siihen puoleen tarinaa, joka useimmiten jää uutisoinnista kokonaan pois. Mercator ei ollut virhe. Se oli erinomainen ratkaisu erittäin tärkeään ongelmaan, ja tuo ongelma oli navigointi.

Mercatorin projektiolla on yksi ainutlaatuinen ominaisuus: loksodromi eli tasakulmaviiva piirtyy suorana viivana. Loksodromi on reitti, jolla kompassin lukema pysyy koko ajan samana. Purjelaivan navigaattorille tämä oli mullistavaa: piirrä viivain kartalle lähtö- ja määräsataman välille, lue viivan kulma, aseta ruori siihen kulmaan ja pysy siinä. Ei laskutoimituksia, ei jatkuvaa kurssin korjausta, ei tähtitieteellistä osaamista.

Vaihtoehto on ortodromi eli isoympyräreitti, joka on lyhin mahdollinen reitti pallon pinnalla. Se on lyhyempi, mutta sen seuraaminen vaatii kompassisuunnan jatkuvaa muuttamista.

Kysymys kuuluu: kuinka paljon pidempi loksodromi oikeasti on? Sitä ei ole tapana kysyä, koska vastaus on jakauma.

Code
# Isoympyraetaisyys (haversine)
etaisyys_ortodromi <- function(lon1, lat1, lon2, lat2, R = R_MAA) {
  rad <- pi / 180
  dphi <- (lat2 - lat1) * rad
  dlam <- (lon2 - lon1) * rad
  a <- sin(dphi / 2)^2 + cos(lat1 * rad) * cos(lat2 * rad) * sin(dlam / 2)^2
  2 * R * asin(pmin(sqrt(a), 1))
}

# Loksodromietaisyys isometrisen leveyden avulla
etaisyys_loksodromi <- function(lon1, lat1, lon2, lat2, R = R_MAA) {
  rad  <- pi / 180
  phi1 <- lat1 * rad; phi2 <- lat2 * rad
  dphi <- phi2 - phi1
  dlam <- (lon2 - lon1) * rad
  dlam <- ((dlam + pi) %% (2 * pi)) - pi        # lyhin suunta
  dpsi <- log(tan(pi / 4 + phi2 / 2) / tan(pi / 4 + phi1 / 2))
  q <- ifelse(abs(dpsi) > 1e-12, dphi / dpsi, cos(phi1))
  R * sqrt(dphi^2 + q^2 * dlam^2)
}

# Validointi: paivantasaajaa pitkin loksodromi = ortodromi
stopifnot(abs(etaisyys_loksodromi(0, 0, 90, 0) - etaisyys_ortodromi(0, 0, 90, 0)) < 1)
# Validointi: samaa meridiaania pitkin myos yhta pitkat
stopifnot(abs(etaisyys_loksodromi(20, -30, 20, 40) - etaisyys_ortodromi(20, -30, 20, 40)) < 1)
# Loksodromi ei koskaan lyhyempi
set.seed(99)
tst <- tibble(a = runif(5000, -180, 180), b = asin(runif(5000, -1, 1)) * 180 / pi,
              c = runif(5000, -180, 180), d = asin(runif(5000, -1, 1)) * 180 / pi)
stopifnot(all(etaisyys_loksodromi(tst$a, tst$b, tst$c, tst$d) >=
              etaisyys_ortodromi(tst$a, tst$b, tst$c, tst$d) - 1))

cat("Loksodromifunktion validointi lapaisty.\n")
Loksodromifunktion validointi lapaisty.
Code
set.seed(1569)   # Mercatorin kartan julkaisuvuosi
N_REITTI <- 200000

reitit <- tibble(
  lon1 = runif(N_REITTI, -180, 180),
  lat1 = asin(runif(N_REITTI, -1, 1)) * 180 / pi,
  lon2 = runif(N_REITTI, -180, 180),
  lat2 = asin(runif(N_REITTI, -1, 1)) * 180 / pi
) |>
  mutate(
    d_orto = etaisyys_ortodromi(lon1, lat1, lon2, lat2) / 1000,
    d_lokso = etaisyys_loksodromi(lon1, lat1, lon2, lat2) / 1000,
    ylimaara = d_lokso / d_orto - 1
  ) |>
  filter(d_orto > 100, is.finite(ylimaara))

stopifnot(all(reitit$ylimaara >= -1e-6))

reitit <- reitit |>
  mutate(pituusluokka = cut(
    d_orto,
    breaks = c(0, 1000, 3000, 6000, 10000, 20100),
    labels = c("alle 1 000 km", "1 000\u20133 000 km", "3 000\u20136 000 km",
               "6 000\u201310 000 km", "yli 10 000 km")
  ))

lokso_yht <- reitit |>
  group_by(pituusluokka) |>
  summarise(
    n = n(),
    mediaani = median(ylimaara),
    p75 = quantile(ylimaara, 0.75),
    p95 = quantile(ylimaara, 0.95),
    .groups = "drop"
  )

lokso_yht |>
  transmute(
    `Reitin pituus` = pituusluokka,
    `Reittejä` = format(n, big.mark = " "),
    `Ylimääräinen matka, mediaani` = percent(mediaani, accuracy = 0.1),
    `75. persentiili` = percent(p75, accuracy = 0.1),
    `95. persentiili` = percent(p95, accuracy = 0.1)
  ) |>
  knitr::kable(caption = "Kuinka paljon pidempi suora kompassikurssi on lyhimpään reittiin verrattuna")
Kuinka paljon pidempi suora kompassikurssi on lyhimpään reittiin verrattuna
Reitin pituus Reittejä Ylimääräinen matka, mediaani 75. persentiili 95. persentiili
alle 1 000 km 1 234 0.0% 0.0% 0.2%
1 000–3 000 km 9 547 0.0% 0.3% 2.5%
3 000–6 000 km 30 350 0.2% 1.3% 12.8%
6 000–10 000 km 58 944 1.0% 4.9% 25.8%
yli 10 000 km 99 916 3.1% 9.2% 22.8%
Code
# Luokiteltu puolisilmakuvaaja ei toimi tassa: ylimaarainen matka on nollassa
# kiinni valtaosalla reitteja, jolloin tiheysestimaatista tulee nakymattoman
# kapea piikki. Piirretaan sen sijaan kvantiilivyohykkeet jatkuvana.

BIN_LEVEYS <- 500   # km

vyohykkeet <- reitit |>
  mutate(bin = floor(d_orto / BIN_LEVEYS) * BIN_LEVEYS + BIN_LEVEYS / 2) |>
  group_by(bin) |>
  filter(n() >= 200) |>          # ohuet reunabinit pois
  summarise(
    n   = n(),
    p05 = quantile(ylimaara, 0.05),
    p25 = quantile(ylimaara, 0.25),
    p50 = quantile(ylimaara, 0.50),
    p75 = quantile(ylimaara, 0.75),
    p95 = quantile(ylimaara, 0.95),
    .groups = "drop"
  )

stopifnot(nrow(vyohykkeet) > 20, all(vyohykkeet$p05 >= 0))

# Toinen paneeli: kuinka suuri osuus reiteista jaa alle viiden prosentin
# ylimaaran. Tama on se luku, joka ratkaisi asian 1500-luvun navigaattorille.
osuudet <- reitit |>
  mutate(bin = floor(d_orto / BIN_LEVEYS) * BIN_LEVEYS + BIN_LEVEYS / 2) |>
  group_by(bin) |>
  filter(n() >= 200) |>
  summarise(osuus_alle_5 = mean(ylimaara < 0.05), .groups = "drop")

pA <- ggplot(vyohykkeet, aes(x = bin)) +
  geom_ribbon(aes(ymin = p05, ymax = p95), fill = "#457b9d", alpha = 0.25) +
  geom_ribbon(aes(ymin = p25, ymax = p75), fill = "#457b9d", alpha = 0.45) +
  geom_line(aes(y = p50), colour = "#f4a261", linewidth = 1.1) +
  geom_hline(yintercept = 0.05, colour = "#e63946",
             linetype = "dashed", linewidth = 0.6) +
  annotate("text", x = max(vyohykkeet$bin), y = 0.052,
           label = "5 %", hjust = 1, vjust = 0, colour = "#e63946", size = 3.4) +
  scale_x_continuous(labels = function(x) format(x, big.mark = " ")) +
  scale_y_continuous(
    trans  = "sqrt",                     # neliojuuriasteikko: pieni pää auki
    breaks = c(0, 0.01, 0.05, 0.1, 0.2, 0.4, 0.6),
    labels = percent_format(accuracy = 1)
  ) +
  labs(
    title    = "Suoran kompassikurssin hinta kasvaa matkan mukana",
    subtitle = paste0(
      "Oranssi viiva = mediaani. Tumma vyöhyke = keskimmäiset 50 %, ",
      "vaalea = keskimmäiset 90 %."
    ),
    x = NULL, y = "Ylimääräinen matka"
  ) +
  theme_kristian()

pB <- ggplot(osuudet, aes(x = bin, y = osuus_alle_5)) +
  geom_line(colour = "#2a9d8f", linewidth = 1.1) +
  geom_hline(yintercept = 0.5, colour = "grey50", linetype = "dotted") +
  scale_x_continuous(labels = function(x) format(x, big.mark = " ")) +
  scale_y_continuous(labels = percent, limits = c(0, 1)) +
  labs(
    x = "Reitin pituus (km)",
    y = "Osuus reiteistä,\njoilla hukka alle 5 %"
  ) +
  theme_kristian()

(pA / pB) +
  plot_layout(heights = c(2.2, 1)) +
  plot_annotation(
    caption = "200 000 satunnaista reittiparia maapallon pinnalla, tasa-aluepainotettu otanta"
  ) &
  theme_kristian()
Figure 8: Suoran kompassikurssin hinta reitin pituuden funktiona.

Miten tätä kuvaa luetaan. Jokainen harjanne vastaa yhtä reitinpituusluokkaa. Pystyakselilla on se, kuinka monta prosenttia pidempi suora kompassikurssi on verrattuna lyhimpään mahdolliseen reittiin. Nolla tarkoittaisi, että hukkaa ei synny lainkaan. Harjanteen leveys kertoo, kuinka paljon tulos vaihtelee reitistä toiseen: lyhyillä reiteillä lähes kaikki reitit ovat lähellä nollaa, mutta pitkillä reiteillä hajonta kasvaa rajusti.

Tässä on Mercatorin puolustus numeroina. Lyhyillä ja keskipitkillä reiteillä suoran kompassikurssin hinta on tyypillisesti muutamia prosentteja. 1500-luvun purjelaivalle tuo muutaman prosentin lisämatka oli täysin merkityksetön verrattuna siihen etuun, että navigointi muuttui viivaimen ja kompassin tehtäväksi. Isoympyräreitin laskeminen käsin pallotrigonometrialla oli aikansa huipputiedettä.

NoteFaktalaatikko 5: Web Mercator ja se, miksi Mercator ei ole poistunut

Mercatorin projektio ei elä pelkästään historiasta. Käytännössä jokainen verkkokartta — Google Maps, OpenStreetMap, Bing, useimmat mobiilisovellukset — käyttää muunnelmaa nimeltä Web Mercator (EPSG:3857). Sen leveyspiirialue katkaistaan noin ±85,05 asteeseen, koska muuten kartta olisi äärettömän korkea.

Battersby, Finn, Usery & Yamamoto (2014) analysoivat, miksi Web Mercator syrjäytti muut vaihtoehdot verkkokartoissa. Keskeiset syyt ovat teknisiä: konformisuus tarkoittaa, että kartta näyttää oikealta millä tahansa zoomaustasolla, kadut kohtaavat oikeissa kulmissa, eikä kuvaa tarvitse laskea uudelleen kun käyttäjä zoomaa. Lisäksi projektio on laskennallisesti erittäin kevyt, mikä 2000-luvun alun palvelinkapasiteetilla oli ratkaisevaa.

Vastaskenaario: jos verkkokartat käyttäisivät Equal Earthia, katujen kulmat piirtyisivät väärin ja korttelit vinoutuisivat sitä enemmän, mitä kauempana päiväntasaajasta ollaan. Suomessa, jossa vääristymä olisi merkittävä, kaupunkinavigointi kärsisi konkreettisesti. YK:n suositus koskeekin maailmankarttoja — ei paikallista navigointia. Tätä eroa ei uutisoinnissa juuri tehdä, ja se on eron ymmärtämisen kannalta olennainen.

Lähde: Battersby, S. E., Finn, M. P., Usery, E. L. & Yamamoto, K. H. (2014). Implications of web Mercator and its use in online mapping. Cartographica, 49(2), 85–101.


14 Vastaskenaariot: mitä olisi voinut olla toisin

Nyt tehdään se, mitä tässä blogissa aina tehdään: kysytään, mitä olisi tapahtunut toisenlaisilla valinnoilla. Vastaskenaario on paras tapa tehdä valinnan hinta näkyväksi, koska se pakottaa vertaamaan johonkin konkreettiseen vaihtoehtoon sen sijaan että vertailtaisiin abstraktiin “täydellisyyteen”, jota ei ole olemassa.

14.1 Vastaskenaario 1: entä jos maailma olisi valinnut Gall–Petersin?

1970- ja 80-luvuilla käytiin kiivas julkinen kiista, jossa saksalainen historioitsija Arno Peters vaati Mercatorin korvaamista pinta-alatarkalla projektiolla. Monet järjestöt, kuten UNESCO ja useat kehitysyhteistyöjärjestöt, ottivat sen käyttöön. Kartografit vastustivat kiivaasti. Miksi?

Code
vertailu_cf1 <- vaaristymat |>
  filter(projektio %in% c("Gall-Peters", "Equal Earth", "Mollweide", "Eckert IV")) |>
  group_by(projektio) |>
  summarise(
    kulma_mediaani = median(omega),
    kulma_p90      = quantile(omega, 0.90),
    osuus_yli_30   = mean(omega > 30),
    .groups = "drop"
  ) |>
  arrange(kulma_mediaani)

vertailu_cf1 |>
  transmute(
    Projektio = projektio,
    `Kulmavirhe, mediaani` = paste0(formatC(kulma_mediaani, digits = 1, format = "f"), "\u00b0"),
    `Kulmavirhe, 90 %`     = paste0(formatC(kulma_p90, digits = 1, format = "f"), "\u00b0"),
    `Osuus maa-alasta, jossa yli 30°` = percent(osuus_yli_30, accuracy = 0.1)
  ) |>
  knitr::kable(caption = "Pinta-alatarkkojen projektioiden muotovirheet")
Pinta-alatarkkojen projektioiden muotovirheet
Projektio Kulmavirhe, mediaani Kulmavirhe, 90 % Osuus maa-alasta, jossa yli 30°
Equal Earth 18.4° 51.0° 25.9%
Eckert IV 19.2° 51.7° 22.6%
Mollweide 21.8° 63.5° 38.9%
Gall-Peters 30.6° 48.0° 51.7%

Kaikki neljä ovat täsmälleen yhtä hyviä pinta-aloissa — mutta niiden muotovirheet eroavat merkittävästi. Gall–Peters venyttää tropiikin pystysuuntaan ja litistää napa-alueet vaakasuuntaan. Se on syy siihen, miksi kartografit inhosivat sitä ja miksi se ei koskaan levinnyt laajaan käyttöön kiistan kiivaudesta huolimatta.

Vastaskenaarion johtopäätös: jos Gall–Peters olisi voittanut 1980-luvulla, pinta-alakiista olisi ratkennut mutta muotokiista olisi alkanut. Equal Earthin todellinen saavutus on, että se saavuttaa saman pinta-alatarkkuuden huomattavasti pienemmällä muotovirheellä. Se on paras vastaus kysymykseen “miksi juuri Equal Earth eikä jokin vanha pinta-alatarkka projektio”.

14.2 Vastaskenaario 2: entä jos kartta olisi keskitetty ihmisten mukaan?

Kaikki maailmankartat keskitetään käytännössä Greenwichin nollameridiaaniin. Se ei ole matemaattinen välttämättömyys vaan historiallinen sopimus vuodelta 1884. Equal Earthin muotovirhe kasvaa sitä suuremmaksi, mitä kauempana kartan keskimeridiaanista ollaan. Mikä olisi väestön kannalta optimaalinen keskimeridiaani?

Code
testi_lon0 <- seq(-180, 175, by = 5)

kohdat <- maapisteet |>
  inner_join(select(painot, ISO3_CODE, paino), by = "ISO3_CODE") |>
  slice_sample(n = 20000)

stopifnot(nrow(kohdat) == 20000, all(kohdat$paino > 0))

koord_k <- as.matrix(kohdat[, c("lon", "lat")])

# Kartan takasauma on kohdassa lon_0 + 180. Numeerinen derivaatta hyppaa siina,
# joten sen valittomassa laheisyydessa olevat pisteet on jatettava pois.
# Poisjaava kaista on 1 aste 360:sta, eli vaikutus tuloksiin on olematon.
optimointi <- map_dfr(testi_lon0, function(l0) {
  crs <- sprintf("+proj=eqearth +lon_0=%g +R=6371007 +no_defs", l0)

  suht_lon <- ((koord_k[, 1] - l0 + 180) %% 360) - 180
  ok <- abs(abs(suht_lon) - 180) > 0.5

  v <- laske_vaaristymat(koord_k[ok, , drop = FALSE], crs)
  stopifnot(all(is.finite(v$omega)))

  tibble(
    lon_0 = l0,
    n_kaytetty = sum(ok),
    kulma_painotettu = weighted.mean(v$omega, kohdat$paino[ok]),
    kulma_maa_ala    = mean(v$omega)
  )
})

stopifnot(min(optimointi$n_kaytetty) > 19800)

paras_vaesto <- optimointi$lon_0[which.min(optimointi$kulma_painotettu)]
paras_ala    <- optimointi$lon_0[which.min(optimointi$kulma_maa_ala)]
greenwich    <- optimointi |> filter(lon_0 == 0)

cat("Vaestopainotettu optimi:", paras_vaesto, "astetta\n")
Vaestopainotettu optimi: 75 astetta
Code
cat("Maa-alapainotettu optimi:", paras_ala, "astetta\n")
Maa-alapainotettu optimi: 40 astetta
Code
cat("Greenwich (nykyinen), vaestopainotettu kulmavirhe:",
    round(greenwich$kulma_painotettu, 2), "astetta\n")
Greenwich (nykyinen), vaestopainotettu kulmavirhe: 19.71 astetta
Code
cat("Optimi, vaestopainotettu kulmavirhe:",
    round(min(optimointi$kulma_painotettu), 2), "astetta\n")
Optimi, vaestopainotettu kulmavirhe: 16.39 astetta
Code
cat("Parannus:",
    percent(1 - min(optimointi$kulma_painotettu) / greenwich$kulma_painotettu,
            accuracy = 0.1), "\n")
Parannus: 16.8% 
Code
optimointi_pitka <- optimointi |>
  select(-n_kaytetty) |>
  pivot_longer(c(kulma_painotettu, kulma_maa_ala),
               names_to = "painotus", values_to = "kulmavirhe") |>
  mutate(painotus = recode(painotus,
                           kulma_painotettu = "Väestöpainotettu",
                           kulma_maa_ala    = "Maapinta-alapainotettu"))

ggplot(optimointi_pitka, aes(x = lon_0, y = kulmavirhe, colour = painotus)) +
  geom_line(linewidth = 1.1) +
  geom_vline(xintercept = 0, colour = "#e63946", linetype = "dashed") +
  annotate("text", x = 3, y = max(optimointi_pitka$kulmavirhe),
           label = "Greenwich", hjust = 0, colour = "#e63946", size = 3.4) +
  scale_x_continuous(breaks = seq(-180, 180, 60),
                     labels = function(x) paste0(x, "\u00b0")) +
  scale_y_continuous(labels = function(x) paste0(round(x, 1), "\u00b0")) +
  scale_colour_manual(values = c("Väestöpainotettu" = "#2a9d8f",
                                 "Maapinta-alapainotettu" = "#f4a261")) +
  labs(
    title    = "Kartan keskikohdan valinta on mitattava päätös",
    subtitle = "Keskimääräinen muotovirhe Equal Earthilla eri keskimeridiaaneilla",
    x = "Kartan keskimeridiaani", y = "Keskimääräinen kulmavirhe",
    colour = NULL
  ) +
  theme_kristian()
Figure 9: Equal Earthin muotovirhe kartan keskikohdan funktiona.

Miten tätä kuvaa luetaan. Vaaka-akselilla on se, minkä pituuspiirin kohdalle kartta keskitetään. Pystyakselilla on siitä seuraava keskimääräinen muotovirhe. Mitä alempana käyrä on, sitä parempi kyseinen keskitys on. Punainen katkoviiva on nykyinen valinta, Greenwich. Kaksi käyrää vastaavat kahta eri kysymystä: vihreä painottaa ihmisiä, oranssi maapinta-alaa. Huomaa, että käyrät eivät ole minimissään samassa kohdassa — kysymyksenasettelu määrää vastauksen.

Tämä on hyvä esimerkki siitä, että karttavalinnassa on useampi kuin yksi poliittinen päätös. Projektion valinta on ollut uutisissa. Keskimeridiaanin valinta ei ole, vaikka sillä on mitattava vaikutus siihen, kenen alue piirtyy kartalle hyvin ja kenen huonosti.

14.3 Vastaskenaario 3: entä jos standardileveyspiiri valittaisiin väestön mukaan?

Gall–Petersin projektio on erikoistapaus sylinterisestä pinta-alaprojektiosta, jonka ainoa parametri on ns. standardileveyspiiri: se leveyspiiri, jolla muodot ovat täsmälleen oikein. Peters valitsi 45 astetta. Mitä tapahtuisi muilla valinnoilla?

Code
testi_lat <- seq(0, 75, by = 1)

lat_optimointi <- map_dfr(testi_lat, function(lt) {
  crs <- sprintf("+proj=cea +lat_ts=%g +lon_0=0 +R=6371007 +no_defs", lt)
  v <- laske_vaaristymat(koord_k, crs)
  tibble(
    lat_ts = lt,
    kulma_painotettu = weighted.mean(v$omega, kohdat$paino),
    kulma_maa_ala    = mean(v$omega)
  )
})

paras_lat_vaesto <- lat_optimointi$lat_ts[which.min(lat_optimointi$kulma_painotettu)]
paras_lat_ala    <- lat_optimointi$lat_ts[which.min(lat_optimointi$kulma_maa_ala)]
peters <- lat_optimointi |> filter(lat_ts == 45)

cat("Petersin valinta (45 astetta), vaestopainotettu kulmavirhe:",
    round(peters$kulma_painotettu, 2), "\n")
Petersin valinta (45 astetta), vaestopainotettu kulmavirhe: 26.04 
Code
cat("Vaestopainotettu optimi:", paras_lat_vaesto, "astetta, kulmavirhe:",
    round(min(lat_optimointi$kulma_painotettu), 2), "\n")
Vaestopainotettu optimi: 26 astetta, kulmavirhe: 12.81 
Code
cat("Maa-alapainotettu optimi:", paras_lat_ala, "astetta\n")
Maa-alapainotettu optimi: 29 astetta
Code
lat_pitka <- lat_optimointi |>
  pivot_longer(c(kulma_painotettu, kulma_maa_ala),
               names_to = "painotus", values_to = "kulmavirhe") |>
  mutate(painotus = recode(painotus,
                           kulma_painotettu = "Väestöpainotettu",
                           kulma_maa_ala    = "Maapinta-alapainotettu"))

ggplot(lat_pitka, aes(x = lat_ts, y = kulmavirhe, colour = painotus)) +
  geom_line(linewidth = 1.1) +
  geom_vline(xintercept = 45, colour = "#e63946", linetype = "dashed") +
  annotate("text", x = 46, y = max(lat_pitka$kulmavirhe),
           label = "Petersin valinta", hjust = 0, colour = "#e63946", size = 3.4) +
  scale_x_continuous(labels = function(x) paste0(x, "\u00b0")) +
  scale_y_continuous(labels = function(x) paste0(round(x, 1), "\u00b0")) +
  scale_colour_manual(values = c("Väestöpainotettu" = "#2a9d8f",
                                 "Maapinta-alapainotettu" = "#f4a261")) +
  labs(
    title    = "Petersin valinta ei ollut optimaalinen kummallakaan mittarilla",
    subtitle = "Sylinterinen pinta-alaprojektio: muotovirhe standardileveyspiirin mukaan",
    x = "Standardileveyspiiri", y = "Keskimääräinen kulmavirhe", colour = NULL
  ) +
  theme_kristian()
Figure 10: Sylinterisen pinta-alaprojektion muotovirhe standardileveyspiirin funktiona.

Tämä on hyvä esimerkki siitä, mitä numerot voivat tehdä poliittiselle väittelylle. Petersin kartta esitettiin 1970-luvulla ainoana oikeudenmukaisena vaihtoehtona, ja sen parametrivalintaa ei juuri kyseenalaistettu. Kun sama projektioperhe optimoidaan eksplisiittisellä kriteerillä, käy ilmi, että valinta oli mielivaltainen. Ja tässä on olennainen huomio: argumentti “pinta-alat oikein” ei riitä määrittämään yhtä karttaa. Pinta-alatarkkoja projektioita on ääretön määrä, ja niiden välillä valitseminen vaatii jonkin toisen kriteerin. Se toinen kriteeri on aina arvovalinta.

14.4 Vastaskenaario 4: entä jos kouluihin valittaisiin kompromissiprojektio?

Yhdysvaltain National Geographic Society käyttää maailmankartoissaan Winkel Tripeliä, ei Equal Earthia eikä Mercatoria. Miten se pärjää?

Code
vertailu_cf4 <- vaaristymat |>
  filter(projektio %in% c("Mercator", "Equal Earth", "Winkel Tripel",
                          "Robinson", "Natural Earth")) |>
  group_by(projektio) |>
  summarise(
    pa_p05  = quantile(r_pinta_ala, 0.05),
    pa_p95  = quantile(r_pinta_ala, 0.95),
    pa_suhde = pa_p95 / pa_p05,
    om_med  = median(omega),
    om_p95  = quantile(omega, 0.95),
    .groups = "drop"
  ) |>
  arrange(pa_suhde)

vertailu_cf4 |>
  transmute(
    Projektio = projektio,
    `Pinta-alojen ylin/alin -suhde` = paste0(formatC(pa_suhde, digits = 1, format = "f"), "\u00d7"),
    `Kulmavirhe, mediaani` = paste0(formatC(om_med, digits = 1, format = "f"), "\u00b0"),
    `Kulmavirhe, 95 %` = paste0(formatC(om_p95, digits = 1, format = "f"), "\u00b0")
  ) |>
  knitr::kable(caption = "Kompromissiprojektiot Mercatorin ja Equal Earthin välissä")
Kompromissiprojektiot Mercatorin ja Equal Earthin välissä
Projektio Pinta-alojen ylin/alin -suhde Kulmavirhe, mediaani Kulmavirhe, 95 %
Equal Earth 1.0× 18.4° 63.2°
Robinson 1.7× 12.0° 54.3°
Natural Earth 1.7× 12.2° 49.9°
Winkel Tripel 1.8× 13.8° 48.8°
Mercator 6.8× 0.0° 0.0°

Kompromissiprojektiot tekevät täsmälleen sen, mitä nimi lupaa. Winkel Tripel ja Robinson vääristävät pinta-aloja selvästi vähemmän kuin Mercator ja muotoja selvästi vähemmän kuin Equal Earth — mutta kumpaakaan ne eivät saa oikein.

Vastaskenaarion johtopäätös: jos tavoite on “kartta, joka ei valehtele pahasti missään”, kompromissiprojektio on puolustettavissa. Jos tavoite on “kartta, jonka pinta-aloista voi lukea totuuden”, vain pinta-alatarkka kelpaa. YK:n päätös valitsi jälkimmäisen. Se on arvovalinta, ei tekninen totuus, ja juuri siksi siitä äänestettiin eikä sitä laskettu.


15 Yhteenveto: mitä tästä jää käteen

Kootaan langat yhteen.

  1. Jokainen tasokartta on väärä. Gaussin Theorema Egregium takaa sen. Ei ole olemassa parasta karttaa, on vain kartta, joka on paras johonkin tarkoitukseen.

  2. Mercator vääristää pinta-aloja systemaattisesti ja voimakkaasti. Vääristymä ei ole yksi luku vaan jakauma, jonka ylä- ja alapään ero on monikymmenkertainen.

  3. Kampanjan iskulause “Afrikka ja Grönlanti näyttävät samankokoisilta” ei kestä tarkkaa tarkastelua — mutta sen taustalla oleva väite Mercatorin systemaattisesta vääristymästä kestää.

  4. Väestöpainotettuna Mercator on paljon vähemmän paha. Suurin osa maailman ihmisistä asuu alueilla, joissa vääristymä on maltillinen. Mercatorin todellinen ongelma on se, että se antaa muutamalle harvaan asutulle pohjoiselle alueelle suhteettoman visuaalisen painoarvon.

  5. Equal Earth maksaa pinta-alatarkkuutensa muodoissa, ja muotovirhe on suurin juuri kartan reunoilla ja korkeilla leveyspiireillä.

  6. Mercator ei ollut virhe. Se ratkaisi navigointiongelman erinomaisesti, ja suoran kompassikurssin hinta oli tyypillisesti muutamia prosentteja.

  7. Pinta-alatarkkuus ei määrää yhtä karttaa. Niitä on ääretön määrä, ja niiden välillä valitaan jollakin toisella kriteerillä, joka on aina arvovalinta.

  8. Karttavalinnassa on useampi kuin yksi poliittinen päätös. Keskimeridiaanin valinta on niistä toinen, ja siitä ei keskustella lainkaan, vaikka sen vaikutus on mitattava.

Jos yksi asia jää mieleen, olkoon se tämä: kun joku esittää yhden luvun, kysy mikä jakauma sen takana on ja millä painotuksella se on laskettu. Karttaprojektioiden tapauksessa vastaus muuttuu radikaalisti sen mukaan, lasketaanko neliökilometrejä vai ihmisiä.


16 Miten tätä käytetään yrityksessä

Tämä ei ollut myyntipostaus, mutta olisi epärehellistä olla sanomatta ääneen, että sama matematiikka tulee vastaan yritysdatassa joka viikko. Tässä konkreettiset kohdat, joissa olen nähnyt täsmälleen tämän ilmiön:

1. Koneoppimisen dimensioreduktio on karttaprojektio. Kun ajat UMAP- tai t-SNE-visualisoinnin asiakassegmenteistä, teet täsmälleen saman asian: kuvaat korkeaulotteisen avaruuden kaksiulotteiselle tasolle. Ja täsmälleen sama Theorema Egregium pätee — et voi säilyttää sekä paikallisia että globaalisia etäisyyksiä. t-SNE valitsee paikalliset (kuin Mercator kulmat), MDS valitsee globaalit (kuin Equal Earth pinta-alat). Klusterien koko t-SNE-kuvassa ei tarkoita mitään, aivan kuten Grönlannin koko Mercatorilla ei tarkoita mitään. Tämä on yleisimpiä virheitä, joita näen data-esityksissä.

2. Karttavisualisointi liiketoimintadatassa. Kun piirrät myyntiluvut Suomen kartalle värialueina, Lapin kunnat vievät valtavan osan pinta-alasta ja Helsinki mikroskooppisen — vaikka asiakkaista valtaosa on Helsingissä. Katsojan silmä painottaa pinta-alaa, ei asiakasmäärää. Ratkaisuja on: kartogrammi, jossa alueiden koko skaalataan asiakasmäärään, tai heksakartta, jossa jokainen alue on samankokoinen. Molemmat ovat “vääriä” karttoja, jotka viestivät oikeamman asian.

3. Mittariston normalisointi. Kysymys “mikä on oikea painotus” on täsmälleen sama kuin tässä postauksessa. Onko toimipisteen suoritus per neliömetri, per työntekijä vai per asiakas? Jokainen näistä antaa eri paremmuusjärjestyksen, jokainen on puolustettavissa, eikä yksikään ole “oikea”. Yleisin virhe on valita painotus kertomatta siitä — jolloin mittarista tulee salaa poliittinen väline.

4. Riskimalleissa asteikkovalinta on projektiovalinta. Kun luottoriskimallissa raportoidaan PD:tä, valinta lineaarisen ja logit-asteikon välillä on täsmälleen sama päätös kuin Mercator vs. Equal Earth: toinen säilyttää suhteelliset erot matalilla arvoilla, toinen absoluuttiset erot. Väärä valinta piilottaa riskikeskittymän täsmälleen kuten Mercator piilottaa Afrikan koon.

5. Kysy jokaisesta luvusta, mikä jakauma sen takana on. Tämä on sen arvoinen, että sen voi tehdä johtoryhmän käytännöksi. “Keskimääräinen läpimenoaika on 4 päivää” on Mercator-tason tieto. “Puolet menee alle kahdessa päivässä, mutta joka kymmenes kestää yli kolme viikkoa” on Equal Earth -tason tieto. Jälkimmäinen kertoo, mihin resurssit kannattaa laittaa.

Jos organisaatiossanne on datavisualisointeja tai mittaristoja, joissa epäilette tällaista piilotettua painotusvalintaa, se on tyypillisesti puolen päivän tarkastelu — ja tulos on lähes aina, että vähintään yksi keskeinen mittari kertoo eri tarinan kuin sen oli tarkoitus kertoa. Otan mielelläni tuollaisen tarkastelun tehtäväksi.


17 Lähteet

Matemaattinen perusta

  • Gauss, C. F. (1827). Disquisitiones generales circa superficies curvas. Commentationes Societatis Regiae Scientiarum Gottingensis Recentiores, 6, 99–146.
  • Tissot, N. A. (1881). Mémoire sur la représentation des surfaces et les projections des cartes géographiques. Gauthier-Villars, Pariisi.
  • Snyder, J. P. (1987). Map Projections: A Working Manual. U.S. Geological Survey Professional Paper 1395.

Projektiot ja niiden arviointi

  • Šavrič, B., Patterson, T. & Jenny, B. (2019). The Equal Earth map projection. International Journal of Geographical Information Science, 33(3), 454–465.
  • Goldberg, D. M. & Gott, J. R. (2007). Flexion and skewness in map projections of the Earth. Cartographica, 42(4), 297–318.
  • Battersby, S. E., Finn, M. P., Usery, E. L. & Yamamoto, K. H. (2014). Implications of web Mercator and its use in online mapping. Cartographica, 49(2), 85–101.
  • Canters, F. (2002). Small-scale Map Projection Design. Taylor & Francis.

Kartat ja havaintokyky

  • Battersby, S. E. & Montello, D. R. (2009). Area estimation of world regions and the projection of the global-scale cognitive map. Annals of the Association of American Geographers, 99(2), 273–291.
  • De Boeck, M. & Ooms, K. (2019). Evaluating young people’s area estimation of countries and continents. ISPRS International Journal of Geo-Information, 8(3), 125.
  • Monmonier, M. (2004). Rhumb Lines and Map Wars: A Social History of the Mercator Projection. University of Chicago Press.

Aineistot

  • GISCO / Eurostat: Countries 2020, resoluutio 1:20 milj. (giscoR-paketti)
  • Maailmanpankki: World Development Indicators, indikaattori SP.POP.TOTL
  • PROJ-kirjasto (versio sf-paketin kautta), projektiokaavat

Uutislähteet

  • YK:n yleiskokouksen päätöslauselma 4.9.2026 (164–1, 6 pidättyi)
  • Afrikan unionin tuki Correct the Map -kampanjalle, elokuu 2025