Repository: IAMconsortium/pyam
Pull request #548 (2021-06-22 12:03:23)

Add a unit_mapping attribute to show a variable -> unit dictionary
# Please confirm that this PR has done the following:

- [x] Tests Added
- [x] Documentation Added
- ~Name of contributors Added to AUTHORS.rst~
- [x] Description in RELEASE_NOTES.md Added

# Description of PR

This PR adds an attribute `unit_mapping` to return a dictionary `{variable: unit or list of units}`. This is intended to replace the current function `variables(include_units=True)`.

For illustration, the IamDataFrame 

| model | scenario | region | variable | unit | 2005 | 2010 |
| -------- | -------- | ------ | --------------- | ----- | --- | --- | 
| model_a | scen_a | World | Primary Energy | EJ/yr | 1 | 6 |
| model_a | scen_a | World | Primary Energy\\|Coal | EJ/yr | 0.5 | 3 |
| model_a | scen_b | World | Primary Energy | GWh/yr | 2 | 7 |

will return

```python
df.unit_mapping
> {"Primary Energy": ["EJ/yr", "GWh/yr"], "Primary Energy|Coal": "EJ/yr"}
```

## Unique unit for a variable?

Note that it may be preferable to require that an IamDataFrame has a unique unit for each variable (see the related discussion at #338). Currently, several units are allowed, so this PR simply maintains the current behavior.

danielhuppmann: I know that everyone is super-busy, but @gidden @znicholls @coroa, if you have a minute for a quick look just about the suggested API change, it would be very much appreciated...
codecov[bot]: # [Codecov](https://codecov.io/gh/IAMconsortium/pyam/pull/548?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium) Report
> Merging [#548](https://codecov.io/gh/IAMconsortium/pyam/pull/548?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium) (740a721) into [main](https://codecov.io/gh/IAMconsortium/pyam/commit/7d83a9836b01069d9fe8a2a50a5383beeab0fe7d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium) (7d83a98) will **increase** coverage by `0.1%`.
> The diff coverage is `100.0%`.

[![Impacted file tree graph](https://codecov.io/gh/IAMconsortium/pyam/pull/548/graphs/tree.svg?width=650&height=150&src=pr&token=ThxWlNkonJ&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium)](https://codecov.io/gh/IAMconsortium/pyam/pull/548?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium)

```diff
@@           Coverage Diff           @@
##            main    #548     +/-   ##
=======================================
+ Coverage   93.4%   93.6%   +0.1%     
=======================================
  Files         48      48             
  Lines       5288    5299     +11     
=======================================
+ Hits        4942    4961     +19     
+ Misses       346     338      -8     
```


| [Impacted Files](https://codecov.io/gh/IAMconsortium/pyam/pull/548?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium) | Coverage Δ | |
|---|---|---|
| [tests/test\_unfccc.py](https://codecov.io/gh/IAMconsortium/pyam/pull/548/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium#diff-dGVzdHMvdGVzdF91bmZjY2MucHk=) | `100.0% <ø> (ø)` | |
| [pyam/core.py](https://codecov.io/gh/IAMconsortium/pyam/pull/548/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium#diff-cHlhbS9jb3JlLnB5) | `92.7% <100.0%> (+<0.1%)` | :arrow_up: |
| [tests/test\_core.py](https://codecov.io/gh/IAMconsortium/pyam/pull/548/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium#diff-dGVzdHMvdGVzdF9jb3JlLnB5) | `100.0% <100.0%> (ø)` | |
| [tests/test\_datareader.py](https://codecov.io/gh/IAMconsortium/pyam/pull/548/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium#diff-dGVzdHMvdGVzdF9kYXRhcmVhZGVyLnB5) | `82.6% <0.0%> (+13.0%)` | :arrow_up: |
| [pyam/datareader.py](https://codecov.io/gh/IAMconsortium/pyam/pull/548/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium#diff-cHlhbS9kYXRhcmVhZGVyLnB5) | `100.0% <0.0%> (+50.0%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/IAMconsortium/pyam/pull/548?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/IAMconsortium/pyam/pull/548?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium). Last update [7d83a98...740a721](https://codecov.io/gh/IAMconsortium/pyam/pull/548?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=IAMconsortium).

gidden: lgtm! I have no strong feelings about the name, given that it is not simply a list like `variables`.

Patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 14884f3..c701670 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,4 +1,6 @@
 # Next Release
+
+- [#548](https://github.com/IAMconsortium/pyam/pull/548) Add a `unit_mapping` attribute to show a variable-unit dictionary 
 - [#546](https://github.com/IAMconsortium/pyam/pull/546) Fixed logging for recursive aggregation
 
 # Release v0.12.0
diff --git a/pyam/core.py b/pyam/core.py
index 0775b7a..6b03d63 100755
--- a/pyam/core.py
+++ b/pyam/core.py
@@ -366,6 +366,27 @@ class IamDataFrame(object):
         """Return the list of (unique) units"""
         return get_index_levels(self._data, "unit")
 
+    @property
+    def unit_mapping(self):
+        """Return a dictionary of variables to (list of) correspoding units"""
+
+        def list_or_str(x):
+            x = list(x.drop_duplicates())
+            return x if len(x) > 1 else x[0]
+
+        return (
+            pd.DataFrame(
+                zip(
+                    self._data.index.get_level_values("variable"),
+                    self._data.index.get_level_values("unit"),
+                ),
+                columns=["variable", "unit"],
+            )
+            .groupby("variable")
+            .apply(lambda u: list_or_str(u.unit))
+            .to_dict()
+        )
+
     @property
     def data(self):
         """Return the timeseries data as a long :class:`pandas.DataFrame`"""
@@ -456,6 +477,7 @@ class IamDataFrame(object):
             return pd.Series(get_index_levels(self._data, _var), name=_var)
 
         # else construct dataframe from variable and unit levels
+        deprecation_warning("Use the attribute `unit_mapping` instead.")
         return (
             pd.DataFrame(
                 zip(


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 9ee6106..38b4ab2 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -316,6 +316,14 @@ def test_index_attributes_extra_col(test_pd_df):
     assert df.subannual == ["summer", "winter"]
 
 
+def test_unit_mapping(test_pd_df):
+    """assert that the `unit_mapping` returns the expected dictionary"""
+    test_pd_df.loc[2, "unit"] = "foo"  # replace unit of one row of Primary Energy data
+    obs = IamDataFrame(test_pd_df).unit_mapping
+
+    assert obs == {"Primary Energy": ["EJ/yr", "foo"], "Primary Energy|Coal": "EJ/yr"}
+
+
 def test_model(test_df):
     exp = pd.Series(data=["model_a"], name="model")
     pd.testing.assert_series_equal(test_df.models(), exp)
diff --git a/tests/test_unfccc.py b/tests/test_unfccc.py
index 7b8ef57..6f7b82a 100644
--- a/tests/test_unfccc.py
+++ b/tests/test_unfccc.py
@@ -4,7 +4,7 @@ from pyam.testing import assert_iamframe_equal
 
 
 UNFCCC_DF = pd.DataFrame(
-    [[1990, 1738.137558], [1991, 1537.282312], [1992, 1499.067572]],
+    [[1990, 1609.25345], [1991, 1434.21149], [1992, 1398.38269]],
     columns=["year", "value"],
 )