Repository: IAMconsortium/pyam
Pull request #176 (2019-01-23 15:38:29)

Correct line plot title
# Please confirm that this PR has done the following:

- [x] Tests Added
- [x] Documentation Added
- [x] Description in RELEASE_NOTES.md Added

## Adding to RELEASE_NOTES.md (remove section after adding to RELEASE_NOTES.md)

Please add a single line in the release notes similar to the following:

```
- (#XX)[http://link-to-pr.com] Added feature which does something
```

# Description of PR

Please describe the changes introduced by this PR.


coveralls: 
[![Coverage Status](https://coveralls.io/builds/21233930/badge)](https://coveralls.io/builds/21233930)

Coverage decreased (-10.0%) to 73.885% when pulling **0a99b1dcfde874f3f63aa98db5d903a7a07d9e90 on OFR-IIASA:correct_line_plot_title** into **22c0b45fb2c665b33f5d5d4e4872c0ed6f236af4 on IAMconsortium:master**.


Patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index f86de86..980f0ee 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,7 +1,9 @@
 
 # Next Release
 
+- [#176](https://github.com/IAMconsortium/pyam/pull/176) Corrected title setting operation in line_plot function
 - [#175](https://github.com/IAMconsortium/pyam/pull/175) Update link to tutorial in readme.md 
+- [#174](https://github.com/IAMconsortium/pyam/pull/174) Add a function `difference()` to compare two IamDataFrames
 - [#171](https://github.com/IAMconsortium/pyam/pull/171) Fix a bug when reading from an `ixmp.TimeSeries` object, refactor to mitigate circular dependency
 - [#162](https://github.com/IAMconsortium/pyam/pull/162) Add a function to sum and append timeseries components to an aggregate variable
 - [#152](https://github.com/IAMconsortium/pyam/pull/152) Fix bug where scatter plots did not work with property metadata when using two variables (#136, #152)
diff --git a/pyam/core.py b/pyam/core.py
index c21a4b1..bddd8c6 100644
--- a/pyam/core.py
+++ b/pyam/core.py
@@ -1418,3 +1418,25 @@ def filter_by_meta(data, df, join_meta=False, **kwargs):
         data.index.name = None
 
     return data
+
+
+def compare(left, right, left_label='left', right_label='right',
+            drop_close=True, **kwargs):
+    """Compare the data in two IamDataFrames and return a pd.DataFrame
+
+    Parameters
+    ----------
+    left, right: IamDataFrames
+        the IamDataFrames to be compared
+    left_label, right_label: str, default `left`, `right`
+        column names of the returned dataframe
+    drop_close: bool, default True
+        remove all data where `left` and `right` are close
+    kwargs: passed to `np.isclose()`
+    """
+    ret = pd.concat({right_label: right.data.set_index(right._LONG_IDX),
+                     left_label: left.data.set_index(left._LONG_IDX)}, axis=1)
+    ret.columns = ret.columns.droplevel(1)
+    if drop_close:
+        ret = ret[~np.isclose(ret[left_label], ret[right_label], **kwargs)]
+    return ret[[right_label, left_label]]
diff --git a/pyam/plotting.py b/pyam/plotting.py
index d586ba8..c142028 100644
--- a/pyam/plotting.py
+++ b/pyam/plotting.py
@@ -1,5 +1,4 @@
 import itertools
-import os
 import warnings
 
 try:
@@ -135,7 +134,7 @@ def reshape_bar_plot(df, x, y, bars):
 @requires_package(gpd, 'Requires geopandas')
 @lru_cache()
 def read_shapefile(fname, region_col=None, **kwargs):
-    """Read a shapefile for use in regional plots. Shapefiles must have a 
+    """Read a shapefile for use in regional plots. Shapefiles must have a
     column denoted as "region".
 
     Parameters
@@ -156,9 +155,9 @@ def read_shapefile(fname, region_col=None, **kwargs):
 
 @requires_package(gpd, 'Requires geopandas')
 @requires_package(cartopy, 'Requires cartopy')
-def region_plot(df, column='value', ax=None, crs=None, gdf=None, add_features=True,
-                vmin=None, vmax=None, cmap=None, cbar=True, legend=False,
-                title=True):
+def region_plot(df, column='value', ax=None, crs=None, gdf=None,
+                add_features=True, vmin=None, vmax=None, cmap=None,
+                cbar=True, legend=False, title=True):
     """Plot data on a map.
 
     Parameters
@@ -181,10 +180,10 @@ def region_plot(df, column='value', ax=None, crs=None, gdf=None, add_features=Tr
     cmap : string, optional
         The colormap to use.
     cbar : bool or dictionary, optional, default: True
-        Add a colorbar. If a dictionary is provided, it will be used as keyword 
+        Add a colorbar. If a dictionary is provided, it will be used as keyword
         arguments in creating the colorbar.
     legend : bool or dictionary, optional, default: False
-        Add a legend. If a dictionary is provided, it will be used as keyword 
+        Add a legend. If a dictionary is provided, it will be used as keyword
         arguments in creating the legend.
     title : bool or string, optional
         Display a default or custom title.
@@ -293,7 +292,8 @@ def pie_plot(df, value='value', category='variable',
     """
     for col in set(SORT_IDX) - set([category]):
         if len(df[col].unique()) > 1:
-            msg = 'Can not plot multiple {}s in pie_plot with value={}, category={}'
+            msg = 'Can not plot multiple {}s in pie_plot with value={},' +\
+                ' category={}'
             raise ValueError(msg.format(col, value, category))
 
     if ax is None:
@@ -378,7 +378,8 @@ def stack_plot(df, x='year', y='value', stack='variable',
     colors = []
     for key in _df.columns:
         c = next(defaults)
-        if 'color' in rc and stack in rc['color'] and key in rc['color'][stack]:
+        if 'color' in rc and stack in rc['color'] and\
+                key in rc['color'][stack]:
             c = rc['color'][stack][key]
         colors.append(c)
 
@@ -797,14 +798,15 @@ def line_plot(df, x='year', y='value', ax=None, legend=None, title=True,
     ax.set_ylabel(ylabel)
 
     # build a default title if possible
-    _title = []
-    for var in ['model', 'scenario', 'region', 'variable']:
-        if var in df.columns.names:
-            values = df.columns.get_level_values(var).unique()
-            if len(values) == 1:
-                _title.append('{}: {}'.format(var, values[0]))
-    if title and _title:
-        ax.set_title(' '.join(_title))
+    if title:
+        default_title = []
+        for var in ['model', 'scenario', 'region', 'variable']:
+            if var in df.columns.names:
+                values = df.columns.get_level_values(var).unique()
+                if len(values) == 1:
+                    default_title.append('{}: {}'.format(var, values[0]))
+        title = ' '.join(default_title) if title is True else title
+        ax.set_title(title)
 
     return ax, handles, labels
 


Test patch:
diff --git a/tests/test_feature_compare.py b/tests/test_feature_compare.py
new file mode 100644
index 0000000..d6074a9
--- /dev/null
+++ b/tests/test_feature_compare.py
@@ -0,0 +1,29 @@
+import copy
+import numpy as np
+import pandas as pd
+from pyam import compare, IAMC_IDX
+
+
+def test_compare(meta_df):
+    clone = copy.deepcopy(meta_df)
+    clone.data.iloc[0, clone.data.columns.get_loc('value')] = 2
+    clone.rename({'variable': {'Primary Energy|Coal': 'Primary Energy|Gas'}},
+                 inplace=True)
+
+    obs = compare(meta_df, clone, right_label='meta_df', left_label='clone')
+
+    exp = pd.DataFrame([
+        ['Primary Energy', 'EJ/y', 2005, 2, 1],
+        ['Primary Energy|Coal', 'EJ/y', 2005, np.nan, 0.5],
+        ['Primary Energy|Coal', 'EJ/y', 2010, np.nan, 3],
+        ['Primary Energy|Gas', 'EJ/y', 2005, 0.5, np.nan],
+        ['Primary Energy|Gas', 'EJ/y', 2010, 3, np.nan],
+    ],
+        columns=['variable', 'unit', 'year', 'meta_df', 'clone'],
+    )
+    exp['model'] = 'a_model'
+    exp['scenario'] = 'a_scenario'
+    exp['region'] = 'World'
+    exp = exp.set_index(IAMC_IDX + ['year'])
+
+    pd.testing.assert_frame_equal(obs, exp)