{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "# Wildlife Network starter notebook\n\nArrival dates, weekly records and observation effort from [Wildlife Network](https://wlnw.org). Methods: https://wlnw.org/methods. API: https://wlnw.org/developers.\n\nCells are 5 degree squares named by their south-west corner; weeks start on Monday. Cite the data release DOI 10.5281/zenodo.23193453 and the GBIF datasets it lists."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "import io, zipfile, urllib.request\nimport pandas as pd\n\nBASE = 'https://wlnw.org'"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 1. Arrival dates for one species (API)\n\nEach row is one 5 degree cell: arrival week with a 90% interval, and support (share of 40 redraws that found a season; below 0.5 is weak)."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "species = 'Hirundo rustica'\narr = pd.read_csv(f\"{BASE}/api/v1/arrivals?species={urllib.parse.quote(species)}&format=csv\", comment='#', parse_dates=['arrivalWeek', 'arrivalLow', 'arrivalHigh', 'peakWeek'])\narr = arr[arr.arrivalSupport >= 0.5]\narr[['region', 'cellLat', 'cellLon', 'arrivalWeek', 'arrivalLow', 'arrivalHigh', 'arrivalSupport']].sort_values('arrivalWeek').head(20)"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 2. Arrival week against latitude\n\nA simple check of the northward progression within one region."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "eu = arr[arr.region == 'Europe'].copy()\neu['doy'] = eu.arrivalWeek.dt.dayofyear\nax = eu.plot.scatter(x='cellLat', y='doy', title=f'{species}: arrival day of year by latitude, Europe')\nprint('days per degree north:', round(eu[['cellLat', 'doy']].cov().iloc[0, 1] / eu.cellLat.var(), 2))"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 3. The open data release (GBIF-derived, CC BY 4.0)\n\nWeekly records per species and cell with the effort behind them, so you can compute shares yourself."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "ZIP = 'https://github.com/wlnw-org/wildnetwork/releases/download/data-2026-10-25/wildnetwork-open-2026-10-25.zip'\nz = zipfile.ZipFile(io.BytesIO(urllib.request.urlopen(ZIP).read()))\nname = lambda f: next(n for n in z.namelist() if n.endswith(f))\nweekly = pd.read_csv(z.open(name('weekly_records.csv')), parse_dates=['week'])\neffort = pd.read_csv(z.open(name('effort.csv')), parse_dates=['week'])\nweekly.head()"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 4. Effort-corrected share for one species in one cell\n\nShare = records of the species / records of its whole class in the same cell and week (the effort)."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "cell = weekly[(weekly.scientific_name == species) & (weekly.cell_lat == 50) & (weekly.cell_lon == 0)]\nm = cell.merge(effort, on=['week', 'cell_lat', 'cell_lon', 'class'], suffixes=('', '_all'))\nm['per_1000'] = 1000 * m.records / m.records_all\nm.plot(x='week', y='per_1000', title=f'{species}, cell 50N 0E, records per 1,000 bird records')"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Next\n\n- Draw a study area and get a dashboard and weekly digest: https://wlnw.org/studies\n- Review arrival estimates you know well (ORCID sign-in): https://wlnw.org/research#review\n- Questions or a study idea: https://wlnw.org/research#propose"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}