From 53747e7dd9244388264476b37bde29438882155f Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Wed, 14 Jun 2023 09:50:29 -0400 Subject: [PATCH 01/22] revise intro portion of notebook --- .../01-high-level-computation-patterns.ipynb | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 77a0a3ad..0cc5a134 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -1,6 +1,7 @@ { "cells": [ { + "attachments": {}, "cell_type": "markdown", "id": "a349a0a5-eeb3-410a-b5d1-f472a8ca14b2", "metadata": { @@ -10,10 +11,28 @@ "tags": [] }, "source": [ - "# High-level computational patterns\n" + "# Utilizing computational patterns\n", + "\n", + "From https://toolz.readthedocs.io/en/latest/control.html\n", + "\n", + "Often when writing code we repeat certain patterns, whether we realize it or not.\n", + "If you have learned to write list comprehensions, you are taking advantage of a \"control pattern\".\n", + "Often, these patterns are so common that many packages have built in functions to implement them.\n", + "\n", + "> The Toolz library contains dozens of patterns like map and groupby. Learning a\n", + "> core set (maybe a dozen) covers the vast majority of common programming tasks\n", + "> often done by hand. A rich vocabulary of core control functions conveys the\n", + "> following benefits:\n", + ">\n", + "> - You identify new patterns\n", + "> - You make fewer errors in rote coding\n", + "> - You can depend on well tested and benchmarked implementations\n", + "\n", + "The same is true for xarray" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "623d5170-f32d-4643-9a59-c54768ee7185", "metadata": { @@ -25,18 +44,9 @@ "source": [ "## Motivation / Learning goals\n", "\n", - "From https://toolz.readthedocs.io/en/latest/control.html\n", - "\n", - "> The Toolz library contains dozens of patterns like map and groupby. Learning a\n", - "> core set (maybe a dozen) covers the vast majority of common programming tasks\n", - "> often done by hand. A rich vocabulary of core control functions conveys the\n", - "> following benefits:\n", - ">\n", - "> - You identify new patterns\n", - "> - You make fewer errors in rote coding\n", - "> - You can depend on well tested and benchmarked implementations\n", - "\n", - "The same is true for xarray\n" + "- Learn what high-level computational patterns are available in Xarray\n", + "- Identify when you are using a high-level computational pattern\n", + "- Implement that pattern using built-in Xarray functionality" ] }, { From 7726976377fac0170300f3a0f8efb261101f6b9b Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Fri, 23 Jun 2023 15:45:57 -0400 Subject: [PATCH 02/22] update rolling-reduce example to use a non built-in function --- .../01-high-level-computation-patterns.ipynb | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 0cc5a134..0c28403c 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -50,6 +50,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "5f0cda65-cfaa-42ed-bd4d-f290c1e98bb3", "metadata": { @@ -79,12 +80,13 @@ " [Bin data in to groups and reduce](https://docs.xarray.dev/en/stable/groupby.html)\n", "1. `groupby_bins`: GroupBy after discretizing a numeric variable.\n", "1. `resample` :\n", - " [Groupby specialized for time axes. Either downsample or upsample your data.](https://docs.xarray.dev/en/stable/user-guide/time-series.html#resampling-and-grouped-operations)\n", + " [GroupBy specialized for time axes. Either downsample or upsample your data.](https://docs.xarray.dev/en/stable/user-guide/time-series.html#resampling-and-grouped-operations)\n", "1. `weighted` :\n", " [Weight your data before reducing](https://docs.xarray.dev/en/stable/user-guide/computation.html#weighted-array-reductions)\n" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "71d8581c-1ffb-47a0-b992-12c3997f3586", "metadata": { @@ -94,7 +96,7 @@ "tags": [] }, "source": [ - "## Load example dataset\n" + "### Load example dataset\n" ] }, { @@ -116,6 +118,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "90832354-d0f3-4d83-a979-23b685203d3e", "metadata": { @@ -127,12 +130,7 @@ "source": [ "---\n", "\n", - "## Concept: \"index space\" vs \"label space\"\n", - "\n", - "These are windowed operations with a window of a fixed size.\n", - "\n", - "- `rolling`: sliding window operations e.g. running mean\n", - "- `coarsen`: decimating; reshaping\n" + "### Concept refresher: \"index space\" vs \"label space\"\n" ] }, { @@ -204,6 +202,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "e9b80381-8a0d-4833-97fa-687bf693ca5a", "metadata": {}, @@ -214,13 +213,17 @@ "\n", "### Index space\n", "\n", + "These are windowed operations with a window of a fixed size.\n", + "\n", "1. `rolling` :\n", - " [Operate on rolling windows of your data e.g. running mean](https://docs.xarray.dev/en/stable/user-guide/computation.html#rolling-window-operations)\n", + " [Operate on rolling (sliding) windows of your data e.g. running mean](https://docs.xarray.dev/en/stable/user-guide/computation.html#rolling-window-operations)\n", "1. `coarsen` :\n", - " [Downsample your data](https://docs.xarray.dev/en/stable/user-guide/computation.html#coarsen-large-arrays)\n", + " [Downsample your data (decimating, reshaping)](https://docs.xarray.dev/en/stable/user-guide/computation.html#coarsen-large-arrays)\n", "\n", "### Label space\n", "\n", + "These are windowed operations with irregular windows based on your data.\n", + "\n", "1. `groupby` :\n", " [Bin data in to groups and reduce](https://docs.xarray.dev/en/stable/groupby.html)\n", "1. `groupby_bins`: GroupBy after discretizing a numeric variable.\n", @@ -229,6 +232,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "339bdf89-c7da-4fca-89e1-a6655e65a6a3", "metadata": { @@ -238,11 +242,15 @@ "tags": [] }, "source": [ + "START HERE\n", + "ToDo revise/edit these examples\n", + "add some \"loop\" versions to show what a user might come up with that could be turned into one of these pattern operations\n", + "\n", "---\n", "\n", "## Index space: windows of fixed width\n", "\n", - "### Sliding windows of fixed length: `rolling`\n", + "### Sliding windows of fixed length: [`rolling`](https://docs.xarray.dev/en/stable/generated/xarray.DataArray.rolling.html)\n", "\n", "- returns object of same shape as input\n", "- pads with NaNs to make this happen\n", @@ -280,6 +288,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "b88c116e-ad63-4fea-81a7-bcabc194dee5", "metadata": { @@ -291,29 +300,41 @@ "source": [ "#### Apply an existing numpy-only function with `reduce`\n", "\n", + "In some cases, we may want to apply a sliding window function using rolling that is not built in to Xarray. In these cases we can still leverage the sliding windows of rolling and apply our own function with [`reduce`](https://docs.xarray.dev/en/stable/generated/xarray.core.rolling.DataArrayRolling.reduce.html).\n", + "\n", "Tip: The `reduce` method expects a function that can receive and return plain\n", - "arrays (e.g. numpy). The `map` method expects a function that can receive and\n", - "return Xarray objects.\n", + "arrays (e.g. numpy), as in each of the \"windows\" provided by the rolling iterator. This is in contrast to the `map` method, which expects a function that can receive and return Xarray objects.\n", "\n", - "Here's an example function: `np.mean`\n" + "Here's an example function: [`np.ptp`](https://numpy.org/doc/stable/reference/generated/numpy.ptp.html).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1610220", + "metadata": {}, + "outputs": [], + "source": [ + "data.rolling(lat=5, lon=5, center=True).reduce(np.ptp).plot()" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "9ef251aa-ce3c-4318-95ba-470568ebd967", "metadata": {}, "source": [ - "**Exercise** Calculate the rolling mean in 5 point bins along both latitude and\n", - "longitude using\n", - "[`rolling(...).reduce`](https://docs.xarray.dev/en/stable/generated/xarray.core.rolling.DataArrayRolling.reduce.html)\n" + "**Exercise** Calculate the rolling mean in 5 point bins along both latitude and longitude using\n", + "[`rolling(**kwargs).reduce`](https://docs.xarray.dev/en/stable/generated/xarray.core.rolling.DataArrayRolling.reduce.html)\n" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "75397b3d-5961-4924-b688-23520b79aae8", "metadata": {}, "source": [ - "**Answer:**\n" + "**Answer**\n" ] }, { From f51eff2c2ae66930349d1af7084a41e58dfbaabd Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Fri, 23 Jun 2023 16:20:15 -0400 Subject: [PATCH 03/22] revisions to rest of rolling and coarsen --- .../01-high-level-computation-patterns.ipynb | 66 +++++++++++-------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 0c28403c..3de052d3 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -82,7 +82,10 @@ "1. `resample` :\n", " [GroupBy specialized for time axes. Either downsample or upsample your data.](https://docs.xarray.dev/en/stable/user-guide/time-series.html#resampling-and-grouped-operations)\n", "1. `weighted` :\n", - " [Weight your data before reducing](https://docs.xarray.dev/en/stable/user-guide/computation.html#weighted-array-reductions)\n" + " [Weight your data before reducing](https://docs.xarray.dev/en/stable/user-guide/computation.html#weighted-array-reductions)\n", + "\n", + "\n", + "Note: the documentation links in this tutorial point to the DataArray implementations of each function, but they are also available for DataSet objects.\n" ] }, { @@ -243,7 +246,6 @@ }, "source": [ "START HERE\n", - "ToDo revise/edit these examples\n", "add some \"loop\" versions to show what a user might come up with that could be turned into one of these pattern operations\n", "\n", "---\n", @@ -357,6 +359,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "d0155b62-d08f-42c6-b467-1af73a7829c0", "metadata": { @@ -366,10 +369,9 @@ "tags": [] }, "source": [ - "#### For more complicated analysis, construct a new array with a new dimension.\n", + "#### Storing the outputs from `rolling` operations with `construct`\n", "\n", - "Allows things like short-time fourier transform, spectrogram, windowed rolling\n", - "etc.\n" + "In the above examples, we plotted the outputs of our rolling operations. Xarray makes it easy to store the outputs from `rolling` directly into the DataArray using the [`construct`](https://docs.xarray.dev/en/stable/generated/xarray.core.rolling.DataArrayRolling.construct.html#xarray.core.rolling.DataArrayRolling.construct) method." ] }, { @@ -395,12 +397,12 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "0a23b9a9-076b-472d-b7a6-57083566a32d", "metadata": {}, "source": [ - "**Exercise** Calculate the 5 point running mean in time using\n", - "`rolling.construct`\n" + "**Exercise** Calculate the 5 point running mean in time and add it to your DataArray using `rolling.construct`" ] }, { @@ -441,6 +443,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "816929d5-6635-4e93-99fc-79b5729c5491", "metadata": { @@ -449,7 +452,7 @@ "source": [ "#### Advanced: Another `construct` example\n", "\n", - "This is a 2D rolling example; we need to provide two new dimension names\n" + "This is a 2D rolling example; we need to provide two new dimension names.\n" ] }, { @@ -459,10 +462,11 @@ "metadata": {}, "outputs": [], "source": [ - "(data.rolling(lat=5, lon=5, center=True).construct(lat=\"lat_roll\", lon=\"lon_roll\"))" + "data.rolling(lat=5, lon=5, center=True).construct(lat=\"lat_roll\", lon=\"lon_roll\")" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "f75d2a5f-31d0-4943-b70a-06e7c8a30601", "metadata": { @@ -476,9 +480,7 @@ "\n", "### Block windows of fixed length: `coarsen`\n", "\n", - "For non-overlapping windows or \"blocks\" use `coarsen`. The syntax is very\n", - "similar to `rolling`. You will need to specify `boundary` if the length of the\n", - "dimension is not a multiple of the block size\n" + "For non-overlapping windows or \"blocks\" use [`coarsen`](https://docs.xarray.dev/en/stable/generated/xarray.DataArray.coarsen.html). The syntax is very similar to `rolling`. You will need to specify how you want Xarray to handle the `boundary` if the length of the dimension is not a multiple of the block size.\n" ] }, { @@ -508,7 +510,7 @@ "metadata": {}, "outputs": [], "source": [ - "data.coarsen(lat=5, lon=5, boundary=\"trim\").std()" + "data.coarsen(lat=5, lon=5, boundary=\"trim\").mean()" ] }, { @@ -522,6 +524,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "b30794c7-1aeb-4e13-b6b4-824f23ac07df", "metadata": { @@ -531,10 +534,9 @@ "tags": [] }, "source": [ - "#### coarsen supports `reduce` for custom reductions\n", + "#### Coarsen supports `reduce` for custom reductions\n", "\n", - "**Exercise** Use `coarsen.reduce` to apply `np.mean` in 5x5 (latxlon) point\n", - "blocks of `data`\n" + "**Exercise** Use `coarsen.reduce` to apply `np.ptp` in 5x5 (lat x lon) point blocks to `data`" ] }, { @@ -561,6 +563,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "0e7cbd7b-da18-48a3-bd29-708d97cc3bb7", "metadata": { @@ -570,12 +573,11 @@ "tags": [] }, "source": [ - "#### coarsen supports `construct` for block reshaping\n", + "#### Coarsen supports `construct` for block reshaping and storing outputs\n", "\n", "This is usually a good alternative to `np.reshape`\n", "\n", - "A simple example splits a 2-year long monthly 1D time series into a 2D array\n", - "shaped (year x month)\n" + "A simple example splits a 2-year long monthly 1D time series into a 2D array shaped (year x month)\n" ] }, { @@ -605,6 +607,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "5dc5c7e7-bc3c-4362-bbd1-6a05801b7c90", "metadata": {}, @@ -615,8 +618,7 @@ "1. The new dimensions `year` and `month` don't have any coordinate labels\n", " associated with them.\n", "\n", - "What if the data had say 23 instead of 24 values? In that case we specify a\n", - "different `boundary` , here we pad to 24 values.\n" + "What if the data had say 23 instead of 24 values (`months.isel(time=slice(1, None)`)? In that case we specify a different `boundary` (the default `boundary=\"exact\"` worked above); here we pad to 24 values.\n" ] }, { @@ -630,11 +632,12 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "f4e90b49-42e4-411f-9148-bcaf145de26c", "metadata": {}, "source": [ - "This ends up adding values at the end of the array, not so sensible for this\n", + "This adds values at the end of the array (see the 'nan' at the end of the time coordinate?), which is not so sensible for this\n", "problem. We have some control of the padding through the `side` kwarg to `coarsen`. For `side=\"right\"` we get more sensible output." ] }, @@ -651,11 +654,12 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "8174aad1-d6e1-4772-bf23-91e363a92c19", "metadata": {}, "source": [ - "Note that `coarsen` pads with NaNs. For more control over paddnig, use\n", + "Note that `coarsen` pads with NaNs. For more control over padding, use\n", "[DataArray.pad](https://docs.xarray.dev/en/stable/generated/xarray.DataArray.pad.html) explicitly." ] }, @@ -671,7 +675,9 @@ " .pad(time=(1, 0), constant_values=-1)\n", " .coarsen(time=12)\n", " .construct(time=(\"year\", \"month\"))\n", - ")" + ")\n", + "\n", + "#NOTE: check output of this cell (why is the first value of time nan instead of -1?)" ] }, { @@ -733,21 +739,20 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "4de2984e-9c28-4ed7-909f-bab47b6eae49", "metadata": {}, "source": [ - "This exercise came up during the live lecture.\n", + "This exercise came up during a live lecture.\n", "\n", "**Exercise** Calculate the rolling 4 month average, averaged across years.\n", "\n", "**Answer**\n", "\n", "1. We first reshape using `coarsen.construct` to add `year` as a new dimension.\n", - "2. Then `rolling` on the month dimension.\n", - "3. It turns out that `roll.mean([\"year\", \"month\"])` doesn't work. So we use\n", - " `roll.construct` to get a DataArray with a new dimension `window` and then\n", - " take the mean over `window` and `year`\n" + "2. Apply `rolling` on the month dimension.\n", + "3. It turns out that `roll.mean([\"year\", \"month\"])` doesn't work. So we use `roll.construct` to get a DataArray with a new dimension `window` and then take the mean over `window` and `year`\n" ] }, { @@ -779,6 +784,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "25fd132c-5436-4af6-b8ad-75269cb45e75", "metadata": { @@ -788,6 +794,8 @@ "tags": [] }, "source": [ + "START EDITING HERE!\n", + "\n", "---\n", "\n", "## Label space \"windows\" or bins : GroupBy\n", From 5173d87ca77c9272db191bd579538504a1e525fc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 23 Jun 2023 20:27:38 +0000 Subject: [PATCH 04/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- intermediate/01-high-level-computation-patterns.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 3de052d3..3ac16c2e 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -677,7 +677,7 @@ " .construct(time=(\"year\", \"month\"))\n", ")\n", "\n", - "#NOTE: check output of this cell (why is the first value of time nan instead of -1?)" + "# NOTE: check output of this cell (why is the first value of time nan instead of -1?)" ] }, { From a1681bc951aad694c5f0d38ab00428ccba91f347 Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Tue, 27 Jun 2023 14:36:09 -0400 Subject: [PATCH 05/22] finish revising groupby through end; start adding loop example --- .../01-high-level-computation-patterns.ipynb | 891 ++++++++++++++++-- 1 file changed, 797 insertions(+), 94 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 3ac16c2e..04d70106 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -1,7 +1,6 @@ { "cells": [ { - "attachments": {}, "cell_type": "markdown", "id": "a349a0a5-eeb3-410a-b5d1-f472a8ca14b2", "metadata": { @@ -32,7 +31,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "623d5170-f32d-4643-9a59-c54768ee7185", "metadata": { @@ -50,7 +48,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "5f0cda65-cfaa-42ed-bd4d-f290c1e98bb3", "metadata": { @@ -81,15 +78,12 @@ "1. `groupby_bins`: GroupBy after discretizing a numeric variable.\n", "1. `resample` :\n", " [GroupBy specialized for time axes. Either downsample or upsample your data.](https://docs.xarray.dev/en/stable/user-guide/time-series.html#resampling-and-grouped-operations)\n", - "1. `weighted` :\n", - " [Weight your data before reducing](https://docs.xarray.dev/en/stable/user-guide/computation.html#weighted-array-reductions)\n", "\n", "\n", "Note: the documentation links in this tutorial point to the DataArray implementations of each function, but they are also available for DataSet objects.\n" ] }, { - "attachments": {}, "cell_type": "markdown", "id": "71d8581c-1ffb-47a0-b992-12c3997f3586", "metadata": { @@ -104,10 +98,31 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "02a9022f-1503-45a2-b57a-05ebfeb11d16", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkMAAAHFCAYAAADxOP3DAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAACPBElEQVR4nO3dd3wURf8H8M9eTQ9JII2EELqRIgJSpUhVlPYoKiIg6KMCQaQpoBJ4aKJ0BUUxoIigQgT1oQSBSEQUA0gVEEI1MYqQEEi9m98f/HIPR7JzubvAJbnP+/XaF2RnZ3dmb2/zzezsjCKEECAiIiJyUxpXF4CIiIjIlRgMERERkVtjMERERERujcEQERERuTUGQ0REROTWGAwRERGRW2MwRERERG6NwRARERG5NQZDRERE5NYYDFGFt3v3bsTFxeHKlSvF0jp27IiOHTve8TLdCSdOnMC4cePQrFkzVKlSBYGBgWjbti2+/PLLErfPyMjAkCFDULVqVXh5eaF169b47rvvim33zTffYNCgQWjUqBH0ej0URSlxf+fPn0ffvn1Rq1YteHt7w9/fH02bNsU777yDwsLCUtejrMtly+LFi9GgQQMYjUZER0dj6tSpKCgosNrmwoULGD16NDp06IAqVapAURSsWLHCoeMRUfnHYIgqvN27d2Pq1KklBkNLlizBkiVL7nyh7oCtW7fi22+/xb/+9S988cUX+PTTT1G3bl089thjmDZtmtW2eXl56Ny5M7777jssXLgQGzZsQEhICHr06IGkpCSrbRMSErBnzx7ExMSgSZMmqse/du0a/Pz88Prrr2Pjxo1Ys2YN2rVrh9jYWLzwwgulqsPtKJfMjBkz8NJLL6Ffv37YsmULhg8fjpkzZ2LEiBFW2/3+++/49NNPYTAY8NBDDzl0LCKqQARRBffWW28JACI1NdXVRbmj/vrrL2E2m4ut79mzp/Dy8hK5ubmWde+++64AIHbv3m1ZV1BQIGJiYsR9991nld9kMln+P2LECGHvbaJ///5Cp9NZHV/NnSzX33//LTw8PMS///1vq/UzZswQiqKII0eOlHisvXv3CgAiPj7eruMRUcXBliGq0OLi4jB+/HgAQHR0NBRFgaIo2LlzJ4Dij8nOnDkDRVHw1ltv4c0330TNmjXh6emJjh074sSJEygoKMCrr76K8PBw+Pv7o2/fvsjIyCh23LVr16J169bw9vaGj48Punfvjv3799+JKltUrVq1xEdF9913H65fv45//vnHsi4hIQH169dH69atLet0Oh0GDhyIn3/+GRcvXrSs12icuy1Uq1YNGo0GWq3W5rZ3slybN29Gbm4unnnmGav1zzzzDIQQ+Oqrr8rsWERUsfAbTxXas88+i9jYWADA+vXr8eOPP+LHH3/EvffeK8337rvv4ocffsC7776LDz/8EL/99hseeeQRDBs2DH/99Rc++ugjzJkzB9u2bcOzzz5rlXfmzJl48sknERMTg88//xyffPIJrl69ivvvvx9Hjx61WebCwsJSLUIIh87Jjh07UK1aNQQHB1vWHT58GI0bNy62bdG6I0eOOHQsABBCoLCwEJcvX8batWuxYsUKjB07Fjqdzmbe21muko4FAI0aNbJaHxYWhqpVq1rSicj92L5bEZVjERERqFGjBgCgadOmqFmzZqnyValSBV999ZWlBeDvv//G6NGj0aBBA2zYsMGy3W+//YYFCxYgKysLfn5+OH/+PKZMmYKRI0di0aJFlu26du2KunXrYurUqVi7dq3qcc+cOYPo6OhSlXHHjh12d/7+8MMPsXPnTixcuNCqZebSpUsIDAwstn3RukuXLtl1nJu9+eabmDhxIgBAURRMmjQJ06dPL1Xe21muko5lNBrh7e1d4vHK8lhEVLEwGCK39NBDD1k9CrnrrrsAAD179rTarmj9uXPn0LBhQ2zZsgWFhYUYNGiQ1RtTHh4e6NChA3bs2CE9bnh4OPbu3VuqMtavX79U2xXZtGkTRowYgUcffdTSWnYz2dtXjr6ZBQBDhgxBly5d8M8//2D79u146623kJmZicWLFwO40XJkMpms8tzcalTW5br1TTatVmvZz+06B0RUsTEYIrd0a2uEwWCQrs/NzQUA/PnnnwCAFi1alLhfW31NDAYD7rnnnlKVsTR9bops2bIF/fr1Q9euXfHpp58W+8UeFBRUYstHUb+iklpnSis0NBShoaEAgG7duiEgIACvvvoqhg4diqZNm2LlypXF+ukUPQK8HeXS6/VWP8fHx2PIkCEICgpCbm4url+/Di8vr2LHa9asmd3HIqLKgcEQkR2qVq0KAPjyyy8RFRVld/7b8Zhsy5Yt6NOnDzp06IB169ZZAribNWrUCIcOHSq2vmhdw4YNS1Wm0rjvvvsA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SZMmISUlBffcc49lm2eeeQZnzpzBmjVrEB4ejoSEBPTo0QOHDh1C3bp18cMPP+CFF17Am2++iV69emHbtm14/fXXix3r999/x+eff45169ZBq9UCAHr27InAwED897//hb+/P95//3107twZJ06cQGBgILZs2YKBAwdi0aJFuP/++3Hq1Cn8+9//BgBMmTIFX375JebPn481a9bg7rvvRnp6erFgSo2teuXm5qJZs2Z45ZVX4Ofnh2+//RZPP/00atWqhZYtWyItLQ1PPvkk5syZg759++Lq1avYtWsXhBAYN24cjh07hqysLMTHxwMAAgMDS/25EFE55Np5YonkBg8eLHr37l1i2qRJk0T9+vWtZi9/9913hY+PjzCZTEIIITp06CDatWtnla9FixbilVdeEUIIsWnTJqHT6URaWpolPTExUQAQCQkJQoj/za69f/9+IYQQO3bsEADE5cuXrfYbFRUl5s+fb7WuSZMmYsqUKUIIIbZs2SK0Wq04f/68JX3Tpk1Wx1q+fHmxOuXl5QlPT0+xZcuWEs9DSeW5evWqMBgMYs2aNZZ1ly5dEp6enuKll14SQgjx+++/C0VRxMWLF63217lzZzFx4kQhhBCPP/646Nmzp1X6U089Jfz9/S0/T5kyRej1epGRkWFZ99133wk/Pz+Rm5trlbd27dri/fffF0IIcf/994uZM2dapX/yySciLCxMCCHE3LlzRb169UR+fn6J9VZTmnqV5KGHHhJjx44VQgiRkpIiAIgzZ86UuK3suoyPj7c6P0RU/rFliCqsY8eOoXXr1lAUxbKubdu2yM7OxoULF1CjRg0AQOPGja3yhYWFISMjA8CNTseRkZEIDQ21pN933323rbw1atRARESEZV3r1q2ttklJScHvv/8OX19fq/W5ubk4depUqY916tQp5OfnW+0/MDAQ9evXt/y8b98+CCFQr149q7x5eXkICgoCcOP89O3b1yr9vvvuwzfffGO1LioqCtWqVbOqR3Z2tmU/RXJyciz1SElJwd69ezFjxgxLuslkQm5uLq5fv47HHnsMCxYsQK1atdCjRw889NBDeOSRR2z23SpNvUwmE2bPno21a9fi4sWLyMvLQ15eHry9vQEATZo0QefOndGoUSN0794d3bp1w6OPPoqAgADpsYmoYmIwRBWWEMIqECpaB8BqvV6vt9pGURSYzWbVfThKo9FYjl/k5s7Et6bdWk7gxqO0Zs2a4dNPPy227c3Bhi0lHetWZrMZWq0WKSkplkdbRXx8fCz7UTvHNysKIm7ed1hYWIl9aYr6G5nNZkydOhX9+vUrto2HhwciIyNx/PhxJCYmYtu2bRg+fDjeeustJCUlFftM7a3X3LlzMX/+fCxYsACNGjWCt7c3Ro8ejfz8fAA3Os0nJiZi9+7d2Lp1KxYvXozJkyfjp59+QnR0tOqxiahiYjBEFVZMTAzWrVtn9Qt79+7d8PX1RfXq1Uu1jwYNGuDcuXP4888/ERISAgDYu3evNE9Rvx2TyWS1vlq1akhLS7P8nJWVhdTUVKvynjt3Dn/88QfCw8MBAD/++KPVPu69916sXbvW0unYUXXq1IFer8eePXssLWSXL1/GiRMn0KFDBwBA06ZNYTKZkJGRgfvvv7/E/TRo0AA///yz1bpffvnF5vHvvfdepKenQ6fToWbNmqrbHD9+HHXq1FHdj6enJ3r16oVevXphxIgRaNCgAQ4dOoR7771XNU9p6rVr1y707t0bAwcOBHAjgDp58iTuuusuyzaKoqBt27Zo27Yt3njjDURFRSEhIQFjxoyBwWAo9vkTUcXFt8mo3MvMzMSBAweslnPnzmH48OE4f/48YmNj8dtvv2HDhg2YMmUKxowZA42mdJd2165dUbt2bQwePBgHDx7EDz/8gMmTJwMo3mpTJCoqCoqi4JtvvsFff/2F7OxsAMADDzyATz75BLt27cLhw4cxePBgq5aJLl26oH79+hg0aBB+/fVX7Nq1y3KsIk899RSqVq2K3r17Y9euXUhNTUVSUhJeeuklXLhwodTnzMfHB8OGDcP48ePx3Xff4fDhwxgyZIjVealXrx6eeuopDBo0COvXr0dqair27t2LN998E//9738BALGxsfjvf/+LefPm4eTJk3j//fexadMmm61pXbp0QevWrdGnTx9s2bIFZ86cwe7du/Haa69Zgqk33ngDH3/8MeLi4nDkyBEcO3YMa9euxWuvvQYAWLFiBZYvX47Dhw/j9OnT+OSTT+Dp6YmoqCjpsUtTrzp16lhafo4dO4bnn38e6enpln389NNPmDlzJn755RecO3cO69evx19//WUJlmrWrImDBw/i+PHj+Pvvv916OAGiSsFFfZWISmXw4MECQLFl8ODBQgghdu7cKVq0aCEMBoMIDQ0Vr7zyiigoKLDk79Chg6XDcJHevXtb8gshxLFjx0Tbtm2FwWAQDRo0EF9//bUAIDZv3iyEKN6BWgghpk2bJkJDQ4WiKJZ9ZWZmiv79+ws/Pz8RGRkpVqxYYdWBWgghjh8/Ltq1aycMBoOoV6+e2Lx5s1UHaiGESEtLE4MGDRJVq1YVRqNR1KpVSzz33HMiMzOzxHOk1qH76tWrYuDAgcLLy0uEhISIOXPmFDsf+fn54o033hA1a9YUer1ehIaGir59+4qDBw9atlm2bJmoXr268PT0FH369BHTp08XoaGhlvQpU6aIJk2aFCtXVlaWiI2NFeHh4UKv14vIyEjx1FNPiXPnzlm22bx5s2jTpo3w9PQUfn5+4r777hPLli0TQgiRkJAgWrZsKfz8/IS3t7do1aqV2LZtW4nn4Fa26nXp0iXRu3dv4ePjI4KDg8Vrr70mBg0aZOkUffToUdG9e3dRrVo1YTQaRb169cTixYst+8/IyBBdu3YVPj4+AoDYsWOHJY0dqIkqHkWIUnQuIHIjP/zwA9q1a4fff/8dtWvXdnVxbNq5cyc6deqEy5cv35FBF5977jn89ttv2LVr120/VkW0YsUKjB49utyNUE5E6thniNxeQkICfHx8ULduXfz+++946aWX0LZt2woRCN0sIiICjzzySLHBA5319ttvo2vXrvD29samTZuwcuVKLFmypEyPUVn4+PigsLAQHh4eri4KEdmBwRC5vatXr2LChAk4f/48qlatii5dupQ4WnN51bJlS5w8eRLA/96WKks///wz5syZg6tXr6JWrVpYtGgRnn322TI/Tmnt2rULDz74oGp6UR8uVyiazPfWt9iIqHzjYzIiqlBycnJw8eJF1XTZ22lERCVhMERERERuja/WExERkVtjMERERERujcEQERERuTUGQ0REROTWGAwRERGRW2MwRERERG6NwRARERG5NQZDRERE5Nb+D/se0KgIrfEhAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "import numpy as np\n", "import xarray as xr\n", @@ -121,7 +136,624 @@ ] }, { - "attachments": {}, + "cell_type": "markdown", + "id": "bd47d199", + "metadata": {}, + "source": [ + "---\n", + "\n", + "### Identifying high-level computation patterns\n", + "\n", + "*or, when should I use these functions?*\n", + "\n", + "Consider a common use case. We want to complete some \"task\" for each of \"something\". The \"task\" might be a computation (e.g. mean, median, plot). The \"something\" could be a group of array values (e.g. pixels) or segments of time (e.g. monthly or seasonally).\n", + "\n", + "Often, our solution to this type of problem is to write a loop. Say we want the average air temperature for each month:" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "70159772", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[273.416748046875, 273.13104248046875, 275.1137390136719, 278.5469970703125, 283.299072265625, 287.5657043457031, 289.90692138671875, 290.089111328125, 287.41375732421875, 283.6811828613281, 277.9678039550781, 274.35107421875]\n" + ] + } + ], + "source": [ + "months = [1,2,3,4,5,6,7,8,9,10,11,12]\n", + "avg_temps = []\n", + "\n", + "for mon in months:\n", + " avg = da[da[\"time.month\"]==mon].mean()\n", + " avg_temps.append(float(avg.data))\n", + "\n", + "print(avg_temps)" + ] + }, + { + "cell_type": "markdown", + "id": "c1772b16", + "metadata": {}, + "source": [ + "Writing a for-loop here is not wrong, but it can quickly become cumbersome if you have a complex function to apply and it will take awhile to compute on a large dataset (you may even run out of memory). Parallelizing the computation would take a lot of additional work.\n", + "\n", + "Xarray's functionality instead allows us to do the same computation in one line of code (plus, the computation is optimized and ready to take advantage of parallel compute resources)!" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "6f1b23fa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "273.41675\n", + "273.13104\n", + "275.11374\n", + "278.547\n", + "283.29907\n", + "287.5657\n", + "289.90692\n", + "290.0891\n", + "287.41376\n", + "283.68118\n", + "277.9678\n", + "274.35107\n", + "[[[246.34987 246.38608 246.21518 ... 243.06113 244.08795 245.6467 ]\n", + " [248.8576 248.90733 248.7104 ... 241.52866 243.50865 246.75471]\n", + " [251.57712 251.19661 250.71463 ... 243.39891 246.78462 251.56572]\n", + " ...\n", + " [295.85028 295.24405 295.22684 ... 295.18625 294.65707 294.0485 ]\n", + " [296.5446 296.46982 296.15994 ... 295.35593 295.0812 294.53006]\n", + " [297.15417 297.2383 297.04892 ... 296.01797 295.77554 295.63647]]\n", + "\n", + " [[246.67715 246.40576 245.9484 ... 241.85838 243.0021 244.44383]\n", + " [247.8001 247.75992 247.47757 ... 240.64706 242.26633 245.06662]\n", + " [249.07079 248.57234 247.94254 ... 242.42874 245.33348 249.72273]\n", + " ...\n", + " [295.92886 295.41788 295.16602 ... 294.4443 293.78143 293.18265]\n", + " [296.78754 296.63443 296.15707 ... 294.51166 294.2178 293.70258]\n", + " [297.2889 297.2165 296.85797 ... 295.16058 294.9558 294.87967]]\n", + "\n", + " [[251.03168 250.67987 250.18945 ... 242.19398 243.11484 244.30956]\n", + " [252.97194 252.86617 252.57347 ... 241.61102 243.02509 245.38196]\n", + " [254.46768 254.09142 253.63428 ... 241.67184 244.49664 248.74258]\n", + " ...\n", + " [295.65652 295.24582 295.22464 ... 294.7663 294.16412 293.6729 ]\n", + " [296.70294 296.68756 296.3824 ... 294.85083 294.57034 294.15213]\n", + " [297.38174 297.4631 297.22668 ... 295.3349 295.11124 295.01654]]\n", + "\n", + " ...\n", + "\n", + " [[261.8136 261.21255 260.5036 ... 248.19336 249.06995 250.41624]\n", + " [269.02225 268.92944 268.71478 ... 246.41554 248.16833 251.14897]\n", + " [269.64017 268.7958 268.45483 ... 246.01215 249.6174 254.69598]\n", + " ...\n", + " [299.09723 298.30466 297.9945 ... 299.09454 298.6955 298.29483]\n", + " [299.43155 299.23853 298.7375 ... 299.2589 299.28873 299.0363 ]\n", + " [299.37054 299.42462 299.15607 ... 299.72403 299.66312 299.76233]]\n", + "\n", + " [[253.74484 253.64487 253.49716 ... 242.96066 243.9345 245.14209]\n", + " [259.12967 258.62927 258.19144 ... 241.84921 243.07965 245.46625]\n", + " [261.04227 258.83536 257.51193 ... 242.38234 245.13663 249.52368]\n", + " ...\n", + " [297.8426 297.1406 296.98773 ... 297.96884 297.56888 297.1611 ]\n", + " [298.58783 298.42026 297.96896 ... 298.16412 298.19397 297.9083 ]\n", + " [298.81143 298.8566 298.62103 ... 298.72955 298.7519 298.8189 ]]\n", + "\n", + " [[247.971 248.02118 247.91302 ... 239.7719 241.02383 242.62823]\n", + " [249.73361 250.16037 250.48581 ... 238.78964 240.96469 244.11626]\n", + " [252.0296 251.53136 251.36629 ... 238.07542 241.91293 247.06987]\n", + " ...\n", + " [296.76508 295.97668 295.88922 ... 296.45605 296.09137 295.65756]\n", + " [297.46814 297.38025 297.04428 ... 296.8556 296.84668 296.52133]\n", + " [297.8809 297.9868 297.77554 ... 297.60034 297.5655 297.53763]]]\n" + ] + } + ], + "source": [ + "for label, group in da.groupby(\"time.month\"):\n", + " print(group.mean().data)\n", + " \n", + "avg_temps = da.groupby(\"time.month\").mean()\n", + "print(avg_temps.data)" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "ad5ee977", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.DataArray 'air' (month: 12, lat: 25, lon: 53)>\n",
+       "246.3 246.4 246.2 245.8 245.2 244.6 ... 298.1 298.0 298.0 297.6 297.6 297.5\n",
+       "Coordinates:\n",
+       "  * lat      (lat) float32 75.0 72.5 70.0 67.5 65.0 ... 25.0 22.5 20.0 17.5 15.0\n",
+       "  * lon      (lon) float32 200.0 202.5 205.0 207.5 ... 322.5 325.0 327.5 330.0\n",
+       "  * month    (month) int64 1 2 3 4 5 6 7 8 9 10 11 12\n",
+       "Attributes:\n",
+       "    long_name:     4xDaily Air temperature at sigma level 995\n",
+       "    units:         degK\n",
+       "    precision:     2\n",
+       "    GRIB_id:       11\n",
+       "    GRIB_name:     TMP\n",
+       "    var_desc:      Air temperature\n",
+       "    dataset:       NMC Reanalysis\n",
+       "    level_desc:    Surface\n",
+       "    statistic:     Individual Obs\n",
+       "    parent_stat:   Other\n",
+       "    actual_range:  [185.16 322.1 ]
" + ], + "text/plain": [ + "\n", + "246.3 246.4 246.2 245.8 245.2 244.6 ... 298.1 298.0 298.0 297.6 297.6 297.5\n", + "Coordinates:\n", + " * lat (lat) float32 75.0 72.5 70.0 67.5 65.0 ... 25.0 22.5 20.0 17.5 15.0\n", + " * lon (lon) float32 200.0 202.5 205.0 207.5 ... 322.5 325.0 327.5 330.0\n", + " * month (month) int64 1 2 3 4 5 6 7 8 9 10 11 12\n", + "Attributes:\n", + " long_name: 4xDaily Air temperature at sigma level 995\n", + " units: degK\n", + " precision: 2\n", + " GRIB_id: 11\n", + " GRIB_name: TMP\n", + " var_desc: Air temperature\n", + " dataset: NMC Reanalysis\n", + " level_desc: Surface\n", + " statistic: Individual Obs\n", + " parent_stat: Other\n", + " actual_range: [185.16 322.1 ]" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "da.groupby(\"time.month\").mean()" + ] + }, + { + "cell_type": "markdown", + "id": "4f548b71", + "metadata": {}, + "source": [ + "Read on through this tutorial to learn some of the incredible ways to use Xarray to avoid writing long for-loops and efficiently complete computational analyses on your data." + ] + }, + { "cell_type": "markdown", "id": "90832354-d0f3-4d83-a979-23b685203d3e", "metadata": { @@ -205,7 +837,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "e9b80381-8a0d-4833-97fa-687bf693ca5a", "metadata": {}, @@ -235,7 +866,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "339bdf89-c7da-4fca-89e1-a6655e65a6a3", "metadata": { @@ -245,7 +875,6 @@ "tags": [] }, "source": [ - "START HERE\n", "add some \"loop\" versions to show what a user might come up with that could be turned into one of these pattern operations\n", "\n", "---\n", @@ -290,7 +919,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "b88c116e-ad63-4fea-81a7-bcabc194dee5", "metadata": { @@ -304,8 +932,7 @@ "\n", "In some cases, we may want to apply a sliding window function using rolling that is not built in to Xarray. In these cases we can still leverage the sliding windows of rolling and apply our own function with [`reduce`](https://docs.xarray.dev/en/stable/generated/xarray.core.rolling.DataArrayRolling.reduce.html).\n", "\n", - "Tip: The `reduce` method expects a function that can receive and return plain\n", - "arrays (e.g. numpy), as in each of the \"windows\" provided by the rolling iterator. This is in contrast to the `map` method, which expects a function that can receive and return Xarray objects.\n", + "Tip: The `reduce` method expects a function that can receive and return plain arrays (e.g. numpy), as in each of the \"windows\" provided by the rolling iterator. This is in contrast to the `map` method, which expects a function that can receive and return Xarray objects.\n", "\n", "Here's an example function: [`np.ptp`](https://numpy.org/doc/stable/reference/generated/numpy.ptp.html).\n" ] @@ -321,7 +948,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "9ef251aa-ce3c-4318-95ba-470568ebd967", "metadata": {}, @@ -331,7 +957,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "75397b3d-5961-4924-b688-23520b79aae8", "metadata": {}, @@ -359,7 +984,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "d0155b62-d08f-42c6-b467-1af73a7829c0", "metadata": { @@ -397,7 +1021,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "0a23b9a9-076b-472d-b7a6-57083566a32d", "metadata": {}, @@ -443,7 +1066,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "816929d5-6635-4e93-99fc-79b5729c5491", "metadata": { @@ -466,7 +1088,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "f75d2a5f-31d0-4943-b70a-06e7c8a30601", "metadata": { @@ -524,7 +1145,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "b30794c7-1aeb-4e13-b6b4-824f23ac07df", "metadata": { @@ -563,7 +1183,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "0e7cbd7b-da18-48a3-bd29-708d97cc3bb7", "metadata": { @@ -607,7 +1226,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "5dc5c7e7-bc3c-4362-bbd1-6a05801b7c90", "metadata": {}, @@ -632,7 +1250,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "f4e90b49-42e4-411f-9148-bcaf145de26c", "metadata": {}, @@ -654,7 +1271,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "8174aad1-d6e1-4772-bf23-91e363a92c19", "metadata": {}, @@ -739,7 +1355,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "4de2984e-9c28-4ed7-909f-bab47b6eae49", "metadata": {}, @@ -784,7 +1399,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "25fd132c-5436-4af6-b8ad-75269cb45e75", "metadata": { @@ -794,35 +1408,42 @@ "tags": [] }, "source": [ - "START EDITING HERE!\n", - "\n", "---\n", "\n", "## Label space \"windows\" or bins : GroupBy\n", "\n", - "Generalization of `coarsen`: sometimes the windows you want are not regular.\n", + "Sometimes the windows you want are not regularly spaced or even defined by a grid.\n", + "For instance, grouping data by month (which have varying numbers of days) or the results of an image classification.\n", + "The GroupBy functions are essentially a generalization of `coarsen`: \n", "\n", - "- `groupby`: e.g. climatologies, composites; works when \"groups\" are exact: e.g.\n", - " characters or integers; not floats\n", - "- `groupby_bins`: binning operations e.g. histograms\n", - "- `resample`: groupby but specialized for time grouping (so far)\n", + "- `groupby`: divide data into distinct groups, e.g. climatologies, composites. Works when \"groups\" are exact and can be determined using equality (`==`), e.g. characters or integers. Remember that floats are not exact values.\n", + "- `groupby_bins`: Use binning operations, e.g. histograms, to group your data.\n", + "- `resample`: Specialized implementation of GroupBy specifically for time grouping (so far)\n", "\n", - "**tip** Both `groupby_bins` and `resample` are implemented as `GroupBy` with a\n", - "specific way of constructing group labels.\n", + "**hint** Both `groupby_bins` and `resample` are implemented as `GroupBy` with a specific way of constructing group labels.\n", "\n", "### Deconstructing GroupBy\n", "\n", - "Commonly called \"split-apply-combine\".\n", + "The GroupBy workflow is commonly called \"split-apply-combine\".\n", "\n", "1. \"split\" : break dataset into groups\n", - "1. \"apply\" : apply an operation, usually a reduction like `mean`\n", - "1. \"combine\" : concatenate results from apply step along new \"group\" dimension\n", + "1. \"apply\" : apply an operation, for instance a reduction like `mean`\n", + "1. \"combine\" : concatenate results from apply step along a new \"group\" dimension\n", "\n", - "But really there is a first step: \"identifying groups\" also called\n", - "\"factorization\" (or \"binning\"). Usually this is the hard part.\n", + "But really there is a \"hidden\" first step: identifying groups (also called factorization or binning). Usually this is the hard part.\n", "\n", - "So \"identify groups\" → \"split into groups\" → \"apply function\" → \"combine\n", - "results\".\n" + "In reality the workflow is: \"identify groups\" → \"split into groups\" → \"apply function\" → \"combine results\".\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55c5e475", + "metadata": {}, + "outputs": [], + "source": [ + "# recall our earlier DataArray\n", + "da" ] }, { @@ -832,6 +1453,9 @@ "metadata": {}, "outputs": [], "source": [ + "# GroupBy returns an iterator that traverses the specified groups, here by month.\n", + "# Notice that groupby is clever enough for us to leave out the `.dt` before `.month`\n", + "# we would need to specify to access the month data directly (see plot below).\n", "da.groupby(\"time.month\")" ] }, @@ -842,6 +1466,8 @@ "metadata": {}, "outputs": [], "source": [ + "# for each group (e.g. the air temperature in a given month for all the years),\n", + "# compute the mean\n", "da.groupby(\"time.month\").mean()" ] }, @@ -850,7 +1476,9 @@ "id": "7a579539-1634-462c-b4d9-ea558fceadfb", "metadata": {}, "source": [ - "This is how xarray identifies \"groups\" for the monthly climatology computation\n" + "Notice that since we have averaged over all the years for each month, our resulting DataArray no longer has a \"year\" coordinate.\n", + "\n", + "If we want to see how Xarray identifies \"groups\" for the monthly climatology computation, we can plot our input to `groupby`. GroupBy is clever enough to figure out how many values there are an thus how many groups to make.\n" ] }, { @@ -860,7 +1488,7 @@ "metadata": {}, "outputs": [], "source": [ - "da.time.dt.month.plot()" + "da.time.month.plot()" ] }, { @@ -868,7 +1496,7 @@ "id": "a6d21727-4c15-4f13-ae53-61d5f4944554", "metadata": {}, "source": [ - "Similarly for binning,\n" + "Similarly for binning (remember this is useful when the parameter you are binning over is not \"exact\", like a float),\n" ] }, { @@ -899,6 +1527,14 @@ "da.resample(time=\"M\")" ] }, + { + "cell_type": "markdown", + "id": "3763efb3", + "metadata": {}, + "source": [ + "QUSTION (intentionally spelled wrong so a check will catch this block) - I want to explain why resample with time=\"M\" has 24 bins, while groupby over month had 12. But I don't actually know..." + ] + }, { "cell_type": "markdown", "id": "0b2de08d-0b7b-4725-80f3-c94d19d91669", @@ -911,14 +1547,13 @@ "source": [ "### Constructing group labels\n", "\n", - "Xarray uses `pandas.factorize` for `groupby` and `pandas.cut` for\n", - "`groupby_bins`.\n", + "Xarray uses [`pandas.factorize`](https://pandas.pydata.org/docs/reference/api/pandas.factorize.html) for `groupby` and [`pandas.cut`](https://pandas.pydata.org/docs/reference/api/pandas.cut.html) for `groupby_bins`.\n", "\n", - "If the automatic group detection doesn't work for your problem then these\n", - "functions are useful for constructing \"group labels\" in many cases\n", + "#### Functions to construct group labels\n", + "If the automatic group detection doesn't work for your problem then these functions are useful for constructing specific \"group labels\" in many cases\n", "\n", "1. [numpy.digitize](https://numpy.org/doc/stable/reference/generated/numpy.digitize.html)\n", - " (binning)\n", + " for binning\n", "1. [numpy.searchsorted](https://numpy.org/doc/stable/reference/generated/numpy.searchsorted.html)\n", " supports many other data types\n", "1. [pandas.factorize](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.factorize.html)\n", @@ -938,12 +1573,12 @@ "tags": [] }, "source": [ - "#### More commonly useful are [\"datetime components\"](https://docs.xarray.dev/en/stable/user-guide/time-series.html#datetime-components)\n", + "#### [\"Datetime components\"](https://docs.xarray.dev/en/stable/user-guide/time-series.html#datetime-components) for creating groups\n", "\n", "See a full list\n", "[here](https://docs.xarray.dev/en/stable/generated/xarray.core.accessor_dt.DatetimeAccessor.html?highlight=DatetimeAccessor)\n", "\n", - "Accessed using `DataArray.dt.*`\n" + "These can be accessed in a few different ways as illustrated below.\n" ] }, { @@ -993,10 +1628,11 @@ "id": "db7bd7e6-59cd-4b2a-ac37-2ff4d40d9fc8", "metadata": {}, "source": [ + "#### Construct and use custom labels\n", + "\n", "**Demo** Grouping over a custom definition of seasons using numpy.isin.\n", "\n", - "We want to group over 4 seasons: `DJF`, `MAM`, `JJAS`, `ON` - this makes\n", - "physical sense in the Indian Ocean basin\n", + "We want to group over four seasons: `DJF`, `MAM`, `JJAS`, `ON` - this makes physical sense in the Indian Ocean basin.\n", "\n", "Start by extracting months.\n" ] @@ -1027,8 +1663,8 @@ "metadata": {}, "outputs": [], "source": [ - "season = np.full(month.shape, \" \")\n", - "season" + "myseason = np.full(month.shape, \" \")\n", + "myseason" ] }, { @@ -1046,12 +1682,29 @@ "metadata": {}, "outputs": [], "source": [ - "season[np.isin(month, [12, 1, 2])] = \"DJF\"\n", - "season[np.isin(month, [3, 4, 5])] = \"MAM\"\n", - "season[np.isin(month, [6, 7, 8, 9])] = \"JJAS\"\n", - "season[np.isin(month, [10, 11])] = \"ON\"\n", - "season = da.time.copy(data=season)\n", - "season" + "myseason[np.isin(month, [12, 1, 2])] = \"DJF\"\n", + "myseason[np.isin(month, [3, 4, 5])] = \"MAM\"\n", + "myseason[np.isin(month, [6, 7, 8, 9])] = \"JJAS\"\n", + "myseason[np.isin(month, [10, 11])] = \"ON\"" + ] + }, + { + "cell_type": "markdown", + "id": "297f4d2f", + "metadata": {}, + "source": [ + "Turn our new seasonal group array into a DataArray." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6a72a117", + "metadata": {}, + "outputs": [], + "source": [ + "myseason_da = da.time.copy(data=myseason)\n", + "myseason_da" ] }, { @@ -1061,9 +1714,38 @@ "metadata": {}, "outputs": [], "source": [ + "# Why don't we add the season array to the original dataarray (da) and\n", + "# use da.groupby(\"myseason\")? To me, it's confusing to now need two dataarrays\n", + "\n", + "(\n", + " # Calculate climatology\n", + " da.groupby(myseason_da)\n", + " .mean()\n", + " # reindex to get seasons in logical order (not alphabetical order)\n", + " .reindex(time=[\"DJF\", \"MAM\", \"JJAS\", \"ON\"])\n", + " .plot(col=\"time\")\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "cae97fe4", + "metadata": {}, + "source": [ + "Equivalently, we could add our custom seasons to our original DataArray and use groupby there." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36dfd6fd", + "metadata": {}, + "outputs": [], + "source": [ + "da = da.assign({\"myseason\" : ((\"time\"), myseason)})\n", "(\n", " # Calculate climatology\n", - " da.groupby(season)\n", + " da.groupby(\"myseason\")\n", " .mean()\n", " # reindex to get seasons in logical order (not alphabetical order)\n", " .reindex(time=[\"DJF\", \"MAM\", \"JJAS\", \"ON\"])\n", @@ -1081,9 +1763,9 @@ "tags": [] }, "source": [ - "#### `floor`, `ceil` and `round` time\n", + "##### `floor`, `ceil` and `round` on time\n", "\n", - "Basically \"resampling\"\n" + "Additional functionality in the [datetime accessor](https://docs.xarray.dev/en/stable/generated/xarray.core.accessor_dt.DatetimeAccessor.html) allows us to effectively \"resample\" our time data to remove roundoff errors in timestamps.\n" ] }, { @@ -1118,11 +1800,11 @@ "tags": [] }, "source": [ - "#### `strftime` can be extremely useful\n", + "##### `strftime` is another powerful option\n", "\n", "So useful and so unintuitive that it has its own website: https://strftime.org/\n", "\n", - "This example avoids merging \"Feb-29\" and \"Mar-01\" for a daily climatology\n" + "This is useful to avoid merging \"Feb-29\" and \"Mar-01\" for a daily climatology\n" ] }, { @@ -1142,9 +1824,9 @@ "tags": [] }, "source": [ - "### groupby supports `reduce` for custom reductions\n", + "### Custom reductions with GroupBy\n", "\n", - "This applies to `groupby_bins` and `resample`\n" + "Analagous to `rolling`, `reduce` and `map` apply custom reductions to `groupby_bins` and `resample`.\n" ] }, { @@ -1154,7 +1836,7 @@ "metadata": {}, "outputs": [], "source": [ - "(da.groupby(\"time.month\").reduce(np.mean).plot(col=\"month\", col_wrap=4))" + "(da.groupby(\"time.month\").reduce(np.ptp).plot(col=\"month\", col_wrap=4))" ] }, { @@ -1162,9 +1844,7 @@ "id": "7cd7ede5-8e57-4099-ab39-b9d75427f125", "metadata": {}, "source": [ - "**tip** `map` is for functions that expect and return xarray objects (see also\n", - "`Dataset.map`). `reduce` is for functions that expect and return plain arrays\n", - "(like numpy or scipy functions)\n" + "**tip** `map` is for functions that expect and return xarray objects (see also [`Dataset.map`](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.map.html)). `reduce` is for functions that expect and return plain arrays (like Numpy or SciPy functions).\n" ] }, { @@ -1174,14 +1854,15 @@ "tags": [] }, "source": [ - "### GroupBy does not provide construct\n", + "### Adding GroupBy outputs to your DataArray or DataSet\n", + "\n", + "GroupBy does not provide a `construct` method, because all the groups need not be the same \"length\" (e.g. months can have 28, 29, 30, or 31 days).\n", "\n", - "All the groups need not be the same \"length\" (e.g. months can have 28, 29, 30,\n", - "or 31 days)\n", + "#### Instead looping over groupby objects is possible\n", "\n", - "### Instead looping over groupby objects is possible\n", + "Because `groupby` returns an iterator that loops over each group, it is easy to loop over groupby objects.\n", "\n", - "Maybe you want to plot data in each group separately?\n" + "Maybe you want to plot data in each group separately:\n" ] }, { @@ -1200,7 +1881,7 @@ "id": "8017d842-ff79-47ec-928d-43e3cf4e7b66", "metadata": {}, "source": [ - "This is a DataArray contain data for all December days\n" + "This is a DataArray containing data for all December days (because the last printed `label` value is `12`, so the last `group` value is for December)." ] }, { @@ -1233,6 +1914,24 @@ "group.plot.hist()" ] }, + { + "cell_type": "markdown", + "id": "d339c52c", + "metadata": {}, + "source": [ + "Remember, this is example is just to show how you could operate on each group object in a groupby operation. If we wanted to just explore the December (or March) data, we should just filter for it directly:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c4fd9b2", + "metadata": {}, + "outputs": [], + "source": [ + "da[[\"time.month\"==12]].plot.hist()" + ] + }, { "cell_type": "markdown", "id": "32dfe5fd-0e8f-4b69-a3c1-03f73c484b6b", @@ -1240,11 +1939,9 @@ "tags": [] }, "source": [ - "### In most cases, avoid a for loop using `map`\n", + "#### In most cases, avoid a for loop using `map`\n", "\n", - "Apply functions that expect xarray Datasets or DataArrays.\n", - "\n", - "Avoid having to manually combine results using concat\n" + "`map` enables us to apply functions that expect xarray Datasets or DataArrays. This makes it easy to perform calculations on the grouped data, add the results from each group back to the original object, and avoid having to manually combine results (using concat).\n" ] }, { @@ -1256,9 +1953,9 @@ }, "outputs": [], "source": [ - "def iqr(da, dim):\n", + "def iqr(gb_da, dim):\n", " \"\"\"Calculates interquartile range\"\"\"\n", - " return (da.quantile(q=0.75, dim=dim) - da.quantile(q=0.25, dim=dim)).rename(\"iqr\")\n", + " return (gb_da.quantile(q=0.75, dim=dim) - gb_da.quantile(q=0.25, dim=dim)).rename(\"iqr\")\n", "\n", "\n", "da.groupby(\"time.month\").map(iqr, dim=\"time\")" @@ -1282,19 +1979,19 @@ "Xarray provides methods for high-level analysis patterns:\n", "\n", "1. `rolling` :\n", - " [Operate on rolling windows of your data e.g. running mean](https://docs.xarray.dev/en/stable/user-guide/computation.html#rolling-window-operations)\n", + " [Operate on rolling (fixed length, overlapping) windows of your data e.g. running mean](https://docs.xarray.dev/en/stable/user-guide/computation.html#rolling-window-operations)\n", "1. `coarsen` :\n", - " [Downsample your data](https://docs.xarray.dev/en/stable/user-guide/computation.html#coarsen-large-arrays)\n", + " [Operate on blocks (fixed length) of your data (downsample)](https://docs.xarray.dev/en/stable/user-guide/computation.html#coarsen-large-arrays)\n", "1. `groupby` :\n", - " [Bin data in to groups and reduce](https://docs.xarray.dev/en/stable/groupby.html)\n", - "1. `groupby_bins`: GroupBy after discretizing a numeric variable.\n", + " [Parse data into groups (using an exact value) and operate on each one (reduce data)](https://docs.xarray.dev/en/stable/groupby.html)\n", + "1. `groupby_bins`: [GroupBy after discretizing a numeric (non-exact, e.g. float) variable](https://docs.xarray.dev/en/stable/generated/xarray.DataArray.groupby_bins.html)\n", "1. `resample` :\n", " [Groupby specialized for time axes. Either downsample or upsample your data.](https://docs.xarray.dev/en/stable/user-guide/time-series.html#resampling-and-grouped-operations)\n", - "1. `weighted` :\n", - " [Weight your data before reducing](https://docs.xarray.dev/en/stable/user-guide/computation.html#weighted-array-reductions)\n", + "\n", "\n", "## More resources\n", "\n", + "1. [Weight your data before reducing](https://docs.xarray.dev/en/stable/user-guide/computation.html#weighted-array-reductions)\n", "1. More tutorials here: https://tutorial.xarray.dev/\n", "1. Answers to common questions on \"how to do X\" are here:\n", " https://docs.xarray.dev/en/stable/howdoi.html\n" @@ -1302,6 +1999,11 @@ } ], "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, "language_info": { "codemirror_mode": { "name": "ipython", @@ -1311,7 +2013,8 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3" + "pygments_lexer": "ipython3", + "version": "3.10.10" } }, "nbformat": 4, From 644e2037f5d81de923078ea9c24b8a716bb3bd5d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 18:37:26 +0000 Subject: [PATCH 06/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../01-high-level-computation-patterns.ipynb | 600 +----------------- 1 file changed, 15 insertions(+), 585 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 04d70106..373d4c2e 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -98,31 +98,10 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "02a9022f-1503-45a2-b57a-05ebfeb11d16", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkMAAAHFCAYAAADxOP3DAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAACPBElEQVR4nO3dd3wURf8H8M9eTQ9JII2EELqRIgJSpUhVlPYoKiIg6KMCQaQpoBJ4aKJ0BUUxoIigQgT1oQSBSEQUA0gVEEI1MYqQEEi9m98f/HIPR7JzubvAJbnP+/XaF2RnZ3dmb2/zzezsjCKEECAiIiJyUxpXF4CIiIjIlRgMERERkVtjMERERERujcEQERERuTUGQ0REROTWGAwRERGRW2MwRERERG6NwRARERG5NQZDRERE5NYYDFGFt3v3bsTFxeHKlSvF0jp27IiOHTve8TLdCSdOnMC4cePQrFkzVKlSBYGBgWjbti2+/PLLErfPyMjAkCFDULVqVXh5eaF169b47rvvim33zTffYNCgQWjUqBH0ej0URSlxf+fPn0ffvn1Rq1YteHt7w9/fH02bNsU777yDwsLCUtejrMtly+LFi9GgQQMYjUZER0dj6tSpKCgosNrmwoULGD16NDp06IAqVapAURSsWLHCoeMRUfnHYIgqvN27d2Pq1KklBkNLlizBkiVL7nyh7oCtW7fi22+/xb/+9S988cUX+PTTT1G3bl089thjmDZtmtW2eXl56Ny5M7777jssXLgQGzZsQEhICHr06IGkpCSrbRMSErBnzx7ExMSgSZMmqse/du0a/Pz88Prrr2Pjxo1Ys2YN2rVrh9jYWLzwwgulqsPtKJfMjBkz8NJLL6Ffv37YsmULhg8fjpkzZ2LEiBFW2/3+++/49NNPYTAY8NBDDzl0LCKqQARRBffWW28JACI1NdXVRbmj/vrrL2E2m4ut79mzp/Dy8hK5ubmWde+++64AIHbv3m1ZV1BQIGJiYsR9991nld9kMln+P2LECGHvbaJ///5Cp9NZHV/NnSzX33//LTw8PMS///1vq/UzZswQiqKII0eOlHisvXv3CgAiPj7eruMRUcXBliGq0OLi4jB+/HgAQHR0NBRFgaIo2LlzJ4Dij8nOnDkDRVHw1ltv4c0330TNmjXh6emJjh074sSJEygoKMCrr76K8PBw+Pv7o2/fvsjIyCh23LVr16J169bw9vaGj48Punfvjv3799+JKltUrVq1xEdF9913H65fv45//vnHsi4hIQH169dH69atLet0Oh0GDhyIn3/+GRcvXrSs12icuy1Uq1YNGo0GWq3W5rZ3slybN29Gbm4unnnmGav1zzzzDIQQ+Oqrr8rsWERUsfAbTxXas88+i9jYWADA+vXr8eOPP+LHH3/EvffeK8337rvv4ocffsC7776LDz/8EL/99hseeeQRDBs2DH/99Rc++ugjzJkzB9u2bcOzzz5rlXfmzJl48sknERMTg88//xyffPIJrl69ivvvvx9Hjx61WebCwsJSLUIIh87Jjh07UK1aNQQHB1vWHT58GI0bNy62bdG6I0eOOHQsABBCoLCwEJcvX8batWuxYsUKjB07Fjqdzmbe21muko4FAI0aNbJaHxYWhqpVq1rSicj92L5bEZVjERERqFGjBgCgadOmqFmzZqnyValSBV999ZWlBeDvv//G6NGj0aBBA2zYsMGy3W+//YYFCxYgKysLfn5+OH/+PKZMmYKRI0di0aJFlu26du2KunXrYurUqVi7dq3qcc+cOYPo6OhSlXHHjh12d/7+8MMPsXPnTixcuNCqZebSpUsIDAwstn3RukuXLtl1nJu9+eabmDhxIgBAURRMmjQJ06dPL1Xe21muko5lNBrh7e1d4vHK8lhEVLEwGCK39NBDD1k9CrnrrrsAAD179rTarmj9uXPn0LBhQ2zZsgWFhYUYNGiQ1RtTHh4e6NChA3bs2CE9bnh4OPbu3VuqMtavX79U2xXZtGkTRowYgUcffdTSWnYz2dtXjr6ZBQBDhgxBly5d8M8//2D79u146623kJmZicWLFwO40XJkMpms8tzcalTW5br1TTatVmvZz+06B0RUsTEYIrd0a2uEwWCQrs/NzQUA/PnnnwCAFi1alLhfW31NDAYD7rnnnlKVsTR9bops2bIF/fr1Q9euXfHpp58W+8UeFBRUYstHUb+iklpnSis0NBShoaEAgG7duiEgIACvvvoqhg4diqZNm2LlypXF+ukUPQK8HeXS6/VWP8fHx2PIkCEICgpCbm4url+/Di8vr2LHa9asmd3HIqLKgcEQkR2qVq0KAPjyyy8RFRVld/7b8Zhsy5Yt6NOnDzp06IB169ZZAribNWrUCIcOHSq2vmhdw4YNS1Wm0rjvvvsA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SZMmISUlBffcc49lm2eeeQZnzpzBmjVrEB4ejoSEBPTo0QOHDh1C3bp18cMPP+CFF17Am2++iV69emHbtm14/fXXix3r999/x+eff45169ZBq9UCAHr27InAwED897//hb+/P95//3107twZJ06cQGBgILZs2YKBAwdi0aJFuP/++3Hq1Cn8+9//BgBMmTIFX375JebPn481a9bg7rvvRnp6erFgSo2teuXm5qJZs2Z45ZVX4Ofnh2+//RZPP/00atWqhZYtWyItLQ1PPvkk5syZg759++Lq1avYtWsXhBAYN24cjh07hqysLMTHxwMAAgMDS/25EFE55Np5YonkBg8eLHr37l1i2qRJk0T9+vWtZi9/9913hY+PjzCZTEIIITp06CDatWtnla9FixbilVdeEUIIsWnTJqHT6URaWpolPTExUQAQCQkJQoj/za69f/9+IYQQO3bsEADE5cuXrfYbFRUl5s+fb7WuSZMmYsqUKUIIIbZs2SK0Wq04f/68JX3Tpk1Wx1q+fHmxOuXl5QlPT0+xZcuWEs9DSeW5evWqMBgMYs2aNZZ1ly5dEp6enuKll14SQgjx+++/C0VRxMWLF63217lzZzFx4kQhhBCPP/646Nmzp1X6U089Jfz9/S0/T5kyRej1epGRkWFZ99133wk/Pz+Rm5trlbd27dri/fffF0IIcf/994uZM2dapX/yySciLCxMCCHE3LlzRb169UR+fn6J9VZTmnqV5KGHHhJjx44VQgiRkpIiAIgzZ86UuK3suoyPj7c6P0RU/rFliCqsY8eOoXXr1lAUxbKubdu2yM7OxoULF1CjRg0AQOPGja3yhYWFISMjA8CNTseRkZEIDQ21pN933323rbw1atRARESEZV3r1q2ttklJScHvv/8OX19fq/W5ubk4depUqY916tQp5OfnW+0/MDAQ9evXt/y8b98+CCFQr149q7x5eXkICgoCcOP89O3b1yr9vvvuwzfffGO1LioqCtWqVbOqR3Z2tmU/RXJyciz1SElJwd69ezFjxgxLuslkQm5uLq5fv47HHnsMCxYsQK1atdCjRw889NBDeOSRR2z23SpNvUwmE2bPno21a9fi4sWLyMvLQ15eHry9vQEATZo0QefOndGoUSN0794d3bp1w6OPPoqAgADpsYmoYmIwRBWWEMIqECpaB8BqvV6vt9pGURSYzWbVfThKo9FYjl/k5s7Et6bdWk7gxqO0Zs2a4dNPPy227c3Bhi0lHetWZrMZWq0WKSkplkdbRXx8fCz7UTvHNysKIm7ed1hYWIl9aYr6G5nNZkydOhX9+vUrto2HhwciIyNx/PhxJCYmYtu2bRg+fDjeeustJCUlFftM7a3X3LlzMX/+fCxYsACNGjWCt7c3Ro8ejfz8fAA3Os0nJiZi9+7d2Lp1KxYvXozJkyfjp59+QnR0tOqxiahiYjBEFVZMTAzWrVtn9Qt79+7d8PX1RfXq1Uu1jwYNGuDcuXP4888/ERISAgDYu3evNE9Rvx2TyWS1vlq1akhLS7P8nJWVhdTUVKvynjt3Dn/88QfCw8MBAD/++KPVPu69916sXbvW0unYUXXq1IFer8eePXssLWSXL1/GiRMn0KFDBwBA06ZNYTKZkJGRgfvvv7/E/TRo0AA///yz1bpffvnF5vHvvfdepKenQ6fToWbNmqrbHD9+HHXq1FHdj6enJ3r16oVevXphxIgRaNCgAQ4dOoR7771XNU9p6rVr1y707t0bAwcOBHAjgDp58iTuuusuyzaKoqBt27Zo27Yt3njjDURFRSEhIQFjxoyBwWAo9vkTUcXFt8mo3MvMzMSBAweslnPnzmH48OE4f/48YmNj8dtvv2HDhg2YMmUKxowZA42mdJd2165dUbt2bQwePBgHDx7EDz/8gMmTJwMo3mpTJCoqCoqi4JtvvsFff/2F7OxsAMADDzyATz75BLt27cLhw4cxePBgq5aJLl26oH79+hg0aBB+/fVX7Nq1y3KsIk899RSqVq2K3r17Y9euXUhNTUVSUhJeeuklXLhwodTnzMfHB8OGDcP48ePx3Xff4fDhwxgyZIjVealXrx6eeuopDBo0COvXr0dqair27t2LN998E//9738BALGxsfjvf/+LefPm4eTJk3j//fexadMmm61pXbp0QevWrdGnTx9s2bIFZ86cwe7du/Haa69Zgqk33ngDH3/8MeLi4nDkyBEcO3YMa9euxWuvvQYAWLFiBZYvX47Dhw/j9OnT+OSTT+Dp6YmoqCjpsUtTrzp16lhafo4dO4bnn38e6enpln389NNPmDlzJn755RecO3cO69evx19//WUJlmrWrImDBw/i+PHj+Pvvv916OAGiSsFFfZWISmXw4MECQLFl8ODBQgghdu7cKVq0aCEMBoMIDQ0Vr7zyiigoKLDk79Chg6XDcJHevXtb8gshxLFjx0Tbtm2FwWAQDRo0EF9//bUAIDZv3iyEKN6BWgghpk2bJkJDQ4WiKJZ9ZWZmiv79+ws/Pz8RGRkpVqxYYdWBWgghjh8/Ltq1aycMBoOoV6+e2Lx5s1UHaiGESEtLE4MGDRJVq1YVRqNR1KpVSzz33HMiMzOzxHOk1qH76tWrYuDAgcLLy0uEhISIOXPmFDsf+fn54o033hA1a9YUer1ehIaGir59+4qDBw9atlm2bJmoXr268PT0FH369BHTp08XoaGhlvQpU6aIJk2aFCtXVlaWiI2NFeHh4UKv14vIyEjx1FNPiXPnzlm22bx5s2jTpo3w9PQUfn5+4r777hPLli0TQgiRkJAgWrZsKfz8/IS3t7do1aqV2LZtW4nn4Fa26nXp0iXRu3dv4ePjI4KDg8Vrr70mBg0aZOkUffToUdG9e3dRrVo1YTQaRb169cTixYst+8/IyBBdu3YVPj4+AoDYsWOHJY0dqIkqHkWIUnQuIHIjP/zwA9q1a4fff/8dtWvXdnVxbNq5cyc6deqEy5cv35FBF5977jn89ttv2LVr120/VkW0YsUKjB49utyNUE5E6thniNxeQkICfHx8ULduXfz+++946aWX0LZt2woRCN0sIiICjzzySLHBA5319ttvo2vXrvD29samTZuwcuVKLFmypEyPUVn4+PigsLAQHh4eri4KEdmBwRC5vatXr2LChAk4f/48qlatii5dupQ4WnN51bJlS5w8eRLA/96WKks///wz5syZg6tXr6JWrVpYtGgRnn322TI/Tmnt2rULDz74oGp6UR8uVyiazPfWt9iIqHzjYzIiqlBycnJw8eJF1XTZ22lERCVhMERERERuja/WExERkVtjMERERERujcEQERERuTUGQ0REROTWGAwRERGRW2MwRERERG6NwRARERG5NQZDRERE5Nb+D/se0KgIrfEhAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "import numpy as np\n", "import xarray as xr\n", @@ -153,24 +132,16 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "id": "70159772", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[273.416748046875, 273.13104248046875, 275.1137390136719, 278.5469970703125, 283.299072265625, 287.5657043457031, 289.90692138671875, 290.089111328125, 287.41375732421875, 283.6811828613281, 277.9678039550781, 274.35107421875]\n" - ] - } - ], - "source": [ - "months = [1,2,3,4,5,6,7,8,9,10,11,12]\n", + "outputs": [], + "source": [ + "months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]\n", "avg_temps = []\n", "\n", "for mon in months:\n", - " avg = da[da[\"time.month\"]==mon].mean()\n", + " avg = da[da[\"time.month\"] == mon].mean()\n", " avg_temps.append(float(avg.data))\n", "\n", "print(avg_temps)" @@ -188,559 +159,24 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": null, "id": "6f1b23fa", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "273.41675\n", - "273.13104\n", - "275.11374\n", - "278.547\n", - "283.29907\n", - "287.5657\n", - "289.90692\n", - "290.0891\n", - "287.41376\n", - "283.68118\n", - "277.9678\n", - "274.35107\n", - "[[[246.34987 246.38608 246.21518 ... 243.06113 244.08795 245.6467 ]\n", - " [248.8576 248.90733 248.7104 ... 241.52866 243.50865 246.75471]\n", - " [251.57712 251.19661 250.71463 ... 243.39891 246.78462 251.56572]\n", - " ...\n", - " [295.85028 295.24405 295.22684 ... 295.18625 294.65707 294.0485 ]\n", - " [296.5446 296.46982 296.15994 ... 295.35593 295.0812 294.53006]\n", - " [297.15417 297.2383 297.04892 ... 296.01797 295.77554 295.63647]]\n", - "\n", - " [[246.67715 246.40576 245.9484 ... 241.85838 243.0021 244.44383]\n", - " [247.8001 247.75992 247.47757 ... 240.64706 242.26633 245.06662]\n", - " [249.07079 248.57234 247.94254 ... 242.42874 245.33348 249.72273]\n", - " ...\n", - " [295.92886 295.41788 295.16602 ... 294.4443 293.78143 293.18265]\n", - " [296.78754 296.63443 296.15707 ... 294.51166 294.2178 293.70258]\n", - " [297.2889 297.2165 296.85797 ... 295.16058 294.9558 294.87967]]\n", - "\n", - " [[251.03168 250.67987 250.18945 ... 242.19398 243.11484 244.30956]\n", - " [252.97194 252.86617 252.57347 ... 241.61102 243.02509 245.38196]\n", - " [254.46768 254.09142 253.63428 ... 241.67184 244.49664 248.74258]\n", - " ...\n", - " [295.65652 295.24582 295.22464 ... 294.7663 294.16412 293.6729 ]\n", - " [296.70294 296.68756 296.3824 ... 294.85083 294.57034 294.15213]\n", - " [297.38174 297.4631 297.22668 ... 295.3349 295.11124 295.01654]]\n", - "\n", - " ...\n", - "\n", - " [[261.8136 261.21255 260.5036 ... 248.19336 249.06995 250.41624]\n", - " [269.02225 268.92944 268.71478 ... 246.41554 248.16833 251.14897]\n", - " [269.64017 268.7958 268.45483 ... 246.01215 249.6174 254.69598]\n", - " ...\n", - " [299.09723 298.30466 297.9945 ... 299.09454 298.6955 298.29483]\n", - " [299.43155 299.23853 298.7375 ... 299.2589 299.28873 299.0363 ]\n", - " [299.37054 299.42462 299.15607 ... 299.72403 299.66312 299.76233]]\n", - "\n", - " [[253.74484 253.64487 253.49716 ... 242.96066 243.9345 245.14209]\n", - " [259.12967 258.62927 258.19144 ... 241.84921 243.07965 245.46625]\n", - " [261.04227 258.83536 257.51193 ... 242.38234 245.13663 249.52368]\n", - " ...\n", - " [297.8426 297.1406 296.98773 ... 297.96884 297.56888 297.1611 ]\n", - " [298.58783 298.42026 297.96896 ... 298.16412 298.19397 297.9083 ]\n", - " [298.81143 298.8566 298.62103 ... 298.72955 298.7519 298.8189 ]]\n", - "\n", - " [[247.971 248.02118 247.91302 ... 239.7719 241.02383 242.62823]\n", - " [249.73361 250.16037 250.48581 ... 238.78964 240.96469 244.11626]\n", - " [252.0296 251.53136 251.36629 ... 238.07542 241.91293 247.06987]\n", - " ...\n", - " [296.76508 295.97668 295.88922 ... 296.45605 296.09137 295.65756]\n", - " [297.46814 297.38025 297.04428 ... 296.8556 296.84668 296.52133]\n", - " [297.8809 297.9868 297.77554 ... 297.60034 297.5655 297.53763]]]\n" - ] - } - ], + "outputs": [], "source": [ "for label, group in da.groupby(\"time.month\"):\n", " print(group.mean().data)\n", - " \n", + "\n", "avg_temps = da.groupby(\"time.month\").mean()\n", "print(avg_temps.data)" ] }, { "cell_type": "code", - "execution_count": 49, + "execution_count": null, "id": "ad5ee977", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
<xarray.DataArray 'air' (month: 12, lat: 25, lon: 53)>\n",
-       "246.3 246.4 246.2 245.8 245.2 244.6 ... 298.1 298.0 298.0 297.6 297.6 297.5\n",
-       "Coordinates:\n",
-       "  * lat      (lat) float32 75.0 72.5 70.0 67.5 65.0 ... 25.0 22.5 20.0 17.5 15.0\n",
-       "  * lon      (lon) float32 200.0 202.5 205.0 207.5 ... 322.5 325.0 327.5 330.0\n",
-       "  * month    (month) int64 1 2 3 4 5 6 7 8 9 10 11 12\n",
-       "Attributes:\n",
-       "    long_name:     4xDaily Air temperature at sigma level 995\n",
-       "    units:         degK\n",
-       "    precision:     2\n",
-       "    GRIB_id:       11\n",
-       "    GRIB_name:     TMP\n",
-       "    var_desc:      Air temperature\n",
-       "    dataset:       NMC Reanalysis\n",
-       "    level_desc:    Surface\n",
-       "    statistic:     Individual Obs\n",
-       "    parent_stat:   Other\n",
-       "    actual_range:  [185.16 322.1 ]
" - ], - "text/plain": [ - "\n", - "246.3 246.4 246.2 245.8 245.2 244.6 ... 298.1 298.0 298.0 297.6 297.6 297.5\n", - "Coordinates:\n", - " * lat (lat) float32 75.0 72.5 70.0 67.5 65.0 ... 25.0 22.5 20.0 17.5 15.0\n", - " * lon (lon) float32 200.0 202.5 205.0 207.5 ... 322.5 325.0 327.5 330.0\n", - " * month (month) int64 1 2 3 4 5 6 7 8 9 10 11 12\n", - "Attributes:\n", - " long_name: 4xDaily Air temperature at sigma level 995\n", - " units: degK\n", - " precision: 2\n", - " GRIB_id: 11\n", - " GRIB_name: TMP\n", - " var_desc: Air temperature\n", - " dataset: NMC Reanalysis\n", - " level_desc: Surface\n", - " statistic: Individual Obs\n", - " parent_stat: Other\n", - " actual_range: [185.16 322.1 ]" - ] - }, - "execution_count": 49, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "da.groupby(\"time.month\").mean()" ] @@ -1742,7 +1178,7 @@ "metadata": {}, "outputs": [], "source": [ - "da = da.assign({\"myseason\" : ((\"time\"), myseason)})\n", + "da = da.assign({\"myseason\": ((\"time\"), myseason)})\n", "(\n", " # Calculate climatology\n", " da.groupby(\"myseason\")\n", @@ -1929,7 +1365,7 @@ "metadata": {}, "outputs": [], "source": [ - "da[[\"time.month\"==12]].plot.hist()" + "da[[\"time.month\" == 12]].plot.hist()" ] }, { @@ -1999,11 +1435,6 @@ } ], "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", @@ -2013,8 +1444,7 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.10" + "pygments_lexer": "ipython3" } }, "nbformat": 4, From c25b749a289add851f55b9c24b0e59a198cb3039 Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Thu, 29 Jun 2023 16:49:33 -0400 Subject: [PATCH 07/22] finish for loop to groupby example and add note about default behavior of mean() --- .../01-high-level-computation-patterns.ipynb | 55 +++++++++++++++---- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 373d4c2e..f41fa69b 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -114,6 +114,16 @@ "data.plot()" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "edc35fa6", + "metadata": {}, + "outputs": [], + "source": [ + "da" + ] + }, { "cell_type": "markdown", "id": "bd47d199", @@ -127,7 +137,7 @@ "\n", "Consider a common use case. We want to complete some \"task\" for each of \"something\". The \"task\" might be a computation (e.g. mean, median, plot). The \"something\" could be a group of array values (e.g. pixels) or segments of time (e.g. monthly or seasonally).\n", "\n", - "Often, our solution to this type of problem is to write a loop. Say we want the average air temperature for each month:" + "Often, our solution to this type of problem is to write a for loop. Say we want the average air temperature for each month across the entire domain (all lat and lon values):" ] }, { @@ -149,12 +159,10 @@ }, { "cell_type": "markdown", - "id": "c1772b16", + "id": "d3a992bf", "metadata": {}, "source": [ - "Writing a for-loop here is not wrong, but it can quickly become cumbersome if you have a complex function to apply and it will take awhile to compute on a large dataset (you may even run out of memory). Parallelizing the computation would take a lot of additional work.\n", - "\n", - "Xarray's functionality instead allows us to do the same computation in one line of code (plus, the computation is optimized and ready to take advantage of parallel compute resources)!" + "An easy conceptual next step for this example (but still using our for loop) would be to use Xarray's `groupby` function to create an iterator that does the work of grouping our data by month and looping over each month." ] }, { @@ -164,21 +172,33 @@ "metadata": {}, "outputs": [], "source": [ + "avg_temps = []\n", + "\n", "for label, group in da.groupby(\"time.month\"):\n", - " print(group.mean().data)\n", + " avg_temps.append(float(group.mean().data))\n", "\n", - "avg_temps = da.groupby(\"time.month\").mean()\n", - "print(avg_temps.data)" + "print(avg_temps)" + ] + }, + { + "cell_type": "markdown", + "id": "c1772b16", + "metadata": {}, + "source": [ + "Writing a for-loop here is not wrong, but it can quickly become cumbersome if you have a complex function to apply and it will take awhile to compute on a large dataset (you may even run out of memory). Parallelizing the computation would take a lot of additional work.\n", + "\n", + "Xarray's functionality instead allows us to do the same computation in one line of code (plus, the computation is optimized and ready to take advantage of parallel compute resources)!" ] }, { "cell_type": "code", "execution_count": null, - "id": "ad5ee977", + "id": "c53fda41", "metadata": {}, "outputs": [], "source": [ - "da.groupby(\"time.month\").mean()" + "avg_temps = da.groupby(\"time.month\").mean(...) # note the use of the ellipses here\n", + "print(avg_temps.data)" ] }, { @@ -186,7 +206,12 @@ "id": "4f548b71", "metadata": {}, "source": [ - "Read on through this tutorial to learn some of the incredible ways to use Xarray to avoid writing long for-loops and efficiently complete computational analyses on your data." + "Read on through this tutorial to learn some of the incredible ways to use Xarray to avoid writing long for-loops and efficiently complete computational analyses on your data.\n", + "\n", + "```{note}\n", + "By default, `da.mean()` (and `df.mean()`) will calculate the mean by reducing your data over all dimensions (unless you specify otherwise using the `dim` kwarg). The default behavior of `.mean()` on a groupby is to calculate the mean over all dimensions of the variable you are grouping by - but not all the dimensions of the object you are operating on. To compute the mean across all dimensions of a groupby, we must specify `...` for all dimensions (or use the `dim` kwarg to specify which dimensions to reduce by).\n", + "\n", + "```" ] }, { @@ -1435,6 +1460,11 @@ } ], "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, "language_info": { "codemirror_mode": { "name": "ipython", @@ -1444,7 +1474,8 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3" + "pygments_lexer": "ipython3", + "version": "3.10.10" } }, "nbformat": 4, From 3af07255a5b2038c6d0be08e2f6cc049e77053cc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 20:52:01 +0000 Subject: [PATCH 08/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- intermediate/01-high-level-computation-patterns.ipynb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index f41fa69b..c4e1e201 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -197,7 +197,7 @@ "metadata": {}, "outputs": [], "source": [ - "avg_temps = da.groupby(\"time.month\").mean(...) # note the use of the ellipses here\n", + "avg_temps = da.groupby(\"time.month\").mean(...) # note the use of the ellipses here\n", "print(avg_temps.data)" ] }, @@ -1460,11 +1460,6 @@ } ], "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", @@ -1474,8 +1469,7 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.10" + "pygments_lexer": "ipython3" } }, "nbformat": 4, From 88c70640ef15ea5a4bd764e6f361b6e0037409d1 Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Fri, 30 Jun 2023 10:44:53 -0400 Subject: [PATCH 09/22] add pointers to complex for loop to groupby example --- intermediate/01-high-level-computation-patterns.ipynb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index c4e1e201..51d41876 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -202,6 +202,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "4f548b71", "metadata": {}, @@ -211,7 +212,9 @@ "```{note}\n", "By default, `da.mean()` (and `df.mean()`) will calculate the mean by reducing your data over all dimensions (unless you specify otherwise using the `dim` kwarg). The default behavior of `.mean()` on a groupby is to calculate the mean over all dimensions of the variable you are grouping by - but not all the dimensions of the object you are operating on. To compute the mean across all dimensions of a groupby, we must specify `...` for all dimensions (or use the `dim` kwarg to specify which dimensions to reduce by).\n", "\n", - "```" + "```\n", + "\n", + "For a more complex example (identifying flood events - including their start and end date - from rainfall data) illustrating the transition from for loops to high level computation tools, see [this discussion](https://github.com/pydata/xarray/discussions/7641). The [original 40 lines of code](https://github.com/pydata/xarray/discussions/7641#discussion-4976005), including nested for loops, was streamlined into [this ~14 line workflow](https://github.com/pydata/xarray/discussions/7641#discussioncomment-5635306) without any loops." ] }, { From 5247e25d4f54dee31913c71f164496cb8104b2c6 Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Fri, 30 Jun 2023 11:14:45 -0400 Subject: [PATCH 10/22] change exercises and solutions to use directives --- .../01-high-level-computation-patterns.ipynb | 139 +++++++++--------- 1 file changed, 72 insertions(+), 67 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 51d41876..8fd04820 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -202,7 +202,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "4f548b71", "metadata": {}, @@ -459,7 +458,7 @@ "source": [ "#### Storing the outputs from `rolling` operations with `construct`\n", "\n", - "In the above examples, we plotted the outputs of our rolling operations. Xarray makes it easy to store the outputs from `rolling` directly into the DataArray using the [`construct`](https://docs.xarray.dev/en/stable/generated/xarray.core.rolling.DataArrayRolling.construct.html#xarray.core.rolling.DataArrayRolling.construct) method." + "In the above examples, we plotted the outputs of our rolling operations. Xarray makes it easy to integrate the outputs from `rolling` directly into the DataArray using the [`construct`](https://docs.xarray.dev/en/stable/generated/xarray.core.rolling.DataArrayRolling.construct.html#xarray.core.rolling.DataArrayRolling.construct) method." ] }, { @@ -486,33 +485,37 @@ }, { "cell_type": "markdown", - "id": "0a23b9a9-076b-472d-b7a6-57083566a32d", + "id": "5d7562a7", "metadata": {}, "source": [ - "**Exercise** Calculate the 5 point running mean in time and add it to your DataArray using `rolling.construct`" + "Because `.construct()` only returns a \"view\" (not a copy) of the original data object, in order to \"save\" the results you would need to rewrite the original object: `simple = simple.rolling(time=5, center=True).construct(\"window\")`." ] }, { + "attachments": {}, "cell_type": "markdown", - "id": "fcd19bd6-0564-4b0e-b3cb-b5c31f88b4da", + "id": "0a23b9a9-076b-472d-b7a6-57083566a32d", "metadata": {}, "source": [ - "**Answer**\n" + "```{exercise}\n", + "Calculate the 5 point running mean in time and add it to your DataArray using `rolling.construct`\n", + "```" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "80c80728-440a-43d9-957c-65bf111e710d", - "metadata": { - "jupyter": { - "source_hidden": true - }, - "tags": [] - }, - "outputs": [], + "attachments": {}, + "cell_type": "markdown", + "id": "fcd19bd6-0564-4b0e-b3cb-b5c31f88b4da", + "metadata": {}, "source": [ - "(simple.rolling(time=5, center=True).construct(\"window\").mean(\"window\"))" + "```{solution}\n", + ":class: dropdown\n", + "\n", + "```python\n", + "simple.rolling(time=5, center=True).construct(\"window\").mean(\"window\")\n", + "```\n", + "\n", + "```\n" ] }, { @@ -609,6 +612,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "b30794c7-1aeb-4e13-b6b4-824f23ac07df", "metadata": { @@ -620,30 +624,25 @@ "source": [ "#### Coarsen supports `reduce` for custom reductions\n", "\n", - "**Exercise** Use `coarsen.reduce` to apply `np.ptp` in 5x5 (lat x lon) point blocks to `data`" + "```{exercise}\n", + "Use `coarsen.reduce` to apply `np.ptp` in 5x5 (lat x lon) point blocks to `data`\n", + "```" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "4f88d113-86d1-4158-b4e7-f54f98af3c0c", "metadata": {}, "source": [ - "**Answer**\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "446c773f-59e4-4b7a-86bd-fd7d40e223e6", - "metadata": { - "jupyter": { - "source_hidden": true - }, - "tags": [] - }, - "outputs": [], - "source": [ - "(data.coarsen(lat=5, lon=5, boundary=\"trim\").reduce(np.mean).plot())" + "```{solution}\n", + ":class: dropdown\n", + "\n", + "```python\n", + "data.coarsen(lat=5, lon=5, boundary=\"trim\").reduce(np.mean).plot()\n", + "```\n", + "\n", + "```\n" ] }, { @@ -761,37 +760,27 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "db43eb72-fb9f-4d6a-aab3-4617c9c41ab1", "metadata": {}, "source": [ - "**Exercise** Reshape the `time` dimension of the DataArray `monthly` to year x\n", - "month and visualize the seasonal cycle for two years at 250°E\n" + "```{exercise}\n", + "Reshape the `time` dimension of the DataArray `monthly` to year x\n", + "month and visualize the seasonal cycle for two years at 250°E\n", + "```\n" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "b668514e-b40c-4c64-98bf-4579747ae6ab", "metadata": {}, "source": [ - "**Answer**\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d01c6873-dc67-4d8b-928a-ad4f834429fa", - "metadata": { - "jupyter": { - "source_hidden": true - }, - "slideshow": { - "slide_type": "subslide" - }, - "tags": [] - }, - "outputs": [], - "source": [ + "```{solution}\n", + ":class: dropdown\n", + "\n", + "```python\n", "# splits time dimension into year x month\n", "year_month = monthly.coarsen(time=12).construct(time=(\"year\", \"month\"))\n", "\n", @@ -815,35 +804,45 @@ "year_month[\"year\"] = [2013, 2014]\n", "\n", "# seasonal cycle for two years\n", - "year_month.sel(lon=250).plot.contourf(col=\"year\", x=\"month\", y=\"lat\")" + "year_month.sel(lon=250).plot.contourf(col=\"year\", x=\"month\", y=\"lat\")\n", + "```\n", + "\n", + "```\n" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "4de2984e-9c28-4ed7-909f-bab47b6eae49", "metadata": {}, "source": [ "This exercise came up during a live lecture.\n", "\n", - "**Exercise** Calculate the rolling 4 month average, averaged across years.\n", - "\n", - "**Answer**\n", - "\n", - "1. We first reshape using `coarsen.construct` to add `year` as a new dimension.\n", - "2. Apply `rolling` on the month dimension.\n", - "3. It turns out that `roll.mean([\"year\", \"month\"])` doesn't work. So we use `roll.construct` to get a DataArray with a new dimension `window` and then take the mean over `window` and `year`\n" + "```{exercise}\n", + "Calculate the rolling 4 month average, averaged across years.\n", + "```\n" ] }, { - "cell_type": "code", - "execution_count": null, + "attachments": {}, + "cell_type": "markdown", "id": "9d907b2b-c9c8-41cb-8af0-756d5c21ffef", "metadata": {}, - "outputs": [], "source": [ + "```{solution}\n", + ":class: dropdown\n", + "\n", + "1. We first reshape using `coarsen.construct` to add `year` as a new dimension.\n", + "2. Apply `rolling` on the month dimension.\n", + "3. It turns out that `roll.mean([\"year\", \"month\"])` doesn't work. So we use `roll.construct` to get a DataArray with a new dimension `window` and then take the mean over `window` and `year`\n", + "\n", + "```python\n", "reshaped = months.coarsen(time=12).construct(time=(\"year\", \"month\"))\n", "roll = reshaped.rolling(month=4, center=True)\n", - "roll.construct(\"window\").mean([\"window\", \"year\"])" + "roll.construct(\"window\").mean([\"window\", \"year\"])\n", + "```\n", + "\n", + "```" ] }, { @@ -1463,6 +1462,11 @@ } ], "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, "language_info": { "codemirror_mode": { "name": "ipython", @@ -1472,7 +1476,8 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3" + "pygments_lexer": "ipython3", + "version": "3.10.10" } }, "nbformat": 4, From fbca164e9a1f3c5637bf09a64e425cbbfa26db33 Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Fri, 30 Jun 2023 11:17:33 -0400 Subject: [PATCH 11/22] fix spelling typo --- intermediate/01-high-level-computation-patterns.ipynb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 8fd04820..c99fad97 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -1281,6 +1281,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "b5724cbb-dd2a-4dc5-9118-7c7dd294755f", "metadata": { @@ -1289,7 +1290,7 @@ "source": [ "### Custom reductions with GroupBy\n", "\n", - "Analagous to `rolling`, `reduce` and `map` apply custom reductions to `groupby_bins` and `resample`.\n" + "Analogous to `rolling`, `reduce` and `map` apply custom reductions to `groupby_bins` and `resample`.\n" ] }, { From 8e839226b56910d4802913c59d5cc292b5a62dd1 Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Fri, 30 Jun 2023 11:55:11 -0400 Subject: [PATCH 12/22] debug code cells --- .../01-high-level-computation-patterns.ipynb | 44 ++----------------- 1 file changed, 3 insertions(+), 41 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index c99fad97..b24198a4 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -492,7 +492,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "0a23b9a9-076b-472d-b7a6-57083566a32d", "metadata": {}, @@ -503,7 +502,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "fcd19bd6-0564-4b0e-b3cb-b5c31f88b4da", "metadata": {}, @@ -612,7 +610,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "b30794c7-1aeb-4e13-b6b4-824f23ac07df", "metadata": { @@ -630,7 +627,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "4f88d113-86d1-4158-b4e7-f54f98af3c0c", "metadata": {}, @@ -760,7 +756,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "db43eb72-fb9f-4d6a-aab3-4617c9c41ab1", "metadata": {}, @@ -772,7 +767,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "b668514e-b40c-4c64-98bf-4579747ae6ab", "metadata": {}, @@ -811,7 +805,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "4de2984e-9c28-4ed7-909f-bab47b6eae49", "metadata": {}, @@ -824,7 +817,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "9d907b2b-c9c8-41cb-8af0-756d5c21ffef", "metadata": {}, @@ -951,7 +943,7 @@ "metadata": {}, "outputs": [], "source": [ - "da.time.month.plot()" + "da.time.dt.month.plot()" ] }, { @@ -1177,9 +1169,6 @@ "metadata": {}, "outputs": [], "source": [ - "# Why don't we add the season array to the original dataarray (da) and\n", - "# use da.groupby(\"myseason\")? To me, it's confusing to now need two dataarrays\n", - "\n", "(\n", " # Calculate climatology\n", " da.groupby(myseason_da)\n", @@ -1190,32 +1179,6 @@ ")" ] }, - { - "cell_type": "markdown", - "id": "cae97fe4", - "metadata": {}, - "source": [ - "Equivalently, we could add our custom seasons to our original DataArray and use groupby there." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "36dfd6fd", - "metadata": {}, - "outputs": [], - "source": [ - "da = da.assign({\"myseason\": ((\"time\"), myseason)})\n", - "(\n", - " # Calculate climatology\n", - " da.groupby(\"myseason\")\n", - " .mean()\n", - " # reindex to get seasons in logical order (not alphabetical order)\n", - " .reindex(time=[\"DJF\", \"MAM\", \"JJAS\", \"ON\"])\n", - " .plot(col=\"time\")\n", - ")" - ] - }, { "cell_type": "markdown", "id": "7f3097b9-987f-4f9b-9c82-d5c6e879a862", @@ -1281,7 +1244,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "b5724cbb-dd2a-4dc5-9118-7c7dd294755f", "metadata": { @@ -1383,7 +1345,7 @@ "id": "d339c52c", "metadata": {}, "source": [ - "Remember, this is example is just to show how you could operate on each group object in a groupby operation. If we wanted to just explore the December (or March) data, we should just filter for it directly:" + "Remember, this example is just to show how you could operate on each group object in a groupby operation. If we wanted to just explore the December (or March) data, we should just filter for it directly:" ] }, { @@ -1393,7 +1355,7 @@ "metadata": {}, "outputs": [], "source": [ - "da[[\"time.month\" == 12]].plot.hist()" + "da[da[\"time.month\"] == 12].plot.hist()" ] }, { From d75298c2048057659e134f449dc5c3ce07227277 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 30 Jun 2023 15:56:07 +0000 Subject: [PATCH 13/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- intermediate/01-high-level-computation-patterns.ipynb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index b24198a4..5e5dbe31 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -1425,11 +1425,6 @@ } ], "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, "language_info": { "codemirror_mode": { "name": "ipython", @@ -1439,8 +1434,7 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.10" + "pygments_lexer": "ipython3" } }, "nbformat": 4, From bd23865972e011179bff4345b4649c2b0bfd09f6 Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Mon, 3 Jul 2023 14:58:40 -0400 Subject: [PATCH 14/22] add another tic to solution syntax --- .../01-high-level-computation-patterns.ipynb | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 5e5dbe31..f4157f8b 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -502,18 +502,19 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "fcd19bd6-0564-4b0e-b3cb-b5c31f88b4da", "metadata": {}, "source": [ - "```{solution}\n", + "````{solution}\n", ":class: dropdown\n", "\n", "```python\n", "simple.rolling(time=5, center=True).construct(\"window\").mean(\"window\")\n", "```\n", "\n", - "```\n" + "````\n" ] }, { @@ -627,18 +628,19 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "4f88d113-86d1-4158-b4e7-f54f98af3c0c", "metadata": {}, "source": [ - "```{solution}\n", + "````{solution}\n", ":class: dropdown\n", "\n", "```python\n", "data.coarsen(lat=5, lon=5, boundary=\"trim\").reduce(np.mean).plot()\n", "```\n", "\n", - "```\n" + "````\n" ] }, { @@ -767,11 +769,12 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "b668514e-b40c-4c64-98bf-4579747ae6ab", "metadata": {}, "source": [ - "```{solution}\n", + "````{solution}\n", ":class: dropdown\n", "\n", "```python\n", @@ -801,7 +804,7 @@ "year_month.sel(lon=250).plot.contourf(col=\"year\", x=\"month\", y=\"lat\")\n", "```\n", "\n", - "```\n" + "````\n" ] }, { @@ -817,11 +820,12 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "9d907b2b-c9c8-41cb-8af0-756d5c21ffef", "metadata": {}, "source": [ - "```{solution}\n", + "````{solution}\n", ":class: dropdown\n", "\n", "1. We first reshape using `coarsen.construct` to add `year` as a new dimension.\n", @@ -834,7 +838,7 @@ "roll.construct(\"window\").mean([\"window\", \"year\"])\n", "```\n", "\n", - "```" + "````" ] }, { From 7dabe1a3ed027a0286bb75c7f9b91583f9a878b0 Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Mon, 3 Jul 2023 15:04:24 -0400 Subject: [PATCH 15/22] add generalize input for solution --- intermediate/01-high-level-computation-patterns.ipynb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index f4157f8b..45cf803c 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -507,7 +507,7 @@ "id": "fcd19bd6-0564-4b0e-b3cb-b5c31f88b4da", "metadata": {}, "source": [ - "````{solution}\n", + "````{solution} generalize\n", ":class: dropdown\n", "\n", "```python\n", @@ -633,7 +633,7 @@ "id": "4f88d113-86d1-4158-b4e7-f54f98af3c0c", "metadata": {}, "source": [ - "````{solution}\n", + "````{solution} generalize\n", ":class: dropdown\n", "\n", "```python\n", @@ -774,7 +774,7 @@ "id": "b668514e-b40c-4c64-98bf-4579747ae6ab", "metadata": {}, "source": [ - "````{solution}\n", + "````{solution} generalize\n", ":class: dropdown\n", "\n", "```python\n", @@ -825,7 +825,7 @@ "id": "9d907b2b-c9c8-41cb-8af0-756d5c21ffef", "metadata": {}, "source": [ - "````{solution}\n", + "````{solution} generalize\n", ":class: dropdown\n", "\n", "1. We first reshape using `coarsen.construct` to add `year` as a new dimension.\n", From 641225527ff9e33e47fa0f2b627732ebed307312 Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Wed, 5 Jul 2023 15:43:51 -0400 Subject: [PATCH 16/22] apply suggestions from review --- .../01-high-level-computation-patterns.ipynb | 159 +++++++++++------- 1 file changed, 102 insertions(+), 57 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 45cf803c..3039db7b 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -1,6 +1,7 @@ { "cells": [ { + "attachments": {}, "cell_type": "markdown", "id": "a349a0a5-eeb3-410a-b5d1-f472a8ca14b2", "metadata": { @@ -10,14 +11,14 @@ "tags": [] }, "source": [ - "# Utilizing computational patterns\n", - "\n", - "From https://toolz.readthedocs.io/en/latest/control.html\n", + "# Computational Patterns\n", "\n", "Often when writing code we repeat certain patterns, whether we realize it or not.\n", "If you have learned to write list comprehensions, you are taking advantage of a \"control pattern\".\n", "Often, these patterns are so common that many packages have built in functions to implement them.\n", "\n", + "Quoting the [toolz documentation](https://toolz.readthedocs.io/en/latest/control.html):\n", + "\n", "> The Toolz library contains dozens of patterns like map and groupby. Learning a\n", "> core set (maybe a dozen) covers the vast majority of common programming tasks\n", "> often done by hand. A rich vocabulary of core control functions conveys the\n", @@ -27,10 +28,11 @@ "> - You make fewer errors in rote coding\n", "> - You can depend on well tested and benchmarked implementations\n", "\n", - "The same is true for xarray" + "The same is true for xarray." ] }, { + "attachments": {}, "cell_type": "markdown", "id": "623d5170-f32d-4643-9a59-c54768ee7185", "metadata": { @@ -43,11 +45,13 @@ "## Motivation / Learning goals\n", "\n", "- Learn what high-level computational patterns are available in Xarray\n", - "- Identify when you are using a high-level computational pattern\n", - "- Implement that pattern using built-in Xarray functionality" + "- Identify when you are re-implementing a high-level computational pattern\n", + "- Implement that pattern using built-in Xarray functionality\n", + "- Understand the difference between `map` and `reduce`" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "5f0cda65-cfaa-42ed-bd4d-f290c1e98bb3", "metadata": { @@ -70,17 +74,21 @@ "analysis tasks.\n", "\n", "1. `rolling` :\n", - " [Operate on rolling windows of your data e.g. running mean](https://docs.xarray.dev/en/stable/user-guide/computation.html#rolling-window-operations)\n", + " [Operate on rolling windows of your data e.g. running mean.](https://docs.xarray.dev/en/stable/user-guide/computation.html#rolling-window-operations)\n", "1. `coarsen` :\n", - " [Downsample your data](https://docs.xarray.dev/en/stable/user-guide/computation.html#coarsen-large-arrays)\n", + " [Downsample your data.](https://docs.xarray.dev/en/stable/user-guide/computation.html#coarsen-large-arrays)\n", "1. `groupby` :\n", - " [Bin data in to groups and reduce](https://docs.xarray.dev/en/stable/groupby.html)\n", + " [Bin data in to groups and reduce.](https://docs.xarray.dev/en/stable/groupby.html)\n", "1. `groupby_bins`: GroupBy after discretizing a numeric variable.\n", "1. `resample` :\n", " [GroupBy specialized for time axes. Either downsample or upsample your data.](https://docs.xarray.dev/en/stable/user-guide/time-series.html#resampling-and-grouped-operations)\n", + "1. `weighted` : [Weight your data before reducing.](https://docs.xarray.dev/en/stable/user-guide/computation.html#weighted-array-reductions), as in [this tutorial](https://tutorial.xarray.dev/fundamentals/03.4_weighted.html).\n", "\n", "\n", - "Note: the documentation links in this tutorial point to the DataArray implementations of each function, but they are also available for DataSet objects.\n" + "\n", + "```{Note}\n", + "the documentation links in this tutorial point to the DataArray implementations of each function, but they are also available for DataSet objects.\n", + "```\n" ] }, { @@ -111,7 +119,7 @@ "da = xr.tutorial.load_dataset(\"air_temperature\", engine=\"netcdf4\").air\n", "monthly = da.resample(time=\"M\").mean()\n", "data = da.isel(time=0)\n", - "data.plot()" + "data.plot();" ] }, { @@ -125,6 +133,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "bd47d199", "metadata": {}, @@ -202,10 +211,13 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "4f548b71", "metadata": {}, "source": [ + "Here we showed an example for computing a mean over a certain period of time (months), which ultimately uses the `GroupBy` function. The transition from loops to a built-in function is similar for `rolling` and `coarsen` over windows of values (e.g. pixels) instead of \"groups\" of time.\n", + "\n", "Read on through this tutorial to learn some of the incredible ways to use Xarray to avoid writing long for-loops and efficiently complete computational analyses on your data.\n", "\n", "```{note}\n", @@ -360,7 +372,7 @@ "metadata": {}, "outputs": [], "source": [ - "data.plot()" + "data.plot();" ] }, { @@ -378,10 +390,11 @@ "metadata": {}, "outputs": [], "source": [ - "data.rolling(lat=5, lon=5, center=True).mean().plot()" + "data.rolling(lat=5, lon=5, center=True).mean().plot();" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "b88c116e-ad63-4fea-81a7-bcabc194dee5", "metadata": { @@ -395,7 +408,9 @@ "\n", "In some cases, we may want to apply a sliding window function using rolling that is not built in to Xarray. In these cases we can still leverage the sliding windows of rolling and apply our own function with [`reduce`](https://docs.xarray.dev/en/stable/generated/xarray.core.rolling.DataArrayRolling.reduce.html).\n", "\n", - "Tip: The `reduce` method expects a function that can receive and return plain arrays (e.g. numpy), as in each of the \"windows\" provided by the rolling iterator. This is in contrast to the `map` method, which expects a function that can receive and return Xarray objects.\n", + "```{tip}\n", + " The `reduce` method expects a function that can receive and return plain arrays (e.g. numpy), as in each of the \"windows\" provided by the rolling iterator. This is in contrast to the `map` method, which expects a function that can receive and return Xarray objects.\n", + "```\n", "\n", "Here's an example function: [`np.ptp`](https://numpy.org/doc/stable/reference/generated/numpy.ptp.html).\n" ] @@ -407,29 +422,26 @@ "metadata": {}, "outputs": [], "source": [ - "data.rolling(lat=5, lon=5, center=True).reduce(np.ptp).plot()" + "data.rolling(lat=5, lon=5, center=True).reduce(np.ptp).plot();" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "9ef251aa-ce3c-4318-95ba-470568ebd967", "metadata": {}, "source": [ - "**Exercise** Calculate the rolling mean in 5 point bins along both latitude and longitude using\n", - "[`rolling(**kwargs).reduce`](https://docs.xarray.dev/en/stable/generated/xarray.core.rolling.DataArrayRolling.reduce.html)\n" + "```{exercise} generalize\n", + "\n", + "Calculate the rolling mean in 5 point bins along both latitude and longitude using\n", + "[`rolling(**kwargs).reduce`](https://docs.xarray.dev/en/stable/generated/xarray.core.rolling.DataArrayRolling.reduce.html)\n", + "\n", + "```" ] }, { + "attachments": {}, "cell_type": "markdown", - "id": "75397b3d-5961-4924-b688-23520b79aae8", - "metadata": {}, - "source": [ - "**Answer**\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, "id": "a36cbf94-ed41-42c6-8ccf-9278927d395b", "metadata": { "jupyter": { @@ -440,13 +452,20 @@ }, "tags": [] }, - "outputs": [], "source": [ + "````{solution} generalize\n", + ":class: dropdown\n", + "\n", + "```python\n", "# exactly equivalent to data.rolling(...).mean()\n", - "data.rolling(lat=5, lon=5, center=True).reduce(np.mean).plot()" + "data.rolling(lat=5, lon=5, center=True).reduce(np.mean).plot();\n", + "```\n", + "\n", + "````" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "d0155b62-d08f-42c6-b467-1af73a7829c0", "metadata": { @@ -456,7 +475,7 @@ "tags": [] }, "source": [ - "#### Storing the outputs from `rolling` operations with `construct`\n", + "#### View outputs from `rolling` operations with `construct`\n", "\n", "In the above examples, we plotted the outputs of our rolling operations. Xarray makes it easy to integrate the outputs from `rolling` directly into the DataArray using the [`construct`](https://docs.xarray.dev/en/stable/generated/xarray.core.rolling.DataArrayRolling.construct.html#xarray.core.rolling.DataArrayRolling.construct) method." ] @@ -484,11 +503,12 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "5d7562a7", "metadata": {}, "source": [ - "Because `.construct()` only returns a \"view\" (not a copy) of the original data object, in order to \"save\" the results you would need to rewrite the original object: `simple = simple.rolling(time=5, center=True).construct(\"window\")`." + "Because `.construct()` only returns a \"view\" (not a copy) of the original data object (i.e. it is not operating \"in-place\"), in order to \"save\" the results you would need to rewrite the original object: `simple = simple.rolling(time=5, center=True).construct(\"window\")`." ] }, { @@ -518,16 +538,15 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "c81f95b1-e1c3-4b28-8b7f-9519c9316e3f", "metadata": {}, "source": [ "`construct` is clever.\n", "\n", - "1. It constructs a **view** of the original array, so it is memory-efficient.\n", - " but you didn't have to know that.\n", - "1. It does something sensible for dask arrays (though generally you want big\n", - " chunksizes for the dimension you're sliding along).\n", + "1. It constructs a [**view**](https://numpy.org/doc/stable/user/basics.copies.html) of the original array, so it is memory-efficient.\n", + "1. It does something sensible for dask arrays (though generally you want big chunksizes for the dimension you're sliding along).\n", "1. It also works with rolling along multiple dimensions!\n" ] }, @@ -587,7 +606,7 @@ "metadata": {}, "outputs": [], "source": [ - "data.plot()" + "data.plot();" ] }, { @@ -607,7 +626,7 @@ "metadata": {}, "outputs": [], "source": [ - "(data.coarsen(lat=5, lon=5, boundary=\"trim\").mean().plot())" + "(data.coarsen(lat=5, lon=5, boundary=\"trim\").mean().plot();)" ] }, { @@ -637,7 +656,7 @@ ":class: dropdown\n", "\n", "```python\n", - "data.coarsen(lat=5, lon=5, boundary=\"trim\").reduce(np.mean).plot()\n", + "data.coarsen(lat=5, lon=5, boundary=\"trim\").reduce(np.mean).plot();\n", "```\n", "\n", "````\n" @@ -752,9 +771,19 @@ " .pad(time=(1, 0), constant_values=-1)\n", " .coarsen(time=12)\n", " .construct(time=(\"year\", \"month\"))\n", - ")\n", - "\n", - "# NOTE: check output of this cell (why is the first value of time nan instead of -1?)" + ")\n" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "fbe916a3", + "metadata": {}, + "source": [ + "```{note}\n", + "The value specified in `.pad` only applies the `fill_value` to the array, not to coordinate variables.\n", + "This is why the first value of time in the above example is NaN and not -1.\n", + "```" ] }, { @@ -858,6 +887,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "25fd132c-5436-4af6-b8ad-75269cb45e75", "metadata": { @@ -879,7 +909,11 @@ "- `groupby_bins`: Use binning operations, e.g. histograms, to group your data.\n", "- `resample`: Specialized implementation of GroupBy specifically for time grouping (so far)\n", "\n", - "**hint** Both `groupby_bins` and `resample` are implemented as `GroupBy` with a specific way of constructing group labels.\n", + "\n", + "```{hint}\n", + " Both `groupby_bins` and `resample` are implemented as `GroupBy` with a specific way of constructing group labels.\n", + "```\n", + "\n", "\n", "### Deconstructing GroupBy\n", "\n", @@ -914,7 +948,7 @@ "source": [ "# GroupBy returns an iterator that traverses the specified groups, here by month.\n", "# Notice that groupby is clever enough for us to leave out the `.dt` before `.month`\n", - "# we would need to specify to access the month data directly (see plot below).\n", + "# we would need to specify to access the month data directly, as in `da.time.dt.month`.\n", "da.groupby(\"time.month\")" ] }, @@ -947,7 +981,7 @@ "metadata": {}, "outputs": [], "source": [ - "da.time.dt.month.plot()" + "da[\"time.month\"].plot();" ] }, { @@ -987,11 +1021,16 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "3763efb3", "metadata": {}, "source": [ - "QUSTION (intentionally spelled wrong so a check will catch this block) - I want to explain why resample with time=\"M\" has 24 bins, while groupby over month had 12. But I don't actually know..." + "```{note}\n", + "\n", + "Resampling is changing the frequency of our data to monthly (for two years), so we have 24 bins. GroupBy is taking the average across all data in the same month for two years, so we have 12 bins.\n", + "\n", + "```" ] }, { @@ -1083,13 +1122,14 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "db7bd7e6-59cd-4b2a-ac37-2ff4d40d9fc8", "metadata": {}, "source": [ "#### Construct and use custom labels\n", "\n", - "**Demo** Grouping over a custom definition of seasons using numpy.isin.\n", + "##### Custom seasons with `numpy.isin`.\n", "\n", "We want to group over four seasons: `DJF`, `MAM`, `JJAS`, `ON` - this makes physical sense in the Indian Ocean basin.\n", "\n", @@ -1270,14 +1310,18 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "7cd7ede5-8e57-4099-ab39-b9d75427f125", "metadata": {}, "source": [ - "**tip** `map` is for functions that expect and return xarray objects (see also [`Dataset.map`](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.map.html)). `reduce` is for functions that expect and return plain arrays (like Numpy or SciPy functions).\n" + "```{tip}\n", + " `map` is for functions that expect and return xarray objects (see also [`Dataset.map`](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.map.html)). `reduce` is for functions that expect and return plain arrays (like Numpy or SciPy functions).\n", + "```\n" ] }, { + "attachments": {}, "cell_type": "markdown", "id": "ed904da0-fb85-4432-8083-ef644209affd", "metadata": { @@ -1290,7 +1334,7 @@ "\n", "#### Instead looping over groupby objects is possible\n", "\n", - "Because `groupby` returns an iterator that loops over each group, it is easy to loop over groupby objects.\n", + "Because `groupby` returns an iterator that loops over each group, it is easy to loop over groupby objects. You can also iterate over `rolling` and `coarsen` objects, however this approach is usually quite slow.\n", "\n", "Maybe you want to plot data in each group separately:\n" ] @@ -1400,6 +1444,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "id": "a37ec2e3-24a0-4306-abe0-a6c04933bd88", "metadata": {}, @@ -1409,22 +1454,22 @@ "Xarray provides methods for high-level analysis patterns:\n", "\n", "1. `rolling` :\n", - " [Operate on rolling (fixed length, overlapping) windows of your data e.g. running mean](https://docs.xarray.dev/en/stable/user-guide/computation.html#rolling-window-operations)\n", + " [Operate on rolling (fixed length, overlapping) windows of your data e.g. running mean.](https://docs.xarray.dev/en/stable/user-guide/computation.html#rolling-window-operations)\n", "1. `coarsen` :\n", - " [Operate on blocks (fixed length) of your data (downsample)](https://docs.xarray.dev/en/stable/user-guide/computation.html#coarsen-large-arrays)\n", + " [Operate on blocks (fixed length) of your data (downsample).](https://docs.xarray.dev/en/stable/user-guide/computation.html#coarsen-large-arrays)\n", "1. `groupby` :\n", - " [Parse data into groups (using an exact value) and operate on each one (reduce data)](https://docs.xarray.dev/en/stable/groupby.html)\n", - "1. `groupby_bins`: [GroupBy after discretizing a numeric (non-exact, e.g. float) variable](https://docs.xarray.dev/en/stable/generated/xarray.DataArray.groupby_bins.html)\n", + " [Parse data into groups (using an exact value) and operate on each one (reduce data).](https://docs.xarray.dev/en/stable/groupby.html)\n", + "1. `groupby_bins`: [GroupBy after discretizing a numeric (non-exact, e.g. float) variable.](https://docs.xarray.dev/en/stable/generated/xarray.DataArray.groupby_bins.html)\n", "1. `resample` :\n", " [Groupby specialized for time axes. Either downsample or upsample your data.](https://docs.xarray.dev/en/stable/user-guide/time-series.html#resampling-and-grouped-operations)\n", "\n", + "Xarray also provides features to make using those patterns easy:\n", "\n", - "## More resources\n", - "\n", - "1. [Weight your data before reducing](https://docs.xarray.dev/en/stable/user-guide/computation.html#weighted-array-reductions)\n", - "1. More tutorials here: https://tutorial.xarray.dev/\n", - "1. Answers to common questions on \"how to do X\" are here:\n", - " https://docs.xarray.dev/en/stable/howdoi.html\n" + "1. [Weight your data before reducing.](https://docs.xarray.dev/en/stable/user-guide/computation.html#weighted-array-reductions)\n", + "1. Iterate over the operators (`rolling`, `coarsen`, `groupby`, `groupby_bins`, `resample`).\n", + "1. Apply functions that accept numpy-like arrays with `reduce`.\n", + "1. Reshape to a new xarray object with `.construct` (`rolling`, `coarsen` only).\n", + "1. Apply functions that accept xarray objects with `map` (`groupby`, `groupby_bins`, `resample` only).\n" ] } ], From 68a9f2b18508564a0cabfaf54a0e025209c9c8c2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Jul 2023 19:44:13 +0000 Subject: [PATCH 17/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- intermediate/01-high-level-computation-patterns.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 3039db7b..97e0db91 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -771,7 +771,7 @@ " .pad(time=(1, 0), constant_values=-1)\n", " .coarsen(time=12)\n", " .construct(time=(\"year\", \"month\"))\n", - ")\n" + ")" ] }, { From 2086172b885c2348bca9147630a07e5d156a77ea Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Thu, 6 Jul 2023 09:27:24 -0400 Subject: [PATCH 18/22] fix typo causing CI failure --- intermediate/01-high-level-computation-patterns.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 97e0db91..5d4f8833 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -626,7 +626,7 @@ "metadata": {}, "outputs": [], "source": [ - "(data.coarsen(lat=5, lon=5, boundary=\"trim\").mean().plot();)" + "(data.coarsen(lat=5, lon=5, boundary=\"trim\").mean().plot())" ] }, { From 38d5bd2fe1a48dd4500d6725e57cd289c234e58b Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Thu, 6 Jul 2023 14:58:34 -0400 Subject: [PATCH 19/22] remove link with failing anchor --- intermediate/01-high-level-computation-patterns.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 5d4f8833..5baca58e 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -225,7 +225,7 @@ "\n", "```\n", "\n", - "For a more complex example (identifying flood events - including their start and end date - from rainfall data) illustrating the transition from for loops to high level computation tools, see [this discussion](https://github.com/pydata/xarray/discussions/7641). The [original 40 lines of code](https://github.com/pydata/xarray/discussions/7641#discussion-4976005), including nested for loops, was streamlined into [this ~14 line workflow](https://github.com/pydata/xarray/discussions/7641#discussioncomment-5635306) without any loops." + "For a more complex example (identifying flood events - including their start and end date - from rainfall data) illustrating the transition from for loops to high level computation tools, see [this discussion](https://github.com/pydata/xarray/discussions/7641). The [original 40 lines of code](https://github.com/pydata/xarray/discussions/7641#discussion-4976005), including nested for loops, was streamlined into a ~15 line workflow without any loops." ] }, { From ded5fbc6c71bf9abd230ebf46ea085f478a5d13a Mon Sep 17 00:00:00 2001 From: Jessica Scheick Date: Thu, 6 Jul 2023 15:10:01 -0400 Subject: [PATCH 20/22] add :label: to solution directive --- .../01-high-level-computation-patterns.ipynb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index 5baca58e..d79ea096 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -453,7 +453,8 @@ "tags": [] }, "source": [ - "````{solution} generalize\n", + "````{solution} \n", + ":label: generalize\n", ":class: dropdown\n", "\n", "```python\n", @@ -527,7 +528,8 @@ "id": "fcd19bd6-0564-4b0e-b3cb-b5c31f88b4da", "metadata": {}, "source": [ - "````{solution} generalize\n", + "````{solution}\n", + ":label: generalize\n", ":class: dropdown\n", "\n", "```python\n", @@ -652,7 +654,8 @@ "id": "4f88d113-86d1-4158-b4e7-f54f98af3c0c", "metadata": {}, "source": [ - "````{solution} generalize\n", + "````{solution}\n", + ":label: generalize\n", ":class: dropdown\n", "\n", "```python\n", @@ -803,7 +806,8 @@ "id": "b668514e-b40c-4c64-98bf-4579747ae6ab", "metadata": {}, "source": [ - "````{solution} generalize\n", + "````{solution}\n", + ":label: generalize\n", ":class: dropdown\n", "\n", "```python\n", @@ -854,7 +858,8 @@ "id": "9d907b2b-c9c8-41cb-8af0-756d5c21ffef", "metadata": {}, "source": [ - "````{solution} generalize\n", + "````{solution}\n", + ":label: generalize\n", ":class: dropdown\n", "\n", "1. We first reshape using `coarsen.construct` to add `year` as a new dimension.\n", From 05c8d9c3689e9444b7746929e30674956dc626f3 Mon Sep 17 00:00:00 2001 From: dcherian Date: Thu, 6 Jul 2023 15:21:53 -0600 Subject: [PATCH 21/22] fix? --- .../01-high-level-computation-patterns.ipynb | 71 ++++++------------- 1 file changed, 22 insertions(+), 49 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index d79ea096..cca30a9f 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -1,7 +1,6 @@ { "cells": [ { - "attachments": {}, "cell_type": "markdown", "id": "a349a0a5-eeb3-410a-b5d1-f472a8ca14b2", "metadata": { @@ -32,7 +31,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "623d5170-f32d-4643-9a59-c54768ee7185", "metadata": { @@ -51,7 +49,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "5f0cda65-cfaa-42ed-bd4d-f290c1e98bb3", "metadata": { @@ -133,12 +130,11 @@ ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "bd47d199", + "id": "6ff7edbb-ab97-4bf0-881a-0627230565f3", "metadata": {}, "source": [ - "---\n", + "***\n", "\n", "### Identifying high-level computation patterns\n", "\n", @@ -211,7 +207,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "4f548b71", "metadata": {}, @@ -238,7 +233,7 @@ "tags": [] }, "source": [ - "---\n", + "***\n", "\n", "### Concept refresher: \"index space\" vs \"label space\"\n" ] @@ -316,7 +311,7 @@ "id": "e9b80381-8a0d-4833-97fa-687bf693ca5a", "metadata": {}, "source": [ - "---\n", + "***\n", "\n", "## Xarray provides high-level patterns in both \"index space\" and \"label space\"\n", "\n", @@ -394,7 +389,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "b88c116e-ad63-4fea-81a7-bcabc194dee5", "metadata": { @@ -426,7 +420,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "9ef251aa-ce3c-4318-95ba-470568ebd967", "metadata": {}, @@ -440,7 +433,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "a36cbf94-ed41-42c6-8ccf-9278927d395b", "metadata": { @@ -466,7 +458,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "d0155b62-d08f-42c6-b467-1af73a7829c0", "metadata": { @@ -504,7 +495,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "5d7562a7", "metadata": {}, @@ -514,22 +504,15 @@ }, { "cell_type": "markdown", - "id": "0a23b9a9-076b-472d-b7a6-57083566a32d", + "id": "5743ba77-def9-4b6f-a777-87014311253d", "metadata": {}, "source": [ "```{exercise}\n", - "Calculate the 5 point running mean in time and add it to your DataArray using `rolling.construct`\n", - "```" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "fcd19bd6-0564-4b0e-b3cb-b5c31f88b4da", - "metadata": {}, - "source": [ - "````{solution}\n", ":label: generalize\n", + "Calculate the 5 point running mean in time and add it to your DataArray using `rolling.construct`\n", + "```\n", + "\n", + "````{solution} generalize\n", ":class: dropdown\n", "\n", "```python\n", @@ -540,7 +523,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "c81f95b1-e1c3-4b28-8b7f-9519c9316e3f", "metadata": {}, @@ -584,7 +566,7 @@ "tags": [] }, "source": [ - "---\n", + "***\n", "\n", "### Block windows of fixed length: `coarsen`\n", "\n", @@ -644,18 +626,17 @@ "#### Coarsen supports `reduce` for custom reductions\n", "\n", "```{exercise}\n", + ":label: coarsen-reduce\n", "Use `coarsen.reduce` to apply `np.ptp` in 5x5 (lat x lon) point blocks to `data`\n", "```" ] }, { - "attachments": {}, "cell_type": "markdown", "id": "4f88d113-86d1-4158-b4e7-f54f98af3c0c", "metadata": {}, "source": [ - "````{solution}\n", - ":label: generalize\n", + "````{solution} coarsen-reduce\n", ":class: dropdown\n", "\n", "```python\n", @@ -778,7 +759,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "fbe916a3", "metadata": {}, @@ -795,19 +775,18 @@ "metadata": {}, "source": [ "```{exercise}\n", + ":label: reshape\n", "Reshape the `time` dimension of the DataArray `monthly` to year x\n", "month and visualize the seasonal cycle for two years at 250°E\n", "```\n" ] }, { - "attachments": {}, "cell_type": "markdown", "id": "b668514e-b40c-4c64-98bf-4579747ae6ab", "metadata": {}, "source": [ - "````{solution}\n", - ":label: generalize\n", + "````{solution} reshape\n", ":class: dropdown\n", "\n", "```python\n", @@ -848,18 +827,17 @@ "This exercise came up during a live lecture.\n", "\n", "```{exercise}\n", + ":label: rolling\n", "Calculate the rolling 4 month average, averaged across years.\n", "```\n" ] }, { - "attachments": {}, "cell_type": "markdown", "id": "9d907b2b-c9c8-41cb-8af0-756d5c21ffef", "metadata": {}, "source": [ - "````{solution}\n", - ":label: generalize\n", + "````{solution} rolling\n", ":class: dropdown\n", "\n", "1. We first reshape using `coarsen.construct` to add `year` as a new dimension.\n", @@ -892,7 +870,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "25fd132c-5436-4af6-b8ad-75269cb45e75", "metadata": { @@ -902,7 +879,7 @@ "tags": [] }, "source": [ - "---\n", + "***\n", "\n", "## Label space \"windows\" or bins : GroupBy\n", "\n", @@ -1026,7 +1003,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "3763efb3", "metadata": {}, @@ -1063,7 +1039,8 @@ " supports characters, strings etc.\n", "1. [pandas.cut](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.cut.html)\n", " for binning\n", - "1. [DataArray.isin](https://docs.xarray.dev/en/stable/generated/xarray.DataArray.isin.html)\n" + "1. [DataArray.isin](https://docs.xarray.dev/en/stable/generated/xarray.DataArray.isin.html)\n", + "1. [scipy.ndimage.label](https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.label.html)" ] }, { @@ -1127,7 +1104,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "db7bd7e6-59cd-4b2a-ac37-2ff4d40d9fc8", "metadata": {}, @@ -1315,7 +1291,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "7cd7ede5-8e57-4099-ab39-b9d75427f125", "metadata": {}, @@ -1326,7 +1301,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "ed904da0-fb85-4432-8083-ef644209affd", "metadata": { @@ -1445,11 +1419,10 @@ "id": "3122e22a-77f0-402f-baf6-111821973250", "metadata": {}, "source": [ - "---\n" + "***" ] }, { - "attachments": {}, "cell_type": "markdown", "id": "a37ec2e3-24a0-4306-abe0-a6c04933bd88", "metadata": {}, @@ -1467,10 +1440,10 @@ "1. `groupby_bins`: [GroupBy after discretizing a numeric (non-exact, e.g. float) variable.](https://docs.xarray.dev/en/stable/generated/xarray.DataArray.groupby_bins.html)\n", "1. `resample` :\n", " [Groupby specialized for time axes. Either downsample or upsample your data.](https://docs.xarray.dev/en/stable/user-guide/time-series.html#resampling-and-grouped-operations)\n", + "1. [Weight your data before reducing.](https://docs.xarray.dev/en/stable/user-guide/computation.html#weighted-array-reductions)\n", "\n", - "Xarray also provides features to make using those patterns easy:\n", + "Xarray also provides a consistent interface to make using those patterns easy:\n", "\n", - "1. [Weight your data before reducing.](https://docs.xarray.dev/en/stable/user-guide/computation.html#weighted-array-reductions)\n", "1. Iterate over the operators (`rolling`, `coarsen`, `groupby`, `groupby_bins`, `resample`).\n", "1. Apply functions that accept numpy-like arrays with `reduce`.\n", "1. Reshape to a new xarray object with `.construct` (`rolling`, `coarsen` only).\n", From 2aa328ed1106997ef7e8d10dc35aa593837ee0ad Mon Sep 17 00:00:00 2001 From: dcherian Date: Thu, 6 Jul 2023 15:25:17 -0600 Subject: [PATCH 22/22] more fix --- .../01-high-level-computation-patterns.ipynb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/intermediate/01-high-level-computation-patterns.ipynb b/intermediate/01-high-level-computation-patterns.ipynb index cca30a9f..ba429693 100644 --- a/intermediate/01-high-level-computation-patterns.ipynb +++ b/intermediate/01-high-level-computation-patterns.ipynb @@ -424,7 +424,8 @@ "id": "9ef251aa-ce3c-4318-95ba-470568ebd967", "metadata": {}, "source": [ - "```{exercise} generalize\n", + "```{exercise}\n", + ":label: rolling-reduce\n", "\n", "Calculate the rolling mean in 5 point bins along both latitude and longitude using\n", "[`rolling(**kwargs).reduce`](https://docs.xarray.dev/en/stable/generated/xarray.core.rolling.DataArrayRolling.reduce.html)\n", @@ -436,17 +437,13 @@ "cell_type": "markdown", "id": "a36cbf94-ed41-42c6-8ccf-9278927d395b", "metadata": { - "jupyter": { - "source_hidden": true - }, "slideshow": { "slide_type": "subslide" }, "tags": [] }, "source": [ - "````{solution} \n", - ":label: generalize\n", + "````{solution} rolling-reduce\n", ":class: dropdown\n", "\n", "```python\n", @@ -508,11 +505,11 @@ "metadata": {}, "source": [ "```{exercise}\n", - ":label: generalize\n", + ":label: rolling-construct\n", "Calculate the 5 point running mean in time and add it to your DataArray using `rolling.construct`\n", "```\n", "\n", - "````{solution} generalize\n", + "````{solution} rolling-construct\n", ":class: dropdown\n", "\n", "```python\n",