{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
\n", "\n", "
\n", "\"Unidata\n", "
\n", "\n", "

Units in MetPy

\n", "

Unidata AMS 2021 Student Conference

\n", "\n", "
\n", "
\n", "\n", "---\n", "\n", "This notebook will help you become familiar with unit support in the `MetPy` library. Learn how to apply units to values, calculate new values from united values, and use units in `MetPy` functions. `MetPy` provides unit support through the `Pint` library. You can access the documentation for `Pint` by clicking on the image to the right.\n", "
\"logo
\n", "\n", "\n", "### Focuses\n", "* Introduce the use of `units` to track values with physical units\n", "* Demonstrate basic operations with and on values with units\n", "* Show examples of using units in `MetPy`\n", "\n", "\n", "### Objectives\n", "1. [Introduction to Units](#1.-Introduction-to-Units)\n", "1. [Quantity Methods and Attributes](#2.-Quantity-Methods-and-Attributes)\n", "1. [MetPy and Units](#3.-MetPy-and-Units)\n", "1. [Gotchas](#4.-Gotchas)\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Imports" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import metpy.calc as mpcalc\n", "from metpy.units import units" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Introduction to Units\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`MetPy` provides units support through the `Pint` library. Values with units attached to them (quantities) are described by the [`Pint` documentation](https://pint.readthedocs.io/en/stable/) as \"the product of a numerical value and a unit of measurement.\" They are used to programatically keep track of physical quantities so we can worry less about unit conversion errors and focus on our coding objective instead.\n", "\n", "### Attaching Units to Numerical Values\n", "We can apply units to a numerical value by multiplying that value by the units we desire in a couple of different ways -- either accessing the units as an attribute of `units` or passing a string to `units` as an argument. Either way, we get the same result:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "distance = 5. * units.meter\n", "distance" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "distance = 5. * units('meter')\n", "distance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively, we can use the `Quantity` constructor to do the same thing." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "distance = units.Quantity(5., 'meter')\n", "distance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We are not limited to applying units to scalars. They work with `numpy` arrays as well!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "distance = np.arange(1., 11.) * units.meter\n", "distance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If our units are not as simple as e.g., distance, we can chain together unit assignments. Say we have a vertical acceleration that we want to apply units to. We can multiply by meters, then divide by seconds twice. Alternatively, we can express this as a string of words, or even a string of abbreviations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "accel = np.arange(10) * units.meter / units.second / units.second\n", "accel" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "accel = np.arange(10) * units('meter / second ** 2')\n", "accel" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "accel = np.arange(10) * units('m / s ** 2')\n", "accel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Calculations with Units\n", "\n", "Once units are attached to a quantity, we can do math with other united quantities." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = np.random.randint(25, size=(5, 5)) * units.meter\n", "y = np.random.randint(25, size=(5, 5)) * units.meter\n", "\n", "z = x - y\n", "z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the dimensionality of the quantities we are operating on don't match, we will get a `DimensionalityError`. For example, try uncommenting the last line in the following cell and then running the cell." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "distance = np.random.random((5, 5)) * units.meter\n", "time = np.random.random((5, 5)) * units.second\n", "\n", "# velocity = distance - time" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the case that the unit dimensionalities are different, but the mathematical operation makes it valid, we won't get an error. Instead we get a quantity with new units, as we expect." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "distance = np.random.random((5, 5)) * units.meter\n", "time = np.random.random((5, 5)) * units.second\n", "\n", "velocity = distance / time\n", "velocity" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Quantity Methods and Attributes\n", "\n", "When we multiply a value by units, what we are actually doing is building a `Quantity` object. A `Quantity` has many useful methods and attributes to manipulate its value and give us information about it. Some of the more common and useful ones are:\n", "* `.magnitude`, and `.m`\n", "* `.to()`\n", "* `.units`\n", "\n", "Say we have a 1-foot long ruler that measures distance in whole inches." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ruler = np.arange(1.0, 13.0) * units.inch\n", "ruler" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Perhaps we want to go rogue and create a ruler without units. We can get the *magnitude* of the ruler with the `.magnitude` attribute (or shorthand `.m`)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ruler.m" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But that probably isn't a great idea. Instead, let's convert the units of the ruler to measure in centimeters. We do this using the `.to` method." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ruler = ruler.to('centimeter')\n", "ruler" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After working with our ruler for awhile, maybe we forget what units are attached to it. We can check with the `.units` attribute." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ruler.units" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. MetPy and Units\n", "\n", "Many of the functions in `MetPy` expect their arguments to have units. Though it may seem like slightly more effort, requiring units saves time for everyone. Let's look at a simple example with u and v wind components. Say we want to calculate wind speed and wind direction from these components." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "u = np.array([5., 3., 7., 2., -9.])\n", "v = np.array([-2., 5., -7., 3., 2.])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we try to calculate wind speed using `mpcalc.wind_speed` without applying units, we will get a `ValueError` and an explanation for how to apply units to our values. For example, try uncommenting the line in the following cell and running the cell." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# mpcalc.wind_speed(u, v)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that at the bottom of the error message, an explanation is provided that `u` needs `\"[speed]\"`. This is not a specific unit, but instead a dimensionality. We can apply any units we like to `u` and `v` as long as their dimensionality is speed. Let's use meter / second." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "u = u * units(\"meter / second\")\n", "v = v * units(\"meter / second\")\n", "wind_speed = mpcalc.wind_speed(u, v)\n", "wind_speed" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Great! Now let's get the wind direction with `mpcalc.wind_direction`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wind_dir = mpcalc.wind_direction(u, v)\n", "wind_dir" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that in both cases the results have units attached to them, and `wind_dir` has units of `degree` as we expect." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Gotchas\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are a few places that one can easily get tripped up when using units in an atmospheric science context. Additionally, sometimes units don't play nicely with the data type we are working with, so we need to handle them in a different way." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Millibars versus millibarns\n", "\n", "We will commonly work with pressure values that have units of millibars, or mb. However, the abbreviation mb means something different to `Pint`..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pressure = 500. * units.mb\n", "print(pressure)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A millibarn is a unit of area, which is likely not what we're looking for. To avoid this, we want to spell out millibar when using it as a physical unit." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pressure = 500. * units.millibar\n", "print(pressure)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Temperature\n", "\n", "Sometimes temperature is expressed with units of a *difference*, or a *delta*. These are treated as different units, but can interact with their \"whole\" counterparts. You can read more about temperature conversion [here.](https://pint.readthedocs.io/en/stable/nonmult.html) Otherwise, here are a few examples to consider." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "delta_t = 6. * units.delta_degF\n", "temperature = 72. * units.degF\n", "temperature + delta_t" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temperature.to('kelvin') + delta_t" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "10. * units('degF') - 1. * units('kelvin')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Attaching Units to Masked Arrays\n", "\n", "Finally, some data types don't play with units as nicely as we would like them to. A well-known and common example of this occurs with `numpy.ma.array`, i.e., `numpy` masked arrays. Instead of multiplying a masked array by the units we desire, we need to call the `Quantity` constructor directly." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mask = [False, True, False, False, True]\n", "values = [87., 105., 94., 45., 107.]\n", "\n", "humidity = units.Quantity(np.ma.array(values, mask=mask), 'percent')\n", "humidity" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## See also\n", "\n", "If you need more examples of working with units, check out the [MetPy docs](https://unidata.github.io/MetPy/latest/index.html), [MetPy example gallery](https://unidata.github.io/MetPy/latest/examples/index.html), and [Pint docs](https://pint.readthedocs.io/en/stable/index.html).\n", "\n", "If you feel like you need a bigger challenge, consider working on one or both of the following:\n", "* [Isentropic Analysis Case Study](https://unidata.github.io/pyaos-ams-2021/projects/isentropic_analysis.html)\n", "* [Severe Weather Outbreak Case Study](https://unidata.github.io/pyaos-ams-2021/projects/severe_wx_outbreak.html)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Top\n", "\n", "---" ] } ], "metadata": { "kernelspec": { "display_name": "Python [conda env:pyaos-ams-2021]", "language": "python", "name": "conda-env-pyaos-ams-2021-py" }, "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.1" } }, "nbformat": 4, "nbformat_minor": 4 }