diff --git a/reports/annual_category_location_heatmap.ipynb b/reports/annual_category_location_heatmap.ipynb index 7c9a219..72f5ea2 100644 --- a/reports/annual_category_location_heatmap.ipynb +++ b/reports/annual_category_location_heatmap.ipynb @@ -5,7 +5,7 @@ "id": "28ca77da", "metadata": {}, "source": [ - "# Nature Recorder Heatmap\n", + "# Category-Location Annual Heatmap\n", "\n", "This notebook generates and exports a heatmap of species sightings for a category for a given location and year. To use it, update the year, location, category and required export format in the first code cell, below, before running the notebook." ] @@ -21,7 +21,7 @@ "year = \"\"\n", "\n", "# Name of the location to report on\n", - "location = \"\"\n", + "locations = [\"\"]\n", "\n", "# Species category to report on\n", "category = \"\"\n", @@ -30,7 +30,7 @@ "# PNG - export as PNG image\n", "# PDF - export as PDF file\n", "# - do not export\n", - "export_format = \"\"" + "export_format = \"PNG\"" ] }, { @@ -41,17 +41,24 @@ "outputs": [], "source": [ "from pathlib import Path\n", + "import sqlparse\n", "\n", "# Read the query file\n", "query_file_path = Path(\"sql\") / \"sightings.sql\"\n", "with open(query_file_path.absolute(), \"r\") as f:\n", " query = f.read().replace(\"\\n\", \" \")\n", "\n", + "# Generate a list of locations suitable for use in the IN clause in the query\n", + "location_list = \", \".join([f\"'{l}'\" for l in locations])\n", + "\n", "# Replace the location and year placeholders\n", "query = query.replace(\"$YEAR\", year) \\\n", - " .replace(\"$LOCATION\", location) \\\n", + " .replace(\"$LOCATION\", location_list) \\\n", " .replace(\"$CATEGORY\", category) \\\n", - " .replace(\"$SPECIES\", \"\")" + " .replace(\"$SPECIES\", \"\")\n", + "\n", + "# Show a pretty-printed form of the query\n", + "print(sqlparse.format(query, reindent=True, keyword_case='upper'))" ] }, { @@ -72,7 +79,8 @@ "\n", "# Check there is some data\n", "if not df.shape[0]:\n", - " message = f\"No data found for category '{category}' at location '{location}' during '{year}'\"\n", + " locations_list = \", \".join(locations)\n", + " message = f\"No data found for category '{category}' at locations '{locations_list}' during '{year}'\"\n", " raise ValueError(message)" ] }, @@ -106,8 +114,9 @@ "heatmap_data.columns = [calendar.month_abbr[m] for m in heatmap_data.columns]\n", "\n", "# Export the heatmap data to Excel\n", - "clean_location = re.sub(\"[^0-9a-zA-Z\\-]+\", \"\", location.replace(\" \", \"-\"))\n", - "export_file_path = export_folder_path / f\"{year}-{category}-{clean_location}-Heatmap.xlsx\"\n", + "locations_list = \"-\".join(locations).replace(\" \", \"-\")\n", + "clean_locations = re.sub(\"[^0-9a-zA-Z\\-]+\", \"\", locations_list)\n", + "export_file_path = export_folder_path / f\"{year}-{category}-{clean_locations}-Heatmap.xlsx\"\n", "heatmap_data.to_excel(export_file_path.absolute(), sheet_name=\"Sightings\")\n", "\n", "# Print the heatmap data\n", @@ -131,18 +140,19 @@ "# Generate the heatmap\n", "plt.figure(figsize=(12, heatmap_data.shape[0] / 3))\n", "sns.heatmap(heatmap_data, cmap=\"YlOrRd\", annot=False)\n", - "plt.title(f\"Number of Sightings of {category} at {location} in {year}\")\n", + "locations_list = \", \".join(locations)\n", + "plt.title(f\"Number of Sightings of {category} at {locations_list} in {year}\")\n", "plt.xlabel(\"\")\n", "plt.ylabel(\"\")\n", "\n", "# Export to PNG\n", "if export_format.casefold() == \"png\":\n", - " export_file_path = export_folder_path / f\"{year}-{category}-{clean_location}-Heatmap.png\"\n", + " export_file_path = export_folder_path / f\"{year}-{category}-{clean_locations}-Heatmap.png\"\n", " plt.savefig(export_file_path.absolute(), format=\"png\", dpi=300, bbox_inches=\"tight\")\n", "\n", "# Export to PDF\n", "if export_format.casefold() == \"pdf\":\n", - " export_file_path = export_folder_path / f\"{year}-{category}-{clean_location}-Heatmap.pdf\"\n", + " export_file_path = export_folder_path / f\"{year}-{category}-{clean_locations}-Heatmap.pdf\"\n", " plt.savefig(export_file_path.absolute(), format=\"pdf\", bbox_inches=\"tight\")\n", "\n", "# And show the plot\n", @@ -152,7 +162,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "venv", "language": "python", "name": "python3" }, diff --git a/reports/requirements.txt b/reports/requirements.txt index fa8ba65..fc940a1 100644 --- a/reports/requirements.txt +++ b/reports/requirements.txt @@ -94,6 +94,7 @@ Send2Trash==1.8.3 six==1.17.0 sniffio==1.3.1 soupsieve==2.6 +sqlparse==0.5.3 stack-data==0.6.3 terminado==0.18.1 tinycss2==1.4.0 diff --git a/reports/sql/sightings.sql b/reports/sql/sightings.sql index da1f68d..52b63a8 100644 --- a/reports/sql/sightings.sql +++ b/reports/sql/sightings.sql @@ -3,7 +3,7 @@ FROM SIGHTINGS s INNER JOIN SPECIES sp ON sp.Id = s.SpeciesId INNER JOIN CATEGORIES c ON c.Id = sp.CategoryId INNER JOIN LOCATIONS l ON l.Id = s.LocationId -WHERE l.Name = '$LOCATION' +WHERE l.Name IN ( $LOCATION ) AND s.Date LIKE '$YEAR-%' AND sp.Name LIKE '%$SPECIES%' AND c.Name = "$CATEGORY";