{ "cells": [ { "cell_type": "markdown", "id": "30ffb843-0f7d-4e46-9b7b-e2674259e811", "metadata": {}, "source": [ "## COMPLETED Franklin notebook" ] }, { "cell_type": "markdown", "id": "0676f004-b5f7-479e-a4ad-b09500d4854d", "metadata": {}, "source": [ "## Overview\n", "In this notebook, you will create visualizations of tropical cyclone data, using Matplotlib and Cartopy." ] }, { "cell_type": "markdown", "id": "64e44801-c39d-4f8b-82e1-7bf8f5afd91d", "metadata": {}, "source": [ "### Imports" ] }, { "cell_type": "markdown", "id": "d676e223-6d1d-40b1-8bfc-834f2e3a9b33", "metadata": {}, "source": [ "
\n", "

Task

\n", " Import the necessary libraries in the cell below.\n", "
\n" ] }, { "cell_type": "code", "execution_count": null, "id": "6973635f-2098-46b6-9f90-1dcb86b5da84", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import cartopy.crs as ccrs\n", "import cartopy.feature as cfeature" ] }, { "cell_type": "markdown", "id": "43e2d8bb-cc34-4c72-9512-717514e47b33", "metadata": {}, "source": [ "### Part 1: Minimum central sea-level pressure and maximum wind speed of Hurricane Franklin (2023)\n", "Search for Franklin's central sea-level pressure and maximum wind speed, for at least eight distinct times since the storm was named.\n", "Use Matplotlib and create a single Figure with two subplots (i.e., *Axes*), one on top of the other:\n", "1. On the top subplot, plot date and time on the x-axis, and central sea-level pressure in hPa on the y-axis.\n", "1. On the bottom subplot, as in the top, but plot maximum sustained wind speed in mph on the y-axis.\n", "1. Save your figure as a PNG" ] }, { "cell_type": "markdown", "id": "9d5e7a87-d44b-4492-8f51-002aae4e4296", "metadata": {}, "source": [ "
\n", "

Task

\n", " Write your code in the cell below.\n", "
\n" ] }, { "cell_type": "markdown", "id": "4bbe919a-b896-4763-827f-757c48e78874", "metadata": {}, "source": [ "Manually create the following lists, corresponding to 2100 UTC data for the period 8/21 through 8/28:\n", "1. Latitude (deg)\n", "1. Longitude (deg)\n", "1. Central SLP (hPa)\n", "1. Maximum wind speed (kts)\n", "1. Date and time (format: YYYY-MM-DD-HH; time zone: UTC)\n", "Source: [NHC archived advisories for Franklin](https://www.nhc.noaa.gov/archive/2023/FRANKLIN.shtml?)" ] }, { "cell_type": "code", "execution_count": null, "id": "066d6668-5b0c-4f93-82e5-44c3165826cd", "metadata": { "tags": [] }, "outputs": [], "source": [ "lat = [14.3, 15.8, 19.8, 22.4, 21.9, 23.8, 25.9, 28.5]\n", "lon = [-70.1, -71.4, -70.6, -68.9, -67.0, -67.5, -70.0, -71.0]\n", "slp = [1002, 1002, 1004, 998, 1003, 982, 970, 937]\n", "wspd = [45, 35, 35, 50, 45, 75, 85, 125]\n", "dattim = ['2023-08-21-21', '2023-08-22-21', '2023-08-23-21', '2023-08-24-21', \n", " '2023-08-25-21', '2023-08-26-21', '2023-08-27-21', '2023-08-28-21']\n", " \n" ] }, { "cell_type": "markdown", "id": "fab38bda-f39f-46c1-b1ea-089e9aeaafa0", "metadata": {}, "source": [ "Create a `Figure` with two subplots (aka, `Axes`) and make two line graphs. On the top (bottom) `Axes`, plot SLP (max wind) versus date/time." ] }, { "cell_type": "code", "execution_count": null, "id": "337023ac-2675-4007-8117-a235336f8013", "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = plt.figure(figsize=(12,9))\n", "ax1 = fig.add_subplot (2,1,1)\n", "ax1.plot(dattim, slp)\n", "ax2 = fig.add_subplot(2,1,2)\n", "ax2.plot(dattim, wspd)" ] }, { "cell_type": "markdown", "id": "f6d5de92-956e-430c-9e10-edbe0afb4c73", "metadata": {}, "source": [ "A quick and easy way to make the plot better looking is to import and apply the [Seaborn](https://seaborn.pydata.org/) package." ] }, { "cell_type": "code", "execution_count": null, "id": "9809f654-4951-4f37-8849-6767af2866b4", "metadata": { "tags": [] }, "outputs": [], "source": [ "import seaborn as sns\n", "sns.set()" ] }, { "cell_type": "code", "execution_count": null, "id": "f1cda1f5-7c49-435c-b78e-808fef04c555", "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = plt.figure(figsize=(12,9))\n", "ax1 = fig.add_subplot (2,1,1)\n", "ax1.plot(dattim, slp)\n", "ax2 = fig.add_subplot(2,1,2)\n", "ax2.plot(dattim, wspd)" ] }, { "cell_type": "markdown", "id": "f16eec3f-91ae-40c1-b014-6548a0ad51cd", "metadata": {}, "source": [ "Add a title and axis labels" ] }, { "cell_type": "code", "execution_count": null, "id": "33f6e7e6-5dc6-4775-8080-7da1007bfc97", "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = plt.figure(figsize=(12,9))\n", "\n", "fig.suptitle('Franklin (2023) 2100 UTC Min. SLP, Max. Wind, 8/21-8/28', fontsize=16)\n", "\n", "ax1 = fig.add_subplot (2,1,1)\n", "ax1.plot(dattim, slp)\n", "ax1.set_xlabel('Date and time')\n", "ax1.set_ylabel('SLP (hPa)')\n", "\n", "ax2 = fig.add_subplot(2,1,2)\n", "ax2.plot(dattim, wspd)\n", "ax2.set_xlabel('Date and time')\n", "ax2.set_ylabel('Windspeed (kts)');" ] }, { "cell_type": "markdown", "id": "16254516-4bcc-47b0-af70-ca17e84b407b", "metadata": {}, "source": [ "
\n", "

A better visualization:

\n", " Rather than placing SLP and wind speed in their own subplots, let's have them share a single subplot. We will need to add a legend, and also have a separate y-axis for each variable.\n", "
\n" ] }, { "cell_type": "code", "execution_count": null, "id": "de5c6722-9e16-4993-8435-f565c82a7d83", "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = plt.figure(figsize=(12,9))\n", "\n", "fig.suptitle('Franklin (2023) 2100 UTC Min. SLP, Max. Wind, 8/21-8/28', fontsize=16)\n", "\n", "ax1 = fig.add_subplot (1,1,1)\n", "\n", "ax1_color='blue'\n", "ax1.plot(dattim, slp, label='SLP', color=ax1_color)\n", "ax1.set_xlabel('Date and time')\n", "ax1.set_ylabel('SLP (hPa)', color=ax1_color)\n", "ax1.tick_params(axis='y', labelcolor=ax1_color)\n", "\n", "ax2_color='red'\n", "ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis\n", "ax2.plot(dattim, wspd, label='Windspeed', color=ax2_color)\n", "ax2.set_ylabel('Windspeed (kts)', color=ax2_color) # we already handled the x-label with ax1\n", "ax2.tick_params(axis='y', labelcolor=ax2_color);\n" ] }, { "cell_type": "markdown", "id": "0c75a146-bfa7-4d8d-954f-f60efda81ef5", "metadata": {}, "source": [ "There are still a few things we can improve:\n", "1. Re-cast the x-axis from strings to `Datetime` objects, using a handy **Pandas** method\n", "1. Re-format the x-axis labels, now that they are `Datetime` objects\n", "1. Distinguish the y-axis gridlines" ] }, { "cell_type": "code", "execution_count": null, "id": "a859c5a9-b475-4cb1-a910-f8708dcd26ec", "metadata": { "tags": [] }, "outputs": [], "source": [ "import pandas as pd\n", "from matplotlib.dates import DateFormatter, AutoDateLocator,HourLocator,DayLocator,MonthLocator" ] }, { "cell_type": "code", "execution_count": null, "id": "2cfb1559-2bb5-4ee1-a0b9-a9714e78b6e1", "metadata": { "tags": [] }, "outputs": [], "source": [ "dattim_dt = pd.to_datetime(dattim,format=\"%Y-%m-%d-%H\")" ] }, { "cell_type": "code", "execution_count": null, "id": "3205a231-ec24-4314-8c8a-28656c164da3", "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = plt.figure(figsize=(12,9))\n", "\n", "fig.suptitle('Franklin (2023) 2100 UTC Min. SLP, Max. Wind, 8/21-8/28', fontsize=16)\n", "\n", "ax1 = fig.add_subplot (1,1,1)\n", "\n", "\n", "ax1_color='blue'\n", "ax1.set_ylim(900,1020)\n", "ax1.plot(dattim_dt, slp, label='SLP', color=ax1_color)\n", "ax1.set_xlabel('Date')\n", "ax1.set_ylabel('SLP (hPa)', color=ax1_color)\n", "ax1.tick_params(axis='y', labelcolor=ax1_color)\n", "ax1.grid(color=ax1_color, axis='y', linestyle='dashed', linewidth=0.5)\n", "ax1.xaxis.set_major_locator(DayLocator(interval=1))\n", "dateFmt = DateFormatter('%b %d')\n", "ax1.xaxis.set_major_formatter(dateFmt)\n", "\n", "\n", "ax2_color='red'\n", "ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis\n", "ax2.set_ylim(0, 160)\n", "ax2.plot(dattim_dt, wspd, label='Windspeed', color=ax2_color)\n", "ax2.set_ylabel('Windspeed (kts)', color=ax2_color) # we already handled the x-label with ax1\n", "ax2.tick_params(axis='y', labelcolor=ax2_color)\n", "ax2.grid(color=ax2_color, axis='y', linestyle='dotted', linewidth=0.3)\n", "\n", "# ask matplotlib for the plotted objects and their labels\n", "lines, labels = ax1.get_legend_handles_labels()\n", "lines2, labels2 = ax2.get_legend_handles_labels()\n", "ax2.legend(lines + lines2, labels + labels2, loc=0);\n", "#ax1.legend()" ] }, { "cell_type": "markdown", "id": "3e035bd2-c1c8-48b6-a4a4-a9536174b0d6", "metadata": {}, "source": [ "Save the figure as a PNG" ] }, { "cell_type": "code", "execution_count": null, "id": "9b68f4c7-894f-4c8b-ac07-e9f1d717c038", "metadata": { "tags": [] }, "outputs": [], "source": [ "fig.savefig('Franklin2023_TimeSeries_SLP_WSPD.png')" ] }, { "cell_type": "markdown", "id": "269a60cc-d749-4231-8fdd-727e6dfd3c48", "metadata": {}, "source": [ "### Part 2: Plot the center of Franklin on a map\n", "1. Use Matplotlib and Cartopy to plot the locations of Franklin for the same time period you used in Part 1.\n", "1. Save your figure as a PNG" ] }, { "cell_type": "markdown", "id": "18c77a32-4fdd-466e-9a8c-35deb4510e56", "metadata": {}, "source": [ "
\n", "

Task

\n", " Write your code in the cell below.\n", "
\n" ] }, { "cell_type": "markdown", "id": "15114cb8-235e-4447-a3b7-a1fc9c02ea35", "metadata": {}, "source": [ "Define an object pointing to the Cartopy coordinate reference system in which the dataset is based. Since the data is in lat-lon coordinates, we'll use `PlateCarree`." ] }, { "cell_type": "code", "execution_count": null, "id": "ec2f7400-09bc-49b0-b48b-1c0a0a7ec450", "metadata": { "tags": [] }, "outputs": [], "source": [ "projData = ccrs.PlateCarree()" ] }, { "cell_type": "markdown", "id": "e8247c51-0637-405d-9a66-854a3173d7a7", "metadata": {}, "source": [ "Define the bounds over which to plot the data" ] }, { "cell_type": "code", "execution_count": null, "id": "e8f04e4e-a1e4-482b-ad56-9d7b60031580", "metadata": { "tags": [] }, "outputs": [], "source": [ "mapBounds = [-90,-50,10,30]" ] }, { "cell_type": "markdown", "id": "f489b3df-b55f-41e5-a71a-ff205b00c959", "metadata": {}, "source": [ "Create the figure" ] }, { "cell_type": "code", "execution_count": null, "id": "2c98e305-3dbc-4c5e-aba6-02e7749ef1b5", "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = plt.figure(figsize=(12,9))\n", "ax = fig.add_subplot (projection=projData)\n", "ax.set_extent(mapBounds, crs=projData)\n", "gl = ax.gridlines(\n", " draw_labels=True, linewidth=2, color='gray', alpha=0.5, linestyle='--'\n", ")\n", "ax.set_facecolor(cfeature.COLORS['water'])\n", "ax.add_feature(cfeature.LAND)\n", "ax.add_feature(cfeature.COASTLINE)\n", "ax.add_feature(cfeature.BORDERS, linestyle='--')\n", "ax.add_feature(cfeature.LAKES, alpha=0.5)\n", "ax.add_feature(cfeature.STATES)\n", "ax.add_feature(cfeature.RIVERS)\n", "ax.set_xlabel('Latitude')\n", "ax.set_title('Daily 2100 UTC locations of tropical cyclone Franklin, 8/21-8/28 2023');\n", "ax.plot(lon,lat);" ] }, { "cell_type": "markdown", "id": "ad4d45a1-fd78-4bc1-837e-adc918c0e778", "metadata": {}, "source": [ "Let's improve the look of the figure by making the track line more conspicuous:" ] }, { "cell_type": "code", "execution_count": null, "id": "17d740e3-7638-4c22-b24a-25b754067580", "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = plt.figure(figsize=(12,9))\n", "ax = fig.add_subplot (projection=projData)\n", "ax.set_extent(mapBounds, crs=projData)\n", "gl = ax.gridlines(\n", " draw_labels=True, linewidth=2, color='gray', alpha=0.5, linestyle='--'\n", ")\n", "ax.set_facecolor(cfeature.COLORS['water'])\n", "ax.add_feature(cfeature.LAND)\n", "ax.add_feature(cfeature.COASTLINE)\n", "ax.add_feature(cfeature.BORDERS, linestyle='--')\n", "ax.add_feature(cfeature.LAKES, alpha=0.5)\n", "ax.add_feature(cfeature.STATES)\n", "ax.add_feature(cfeature.RIVERS)\n", "ax.set_xlabel('Latitude')\n", "ax.set_title('Daily 2100 UTC locations of tropical cyclone Franklin, 8/21-8/28 2023');\n", "ax.plot(lon,lat, color='darkgreen', marker='o', linestyle='dashed',\n", " linewidth=2, markersize=12);" ] }, { "cell_type": "markdown", "id": "7ca35096-6106-490a-b668-5bec18ea0e1e", "metadata": {}, "source": [ "We'll further improve the figure by using tropical cyclone symbols to mark the locations; the [tcmarkers](https://github.com/abrammer/tc_markers) package works nicely!" ] }, { "cell_type": "code", "execution_count": null, "id": "9f9fa523-ccd1-4a64-a987-a7b9ef6f995f", "metadata": { "tags": [] }, "outputs": [], "source": [ "import tcmarkers" ] }, { "cell_type": "code", "execution_count": null, "id": "36ab8a03-8254-4b79-ac92-f5c712381a4d", "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = plt.figure(figsize=(12,9))\n", "ax = fig.add_subplot (projection=projData)\n", "ax.set_extent(mapBounds, crs=projData)\n", "gl = ax.gridlines(\n", " draw_labels=True, linewidth=2, color='gray', alpha=0.5, linestyle='--'\n", ")\n", "ax.set_facecolor(cfeature.COLORS['water'])\n", "ax.add_feature(cfeature.LAND)\n", "ax.add_feature(cfeature.COASTLINE)\n", "ax.add_feature(cfeature.BORDERS, linestyle='--')\n", "ax.add_feature(cfeature.LAKES, alpha=0.5)\n", "ax.add_feature(cfeature.STATES)\n", "ax.add_feature(cfeature.RIVERS)\n", "ax.set_xlabel('Latitude')\n", "ax.set_title('Daily 2100 UTC locations of tropical cyclone Franklin, 8/21-8/28 2023');\n", "ax.plot(lon,lat, color='darkgreen')\n", "marker_kwargs = {'s': 40, 'color':'darkgreen', 'edgecolor':'darkgreen'}\n", "for idx, value in enumerate (wspd):\n", " if (value < 34):\n", " sym = tcmarkers.TD\n", " if (lat[idx] < 0):\n", " sym = tcmarkers.SH_TD\n", " elif (value < 64): \n", " sym = tcmarkers.TS\n", " if (lat[idx] < 0):\n", " sym = tcmarkers.SH_TS\n", " else:\n", " sym = tcmarkers.HU\n", " if (lat[idx] < 0):\n", " sym = tcmarkers.SH_HU\n", " ax.scatter(lon[idx], lat[idx], marker=sym, **marker_kwargs);\n" ] }, { "cell_type": "markdown", "id": "e9458f64-3586-4005-a717-ea542fb04ece", "metadata": {}, "source": [ "Save the figure as a PNG" ] }, { "cell_type": "code", "execution_count": null, "id": "93e8070d-a60e-45c9-98c9-8396aa48cfb5", "metadata": {}, "outputs": [], "source": [ "fig.savefig('Franklin2023_TrackMap.png')" ] }, { "cell_type": "markdown", "id": "d63ac78e-8395-4eda-8b61-e1a5efd1d3fb", "metadata": {}, "source": [ "
\n", "

REMINDER

\n", " Remember to save, close and shutdown your notebook when you are not actively developing it!\n", "
\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 August 2023 Environment", "language": "python", "name": "aug23" }, "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.11.4" } }, "nbformat": 4, "nbformat_minor": 5 }