Repository: IAMconsortium/pyam
Pull request #63 (2018-04-27 08:00:22)

Add conv unit function

danielhuppmann: @gidden, I am not sure if the input arg format using a dictionary of str -> list is most appropriate here - would a tuple be better? Or some other input args structure?
gidden: looks good, a few more suggestions added to those by @danielhuppmann 
gidden: this will probably need a rebase
gidden: lgtm, @danielhuppmann ?

Patch:
diff --git a/pyam/core.py b/pyam/core.py
index bbbde2e..c150108 100644
--- a/pyam/core.py
+++ b/pyam/core.py
@@ -409,7 +409,7 @@ class IamDataFrame(object):
             return df
 
     def rename(self, mapping, inplace=False):
-        """ Rename and aggregate column entries using groupby.sum()
+        """Rename and aggregate column entries using groupby.sum()
 
         Parameters
         ----------
@@ -429,7 +429,28 @@ class IamDataFrame(object):
         if not inplace:
             return ret
 
-    def filter(self, filters=None, keep=True, inplace=False,**kwargs):
+    def convert_unit(self, conversion_mapping, inplace=False):
+        """Converts units based on provided unit conversion factors
+
+        Parameters
+        ----------
+        conversion_mapping: dict
+            for each unit for which a conversion should be carried out, provide current unit 
+            and target unit and conversion factor
+            {<current unit>: [<target unit>, <conversion factor>]}
+        inplace: bool, default False
+            if True, do operation inplace and return None
+        """
+        ret = copy.deepcopy(self) if not inplace else self
+        for current_unit, (new_unit, factor) in conversion_mapping.items():
+            factor = pd.to_numeric(factor)
+            where = ret.data['unit'] == current_unit
+            ret.data.loc[where, 'value'] *= factor
+            ret.data.loc[where, 'unit'] = new_unit
+        if not inplace:
+            return ret
+
+    def filter(self, filters=None, keep=True, inplace=False, **kwargs):
         """Return a filtered IamDataFrame (i.e., a subset of current data)
 
         Parameters
@@ -450,7 +471,8 @@ class IamDataFrame(object):
                 so ``range(2010,2015)`` is interpreted as ``[2010, ..., 2014]``
         """
         if filters is not None:
-            warnings.warn('`filters` keyword argument in filters() is deprecated and will be removed in the next release')
+            warnings.warn(
+                '`filters` keyword argument in filters() is deprecated and will be removed in the next release')
             kwargs.update(filters)
 
         _keep = _apply_filters(self.data, self.meta, kwargs)


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 79ec236..e91be53 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -261,12 +261,14 @@ def test_load_metadata(test_df):
     exp = pd.DataFrame(dct).set_index(['model', 'scenario'])
     pd.testing.assert_series_equal(obs['category'], exp['category'])
 
+
 def test_load_SSP_database_downloaded_file(test_df):
     obs_df = IamDataFrame(os.path.join(
         TEST_DATA_DIR, 'test_SSP_database_raw_download.xlsx')
     )
     pd.testing.assert_frame_equal(obs_df.as_pandas(), test_df.as_pandas())
 
+
 def test_append(test_df):
     df2 = test_df.append(other=os.path.join(
         TEST_DATA_DIR, 'testing_data_2.csv'))
@@ -461,6 +463,7 @@ def test_48b():
 
     pd.testing.assert_frame_equal(obs, exp, check_index_type=False)
 
+
 def test_rename():
     df = IamDataFrame(pd.DataFrame([
         ['model', 'scen', 'SST', 'test_1', 'unit', 1, 5],
@@ -482,3 +485,27 @@ def test_rename():
     )).data.sort_values(by='region').reset_index(drop=True)
 
     pd.testing.assert_frame_equal(obs, exp, check_index_type=False)
+
+
+def test_convert_unit():
+    df = IamDataFrame(pd.DataFrame([
+        ['model', 'scen', 'SST', 'test_1', 'A', 1, 5],
+        ['model', 'scen', 'SDN', 'test_2', 'unit', 2, 6],
+        ['model', 'scen', 'SST', 'test_3', 'C', 3, 7],
+    ], columns=['model', 'scenario', 'region',
+                'variable', 'unit', 2005, 2010],
+    ))
+
+    unit_conv = {'A': ['B', 5], 'C': ['D', 3]}
+
+    obs = df.convert_unit(unit_conv).data.reset_index(drop=True)
+
+    exp = IamDataFrame(pd.DataFrame([
+        ['model', 'scen', 'SST', 'test_1', 'B', 5, 25],
+        ['model', 'scen', 'SDN', 'test_2', 'unit', 2, 6],
+        ['model', 'scen', 'SST', 'test_3', 'D', 9, 21],
+    ], columns=['model', 'scenario', 'region',
+                'variable', 'unit', 2005, 2010],
+    )).data.reset_index(drop=True)
+
+    pd.testing.assert_frame_equal(obs, exp, check_index_type=False)