{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"[](https://colab.research.google.com/github/ClimateMatchAcademy/course-content/blob/main/tutorials/W1D4_Paleoclimate/student/W1D4_Tutorial9.ipynb)
"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"# Tutorial 9: Paleoclimate Reanalysis Products\n",
"**Week 1, Day 4, Paleoclimate**\n",
"\n",
"**Content creators:** Sloane Garelick\n",
"\n",
"**Content reviewers:** Yosmely Bermúdez, Dionessa Biton, Katrina Dobson, Maria Gonzalez, Will Gregory, Nahid Hasan, Sherry Mi, Beatriz Cosenza Muralles, Brodie Pearson, Jenna Pearson, Agustina Pesce, Chi Zhang, Ohad Zivan \n",
"\n",
"**Content editors:** Yosmely Bermúdez, Zahra Khodakaramimaghsoud, Jenna Pearson, Agustina Pesce, 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",
"metadata": {
"execution": {}
},
"source": [
"# Tutorial Objectives\n",
"\n",
"As we discussed in the video, proxies and models both have advantages and limitations for reconstructing past changes in Earth's climate system. One approach for combining the strengths of both paleoclimate proxies and models is data assimilation. This is the same approach used in Day 2, except instead of simulations of Earth's recent past, we are using a simulation that spans many thousands of years back in time and is constrained by proxies, rather than modern instrumental measurements. The results of this process are called reanalysis products.\n",
"\n",
"In this tutorial, we'll look at paleoclimate reconstructions from the Last Glacial Maximum Reanalysis (LGMR) product from [Osman et al. (2021)](https://www.nature.com/articles/s41586-021-03984-4), which contains temperature for the past 24,000 years.\n",
"\n",
"\n",
"During this tutorial you will:\n",
"\n",
"* Plot a time series of the paleoclimate reconstruction\n",
"* Create global maps and zonal mean plots of temperature anomalies \n",
"* Assess how and why LGM to present temperature anomalies vary with latitude\n",
"\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"# Setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# installations ( uncomment and run this cell ONLY when using google colab or kaggle )\n",
"\n",
"# !pip install cartopy"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {}
},
"outputs": [],
"source": [
"# imports\n",
"import pooch\n",
"import os\n",
"import tempfile\n",
"import numpy as np\n",
"import xarray as xr\n",
"import matplotlib.pyplot as plt\n",
"\n",
"import cartopy.crs as ccrs\n",
"import cartopy.util as cutil"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# @title Helper functions\n",
"\n",
"def pooch_load(filelocation=None, filename=None, processor=None):\n",
" shared_location = \"/home/jovyan/shared/Data/tutorials/W1D4_Paleoclimate\" # this is different for each day\n",
" user_temp_cache = tempfile.gettempdir()\n",
"\n",
" if os.path.exists(os.path.join(shared_location, filename)):\n",
" file = os.path.join(shared_location, filename)\n",
" else:\n",
" file = pooch.retrieve(\n",
" filelocation,\n",
" known_hash=None,\n",
" fname=os.path.join(user_temp_cache, filename),\n",
" processor=processor,\n",
" )\n",
"\n",
" return file"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"execution": {},
"tags": []
},
"outputs": [],
"source": [
"# @title Video 1: Paleoclimate Data Assimilation\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', 'PRKLrJDPMWk'), ('Bilibili', 'BV16W4y1o7ia')]\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,
"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 = \"f2ynq\"\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",
"metadata": {
"execution": {}
},
"source": [
"# Section 1: Load the LGMR Paleoclimate Reconstruction\n",
"\n",
"This dataset contains a reconstruction of surface air temperature (SAT) from the product [Last Glacial Maximum Reanalysis (LGMR)](https://www.ncdc.noaa.gov/paleo/study/33112). Note that this data starts from 100 years before present and goes back in time to ~24,000 BP. The period of time from ~21,000 to 18,000 years ago is referred to as the Last Glacial Maximum (LGM). The LGM was the most recent glacial period in Earth's history. During this time, northern hemisphere ice sheets were larger, global sea level was lower, atmospheric CO2 was lower, and global mean temperature was cooler. (Note: if you are interested in looking at data for the present to last millenium, that reanalyses product is available [here](https://www.ncei.noaa.gov/access/paleo-search/study/27850).)\n",
"\n",
"We will calculate the global mean temperature from the LGM to 100 years before present from a paleoclimate data assimilation to asses how Earth's climate varied over the past 24,000 years.\n",
"\n",
"First let's download the paleoclimate data assimilation reconstruction for surface air temperature (SAT). "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {},
"executionInfo": {
"elapsed": 1997,
"status": "ok",
"timestamp": 1681256682525,
"user": {
"displayName": "Brodie Pearson",
"userId": "05269028596972519847"
},
"user_tz": 420
}
},
"outputs": [],
"source": [
"filename_LGMR_SAT_climo = \"LGMR_SAT_climo.nc\"\n",
"url_LGMR_SAT_climo = \"https://www.ncei.noaa.gov/pub/data/paleo/reconstructions/osman2021/LGMR_SAT_climo.nc\"\n",
"\n",
"ds = xr.open_dataset(\n",
" pooch_load(filelocation=url_LGMR_SAT_climo, filename=filename_LGMR_SAT_climo)\n",
")\n",
"ds"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"## Section 1.1: Plotting the Temperature Time Series\n",
"\n",
"Now that the data is loaded, we can plot a time series of the temperature data to assess global changes. However, the dimensions of the `sat_mean` variable are age-lat-lon, so we first need to weight the data and calculate a global mean."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {},
"executionInfo": {
"elapsed": 143,
"status": "ok",
"timestamp": 1681256741486,
"user": {
"displayName": "Brodie Pearson",
"userId": "05269028596972519847"
},
"user_tz": 420
}
},
"outputs": [],
"source": [
"# assign weights\n",
"weights = np.cos(np.deg2rad(ds.lat))\n",
"\n",
"# calculate the global mean surface temperature\n",
"sat_global_mean = ds.sat.weighted(weights).mean(dim=[\"lat\", \"lon\"])\n",
"sat_global_mean"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"Now that we calculated our global mean, we can plot the results as a time series to assess global changes in temperature over the past 24,000 years:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {},
"executionInfo": {
"elapsed": 559,
"status": "ok",
"timestamp": 1681256745162,
"user": {
"displayName": "Brodie Pearson",
"userId": "05269028596972519847"
},
"user_tz": 420
}
},
"outputs": [],
"source": [
"# plot the global mean surface temperature since the LGM\n",
"f, ax1 = plt.subplots(1, 1, figsize=(12, 6))\n",
"ax1.plot(ds[\"age\"], sat_global_mean, linewidth=3)\n",
"\n",
"ax1.set_xlim(ds[\"age\"].max().values, ds[\"age\"].min().values)\n",
"ax1.set_ylabel(\"Global Mean SAT for LGM ($^\\circ$C)\", fontsize=16)\n",
"ax1.set_xlabel(\"Age (yr BP)\", fontsize=16)"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"### Questions 1.1: Climate Connection\n",
"1. How has global temperature varied over the past 24,000 years?\n",
"2. What climate forcings may have contributed to the increase in temperature ~17,000 years ago? "
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"## Section 1.2: Plotting a Temperature Anomaly Map\n",
"\n",
"The reanalysis contains *spatial* reconstructions, so we can also make figures showing spatial temperature anomalies for different time periods (i.e., the change in temperature between two specified times). The anomaly that we'll interpret is the difference between global temperature from 18,000 to 21,000 years ago (i.e. \"LGM\") and 100 to 1,000 years ago (i.e. \"modern\") . First, we'll calculate the average temperatures for each time period."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {}
},
"outputs": [],
"source": [
"# calculate the LGM (18,000-21,000 year) mean temperature\n",
"lgm = ds.sat.sel(age=slice(\"18000\", \"21000\"), lon=slice(0, 357.5), lat=slice(-90, 90))\n",
"lgm_mean = lgm.mean(dim=\"age\")\n",
"\n",
"# calculate the \"modern\" (100-1000 year) mean temperature\n",
"modern = ds.sat.sel(age=slice(\"100\", \"1000\"), lon=slice(0, 357.5), lat=slice(-90, 90))\n",
"modern_mean = modern.mean(dim=\"age\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"Now we can calculate the anomaly and create a map to visualize the change in temperature from the LGM to present in different parts on Earth."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {}
},
"outputs": [],
"source": [
"sat_change = modern_mean - lgm_mean"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {}
},
"outputs": [],
"source": [
"# make a map of changes\n",
"fig, ax = plt.subplots(figsize=(12, 8), subplot_kw={\"projection\": ccrs.Robinson()})\n",
"ax.set_global()\n",
"sat_change.plot(\n",
" ax=ax,\n",
" transform=ccrs.PlateCarree(),\n",
" x=\"lon\",\n",
" y=\"lat\",\n",
" cmap=\"Reds\",\n",
" vmax=30,\n",
" cbar_kwargs={\"orientation\": \"horizontal\", \"label\": \"$\\Delta$SAT ($^\\circ$C)\"},\n",
")\n",
"ax.coastlines()\n",
"ax.set_title(f\"Modern - LGM SAT ($^\\circ$C)\", loc=\"center\", fontsize=16)\n",
"ax.gridlines(color=\"k\", linewidth=1, linestyle=(0, (1, 5)))\n",
"ax.spines[\"geo\"].set_edgecolor(\"black\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"Before we interpret this data, another useful way to visualize this data is through a plot of zonal mean temperature (the average temperature for all locations at a single latitude). Once we calculate this zonal mean, we can create a plot of LGM to present temperature anomalies versus latitude."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {}
},
"outputs": [],
"source": [
"zonal_mean = sat_change.mean(dim=\"lon\")\n",
"latitude = ds.lat"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"execution": {}
},
"outputs": [],
"source": [
"# Make a zonal mean figure of the changes\n",
"fig, ax1 = plt.subplots(1, 1)\n",
"ax1.plot(zonal_mean, latitude)\n",
"ax1.axvline(x=0, color=\"gray\", alpha=1, linestyle=\":\", linewidth=2)\n",
"ax1.set_ylim(-90, 90)\n",
"ax1.set_xlabel(\"$\\Delta$T ($^\\circ$C)\")\n",
"ax1.set_ylabel(\"Latitude ($^\\circ$)\")\n",
"ax1.set_title(\n",
" f\"Zonal-mean $\\Delta$T ($^\\circ$C) changes\", # ohad comment: same changes\n",
" loc=\"center\",\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"### Questions 1.1: Climate Connection\n",
"\n",
"Looking at both the map and zonal mean plot, consider the following questions: \n",
"\n",
"1. How does the temperature anomaly vary with latitude? \n",
"2. What might be causing spatial differences in the temperature anomaly?"
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"# Summary\n",
"In this last tutorial of this day, you explored the intersection of paleoclimate proxies and models through reanalysis products, specifically analyzing the Last Glacial Maximum Reanalysis (LGMR) from Osman et al. (2021).\n",
"\n",
"Through this tutorial, you've learned a range of new skills and knowledge:\n",
"- Interpreting Paleoclimate Reconstructions: You have learned how to decode and visualize the time series of a paleoclimate reconstruction of surface air temprature from a reanalysis product in order to enhance your understanding of the temperature variations from the Last Glacial Maximum to the present day.\n",
"- Constructing Global Temperature Anomaly Maps: You've acquired the ability to construct and understand global maps that represent temperature anomalies, providing a more holistic view of the Earth's climate during the Last Glacial Maximum.\n",
"- Examining Latitude's Role in Temperature Variations: You've explored how temperature anomalies from the Last Glacial Maximum to the present day vary by latitude and pondered the reasons behind these variations, enriching your understanding of latitudinal effects on global climate patterns."
]
},
{
"cell_type": "markdown",
"metadata": {
"execution": {}
},
"source": [
"# Resources\n",
"\n",
"The code for this notebook is based on [code available from Erb et al. (2022)](https://github.com/Holocene-Reconstruction/Holocene-code) and workflow presented during the [Paleoclimate Data Assimilation Workshop 2022](https://github.com/michaelerb/da-workshop).\n",
"\n",
"Data from the following sources are used in this tutorial:\n",
"\n",
"* Matthew B. Osman, Jessica E. Tierney, Jiang Zhu, Robert Tardif, Gregory J. Hakim, Jonathan King, Christopher J. Poulsen. 2021. Globally resolved surface temperatures since the Last Glacial Maximum. Nature, 599, 239-244. http://doi.org/10.1038/s41586-021-03984-4.\n",
"* King, J. M., Tierney, J., Osman, M., Judd, E. J., & Anchukaitis, K. J. (2023). DASH: A MATLAB Toolbox for Paleoclimate Data Assimilation. Geoscientific Model Development, preprint. https://doi.org/10.5194/egusphere-2023-68."
]
}
],
"metadata": {
"colab": {
"collapsed_sections": [],
"include_colab_link": true,
"name": "W1D4_Tutorial9",
"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.10.8"
}
},
"nbformat": 4,
"nbformat_minor": 4
}