{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c34f0dfb",
   "metadata": {},
   "source": [
    "[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/niconoe/pyinaturalist/main?filepath=examples%2FData%2520Visualizations%2520-%2520Regional%2520Activity%2520Report.ipynb)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "attached-priority",
   "metadata": {},
   "source": [
    "# Regional activity time series visualizations\n",
    "This example shows how to create visualizations of iNaturalist activity over time in a given region.\n",
    "See https://www.inaturalist.org/places to find place IDs.\n",
    "\n",
    "Visualization are made using [Altair](https://altair-viz.github.io), with the following metrics:\n",
    "* Number of observations\n",
    "* Number of taxa observed\n",
    "* Number of observers\n",
    "* Number of identifiers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "prostate-overall",
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import datetime\n",
    "from time import sleep\n",
    "\n",
    "from dateutil.relativedelta import relativedelta\n",
    "from IPython.display import Image\n",
    "from typing import Any, BinaryIO, Dict, Iterable, List, Optional, Tuple\n",
    "\n",
    "import altair as alt\n",
    "import pandas as pd\n",
    "\n",
    "from pyinaturalist import (\n",
    "    get_observations,\n",
    "    get_observation_histogram,\n",
    "    get_observation_species_counts,\n",
    "    get_observation_observers,\n",
    "    get_observation_identifiers,\n",
    ")\n",
    "from pyinaturalist.constants import ICONIC_TAXA\n",
    "from pyinaturalist.request_params import get_interval_ranges\n",
    "\n",
    "# Adjustable values\n",
    "PLACE_ID = 6\n",
    "PLACE_NAME = 'Alaska'\n",
    "YEAR = 2020\n",
    "\n",
    "THROTTLING_DELAY = 1.0  # Time to wait in between subsequent requests"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "angry-longer",
   "metadata": {},
   "source": [
    "### Observations per year"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "joint-interference",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<div id=\"altair-viz-cbdfe038e04a40039fe70f8cf76decbf\"></div>\n",
       "<script type=\"text/javascript\">\n",
       "  (function(spec, embedOpt){\n",
       "    let outputDiv = document.currentScript.previousElementSibling;\n",
       "    if (outputDiv.id !== \"altair-viz-cbdfe038e04a40039fe70f8cf76decbf\") {\n",
       "      outputDiv = document.getElementById(\"altair-viz-cbdfe038e04a40039fe70f8cf76decbf\");\n",
       "    }\n",
       "    const paths = {\n",
       "      \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n",
       "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n",
       "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n",
       "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n",
       "    };\n",
       "\n",
       "    function loadScript(lib) {\n",
       "      return new Promise(function(resolve, reject) {\n",
       "        var s = document.createElement('script');\n",
       "        s.src = paths[lib];\n",
       "        s.async = true;\n",
       "        s.onload = () => resolve(paths[lib]);\n",
       "        s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
       "        document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
       "      });\n",
       "    }\n",
       "\n",
       "    function showError(err) {\n",
       "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
       "      throw err;\n",
       "    }\n",
       "\n",
       "    function displayChart(vegaEmbed) {\n",
       "      vegaEmbed(outputDiv, spec, embedOpt)\n",
       "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
       "    }\n",
       "\n",
       "    if(typeof define === \"function\" && define.amd) {\n",
       "      requirejs.config({paths});\n",
       "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
       "    } else if (typeof vegaEmbed === \"function\") {\n",
       "      displayChart(vegaEmbed);\n",
       "    } else {\n",
       "      loadScript(\"vega\")\n",
       "        .then(() => loadScript(\"vega-lite\"))\n",
       "        .then(() => loadScript(\"vega-embed\"))\n",
       "        .catch(showError)\n",
       "        .then(() => displayChart(vegaEmbed));\n",
       "    }\n",
       "  })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}}, \"data\": {\"name\": \"data-95560fb10a5f5b4c33e79b5b21d9623b\"}, \"mark\": \"bar\", \"encoding\": {\"x\": {\"type\": \"temporal\", \"field\": \"date\", \"timeUnit\": \"year\"}, \"y\": {\"type\": \"quantitative\", \"field\": \"observations\"}}, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\", \"datasets\": {\"data-95560fb10a5f5b4c33e79b5b21d9623b\": [{\"date\": \"2008-01-01T00:00:00\", \"observations\": 1969}, {\"date\": \"2009-01-01T00:00:00\", \"observations\": 1377}, {\"date\": \"2010-01-01T00:00:00\", \"observations\": 2030}, {\"date\": \"2011-01-01T00:00:00\", \"observations\": 1438}, {\"date\": \"2012-01-01T00:00:00\", \"observations\": 4609}, {\"date\": \"2013-01-01T00:00:00\", \"observations\": 2144}, {\"date\": \"2014-01-01T00:00:00\", \"observations\": 3420}, {\"date\": \"2015-01-01T00:00:00\", \"observations\": 4975}, {\"date\": \"2016-01-01T00:00:00\", \"observations\": 8360}, {\"date\": \"2017-01-01T00:00:00\", \"observations\": 21446}, {\"date\": \"2018-01-01T00:00:00\", \"observations\": 25594}, {\"date\": \"2019-01-01T00:00:00\", \"observations\": 36506}, {\"date\": \"2020-01-01T00:00:00\", \"observations\": 39616}]}}, {\"mode\": \"vega-lite\"});\n",
       "</script>"
      ],
      "text/plain": [
       "alt.Chart(...)"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "observations_by_year = get_observation_histogram(\n",
    "    place_id=PLACE_ID,\n",
    "    interval='year',\n",
    "    d1='2008-01-01',\n",
    "    d2=f'{YEAR}-12-31',\n",
    "    verifiable=True,\n",
    ")\n",
    "observations_by_year = pd.DataFrame([\n",
    "    {'date': k, 'observations': v}\n",
    "    for k, v in observations_by_year.items()\n",
    "])\n",
    "\n",
    "# Including the rendered image so the chart will display outside Jupyter, e.g. on GitHub's notebook viewer\n",
    "Image('images/observations_by_year.png')\n",
    "alt.Chart(observations_by_year).mark_bar().encode(x='year(date):T', y='observations:Q')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "invisible-needle",
   "metadata": {},
   "source": [
    "### Observations per month"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "dietary-tours",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<div id=\"altair-viz-0c274e6b9c414c9c96f2674cd9cef566\"></div>\n",
       "<script type=\"text/javascript\">\n",
       "  (function(spec, embedOpt){\n",
       "    let outputDiv = document.currentScript.previousElementSibling;\n",
       "    if (outputDiv.id !== \"altair-viz-0c274e6b9c414c9c96f2674cd9cef566\") {\n",
       "      outputDiv = document.getElementById(\"altair-viz-0c274e6b9c414c9c96f2674cd9cef566\");\n",
       "    }\n",
       "    const paths = {\n",
       "      \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n",
       "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n",
       "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n",
       "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n",
       "    };\n",
       "\n",
       "    function loadScript(lib) {\n",
       "      return new Promise(function(resolve, reject) {\n",
       "        var s = document.createElement('script');\n",
       "        s.src = paths[lib];\n",
       "        s.async = true;\n",
       "        s.onload = () => resolve(paths[lib]);\n",
       "        s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
       "        document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
       "      });\n",
       "    }\n",
       "\n",
       "    function showError(err) {\n",
       "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
       "      throw err;\n",
       "    }\n",
       "\n",
       "    function displayChart(vegaEmbed) {\n",
       "      vegaEmbed(outputDiv, spec, embedOpt)\n",
       "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
       "    }\n",
       "\n",
       "    if(typeof define === \"function\" && define.amd) {\n",
       "      requirejs.config({paths});\n",
       "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
       "    } else if (typeof vegaEmbed === \"function\") {\n",
       "      displayChart(vegaEmbed);\n",
       "    } else {\n",
       "      loadScript(\"vega\")\n",
       "        .then(() => loadScript(\"vega-lite\"))\n",
       "        .then(() => loadScript(\"vega-embed\"))\n",
       "        .catch(showError)\n",
       "        .then(() => displayChart(vegaEmbed));\n",
       "    }\n",
       "  })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}}, \"data\": {\"name\": \"data-58c6a7131dcf97a32a0bd0d712fc071d\"}, \"mark\": \"bar\", \"encoding\": {\"x\": {\"type\": \"temporal\", \"field\": \"date\", \"timeUnit\": \"month\"}, \"y\": {\"type\": \"quantitative\", \"field\": \"count\"}}, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\", \"datasets\": {\"data-58c6a7131dcf97a32a0bd0d712fc071d\": [{\"metric\": \"Observations\", \"date\": \"2020-01-01T00:00:00\", \"count\": 423}, {\"metric\": \"Observations\", \"date\": \"2020-02-01T00:00:00\", \"count\": 375}, {\"metric\": \"Observations\", \"date\": \"2020-03-01T00:00:00\", \"count\": 661}, {\"metric\": \"Observations\", \"date\": \"2020-04-01T00:00:00\", \"count\": 2667}, {\"metric\": \"Observations\", \"date\": \"2020-05-01T00:00:00\", \"count\": 5859}, {\"metric\": \"Observations\", \"date\": \"2020-06-01T00:00:00\", \"count\": 6299}, {\"metric\": \"Observations\", \"date\": \"2020-07-01T00:00:00\", \"count\": 7640}, {\"metric\": \"Observations\", \"date\": \"2020-08-01T00:00:00\", \"count\": 7013}, {\"metric\": \"Observations\", \"date\": \"2020-09-01T00:00:00\", \"count\": 4681}, {\"metric\": \"Observations\", \"date\": \"2020-10-01T00:00:00\", \"count\": 1513}, {\"metric\": \"Observations\", \"date\": \"2020-11-01T00:00:00\", \"count\": 977}, {\"metric\": \"Observations\", \"date\": \"2020-12-01T00:00:00\", \"count\": 1505}]}}, {\"mode\": \"vega-lite\"});\n",
       "</script>"
      ],
      "text/plain": [
       "alt.Chart(...)"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "observations_by_month = get_observation_histogram(\n",
    "    place_id=PLACE_ID,\n",
    "    interval='month',\n",
    "    d1='2020-01-02',\n",
    "    d2='2020-12-31',\n",
    "    verifiable=True,\n",
    ")\n",
    "observations_by_month = pd.DataFrame([\n",
    "    {'metric': 'Observations', 'date': k, 'count': v}\n",
    "    for k, v in observations_by_month.items()\n",
    "])\n",
    "Image('images/observations_by_month.png')\n",
    "alt.Chart(observations_by_month).mark_bar().encode(x='month(date):T', y='count:Q')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "genetic-camping",
   "metadata": {},
   "source": [
    "### Histograms with custom metrics\n",
    "The API does not have a histogram endpoint for taxa observed, observers, or identifiers,\n",
    "so we first need to determine our date ranges of interest, and then run one search per date range.\n",
    "\n",
    "Here are a couple helper functions to make this easier:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "duplicate-attribute",
   "metadata": {},
   "outputs": [],
   "source": [
    "def count_date_range_results(function, start_date, end_date):\n",
    "    \"\"\"Get the count of results for the given date range and search function\"\"\"\n",
    "    # Running this search with per_page=0 will (quickly) return only a count of results, not complete results\n",
    "    response = function(\n",
    "        place_id=PLACE_ID,\n",
    "        d1=start_date,\n",
    "        d2=end_date,\n",
    "        verifiable=True,\n",
    "        per_page=0,\n",
    "    )\n",
    "    print(f'Total results for {start_date.strftime(\"%b\")}: {response[\"total_results\"]}')\n",
    "    return response['total_results']\n",
    "    if start_date.month != 12:\n",
    "        sleep(THROTTLING_DELAY)\n",
    "\n",
    "\n",
    "def get_monthly_counts(function, label):\n",
    "    \"\"\"Get the count of results per month for the given search function\"\"\"\n",
    "    month_ranges = get_interval_ranges(datetime(YEAR, 1, 1), datetime(YEAR, 12, 31), 'monthly')\n",
    "    counts_by_month = {\n",
    "        start_date: count_date_range_results(function, start_date, end_date)\n",
    "        for (start_date, end_date) in month_ranges\n",
    "    }\n",
    "    return pd.DataFrame([{'metric': label, 'date': k, 'count': v} for k, v in counts_by_month.items()])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "induced-stone",
   "metadata": {},
   "source": [
    "### Unique taxa observed per month"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "exempt-victor",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total results for Jan: 184\n",
      "Total results for Feb: 176\n",
      "Total results for Mar: 318\n",
      "Total results for Apr: 790\n",
      "Total results for May: 1334\n",
      "Total results for Jun: 1504\n",
      "Total results for Jul: 1684\n",
      "Total results for Aug: 1570\n",
      "Total results for Sep: 1250\n",
      "Total results for Oct: 639\n",
      "Total results for Nov: 408\n",
      "Total results for Dec: 550\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "<div id=\"altair-viz-6dd96666c80d4bccb191378367b09990\"></div>\n",
       "<script type=\"text/javascript\">\n",
       "  (function(spec, embedOpt){\n",
       "    let outputDiv = document.currentScript.previousElementSibling;\n",
       "    if (outputDiv.id !== \"altair-viz-6dd96666c80d4bccb191378367b09990\") {\n",
       "      outputDiv = document.getElementById(\"altair-viz-6dd96666c80d4bccb191378367b09990\");\n",
       "    }\n",
       "    const paths = {\n",
       "      \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n",
       "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n",
       "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n",
       "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n",
       "    };\n",
       "\n",
       "    function loadScript(lib) {\n",
       "      return new Promise(function(resolve, reject) {\n",
       "        var s = document.createElement('script');\n",
       "        s.src = paths[lib];\n",
       "        s.async = true;\n",
       "        s.onload = () => resolve(paths[lib]);\n",
       "        s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
       "        document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
       "      });\n",
       "    }\n",
       "\n",
       "    function showError(err) {\n",
       "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
       "      throw err;\n",
       "    }\n",
       "\n",
       "    function displayChart(vegaEmbed) {\n",
       "      vegaEmbed(outputDiv, spec, embedOpt)\n",
       "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
       "    }\n",
       "\n",
       "    if(typeof define === \"function\" && define.amd) {\n",
       "      requirejs.config({paths});\n",
       "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
       "    } else if (typeof vegaEmbed === \"function\") {\n",
       "      displayChart(vegaEmbed);\n",
       "    } else {\n",
       "      loadScript(\"vega\")\n",
       "        .then(() => loadScript(\"vega-lite\"))\n",
       "        .then(() => loadScript(\"vega-embed\"))\n",
       "        .catch(showError)\n",
       "        .then(() => displayChart(vegaEmbed));\n",
       "    }\n",
       "  })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}}, \"data\": {\"name\": \"data-467545dbd4e6e6b384bc6a725a2f47f7\"}, \"mark\": \"bar\", \"encoding\": {\"x\": {\"type\": \"temporal\", \"field\": \"date\", \"timeUnit\": \"month\"}, \"y\": {\"type\": \"quantitative\", \"field\": \"count\"}}, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\", \"datasets\": {\"data-467545dbd4e6e6b384bc6a725a2f47f7\": [{\"metric\": \"Taxa\", \"date\": \"2020-01-01T00:00:00\", \"count\": 184}, {\"metric\": \"Taxa\", \"date\": \"2020-02-01T00:00:00\", \"count\": 176}, {\"metric\": \"Taxa\", \"date\": \"2020-03-01T00:00:00\", \"count\": 318}, {\"metric\": \"Taxa\", \"date\": \"2020-04-01T00:00:00\", \"count\": 790}, {\"metric\": \"Taxa\", \"date\": \"2020-05-01T00:00:00\", \"count\": 1334}, {\"metric\": \"Taxa\", \"date\": \"2020-06-01T00:00:00\", \"count\": 1504}, {\"metric\": \"Taxa\", \"date\": \"2020-07-01T00:00:00\", \"count\": 1684}, {\"metric\": \"Taxa\", \"date\": \"2020-08-01T00:00:00\", \"count\": 1570}, {\"metric\": \"Taxa\", \"date\": \"2020-09-01T00:00:00\", \"count\": 1250}, {\"metric\": \"Taxa\", \"date\": \"2020-10-01T00:00:00\", \"count\": 639}, {\"metric\": \"Taxa\", \"date\": \"2020-11-01T00:00:00\", \"count\": 408}, {\"metric\": \"Taxa\", \"date\": \"2020-12-01T00:00:00\", \"count\": 550}]}}, {\"mode\": \"vega-lite\"});\n",
       "</script>"
      ],
      "text/plain": [
       "alt.Chart(...)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "taxa_by_month = get_monthly_counts(get_observation_species_counts, 'Taxa')\n",
    "Image('images/taxa_by_month.png')\n",
    "alt.Chart(taxa_by_month).mark_bar().encode(x='month(date):T', y='count:Q')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "brief-daniel",
   "metadata": {},
   "source": [
    "### Observers per month"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "generous-candy",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total results for Jan: 36\n",
      "Total results for Feb: 42\n",
      "Total results for Mar: 71\n",
      "Total results for Apr: 141\n",
      "Total results for May: 361\n",
      "Total results for Jun: 458\n",
      "Total results for Jul: 530\n",
      "Total results for Aug: 563\n",
      "Total results for Sep: 404\n",
      "Total results for Oct: 174\n",
      "Total results for Nov: 86\n",
      "Total results for Dec: 51\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "<div id=\"altair-viz-816dd99f2b2441d091f200e49758ef5d\"></div>\n",
       "<script type=\"text/javascript\">\n",
       "  (function(spec, embedOpt){\n",
       "    let outputDiv = document.currentScript.previousElementSibling;\n",
       "    if (outputDiv.id !== \"altair-viz-816dd99f2b2441d091f200e49758ef5d\") {\n",
       "      outputDiv = document.getElementById(\"altair-viz-816dd99f2b2441d091f200e49758ef5d\");\n",
       "    }\n",
       "    const paths = {\n",
       "      \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n",
       "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n",
       "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n",
       "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n",
       "    };\n",
       "\n",
       "    function loadScript(lib) {\n",
       "      return new Promise(function(resolve, reject) {\n",
       "        var s = document.createElement('script');\n",
       "        s.src = paths[lib];\n",
       "        s.async = true;\n",
       "        s.onload = () => resolve(paths[lib]);\n",
       "        s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
       "        document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
       "      });\n",
       "    }\n",
       "\n",
       "    function showError(err) {\n",
       "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
       "      throw err;\n",
       "    }\n",
       "\n",
       "    function displayChart(vegaEmbed) {\n",
       "      vegaEmbed(outputDiv, spec, embedOpt)\n",
       "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
       "    }\n",
       "\n",
       "    if(typeof define === \"function\" && define.amd) {\n",
       "      requirejs.config({paths});\n",
       "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
       "    } else if (typeof vegaEmbed === \"function\") {\n",
       "      displayChart(vegaEmbed);\n",
       "    } else {\n",
       "      loadScript(\"vega\")\n",
       "        .then(() => loadScript(\"vega-lite\"))\n",
       "        .then(() => loadScript(\"vega-embed\"))\n",
       "        .catch(showError)\n",
       "        .then(() => displayChart(vegaEmbed));\n",
       "    }\n",
       "  })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}}, \"data\": {\"name\": \"data-7f89798141a7c83b2f3f1716a81642c9\"}, \"mark\": \"bar\", \"encoding\": {\"x\": {\"type\": \"temporal\", \"field\": \"date\", \"timeUnit\": \"month\"}, \"y\": {\"type\": \"quantitative\", \"field\": \"count\"}}, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\", \"datasets\": {\"data-7f89798141a7c83b2f3f1716a81642c9\": [{\"metric\": \"Observers\", \"date\": \"2020-01-01T00:00:00\", \"count\": 36}, {\"metric\": \"Observers\", \"date\": \"2020-02-01T00:00:00\", \"count\": 42}, {\"metric\": \"Observers\", \"date\": \"2020-03-01T00:00:00\", \"count\": 71}, {\"metric\": \"Observers\", \"date\": \"2020-04-01T00:00:00\", \"count\": 141}, {\"metric\": \"Observers\", \"date\": \"2020-05-01T00:00:00\", \"count\": 361}, {\"metric\": \"Observers\", \"date\": \"2020-06-01T00:00:00\", \"count\": 458}, {\"metric\": \"Observers\", \"date\": \"2020-07-01T00:00:00\", \"count\": 530}, {\"metric\": \"Observers\", \"date\": \"2020-08-01T00:00:00\", \"count\": 563}, {\"metric\": \"Observers\", \"date\": \"2020-09-01T00:00:00\", \"count\": 404}, {\"metric\": \"Observers\", \"date\": \"2020-10-01T00:00:00\", \"count\": 174}, {\"metric\": \"Observers\", \"date\": \"2020-11-01T00:00:00\", \"count\": 86}, {\"metric\": \"Observers\", \"date\": \"2020-12-01T00:00:00\", \"count\": 51}]}}, {\"mode\": \"vega-lite\"});\n",
       "</script>"
      ],
      "text/plain": [
       "alt.Chart(...)"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "observers_by_month = get_monthly_counts(get_observation_observers, 'Observers')\n",
    "Image('images/observers_by_month.png')\n",
    "alt.Chart(observers_by_month).mark_bar().encode(x='month(date):T', y='count:Q')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "earlier-warren",
   "metadata": {},
   "source": [
    "### Identifiers per month"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "parliamentary-edward",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total results for Jan: 135\n",
      "Total results for Feb: 152\n",
      "Total results for Mar: 187\n",
      "Total results for Apr: 349\n",
      "Total results for May: 619\n",
      "Total results for Jun: 602\n",
      "Total results for Jul: 662\n",
      "Total results for Aug: 616\n",
      "Total results for Sep: 492\n",
      "Total results for Oct: 314\n",
      "Total results for Nov: 219\n",
      "Total results for Dec: 208\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "<div id=\"altair-viz-26b680089ba44789b4864683a77509ac\"></div>\n",
       "<script type=\"text/javascript\">\n",
       "  (function(spec, embedOpt){\n",
       "    let outputDiv = document.currentScript.previousElementSibling;\n",
       "    if (outputDiv.id !== \"altair-viz-26b680089ba44789b4864683a77509ac\") {\n",
       "      outputDiv = document.getElementById(\"altair-viz-26b680089ba44789b4864683a77509ac\");\n",
       "    }\n",
       "    const paths = {\n",
       "      \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n",
       "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n",
       "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n",
       "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n",
       "    };\n",
       "\n",
       "    function loadScript(lib) {\n",
       "      return new Promise(function(resolve, reject) {\n",
       "        var s = document.createElement('script');\n",
       "        s.src = paths[lib];\n",
       "        s.async = true;\n",
       "        s.onload = () => resolve(paths[lib]);\n",
       "        s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
       "        document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
       "      });\n",
       "    }\n",
       "\n",
       "    function showError(err) {\n",
       "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
       "      throw err;\n",
       "    }\n",
       "\n",
       "    function displayChart(vegaEmbed) {\n",
       "      vegaEmbed(outputDiv, spec, embedOpt)\n",
       "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
       "    }\n",
       "\n",
       "    if(typeof define === \"function\" && define.amd) {\n",
       "      requirejs.config({paths});\n",
       "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
       "    } else if (typeof vegaEmbed === \"function\") {\n",
       "      displayChart(vegaEmbed);\n",
       "    } else {\n",
       "      loadScript(\"vega\")\n",
       "        .then(() => loadScript(\"vega-lite\"))\n",
       "        .then(() => loadScript(\"vega-embed\"))\n",
       "        .catch(showError)\n",
       "        .then(() => displayChart(vegaEmbed));\n",
       "    }\n",
       "  })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}}, \"data\": {\"name\": \"data-e98d6286c715382d679dc791189fb1da\"}, \"mark\": \"bar\", \"encoding\": {\"x\": {\"type\": \"temporal\", \"field\": \"date\", \"timeUnit\": \"month\"}, \"y\": {\"type\": \"quantitative\", \"field\": \"count\"}}, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\", \"datasets\": {\"data-e98d6286c715382d679dc791189fb1da\": [{\"metric\": \"Identifiers\", \"date\": \"2020-01-01T00:00:00\", \"count\": 135}, {\"metric\": \"Identifiers\", \"date\": \"2020-02-01T00:00:00\", \"count\": 152}, {\"metric\": \"Identifiers\", \"date\": \"2020-03-01T00:00:00\", \"count\": 187}, {\"metric\": \"Identifiers\", \"date\": \"2020-04-01T00:00:00\", \"count\": 349}, {\"metric\": \"Identifiers\", \"date\": \"2020-05-01T00:00:00\", \"count\": 619}, {\"metric\": \"Identifiers\", \"date\": \"2020-06-01T00:00:00\", \"count\": 602}, {\"metric\": \"Identifiers\", \"date\": \"2020-07-01T00:00:00\", \"count\": 662}, {\"metric\": \"Identifiers\", \"date\": \"2020-08-01T00:00:00\", \"count\": 616}, {\"metric\": \"Identifiers\", \"date\": \"2020-09-01T00:00:00\", \"count\": 492}, {\"metric\": \"Identifiers\", \"date\": \"2020-10-01T00:00:00\", \"count\": 314}, {\"metric\": \"Identifiers\", \"date\": \"2020-11-01T00:00:00\", \"count\": 219}, {\"metric\": \"Identifiers\", \"date\": \"2020-12-01T00:00:00\", \"count\": 208}]}}, {\"mode\": \"vega-lite\"});\n",
       "</script>"
      ],
      "text/plain": [
       "alt.Chart(...)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "identifiers_by_month = get_monthly_counts(get_observation_identifiers, 'Identifiers')\n",
    "Image('images/identifiers_by_month.png')\n",
    "alt.Chart(identifiers_by_month).mark_bar().encode(x='month(date):T', y='count:Q')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "another-ambassador",
   "metadata": {},
   "source": [
    "### Combine all monthly metrics into one plot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "serious-literacy",
   "metadata": {
    "tags": [
     "nbsphinx-thumbnail"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<div id=\"altair-viz-6ad66f00344f487598dca5c1cfb3cc1d\"></div>\n",
       "<script type=\"text/javascript\">\n",
       "  (function(spec, embedOpt){\n",
       "    let outputDiv = document.currentScript.previousElementSibling;\n",
       "    if (outputDiv.id !== \"altair-viz-6ad66f00344f487598dca5c1cfb3cc1d\") {\n",
       "      outputDiv = document.getElementById(\"altair-viz-6ad66f00344f487598dca5c1cfb3cc1d\");\n",
       "    }\n",
       "    const paths = {\n",
       "      \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n",
       "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n",
       "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n",
       "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n",
       "    };\n",
       "\n",
       "    function loadScript(lib) {\n",
       "      return new Promise(function(resolve, reject) {\n",
       "        var s = document.createElement('script');\n",
       "        s.src = paths[lib];\n",
       "        s.async = true;\n",
       "        s.onload = () => resolve(paths[lib]);\n",
       "        s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
       "        document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
       "      });\n",
       "    }\n",
       "\n",
       "    function showError(err) {\n",
       "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
       "      throw err;\n",
       "    }\n",
       "\n",
       "    function displayChart(vegaEmbed) {\n",
       "      vegaEmbed(outputDiv, spec, embedOpt)\n",
       "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
       "    }\n",
       "\n",
       "    if(typeof define === \"function\" && define.amd) {\n",
       "      requirejs.config({paths});\n",
       "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
       "    } else if (typeof vegaEmbed === \"function\") {\n",
       "      displayChart(vegaEmbed);\n",
       "    } else {\n",
       "      loadScript(\"vega\")\n",
       "        .then(() => loadScript(\"vega-lite\"))\n",
       "        .then(() => loadScript(\"vega-embed\"))\n",
       "        .catch(showError)\n",
       "        .then(() => displayChart(vegaEmbed));\n",
       "    }\n",
       "  })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}, \"axis\": {\"labelFontSize\": 15, \"titleFontSize\": 20}}, \"data\": {\"name\": \"data-19e795e8d786fa26efb12cd492ebf244\"}, \"mark\": \"line\", \"encoding\": {\"color\": {\"type\": \"nominal\", \"field\": \"metric\"}, \"strokeDash\": {\"type\": \"nominal\", \"field\": \"metric\"}, \"x\": {\"type\": \"temporal\", \"axis\": {\"title\": \"Month\"}, \"field\": \"date\", \"timeUnit\": \"month\"}, \"y\": {\"type\": \"quantitative\", \"axis\": {\"title\": \"Count\"}, \"field\": \"count\"}}, \"height\": 500, \"title\": \"iNaturalist activity in Alaska (2020)\", \"width\": 750, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\", \"datasets\": {\"data-19e795e8d786fa26efb12cd492ebf244\": [{\"metric\": \"Observations\", \"date\": \"2020-01-01T00:00:00\", \"count\": 423}, {\"metric\": \"Observations\", \"date\": \"2020-02-01T00:00:00\", \"count\": 375}, {\"metric\": \"Observations\", \"date\": \"2020-03-01T00:00:00\", \"count\": 661}, {\"metric\": \"Observations\", \"date\": \"2020-04-01T00:00:00\", \"count\": 2667}, {\"metric\": \"Observations\", \"date\": \"2020-05-01T00:00:00\", \"count\": 5859}, {\"metric\": \"Observations\", \"date\": \"2020-06-01T00:00:00\", \"count\": 6299}, {\"metric\": \"Observations\", \"date\": \"2020-07-01T00:00:00\", \"count\": 7640}, {\"metric\": \"Observations\", \"date\": \"2020-08-01T00:00:00\", \"count\": 7013}, {\"metric\": \"Observations\", \"date\": \"2020-09-01T00:00:00\", \"count\": 4681}, {\"metric\": \"Observations\", \"date\": \"2020-10-01T00:00:00\", \"count\": 1513}, {\"metric\": \"Observations\", \"date\": \"2020-11-01T00:00:00\", \"count\": 977}, {\"metric\": \"Observations\", \"date\": \"2020-12-01T00:00:00\", \"count\": 1505}, {\"metric\": \"Taxa\", \"date\": \"2020-01-01T00:00:00\", \"count\": 184}, {\"metric\": \"Taxa\", \"date\": \"2020-02-01T00:00:00\", \"count\": 176}, {\"metric\": \"Taxa\", \"date\": \"2020-03-01T00:00:00\", \"count\": 318}, {\"metric\": \"Taxa\", \"date\": \"2020-04-01T00:00:00\", \"count\": 790}, {\"metric\": \"Taxa\", \"date\": \"2020-05-01T00:00:00\", \"count\": 1334}, {\"metric\": \"Taxa\", \"date\": \"2020-06-01T00:00:00\", \"count\": 1504}, {\"metric\": \"Taxa\", \"date\": \"2020-07-01T00:00:00\", \"count\": 1684}, {\"metric\": \"Taxa\", \"date\": \"2020-08-01T00:00:00\", \"count\": 1570}, {\"metric\": \"Taxa\", \"date\": \"2020-09-01T00:00:00\", \"count\": 1250}, {\"metric\": \"Taxa\", \"date\": \"2020-10-01T00:00:00\", \"count\": 639}, {\"metric\": \"Taxa\", \"date\": \"2020-11-01T00:00:00\", \"count\": 408}, {\"metric\": \"Taxa\", \"date\": \"2020-12-01T00:00:00\", \"count\": 550}, {\"metric\": \"Observers\", \"date\": \"2020-01-01T00:00:00\", \"count\": 36}, {\"metric\": \"Observers\", \"date\": \"2020-02-01T00:00:00\", \"count\": 42}, {\"metric\": \"Observers\", \"date\": \"2020-03-01T00:00:00\", \"count\": 71}, {\"metric\": \"Observers\", \"date\": \"2020-04-01T00:00:00\", \"count\": 141}, {\"metric\": \"Observers\", \"date\": \"2020-05-01T00:00:00\", \"count\": 361}, {\"metric\": \"Observers\", \"date\": \"2020-06-01T00:00:00\", \"count\": 458}, {\"metric\": \"Observers\", \"date\": \"2020-07-01T00:00:00\", \"count\": 530}, {\"metric\": \"Observers\", \"date\": \"2020-08-01T00:00:00\", \"count\": 563}, {\"metric\": \"Observers\", \"date\": \"2020-09-01T00:00:00\", \"count\": 404}, {\"metric\": \"Observers\", \"date\": \"2020-10-01T00:00:00\", \"count\": 174}, {\"metric\": \"Observers\", \"date\": \"2020-11-01T00:00:00\", \"count\": 86}, {\"metric\": \"Observers\", \"date\": \"2020-12-01T00:00:00\", \"count\": 51}, {\"metric\": \"Identifiers\", \"date\": \"2020-01-01T00:00:00\", \"count\": 135}, {\"metric\": \"Identifiers\", \"date\": \"2020-02-01T00:00:00\", \"count\": 152}, {\"metric\": \"Identifiers\", \"date\": \"2020-03-01T00:00:00\", \"count\": 187}, {\"metric\": \"Identifiers\", \"date\": \"2020-04-01T00:00:00\", \"count\": 349}, {\"metric\": \"Identifiers\", \"date\": \"2020-05-01T00:00:00\", \"count\": 619}, {\"metric\": \"Identifiers\", \"date\": \"2020-06-01T00:00:00\", \"count\": 602}, {\"metric\": \"Identifiers\", \"date\": \"2020-07-01T00:00:00\", \"count\": 662}, {\"metric\": \"Identifiers\", \"date\": \"2020-08-01T00:00:00\", \"count\": 616}, {\"metric\": \"Identifiers\", \"date\": \"2020-09-01T00:00:00\", \"count\": 492}, {\"metric\": \"Identifiers\", \"date\": \"2020-10-01T00:00:00\", \"count\": 314}, {\"metric\": \"Identifiers\", \"date\": \"2020-11-01T00:00:00\", \"count\": 219}, {\"metric\": \"Identifiers\", \"date\": \"2020-12-01T00:00:00\", \"count\": 208}]}}, {\"mode\": \"vega-lite\"});\n",
       "</script>"
      ],
      "text/plain": [
       "alt.Chart(...)"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "combined_results = observations_by_month.append([taxa_by_month, observers_by_month, identifiers_by_month])\n",
    "\n",
    "Image('images/combined_activity_stats.png')\n",
    "alt.Chart(\n",
    "    combined_results,\n",
    "    title=f'iNaturalist activity in {PLACE_NAME} ({YEAR})',\n",
    "    width=750,\n",
    "    height=500,\n",
    ").mark_line().encode(\n",
    "    alt.X('month(date):T', axis=alt.Axis(title=\"Month\")),\n",
    "    alt.Y('count:Q', axis=alt.Axis(title=\"Count\")),\n",
    "    color='metric',\n",
    "    strokeDash='metric',\n",
    ").configure_axis(\n",
    "    labelFontSize=15,\n",
    "    titleFontSize=20,\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
