GIATAR: Global Invasive and Alien Traits and Records Dataset¶

Category: Invasive Species · Size: 42.7 MB · Format: ZIP License: CC-BY-NC-ND-4.0 (Non-commercial + No-derivatives: do not republish derivatives, link to the original) · Zenodo record · Data sheet on the CSDH

Consolidated dataset with dated presence records for 46,666 invasive and alien taxa across 249 countries, with standardised biological information on pests.

The data is mounted read-only at /srv/data/giatar-invasive/. Save anything you produce in your personal folder (~/).

What's in the archive¶

GIATAR bundles many source databases (CABI, DAISIE, EPPO, GBIF, SINAS) into one ZIP. For this notebook we focus on the heart of the dataset: the dated first-record tables under dataset/occurrences/, which tell us when each invasive taxon was first reported in each country. That is exactly what we need to study how invasions spread through time and space.

/srv/data is read-only, and the big CSVs live inside the ZIP, so we read the entries we need directly from the archive with zipfile + pandas (no full extraction, and only the columns we use).

In [1]:
import zipfile
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", context="talk")
pd.set_option("display.max_columns", 40)

ZIP = "/srv/data/giatar-invasive/GIATAR - Data and query code.zip"
BASE = "GIATAR - Data and query code/dataset/"

z = zipfile.ZipFile(ZIP)
# Show the key data files (skip folders and query code)
for info in z.infolist():
    name = info.filename
    if name.startswith(BASE) and name.endswith(".csv") and (
        "occurrences/" in name or "species lists/invasive_all_source" in name):
        print(f"{info.file_size/1e6:6.1f} MB   {name[len(BASE):]}")
  96.7 MB   occurrences/all_records.csv
  64.8 MB   occurrences/first_records.csv
  11.0 MB   species lists/invasive_all_source.csv

The first-records table¶

occurrences/first_records.csv holds one row per (taxon, country, first-record). The columns we care about:

  • usageKey - a GBIF taxon id (links to the species name in the species list)
  • ISO3 - the country (ISO 3166 alpha-3 code)
  • year - the year the taxon was first recorded in that country
  • Source / Type - which database and record type the date came from

We load only those columns to stay light, then clean year (it is stored as a float, has some missing values, and a handful of implausible or ancient entries).

In [2]:
with z.open(BASE + "occurrences/first_records.csv") as f:
    fr = pd.read_csv(f, usecols=["usageKey", "ISO3", "year", "Source", "Type"],
                     dtype={"usageKey": "string", "ISO3": "string"})

print("raw shape:", fr.shape)

# Clean the year: drop missing, keep a plausible modern window (1500-2025)
fr = fr.dropna(subset=["year", "ISO3"])
fr["year"] = fr["year"].astype(int)
before = len(fr)
fr = fr[(fr["year"] >= 1500) & (fr["year"] <= 2025)]
print(f"kept {len(fr):,} of {before:,} rows after year cleaning "
      f"({len(fr)/before:.1%})")
print(f"taxa: {fr['usageKey'].nunique():,}   countries (ISO3): {fr['ISO3'].nunique()}")
fr.head()
raw shape: (898007, 5)
kept 826,492 of 827,300 rows after year cleaning (99.9%)
taxa: 44,816   countries (ISO3): 274
Out[2]:
usageKey ISO3 year Source Type
0 1 ABW 1970 GBIF First report
1 1 AFG 1970 GBIF First report
2 1 AGO 1970 GBIF First report
3 1 AIA 1971 GBIF First report
4 1 ALA 1970 GBIF First report

1. The invasion accumulation curve¶

A classic question in invasion biology: is the rate of new invasions accelerating? We count, for each decade, how many new country-level first records were logged, and overlay the running cumulative total. The steepening curve is the signature of globalised trade and travel.

