Многоуровневый индекс / Продвинутый индексирование
В этом разделе рассматривается индексирование с помощью MultiIndex и более продвинутыми функциями индексирования.
См. Индексирование и выбор данных для общей документации по индексированию.
Предупреждение
Возвращается копия или ссылка при выполнении операции присваивания, может зависеть от контекста. Это иногда называется chained assignment и следует избегать. См. Возвращение представления или копии
Предупреждение
В версии 0.15.0 Index был внутренне переработан, чтобы больше не наследовать от ndarray, а вместо этого наследовать от PandasObject, аналогично остальным объектам pandas. Это должно быть прозрачное изменение с очень ограниченными последствиями для API (См. Внутренняя рефакторинг)
См. справочник для некоторых продвинутых стратегий
Иерархическое индексирование (Многоуровневый индекс)
Иерархическое/многоуровневое индексирование очень полезно, поскольку открывает двери для довольно сложного анализа и обработки данных, особенно при работе с данными более высокой размерности. По сути, это позволяет хранить и обрабатывать данные с произвольным количеством измерений в структурах данных меньшей размерности, таких как Series (1d) и DataFrame (2d).
В этом разделе мы покажем, что именно мы подразумеваем под «иерархическим» индексированием и как оно интегрируется со всеми функциями индексирования pandas, описанными выше и в предыдущих разделах. Позже, при обсуждении группировки и преобразования и изменения формы данных, мы покажем нетривиальные приложения, чтобы проиллюстрировать, как это помогает структурировать данные для анализа.
См. справочник для некоторых продвинутых стратегий
Создание объекта MultiIndex (иерархический индекс)
Объект MultiIndex является иерархическим аналогом стандартного объекта Index, который обычно хранит метки осей в объектах pandas. Можно представить себе MultiIndex как массив кортежей, где каждый кортеж уникален. MultiIndex может быть создан из списка массивов (используя MultiIndex.from_arrays), массива кортежей (используя MultiIndex.from_tuples) или пересекающегося набора итерируемых объектов (используя MultiIndex.from_product). Конструктор Index попытается вернуть MultiIndex, когда ему передаётся список кортежей. Следующие примеры демонстрируют различные способы инициализации MultiIndex.
In [1]: arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
...: ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]
...:
In [2]: tuples = list(zip(*arrays))
In [3]: tuples
Out[3]:
[('bar', 'one'),
('bar', 'two'),
('baz', 'one'),
('baz', 'two'),
('foo', 'one'),
('foo', 'two'),
('qux', 'one'),
('qux', 'two')]
In [4]: index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])
In [5]: index
Out[5]:
MultiIndex(levels=[[u'bar', u'baz', u'foo', u'qux'], [u'one', u'two']],
labels=[[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 0, 1, 0, 1, 0, 1]],
names=[u'first', u'second'])
In [6]: s = pd.Series(np.random.randn(8), index=index)
In [7]: s
Out[7]:
first second
bar one 0.469112
two -0.282863
baz one -1.509059
two -1.135632
foo one 1.212112
two -0.173215
qux one 0.119209
two -1.044236
dtype: float64
Когда вам нужно каждое сочетание элементов в двух итерируемых объектах, может быть легче использовать функцию MultiIndex.from_product:
In [8]: iterables = [['bar', 'baz', 'foo', 'qux'], ['one', 'two']]
In [9]: pd.MultiIndex.from_product(iterables, names=['first', 'second'])
Out[9]:
MultiIndex(levels=[[u'bar', u'baz', u'foo', u'qux'], [u'one', u'two']],
labels=[[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 0, 1, 0, 1, 0, 1]],
names=[u'first', u'second'])
Для удобства вы можете напрямую передать список массивов в Series или DataFrame для автоматической конструкции MultiIndex:
In [10]: arrays = [np.array(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux']),
....: np.array(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'])]
....:
In [11]: s = pd.Series(np.random.randn(8), index=arrays)
In [12]: s
Out[12]:
bar one -0.861849
two -2.104569
baz one -0.494929
two 1.071804
foo one 0.721555
two -0.706771
qux one -1.039575
two 0.271860
dtype: float64
In [13]: df = pd.DataFrame(np.random.randn(8, 4), index=arrays)
In [14]: df
Out[14]:
0 1 2 3
bar one -0.424972 0.567020 0.276232 -1.087401
two -0.673690 0.113648 -1.478427 0.524988
baz one 0.404705 0.577046 -1.715002 -1.039268
two -0.370647 -1.157892 -1.344312 0.844885
foo one 1.075770 -0.109050 1.643563 -1.469388
two 0.357021 -0.674600 -1.776904 -0.968914
qux one -1.294524 0.413738 0.276662 -0.472035
two -0.013960 -0.362543 -0.006154 -0.923061
Все конструкторы MultiIndex принимают аргумент names, который хранит строковые имена самих уровней. Если имена не указаны, None будет назначен:
In [15]: df.index.names Out[15]: FrozenList([None, None])
Этот индекс может служить любой осью объекта pandas, а количество **уровней** индекса зависит от вас:
In [16]: df = pd.DataFrame(np.random.randn(3, 8), index=['A', 'B', 'C'], columns=index)
In [17]: df
Out[17]:
first bar baz foo qux \
second one two one two one two one
A 0.895717 0.805244 -1.206412 2.565646 1.431256 1.340309 -1.170299
B 0.410835 0.813850 0.132003 -0.827317 -0.076467 -1.187678 1.130127
C -1.413681 1.607920 1.024180 0.569605 0.875906 -2.211372 0.974466
first
second two
A -0.226169
B -1.436737
C -2.006747
In [18]: pd.DataFrame(np.random.randn(6, 6), index=index[:6], columns=index[:6])
Out[18]:
first bar baz foo
second one two one two one two
first second
bar one -0.410001 -0.078638 0.545952 -1.219217 -1.226825 0.769804
two -1.281247 -0.727707 -0.121306 -0.097883 0.695775 0.341734
baz one 0.959726 -1.110336 -0.619976 0.149748 -0.732339 0.687738
two 0.176444 0.403310 -0.154951 0.301624 -2.179861 -1.369849
foo one -0.954208 1.462696 -1.743161 -0.826591 -0.345352 1.314232
two 0.690579 0.995761 2.396780 0.014871 3.357427 -0.317441
Мы «раредили» более высокие уровни индексов, чтобы вывод в консоли был более понятным.
Стоит помнить, что ничего не мешает вам использовать кортежи в качестве атомарных меток на оси:
In [19]: pd.Series(np.random.randn(8), index=tuples) Out[19]: (bar, one) -1.236269 (bar, two) 0.896171 (baz, one) -0.487602 (baz, two) -0.082240 (foo, one) -2.182937 (foo, two) 0.380396 (qux, one) 0.084844 (qux, two) 0.432390 dtype: float64
Причина, по которой MultiIndex имеет значение, заключается в том, что это может позволить вам выполнять операции группировки, выбора и изменения формы, как будет описано ниже и в последующих разделах документации. Как вы увидите в последующих разделах, вы можете работать с данными с иерархическим индексом, не создавая MultiIndex явно. Однако при загрузке данных из файла вам может потребоваться сгенерировать собственный MultiIndex при подготовке набора данных.
Обратите внимание, что отображение индекса может контролироваться с помощью параметра multi_sparse в pandas.set_printoptions:
In [20]: pd.set_option('display.multi_sparse', False)
In [21]: df
Out[21]:
first bar bar baz baz foo foo qux \
second one two one two one two one
A 0.895717 0.805244 -1.206412 2.565646 1.431256 1.340309 -1.170299
B 0.410835 0.813850 0.132003 -0.827317 -0.076467 -1.187678 1.130127
C -1.413681 1.607920 1.024180 0.569605 0.875906 -2.211372 0.974466
first qux
second two
A -0.226169
B -1.436737
C -2.006747
In [22]: pd.set_option('display.multi_sparse', True)
Восстановление меток уровня
Метод get_level_values вернёт вектор меток для каждой позиции на определённом уровне:
In [23]: index.get_level_values(0)
Out[23]: Index([u'bar', u'bar', u'baz', u'baz', u'foo', u'foo', u'qux', u'qux'], dtype='object', name=u'first')
In [24]: index.get_level_values('second')
Out[24]: Index([u'one', u'two', u'one', u'two', u'one', u'two', u'one', u'two'], dtype='object', name=u'second')
Базовое индексирование на оси с MultiIndex
Одна из важных функций иерархического индексирования заключается в том, что вы можете выбирать данные по «частичной» метке, определяющей подгруппу в данных. **Частичный** выбор «удаляет» уровни иерархического индекса в результате аналогичным образом, как выбор столбца в обычном DataFrame:
In [25]: df['bar'] Out[25]: second one two A 0.895717 0.805244 B 0.410835 0.813850 C -1.413681 1.607920 In [26]: df['bar', 'one'] Out[26]: A 0.895717 B 0.410835 C -1.413681 Name: (bar, one), dtype: float64 In [27]: df['bar']['one'] Out[27]: A 0.895717 B 0.410835 C -1.413681 Name: one, dtype: float64 In [28]: s['qux'] Out[28]: one -1.039575 two 0.271860 dtype: float64
См. Пересечение с многоуровневым индексом, как выбрать на более глубоком уровне.
Примечание
Представление MultiIndex отображает ВСЕ определенные уровни индекса, даже если они фактически не используются. Это можно заметить при срезе индекса. Например:
# original multi-index
In [29]: df.columns
Out[29]:
MultiIndex(levels=[[u'bar', u'baz', u'foo', u'qux'], [u'one', u'two']],
labels=[[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 0, 1, 0, 1, 0, 1]],
names=[u'first', u'second'])
# sliced
In [30]: df[['foo','qux']].columns
Out[30]:
MultiIndex(levels=[[u'bar', u'baz', u'foo', u'qux'], [u'one', u'two']],
labels=[[2, 2, 3, 3], [0, 1, 0, 1]],
names=[u'first', u'second'])
Это делается для предотвращения повторного вычисления уровней, чтобы сделать срез высокопроизводительным. Если вы хотите увидеть фактически используемые уровни.
In [31]: df[['foo','qux']].columns.values
Out[31]: array([('foo', 'one'), ('foo', 'two'), ('qux', 'one'), ('qux', 'two')], dtype=object)
# for a specific level
In [32]: df[['foo','qux']].columns.get_level_values(0)
Out[32]: Index([u'foo', u'foo', u'qux', u'qux'], dtype='object', name=u'first')
Для восстановления многоуровневого индекса только с используемыми уровнями
In [33]: pd.MultiIndex.from_tuples(df[['foo','qux']].columns.values)
Out[33]:
MultiIndex(levels=[[u'foo', u'qux'], [u'one', u'two']],
labels=[[0, 0, 1, 1], [0, 1, 0, 1]])
Выравнивание данных и использование reindex
Операции между объектами с различными индексами, имеющими MultiIndex на осях, будут работать так, как вы ожидаете; выравнивание данных будет работать так же, как и индекс кортежей:
In [34]: s + s[:-2]
Out[34]:
bar one -1.723698
two -4.209138
baz one -0.989859
two 2.143608
foo one 1.443110
two -1.413542
qux one NaN
two NaN
dtype: float64
In [35]: s + s[::2]
Out[35]:
bar one -1.723698
two NaN
baz one -0.989859
two NaN
foo one 1.443110
two NaN
qux one -2.079150
two NaN
dtype: float64
reindex можно вызвать с другим MultiIndex или даже со списком или массивом кортежей:
In [36]: s.reindex(index[:3])
Out[36]:
first second
bar one -0.861849
two -2.104569
baz one -0.494929
dtype: float64
In [37]: s.reindex([('foo', 'two'), ('bar', 'one'), ('qux', 'one'), ('baz', 'one')])
Out[37]:
foo two -0.706771
bar one -0.861849
qux one -1.039575
baz one -0.494929
dtype: float64
Продвинутое индексирование с иерархическим индексом
Синтаксическая интеграция MultiIndex в продвинутое индексирование с .loc/.ix немного сложна, но мы приложили все усилия для этого. Например, следующее работает так, как вы ожидаете:
In [38]: df = df.T
In [39]: df
Out[39]:
A B C
first second
bar one 0.895717 0.410835 -1.413681
two 0.805244 0.813850 1.607920
baz one -1.206412 0.132003 1.024180
two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
qux one -1.170299 1.130127 0.974466
two -0.226169 -1.436737 -2.006747
In [40]: df.loc['bar']
Out[40]:
A B C
second
one 0.895717 0.410835 -1.413681
two 0.805244 0.813850 1.607920
In [41]: df.loc['bar', 'two']
Out[41]:
A 0.805244
B 0.813850
C 1.607920
Name: (bar, two), dtype: float64
«Частичный» срез также работает очень хорошо.
In [42]: df.loc['baz':'foo']
Out[42]:
A B C
first second
baz one -1.206412 0.132003 1.024180
two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
Вы можете выполнить срез с «диапазоном» значений, указав срез кортежей.
In [43]: df.loc[('baz', 'two'):('qux', 'one')]
Out[43]:
A B C
first second
baz two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
qux one -1.170299 1.130127 0.974466
In [44]: df.loc[('baz', 'two'):'foo']
Out[44]:
A B C
first second
baz two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
Передача списка меток или кортежей работает аналогично повторному индексированию:
In [45]: df.ix[[('bar', 'two'), ('qux', 'one')]]
Out[45]:
A B C
first second
bar two 0.805244 0.813850 1.607920
qux one -1.170299 1.130127 0.974466
Использование селекторов срезов
Введено в версии 0.14.0.
В версии 0.14.0 мы добавили новый способ срезать объекты с многоуровневым индексом. Вы можете выполнить срез многоуровневого индекса, указав несколько индексаторов.
Вы можете указать любой из селекторов, как если бы вы индексировали по метке, см. Выбор по метке, включая срезы, списки меток, метки и булевы индексаторы.
Вы можете использовать slice(None) для выбора всего содержимого этого уровня. Вам не нужно указывать все более глубокие уровни, они будут подразумеваться как slice(None).
Как обычно, **обе стороны** срезов включены, так как это индексирование по меткам.
Предупреждение
Вы должны указать все оси в указателе .loc, то есть индексатор для **индекса** и для **столбцов**. Есть некоторые неоднозначные случаи, когда переданный индексатор может быть неправильно истолкован как индексация обеих осей, а не, скажем, в MuliIndex для строк.
Вы должны сделать так:
df.loc[(slice('A1','A3'),.....),:]
а не так:
df.loc[(slice('A1','A3'),.....)]
In [46]: def mklbl(prefix,n):
....: return ["%s%s" % (prefix,i) for i in range(n)]
....:
In [47]: miindex = pd.MultiIndex.from_product([mklbl('A',4),
....: mklbl('B',2),
....: mklbl('C',4),
....: mklbl('D',2)])
....:
In [48]: micolumns = pd.MultiIndex.from_tuples([('a','foo'),('a','bar'),
....: ('b','foo'),('b','bah')],
....: names=['lvl0', 'lvl1'])
....:
In [49]: dfmi = pd.DataFrame(np.arange(len(miindex)*len(micolumns)).reshape((len(miindex),len(micolumns))),
....: index=miindex,
....: columns=micolumns).sort_index().sort_index(axis=1)
....:
In [50]: dfmi
Out[50]:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C0 D0 1 0 3 2
D1 5 4 7 6
C1 D0 9 8 11 10
D1 13 12 15 14
C2 D0 17 16 19 18
D1 21 20 23 22
C3 D0 25 24 27 26
... ... ... ... ...
A3 B1 C0 D1 229 228 231 230
C1 D0 233 232 235 234
D1 237 236 239 238
C2 D0 241 240 243 242
D1 245 244 247 246
C3 D0 249 248 251 250
D1 253 252 255 254
[64 rows x 4 columns]
Базовый срез с многоуровневым индексом, использующий срезы, списки и метки.
In [51]: dfmi.loc[(slice('A1','A3'),slice(None), ['C1','C3']),:]
Out[51]:
lvl0 a b
lvl1 bar foo bah foo
A1 B0 C1 D0 73 72 75 74
D1 77 76 79 78
C3 D0 89 88 91 90
D1 93 92 95 94
B1 C1 D0 105 104 107 106
D1 109 108 111 110
C3 D0 121 120 123 122
... ... ... ... ...
A3 B0 C1 D1 205 204 207 206
C3 D0 217 216 219 218
D1 221 220 223 222
B1 C1 D0 233 232 235 234
D1 237 236 239 238
C3 D0 249 248 251 250
D1 253 252 255 254
[24 rows x 4 columns]
Вы можете использовать pd.IndexSlice для более естественного синтаксиса, используя : вместо slice(None)
In [52]: idx = pd.IndexSlice
In [53]: dfmi.loc[idx[:,:,['C1','C3']],idx[:,'foo']]
Out[53]:
lvl0 a b
lvl1 foo foo
A0 B0 C1 D0 8 10
D1 12 14
C3 D0 24 26
D1 28 30
B1 C1 D0 40 42
D1 44 46
C3 D0 56 58
... ... ...
A3 B0 C1 D1 204 206
C3 D0 216 218
D1 220 222
B1 C1 D0 232 234
D1 236 238
C3 D0 248 250
D1 252 254
[32 rows x 2 columns]
Можно выполнить довольно сложные выборки с помощью этого метода на нескольких осях одновременно.
In [54]: dfmi.loc['A1',(slice(None),'foo')]
Out[54]:
lvl0 a b
lvl1 foo foo
B0 C0 D0 64 66
D1 68 70
C1 D0 72 74
D1 76 78
C2 D0 80 82
D1 84 86
C3 D0 88 90
... ... ...
B1 C0 D1 100 102
C1 D0 104 106
D1 108 110
C2 D0 112 114
D1 116 118
C3 D0 120 122
D1 124 126
[16 rows x 2 columns]
In [55]: dfmi.loc[idx[:,:,['C1','C3']],idx[:,'foo']]
Out[55]:
lvl0 a b
lvl1 foo foo
A0 B0 C1 D0 8 10
D1 12 14
C3 D0 24 26
D1 28 30
B1 C1 D0 40 42
D1 44 46
C3 D0 56 58
... ... ...
A3 B0 C1 D1 204 206
C3 D0 216 218
D1 220 222
B1 C1 D0 232 234
D1 236 238
C3 D0 248 250
D1 252 254
[32 rows x 2 columns]
Используя булевый индексатор, вы можете предоставить выбор, связанный со значениями.
In [56]: mask = dfmi[('a','foo')]>200
In [57]: dfmi.loc[idx[mask,:,['C1','C3']],idx[:,'foo']]
Out[57]:
lvl0 a b
lvl1 foo foo
A3 B0 C1 D1 204 206
C3 D0 216 218
D1 220 222
B1 C1 D0 232 234
D1 236 238
C3 D0 248 250
D1 252 254
Вы также можете указать аргумент axis к .loc для интерпретации переданных селекторов среза на одной оси.
In [58]: dfmi.loc(axis=0)[:,:,['C1','C3']]
Out[58]:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C1 D0 9 8 11 10
D1 13 12 15 14
C3 D0 25 24 27 26
D1 29 28 31 30
B1 C1 D0 41 40 43 42
D1 45 44 47 46
C3 D0 57 56 59 58
... ... ... ... ...
A3 B0 C1 D1 205 204 207 206
C3 D0 217 216 219 218
D1 221 220 223 222
B1 C1 D0 233 232 235 234
D1 237 236 239 238
C3 D0 249 248 251 250
D1 253 252 255 254
[32 rows x 4 columns]
Кроме того, вы можете изменить значения с помощью этих методов
In [59]: df2 = dfmi.copy()
In [60]: df2.loc(axis=0)[:,:,['C1','C3']] = -10
In [61]: df2
Out[61]:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C0 D0 1 0 3 2
D1 5 4 7 6
C1 D0 -10 -10 -10 -10
D1 -10 -10 -10 -10
C2 D0 17 16 19 18
D1 21 20 23 22
C3 D0 -10 -10 -10 -10
... ... ... ... ...
A3 B1 C0 D1 229 228 231 230
C1 D0 -10 -10 -10 -10
D1 -10 -10 -10 -10
C2 D0 241 240 243 242
D1 245 244 247 246
C3 D0 -10 -10 -10 -10
D1 -10 -10 -10 -10
[64 rows x 4 columns]
Вы можете использовать правую часть выравниваемого объекта тоже.
In [62]: df2 = dfmi.copy()
In [63]: df2.loc[idx[:,:,['C1','C3']],:] = df2*1000
In [64]: df2
Out[64]:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C0 D0 1 0 3 2
D1 5 4 7 6
C1 D0 9000 8000 11000 10000
D1 13000 12000 15000 14000
C2 D0 17 16 19 18
D1 21 20 23 22
C3 D0 25000 24000 27000 26000
... ... ... ... ...
A3 B1 C0 D1 229 228 231 230
C1 D0 233000 232000 235000 234000
D1 237000 236000 239000 238000
C2 D0 241 240 243 242
D1 245 244 247 246
C3 D0 249000 248000 251000 250000
D1 253000 252000 255000 254000
[64 rows x 4 columns]
Пересечение
Метод xs объекта DataFrame дополнительно принимает аргумент уровня, чтобы упростить выбор данных на определённом уровне MultiIndex.
In [65]: df
Out[65]:
A B C
first second
bar one 0.895717 0.410835 -1.413681
two 0.805244 0.813850 1.607920
baz one -1.206412 0.132003 1.024180
two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
qux one -1.170299 1.130127 0.974466
two -0.226169 -1.436737 -2.006747
In [66]: df.xs('one', level='second')
Out[66]:
A B C
first
bar 0.895717 0.410835 -1.413681
baz -1.206412 0.132003 1.024180
foo 1.431256 -0.076467 0.875906
qux -1.170299 1.130127 0.974466
# using the slicers (new in 0.14.0)
In [67]: df.loc[(slice(None),'one'),:]
Out[67]:
A B C
first second
bar one 0.895717 0.410835 -1.413681
baz one -1.206412 0.132003 1.024180
foo one 1.431256 -0.076467 0.875906
qux one -1.170299 1.130127 0.974466
Вы также можете выполнить выбор по столбцам с xs(), указав аргумент оси
In [68]: df = df.T
In [69]: df.xs('one', level='second', axis=1)
Out[69]:
first bar baz foo qux
A 0.895717 -1.206412 1.431256 -1.170299
B 0.410835 0.132003 -0.076467 1.130127
C -1.413681 1.024180 0.875906 0.974466
# using the slicers (new in 0.14.0) In [70]: df.loc[:,(slice(None),'one')] Out[70]: first bar baz foo qux second one one one one A 0.895717 -1.206412 1.431256 -1.170299 B 0.410835 0.132003 -0.076467 1.130127 C -1.413681 1.024180 0.875906 0.974466
xs() также позволяет выполнять выбор по нескольким ключам
In [71]: df.xs(('one', 'bar'), level=('second', 'first'), axis=1)
Out[71]:
first bar
second one
A 0.895717
B 0.410835
C -1.413681
# using the slicers (new in 0.14.0)
In [72]: df.loc[:,('bar','one')]
Out[72]:
A 0.895717
B 0.410835
C -1.413681
Name: (bar, one), dtype: float64
Введено в версии 0.13.0.
Вы можете передать drop_level=False в xs() для сохранения выбранного уровня
In [73]: df.xs('one', level='second', axis=1, drop_level=False)
Out[73]:
first bar baz foo qux
second one one one one
A 0.895717 -1.206412 1.431256 -1.170299
B 0.410835 0.132003 -0.076467 1.130127
C -1.413681 1.024180 0.875906 0.974466
в отличие от результата с drop_level=True (значение по умолчанию)
In [74]: df.xs('one', level='second', axis=1, drop_level=True)
Out[74]:
first bar baz foo qux
A 0.895717 -1.206412 1.431256 -1.170299
B 0.410835 0.132003 -0.076467 1.130127
C -1.413681 1.024180 0.875906 0.974466
Продвинутое повторное индексирование и выравнивание
Параметр level был добавлен к методам reindex и align объектов pandas. Это полезно для трансляции значений по уровню. Например:
In [75]: midx = pd.MultiIndex(levels=[['zero', 'one'], ['x','y']],
....: labels=[[1,1,0,0],[1,0,1,0]])
....:
In [76]: df = pd.DataFrame(np.random.randn(4,2), index=midx)
In [77]: df
Out[77]:
0 1
one y 1.519970 -0.493662
x 0.600178 0.274230
zero y 0.132885 -0.023688
x 2.410179 1.450520
In [78]: df2 = df.mean(level=0)
In [79]: df2
Out[79]:
0 1
zero 1.271532 0.713416
one 1.060074 -0.109716
In [80]: df2.reindex(df.index, level=0)
Out[80]:
0 1
one y 1.060074 -0.109716
x 1.060074 -0.109716
zero y 1.271532 0.713416
x 1.271532 0.713416
# aligning
In [81]: df_aligned, df2_aligned = df.align(df2, level=0)
In [82]: df_aligned
Out[82]:
0 1
one y 1.519970 -0.493662
x 0.600178 0.274230
zero y 0.132885 -0.023688
x 2.410179 1.450520
In [83]: df2_aligned
Out[83]:
0 1
one y 1.060074 -0.109716
x 1.060074 -0.109716
zero y 1.271532 0.713416
x 1.271532 0.713416
Перемещение уровней с помощью swaplevel()
Функция swaplevel может изменить порядок двух уровней:
In [84]: df[:5]
Out[84]:
0 1
one y 1.519970 -0.493662
x 0.600178 0.274230
zero y 0.132885 -0.023688
x 2.410179 1.450520
In [85]: df[:5].swaplevel(0, 1, axis=0)
Out[85]:
0 1
y one 1.519970 -0.493662
x one 0.600178 0.274230
y zero 0.132885 -0.023688
x zero 2.410179 1.450520
Переупорядочивание уровней с помощью reorder_levels()
Функция reorder_levels обобщает функцию swaplevel, позволяя переупорядочить уровни иерархического индекса за один шаг:
In [86]: df[:5].reorder_levels([1,0], axis=0)
Out[86]:
0 1
y one 1.519970 -0.493662
x one 0.600178 0.274230
y zero 0.132885 -0.023688
x zero 2.410179 1.450520
Сортировка MultiIndex
Для эффективного индексирования и срезов объектов с MultiIndex они должны быть отсортированы. Как и любой индекс, вы можете использовать sort_index.
In [87]: import random; random.shuffle(tuples)
In [88]: s = pd.Series(np.random.randn(8), index=pd.MultiIndex.from_tuples(tuples))
In [89]: s
Out[89]:
baz two 0.206053
qux one -0.251905
bar two -2.213588
one 1.063327
baz one 1.266143
qux two 0.299368
foo one -0.863838
two 0.408204
dtype: float64
In [90]: s.sort_index()
Out[90]:
bar one 1.063327
two -2.213588
baz one 1.266143
two 0.206053
foo one -0.863838
two 0.408204
qux one -0.251905
two 0.299368
dtype: float64
In [91]: s.sort_index(level=0)
Out[91]:
bar one 1.063327
two -2.213588
baz one 1.266143
two 0.206053
foo one -0.863838
two 0.408204
qux one -0.251905
two 0.299368
dtype: float64
In [92]: s.sort_index(level=1)
Out[92]:
bar one 1.063327
baz one 1.266143
foo one -0.863838
qux one -0.251905
bar two -2.213588
baz two 0.206053
foo two 0.408204
qux two 0.299368
dtype: float64
Вы также можете передать имя уровня в sort_index , если уровни MultiIndex имеют имена.
In [93]: s.index.set_names(['L1', 'L2'], inplace=True)
In [94]: s.sort_index(level='L1')
Out[94]:
L1 L2
bar one 1.063327
two -2.213588
baz one 1.266143
two 0.206053
foo one -0.863838
two 0.408204
qux one -0.251905
two 0.299368
dtype: float64
In [95]: s.sort_index(level='L2')
Out[95]:
L1 L2
bar one 1.063327
baz one 1.266143
foo one -0.863838
qux one -0.251905
bar two -2.213588
baz two 0.206053
foo two 0.408204
qux two 0.299368
dtype: float64
На объектах более высокой размерности вы можете сортировать любой другой ось по уровню, если у них есть MultiIndex:
In [96]: df.T.sort_index(level=1, axis=1)
Out[96]:
zero one zero one
x x y y
0 2.410179 0.600178 0.132885 1.519970
1 1.450520 0.274230 -0.023688 -0.493662
Индексация будет работать, даже если данные не отсортированы, но будет довольно неэффективной (и покажет PerformanceWarning). Она также вернёт копию данных, а не представление:
In [97]: dfm = pd.DataFrame({'jim': [0, 0, 1, 1],
....: 'joe': ['x', 'x', 'z', 'y'],
....: 'jolie': np.random.rand(4)})
....:
In [98]: dfm = dfm.set_index(['jim', 'joe'])
In [99]: dfm
Out[99]:
jolie
jim joe
0 x 0.490671
x 0.120248
1 z 0.537020
y 0.110968
In [4]: dfm.loc[(1, 'z')]
PerformanceWarning: indexing past lexsort depth may impact performance.
Out[4]:
jolie
jim joe
1 z 0.64094
Кроме того, если вы попытаетесь индексировать что-то, что не полностью отсортировано лексикографически, это может вызвать:
In [5]: dfm.loc[(0,'y'):(1, 'z')] KeyError: 'Key length (2) was greater than MultiIndex lexsort depth (1)'
Метод is_lexsorted() на Index покажет, отсортирован ли индекс, а свойство lexsort_depth возвращает глубину сортировки:
In [100]: dfm.index.is_lexsorted() Out[100]: False In [101]: dfm.index.lexsort_depth Out[101]: 1
In [102]: dfm = dfm.sort_index()
In [103]: dfm
Out[103]:
jolie
jim joe
0 x 0.490671
x 0.120248
1 y 0.110968
z 0.537020
In [104]: dfm.index.is_lexsorted()
Out[104]: True
In [105]: dfm.index.lexsort_depth
Out[105]: 2
И теперь выборка работает как ожидается.
In [106]: dfm.loc[(0,'y'):(1, 'z')]
Out[106]:
jolie
jim joe
1 y 0.110968
z 0.537020
Методы Take
Аналогично массивам numpy, индексы pandas, Series и DataFrame также предоставляют метод take , который извлекает элементы по заданной оси в заданных индексах. Заданные индексы должны быть либо списком, либо массивом ndarray целых позиций индексов. take также примет отрицательные целые числа как относительные позиции к концу объекта.
In [107]: index = pd.Index(np.random.randint(0, 1000, 10)) In [108]: index Out[108]: Int64Index([214, 502, 712, 567, 786, 175, 993, 133, 758, 329], dtype='int64') In [109]: positions = [0, 9, 3] In [110]: index[positions] Out[110]: Int64Index([214, 329, 567], dtype='int64') In [111]: index.take(positions) Out[111]: Int64Index([214, 329, 567], dtype='int64') In [112]: ser = pd.Series(np.random.randn(10)) In [113]: ser.iloc[positions] Out[113]: 0 -0.179666 9 1.824375 3 0.392149 dtype: float64 In [114]: ser.take(positions) Out[114]: 0 -0.179666 9 1.824375 3 0.392149 dtype: float64
Для DataFrames заданные индексы должны быть 1d списком или массивом ndarray, которые определяют позиции строк или столбцов.
In [115]: frm = pd.DataFrame(np.random.randn(5, 3))
In [116]: frm.take([1, 4, 3])
Out[116]:
0 1 2
1 -1.237881 0.106854 -1.276829
4 0.629675 -1.425966 1.857704
3 0.979542 -1.633678 0.615855
In [117]: frm.take([0, 2], axis=1)
Out[117]:
0 2
0 0.595974 0.601544
1 -1.237881 -1.276829
2 -0.767101 1.499591
3 0.979542 0.615855
4 0.629675 1.857704
Важно отметить, что метод take на объектах pandas не предназначен для работы с булевыми индексами и может возвращать неожиданные результаты.
In [118]: arr = np.random.randn(10) In [119]: arr.take([False, False, True, True]) Out[119]: array([-1.1935, -1.1935, 0.6775, 0.6775]) In [120]: arr[[0, 1]] Out[120]: array([-1.1935, 0.6775]) In [121]: ser = pd.Series(np.random.randn(10)) In [122]: ser.take([False, False, True, True]) Out[122]: 0 0.233141 0 0.233141 1 -0.223540 1 -0.223540 dtype: float64 In [123]: ser.ix[[0, 1]] Out[123]: 0 0.233141 1 -0.223540 dtype: float64
Наконец, небольшая заметка по производительности, поскольку метод take обрабатывает более узкий диапазон входных данных, он может обеспечить производительность, которая значительно быстрее, чем сложная индексация.
Типы индексов
Мы подробно обсуждали MultiIndex в предыдущих разделах. DatetimeIndex и PeriodIndex показаны здесь. TimedeltaIndex находятся здесь.
В следующих подразделах мы подробнее рассмотрим другие типы индексов.
CategoricalIndex
Новая функция с версии 0.16.1.
Мы представляем CategoricalIndex, новый тип объекта индекса, который полезен для поддержки индексации с дубликатами. Это контейнер вокруг Categorical (введён в v0.15.0) и позволяет эффективно индексировать и хранить индекс с большим количеством дублированных элементов. До версии 0.16.1, установка индекса DataFrame/Series с типом category приведет к преобразованию в обычный индекс на основе объектов Index.
In [124]: df = pd.DataFrame({'A': np.arange(6),
.....: 'B': list('aabbca')})
.....:
In [125]: df['B'] = df['B'].astype('category', categories=list('cab'))
In [126]: df
Out[126]:
A B
0 0 a
1 1 a
2 2 b
3 3 b
4 4 c
5 5 a
In [127]: df.dtypes
Out[127]:
A int64
B category
dtype: object
In [128]: df.B.cat.categories
Out[128]: Index([u'c', u'a', u'b'], dtype='object')
Установка индекса создаст CategoricalIndex
In [129]: df2 = df.set_index('B')
In [130]: df2.index
Out[130]: CategoricalIndex([u'a', u'a', u'b', u'b', u'c', u'a'], categories=[u'c', u'a', u'b'], ordered=False, name=u'B', dtype='category')
Индексация с __getitem__/.iloc/.loc/.ix работает аналогично Index с дубликатами. Индексаторы ДОЛЖНЫ быть в категории, иначе операция вызовет ошибку.
In [131]: df2.loc['a'] Out[131]: A B a 0 a 1 a 5
Эти сохраняют CategoricalIndex
In [132]: df2.loc['a'].index Out[132]: CategoricalIndex([u'a', u'a', u'a'], categories=[u'c', u'a', u'b'], ordered=False, name=u'B', dtype='category')
Сортировка будет упорядочивать по порядку категорий
In [133]: df2.sort_index() Out[133]: A B c 4 a 0 a 1 a 5 b 2 b 3
Операции группировки по индексу также сохранят характер индекса
In [134]: df2.groupby(level=0).sum() Out[134]: A B c 4 a 6 b 5 In [135]: df2.groupby(level=0).sum().index Out[135]: CategoricalIndex([u'c', u'a', u'b'], categories=[u'c', u'a', u'b'], ordered=False, name=u'B', dtype='category')
Операции переиндексации вернут результирующий индекс, основанный на типе переданного индексатора, что означает, что передача списка вернёт обычный Index; индексация с Categorical вернёт CategoricalIndex, индексированный по категориям ПЕРЕДАННОГО Categorical типа. Это позволяет произвольно индексировать их даже со значениями, НЕ находящимися в категориях, аналогично тому, как можно переиндексировать ЛЮБОЙ индекс pandas.
In [136]: df2.reindex(['a','e'])
Out[136]:
A
B
a 0.0
a 1.0
a 5.0
e NaN
In [137]: df2.reindex(['a','e']).index
Out[137]: Index([u'a', u'a', u'a', u'e'], dtype='object', name=u'B')
In [138]: df2.reindex(pd.Categorical(['a','e'],categories=list('abcde')))
Out[138]:
A
B
a 0.0
a 1.0
a 5.0
e NaN
In [139]: df2.reindex(pd.Categorical(['a','e'],categories=list('abcde'))).index
Out[139]: CategoricalIndex([u'a', u'a', u'a', u'e'], categories=[u'a', u'b', u'c', u'd', u'e'], ordered=False, name=u'B', dtype='category')
Предупреждение
Операции преобразования формы и сравнения на CategoricalIndex должны иметь одинаковые категории, иначе будет вызвана ошибка TypeError.
In [9]: df3 = pd.DataFrame({'A' : np.arange(6),
'B' : pd.Series(list('aabbca')).astype('category')})
In [11]: df3 = df3.set_index('B')
In [11]: df3.index
Out[11]: CategoricalIndex([u'a', u'a', u'b', u'b', u'c', u'a'], categories=[u'a', u'b', u'c'], ordered=False, name=u'B', dtype='category')
In [12]: pd.concat([df2, df3]
TypeError: categories must match existing categories when appending
Int64Index и RangeIndex
Предупреждение
Индексация на основе целых чисел с плавающей точкой была уточнена в версии 0.18.0. Для краткого описания изменений см. здесь.
Int64Index является фундаментальным базовым индексом в pandas. Это неизменяемый массив, реализующий упорядоченный, разрезаемый набор. До версии 0.18.0 Int64Index предоставил бы индекс по умолчанию для всех NDFrame объектов.
RangeIndex является подклассом Int64Index, добавленным в версии 0.18.0, и теперь предоставляет индекс по умолчанию для всех NDFrame объектов. RangeIndex — это оптимизированная версия Int64Index, которая может представлять упорядоченный набор. Они аналогичны типам python range.
Float64Index
Примечание
Начиная с версии 0.14.0, Float64Index использует собственный массив типа float64. До версии 0.14.0, Float64Index использовал массив типа object. Использование типа float64 в качестве бэкэнда ускоряет арифметические операции примерно в 30 раз, а булевы операции индексации на Float64Index самих по себе примерно в 2 раза быстрее.
Новая функция с версии 0.13.0.
По умолчанию Float64Index будет автоматически создан при передаче чисел с плавающей запятой или смешанных целых и чисел с плавающей запятой при создании индекса. Это позволяет использовать чистую парадигму срезов на основе меток, которая делает [],ix,loc для скалярной индексации и срезов работать точно так же.
In [140]: indexf = pd.Index([1.5, 2, 3, 4.5, 5]) In [141]: indexf Out[141]: Float64Index([1.5, 2.0, 3.0, 4.5, 5.0], dtype='float64') In [142]: sf = pd.Series(range(5), index=indexf) In [143]: sf Out[143]: 1.5 0 2.0 1 3.0 2 4.5 3 5.0 4 dtype: int64
Скалярный выбор для [],.ix,.loc всегда основан на метках. Целое число будет соответствовать равному индексу с плавающей точкой (например, 3 эквивалентно 3.0)
In [144]: sf[3] Out[144]: 2 In [145]: sf[3.0] Out[145]: 2 In [146]: sf.ix[3] Out[146]: 2 In [147]: sf.ix[3.0] Out[147]: 2 In [148]: sf.loc[3] Out[148]: 2 In [149]: sf.loc[3.0] Out[149]: 2
Единственная позиционная индексация осуществляется через iloc
In [150]: sf.iloc[3] Out[150]: 3
Скалярный индекс, которого нет, вызовет KeyError
Срезы ВСЕГДА производятся по значениям индекса, для [],ix,loc и ВСЕГДА позиционные с iloc
In [151]: sf[2:4] Out[151]: 2.0 1 3.0 2 dtype: int64 In [152]: sf.ix[2:4] Out[152]: 2.0 1 3.0 2 dtype: int64 In [153]: sf.loc[2:4] Out[153]: 2.0 1 3.0 2 dtype: int64 In [154]: sf.iloc[2:4] Out[154]: 3.0 2 4.5 3 dtype: int64
В индексах с плавающей точкой разрешается использование срезов с плавающей точкой
In [155]: sf[2.1:4.6] Out[155]: 3.0 2 4.5 3 dtype: int64 In [156]: sf.loc[2.1:4.6] Out[156]: 3.0 2 4.5 3 dtype: int64
В индексах без плавающей точки использование срезов с плавающей точкой вызовет TypeError
In [1]: pd.Series(range(5))[3.5] TypeError: the label [3.5] is not a proper indexer for this index type (Int64Index) In [1]: pd.Series(range(5))[3.5:4.5] TypeError: the slice start [3.5] is not a proper indexer for this index type (Int64Index)
Предупреждение
Использование скалярного индекса с плавающей точкой для .iloc было удалено в версии 0.18.0, поэтому следующее вызовет TypeError
In [3]: pd.Series(range(5)).iloc[3.0] TypeError: cannot do positional indexing on <class 'pandas.indexes.range.RangeIndex'> with these indexers [3.0] of <type 'float'>
Кроме того, обработка .ix с индексом с плавающей точкой в индексе без плавающей точки будет основана на метках и, следовательно, приведёт к приведению типа индекса.
In [157]: s2 = pd.Series([1, 2, 3], index=list('abc'))
In [158]: s2
Out[158]:
a 1
b 2
c 3
dtype: int64
In [159]: s2.ix[1.0] = 10
In [160]: s2
Out[160]:
a 1
b 2
c 3
1.0 10
dtype: int64
Вот типичный пример использования такого типа индексации. Представьте, что у вас есть некоторая нерегулярная схема индексации типа timedelta, но данные записаны как числа с плавающей точкой. Например, это могут быть миллисекундные смещения.
In [161]: dfir = pd.concat([pd.DataFrame(np.random.randn(5,2),
.....: index=np.arange(5) * 250.0,
.....: columns=list('AB')),
.....: pd.DataFrame(np.random.randn(6,2),
.....: index=np.arange(4,10) * 250.1,
.....: columns=list('AB'))])
.....:
In [162]: dfir
Out[162]:
A B
0.0 0.997289 -1.693316
250.0 -0.179129 -1.598062
500.0 0.936914 0.912560
750.0 -1.003401 1.632781
1000.0 -0.724626 0.178219
1000.4 0.310610 -0.108002
1250.5 -0.974226 -1.147708
1500.6 -2.281374 0.760010
1750.7 -0.742532 1.533318
2000.8 2.495362 -0.432771
2250.9 -0.068954 0.043520
Операции выбора тогда всегда будут работать на основе значения для всех операторов выбора.
In [163]: dfir[0:1000.4]
Out[163]:
A B
0.0 0.997289 -1.693316
250.0 -0.179129 -1.598062
500.0 0.936914 0.912560
750.0 -1.003401 1.632781
1000.0 -0.724626 0.178219
1000.4 0.310610 -0.108002
In [164]: dfir.loc[0:1001,'A']
Out[164]:
0.0 0.997289
250.0 -0.179129
500.0 0.936914
750.0 -1.003401
1000.0 -0.724626
1000.4 0.310610
Name: A, dtype: float64
In [165]: dfir.loc[1000.4]
Out[165]:
A 0.310610
B -0.108002
Name: 1000.4, dtype: float64
Тогда вы легко могли бы выбрать первые 1 секунду (1000 мс) данных.
In [166]: dfir[0:1000]
Out[166]:
A B
0.0 0.997289 -1.693316
250.0 -0.179129 -1.598062
500.0 0.936914 0.912560
750.0 -1.003401 1.632781
1000.0 -0.724626 0.178219
Конечно, если вам нужна индексация на основе целых чисел, используйте iloc
In [167]: dfir.iloc[0:5]
Out[167]:
A B
0.0 0.997289 -1.693316
250.0 -0.179129 -1.598062
500.0 0.936914 0.912560
750.0 -1.003401 1.632781
1000.0 -0.724626 0.178219
© 2008–2012, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/0.19.2/advanced.html