← 返回 Tessera
经济217 行 × 1741 KB12 列有缺失数据 2026-04-16

economy_countries

带坐标、两级分类和多个跨数量级的连续量——这批数据里唯一能直接上地图的一份,也是练缺失值处理的那份。

World Bank WDI + REST Countries · 收录于 2026-04-16

下载 CSV · 41 KB
数据预览6
countryiso3iso2regionincome_levelcapitallongitudelatitudeyearpopulationgdp_usdgdp_per_capita_usdinflation_pctunemployment_pctexports_pct_gdpimports_pct_gdptrade_pct_gdp
AfghanistanAFGAFMiddle East & North AfricaLow incomeKabul65.033.02,0234.15e71.72e+10413.8-4.6414.016.950.767.6
AlbaniaALBALEurope & Central AsiaUpper middle incomeTirana20.041.02,0232.41e62.35e+109,7314.7610.738.443.982.4
AlgeriaDZADZMiddle East & North AfricaUpper middle incomeAlgiers328.02,0234.62e72.48e+115,3709.3211.623.819.943.7
American SamoaASMASEast Asia & PacificHigh incomePago Pago-170.0-14.32,02347,521NANANANANANANA
AndorraANDADEurope & Central AsiaHigh incomeAndorra la Vella1.542.52,02380,8563.79e946,812NANANANANA
AngolaAGOAOSub-Saharan AfricaLower middle incomeLuanda18.5-12.52,0233.67e71.07e+112,91613.614.132.320.753.0
变量17
字符型6整数2双精度9
类型
#变量类型缺失统计
1country字符型不同值 217Afghanistan · Albania · Algeria · American Samoa · Andorra · Angola · Antigua and Barbuda · Argentina
2iso3字符型不同值 217AFG · ALB · DZA · ASM · AND · AGO · ATG · ARG
3iso2字符型2不同值 215AF · AL · DZ · AS · AD · AO · AG · AR
4region字符型不同值 7Europe & Central Asia · Sub-Saharan Africa · Latin America & Caribbean · East Asia & Pacific · Middle East & North Africa · South Asia · North America
5income_level字符型2不同值 4High income · Upper middle income · Lower middle income · Low income
6capitalREST Countries · capital字符型3不同值 214Kabul · Tirana · Algiers · Pago Pago · Andorra la Vella · Luanda · Saint John's · Buenos Aires
7longitudeREST Countries · latlng[1]双精度2min -175.0q1 中位 19.5均值 18.2q3 max 178.1
8latitudeREST Countries · latlng[0]双精度2min -41.0q1 中位 17.3均值 19.1q3 max 72.0
9year整数min 2,023q1 中位 2,023均值 2,023q3 max 2,023
10populationWDI · SP.POP.TOTL整数min 9,816q1 中位 6.6e+6均值 3.7e+7q3 max 1.4e+9
11gdp_usdWDI · NY.GDP.MKTP.CD双精度14min 6.2e+7q1 中位 4.0e+10均值 5.2e+11q3 max 2.7e+13
12gdp_per_capita_usdkeyWDI · NY.GDP.PCAP.CD双精度14min 250.6q1 中位 7,826均值 21,902q3 max 256,800
13inflation_pctWDI · FP.CPI.TOTL.ZG双精度41min -4.64q1 中位 5.87均值 10.2q3 max 221.3
14unemployment_pctWDI · SL.UEM.TOTL.ZS双精度33min 0.13q1 中位 5.21均值 6.78q3 max 34.6
15exports_pct_gdpWDI · NE.EXP.GNFS.ZS双精度44min 1.11q1 中位 37.8均值 45.1q3 max 217.4
16imports_pct_gdpWDI · NE.IMP.GNFS.ZS双精度44min 1.37q1 中位 44.9均值 50.5q3 max 185.2
17trade_pct_gdp= exports_pct_gdp + imports_pct_gdp双精度44min 2.47q1 中位 82.4均值 95.5q3 max 402.6
载入已写好列类型
library(readr)

