Многоуровневый индекс / Расширенный индексирование
В этом разделе рассматривается индексирование с помощью MultiIndex и другие расширенные функции индексирования.
Для получения общей документации по индексированию см. Индексирование и выбор данных.
Предупреждение
Возвращается копия или ссылка при операциях изменения, может зависеть от контекста. Это иногда называется chained assignment и следует избегать. См. Возвращение представления или копии
Предупреждение
В версии 0.15.0 Index был внутренне переработан и больше не наследуется от ndarray, а вместо этого наследуется от PandasObject, аналогично остальным объектам pandas. Это должно быть прозрачным изменением с очень ограниченными последствиями для API (см. Внутренняя переработка)
См. сборник рецептов для некоторых расширенных стратегий
Иерархическое индексирование (MultiIndex)
Иерархическое/многоуровневое индексирование очень перспективно, так как открывает возможности для довольно сложного анализа и обработки данных, особенно при работе с многомерными данными. По сути, оно позволяет хранить и обрабатывать данные с произвольным числом измерений в структурах данных меньшей размерности, таких как Series (1d) и DataFrame (2d).
В этом разделе мы покажем, что мы подразумеваем под «иерархическим» индексированием, и как оно интегрируется со всеми функциями индексирования pandas, описанными выше и в предыдущих разделах. Позже, при обсуждении группировки и преобразования и изменения структуры данных, мы покажем нетривиальные применения, чтобы проиллюстрировать, как это помогает структурировать данные для анализа.
См. сборник рецептов для некоторых расширенных стратегий
Создание объекта MultiIndex (иерархический индекс)
Объект MultiIndex является иерархическим аналогом стандартного объекта Index, который обычно хранит метки осей в объектах pandas. Можно представить MultiIndex как массив кортежей, где каждый кортеж уникален. MultiIndex можно создать из списка массивов (используя MultiIndex.from_arrays), массива кортежей (используя MultiIndex.from_tuples) или пересечения итерируемых объектов (используя MultiIndex.from_product). Конструктор Index попытается вернуть MultiIndex, если ему передается список кортежей. В следующих примерах демонстрируются различные способы инициализации MultiIndexes.
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')
Чтобы восстановить MultiIndex только с используемыми уровнями
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 мы добавили новый способ среза многоуровневых объектов. Вы можете выполнить срез MultiIndex, указав несколько индексаторов.
Вы можете предоставить любой из селекторов, как если бы вы индексировали по метке, см. Выбор по метке, включая срезы, списки меток, метки и булевы индексаторы.
Вы можете использовать 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]
Базовый срез MultiIndex с помощью срезов, списков и меток.
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 требует, чтобы метки были упорядочены для корректной работы некоторых процедур среза/индексирования. Можно представить ось, разбитую на уникальные группы, где на интересующем иерархическом уровне каждая отдельная группа имеет одинаковую метку, но никакие две группы не имеют одинаковой метки. Однако MultiIndex этого не выполняет: **вы несете ответственность за обеспечение правильного упорядочивания**. Есть важный новый метод sort_index для сортировки оси внутри MultiIndex, чтобы ее метки были сгруппированы и отсортированы в соответствии с исходным порядком соответствующего фактора на этом уровне. Обратите внимание, что это не обязательно означает, что метки будут отсортированы лексикографически!
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]:
qux one 0.206053
two -0.251905
foo one -2.213588
two 1.063327
baz two 1.266143
one 0.299368
bar one -0.863838
two 0.408204
dtype: float64
In [90]: s.sort_index(level=0)
Out[90]:
bar one -0.863838
two 0.408204
baz one 0.299368
two 1.266143
foo one -2.213588
two 1.063327
qux one 0.206053
two -0.251905
dtype: float64
In [91]: s.sort_index(level=1)
Out[91]:
bar one -0.863838
baz one 0.299368
foo one -2.213588
qux one 0.206053
bar two 0.408204
baz two 1.266143
foo two 1.063327
qux two -0.251905
dtype: float64
Обратите внимание, вы также можете передать имя уровня в sort_index, если уровни MultiIndex имеют имена.
In [92]: s.index.set_names(['L1', 'L2'], inplace=True)
In [93]: s.sort_index(level='L1')
Out[93]:
L1 L2
bar one -0.863838
two 0.408204
baz one 0.299368
two 1.266143
foo one -2.213588
two 1.063327
qux one 0.206053
two -0.251905
dtype: float64
In [94]: s.sort_index(level='L2')
Out[94]:
L1 L2
bar one -0.863838
baz one 0.299368
foo one -2.213588
qux one 0.206053
bar two 0.408204
baz two 1.266143
foo two 1.063327
qux two -0.251905
dtype: float64
Некоторые индексирования будут работать даже если данные не отсортированы, но это будет неэффективно и вернёт копию данных, а не представление:
In [95]: s['qux'] Out[95]: L2 one 0.206053 two -0.251905 dtype: float64 In [96]: s.sort_index(level=1)['qux'] Out[96]: L2 one 0.206053 two -0.251905 dtype: float64
На объектах с более высокой размерностью вы можете отсортировать любой из других осей по уровню, если они имеют MultiIndex:
In [97]: df.T.sort_index(level=1, axis=1)
Out[97]:
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
Объект MultiIndex имеет код для явной проверки глубины сортировки. Таким образом, если вы попытаетесь выполнить индексирование на глубине, на которой индекс не отсортирован, будет выброшено исключение. Вот конкретный пример для иллюстрации этого:
In [98]: tuples = [('a', 'a'), ('a', 'b'), ('b', 'a'), ('b', 'b')]
In [99]: idx = pd.MultiIndex.from_tuples(tuples)
In [100]: idx.lexsort_depth
Out[100]: 2
In [101]: reordered = idx[[1, 0, 3, 2]]
In [102]: reordered.lexsort_depth
Out[102]: 1
In [103]: s = pd.Series(np.random.randn(4), index=reordered)
In [104]: s.ix['a':'a']
Out[104]:
a b -1.048089
a -0.025747
dtype: float64
Однако:
>>> s.ix[('a', 'b'):('b', 'a')]
Traceback (most recent call last)
...
KeyError: Key length (3) was greater than MultiIndex lexsort depth (2)
Методы take
Аналогично numpy ndarrays, pandas Index, Series и DataFrame также предоставляют метод take, который извлекает элементы вдоль заданной оси по заданным индексам. Заданные индексы должны быть либо списком, либо ndarray целых позиций индексов. take также принимает отрицательные целые числа как относительные позиции к концу объекта.
In [105]: index = pd.Index(np.random.randint(0, 1000, 10)) In [106]: index Out[106]: Int64Index([214, 502, 712, 567, 786, 175, 993, 133, 758, 329], dtype='int64') In [107]: positions = [0, 9, 3] In [108]: index[positions] Out[108]: Int64Index([214, 329, 567], dtype='int64') In [109]: index.take(positions) Out[109]: Int64Index([214, 329, 567], dtype='int64') In [110]: ser = pd.Series(np.random.randn(10)) In [111]: ser.iloc[positions] Out[111]: 0 -0.179666 9 1.824375 3 0.392149 dtype: float64 In [112]: ser.take(positions) Out[112]: 0 -0.179666 9 1.824375 3 0.392149 dtype: float64
Для DataFrames заданные индексы должны быть 1d списком или ndarray, который определяет позиции строк или столбцов.
In [113]: frm = pd.DataFrame(np.random.randn(5, 3))
In [114]: frm.take([1, 4, 3])
Out[114]:
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 [115]: frm.take([0, 2], axis=1)
Out[115]:
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 [116]: arr = np.random.randn(10) In [117]: arr.take([False, False, True, True]) Out[117]: array([-1.1935, -1.1935, 0.6775, 0.6775]) In [118]: arr[[0, 1]] Out[118]: array([-1.1935, 0.6775]) In [119]: ser = pd.Series(np.random.randn(10)) In [120]: ser.take([False, False, True, True]) Out[120]: 0 0.233141 0 0.233141 1 -0.223540 1 -0.223540 dtype: float64 In [121]: ser.ix[[0, 1]] Out[121]: 0 0.233141 1 -0.223540 dtype: float64
Наконец, небольшое примечание о производительности, поскольку метод take обрабатывает более узкий диапазон входных данных, он может обеспечить производительность, которая намного быстрее, чем продвинутое индексирование.
Типы индексов
Мы довольно подробно обсуждали MultiIndex в предыдущих разделах. DatetimeIndex и PeriodIndex показаны здесь. TimedeltaIndex находятся здесь.
В следующих подразделах мы выделим некоторые другие типы индексов.
CategoricalIndex
Новое в версии 0.16.1.
Мы вводим CategoricalIndex, новый тип объекта индекса, который полезен для поддержки индексирования с дубликатами. Это контейнер вокруг Categorical (введён в версии 0.15.0) и позволяет эффективно индексировать и хранить индекс с большим количеством дублируемых элементов. До версии 0.16.1 установка индекса DataFrame/Series с типом category привела к преобразованию в обычный индекс на основе объектов Index.
In [122]: df = pd.DataFrame({'A': np.arange(6),
.....: 'B': list('aabbca')})
.....:
In [123]: df['B'] = df['B'].astype('category', categories=list('cab'))
In [124]: df
Out[124]:
A B
0 0 a
1 1 a
2 2 b
3 3 b
4 4 c
5 5 a
In [125]: df.dtypes
Out[125]:
A int64
B category
dtype: object
In [126]: df.B.cat.categories
Out[126]: Index([u'c', u'a', u'b'], dtype='object')
Установка индекса создаст CategoricalIndex
In [127]: df2 = df.set_index('B')
In [128]: df2.index
Out[128]: 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 [129]: df2.loc['a'] Out[129]: A B a 0 a 1 a 5
Эти элементы СОХРАНЯЮТ CategoricalIndex
In [130]: df2.loc['a'].index Out[130]: CategoricalIndex([u'a', u'a', u'a'], categories=[u'c', u'a', u'b'], ordered=False, name=u'B', dtype='category')
Сортировка будет производиться в порядке категорий
In [131]: df2.sort_index() Out[131]: A B c 4 a 0 a 1 a 5 b 2 b 3
Операции groupby над индексом также сохранят природу индекса
In [132]: df2.groupby(level=0).sum() Out[132]: A B c 4 a 6 b 5 In [133]: df2.groupby(level=0).sum().index Out[133]: 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 [134]: df2.reindex(['a','e'])
Out[134]:
A
B
a 0.0
a 1.0
a 5.0
e NaN
In [135]: df2.reindex(['a','e']).index
Out[135]: Index([u'a', u'a', u'a', u'e'], dtype='object', name=u'B')
In [136]: df2.reindex(pd.Categorical(['a','e'],categories=list('abcde')))
Out[136]:
A
B
a 0.0
a 1.0
a 5.0
e NaN
In [137]: df2.reindex(pd.Categorical(['a','e'],categories=list('abcde'))).index
Out[137]: 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, которая может представлять монотонно упорядоченный набор. Они аналогичны питоновским range types.
Float64Index
Примечание
Начиная с версии 0.14.0, Float64Index поддерживается массивом с типом данных float64. До версии 0.14.0 Float64Index поддерживался массивом с типом данных object. Использование типа данных float64 в ядре ускоряет арифметические операции примерно в 30 раз, а булевые индексирования на Float64Index самих по себе примерно в 2 раза быстрее.
Новое в версии 0.13.0.
По умолчанию Float64Index создаётся автоматически при передаче чисел с плавающей точкой или смешанных целых и чисел с плавающей точкой в создании индекса. Это позволяет использовать чистый парадигму нарезки по меткам, которая делает [],ix,loc для скалярного индексирования и нарезки работают абсолютно одинаково.
In [138]: indexf = pd.Index([1.5, 2, 3, 4.5, 5]) In [139]: indexf Out[139]: Float64Index([1.5, 2.0, 3.0, 4.5, 5.0], dtype='float64') In [140]: sf = pd.Series(range(5), index=indexf) In [141]: sf Out[141]: 1.5 0 2.0 1 3.0 2 4.5 3 5.0 4 dtype: int64
Скалярный выбор для [],.ix,.loc всегда основан на метке. Целое число будет соответствовать равному индексу с плавающей точкой (например, 3 эквивалентно 3.0).
In [142]: sf[3] Out[142]: 2 In [143]: sf[3.0] Out[143]: 2 In [144]: sf.ix[3] Out[144]: 2 In [145]: sf.ix[3.0] Out[145]: 2 In [146]: sf.loc[3] Out[146]: 2 In [147]: sf.loc[3.0] Out[147]: 2
Единственное позиционное индексирование — через iloc
In [148]: sf.iloc[3] Out[148]: 3
Скалярный индекс, которого нет, вызовет исключение KeyError
Нарезка ВСЕГДА производится по значениям индекса для [],ix,loc и ВСЕГДА позиционно для iloc
In [149]: sf[2:4] Out[149]: 2.0 1 3.0 2 dtype: int64 In [150]: sf.ix[2:4] Out[150]: 2.0 1 3.0 2 dtype: int64 In [151]: sf.loc[2:4] Out[151]: 2.0 1 3.0 2 dtype: int64 In [152]: sf.iloc[2:4] Out[152]: 3.0 2 4.5 3 dtype: int64
В индексах с плавающей точкой разрешено нарезание с использованием чисел с плавающей точкой.
In [153]: sf[2.1:4.6] Out[153]: 3.0 2 4.5 3 dtype: int64 In [154]: sf.loc[2.1:4.6] Out[154]: 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 [155]: s2 = pd.Series([1, 2, 3], index=list('abc'))
In [156]: s2
Out[156]:
a 1
b 2
c 3
dtype: int64
In [157]: s2.ix[1.0] = 10
In [158]: s2
Out[158]:
a 1
b 2
c 3
1.0 10
dtype: int64
Вот типичный случай использования этого типа индексирования. Представьте, что у вас есть схема индексирования, несколько отличная от timedelta, но данные записаны как числа с плавающей точкой. Например, это могут быть миллисекундные смещения.
In [159]: 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 [160]: dfir
Out[160]:
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 [161]: dfir[0:1000.4]
Out[161]:
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 [162]: dfir.loc[0:1001,'A']
Out[162]:
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 [163]: dfir.loc[1000.4]
Out[163]:
A 0.310610
B -0.108002
Name: 1000.4, dtype: float64
Тогда вы можете легко выбрать первые 1 секунду (1000 мс) данных.
In [164]: dfir[0:1000]
Out[164]:
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 [165]: dfir.iloc[0:5]
Out[165]:
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.18.1/advanced.html