Skip to content

Commit fb577b7

Browse files
committed
ENH: allow index to be referenced by name
closes #8162, #10816
1 parent 23810e5 commit fb577b7

File tree

4 files changed

+120
-6
lines changed

4 files changed

+120
-6
lines changed

doc/source/whatsnew/v0.18.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ Other enhancements
432432
- Added Google ``BigQuery`` service account authentication support, which enables authentication on remote servers. (:issue:`11881`). For further details see :ref:`here <io.bigquery_authentication>`
433433
- ``HDFStore`` is now iterable: ``for k in store`` is equivalent to ``for k in store.keys()`` (:issue:`12221`).
434434
- The entire codebase has been ``PEP``-ified (:issue:`12096`)
435+
- Index (or index levels, with a MultiIndex) can now be referenced like column names (:issue:`8162`, :issue:`10816`).
435436

436437
.. _whatsnew_0180.api_breaking:
437438

pandas/core/frame.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -2018,7 +2018,30 @@ def _getitem_array(self, key):
20182018
indexer = key.nonzero()[0]
20192019
return self.take(indexer, axis=0, convert=False)
20202020
else:
2021-
indexer = self.ix._convert_to_indexer(key, axis=1)
2021+
try:
2022+
indexer = self.ix._convert_to_indexer(key, axis=1)
2023+
2024+
except KeyError:
2025+
if self.index.name in key:
2026+
ix_name = self.index.name
2027+
ix_ix = key.index(ix_name)
2028+
2029+
elif (isinstance(self.index, MultiIndex) and
2030+
any(item in self.index.names for item in key)):
2031+
for item in key:
2032+
if item in self.index.names:
2033+
ix_name = item
2034+
ix_ix = key.index(item)
2035+
2036+
else:
2037+
raise
2038+
2039+
key.remove(ix_name)
2040+
ix_col = self[ix_name]
2041+
other_cols = self[key]
2042+
other_cols.insert(ix_ix, ix_name, ix_col)
2043+
return other_cols
2044+
20222045
return self.take(indexer, axis=1, convert=True)
20232046

20242047
def _getitem_multilevel(self, key):

pandas/core/generic.py

+20-5
Original file line numberDiff line numberDiff line change
@@ -1306,8 +1306,20 @@ def _get_item_cache(self, item):
13061306
cache = self._item_cache
13071307
res = cache.get(item)
13081308
if res is None:
1309-
values = self._data.get(item)
1310-
res = self._box_item_values(item, values)
1309+
try:
1310+
values = self._data.get(item)
1311+
res = self._box_item_values(item, values)
1312+
except KeyError:
1313+
if hasattr(self, 'index') and self.index.name == item:
1314+
res = self.index.to_series()
1315+
1316+
elif (isinstance(self.index, MultiIndex) and
1317+
item in self.index.names):
1318+
res = pd.Series(self.index.get_level_values(item).values,
1319+
index=self.index, name=item)
1320+
1321+
else:
1322+
raise
13111323
cache[item] = res
13121324
res._set_as_cached(item, self)
13131325

@@ -2623,10 +2635,13 @@ def __getattr__(self, name):
26232635
if (name in self._internal_names_set or name in self._metadata or
26242636
name in self._accessors):
26252637
return object.__getattribute__(self, name)
2626-
else:
2627-
if name in self._info_axis:
2638+
elif (
2639+
name in self._info_axis or
2640+
name == self.index.name or
2641+
(isinstance(self.index, MultiIndex) and name in self.index.names)
2642+
):
26282643
return self[name]
2629-
return object.__getattribute__(self, name)
2644+
return object.__getattribute__(self, name)
26302645

26312646
def __setattr__(self, name, value):
26322647
"""After regular attribute access, try setting the name

pandas/tests/test_generic.py

+75
Original file line numberDiff line numberDiff line change
@@ -1853,6 +1853,81 @@ def test_to_xarray(self):
18531853
expected,
18541854
check_index_type=False)
18551855

1856+
def test_getitem_index(self):
1857+
# GH8162
1858+
idx = pd.Index(list('abc'), name='idx')
1859+
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]}, index=idx)
1860+
expected = pd.Series(['a', 'b', 'c'], index=idx, name='idx')
1861+
1862+
assert_series_equal(df['idx'], expected)
1863+
assert_series_equal(df.idx, expected)
1864+
1865+
def test_getitem_index_listlike(self):
1866+
idx = pd.Index(list('abc'), name='idx')
1867+
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]}, index=idx)
1868+
assert_frame_equal(
1869+
df[['idx', 'B']],
1870+
pd.DataFrame([
1871+
['a', 4],
1872+
['b', 5],
1873+
['c', 6],
1874+
],
1875+
columns=['idx', 'B'],
1876+
index=idx)
1877+
)
1878+
assert_frame_equal(
1879+
df[['idx', 'A', 'B']],
1880+
pd.DataFrame([
1881+
['a', 1, 4],
1882+
['b', 2, 5],
1883+
['c', 3, 6],
1884+
],
1885+
columns=['idx', 'A', 'B'],
1886+
index=idx)
1887+
)
1888+
1889+
def test_getitem_multiindex_level(self):
1890+
# GH10816
1891+
idx = pd.MultiIndex.from_product([list('abc'), list('fg')],
1892+
names=['lev0', 'lev1'])
1893+
df = pd.DataFrame({'A': range(6), 'B': range(10, 16)}, index=idx)
1894+
expected = pd.Series(list('aabbcc'), index=idx, name='lev0')
1895+
1896+
assert_series_equal(df['lev0'], expected)
1897+
assert_series_equal(df.lev0, expected)
1898+
1899+
def test_getitem_multiindex_level_listlike(self):
1900+
idx = pd.MultiIndex.from_product([list('abc'), list('fg')],
1901+
names=['lev0', 'lev1'])
1902+
df = pd.DataFrame({'A': range(6), 'B': range(10, 16)}, index=idx)
1903+
assert_frame_equal(
1904+
df[['A', 'lev1']],
1905+
pd.DataFrame([
1906+
[0, 'f'],
1907+
[1, 'g'],
1908+
[2, 'f'],
1909+
[3, 'g'],
1910+
[4, 'f'],
1911+
[5, 'g'],
1912+
],
1913+
columns=['A', 'lev1'],
1914+
index=idx)
1915+
)
1916+
1917+
assert_frame_equal(
1918+
df[['A', 'B', 'lev1', 'lev0']],
1919+
pd.DataFrame([
1920+
[0, 10, 'f', 'a'],
1921+
[1, 11, 'g', 'a'],
1922+
[2, 12, 'f', 'b'],
1923+
[3, 13, 'g', 'b'],
1924+
[4, 14, 'f', 'c'],
1925+
[5, 15, 'g', 'c'],
1926+
],
1927+
columns=['A', 'B', 'lev1', 'lev0'],
1928+
index=idx)
1929+
)
1930+
18561931

18571932
class TestPanel(tm.TestCase, Generic):
18581933
_typ = Panel

0 commit comments

Comments
 (0)