From a2a062e6bf24d0cbe9383d469711057a4b86eb81 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Fri, 28 Jul 2023 22:16:39 +0000 Subject: [PATCH 01/10] Split plotting material from numpy notebook --- lessons/python/4_arrays.ipynb | 347 +----------------------- lessons/python/4a_plots.ipynb | 484 ++++++++++++++++++++++++++++++++++ 2 files changed, 497 insertions(+), 334 deletions(-) create mode 100644 lessons/python/4a_plots.ipynb diff --git a/lessons/python/4_arrays.ipynb b/lessons/python/4_arrays.ipynb index 548ea11..e383e6e 100644 --- a/lessons/python/4_arrays.ipynb +++ b/lessons/python/4_arrays.ipynb @@ -42,12 +42,11 @@ "source": [ "## NumPy\n", "\n", - "NumPy is short for \"Numerical Python\".\n", - "It is a fundamental Python package for scientific computing.\n", - "NumPy uses a high-performance data structure known as the *n-dimensional array* or *ndarray*, a multi-dimensional array object, for efficient computation of arrays and matrices.\n", - "You should use this library if you want to do numerical computation on a large number of data values.\n", + "NumPy is a fundamental Python package for scientific computing.\n", + "NumPy uses a high-performance data structure known as the *n-dimensional array* or *ndarray*, a multi-dimensional array object, for efficient computation.\n", + "Use NumPy arrays if you want to do numerical computation on a large number of data values.\n", "\n", - "We can load NumPy with:" + "Import NumPy with its common nickname, `np`." ] }, { @@ -56,7 +55,14 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy" + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exploring topographic data" ] }, { @@ -223,53 +229,6 @@ "whos" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plotting\n", - " \n", - "Rasters are arrays of values. In the case of DEMs, those values\n", - "are elevations. It's very hard to get a good sense of what this landscape\n", - "looks like by looking directly at the data. This information is better\n", - "conveyed through plots and graphics.\n", - "\n", - "Data visualization deserves an entire lecture (or course) of its own,\n", - "but we can explore a few features of Python's `matplotlib` library here.\n", - "While there is no \"official\" plotting library in Python, this package is\n", - "the de facto standard.\n", - "\n", - "We start by importing the `pyplot` module from the library `matplotlib`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from matplotlib import pyplot as plt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use the function `imshow` within `matplotlib.pyplot` to display arrays as a 2D\n", - "image. \n", - "\n", - "Try to display the 2D `topo` array" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.imshow(topo)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -441,27 +400,6 @@ "print(topo[int(topo.shape[0] / 2), int(topo.shape[1] / 2)])" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " ## Plotting smaller regions \n", - " \n", - " Use the function `imshow` from `matplotlib.pyplot` to make one plot showing the northern half of the region and another plot showing the southern half. Use the pyplot show() function to display the current figure and start a new one. Render the figures in the notebook using `%matplotlib inline`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.figure()\n", - "plt.imshow(topo[: int(topo.shape[0] / 2), :])\n", - "plt.figure()\n", - "plt.imshow(topo[int(topo.shape[0] / 2) :, :])" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -612,265 +550,6 @@ "source": [ "print(topo.mean(axis=1))" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plotting, take two\n", - " \n", - "It's hard to get a sense of how the topography changes across the\n", - "landscape from these big tables of numbers. A simpler way to display\n", - "this information is with line plots.\n", - "\n", - "We are again going to use the `matplotlib` package for data\n", - "visualization. Since we imported the `matplotlib.pyplot` library once\n", - "already, those tools are available and can be called within Python. As a\n", - "review, though, we are going to write every step needed to load and plot\n", - "the data.\n", - "\n", - "We use the function `plot` to create two basic line plots of the\n", - "topography:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.plot(topo[-1, :], \"r--\")\n", - "plt.title(\"Topographic profile, southern edge\")\n", - "plt.ylabel(\"Elevation (m)\")\n", - "plt.xlabel(\"<-- West East -->\")\n", - "plt.show()\n", - "\n", - "# Northern edge\n", - "plt.plot(topo[0, :])\n", - "plt.title(\"Topographic profile, northern edge\")\n", - "plt.ylabel(\"Elevation\")\n", - "plt.xlabel(\"<-- West East-->\")\n", - "plt.show()\n", - "\n", - "# Can you plot the southern edge\n", - "plt.plot(topo[0, -1])\n", - "plt.title(\"Topographic profile, southern edge\")\n", - "plt.ylabel(\"Elevation\")\n", - "plt.xlabel(\"<-- West East-->\")\n", - "plt.show()\n", - "\n", - "# And the mean elevation changes with longitude (E-W)?\n", - "plt.plot(topo.mean(axis=0))\n", - "plt.title(\"Topographic profile, mean elevations\")\n", - "plt.ylabel(\"Elevation\")\n", - "plt.xlabel(\"<-- West East-->\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To better compare these profiles, we can plot them as separate lines in\n", - "a single figure. Note that this is the default configuration in python 3. Unless a new figure instance is opened or the existing figure is shown (`plt.show`), all subsequent calls to `plt.plot` will use the same axes (until it reaches `plt.show()`). The argument `label=` holds the label that will appear in the legend.Try it" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.plot(topo[0, :], label=\"North\")\n", - "plt.plot(topo[-1, :], \"r--\", label=\"South\")\n", - "plt.plot(topo[int(len(topo) / 2), :], \"g:\", linewidth=3, label=\"Mid\")\n", - "\n", - "plt.title(\"Topographic profiles\")\n", - "plt.ylabel(\"Elevation (m)\")\n", - "plt.xlabel(\"<-- West East -->\")\n", - "plt.legend(loc=\"lower left\")\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " ## Practice your skills: Make your own plots \n", - "\n", - " Create a single plot showing how the maximum (`numpy.max()`),\n", - " minimum (`numpy.min()`), and mean (`numpy.mean()`) elevation changes with longitude. Label the axes and include a title for each of the plots (Hint: use `axis=0`). Create a legend." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.plot(topo.min(axis=0), label=\"Min\")\n", - "plt.plot(topo.max(axis=0), \"r--\", label=\"Max\")\n", - "plt.plot(topo.mean(axis=0), \"g:\", linewidth=3, label=\"mean\")\n", - "\n", - "plt.title(\"Topographic profiles\")\n", - "plt.ylabel(\"Elevation (m)\")\n", - "plt.xlabel(\"<-- West East -->\")\n", - "plt.legend(loc=\"lower left\")\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " ## Practice your skills: Subplots \n", - "\n", - " We often want to arrange separate plots in layouts with multiple rows\n", - " and columns. The script below uses subplots to show the elevation\n", - " profile at the western edge, the mid longitude, and eastern edge of\n", - " the region. Subplots can be a little weird because they require the\n", - " axes to be defined before plotting. Type (don't copy-past!) the code\n", - " below to get a sense of how it works.\n", - " \n", - "This script uses a number of new commands. The function `plt.figure()`\n", - "creates a space into which we will place the three plots. The parameter\n", - "`figsize` tells Python how big to make this space. Each subplot is\n", - "placed into the figure using the `subplot` command. The `subplot`\n", - "command takes 3 parameters: the first denotes the total number of rows\n", - "of subplots in the figure, the second is the total number of columns of\n", - "subplots in the figure, and the final parameters identifies the\n", - "position of the subplot in the grid. The axes of each subplot are\n", - "called with different variable (axes1, axes2, axes3, axes4). Once a\n", - "subplot is created, the axes can be labeled using the `set_xlabel()`\n", - "(or `set_ylabel()`) method. `plt.show()` is called after the entire\n", - "figure is set up." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "\n", - "topo = np.loadtxt(\"data/topo.asc\", delimiter=\",\")\n", - "\n", - "fig = plt.figure(figsize=(16.0, 3.0))\n", - "\n", - "axes1 = fig.add_subplot(1, 3, 1)\n", - "axes2 = fig.add_subplot(1, 3, 2)\n", - "axes3 = fig.add_subplot(1, 3, 3)\n", - "\n", - "axes1.plot(topo[:, 0])\n", - "axes1.set_ylim([2500, 3900])\n", - "axes1.set_ylabel(\"Elevation (m)\")\n", - "axes1.set_xlabel(\"<-- N S -->\")\n", - "axes1.set_title(\"West\")\n", - "\n", - "axes2.plot(topo[:, int(len(topo) / 2)])\n", - "axes2.set_ylim([2500, 3900])\n", - "axes2.set_xlabel(\"<-- N S -->\")\n", - "axes2.set_title(\"Mid\")\n", - "\n", - "axes3.plot(topo[:, -1])\n", - "axes3.set_ylim([2500, 3900])\n", - "axes3.set_xlabel(\"<--N S -->\")\n", - "axes3.set_title(\"East\")\n", - "\n", - "plt.show(fig)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Subplots of DEMs (Takehome) \n", - " \n", - "Make a 4x2 grid of subplots that use the function `imshow` to display each quarter of the dataset (ie. split down the middle in both x and y) in the left column. Plot corresponding profiles going from east to west in center of the image (cfr. Mid) in the right column.\n", - "\n", - "When plotting the DEMs (left column)\n", - "* Don't label axes or add a colorbar. It can be tricky to do this with subplots.\n", - "* To set the range of colors for one subplot, include the arguments `vmin` and `vmax` in `imshow` like this:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "vmin = topo.min()\n", - "vmax = topo.max()\n", - "plt.imshow(topo, vmin=vmin, vmax=vmax)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(16.0, 3.0))\n", - "\n", - "axes1 = fig.add_subplot(4, 2, 1)\n", - "axes2 = fig.add_subplot(4, 2, 2)\n", - "axes3 = fig.add_subplot(4, 2, 3)\n", - "axes4 = fig.add_subplot(4, 2, 4)\n", - "axes5 = fig.add_subplot(4, 2, 5)\n", - "axes6 = fig.add_subplot(4, 2, 6)\n", - "axes7 = fig.add_subplot(4, 2, 7)\n", - "axes8 = fig.add_subplot(4, 2, 8)\n", - "\n", - "vmin = topo.min()\n", - "vmax = topo.max()\n", - "\n", - "topo1 = topo[: int(topo.shape[0] / 2), : int(topo.shape[1] / 2)]\n", - "axes1.imshow(topo1, vmin=vmin, vmax=vmax)\n", - "axes1.axes.get_xaxis().set_visible(False)\n", - "axes1.axes.get_yaxis().set_visible(False)\n", - "axes2.plot(topo1[:, -1])\n", - "axes2.set_ylim([vmin, vmax])\n", - "axes2.set_xlabel(\"<--N S -->\")\n", - "axes2.set_title(\"North-West\")\n", - "\n", - "\n", - "topo2 = topo[int(topo.shape[0] / 2) :, : int(topo.shape[1] / 2)]\n", - "axes3.imshow(topo2, vmin=vmin, vmax=vmax)\n", - "axes3.axes.get_xaxis().set_visible(False)\n", - "axes3.axes.get_yaxis().set_visible(False)\n", - "axes4.plot(topo2[:, -1])\n", - "axes4.set_ylim([vmin, vmax])\n", - "axes4.set_xlabel(\"<--N S -->\")\n", - "axes4.set_title(\"South-West\")\n", - "\n", - "topo3 = topo[: int(topo.shape[0] / 2), int(topo.shape[1] / 2) :]\n", - "axes5.imshow(topo3, vmin=vmin, vmax=vmax)\n", - "axes5.axes.get_xaxis().set_visible(False)\n", - "axes5.axes.get_yaxis().set_visible(False)\n", - "axes6.plot(topo3[:, -1])\n", - "axes6.set_ylim([vmin, vmax])\n", - "axes6.set_xlabel(\"<--N S -->\")\n", - "axes6.set_title(\"North-East\")\n", - "\n", - "topo4 = topo[int(topo.shape[0] / 2) :, int(topo.shape[1] / 2) :]\n", - "axes7.imshow(topo4, vmin=vmin, vmax=vmax)\n", - "axes7.axes.get_xaxis().set_visible(False)\n", - "axes7.axes.get_yaxis().set_visible(False)\n", - "axes8.plot(topo4[:, -1])\n", - "axes8.set_ylim([vmin, vmax])\n", - "axes8.set_xlabel(\"<--N S -->\")\n", - "axes8.set_title(\"South-East\")\n", - "\n", - "plt.show()\n", - "\n", - "ax = plt.imshow(topo)\n", - "ax.axes.get_xaxis().set_visible(False)\n", - "ax.axes.get_yaxis().set_visible(False)" - ] } ], "metadata": { @@ -890,7 +569,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.0" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/lessons/python/4a_plots.ipynb b/lessons/python/4a_plots.ipynb new file mode 100644 index 0000000..03c1530 --- /dev/null +++ b/lessons/python/4a_plots.ipynb @@ -0,0 +1,484 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "# Plotting\n", + "\n", + "The digital elevation model data used in the Arrays lesson is handy for exploring visualization techniques in Python. \n", + "\n", + "In this lesson,\n", + "we'll explore how to work with arrays using topographic data\n", + "in the form of a digital elevation model.\n", + "\n", + "We'll use a library called NumPy." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## matplotlib\n", + "\n", + "NumPy is short for \"Numerical Python\".\n", + "It is a fundamental Python package for scientific computing.\n", + "NumPy uses a high-performance data structure known as the *n-dimensional array* or *ndarray*, a multi-dimensional array object, for efficient computation of arrays and matrices.\n", + "You should use this library if you want to do numerical computation on a large number of data values.\n", + "\n", + "We can load NumPy with:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once we’ve loaded the library, we can\n", + "call a function inside that library to read a data file included in the Ivy coursefiles:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "numpy.loadtxt(\"data/topo.asc\", delimiter=\",\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The expression `numpy.loadtxt(...)` is a function call\n", + "that asks Python to run the function `loadtxt` that belongs to the `numpy` library.\n", + "This dotted notation, with the syntax `thing.component`, is used\n", + "everywhere in Python to refer to parts of things.\n", + "\n", + "The function call to `numpy.loadtxt` has two parameters:\n", + "the name of the file we want to read,\n", + "and the delimiter that separates values on a line.\n", + "Both need to be character strings (or strings, for short)\n", + "so we write them in quotes.\n", + "\n", + "Within the Jupyter Notebook, pressing Shift+Enter runs the\n", + "commands in the selected cell. Because we haven't told iPython what to\n", + "do with the output of `numpy.loadtxt`, the notebook just displays it on\n", + "the screen. In this case, that output is the data we just loaded. By\n", + "default, only a few rows and columns are shown (with `...` to omit\n", + "elements when displaying big arrays).\n", + "\n", + "Our call to `numpy.loadtxt` read the file but didn’t save it to memory.\n", + "In order to access the data, we need to assign the values to a variable.\n", + "A variable is just a name that refers to an object. Python’s variables\n", + "must begin with a letter and are case sensitive. We can assign a\n", + "variable name to an object using `=`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "topo = numpy.loadtxt(\"data/topo.asc\", delimiter=\",\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "topo.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This tells us that `topo` has 500 rows and 500 columns. The file\n", + "we imported contains elevation data (in meters, 2 degree spacing) for an\n", + "area along the Front Range of Colorado, so the area that this array represents is 1 km x 1 km.\n", + "\n", + "The object of\n", + "type `numpy.ndarray` that the variable `topo` is assigned to contains the values of the array\n", + "as well as some extra information about the array. These are the members or attributes of the object, and they\n", + "describe the data in the same way an adjective describes a noun. The\n", + "command `topo.shape` calls the `shape` attribute of the object with the variable name\n", + "`topo` that describes its dimensions. We use the same dotted notation\n", + "for the attributes of objects that we use for the functions inside\n", + "libraries because they have the same part-and-whole relationship." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plotting\n", + " \n", + "Rasters are arrays of values. In the case of DEMs, those values\n", + "are elevations. It's very hard to get a good sense of what this landscape\n", + "looks like by looking directly at the data. This information is better\n", + "conveyed through plots and graphics.\n", + "\n", + "Data visualization deserves an entire lecture (or course) of its own,\n", + "but we can explore a few features of Python's `matplotlib` library here.\n", + "While there is no \"official\" plotting library in Python, this package is\n", + "the de facto standard.\n", + "\n", + "We start by importing the `pyplot` module from the library `matplotlib`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use the function `imshow` within `matplotlib.pyplot` to display arrays as a 2D\n", + "image. \n", + "\n", + "Try to display the 2D `topo` array" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.imshow(topo)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " ## Plotting smaller regions \n", + " \n", + " Use the function `imshow` from `matplotlib.pyplot` to make one plot showing the northern half of the region and another plot showing the southern half. Use the pyplot show() function to display the current figure and start a new one. Render the figures in the notebook using `%matplotlib inline`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.figure()\n", + "plt.imshow(topo[: int(topo.shape[0] / 2), :])\n", + "plt.figure()\n", + "plt.imshow(topo[int(topo.shape[0] / 2) :, :])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plotting, take two\n", + " \n", + "It's hard to get a sense of how the topography changes across the\n", + "landscape from these big tables of numbers. A simpler way to display\n", + "this information is with line plots.\n", + "\n", + "We are again going to use the `matplotlib` package for data\n", + "visualization. Since we imported the `matplotlib.pyplot` library once\n", + "already, those tools are available and can be called within Python. As a\n", + "review, though, we are going to write every step needed to load and plot\n", + "the data.\n", + "\n", + "We use the function `plot` to create two basic line plots of the\n", + "topography:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.plot(topo[-1, :], \"r--\")\n", + "plt.title(\"Topographic profile, southern edge\")\n", + "plt.ylabel(\"Elevation (m)\")\n", + "plt.xlabel(\"<-- West East -->\")\n", + "plt.show()\n", + "\n", + "# Northern edge\n", + "plt.plot(topo[0, :])\n", + "plt.title(\"Topographic profile, northern edge\")\n", + "plt.ylabel(\"Elevation\")\n", + "plt.xlabel(\"<-- West East-->\")\n", + "plt.show()\n", + "\n", + "# Can you plot the southern edge\n", + "plt.plot(topo[0, -1])\n", + "plt.title(\"Topographic profile, southern edge\")\n", + "plt.ylabel(\"Elevation\")\n", + "plt.xlabel(\"<-- West East-->\")\n", + "plt.show()\n", + "\n", + "# And the mean elevation changes with longitude (E-W)?\n", + "plt.plot(topo.mean(axis=0))\n", + "plt.title(\"Topographic profile, mean elevations\")\n", + "plt.ylabel(\"Elevation\")\n", + "plt.xlabel(\"<-- West East-->\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To better compare these profiles, we can plot them as separate lines in\n", + "a single figure. Note that this is the default configuration in python 3. Unless a new figure instance is opened or the existing figure is shown (`plt.show`), all subsequent calls to `plt.plot` will use the same axes (until it reaches `plt.show()`). The argument `label=` holds the label that will appear in the legend.Try it" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.plot(topo[0, :], label=\"North\")\n", + "plt.plot(topo[-1, :], \"r--\", label=\"South\")\n", + "plt.plot(topo[int(len(topo) / 2), :], \"g:\", linewidth=3, label=\"Mid\")\n", + "\n", + "plt.title(\"Topographic profiles\")\n", + "plt.ylabel(\"Elevation (m)\")\n", + "plt.xlabel(\"<-- West East -->\")\n", + "plt.legend(loc=\"lower left\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " ## Practice your skills: Make your own plots \n", + "\n", + " Create a single plot showing how the maximum (`numpy.max()`),\n", + " minimum (`numpy.min()`), and mean (`numpy.mean()`) elevation changes with longitude. Label the axes and include a title for each of the plots (Hint: use `axis=0`). Create a legend." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.plot(topo.min(axis=0), label=\"Min\")\n", + "plt.plot(topo.max(axis=0), \"r--\", label=\"Max\")\n", + "plt.plot(topo.mean(axis=0), \"g:\", linewidth=3, label=\"mean\")\n", + "\n", + "plt.title(\"Topographic profiles\")\n", + "plt.ylabel(\"Elevation (m)\")\n", + "plt.xlabel(\"<-- West East -->\")\n", + "plt.legend(loc=\"lower left\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " ## Practice your skills: Subplots \n", + "\n", + " We often want to arrange separate plots in layouts with multiple rows\n", + " and columns. The script below uses subplots to show the elevation\n", + " profile at the western edge, the mid longitude, and eastern edge of\n", + " the region. Subplots can be a little weird because they require the\n", + " axes to be defined before plotting. Type (don't copy-past!) the code\n", + " below to get a sense of how it works.\n", + " \n", + "This script uses a number of new commands. The function `plt.figure()`\n", + "creates a space into which we will place the three plots. The parameter\n", + "`figsize` tells Python how big to make this space. Each subplot is\n", + "placed into the figure using the `subplot` command. The `subplot`\n", + "command takes 3 parameters: the first denotes the total number of rows\n", + "of subplots in the figure, the second is the total number of columns of\n", + "subplots in the figure, and the final parameters identifies the\n", + "position of the subplot in the grid. The axes of each subplot are\n", + "called with different variable (axes1, axes2, axes3, axes4). Once a\n", + "subplot is created, the axes can be labeled using the `set_xlabel()`\n", + "(or `set_ylabel()`) method. `plt.show()` is called after the entire\n", + "figure is set up." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "topo = np.loadtxt(\"data/topo.asc\", delimiter=\",\")\n", + "\n", + "fig = plt.figure(figsize=(16.0, 3.0))\n", + "\n", + "axes1 = fig.add_subplot(1, 3, 1)\n", + "axes2 = fig.add_subplot(1, 3, 2)\n", + "axes3 = fig.add_subplot(1, 3, 3)\n", + "\n", + "axes1.plot(topo[:, 0])\n", + "axes1.set_ylim([2500, 3900])\n", + "axes1.set_ylabel(\"Elevation (m)\")\n", + "axes1.set_xlabel(\"<-- N S -->\")\n", + "axes1.set_title(\"West\")\n", + "\n", + "axes2.plot(topo[:, int(len(topo) / 2)])\n", + "axes2.set_ylim([2500, 3900])\n", + "axes2.set_xlabel(\"<-- N S -->\")\n", + "axes2.set_title(\"Mid\")\n", + "\n", + "axes3.plot(topo[:, -1])\n", + "axes3.set_ylim([2500, 3900])\n", + "axes3.set_xlabel(\"<--N S -->\")\n", + "axes3.set_title(\"East\")\n", + "\n", + "plt.show(fig)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Subplots of DEMs (Takehome) \n", + " \n", + "Make a 4x2 grid of subplots that use the function `imshow` to display each quarter of the dataset (ie. split down the middle in both x and y) in the left column. Plot corresponding profiles going from east to west in center of the image (cfr. Mid) in the right column.\n", + "\n", + "When plotting the DEMs (left column)\n", + "* Don't label axes or add a colorbar. It can be tricky to do this with subplots.\n", + "* To set the range of colors for one subplot, include the arguments `vmin` and `vmax` in `imshow` like this:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vmin = topo.min()\n", + "vmax = topo.max()\n", + "plt.imshow(topo, vmin=vmin, vmax=vmax)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = plt.figure(figsize=(16.0, 3.0))\n", + "\n", + "axes1 = fig.add_subplot(4, 2, 1)\n", + "axes2 = fig.add_subplot(4, 2, 2)\n", + "axes3 = fig.add_subplot(4, 2, 3)\n", + "axes4 = fig.add_subplot(4, 2, 4)\n", + "axes5 = fig.add_subplot(4, 2, 5)\n", + "axes6 = fig.add_subplot(4, 2, 6)\n", + "axes7 = fig.add_subplot(4, 2, 7)\n", + "axes8 = fig.add_subplot(4, 2, 8)\n", + "\n", + "vmin = topo.min()\n", + "vmax = topo.max()\n", + "\n", + "topo1 = topo[: int(topo.shape[0] / 2), : int(topo.shape[1] / 2)]\n", + "axes1.imshow(topo1, vmin=vmin, vmax=vmax)\n", + "axes1.axes.get_xaxis().set_visible(False)\n", + "axes1.axes.get_yaxis().set_visible(False)\n", + "axes2.plot(topo1[:, -1])\n", + "axes2.set_ylim([vmin, vmax])\n", + "axes2.set_xlabel(\"<--N S -->\")\n", + "axes2.set_title(\"North-West\")\n", + "\n", + "\n", + "topo2 = topo[int(topo.shape[0] / 2) :, : int(topo.shape[1] / 2)]\n", + "axes3.imshow(topo2, vmin=vmin, vmax=vmax)\n", + "axes3.axes.get_xaxis().set_visible(False)\n", + "axes3.axes.get_yaxis().set_visible(False)\n", + "axes4.plot(topo2[:, -1])\n", + "axes4.set_ylim([vmin, vmax])\n", + "axes4.set_xlabel(\"<--N S -->\")\n", + "axes4.set_title(\"South-West\")\n", + "\n", + "topo3 = topo[: int(topo.shape[0] / 2), int(topo.shape[1] / 2) :]\n", + "axes5.imshow(topo3, vmin=vmin, vmax=vmax)\n", + "axes5.axes.get_xaxis().set_visible(False)\n", + "axes5.axes.get_yaxis().set_visible(False)\n", + "axes6.plot(topo3[:, -1])\n", + "axes6.set_ylim([vmin, vmax])\n", + "axes6.set_xlabel(\"<--N S -->\")\n", + "axes6.set_title(\"North-East\")\n", + "\n", + "topo4 = topo[int(topo.shape[0] / 2) :, int(topo.shape[1] / 2) :]\n", + "axes7.imshow(topo4, vmin=vmin, vmax=vmax)\n", + "axes7.axes.get_xaxis().set_visible(False)\n", + "axes7.axes.get_yaxis().set_visible(False)\n", + "axes8.plot(topo4[:, -1])\n", + "axes8.set_ylim([vmin, vmax])\n", + "axes8.set_xlabel(\"<--N S -->\")\n", + "axes8.set_title(\"South-East\")\n", + "\n", + "plt.show()\n", + "\n", + "ax = plt.imshow(topo)\n", + "ax.axes.get_xaxis().set_visible(False)\n", + "ax.axes.get_yaxis().set_visible(False)" + ] + } + ], + "metadata": { + "celltoolbar": "Tags", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 299313228545ba61b0905418cf9279f23787e024 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 31 Jul 2023 17:16:22 +0000 Subject: [PATCH 02/10] Move the section to lesson 1 --- lessons/python/1_fundamentals.ipynb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lessons/python/1_fundamentals.ipynb b/lessons/python/1_fundamentals.ipynb index a1f7724..fb92726 100644 --- a/lessons/python/1_fundamentals.ipynb +++ b/lessons/python/1_fundamentals.ipynb @@ -93,6 +93,26 @@ "print(z)" ] }, + { + "cell_type": "markdown", + "id": "9e56cfc3-8fd3-45a5-ae1a-ac083a3a280d", + "metadata": {}, + "source": [ + "In a notebook (or an IPython session), use the `whos` command to see what variables are in memory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b43d268-d1b7-41c4-8acd-8f9cd276db7c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "whos" + ] + }, { "cell_type": "markdown", "id": "d6acccf0", From 55d537b3de4a94e9a269b2c2bff2c77f63facecd Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 31 Jul 2023 21:57:01 +0000 Subject: [PATCH 03/10] Add simple example and update text for DEM data example --- lessons/python/4_arrays.ipynb | 687 +++++++++++++++++++++++++--------- 1 file changed, 504 insertions(+), 183 deletions(-) diff --git a/lessons/python/4_arrays.ipynb b/lessons/python/4_arrays.ipynb index e383e6e..07725c7 100644 --- a/lessons/python/4_arrays.ipynb +++ b/lessons/python/4_arrays.ipynb @@ -18,18 +18,11 @@ "Arrays are containers that store multiple values of the same type.\n", "Values in an array are stored contiguously in memory,\n", "which makes operations on arrays fast.\n", - "Arrays can be mutlidimensional." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ + "Arrays can be mutlidimensional.\n", + "\n", "In this lesson,\n", - "we'll explore how to work with arrays using topographic data\n", - "in the form of a digital elevation model.\n", + "we'll explore how to work with arrays using some simple examples,\n", + "as well as with some topographic data in the form of a digital elevation model.\n", "\n", "We'll use a library called NumPy." ] @@ -42,9 +35,10 @@ "source": [ "## NumPy\n", "\n", - "NumPy is a fundamental Python package for scientific computing.\n", - "NumPy uses a high-performance data structure known as the *n-dimensional array* or *ndarray*, a multi-dimensional array object, for efficient computation.\n", - "Use NumPy arrays if you want to do numerical computation on a large number of data values.\n", + "[NumPy](https://numpy.org/) is a fundamental Python package for scientific computing.\n", + "NumPy uses a high-performance data structure, the *n-dimensional array* or *ndarray*,\n", + "for efficient computation.\n", + "Use NumPy arrays to do numerical computations on a large number of data values.\n", "\n", "Import NumPy with its common nickname, `np`." ] @@ -62,283 +56,505 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Exploring topographic data" + "## Array generators\n", + "\n", + "NumPy provides a number of built-in functions for creating arrays.\n", + "We'll look at a few of them here, including:\n", + "\n", + "- `empty`\n", + "- `zeros`\n", + "- `ones`\n", + "- `zeros_like`\n", + "- `ones_like`\n", + "- `arange`\n", + "- `linspace`\n", + "\n", + "More can be found in the NumPy documentation under [Array creation routines](https://numpy.org/doc/stable/reference/routines.array-creation.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Once we’ve loaded the library, we can\n", - "call a function inside that library to read a data file included in the Ivy coursefiles:" + "Create a new array of five elements with the `empty` function." ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "a = np.empty(5)" + ] + }, + { + "cell_type": "markdown", "metadata": {}, + "source": [ + "What is the type of the variable `a`?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "numpy.loadtxt(\"data/topo.asc\", delimiter=\",\")" + "type(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The expression `numpy.loadtxt(...)` is a function call\n", - "that asks Python to run the function `loadtxt` that belongs to the `numpy` library.\n", - "This dotted notation, with the syntax `thing.component`, is used\n", - "everywhere in Python to refer to parts of things.\n", - "\n", - "The function call to `numpy.loadtxt` has two parameters:\n", - "the name of the file we want to read,\n", - "and the delimiter that separates values on a line.\n", - "Both need to be character strings (or strings, for short)\n", - "so we write them in quotes.\n", - "\n", - "Within the Jupyter Notebook, pressing Shift+Enter runs the\n", - "commands in the selected cell. Because we haven't told iPython what to\n", - "do with the output of `numpy.loadtxt`, the notebook just displays it on\n", - "the screen. In this case, that output is the data we just loaded. By\n", - "default, only a few rows and columns are shown (with `...` to omit\n", - "elements when displaying big arrays).\n", + "NumPy arrays have an attribute\n", + "(this is an object-oriented programming term; more [later](7_oop.ipynb))\n", + "called `shape` that provides the dimensions of an array.\n", "\n", - "Our call to `numpy.loadtxt` read the file but didn’t save it to memory.\n", - "In order to access the data, we need to assign the values to a variable.\n", - "A variable is just a name that refers to an object. Python’s variables\n", - "must begin with a letter and are case sensitive. We can assign a\n", - "variable name to an object using `=`." + "What is the shape of `a`?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "a.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Objects and their names\n", - "\n", - "What happens when a function is called but the output is not assigned to\n", - "a variable is a bit more complicated than simply not saving it. The call\n", - "to `numpy.loadtxt` read the file and created an object in memory that\n", - "contains the data, but because we didn't assign it to a variable name,\n", - "there is no way for us to call this object. \n", - "\n", - "Let’s re-run numpy.loadtxt and assign the output to a variable name:" + "What are the values of `a`?" ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "a" + ] + }, + { + "cell_type": "markdown", "metadata": {}, + "source": [ + "The `empty` function simply allocates memory for an array without clearing whatever is currently in memory at those location.\n", + "It's best to use `empty` when you need an array but will overwrite the values,\n", + "like when reading data from a file." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `zeros` and `ones` functions make arrays populated with 0s and 1s." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "np.zeros(10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "topo = numpy.loadtxt(\"data/topo.asc\", delimiter=\",\")" + "np.ones(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "This command doesn’t produce any visible output. If we want to see the\n", - "data, we can print the variable’s value with the command `print`:" + "The `zeros_like` and `ones_like` functions create new arrays with the same shape and type as an existing array.\n", + "\n", + "Make a new array of zeros with the same type and shape as the existing array `a`." ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "b = np.zeros_like(a)\n", + "b" + ] + }, + { + "cell_type": "markdown", "metadata": {}, + "source": [ + "The `arange` function creates an indexed array of values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "print(topo)" + "x = np.arange(5)\n", + "x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Using its variable name, we can see that [type](reference.html#type) of object the variable:" + "The `linspace` function creates an array of evenly spaced values." ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "y = np.linspace(0, 6, num=5)\n", + "y" + ] + }, + { + "cell_type": "markdown", "metadata": {}, + "source": [ + "## Indexing\n", + "\n", + "NumPy arrays can be indexed in the manner described in the first lesson on [variables](1_fundamentals.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using the variable `y` created above, get its first value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "type(topo)" + "y[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The function `type` tells us that the variable name `topo` currently\n", - "points to an N-dimensional array created by the NumPy library. We can also get the shape of the\n", - "array:" + "And the last value." ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "y[-1]" + ] + }, + { + "cell_type": "markdown", "metadata": {}, + "source": [ + "And every value after the second." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "topo.shape" + "y[2:]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "This tells us that `topo` has 500 rows and 500 columns. The file\n", - "we imported contains elevation data (in meters, 2 degree spacing) for an\n", - "area along the Front Range of Colorado, so the area that this array represents is 1 km x 1 km.\n", + "## Indexing and slicing nD arrays\n", "\n", - "The object of\n", - "type `numpy.ndarray` that the variable `topo` is assigned to contains the values of the array\n", - "as well as some extra information about the array. These are the members or attributes of the object, and they\n", - "describe the data in the same way an adjective describes a noun. The\n", - "command `topo.shape` calls the `shape` attribute of the object with the variable name\n", - "`topo` that describes its dimensions. We use the same dotted notation\n", - "for the attributes of objects that we use for the functions inside\n", - "libraries because they have the same part-and-whole relationship." + "Things get more interesting when we move to higher-dimensional arrays.\n", + "In two dimensions,\n", + "indices are ordered `[row,column]`;\n", + "i.e., `y`-direction index first, then `x`-direction index.\n", + "\n", + "Let's consider an array that has three rows and five columns," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "n_rows = 3\n", + "n_cols = 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - " ## Who's who in the memory\n", - "\n", - " You can use the `whos` command at any time to see what variables you have created and what modules you have loaded into the computers memory. As this is an IPython command, it will only work if you are in an iPython\n", - " terminal or the Jupyter Notebook.\n", - " \n", - " Try it, check what is currently on your memory" + "Use the `arange` function to create a 1D indexed array of `n_rows * n_cols = 15` elements." ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "tags": [ - "skip-flake8" - ] + "tags": [] }, "outputs": [], "source": [ - "whos" + "a = np.arange(n_rows*n_cols)\n", + "a" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Indexing\n", - "\n", - "We can access individual values in an array using an [index](reference.html#index) in square brackets:" + "Now reshape this array in rows and columns." ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "b = a.reshape(n_rows, n_cols)\n", + "b" + ] + }, + { + "cell_type": "markdown", "metadata": {}, + "source": [ + "What is the shape of `b`?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "print(\"elevation at the corner of topo:\", topo[0, 0], \"meters\")" + "b.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can index (or \"slice\") 2D arrays using the same syntax as 1D arrays,\n", + "but the rules are are applied to each dimension,\n", + "so the syntax is more complicated.\n", + "\n", + "Get the first element of the 2D array `b`." ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "b[0, 0]" + ] + }, + { + "cell_type": "markdown", "metadata": {}, + "source": [ + "And the last element." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "print(\"elevation at a point in topo:\", topo[137, 65], \"meters\")" + "b[-1, -1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "When referring to entries in a two dimensional array, the indices are\n", - "ordered `[row,column]`. The expression `topo[137, 65]` should not surprise you but `topo[0,0]` might. Programming languages like Fortran and MATLAB\n", - "start counting at 1 because that’s what (most) humans have done for\n", - "thousands of years. Languages in the C family (including C++, Java,\n", - "Perl, and Python) count from 0 because that’s simpler for computers to\n", - "do. So if we have an M×N array in Python, the indices go from 0 to M-1\n", - "on the first axis (rows) and 0 to N-1 on the second (columns). In\n", - "MATLAB, the same array (or matrix) would have indices that go from 1 to\n", - "M and 1 to N. Zero-based indexing takes a bit of getting used to, but\n", - "one way to remember the rule is that the index is how many steps we have\n", - "to take from the start to get to the item we want.\n", - "\n", - "Python also allows for **negative indices** to refer to the position of\n", - "elements with respect to the end of each axis. An index of -1 refers to\n", - "the last item in a list, -2 is the second to last, and so on. Since\n", - "index `[0,0]` is the upper left corner of an array, index `[-1,-1]`\n", - "therefore the lower right corner of the array. \n", - "\n", - "Print the lower right corner of the `topo` array: " + "What are the values in the first column?" ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "b[:,0]" + ] + }, + { + "cell_type": "markdown", "metadata": {}, + "source": [ + "What are the values in the first row?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "print(topo[-1, -1])" + "b[0,:]" ] }, { "cell_type": "markdown", + "metadata": {}, + "source": [ + "What about 3D arrays?\n", + "Consider the following case." + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": { - "attributes": { - "classes": [ - "output" - ], - "id": "" - } + "tags": [] }, + "outputs": [], "source": [ - "Print the upper left corner of the `topo` array: " + "nx = 4 # columns\n", + "ny = 3 # rows\n", + "nz = 2 # levels" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "attributes": { - "classes": [ - "output" - ], - "id": "" - } + "tags": [] + }, + "outputs": [], + "source": [ + "c = np.arange(nx*ny*nz)\n", + "c" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "d = c.reshape(nz, ny, nx)\n", + "d" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] }, "outputs": [], "source": [ - "print(topo[0, 0])" + "d.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "> ## In the Corner\n", - ">\n", - "> What may also surprise you is that when Python displays an array,\n", - "> it shows the element with index `[0, 0]` in the upper left corner\n", - "> rather than the lower left.\n", - "> This is consistent with the way mathematicians draw matrices,\n", - "> but different from the Cartesian coordinates.\n", - "> The indices are (row, column) instead of (column, row) for the same reason,\n", - "> which can be confusing when plotting data.\n", - "\n", - "## Slicing\n", + "Slice the array `d` to get the first *z* level." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "d[0, :, :]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exploring topographic data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "NumPy arrays are perfectly suited for working with topographics data from a digital elevation model.\n", "\n", - "A command like `topo[0,0]` selects a single element in the array `topo`.\n", - "Indices can also be used to [slice](reference.html#slice) sections of the array. For example, we\n", - "can select the top left quarter of the array like this:" + "The NumPy `loadtxt` function can read simple text files.\n", + "Use it to read a data file included in the Ivy course files." ] }, { @@ -347,23 +563,24 @@ "metadata": {}, "outputs": [], "source": [ - "print(topo[0:5, 0:5])" + "topo = np.loadtxt(\"data/topo.asc\", delimiter=\",\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The slice `[0:5]` means \"Start at index 0 and go along the axis up to,\n", - "but not including, index 5\".\n", - "\n", - "We don’t need to include the upper or lower bound of the slice if we\n", - "want to go all the way to the edge. If we don’t include the lower bound,\n", - "Python uses 0 by default; if we don’t include the upper bound, the slice\n", - "runs to the end of the axis. If we don’t include either (i.e., if we\n", - "just use ‘:’), the slice includes everything. \n", - "\n", - "Print out the first 5 rows and last 6 columns op the topo array:" + "The call to `loadtxt` has two parameters:\n", + "the path to the file to read,\n", + "and the delimiter separating values on a line in the file.\n", + "Both need to be strings." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Print the values of `topo`." ] }, { @@ -372,21 +589,14 @@ "metadata": {}, "outputs": [], "source": [ - "print(topo[:5, -6:])" + "print(topo)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - " ## Point elevations: Practice your skills \n", - " \n", - " Use indexing to answer the following questions and check your answers\n", - " against the data visualization:\n", - " \n", - " * Is the NW corner of the region higher than the SW corner? What's the elevation difference? You can assume the NW corner to be in the upper left corner of the matrix (NW of at [0,0], not the Cartesian NW, see also (In the Corner)\n", - " * What's the elevation difference between the NE corner and the SE corner?\n", - " * What's the elevation at the center of the region shown in the array?" + "What are the type and dimensions of `topo`?" ] }, { @@ -395,18 +605,81 @@ "metadata": {}, "outputs": [], "source": [ - "print(topo[0, 0] - topo[-1, 0])\n", - "print(topo.shape[0] / 2)\n", - "print(topo[int(topo.shape[0] / 2), int(topo.shape[1] / 2)])" + "type(topo)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "topo.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This tells us that `topo` has 500 rows and 500 columns. The file\n", + "we imported contains elevation data (in meters, with 2.0 degree spacing) for an\n", + "area along the Front Range of Colorado.\n", + "The area that this array represents is 1 km x 1 km.\n", + "\n", + "The object of\n", + "type `numpy.ndarray` that the variable `topo` is assigned to contains the values of the array\n", + "as well as some extra information about the array. These are the members or attributes of the object, and they\n", + "describe the data in the same way an adjective describes a noun. The\n", + "command `topo.shape` calls the `shape` attribute of the object with the variable name\n", + "`topo` that describes its dimensions. We use the same dotted notation\n", + "for the attributes of objects that we use for the functions inside\n", + "libraries because they have the same part-and-whole relationship." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Numerical operations on arrays\n", + "### Indexing and slicing\n", "\n", - "We can perform basic mathematical operations on each individual element of a NumPy array. We can create a new array with elevations in feet:" + "Indexing and slicing works on the `topo` array as described above." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "attributes": { + "classes": [ + "output" + ], + "id": "" + } + }, + "source": [ + "Print the elevation value at upper left corner of the `topo` array: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "output" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "topo[0, 0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Print the elevation value of lower right corner of the `topo` array: " ] }, { @@ -415,16 +688,14 @@ "metadata": {}, "outputs": [], "source": [ - "topo_in_feet = topo * 3.2808\n", - "print(\"Elevation in meters:\", topo[0, 0])\n", - "print(\"Elevation in feet:\", topo_in_feet[0, 0])" + "topo[-1, -1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Arrays of the same size can be used together in arithmatic operations:" + "Slice a 5x5 array from `topo`." ] }, { @@ -433,15 +704,23 @@ "metadata": {}, "outputs": [], "source": [ - "double_topo = topo + topo\n", - "print(\"Double topo:\", double_topo[0, 0], \"meters\")" + "topo[0:5, 0:5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We can also perform statistical operations on arrays:" + "The slice `[0:5]` means \"start at index 0 and go along the axis up to,\n", + "but not including, index 5\".\n", + "\n", + "We don’t need to include the upper or lower bound of the slice if we\n", + "want to go all the way to the edge. If we don’t include the lower bound,\n", + "Python uses 0 by default; if we don’t include the upper bound, the slice\n", + "runs to the end of the axis. If we don’t include either (i.e., if we\n", + "just use ‘:’), the slice includes everything. \n", + "\n", + "Show the elevations of the first 5 rows and last 6 columns of the `topo` array." ] }, { @@ -450,24 +729,19 @@ "metadata": {}, "outputs": [], "source": [ - "print(\"Mean elevation:\", topo.mean(), \"meters\")" + "print(topo[:5, -6:])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "> ## Methods vs. attributes\n", - "> \n", - "> `mean` is a method that belongs to the array `topo`, i.e., it is a\n", - "> function `topo` can inherently call just because of its type.\n", - "> When we call `topo.mean()`, we are asking `topo` to calculate its mean\n", - "> value. Because it is a function, we need to include parenthesis in the\n", - "> command. Because it is an `np.array`, `topo` also has an attribute called `shape`, but it doesn't include parenthesis because\n", - "> attributes are objects, not functions.\n", + "### Mathematical operations\n", "\n", - "Python will kindly tell us if we mix up the parentheses:\n", - " " + "NumPy arrays support a variety of mathematical operations.\n", + "\n", + "For example,\n", + "we can create a new array with elevations in feet." ] }, { @@ -476,14 +750,33 @@ "metadata": {}, "outputs": [], "source": [ - "topo.mean" + "topo_in_feet = topo * 3.2808\n", + "print(f\"Elevation in meters: {topo[0, 0]:.2f}\")\n", + "print(f\"Elevation in feet: {topo_in_feet[0, 0]:.2f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "NumPy arrays have many other useful methods. Print the min and max elevation of the topo dataset" + "We can also perform statistical operations.\n", + "Find the mean of the `topo` dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Mean elevation: {topo.mean():.2f} meters\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Find the minimum and maximum elevations of the `topo` dataset." ] }, { @@ -500,7 +793,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can also call methods on slices of the array:" + "We can also operate on array slices." ] }, { @@ -520,9 +813,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Methods can also be used along individual axes (rows or columns) of an\n", + "Operations can also be performed along individual axes (rows or columns) of an\n", "array. If we want to see how the mean elevation changes with longitude\n", - "(E-W), we can use the method along `axis=0`:" + "(E-W), we can calculate it along `axis=0`:" ] }, { @@ -531,7 +824,7 @@ "metadata": {}, "outputs": [], "source": [ - "print(topo.mean(axis=0))" + "topo.mean(axis=0)" ] }, { @@ -548,7 +841,35 @@ "metadata": {}, "outputs": [], "source": [ - "print(topo.mean(axis=1))" + "topo.mean(axis=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercises \n", + " \n", + "1. Is the NW corner of the region higher than the SW corner? What's the elevation difference?\n", + "1. What's the elevation difference between the NE corner and the SE corner?\n", + "1. What's the elevation at the center of the region represented in the `topo` array?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "NumPy is ubiquitous in Python.\n", + "Any time you work with data, you'll work with NumPy.\n", + "\n", + "The Numpy documentation at https://numpy.org/doc/stable/index.html contains just about anything you'll need to know about NumPy.\n", + "\n", + "Last, although we deemphasize visualization here,\n", + "it almost always really helps to visualize data.\n", + "Data visualization is covered well in many other places,\n", + "but we do have a [notebook](./4a_plots.ipynb) demonstrating how to visualize the DEM data used here." ] } ], From ed8482ee7305e3f94a27de078fdbef004d387acf Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 31 Jul 2023 22:02:27 +0000 Subject: [PATCH 04/10] Add intro paragraph --- lessons/python/4a_plots.ipynb | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/lessons/python/4a_plots.ipynb b/lessons/python/4a_plots.ipynb index 03c1530..f6823d1 100644 --- a/lessons/python/4a_plots.ipynb +++ b/lessons/python/4a_plots.ipynb @@ -13,15 +13,10 @@ "tags": [] }, "source": [ - "# Plotting\n", + "# Data visualization\n", "\n", - "The digital elevation model data used in the Arrays lesson is handy for exploring visualization techniques in Python. \n", - "\n", - "In this lesson,\n", - "we'll explore how to work with arrays using topographic data\n", - "in the form of a digital elevation model.\n", - "\n", - "We'll use a library called NumPy." + "The digital elevation model data used in the [Arrays](./4_arrays.ipynb) lesson is handy for exploring visualization techniques in Python.\n", + "Let's visualize the data with a library called Matplotlib." ] }, { @@ -30,7 +25,7 @@ "tags": [] }, "source": [ - "## matplotlib\n", + "## Matplotlib\n", "\n", "NumPy is short for \"Numerical Python\".\n", "It is a fundamental Python package for scientific computing.\n", @@ -457,6 +452,22 @@ "ax.axes.get_xaxis().set_visible(False)\n", "ax.axes.get_yaxis().set_visible(False)" ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## Summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From fd85b75f49eb65a59ed6c272b0aea961e702ab10 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 31 Jul 2023 22:14:55 +0000 Subject: [PATCH 05/10] Remove lint --- lessons/python/4_arrays.ipynb | 8 ++++---- lessons/python/4a_plots.ipynb | 7 ------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/lessons/python/4_arrays.ipynb b/lessons/python/4_arrays.ipynb index 07725c7..102636a 100644 --- a/lessons/python/4_arrays.ipynb +++ b/lessons/python/4_arrays.ipynb @@ -349,7 +349,7 @@ }, "outputs": [], "source": [ - "a = np.arange(n_rows*n_cols)\n", + "a = np.arange(n_rows * n_cols)\n", "a" ] }, @@ -445,7 +445,7 @@ }, "outputs": [], "source": [ - "b[:,0]" + "b[:, 0]" ] }, { @@ -463,7 +463,7 @@ }, "outputs": [], "source": [ - "b[0,:]" + "b[0, :]" ] }, { @@ -495,7 +495,7 @@ }, "outputs": [], "source": [ - "c = np.arange(nx*ny*nz)\n", + "c = np.arange(nx * ny * nz)\n", "c" ] }, diff --git a/lessons/python/4a_plots.ipynb b/lessons/python/4a_plots.ipynb index f6823d1..56f5063 100644 --- a/lessons/python/4a_plots.ipynb +++ b/lessons/python/4a_plots.ipynb @@ -461,13 +461,6 @@ "source": [ "## Summary" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From 3ccf382ed3251d5335a1336c662a4118eee5fa95 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Tue, 1 Aug 2023 16:27:35 +0000 Subject: [PATCH 06/10] Update lesson text --- lessons/python/4a_plots.ipynb | 116 +++++++++++++--------------------- 1 file changed, 45 insertions(+), 71 deletions(-) diff --git a/lessons/python/4a_plots.ipynb b/lessons/python/4a_plots.ipynb index 56f5063..a47b5f2 100644 --- a/lessons/python/4a_plots.ipynb +++ b/lessons/python/4a_plots.ipynb @@ -25,14 +25,9 @@ "tags": [] }, "source": [ - "## Matplotlib\n", - "\n", - "NumPy is short for \"Numerical Python\".\n", - "It is a fundamental Python package for scientific computing.\n", - "NumPy uses a high-performance data structure known as the *n-dimensional array* or *ndarray*, a multi-dimensional array object, for efficient computation of arrays and matrices.\n", - "You should use this library if you want to do numerical computation on a large number of data values.\n", + "## Read the data\n", "\n", - "We can load NumPy with:" + "Use the NumPy `loadtxt` function to read the digital elevation dataset. " ] }, { @@ -41,62 +36,38 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy" + "import numpy\n", + "\n", + "topo = numpy.loadtxt(\"data/topo.asc\", delimiter=\",\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Once we’ve loaded the library, we can\n", - "call a function inside that library to read a data file included in the Ivy coursefiles:" + "Recall the type and shape of the resulting NumPy array." ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "numpy.loadtxt(\"data/topo.asc\", delimiter=\",\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The expression `numpy.loadtxt(...)` is a function call\n", - "that asks Python to run the function `loadtxt` that belongs to the `numpy` library.\n", - "This dotted notation, with the syntax `thing.component`, is used\n", - "everywhere in Python to refer to parts of things.\n", - "\n", - "The function call to `numpy.loadtxt` has two parameters:\n", - "the name of the file we want to read,\n", - "and the delimiter that separates values on a line.\n", - "Both need to be character strings (or strings, for short)\n", - "so we write them in quotes.\n", - "\n", - "Within the Jupyter Notebook, pressing Shift+Enter runs the\n", - "commands in the selected cell. Because we haven't told iPython what to\n", - "do with the output of `numpy.loadtxt`, the notebook just displays it on\n", - "the screen. In this case, that output is the data we just loaded. By\n", - "default, only a few rows and columns are shown (with `...` to omit\n", - "elements when displaying big arrays).\n", - "\n", - "Our call to `numpy.loadtxt` read the file but didn’t save it to memory.\n", - "In order to access the data, we need to assign the values to a variable.\n", - "A variable is just a name that refers to an object. Python’s variables\n", - "must begin with a letter and are case sensitive. We can assign a\n", - "variable name to an object using `=`." + "type(topo)" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "topo = numpy.loadtxt(\"data/topo.asc\", delimiter=\",\")" + "topo.ndim" ] }, { @@ -114,35 +85,31 @@ "source": [ "This tells us that `topo` has 500 rows and 500 columns. The file\n", "we imported contains elevation data (in meters, 2 degree spacing) for an\n", - "area along the Front Range of Colorado, so the area that this array represents is 1 km x 1 km.\n", - "\n", - "The object of\n", - "type `numpy.ndarray` that the variable `topo` is assigned to contains the values of the array\n", - "as well as some extra information about the array. These are the members or attributes of the object, and they\n", - "describe the data in the same way an adjective describes a noun. The\n", - "command `topo.shape` calls the `shape` attribute of the object with the variable name\n", - "`topo` that describes its dimensions. We use the same dotted notation\n", - "for the attributes of objects that we use for the functions inside\n", - "libraries because they have the same part-and-whole relationship." + "area along the Front Range of Colorado, so the area that this array represents is 1 km x 1 km." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Rasters are arrays of values.\n", + "In the case of DEMs, those values are elevations.\n", + "It's very hard to get a good sense of what this landscape looks like by looking directly at the data.\n", + "This information is better conveyed through plots and graphics." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Plotting\n", + "## Matplotlib\n", " \n", - "Rasters are arrays of values. In the case of DEMs, those values\n", - "are elevations. It's very hard to get a good sense of what this landscape\n", - "looks like by looking directly at the data. This information is better\n", - "conveyed through plots and graphics.\n", - "\n", "Data visualization deserves an entire lecture (or course) of its own,\n", - "but we can explore a few features of Python's `matplotlib` library here.\n", - "While there is no \"official\" plotting library in Python, this package is\n", - "the de facto standard.\n", + "but we can explore a few features of Python's [Matplotlib](https://matplotlib.org/) library here.\n", + "While there is no official plotting library in Python,\n", + "this package is the de facto standard.\n", "\n", - "We start by importing the `pyplot` module from the library `matplotlib`:" + "We start by importing the `pyplot` module from the Matplotlib library using its common nickname `plt`." ] }, { @@ -158,7 +125,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can use the function `imshow` within `matplotlib.pyplot` to display arrays as a 2D\n", + "We can use the function `imshow` within `matplotlib.pyplot` to display an array as a 2D\n", "image. \n", "\n", "Try to display the 2D `topo` array" @@ -177,9 +144,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - " ## Plotting smaller regions \n", + "## Plotting smaller regions \n", " \n", - " Use the function `imshow` from `matplotlib.pyplot` to make one plot showing the northern half of the region and another plot showing the southern half. Use the pyplot show() function to display the current figure and start a new one. Render the figures in the notebook using `%matplotlib inline`" + "Use the `imshow` function to make one plot showing the northern half of the region and another plot showing the southern half." ] }, { @@ -189,9 +156,9 @@ "outputs": [], "source": [ "plt.figure()\n", - "plt.imshow(topo[: int(topo.shape[0] / 2), :])\n", + "plt.imshow(topo[:int(topo.shape[0] / 2), :])\n", "plt.figure()\n", - "plt.imshow(topo[int(topo.shape[0] / 2) :, :])" + "plt.imshow(topo[int(topo.shape[0] / 2):, :])" ] }, { @@ -461,14 +428,21 @@ "source": [ "## Summary" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "CSDMS", "language": "python", - "name": "python3" + "name": "csdms" }, "language_info": { "codemirror_mode": { @@ -480,7 +454,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.9" + "version": "3.11.3" } }, "nbformat": 4, From b960d59b0c22fcea3d00edb2aa5c5db3decb0bc0 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Tue, 1 Aug 2023 17:56:50 +0000 Subject: [PATCH 07/10] Improve text and remove typos in arrays lesson --- lessons/python/4_arrays.ipynb | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lessons/python/4_arrays.ipynb b/lessons/python/4_arrays.ipynb index 102636a..09fe497 100644 --- a/lessons/python/4_arrays.ipynb +++ b/lessons/python/4_arrays.ipynb @@ -18,7 +18,7 @@ "Arrays are containers that store multiple values of the same type.\n", "Values in an array are stored contiguously in memory,\n", "which makes operations on arrays fast.\n", - "Arrays can be mutlidimensional.\n", + "Arrays can be multidimensional.\n", "\n", "In this lesson,\n", "we'll explore how to work with arrays using some simple examples,\n", @@ -38,7 +38,7 @@ "[NumPy](https://numpy.org/) is a fundamental Python package for scientific computing.\n", "NumPy uses a high-performance data structure, the *n-dimensional array* or *ndarray*,\n", "for efficient computation.\n", - "Use NumPy arrays to do numerical computations on a large number of data values.\n", + "Use NumPy arrays when you need to do numerical computations on a large number of data values.\n", "\n", "Import NumPy with its common nickname, `np`." ] @@ -59,7 +59,7 @@ "## Array generators\n", "\n", "NumPy provides a number of built-in functions for creating arrays.\n", - "We'll look at a few of them here, including:\n", + "We'll look at a few of the frequently used ones here, including:\n", "\n", "- `empty`\n", "- `zeros`\n", @@ -152,8 +152,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The `empty` function simply allocates memory for an array without clearing whatever is currently in memory at those location.\n", - "It's best to use `empty` when you need an array but will overwrite the values,\n", + "The `empty` function simply allocates memory for an array without clearing whatever is already in memory at those locations.\n", + "It's best to use `empty` when you need an array but intend to overwrite the values,\n", "like when reading data from a file." ] }, @@ -317,7 +317,7 @@ "Things get more interesting when we move to higher-dimensional arrays.\n", "In two dimensions,\n", "indices are ordered `[row,column]`;\n", - "i.e., `y`-direction index first, then `x`-direction index.\n", + "i.e., the `y`-direction index first, then the `x`-direction index.\n", "\n", "Let's consider an array that has three rows and five columns," ] @@ -357,7 +357,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now reshape this array in rows and columns." + "Now reshape this array into rows and columns." ] }, { @@ -395,8 +395,8 @@ "metadata": {}, "source": [ "We can index (or \"slice\") 2D arrays using the same syntax as 1D arrays,\n", - "but the rules are are applied to each dimension,\n", - "so the syntax is more complicated.\n", + "but the rules are applied to each dimension,\n", + "so it's more complicated.\n", "\n", "Get the first element of the 2D array `b`." ] @@ -551,7 +551,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "NumPy arrays are perfectly suited for working with topographics data from a digital elevation model.\n", + "NumPy arrays are perfectly suited for working with topographic data from a digital elevation model.\n", "\n", "The NumPy `loadtxt` function can read simple text files.\n", "Use it to read a data file included in the Ivy course files." @@ -656,7 +656,7 @@ } }, "source": [ - "Print the elevation value at upper left corner of the `topo` array: " + "Show the elevation value at upper left corner of the `topo` array." ] }, { @@ -679,7 +679,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Print the elevation value of lower right corner of the `topo` array: " + "Show the elevation value of lower right corner of the `topo` array." ] }, { @@ -714,11 +714,11 @@ "The slice `[0:5]` means \"start at index 0 and go along the axis up to,\n", "but not including, index 5\".\n", "\n", - "We don’t need to include the upper or lower bound of the slice if we\n", - "want to go all the way to the edge. If we don’t include the lower bound,\n", - "Python uses 0 by default; if we don’t include the upper bound, the slice\n", - "runs to the end of the axis. If we don’t include either (i.e., if we\n", - "just use ‘:’), the slice includes everything. \n", + "We don't need to include the upper or lower bound of the slice if we\n", + "want to go all the way to the edge. If we don't include the lower bound,\n", + "Python uses 0 by default; if we don't include the upper bound, the slice\n", + "runs to the end of the axis. If we don't include either (i.e., if we\n", + "just use `:`), the slice includes everything. \n", "\n", "Show the elevations of the first 5 rows and last 6 columns of the `topo` array." ] @@ -864,7 +864,7 @@ "NumPy is ubiquitous in Python.\n", "Any time you work with data, you'll work with NumPy.\n", "\n", - "The Numpy documentation at https://numpy.org/doc/stable/index.html contains just about anything you'll need to know about NumPy.\n", + "The [NumPy documentation](https://numpy.org/doc/stable/index.html) contains just about anything you'll need to know about NumPy.\n", "\n", "Last, although we deemphasize visualization here,\n", "it almost always really helps to visualize data.\n", From c189528c681c2cd5a526bf295b4c29d7b128fe95 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Tue, 1 Aug 2023 20:26:44 +0000 Subject: [PATCH 08/10] Add summary; spellcheck --- lessons/python/4a_plots.ipynb | 43 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/lessons/python/4a_plots.ipynb b/lessons/python/4a_plots.ipynb index a47b5f2..28f860c 100644 --- a/lessons/python/4a_plots.ipynb +++ b/lessons/python/4a_plots.ipynb @@ -27,7 +27,7 @@ "source": [ "## Read the data\n", "\n", - "Use the NumPy `loadtxt` function to read the digital elevation dataset. " + "First, use the NumPy `loadtxt` function to read the digital elevation dataset. " ] }, { @@ -126,9 +126,7 @@ "metadata": {}, "source": [ "We can use the function `imshow` within `matplotlib.pyplot` to display an array as a 2D\n", - "image. \n", - "\n", - "Try to display the 2D `topo` array" + "image." ] }, { @@ -168,14 +166,12 @@ "## Plotting, take two\n", " \n", "It's hard to get a sense of how the topography changes across the\n", - "landscape from these big tables of numbers. A simpler way to display\n", + "landscape from tables of numbers. A simpler way to display\n", "this information is with line plots.\n", "\n", "We are again going to use the `matplotlib` package for data\n", "visualization. Since we imported the `matplotlib.pyplot` library once\n", - "already, those tools are available and can be called within Python. As a\n", - "review, though, we are going to write every step needed to load and plot\n", - "the data.\n", + "already, those tools are available and can be called within Python.\n", "\n", "We use the function `plot` to create two basic line plots of the\n", "topography:" @@ -245,10 +241,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - " ## Practice your skills: Make your own plots \n", + "## Practice your skills: Make your own plots \n", "\n", - " Create a single plot showing how the maximum (`numpy.max()`),\n", - " minimum (`numpy.min()`), and mean (`numpy.mean()`) elevation changes with longitude. Label the axes and include a title for each of the plots (Hint: use `axis=0`). Create a legend." + "Create a single plot showing how the maximum, minimum, and mean elevation changes with longitude.\n", + "Label the axes and include a title for each of the plots (Hint: use `axis=0`). Create a legend." ] }, { @@ -302,11 +298,6 @@ "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "\n", - "topo = np.loadtxt(\"data/topo.asc\", delimiter=\",\")\n", - "\n", "fig = plt.figure(figsize=(16.0, 3.0))\n", "\n", "axes1 = fig.add_subplot(1, 3, 1)\n", @@ -336,9 +327,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Subplots of DEMs (Takehome) \n", + "## Subplots of DEMs (Take-home) \n", " \n", - "Make a 4x2 grid of subplots that use the function `imshow` to display each quarter of the dataset (ie. split down the middle in both x and y) in the left column. Plot corresponding profiles going from east to west in center of the image (cfr. Mid) in the right column.\n", + "Make a 4x2 grid of subplots that use the function `imshow` to display each quarter of the dataset (i.e., split down the middle in both x and y) in the left column. Plot corresponding profiles going from east to west in center of the image (cfr. Mid) in the right column.\n", "\n", "When plotting the DEMs (left column)\n", "* Don't label axes or add a colorbar. It can be tricky to do this with subplots.\n", @@ -430,11 +421,19 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], - "source": [] + "source": [ + "Data visualization is key for understanding data and for communicating research results.\n", + "We intentionally do not focus on visualization in Ivy\n", + "because many others have done it already,\n", + "and have done it better.\n", + "A wonderful example is the [Matplotlib Examples](https://matplotlib.org/stable/gallery/index.html),\n", + "which holds a gallery of over 100 example visualizations.\n", + "A strategy for using it is to find a plot in the gallery similar to what you want,\n", + "then copying the code provided that generates the plot,\n", + "tailoring it to suit your needs." + ] } ], "metadata": { From 00dfcdccee74b6ac40730ef99baccc5aa44505a5 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Tue, 1 Aug 2023 20:32:13 +0000 Subject: [PATCH 09/10] Remove lint --- lessons/python/4a_plots.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lessons/python/4a_plots.ipynb b/lessons/python/4a_plots.ipynb index 28f860c..88cc297 100644 --- a/lessons/python/4a_plots.ipynb +++ b/lessons/python/4a_plots.ipynb @@ -154,9 +154,9 @@ "outputs": [], "source": [ "plt.figure()\n", - "plt.imshow(topo[:int(topo.shape[0] / 2), :])\n", + "plt.imshow(topo[: int(topo.shape[0] / 2), :])\n", "plt.figure()\n", - "plt.imshow(topo[int(topo.shape[0] / 2):, :])" + "plt.imshow(topo[int(topo.shape[0] / 2) :, :])" ] }, { From 441e545de130f50bb6f8dc3a4f02affafa27e88e Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Tue, 1 Aug 2023 20:37:11 +0000 Subject: [PATCH 10/10] Use a magic command to satisfy linter --- lessons/python/1_fundamentals.ipynb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lessons/python/1_fundamentals.ipynb b/lessons/python/1_fundamentals.ipynb index fb92726..142a5d1 100644 --- a/lessons/python/1_fundamentals.ipynb +++ b/lessons/python/1_fundamentals.ipynb @@ -98,7 +98,9 @@ "id": "9e56cfc3-8fd3-45a5-ae1a-ac083a3a280d", "metadata": {}, "source": [ - "In a notebook (or an IPython session), use the `whos` command to see what variables are in memory." + "In a notebook (or an IPython session),\n", + "use the [magic command](https://ipython.readthedocs.io/en/stable/interactive/magics.html)\n", + "`%whos` to see what variables are in memory." ] }, { @@ -110,7 +112,7 @@ }, "outputs": [], "source": [ - "whos" + "%whos" ] }, {