economy_countries <- read_csv(
  "https://assets.evanzhou.org/tessera/csv/economy_countries.csv",
  col_types = cols(
    country            = col_character(),
    iso3               = col_character(),
    iso2               = col_character(),
    region             = col_character(),
    income_level       = col_character(),
    capital            = col_character(),
    longitude          = col_double(),
    latitude           = col_double(),
    year               = col_integer(),
    population         = col_integer(),
    gdp_usd            = col_double(),
    gdp_per_capita_usd = col_double(),
    inflation_pct      = col_double(),
    unemployment_pct   = col_double(),
    exports_pct_gdp    = col_double(),
    imports_pct_gdp    = col_double(),
    trade_pct_gdp      = col_double()
  )
)
URLhttps://assets.evanzhou.org/tessera/csv/economy_countries.csv

来源

217 个国家或经济体 2023 年的宏观指标。主体取自世界银行 WDI 的批量数据集,首都和国土形心坐标来自 REST Countries。一行是一个经济体。

「经济体」不等于国家。 世界银行的口径里包含香港、澳门这类单独统计的经济体,所以 217 这个数和联合国会员国数对不上——别拿它当"国家数"用,也别拿它算"全球占比"。同理 iso3 里混着世行自己的经济体代码,不全是 ISO 3166 国家码。

变量表里那一列等宽小字是出处句柄,不是我写的定义:WDI · NY.GDP.MKTP.CD 拿去 WDI 一查就是官方口径(现价还是不变价、含不含某项),比我在这儿复述一遍可靠。写不出句柄的列就没有那一行。

用之前要知道的

缺失集中在最想拿来排名的那几列上:贸易三列各缺 44 个、通胀缺 41、失业率缺 33。满列只有 countryiso3regionyearpopulation 五个。

后果很具体:任何按贸易占比或通胀做的排名,实际参与排序的都不是 217 个。dropna() 再报"全球第几"不会报错——你会得到一个看着完全合理的名次。做这类排名时把有效样本量一起写出来。

适用图形

图形结构 用途
分级填色地图 iso3 关联的国家级指标
气泡地图 用形心坐标定位,按人口或 GDP 定大小
排序条形图 按 GDP、人均 GDP、通胀或失业率排名
散点图 人均 GDP 对通胀、失业率或贸易占比
分面小图 按区域或收入分组做对比

income_level 4 档、region 7 类,正好可以拿来试 5 色和多色配色够不够用。人均 GDP 和 GDP 都跨了四个以上数量级,画之前基本都要取对数——上面变量表里那两条分布条一眼就能看出来。

生成脚本Python · 198
"""
economy_countries.py

Build a country-level toy economy dataset.

Sources:
- World Bank WDI bulk CSV for country metadata and economic indicators.
- REST Countries for capital city and country centroid coordinates.

Output:
- assets/toy/economy/economy_countries.csv
"""

from pathlib import Path
import csv
import tempfile
import zipfile

import pandas as pd
import requests


YEAR = 2023
OUT = Path(__file__).with_name("economy_countries.csv")

WDI_BULK_URL = "https://databank.worldbank.org/data/download/WDI_CSV.zip"
REST_COUNTRIES_URL = "https://restcountries.com/v3.1/all"
REQUEST_TIMEOUT = 120

INDICATORS = {
    "SP.POP.TOTL": "population",
    "NY.GDP.MKTP.CD": "gdp_usd",
    "NY.GDP.PCAP.CD": "gdp_per_capita_usd",
    "FP.CPI.TOTL.ZG": "inflation_pct",
    "SL.UEM.TOTL.ZS": "unemployment_pct",
    "NE.EXP.GNFS.ZS": "exports_pct_gdp",
    "NE.IMP.GNFS.ZS": "imports_pct_gdp",
}


def download_wdi_bulk(tmpdir: str) -> Path:
    print("Downloading World Bank WDI bulk CSV")
    zip_path = Path(tmpdir) / "WDI_CSV.zip"

    with requests.get(WDI_BULK_URL, stream=True, timeout=REQUEST_TIMEOUT) as response:
        response.raise_for_status()
        with zip_path.open("wb") as handle:
            for chunk in response.iter_content(chunk_size=1024 * 1024):
                if chunk:
                    handle.write(chunk)

    return zip_path


