{ "cells": [ { "cell_type": "markdown", "id": "330b2804", "metadata": { "execution": {} }, "source": [ "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/neuromatch/climate-course-content/blob/main/tutorials/W1D1_ClimateSystemOverview/student/W1D1_Tutorial6.ipynb)   \"Open" ] }, { "cell_type": "markdown", "id": "s2ACfZ9jIXrv", "metadata": { "execution": {} }, "source": [ "# Tutorial 6: Compute and Plot Temperature Anomalies\n", "\n", "**Week 1, Day 1, Climate System Overview**\n", "\n", "**Content creators:** Sloane Garelick, Julia Kent\n", "\n", "**Content reviewers:** Katrina Dobson, Younkap Nina Duplex, Danika Gupta, Maria Gonzalez, Will Gregory, Nahid Hasan, Paul Heubel, Sherry Mi, Beatriz Cosenza Muralles, Jenna Pearson, Agustina Pesce, Chi Zhang, Ohad Zivan\n", "\n", "**Content editors:** Paul Heubel, Jenna Pearson, Chi Zhang, Ohad Zivan\n", "\n", "**Production editors:** Wesley Banfield, Paul Heubel, Jenna Pearson, Konstantine Tsafatinos, Chi Zhang, Ohad Zivan\n", "\n", "**Our 2024 Sponsors:** CMIP, NFDI4Earth" ] }, { "cell_type": "markdown", "id": "c130f230-24c5-4ae4-8da8-e4b679e78dff", "metadata": { "execution": {} }, "source": [ "## ![project pythia](https://projectpythia.org/_static/images/logos/pythia_logo-blue-rtext.svg)\n", "\n", "Pythia credit: Rose, B. E. J., Kent, J., Tyle, K., Clyne, J., Banihirwe, A., Camron, D., May, R., Grover, M., Ford, R. R., Paul, K., Morley, J., Eroglu, O., Kailyn, L., & Zacharias, A. (2023). Pythia Foundations (Version v2023.05.01) https://zenodo.org/record/8065851\n", "\n", "## ![CMIP.png](https://github.com/ClimateMatchAcademy/course-content/blob/main/tutorials/Art/CMIP.png?raw=true)\n" ] }, { "cell_type": "markdown", "id": "LZmCPoGUjyUv", "metadata": { "execution": {} }, "source": [ "# Tutorial Objectives\n", "\n", "*Estimated timing of tutorial:* 20 minutes \n", "\n", "In the previous tutorials, we have explored global climate patterns and processes, focusing on the terrestrial, atmospheric, and oceanic climate systems. We have understood that Earth's energy budget, primarily controlled by incoming solar radiation, plays a crucial role in shaping Earth's climate. In addition to these factors, other significant long-term climate forcings can influence global temperatures. To gain insight into these forcings, we need to look into historical temperature data, as it offers a valuable point of comparison for assessing changes in temperature and understanding climatic influences.\n", "\n", "Recent and future temperature change is often presented as an anomaly relative to a past climate state or historical period. For example, past and future temperature changes relative to pre-industrial average temperature are a common comparison. \n", "\n", "In this tutorial, our objective is to deepen our understanding of these temperature anomalies. We will compute and plot the global temperature anomaly from 2000-01-15 to 2014-12-1, providing us with a clearer perspective on recent climatic changes." ] }, { "cell_type": "markdown", "id": "0af7bee1-3de3-453a-8ae8-bcd7910b4266", "metadata": { "execution": {}, "tags": [] }, "source": [ "# Setup\n" ] }, { "cell_type": "code", "execution_count": null, "id": "b3ea206d-56dd-43fe-a1e3-01b2235c9f51", "metadata": { "execution": {} }, "outputs": [], "source": [ "# installations ( uncomment and run this cell ONLY when using google colab or kaggle )\n", "#!pip install pythia_datasets" ] }, { "cell_type": "code", "execution_count": null, "id": "06073287-7bdb-45b5-9cec-8cdf123adb49", "metadata": { "execution": {}, "tags": [] }, "outputs": [], "source": [ "# imports\n", "import xarray as xr\n", "from pythia_datasets import DATASETS\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Figure Settings\n" ] }, { "cell_type": "code", "execution_count": null, "id": "b4cabcfc-2cc0-450a-8e94-4f8e635213c5", "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Figure Settings\n", "import ipywidgets as widgets # interactive display\n", "\n", "%config InlineBackend.figure_format = 'retina'\n", "plt.style.use(\n", " \"https://raw.githubusercontent.com/neuromatch/climate-course-content/main/cma.mplstyle\"\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Video 1: Orbital Cycles\n" ] }, { "cell_type": "code", "execution_count": null, "id": "e941ab00-bac3-4fbb-a582-9a73da96b617", "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 1: Orbital Cycles\n", "\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'CdZkSWnfvYs'), ('Bilibili', 'BV1p8411D7ic')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "code", "execution_count": null, "id": "a1ac91c8-11f3-4bb0-86ee-8eff1e61187c", "metadata": { "cellView": "form", "execution": {}, "pycharm": { "name": "#%%\n" }, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @markdown\n", "from ipywidgets import widgets\n", "from IPython.display import IFrame\n", "\n", "link_id = \"tcb2q\"\n", "\n", "download_link = f\"https://osf.io/download/{link_id}/\"\n", "render_link = f\"https://mfr.ca-1.osf.io/render?url=https://osf.io/{link_id}/?direct%26mode=render%26action=download%26mode=render\"\n", "# @markdown\n", "out = widgets.Output()\n", "with out:\n", " print(f\"If you want to download the slides: {download_link}\")\n", " display(IFrame(src=f\"{render_link}\", width=730, height=410))\n", "display(out)" ] }, { "cell_type": "markdown", "id": "266b8130-ca7d-4aec-a9a2-d7281ad64425", "metadata": { "execution": {} }, "source": [ "# Section 1: Compute Anomaly\n", "\n", "First, let's load the same data that we used in the previous tutorial (monthly SST data from CESM2):" ] }, { "cell_type": "code", "execution_count": null, "id": "7837f8bd-da89-4718-ab02-d5107576d2d6", "metadata": { "execution": {}, "executionInfo": { "elapsed": 303, "status": "ok", "timestamp": 1681569406112, "user": { "displayName": "Sloane Garelick", "userId": "04706287370408131987" }, "user_tz": 240 }, "tags": [] }, "outputs": [], "source": [ "filepath = DATASETS.fetch(\"CESM2_sst_data.nc\")\n", "ds = xr.open_dataset(filepath)\n", "ds" ] }, { "cell_type": "markdown", "id": "9719db5b-e645-4815-b8df-d454fa7703e7", "metadata": { "execution": {} }, "source": [ "We'll compute the climatology using xarray's [`.groupby()`](https://xarray.pydata.org/en/stable/user-guide/groupby.html) operation to split the SST data by month. Then, we'll remove this climatology from our original data to find the anomaly:" ] }, { "cell_type": "code", "execution_count": null, "id": "4c9940df-5174-49bf-9117-eef1e14abec0", "metadata": { "execution": {}, "executionInfo": { "elapsed": 303, "status": "ok", "timestamp": 1681569588942, "user": { "displayName": "Sloane Garelick", "userId": "04706287370408131987" }, "user_tz": 240 }, "tags": [] }, "outputs": [], "source": [ "# group all data by month\n", "gb = ds.tos.groupby(\"time.month\")\n", "\n", "# take the mean over time to get monthly averages\n", "tos_clim = gb.mean(dim=\"time\")\n", "\n", "# subtract this mean from all data of the same month\n", "tos_anom = gb - tos_clim\n", "tos_anom" ] }, { "cell_type": "markdown", "id": "8c5d2ebe", "metadata": { "execution": {} }, "source": [ "Let's try plotting the anomaly of a specific location:" ] }, { "cell_type": "code", "execution_count": null, "id": "35fc2054-df48-4ea2-8433-632ba8755c61", "metadata": { "execution": {}, "executionInfo": { "elapsed": 970, "status": "ok", "timestamp": 1681569593750, "user": { "displayName": "Sloane Garelick", "userId": "04706287370408131987" }, "user_tz": 240 }, "tags": [] }, "outputs": [], "source": [ "tos_anom.sel(lon=310, lat=50, method=\"nearest\").plot()\n", "plt.ylabel(\"tos anomaly\")" ] }, { "cell_type": "markdown", "id": "c3c087dc-966d-48a0-bb99-ca63cf20ff05", "metadata": { "execution": {} }, "source": [ "Next, let's compute and visualize the mean global anomaly over time. We need to specify both `lat` and `lon` dimensions in the `dim` argument to `.mean()`:" ] }, { "cell_type": "code", "execution_count": null, "id": "5d3abf06-a341-45ac-a3f2-76131016c0b3", "metadata": { "execution": {}, "executionInfo": { "elapsed": 661, "status": "ok", "timestamp": 1681569602365, "user": { "displayName": "Sloane Garelick", "userId": "04706287370408131987" }, "user_tz": 240 }, "tags": [] }, "outputs": [], "source": [ "unweighted_mean_global_anom = tos_anom.mean(dim=[\"lat\", \"lon\"])\n", "unweighted_mean_global_anom.plot()\n", "# aesthetics\n", "plt.ylabel(\"Global mean tos anomaly\")\n", "plt.xlabel(\"Time (months)\")" ] }, { "cell_type": "markdown", "id": "7c7409af", "metadata": { "execution": {} }, "source": [ "Notice that we called our variable `unweighted_mean_global_anom`. Next, we are going to compute the `weighted_mean_global_anom`. Why do we need to weight our data? Grid cells with the same range of degrees latitude and longitude are not necessarily same size. Specifically, grid cells closer to the equator are much larger than those near the poles, as seen in the figure below (Djexplo, 2011, CC-BY). \n", "\n", "\"area\n", "\n", "\n", "Therefore, an operation which combines grid cells of different size is not scientifically valid unless each cell is weighted by the size of the grid cell. Xarray has a convenient [`.weighted()`](https://xarray.pydata.org/en/stable/user-guide/computation.html#weighted-array-reductions) method to accomplish this." ] }, { "cell_type": "markdown", "id": "908bcc38-bf93-478c-99e4-8bbafeec1f21", "metadata": { "execution": {} }, "source": [ "Let's first load the grid cell area data from another CESM2 dataset that contains the weights for the grid cells:" ] }, { "cell_type": "code", "execution_count": null, "id": "a5878de6-f3ab-43e0-8f0d-12ab51631450", "metadata": { "execution": {}, "executionInfo": { "elapsed": 1450, "status": "ok", "timestamp": 1681569607108, "user": { "displayName": "Sloane Garelick", "userId": "04706287370408131987" }, "user_tz": 240 }, "tags": [] }, "outputs": [], "source": [ "filepath2 = DATASETS.fetch(\"CESM2_grid_variables.nc\")\n", "areacello = xr.open_dataset(filepath2).areacello\n", "areacello" ] }, { "cell_type": "markdown", "id": "8a73a748-46b4-4350-b167-32725eebaec8", "metadata": { "execution": {} }, "source": [ "Let's calculate area-weighted mean global anomaly:" ] }, { "cell_type": "code", "execution_count": null, "id": "b8f7e3a5-0748-4395-95b0-0e31d0a5d4d1", "metadata": { "execution": {}, "tags": [] }, "outputs": [], "source": [ "weighted_mean_global_anom = tos_anom.weighted(\n", " areacello).mean(dim=[\"lat\", \"lon\"])" ] }, { "cell_type": "markdown", "id": "17da2e3a-3ca6-41f4-892e-b26021c492e6", "metadata": { "execution": {} }, "source": [ "Let's plot both unweighted and weighted means:" ] }, { "cell_type": "code", "execution_count": null, "id": "802c5e99-7223-49b5-a867-91d943075d52", "metadata": { "execution": {}, "executionInfo": { "elapsed": 985, "status": "ok", "timestamp": 1681569614744, "user": { "displayName": "Sloane Garelick", "userId": "04706287370408131987" }, "user_tz": 240 }, "tags": [] }, "outputs": [], "source": [ "unweighted_mean_global_anom.plot(size=7)\n", "weighted_mean_global_anom.plot()\n", "plt.legend([\"unweighted\", \"weighted\"])\n", "plt.ylabel(\"Global mean tos anomaly\")\n", "plt.xlabel(\"Time (months)\")" ] }, { "cell_type": "markdown", "id": "3c399f9f-83ad-403e-a092-75f3f47f0322", "metadata": { "execution": {} }, "source": [ "## Questions 1: Climate Connection\n", "\n", "1. What is the significance of calculating area-weighted mean global temperature anomalies when assessing climate change? How are the weighted and unweighted SST means similar and different? \n", "2. What overall trends do you observe in the global SST mean over this time? How does this magnitude and rate of temperature change compare to past temperature variations on longer timescales (refer back to the figures in the video/slides)?\n" ] }, { "cell_type": "markdown", "id": "5b3181b8-e3a2-4740-aac7-8ba205e12e52", "metadata": { "colab_type": "text", "execution": {}, "tags": [] }, "source": [ "[*Click for solution*](https://github.com/neuromatch/climate-course-content/tree/main/tutorials/W1D1_ClimateSystemOverview/solutions/W1D1_Tutorial6_Solution_d05ad784.py)\n", "\n" ] }, { "cell_type": "markdown", "id": "bbbe7478-dd54-4bb1-9e82-226a655a26d0", "metadata": { "execution": {} }, "source": [ "# Summary\n", "\n", "In this tutorial, we focused on historical temperature changes. We computed and plotted the global temperature anomaly from 2000 to 2014 and compared a weighted and unweighted time series of the global mean SST anomaly. This helped us enhance our understanding of recent climatic changes and their potential implications for the future.\n", "\n", "\n" ] }, { "cell_type": "markdown", "id": "bc61da9e-deed-42b6-9dcc-05864e425a0b", "metadata": { "execution": {} }, "source": [ "# Resources" ] }, { "cell_type": "markdown", "id": "76f506e1-74ab-47a3-b0c1-2ed3ed659fdd", "metadata": { "execution": {} }, "source": [ "Code and data for this tutorial is based on existing content from [Project Pythia](https://foundations.projectpythia.org/core/xarray/computation-masking.html)." ] } ], "metadata": { "colab": { "collapsed_sections": [], "include_colab_link": true, "name": "W1D1_Tutorial6", "provenance": [], "toc_visible": true }, "kernel": { "display_name": "Python 3", "language": "python", "name": "python3" }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.19" } }, "nbformat": 4, "nbformat_minor": 5 }