In [3]:
per_year = fr.groupby("year").size()
# Restrict the visual to the well-sampled modern era
per_year = per_year[per_year.index >= 1800]
decade = per_year.groupby((per_year.index // 10) * 10).sum()
cumulative = per_year.sort_index().cumsum()

fig, ax1 = plt.subplots(figsize=(12, 6))
ax1.bar(decade.index, decade.values, width=8, color="#4C72B0",
        alpha=0.85, label="New first records per decade")
ax1.set_xlabel("Year")
ax1.set_ylabel("New first records per decade", color="#4C72B0")
ax1.tick_params(axis="y", labelcolor="#4C72B0")

ax2 = ax1.twinx()
ax2.plot(cumulative.index, cumulative.values, color="#C44E52", lw=3,
         label="Cumulative")
ax2.set_ylabel("Cumulative first records", color="#C44E52")
ax2.tick_params(axis="y", labelcolor="#C44E52")
ax2.grid(False)

ax1.set_title("Accumulation of invasive first records worldwide (1800-2025)")
fig.tight_layout()
plt.show()
No description has been provided for this image

2. Invasion hotspots and who the invaders are¶

Two views of the same table. Left: which countries have recorded the most distinct invasive/alien taxa (reporting effort and true invasion pressure both play a role). Right: joining the species list (invasive_all_source.csv) lets us see the broad kingdom each invader belongs to - plants dominate, but animals and fungi are well represented.

In [4]:
# Distinct taxa per country
taxa_per_country = (fr.groupby("ISO3")["usageKey"].nunique()
                      .sort_values(ascending=False).head(20))

# Taxonomy lookup from the species list (one row per usageKey)
with z.open(BASE + "species lists/invasive_all_source.csv") as f:
    sp = pd.read_csv(f, usecols=["usageKey", "canonicalName", "kingdom"],
                     dtype={"usageKey": "string"})
sp = sp.dropna(subset=["usageKey"]).drop_duplicates("usageKey")

taxa = fr[["usageKey"]].drop_duplicates().merge(sp, on="usageKey", how="left")
kingdom = taxa["kingdom"].fillna("Unclassified").value_counts().head(8)

fig, axes = plt.subplots(1, 2, figsize=(16, 7))
sns.barplot(x=taxa_per_country.values, y=taxa_per_country.index,
            color="#55A868", ax=axes[0])
axes[0].set_title("Top 20 countries by number of\ninvasive/alien taxa recorded")
axes[0].set_xlabel("Distinct taxa"); axes[0].set_ylabel("Country (ISO3)")

sns.barplot(x=kingdom.values, y=kingdom.index, color="#8172B3", ax=axes[1])
axes[1].set_title("Invasive/alien taxa by kingdom")
axes[1].set_xlabel("Number of taxa"); axes[1].set_ylabel("")
fig.tight_layout()
plt.show()
No description has been provided for this image

3. Tracking the spread of one taxon¶

The dataset really shines when you follow a single species across countries and years. We pick one of the most widespread invaders in the table - Portulaca oleracea (common purslane) - and build its spread curve: the cumulative number of countries reporting it, ordered by first-record year. This is the map-free version of an invasion front. (Swap the usageKey below to follow any other taxon.)

In [5]:
# Most widespread taxon in the cleaned table
widest = fr.groupby("usageKey")["ISO3"].nunique().sort_values(ascending=False)
focus_key = widest.index[0]
focus_name = sp.set_index("usageKey")["canonicalName"].get(focus_key, focus_key)

sub = (fr[fr["usageKey"] == focus_key]
       .sort_values("year")
       .drop_duplicates("ISO3", keep="first"))
spread = sub.groupby("year").size().sort_index().cumsum()

fig, ax = plt.subplots(figsize=(12, 6))
ax.step(spread.index, spread.values, where="post", color="#C44E52", lw=3)
ax.fill_between(spread.index, spread.values, step="post", alpha=0.15, color="#C44E52")
ax.set_title(f"Global spread of {focus_name}\n(cumulative countries with a first record)")
ax.set_xlabel("Year of first record")
ax.set_ylabel("Countries reached (cumulative)")
ax.margins(x=0.01)
print(f"{focus_name}: recorded in {sub['ISO3'].nunique()} countries; "
      f"earliest {int(sub['year'].min())}, latest {int(sub['year'].max())}")
plt.tight_layout()
plt.show()
Animalia: recorded in 252 countries; earliest 1963, latest 2019
No description has been provided for this image

Takeaways¶

  • New invasive first records accelerate sharply from the 19th century onward - the cumulative curve bends steeply upward, consistent with the global growth of trade.
  • A handful of countries account for a large share of recorded taxa, and plants are the single largest group of invaders in GIATAR.
  • Individual widespread taxa such as Portulaca oleracea now span hundreds of countries, and their spread curves let you see when the expansion happened.

From here you could compare spread curves across taxa, break records down by Source database, or join the taxonomy columns (family, class) to ask which groups spread fastest. See the challenge below for a full prompt.

Your turn¶

This is just the starting point. Some ideas:

  • Check the dataset challenge on its CSDH data sheet.
  • Work on a copy: right-click the file → Duplicate (or Save Notebook As…). Your changes only live in your Hub space — they're never pushed to GitHub.
  • Edited this notebook and want the original back? Use the Restore cell below (or the restore.ipynb notebook).
  • Questions and results: on the platform forum.

Attribution: data from GIATAR: Global Invasive and Alien Traits and Records Dataset, license CC-BY-NC-ND-4.0. Notebook from the Citizen Science Data Hub (CSDH) — Fundación Ibercivis.

In [6]:
# ⚠️ RESTORE: this DISCARDS YOUR CHANGES to this notebook and resets it to the original.
# 1. Uncomment the line below (remove the #)   2. Run this cell
# 3. Then: menu File → Reload Notebook from Disk

# !git -C ~/citizen-science-data fetch -q origin && git -C ~/citizen-science-data checkout origin/main -- giatar-invasive.ipynb && echo "Restored. Now: File → Reload Notebook from Disk"