def read_wdi_countries(archive: zipfile.ZipFile) -> pd.DataFrame:
    country_file = next(
        name for name in archive.namelist()
        if name.endswith("WDICountry.csv")
    )
    records = []

    with archive.open(country_file) as raw:
        reader = csv.DictReader((line.decode("utf-8-sig") for line in raw))
        for row in reader:
            region = row.get("Region")
            if not region or region == "Aggregates":
                continue

            records.append(
                {
                    "country": row.get("Short Name") or row.get("Table Name"),
                    "iso3": row.get("Country Code"),
                    "iso2": row.get("2-alpha code"),
                    "region": region,
                    "income_level": row.get("Income Group"),
                }
            )

    return (
        pd.DataFrame.from_records(records)
        .sort_values("country")
        .reset_index(drop=True)
    )


def read_wdi_indicators(archive: zipfile.ZipFile) -> dict[str, pd.DataFrame]:
    data_file = next(
        name for name in archive.namelist()
        if name.endswith("WDICSV.csv") or name.endswith("WDIData.csv")
    )
    wanted_codes = set(INDICATORS)
    year_column = str(YEAR)
    records = []

    with archive.open(data_file) as raw:
        reader = csv.DictReader((line.decode("utf-8-sig") for line in raw))
        for row in reader:
            code = row.get("Indicator Code")
            if code not in wanted_codes:
                continue

            records.append(
                {
                    "iso3": row.get("Country Code"),
                    "column": INDICATORS[code],
                    "value": row.get(year_column),
                }
            )

    raw_data = pd.DataFrame.from_records(records)
    indicators = {}

    for column in INDICATORS.values():
        data = raw_data.loc[raw_data["column"] == column, ["iso3", "value"]].copy()
        data.rename(columns={"value": column}, inplace=True)
        data[column] = pd.to_numeric(data[column], errors="coerce")
        indicators[column] = data.drop_duplicates("iso3")

    return indicators


def fetch_wdi_bulk() -> tuple[pd.DataFrame, dict[str, pd.DataFrame]]:
    with tempfile.TemporaryDirectory() as tmpdir:
        zip_path = download_wdi_bulk(tmpdir)
        with zipfile.ZipFile(zip_path) as archive:
            countries = read_wdi_countries(archive)
            indicators = read_wdi_indicators(archive)

    return countries, indicators


def fetch_country_geography() -> pd.DataFrame:
    print("Fetching country capitals and coordinates from REST Countries")
    response = requests.get(
        REST_COUNTRIES_URL,
        params={"fields": "cca3,capital,latlng"},
        timeout=REQUEST_TIMEOUT,
    )
    response.raise_for_status()

    records = []
    for row in response.json():
        latlng = row.get("latlng") or [None, None]
        capital = row.get("capital") or []
        records.append(
            {
                "iso3": row.get("cca3"),
                "capital": capital[0] if capital else "",
                "latitude": latlng[0] if len(latlng) > 0 else None,
                "longitude": latlng[1] if len(latlng) > 1 else None,
            }
        )

    geography = pd.DataFrame.from_records(records)
    geography["latitude"] = pd.to_numeric(geography["latitude"], errors="coerce")
    geography["longitude"] = pd.to_numeric(geography["longitude"], errors="coerce")
    return geography.drop_duplicates("iso3")


def main() -> None:
    countries, indicators = fetch_wdi_bulk()
    geography = fetch_country_geography()

    data = countries.merge(geography, on="iso3", how="left")
    data["year"] = YEAR

    for indicator in indicators.values():
        data = data.merge(indicator, on="iso3", how="left")

    data["trade_pct_gdp"] = data["exports_pct_gdp"] + data["imports_pct_gdp"]

    columns = [
        "country",
        "iso3",
        "iso2",
        "region",
        "income_level",
        "capital",
        "longitude",
        "latitude",
        "year",
        "population",
        "gdp_usd",
        "gdp_per_capita_usd",
        "inflation_pct",
        "unemployment_pct",
        "exports_pct_gdp",
        "imports_pct_gdp",
        "trade_pct_gdp",
    ]

    data = data[columns]
    data.to_csv(OUT, index=False)
    print(f"Done: {len(data)} countries saved to {OUT}")


if __name__ == "__main__":
    main()

表中统计由 scripts/profile_dataset.py 于 2026-08-03 数出。