{
"cells": [
{
"cell_type": "markdown",
"id": "330b2804",
"metadata": {
"execution": {}
},
"source": [
"[](https://colab.research.google.com/github/ClimateMatchAcademy/course-content/blob/main/tutorials/W1D1_ClimateSystemOverview/W1D1_Tutorial6.ipynb)
"
]
},
{
"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, Sherry Mi, Beatriz Cosenza Muralles, Jenna Pearson, Agustina Pesce, Chi Zhang, Ohad Zivan\n",
"\n",
"**Content editors:** Jenna Pearson, Chi Zhang, Ohad Zivan\n",
"\n",
"**Production editors:** Wesley Banfield, Jenna Pearson, Chi Zhang, Ohad Zivan\n",
"\n",
"**Our 2023 Sponsors:** NASA TOPS and Google DeepMind"
]
},
{
"cell_type": "markdown",
"id": "c130f230-24c5-4ae4-8da8-e4b679e78dff",
"metadata": {
"execution": {}
},
"source": [
"## \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",
"## \n"
]
},
{
"cell_type": "markdown",
"id": "LZmCPoGUjyUv",
"metadata": {
"execution": {}
},
"source": [
"# Tutorial Objectives\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, there are other significant long-term climate forcings that 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 is 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": "06073287-7bdb-45b5-9cec-8cdf123adb49",
"metadata": {
"execution": {},
"tags": []
},
"outputs": [],
"source": [
"# imports\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"import xarray as xr\n",
"from pythia_datasets import DATASETS\n",
"import pandas as pd\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/ClimateMatchAcademy/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": "markdown",
"metadata": {},
"source": [
"## Tutorial slides\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" These are the slides for the videos in all tutorials today\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a1ac91c8-11f3-4bb0-86ee-8eff1e61187c",
"metadata": {
"cellView": "form",
"execution": {},
"pycharm": {
"name": "#%%\n"
},
"tags": [
"remove-input"
]
},
"outputs": [],
"source": [
"# @title Tutorial slides\n",
"# @markdown These are the slides for the videos in all tutorials today\n",
"from IPython.display import IFrame\n",
"link_id = \"tcb2q\""
]
},
{
"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` 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 from 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",
"plt.ylabel(\"global mean tos anomaly\")"
]
},
{
"attachments": {},
"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",
"
\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\")"
]
},
{
"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)?\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5b3181b8-e3a2-4740-aac7-8ba205e12e52",
"metadata": {
"execution": {},
"tags": []
},
"outputs": [],
"source": [
"# to_remove explanation\n",
"\n",
"\"\"\"\n",
"1. An area-weighted mean global temperature anomaly provides a more accurate representation of global temperature changes by accounting for the uneven distribution of grid cell sizes across latitudes. It ensures each region's contribution to the global mean is proportional to its surface area, avoiding any disproportionate influence from smaller areas at higher latitudes. Both weighted and unweighted SST means show the similar increasing trend in temperature from 2000 to 2014, although by eye, it appears that the trend would be slightly lower for the unweighted mean.\n",
"2. The observed SST warming trend from 2000 to 2014 is similar to the rate of change observed from 1980 to 2000, which fits into the larger context of the substantial warming trend observed over the past century. Over this period, global average temperatures have risen rapidly, at a rate that is not typical in the context of most longer-term naturally driven temperature variations.\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. 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": "climatematch",
"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.10.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}