{ "cells": [ { "cell_type": "markdown", "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/W1D4_Paleoclimate/instructor/W1D4_Tutorial7.ipynb)   \"Open" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Bonus Tutorial 7: Assessing Climate Forcings\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, Paul Heubel, Sherry Mi, Beatriz Cosenza Muralles, Brodie Pearson, Jenna Pearson, Mauro Tripaldi, Chi Zhang, Ohad Zivan \n", "\n", "**Content editors:** Yosmely Bermúdez, Paul Heubel, Zahra Khodakaramimaghsoud, Jenna Pearson, Agustina Pesce, 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", "metadata": { "execution": {} }, "source": [ "# Tutorial Objectives\n", "\n", "*Estimated timing of tutorial:* 25 minutes\n", "\n", "In this tutorial, you will use data analysis tools and climate concepts you've learned today, and on previous days, to assess the forcings of climate variations observed in paleoclimate records. \n", "\n", "By the end of this tutorial you will be able to:\n", "\n", "* Plot and interpret temperature reconstructions from speleothem oxygen isotopes\n", "* Generate and plot time series of solar insolation\n", "* Assess the orbital forcings on monsoon intensity over the past 400,000 years using spectral analysis" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Setup " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# installations ( uncomment and run this cell ONLY when using google colab or kaggle )\n", "\n", "# !pip install pyleoclim\n", "# !pip install climlab\n", "\n", "# you may get errors about fortran extensions not working, and that is ok we are not using those here\n", "# in W1D5 you will learn how to install climlab in colab using conda which will fix these errors and give you\n", "# full functionality of the package" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {}, "executionInfo": { "elapsed": 7892, "status": "ok", "timestamp": 1681252345527, "user": { "displayName": "Brodie Pearson", "userId": "05269028596972519847" }, "user_tz": 420 } }, "outputs": [], "source": [ "# imports\n", "import pooch\n", "import os\n", "import tempfile\n", "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "import pyleoclim as pyleo\n", "\n", "from climlab import constants as const\n", "from climlab.solar.orbital import OrbitalTable\n", "from climlab.solar.insolation import daily_insolation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Install and import feedback gadget\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Install and import feedback gadget\n", "\n", "!pip3 install vibecheck datatops --quiet\n", "\n", "from vibecheck import DatatopsContentReviewContainer\n", "def content_review(notebook_section: str):\n", " return DatatopsContentReviewContainer(\n", " \"\", # No text prompt\n", " notebook_section,\n", " {\n", " \"url\": \"https://pmyvdlilci.execute-api.us-east-1.amazonaws.com/klab\",\n", " \"name\": \"comptools_4clim\",\n", " \"user_key\": \"l5jpxuee\",\n", " },\n", " ).render()\n", "\n", "\n", "feedback_prefix = \"W1D4_T7\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Figure Settings\n" ] }, { "cell_type": "code", "execution_count": null, "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": [ "## Helper functions\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "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": "markdown", "metadata": {}, "source": [ "## Video 1: Interpreting Climate Forcings\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 1: Interpreting Climate Forcings\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', 'ELBOupIFjWw'), ('Bilibili', 'BV1N14y1d7L9')]\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": [ "## Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Interpreting_Climate_Forcings_Video\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n" ] }, { "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 = \"ztfwk\"\n", "\n", "print(f\"If you want to download the slides: https://osf.io/download/{link_id}/\")\n", "IFrame(src=f\"https://mfr.ca-1.osf.io/render?url=https://osf.io/{link_id}/?direct%26mode=render%26action=download%26mode=render\", width=854, height=480)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Interpreting_Climate_Forcings_Slides\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Section 1: Understanding Climate Forcings\n", "\n", "A common task in paleoclimatology is to relate a proxy record (or several of them) to the particular forcing(s) that is thought to dominate that particular record (e.g., based on the proxy, location, etc.). We've already spent some time in earlier tutorials learning about the influence of Earth's orbital configuration on glacial-interglacial cycles. In this tutorial, we'll assess the climate forcings of monsoon intensity over the past 400,000 years. \n", "\n", "Recall from the video that monsoons are seasonal changes in the direction of the strongest wind and precipitation that are primarily driven by variations in seasonal insolation. Land and the ocean have different abilities to hold onto heat. Land cools and warms much faster than the ocean does due to high heat capacity. This temperature difference leads to a pressure difference that drives atmospheric circulations called monsoons. \n", "\n", "* **Summer (Northern Hemisphere)**: the land is warmer than the ocean, so the winds blow towards the land, resulting in heavy rainfall over land.\n", "* **Winter (Northern Hemisphere)**: the land is cooler than the ocean, so the winds blow away from the land, resulting in heavy rainfall over the ocean and decreased rainfall over land.\n", "\n", "On longer timescales, changes in insolation and the mean climate state can drive changes in monsoon intensity. To assess these long-term changes, we can analyze paleoclimate reconstructions from monsoon regions such as India, Southeast Asia or Africa. δ18O records from speleothems in Chinese caves are broadly interpreted to reflect continental-scale monsoon circulations. \n", "\n", "In this tutorial, we'll plot and analyze a composite of three δ18O speleothem records from multiple caves in China (Sanbao, Hulu, and Dongge caves) from [Cheng et al. (2016)](https://hwww.nature.com/articles/nature18591). We will then assess the relationship between the climate signals recorded by the speleothem δ18O and solar insolation." ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "First, we can download and plot the speleothem oxygen isotope data:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {}, "executionInfo": { "elapsed": 927, "status": "ok", "timestamp": 1681252746094, "user": { "displayName": "Brodie Pearson", "userId": "05269028596972519847" }, "user_tz": 420 } }, "outputs": [], "source": [ "# download the data from the url\n", "filename_Sanbao_composite = \"Sanbao_composite.csv\"\n", "url_Sanbao_composite = \"https://raw.githubusercontent.com/LinkedEarth/paleoHackathon/main/data/Orbital_records/Sanbao_composite.csv\"\n", "data = pd.read_csv(\n", " pooch_load(filelocation=url_Sanbao_composite, filename=filename_Sanbao_composite)\n", ")\n", "\n", "# create a pyleo.Series\n", "d18O_data = pyleo.Series(\n", " time=data[\"age\"] / 1000,\n", " time_name=\"Age\",\n", " time_unit=\"kyr BP\",\n", " value=-data[\"d18O\"],\n", " value_name=r\"$\\delta^{18}$O\",\n", " value_unit=\"\\u2030\",\n", ")\n", "d18O_data.plot()" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "You may notice that in the figure we just made, the δ18O values on the y-axis is plotted with more positive values up, whereas in previous tutorials, we've plotted isotopic data with more negative values up (since more negative/\"depleted\" suggests warmer temperatures or increased rainfall). However, the pre-processed δ18O data that we're using in this tutorial was multipled by -1, so now a more positive/\"enriched\" value suggests warmer temperatures or increased rainfall. In other words, in this figure, upward on the y-axis is increased monsoon intensity and downward on the y-axis is decreased monsoon intensity." ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Let's apply what we learned in the previous tutorial to perform spectral analysis on the speleothem oxygen isotope data. Recall from the previous tutorial that spectral analysis can help us identify dominant cyclicities in the data, which can be useful for assessing potential climate forcings.\n", "\n", "Here we'll use the Weighted Wavelet Z-Transform (WWZ) method you learned about in the previous tutorial:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# standardize the data\n", "d18O_stnd = d18O_data.interp(step=0.5).standardize() # save it for future use" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# calculate the WWZ spectral analysis\n", "d18O_wwz = d18O_stnd.spectral(method=\"wwz\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {}, "executionInfo": { "elapsed": 1575, "status": "ok", "timestamp": 1681252948290, "user": { "displayName": "Brodie Pearson", "userId": "05269028596972519847" }, "user_tz": 420 } }, "outputs": [], "source": [ "# plot WWZ results\n", "d18O_wwz.plot(xlim=[100, 5], ylim=[0.001, 1000])" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Coding Exercises 1\n", "\n", "1. The dominant spectral power is at ~23,000 years. This suggests a link between monsoon intensity and orbital precession! Is this peak significant? Use the skills you learned in the last tutorial to test the significance of this peak at the 95% confidence level. For this exercise, input `number = 30` as the default value which will take a long time to run." ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "execution": {} }, "source": [ "```python\n", "# perform significance test with 5 surrogates\n", "d18O_wwz_sig = ...\n", "\n", "# plot the results\n", "_ = ...\n", "\n", "```" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# to_remove solution\n", "\n", "# perform significance test with 5 surrogates\n", "d18O_wwz_sig = d18O_wwz.signif_test(number=5)\n", "\n", "# plot the results\n", "_ = d18O_wwz_sig.plot(xlabel=\"Period [kyrs]\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Coding_Exercises_1\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Section 2: Constructing Insolation Curves\n", "\n", "To further explore and confirm the relationship between monsoon intensity and orbital precession, let's take a look at insolation data and compare this to the speleothem δ18O records from Asia. Recall that insolation is controlled by variations in Earth's orbital cycles (eccentricity, obliquity, precession), so by comparing the δ18O record to insolation, we can assess the influence of orbital variations on δ18O and monsoon intensity. \n", "\n", "To compute solar insolation, we can use the package [`climlab`](https://climlab.readthedocs.io/en/latest/index.html) by Brian Rose. Let's create a time series over the past 400,000 years of changes in summer insolation at 31.67ºN, which is the latitude of Sanbao, one of the caves from which the speleothem records were produced." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# specify time interval and units\n", "kyears = np.linspace(-400, 0, 1001)\n", "\n", "# subset of orbital parameters for specified time\n", "orb = OrbitalTable.interp(kyear=kyears)\n", "days = np.linspace(0, const.days_per_year, 365)\n", "\n", "# generate insolation at Sanbao latitude (31.67)\n", "Qsb = daily_insolation(31.67, days, orb)\n", "\n", "# julian days 152-243 are JJA\n", "Qsb_jja = np.mean(Qsb[:, 151:243], axis=1)" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Now we can store this data as a `Series` in Pyleoclim and plot the data versus time:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {}, "executionInfo": { "elapsed": 540, "status": "ok", "timestamp": 1681253008796, "user": { "displayName": "Brodie Pearson", "userId": "05269028596972519847" }, "user_tz": 420 } }, "outputs": [], "source": [ "ts_qsb = pyleo.Series(\n", " time=-kyears,\n", " time_name=\"Age\",\n", " time_unit=\"ky BP\",\n", " value=Qsb_jja,\n", " value_name=\"JJA Insolation\",\n", " value_unit=r\"$W.m^{-2}$\",\n", ")\n", "\n", "ts_qsb.plot()" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Next, let's plot and compare the speleothem δ18O data and the solar insolation data:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {}, "executionInfo": { "elapsed": 819, "status": "ok", "timestamp": 1681253013959, "user": { "displayName": "Brodie Pearson", "userId": "05269028596972519847" }, "user_tz": 420 } }, "outputs": [], "source": [ "# standardize the insolation data\n", "ts_qsb_stnd = ts_qsb.standardize()\n", "\n", "# create a MultipleSeries of the speleothem d18O record and insolation data\n", "compare = [d18O_stnd, ts_qsb_stnd]\n", "ms_compare = pyleo.MultipleSeries(compare, time_unit=\"kyr BP\", name=None)\n", "\n", "# create a stackplot to compare the data\n", "ms_compare.stackplot()" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "By visually comparing the time series of the two records, we can see similarites at orbital scales. To confirm this, we can use spectral analysis to determine the dominant spectral power of the insolation data:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# calculate the WWZ spectral analysis\n", "psd_wwz = ts_qsb_stnd.spectral(method=\"wwz\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {}, "executionInfo": { "elapsed": 602, "status": "ok", "timestamp": 1681253046710, "user": { "displayName": "Brodie Pearson", "userId": "05269028596972519847" }, "user_tz": 420 } }, "outputs": [], "source": [ "psd_wwz.plot()" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Questions 2: Climate Connection\n", "\n", "1. What is the dominant spectral power in summer insolation at 31ºN latitude? How does this compare to the speleothem data?\n", "2. Why might there be a relationship between solar insolation and monsoon intensity? What does the common spectral power in both the insolation and δ18O records suggest about the climate forcings driving monsoon intensity in this region?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# to_remove explanation\n", "\n", "\"\"\"\n", "1. The dominant spectral power for the insolation data is ~23,000 years, which is roughly the same signal observed in the spectral analysis of the speleothem d18O record.\n", "2. The relationship between solar insolation and monsoon intensity can be explained by the fact that solar insolation affects the temperature gradients between land and sea. During the summer, if there's high solar insolation, the land warms up more than the sea, leading to lower pressure over the land. This low pressure attracts moisture-rich air from the sea, causing the monsoon. Therefore, changes in solar insolation caused by Earth's orbital cycles could influence the intensity of monsoons. Specifically, the 23,000 year signal in these records suggests that precession-driven changes in insolation was the dominant forcing of monsoon intensity.\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Questions_2\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Summary\n", "\n", "In this tutorial, you've gained valuable insights into the complex world of paleoclimatology and climate forcings. Here's a recap of what you've learned:\n", "* You've discovered how to plot and interpret temperature reconstructions derived from speleothem oxygen isotopes. \n", "* You've assessed the likely impact of orbital forcings on monsoon intensity over the past 400,000 years using spectral analysis. \n", "* By comparing the δ18O record to insolation, you've developed a deeper understanding of the relationship between solar insolation and monsoon intensity." ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Resources\n", "\n", "Code for this tutorial is based on an existing notebook from LinkedEarth that explore [forcing and responses in paleoclimate data](https://github.com/LinkedEarth/paleoHackathon/blob/main/notebooks/PaleoHack-nb04_Forcing%26Response.ipynb).\n", "\n", "Data from the following sources are used in this tutorial:\n", "\n", "* Cheng, H., Edwards, R., Sinha, A. et al. The Asian monsoon over the past 640,000 years and ice age terminations. Nature 534, 640–646 (2016). https://doi.org/10.1038/nature18591" ] } ], "metadata": { "colab": { "collapsed_sections": [], "include_colab_link": true, "name": "W1D4_Tutorial7", "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": 4 }