{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Performing unit conversions\n", "\n", "Conversion of timeseries data units is one of the most tedious aspects of modelling and scenario analysis -\n", "and it is a frequent source for errors!\n", "\n", "The **pyam** function [convert_unit()](https://pyam-iamc.readthedocs.io/en/stable/api/iamdataframe.html#pyam.IamDataFrame.convert_unit) can support and simplify this task.\n", "The function uses the Python package [pint](https://pint.readthedocs.io),\n", "which natively handles conversion of standard (SI) units and commonly used equivalents \n", "(e.g., exajoule to terawatt-hours, `EJ -> TWh`).\n", "The **pint** package can also parse combined units (e.g., exajoule per year, `EJ/yr`).\n", "\n", "To better support common use cases when working with energy systems analysis and integrated-assessment scenarios,\n", "the default [pint.UnitRegistry](https://pint.readthedocs.io/en/stable/developers_reference.html#pint.UnitRegistry)\n", "used by **pyam** loads the unit definitions collected at [IAMconsortium/units](https://github.com/IAMconsortium/units).\n", "This repository provides a wide range of conversion factors in a **pint**-compatible format\n", "so that they can easily be used across multiple applications (**pyam** is just one of them).\n", "\n", "
\n", " \n", "If you have suggestions for additional units to be handled in **pyam** by default,\n", "please [start an issue](https://github.com/IAMconsortium/units/issues) in the units repository -\n", "or make a pull request!\n", "\n", "
\n", "\n", "## Overview\n", "\n", "This notebook illustrates the following features:\n", "\n", "0. Define timeseries data and initialize an **IamDataFrame**\n", "1. Use the default **pint** unit conversion\n", "2. Use a unit & conversion factor from the [IAMconsortium/units](https://github.com/IAMconsortium/units) repository\n", "3. Use a custom conversion factor\n", "4. Use contexts to specify conversion metrics\n", "5. More advanced use cases with a unit registry" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 0. Define timeseries data and initialize an IamDataFrame\n", "\n", "This tutorial uses a scenario similar to the data in the **first-steps tutorial** (here on\n", "[GitHub](https://github.com/IAMconsortium/pyam/blob/master/doc/source/tutorials/pyam_first_steps.ipynb)\n", "and on [read the docs](https://pyam-iamc.readthedocs.io/en/stable/tutorials/pyam_first_steps.html)). \n", "Please read that tutorial for the reference and further information." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "from pyam import IAMC_IDX, IamDataFrame\n", "\n", "UNIT_DF = pd.DataFrame(\n", " [\n", " [\n", " \"MESSAGEix-GLOBIOM 1.0\",\n", " \"CD-LINKS_NPi\",\n", " \"World\",\n", " \"Primary Energy\",\n", " \"EJ/yr\",\n", " 500.74,\n", " 636.79,\n", " 809.93,\n", " 1284.78,\n", " ],\n", " [\n", " \"MESSAGEix-GLOBIOM 1.0\",\n", " \"CD-LINKS_NPi\",\n", " \"World\",\n", " \"Emissions|CH4\",\n", " \"Mt CH4/yr\",\n", " 327.92,\n", " 354.35,\n", " 377.88,\n", " 403.98,\n", " ],\n", " ],\n", " columns=IAMC_IDX + [2010, 2030, 2050, 2100],\n", ")\n", "\n", "df = IamDataFrame(UNIT_DF)\n", "df.timeseries()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Use the default pint unit conversion\n", "\n", "As a first step, we illustrate unit conversion between \"standard formats\",\n", "i.e., units that **pint** knows by default.\n", "\n", "In this particular case, we convert exajoule to petawatthours, `EJ/yr -> PWh/yr`.\n", "Note that the timeseries data for other units (CO2 emissions in this case) are not changed." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.convert_unit(\"EJ/yr\", to=\"PWh/yr\").timeseries()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The **pint** package usually does a good job at parsing orders of magnitude (peta, giga, mega, milli, ...)\n", "and their abbreviations (`P`, `G`, `M`, `m`, ...)\n", "as well as common units (centimeter, inch, kilometer, mile).\n", "It also handles combined units like exajoule per year with various spellings:\n", "`PWh/yr`, `PWh / yr` and `petawatthour / year` will all be treated as synonyms by the conversion.\n", "The only difference is the format in the resulting **IamDataFrame**.\n", "\n", "[Read the docs](https://pint.readthedocs.io) for more information!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.convert_unit(\"EJ/yr\", to=\"petawatthour / year\").timeseries()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Use a conversion factor from the shared energy & IAM units repository\n", "\n", "The **pint** package includes standard units, but many units often encountered in the context of energy systems analysis and integrated assessment scenarios are not defined by default.\n", "\n", "Therefore, the [IAMconsortium/units](https://github.com/IAMconsortium/units) repository\n", "provides a common location to define such units.\n", "The **pyam** package loads these definitions and uses them by default in any unit conversion.\n", "\n", "One entry defined there is 'tons of coal equivalent' (`tce`) as a measure of energy (content).\n", "This is used in the next cell." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.convert_unit(\"EJ/yr\", to=\"Gtce/yr\").timeseries()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Use a custom conversion factor\n", "\n", "In some cases, a user needs to specify a custom unit.\n", "The `convert_unit()` function supports that by specifying a `factor` as a keyword argument." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.convert_unit(\"EJ/yr\", to=\"my_unit\", factor=2.3).timeseries()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Use contexts to specify conversion metrics\n", "\n", "There are unit conversions where no \"default\" factor exists.\n", "One such case is calculating the CO2-equivalent of CH4 emissions (or other greenhouse gases),\n", "because the conversion depends on the species' \"global warming potential\"\n", "and estimates for that potential are updated regularly in the literature.\n", "\n", "To facilitate such use cases, **pint** provides \"contexts\" to allow specifying the appropriate metric.\n", "The [IAMconsortium/units](https://github.com/IAMconsortium/units) parametrizes multiple contexts\n", "for the conversion of greenhouse gases;\n", "see the [emissions module](https://github.com/IAMconsortium/units/blob/master/modules/emissions) for details.\n", "\n", "Performing a unit conversion with context is illustrated below using the IPCC AR5-GWP100 factor;\n", "in this situation, not specifying a context would result in a **pint.DimensionalityError**." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.convert_unit(\"Mt CH4/yr\", to=\"Mt CO2e/yr\", context=\"AR5GWP100\").timeseries()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "When working with contexts, it is important to track the information which metric was used.\n", "This can be done either in the metadata of the resulting data (file)\n", "or directly in the unit (or variable) of the timeseries.\n", "See an illustration below for a simple workflow.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "gwp = \"AR5GWP100\"\n", "target = \"Mt CO2e/yr\"\n", "(\n", " df.convert_unit(\"Mt CH4/yr\", to=target, context=gwp)\n", " .rename(unit={target: f\"{target} ({gwp})\"})\n", " .timeseries()\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. More advanced use cases with a unit registry\n", "\n", "For more advanced use cases, **pyam** supports two further features: first, it can sometimes be useful\n", "to work with the **UnitRegistry** used by default directly. This registry can be accessed\n", "via [pint.get_application_registry()](https://pint.readthedocs.io/en/latest/developers_reference.html#pint.get_application_registry)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pint\n", "\n", "pint.get_application_registry()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In other use cases, it can be helpful to use one (or several) specific registries\n", "instead of the default application registry.\n", "The `convert_unit()` function therefore allows passing a `registry` as a keyword argument.\n", "\n", "The specifications below are the same as the example in section 3." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ureg = pint.UnitRegistry()\n", "ureg.define(\"my_unit = 1 / 2.3 * EJ/yr\")\n", "\n", "df.convert_unit(\"EJ/yr\", to=\"my_unit\", registry=ureg).timeseries()" ] } ], "metadata": { "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.10.0" } }, "nbformat": 4, "nbformat_minor": 2 }