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¶
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:
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¶
Columns: code, date, and one value column named after the indicator, slugified
(Turkish characters transliterated, snake_cased) — e.g. nufus_yogunlugu.
codeis the geographic unit code, kept as a string so it joins cleanly against the boundary frames.dateisYYYYfor annual series andYYYY-MMfor monthly ones (TUIK sendsYYYYMM; 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:
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}.