Skip to content

Quickstart — geographic data

The geographic portal (cip.tuik.gov.tr) is a separate world from the statistical one: a catalog of regional indicators, their values per geographic unit, and the boundary geometries to draw them on. This walkthrough goes catalog → values → choropleth.

Geographic levels

Level Unit Count Geometry
2 NUTS-2 region 26 Polygon
3 NUTS-3 province 81 Polygon
4 LAU-1 district 973 Polygon
9 Settlement point ~1000 Point — maps only, no indicator data

1. Browse the indicator catalog

import pytuik

catalog = pytuik.geo_variables()

Columns: var_name, var_num, var_levels (a list[int]), var_period (yillik = annual, aylik = monthly).

var_name is the English name when TUIK publishes one and falls back to the Turkish name otherwise; pytuik.geo_variables(lang="tr") asks for Turkish throughout. Find what you want with ordinary pandas:

catalog[catalog["var_name"].str.contains("population", case=False, na=False)]

Two functions where tuikr has one

tuikr's geo_data() returns either the catalog or the data depending on its arguments. Here the catalog is geo_variables() and the data is geo_data() — stable signatures, stable return types.

2. Pull an indicator

df = pytuik.geo_data("1000", level=3)

Columns: code, date, and one value column named after the indicator, slugified (Turkish characters transliterated, snake_cased) — e.g. nufus_yogunlugu.

  • code is the geographic unit code, kept as a string so it joins cleanly against the boundary frames.
  • date is YYYY for annual series and YYYY-MM for monthly ones (TUIK sends YYYYMM; it is normalized here).
  • The value column is a float.

level may be omitted when the indicator is published at exactly one level; when several are available, it is required and the error lists them:

pytuik.geo_data("1000")
# TuikValidationError: Indicator '1000' is available at levels [2, 3, 4];
# pass level=... to choose one.

3. Get the boundaries

gdf = pytuik.geo_map(level=3)                   # GeoDataFrame, EPSG:4326
attrs = pytuik.geo_map(level=3, geometry=False) # plain pandas, no geopandas needed

Polygon levels expose code, bolgeKodu, nutsKodu, ad (plus geometry when requested); level 9 exposes ad, tp, bs, bm.

geometry=True requires the optional extra:

pip install "pytuik[geo]"

Without it you get a clear TuikError telling you exactly that, and geometry=False keeps working — attributes are parsed straight from the JSON, so nothing heavyweight is imported.

About that GeoJSON

TUIK's geometry files declare "type": ["FeatureCollection"] — array-wrapped, and therefore invalid GeoJSON that most parsers reject outright. pytuik repairs the parsed JSON in place (document level and feature level) before handing it to geopandas. This is a targeted fix on the decoded object, not a regex over the raw text.

4. Draw a choropleth

import matplotlib.pyplot as plt
import pytuik

values = pytuik.geo_data("1000", level=3)
latest = values[values["date"] == values["date"].max()]
value_col = latest.columns[-1]

boundaries = pytuik.geo_map(level=3)
merged = boundaries.merge(latest, on="code", how="left")

ax = merged.plot(
    column=value_col,
    cmap="viridis",
    legend=True,
    edgecolor="white",
    linewidth=0.3,
    figsize=(11, 6),
    missing_kwds={"color": "lightgrey"},
)
ax.set_title(f"{value_col} by province, {latest['date'].iloc[0]}")
ax.set_axis_off()
plt.tight_layout()
plt.show()

The join key is code on both sides, which is why pytuik keeps it as a string: province codes have leading zeros that an integer cast would silently eat.

5. Settlement points

Level 9 is the point layer — useful as a reference overlay, and the one level with no indicator data behind it:

points = pytuik.geo_map(level=9)   # Point geometries
provinces = pytuik.geo_map(level=3)

ax = provinces.plot(color="whitesmoke", edgecolor="grey", linewidth=0.3, figsize=(11, 6))
points.plot(ax=ax, markersize=1, color="crimson")
ax.set_axis_off()

Asking for level 9 from geo_data raises TuikValidationError: data levels are {2, 3, 4}, map levels are {2, 3, 4, 9}.