Repository: IAMconsortium/pyam (python)

Issue:
IamDataFrame constructor should take pd.DataFrame with index
Currently, the `IamDataFrame.__init__()` can take a `pd.DataFrame` with columns `['model', 'scenario' , ...]`, but it fails if these identifiers are given as an index.

This would be a nice-to-have feature so that the `pd.DataFrame` returned by the `timeseries()` function can be turned back into an `IamDataFrame`. This currently requires a `reset_index()`.

Pull request description:
initialise IamDataFrame with index
# Please confirm that this PR has done the following:

- [x] Tests Added
- [x] Documentation Added
- [x] Description in RELEASE_NOTES.md Added

# Description of PR

This PR extends the IamDataFrame constructor to take a pd.DataFrame with a meaningful index, for example the dataframe returned by `IamDataFrame.timeseries()`. Closes #77 

Patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 3dbce36..cfdcc6c 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,6 +1,7 @@
 
 # Next Release
 
+- (#93)[https://github.com/IAMconsortium/pyam/pull/93] IamDataFrame can be initilialzed from pd.DataFrame with index
 - (#92)[https://github.com/IAMconsortium/pyam/pull/92] Adding `$` to the pseudo-regexp syntax in `pattern_match()`, adds override option
 - (#90)[https://github.com/IAMconsortium/pyam/pull/90] Adding a function to `set_panel_label()` as part of the plotting library
 - (#87)[https://github.com/IAMconsortium/pyam/pull/87] Extending `rename()` to work with model and scenario names
diff --git a/pyam/core.py b/pyam/core.py
index f338d58..c0ea01d 100644
--- a/pyam/core.py
+++ b/pyam/core.py
@@ -52,6 +52,7 @@ class IamDataFrame(object):
         data: ixmp.TimeSeries, ixmp.Scenario, pd.DataFrame or data file
             an instance of an TimeSeries or Scenario (requires `ixmp`),
             or pd.DataFrame or data file with IAMC-format data columns.
+            A pd.DataFrame can have the required data as columns or index.
 
             Special support is provided for data files downloaded directly from
             IIASA SSP and RCP databases. If you run into any problems loading
diff --git a/pyam/utils.py b/pyam/utils.py
index 4f6d2a7..ceed03a 100644
--- a/pyam/utils.py
+++ b/pyam/utils.py
@@ -169,6 +169,10 @@ def format_data(df):
             df['scenario'] = scen.apply(
                 lambda s: '-'.join(s.split('-')[1:]).strip())
 
+    # reset the index if meaningful entries are included there
+    if not list(df.index.names) == [None]:
+        df.reset_index(inplace=True)
+
     # format columns to lower-case and check that all required columns exist
     if not set(IAMC_IDX).issubset(set(df.columns)):
         missing = list(set(IAMC_IDX) - set(df.columns))


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 6903a21..9bf6e5b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -11,7 +11,7 @@ TEST_DATA_DIR = os.path.join(here, 'data')
 
 
 TEST_DF = pd.DataFrame([
-    ['a_model', 'a_scenario', 'World', 'Primary Energy', 'EJ/y', 1, 6],
+    ['a_model', 'a_scenario', 'World', 'Primary Energy', 'EJ/y', 1, 6.],
     ['a_model', 'a_scenario', 'World', 'Primary Energy|Coal', 'EJ/y', 0.5, 3],
     ['a_model', 'a_scenario2', 'World', 'Primary Energy', 'EJ/y', 2, 7],
 ],
@@ -37,6 +37,11 @@ def test_df():
     yield df
 
 
+@pytest.fixture(scope="function")
+def test_pd_df():
+    yield TEST_DF
+
+
 @pytest.fixture(scope="function")
 def meta_df():
     df = IamDataFrame(data=TEST_DF)
diff --git a/tests/test_core.py b/tests/test_core.py
index fd229c1..ced7a2c 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -28,6 +28,16 @@ df_filter_by_meta_nonmatching_idx = pd.DataFrame([
 ).set_index(['model', 'region'])
 
 
+def test_init_df_with_index(test_pd_df):
+    df = IamDataFrame(test_pd_df.set_index(META_IDX))
+    pd.testing.assert_frame_equal(df.timeseries().reset_index(), test_pd_df)
+
+
+def test_init_df_from_timeseries(test_df):
+    df = IamDataFrame(test_df.timeseries())
+    pd.testing.assert_frame_equal(df.timeseries(), test_df.timeseries())
+
+
 def test_get_item(test_df):
     assert test_df['model'].unique() == ['a_model']