{ "cells": [ { "cell_type": "markdown", "id": "f8610814", "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_Tutorial8.ipynb)   \"Open" ] }, { "cell_type": "markdown", "id": "mVZDbCNcJlom", "metadata": { "execution": {} }, "source": [ "# Tutorial 8: Masking with One Condition\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": "06c3e74f-762e-42d4-abbc-e3acb0b49cb9", "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": "tfnrWLSylttw", "metadata": { "execution": {} }, "source": [ "# Tutorial Objectives\n", "\n", "*Estimated timing of tutorial:* 15 minutes \n", "\n", "One useful tool for assessing climate data is masking, which allows you to filter elements of a dataset according to a specific condition and create a \"masked array\" in which the elements not fulfilling the condition will not be shown. This tool is helpful if you wish to, for example, only look at data greater or less than a certain value, or from a specific temporal or spatial range. For instance, when analyzing a map of global precipitation, we could mask regions that contain a value of mean annual precipitation above or below a specific value or range of values in order to assess wet and dry seasons. \n", "\n", "In this tutorial, you will learn how to mask data with one condition and will apply this to your map of global SST." ] }, { "cell_type": "markdown", "id": "0af7bee1-3de3-453a-8ae8-bcd7910b4266", "metadata": { "execution": {}, "tags": [] }, "source": [ "# Setup\n" ] }, { "cell_type": "code", "execution_count": null, "id": "5d7768f4-c27d-4502-a975-934632c1d9b3", "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": {}, "executionInfo": { "elapsed": 1691, "status": "ok", "timestamp": 1681573592149, "user": { "displayName": "Sloane Garelick", "userId": "04706287370408131987" }, "user_tz": 240 }, "tags": [] }, "outputs": [], "source": [ "# imports\n", "import matplotlib.pyplot as plt\n", "import xarray as xr\n", "from pythia_datasets import DATASETS" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Figure Settings\n" ] }, { "cell_type": "code", "execution_count": null, "id": "994efaf1-a38b-42f4-ac31-40b747e6b806", "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: Climate Feedbacks\n" ] }, { "cell_type": "code", "execution_count": null, "id": "7bc232cf-a917-49e3-93b9-77afb5fb3f20", "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 1: Climate Feedbacks\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', 'cx1DGGOBD74'), ('Bilibili', 'BV1pV411T7h5')]\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": "bc214dbf-b28f-44c4-bb53-8d8e852610e6", "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 = \"zhb3c\"\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": "da76db37-e833-42c5-a740-5dcf0877b43c", "metadata": { "execution": {}, "tags": [] }, "source": [ "# Section 1: Masking Data\n" ] }, { "cell_type": "markdown", "id": "8a657ca8-fa2d-409c-9aaf-580828671018", "metadata": { "execution": {}, "tags": [] }, "source": [ "Using the `xr.where()` or `.where()` method, elements of an Xarray Dataset or Xarray DataArray that satisfy a given condition or multiple conditions can be replaced/masked. To demonstrate this, we are going to use the `.where()` method on the `tos` DataArray from the climate model CESM2 that we've been using in the past few tutorials. " ] }, { "cell_type": "markdown", "id": "59a59bef-b08c-4e0f-a48a-894565a962e7", "metadata": { "execution": {} }, "source": [ "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": 4386, "status": "ok", "timestamp": 1681573597967, "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": "386c7a1b-1a47-4f52-a42d-27fa997427d3", "metadata": { "execution": {} }, "source": [ "## Section 1.1: Using `.where()` with One Condition" ] }, { "cell_type": "markdown", "id": "a8d48b6b-a40e-469f-861f-83d943d70f03", "metadata": { "execution": {} }, "source": [ "Let's say we want to analyze SST just from the last time in the dataset (2014-09-15). We can isolate this time using `.isel()`:" ] }, { "cell_type": "code", "execution_count": null, "id": "ac3e42eb-1852-4580-9c52-e7237135ed01", "metadata": { "execution": {}, "executionInfo": { "elapsed": 166, "status": "ok", "timestamp": 1681573604936, "user": { "displayName": "Sloane Garelick", "userId": "04706287370408131987" }, "user_tz": 240 }, "tags": [] }, "outputs": [], "source": [ "sample = ds.tos.sel(time='2014-09')\n", "sample" ] }, { "cell_type": "markdown", "id": "ccdd1fa6-93fd-490d-8b05-c222ddcf953a", "metadata": { "execution": {} }, "source": [ "Now that we have our `DataArray` from the desired time period, we can use another function, [`.where()` documentation](http://xarray.pydata.org/en/stable/generated/xarray.DataArray.where.html) to filter elements according to a condition. The conditional expression in `.where()` can be a `DataArray`, a `Dataset`, or a function. Indexing methods on Xarray objects generally return a subset of the original data. However, it is sometimes useful to select an object with the same shape as the original data, but with some elements masked. Unlike `.isel()` and `.sel()` which change the shape of the returned results, `.where()` preserves the shape of the original data. It accomplishes this by returning values from the original `DataArray` or `Dataset` if the `condition` is `True`, and fills in values (by default `nan`) wherever the `condition` is `False`. Additional information can be found in the [`.where()` documentation](http://xarray.pydata.org/en/stable/generated/xarray.DataArray.where.html). \n", "\n", "Let's use `.where()` to mask locations with temperature values greater than 0ºC. Note that the condition we supply to `.where()` is the regions we wish to preserve, not those we wish to mask. So if we are interested in masking temperature values that are above 0ºC, we will pass the condition to preserve those that are less than or equal to 0ºC." ] }, { "cell_type": "code", "execution_count": null, "id": "61abc5b3-aadf-4a96-98a2-c9c36094a863", "metadata": { "execution": {}, "executionInfo": { "elapsed": 159, "status": "ok", "timestamp": 1681573607537, "user": { "displayName": "Sloane Garelick", "userId": "04706287370408131987" }, "user_tz": 240 }, "tags": [] }, "outputs": [], "source": [ "# mask temperatures greater than or equal to 0, preserve those that are less than 0\n", "masked_sample = sample.where(sample <= 0.0)\n", "masked_sample" ] }, { "cell_type": "markdown", "id": "09aeeee1-3924-4ccd-9b69-1be396c496b9", "metadata": { "execution": {} }, "source": [ "Let's plot both our original sample, and the masked sample for September 15th, 2014. Note we are using a different colorbar for the right hand figure, where the range of values is much smaller, and the same colors on the left would not correspond to the same colors on the right." ] }, { "cell_type": "code", "execution_count": null, "id": "eced16af", "metadata": { "execution": {}, "tags": [] }, "outputs": [], "source": [ "fig, axes = plt.subplots(ncols=2, figsize=(19, 6))\n", "sample.plot(ax=axes[0])\n", "masked_sample.plot(ax=axes[1])" ] }, { "cell_type": "markdown", "id": "c9183d75-f932-4870-b9d9-7a9b9ce7fd99", "metadata": { "execution": {} }, "source": [ "Notice how in the figure on the right, only the SST from the areas where SST is below 0ºC is shown and the other areas are white since these are now NaN values. Now let's assess how polar SST has changed over the time period recorded by the original dataset. To do so, we can run the same code but focus on the time 2000-09-15." ] }, { "cell_type": "code", "execution_count": null, "id": "ca1e150f-c422-4f0c-8f64-9c734bcbdee3", "metadata": { "execution": {}, "tags": [] }, "outputs": [], "source": [ "# create a second sample of September 2000\n", "sample_2 = ds.tos.sel(time=\"2000-09\")\n", "# mask it\n", "masked_sample_2 = sample_2.where(sample_2 < 0.0)\n", "# plot both samples next to each other\n", "fig, axes = plt.subplots(ncols=2, figsize=(19, 6))\n", "masked_sample_2.plot(ax=axes[0])\n", "masked_sample.plot(ax=axes[1])" ] }, { "cell_type": "markdown", "id": "c60366bb-523c-4bf1-ad2d-3d8facc38b97", "metadata": { "execution": {} }, "source": [ "### Questions 1.1: Climate Connection\n", "\n", "1. What is the purpose of masking in the analysis of climate data?\n", "2. Within the areas that are not masked, how does the distribution of SST compare between these maps?\n", "3. The minimum sea ice extent in the Arctic typically occurs annually in September after spring has brought in more sunlight and warmer temperatures. Considering both plots of September SST above (from 2000 on the left and 2014 on the right), how might changes in the ice-albedo feedback be playing a role in what you observe? Please state any assumptions you would make in your answer." ] }, { "cell_type": "markdown", "id": "e6518686-8d6b-41be-adbb-b8b121894fd7", "metadata": { "colab_type": "text", "execution": {}, "tags": [] }, "source": [ "[*Click for solution*](https://github.com/neuromatch/climate-course-content/tree/main/tutorials/W1D1_ClimateSystemOverview/solutions/W1D1_Tutorial8_Solution_041d7c6d.py)\n", "\n" ] }, { "cell_type": "markdown", "id": "4ce1373d-e4fc-437e-a445-13dd8fa5f22f", "metadata": { "execution": {} }, "source": [ "# Summary\n", "\n", "In this tutorial, we've explored the application of masking tools in the analysis of Sea Surface Temperature (SST) maps. Through masking, we've been able to focus our attention on areas where the SST is below 0°C. These are the regions where changes in the ice-albedo feedback mechanism are most evident in our present day. This has facilitated a more targeted analysis and clearer understanding of the data.\n", "\n", "\n", "\n" ] }, { "cell_type": "markdown", "id": "2abd55dc-f7ee-4a32-8de6-c955ba3c1929", "metadata": { "execution": {} }, "source": [ "# Resources" ] }, { "cell_type": "markdown", "id": "4ce9e503-986e-4784-a522-631150e01fb2", "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_Tutorial8", "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 }