Подтвердить что ты не робот

"Слишком много индексировщиков" с DataFrame.loc

Я прочитал документы о слайсерах миллион раз, но никогда не обнимал его, поэтому я все еще пытаюсь чтобы определить, как использовать loc, чтобы нарезать a DataFrame с помощью MultiIndex.

Я начну с DataFrame из этого SO-ответа:

                           value
first second third fourth       
A0    B0     C1    D0          2
                   D1          3
             C3    D0          6
                   D1          7
      B1     C1    D0         10
                   D1         11
             C3    D0         14
                   D1         15
A1    B0     C1    D0         18
                   D1         19
             C3    D0         22
                   D1         23
      B1     C1    D0         26
                   D1         27
             C3    D0         30
                   D1         31
A2    B0     C1    D0         34
                   D1         35
             C3    D0         38
                   D1         39
      B1     C1    D0         42
                   D1         43
             C3    D0         46
                   D1         47
A3    B0     C1    D0         50
                   D1         51
             C3    D0         54
                   D1         55
      B1     C1    D0         58
                   D1         59
             C3    D0         62
                   D1         63

Чтобы выбрать только значения A0 и C1, я могу сделать:

In [26]: df.loc['A0', :, 'C1', :]
Out[26]: 
                           value
first second third fourth       
A0    B0     C1    D0          2
                   D1          3
      B1     C1    D0         10
                   D1         11

который также выполняет выбор из трех уровней и даже с кортежами:

In [28]: df.loc['A0', :, ('C1', 'C2'), 'D1']
Out[28]: 
                           value
first second third fourth       
A0    B0     C1    D1          3
             C2    D1          5
      B1     C1    D1         11
             C2    D1         13

Пока, интуитивно и блестяще.

Итак, почему я не могу выбрать все значения с первого уровня индекса?

In [30]: df.loc[:, :, 'C1', :]
---------------------------------------------------------------------------
IndexingError                             Traceback (most recent call last)
<ipython-input-30-57b56108d941> in <module>()
----> 1 df.loc[:, :, 'C1', :]

/usr/local/lib/python2.7/dist-packages/pandas/core/indexing.pyc in __getitem__(self, key)
   1176     def __getitem__(self, key):
   1177         if type(key) is tuple:
-> 1178             return self._getitem_tuple(key)
   1179         else:
   1180             return self._getitem_axis(key, axis=0)

/usr/local/lib/python2.7/dist-packages/pandas/core/indexing.pyc in _getitem_tuple(self, tup)
    694 
    695         # no multi-index, so validate all of the indexers
--> 696         self._has_valid_tuple(tup)
    697 
    698         # ugly hack for GH #836

/usr/local/lib/python2.7/dist-packages/pandas/core/indexing.pyc in _has_valid_tuple(self, key)
    125         for i, k in enumerate(key):
    126             if i >= self.obj.ndim:
--> 127                 raise IndexingError('Too many indexers')
    128             if not self._has_valid_type(k, i):
    129                 raise ValueError("Location based indexing can only have [%s] "

IndexingError: Too many indexers

Неужели это не намеренное поведение?

Примечание. Я знаю, что это возможно с помощью df.xs('C1', level='third'), но текущее поведение .loc кажется непоследовательным.

4b9b3361

Ответ 1

Чтобы быть в безопасности (в смысле: это будет работать во всех случаях), вам нужно индексировать индекс строки и столбцы, для которых вы можете легко использовать pd.IndexSlice:

In [26]: idx = pd.IndexSlice

In [27]: df.loc[idx[:, :, 'C1', :],:]
Out[27]:
                           value
first second third fourth
A0    B0     C1    D0          2
                   D1          3
      B1     C1    D0         10
                   D1         11
A1    B0     C1    D0         18
                   D1         19
      B1     C1    D0         26
                   D1         27
A2    B0     C1    D0         34
                   D1         35
      B1     C1    D0         42
                   D1         43
A3    B0     C1    D0         50
                   D1         51
      B1     C1    D0         58
                   D1         59

Здесь idx[:, :, 'C1', :] - более простой способ написать [slice(None), slice(None),'C1', slice(None)]. Вместо pd.IndexSlice вы также можете использовать np.s_, который немного короче.

Причина, по которой работают другие, я не совсем уверен. Но см. Примечание в документации здесь: http://pandas.pydata.org/pandas-docs/stable/advanced.html#using-slicers (первый красный предупреждающий блок), где указано, что:

Вы должны указать все оси в спецификаторе .loc, что означает индекс для индекса и столбцов. Их некоторые неоднозначные случаи, когда пропущенный индекс можно было неправильно интерпретировать как индексирование обеих осей, а не сказать MuliIndex для строк.

Ответ 2

Причина, по которой это не работает, связана с необходимостью указания оси индексации (упомянутой в http://pandas.pydata.org/pandas-docs/stable/advanced.html). Альтернативное решение вашей проблемы состоит в том, чтобы просто сделать это:

df.loc(axis=0)[:, :, 'C1', :]

Pandas иногда путается, когда индексы похожи или содержат одинаковые значения. Если у вас должен быть столбец с именем "C1" или что-то, что вам также нужно будет сделать в этом стиле для нарезки/выбора.