import pandas midx = pandas.MultiIndex(levels=None, codes=None, sortorder=None, names=None, copy=False, verify_integrity=True)
levels=None
- последовательность уникальных индексных меток для каждого уровня. codes=None
- последовательность целых чисел для каждого уровня, обозначающие, какая метка - где находится.sortorder=None
- целое число - уровень сортировки (лексикографическая отсортировка по этому уровню).names=None
- последовательность имен для каждого из уровней индекса (имя принимается для сопоставления).copy=False
- копирует метаданные.verify_integrity=True
- проверяет на согласованность аргументы levels
и codes
.MultiIndex
.MultiIndex
Класс MultiIndex()
представляет собой многоуровневый или иерархический объект индекса для объектов pandas
. Объект MultiIndex
позволяет хранить и манипулировать данными с помощью произвольного числа измерений в структурах данных с более меньшей размерностью, таких как Series
(1D) и DataFrame
(2D).
Важность MultiIndex
заключается в том, что он позволяет выполнять операции группировки, выбора и изменения формы данных.
Можно работать с иерархически индексированными данными, не создавая самостоятельно MultiIndex
. Но иногда, при загрузке данных из файла и подготовки набора данных, может потребоваться создать свой собственный MultiIndex
.
MultiIndex
;MultiIndex
;MultiIndex
;MultiIndex
;MultiIndex
;MultiIndex
;MultiIndex
;slice()
и pandas.IndexSlice
для выполнения срезов с MultiIndex
;MultiIndex
;DataFrame.swaplevel()
;DataFrame.reorder_levels()
;Index
или MultiIndex
;MultiIndex
.MultiIndex
Объект MultiIndex
является иерархическим аналогом стандартного объекта Index
, который обычно хранит метки осей. Об MultiIndex
можно думать как о массиве кортежей, каждый из которых уникален. MultiIndex
можно создать из списка массивов (MultiIndex.from_arrays()
), массива кортежей (MultiIndex.from_tuples()
), декартова произведения нескольких итераций (MultiIndex.from_product()
) или DataFrame
(MultiIndex.from_frame()
). Конструктор Index
попытается вернуть MultiIndex
, когда ему будет передан список кортежей.
Примеры различных способов инициализации MultiIndex
.
>>> import pandas as pd arrays = [ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"], ["one", "two", "one", "two", "one", "two", "one", "two"], ] >>> tuples = list(zip(*arrays)) >>> tuples # [('bar', 'one'), # ('bar', 'two'), # ('baz', 'one'), # ('baz', 'two'), # ('foo', 'one'), # ('foo', 'two'), # ('qux', 'one'), # ('qux', 'two')] >>> index = pd.MultiIndex.from_tuples(tuples, names=["first", "second"]) >>> index # MultiIndex([('bar', 'one'), # ('bar', 'two'), # ('baz', 'one'), # ('baz', 'two'), # ('foo', 'one'), # ('foo', 'two'), # ('qux', 'one'), # ('qux', 'two')], # names=['first', 'second']) >>> import numpy as np >>> s = pd.Series(np.random.randn(8), index=index) >>> s # 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()
:
>>> iterables = [["bar", "baz", "foo", "qux"], ["one", "two"]] >>> pd.MultiIndex.from_product(iterables, names=["first", "second"]) # MultiIndex([('bar', 'one'), # ('bar', 'two'), # ('baz', 'one'), # ('baz', 'two'), # ('foo', 'one'), # ('foo', 'two'), # ('qux', 'one'), # ('qux', 'two')], # names=['first', 'second'])
Можно создать MultiIndex
напрямую из DataFrame
, используя метод MultiIndex.from_frame()
или дополнительного метода MultiIndex.to_frame()
.
df = pd.DataFrame( [["bar", "one"], ["bar", "two"], ["foo", "one"], ["foo", "two"]], columns=["first", "second"], ) >>> pd.MultiIndex.from_frame(df) # MultiIndex([('bar', 'one'), # ('bar', 'two'), # ('foo', 'one'), # ('foo', 'two')], # names=['first', 'second'])
Для автоматического создания MultiIndex
можно передать список массивов непосредственно в Series
или DataFrame
:
arrays = [ np.array(["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"]), np.array(["one", "two", "one", "two", "one", "two", "one", "two"]), ] >>> s = pd.Series(np.random.randn(8), index=arrays) >>> s # 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 >>> df = pd.DataFrame(np.random.randn(8, 4), index=arrays) >>> df # 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
:
>>> df.index.names # FrozenList([None, None])
MultiIndex
может поддерживать любую ось объекта pandas
, а количество уровней индекса зависит пользователя:
>>> df = pd.DataFrame(np.random.randn(3, 8), index=["A", "B", "C"], columns=index) >>> df # first bar baz ... foo qux # second one two one ... two one two # A 0.895717 0.805244 -1.206412 ... 1.340309 -1.170299 -0.226169 # B 0.410835 0.813850 0.132003 ... -1.187678 1.130127 -1.436737 # C -1.413681 1.607920 1.024180 ... -2.211372 0.974466 -2.006747 # # [3 rows x 8 columns] >>> pd.DataFrame(np.random.randn(6, 6), index=index[:6], columns=index[:6]) # 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
В примерах выше верхние уровни индексов "урезаются" , чтобы сделать вывод на консоль более удобным для глаз.
Стоит иметь в виду, что ничто не мешает использовать кортежи в качестве атомарных меток оси:
arrays = [ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"], ["one", "two", "one", "two", "one", "two", "one", "two"], ] >>> tuples = list(zip(*arrays)) >>> pd.Series(np.random.randn(8), index=tuples) # (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.get_level_values()
вернет вектор меток для каждого положения на определенном уровне:
# принимает уровень в виде числа >>> index.get_level_values(0) # Index(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'], dtype='object', name='first') # или в виде индексной метки >>> index.get_level_values("second") # Index(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'], dtype='object', name='second')
MultiIndex
Одной из важных особенностей MultiIndex
является то, что можно сделать "частичный выбор" по метке, которая идентифицирует подгруппу в данных. В результате частичного выбора "сбрасываются" уровни MultiIndex
, аналогично выбору столбца в обычном DataFrame
:
Пример с DataFrame
:
# используем `DataFrame`, созданный ранее >>> df # first bar baz ... foo qux # second one two one ... two one two # A 0.895717 0.805244 -1.206412 ... 1.340309 -1.170299 -0.226169 # B 0.410835 0.813850 0.132003 ... -1.187678 1.130127 -1.436737 # C -1.413681 1.607920 1.024180 ... -2.211372 0.974466 -2.006747 # # [3 rows x 8 columns] >>> df["bar"] # second one two # A 0.895717 0.805244 # B 0.410835 0.813850 # C -1.413681 1.607920 >>> df["bar", "one"] # A 0.895717 # B 0.410835 # C -1.413681 # Name: (bar, one), dtype: float64 >>> df["bar"]["one"] # A 0.895717 # B 0.410835 # C -1.413681 # Name: one, dtype: float64
Пример с Series
:
# используем `Series`, созданную ранее >>> s # 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 >>> s["qux"] # one -1.039575 # two 0.271860 # dtype: float64
Ниже, в подразделе "Использование функций slice()
и pandas.IndexSlice
для выполнения срезов с MultiIndex
" объясняется как сделать выбор данных на более глубоком уровне.
MultiIndex
MultiIndex
сохраняет все определенные уровни индекса, даже если они фактически не используются. Это можно заметить при создании среза индекса. Например:
# исходный `MultiIndex` >>> df.columns.levels # FrozenList([['bar', 'baz', 'foo', 'qux'], ['one', 'two']]) # срез `MultiIndex` >>> df[["foo","qux"]].columns.levels # FrozenList([['bar', 'baz', 'foo', 'qux'], ['one', 'two']])
Это сделано для того, чтобы избежать повторного расчета уровней и повысить производительность срезов. Если нужно посмотреть только использованные уровни, то можно использовать метод MultiIndex.get_level_values()
.
>>> df[["foo", "qux"]].columns.to_numpy() # array([('foo', 'one'), ('foo', 'two'), ('qux', 'one'), ('qux', 'two')], dtype=object) # для определенного уровня >>> df[["foo", "qux"]].columns.get_level_values(0) # Index(['foo', 'foo', 'qux', 'qux'], dtype='object', name='first')
Чтобы восстановить MultiIndex
только используемых уровней, может быть использован метод MultiIndex.remove_unused_levels()
.
>>> new_mi = df[["foo", "qux"]].columns.remove_unused_levels() >>> new_mi.levels # FrozenList([['foo', 'qux'], ['one', 'two']])
MultiIndex
Операции между объектами с разными индексами, имеющими MultiIndex
по осям, будут работать так, как ожидается. Выравнивание данных будет работать так же, как обычный индекс кортежей:
# используем `Series`, созданную ранее >>> s # 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 >>> s + s[:-2] # 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 >>> s + s[::2] # 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
MultiIndex
Метод .reindex()
объектов Series
/DataFrame
можно вызвать с другим MultiIndex
или даже со списком или массивом кортежей:
>>> s.reindex(index[:3]) # first second # bar one -0.861849 # two -2.104569 # baz one -0.494929 # dtype: float64 >>> s.reindex([("foo", "two"), ("bar", "one"), ("qux", "one"), ("baz", "one")]) # foo two -0.706771 # bar one -0.861849 # qux one -1.039575 # baz one -0.494929 # dtype: float64
MultiIndex
Синтаксическая интеграция MultiIndex
в расширенное индексирование с помощью DataFrame.loc
немного сложна... Как правило, ключи MultiIndex
имеют форму кортежей. Например, следующий код работает так, как и ожидается:
# используем `DataFrame`, созданный ранее >>> df # first bar baz ... foo qux # second one two one ... two one two # A 0.895717 0.805244 -1.206412 ... 1.340309 -1.170299 -0.226169 # B 0.410835 0.813850 0.132003 ... -1.187678 1.130127 -1.436737 # C -1.413681 1.607920 1.024180 ... -2.211372 0.974466 -2.006747 # # [3 rows x 8 columns] # транспонируем `DataFrame` # (меняем оси местами) >>> df = df.T >>> df # 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 >>> df.loc[("bar", "two")] # A 0.805244 # B 0.813850 # C 1.607920 # Name: (bar, two), dtype: float64
Обратите внимание, что в этом примере также будет работать вызов df.loc['bar', 'two']
, но такое сокращенное обозначение в целом может привести к неоднозначности.
Если с помощью DataFrame.loc[]
нужно выбрать определенный столбец, то необходимо использовать следующий кортеж:
>>> df.loc[("bar", "two"), "A"] # 0.8052440253863785
Не обязательно указывать все уровни MultiIndex
, а передать только первые элементы кортежа. Например, можно использовать "частичную" индексацию, чтобы получить все элементы группы bar
на первом уровне:
# полная запись => df.loc['bar',] >>> df.loc["bar"] # A B C # second # one 0.895717 0.410835 -1.413681 # two 0.805244 0.813850 1.607920
Это сокращение для более подробной записи df.loc[('bar',),]
.
Также неплохо работает "частичный" срез.
>>> df.loc["baz":"foo"] # 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
Можно выполнить срез по диапазону значений, предоставив фрагмент кортежей.
>>> df.loc[("baz", "two"):("qux", "one")] # 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 >>> df.loc[("baz", "two"):"foo"] # 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
Передача списка индексных меток или кортежей работает аналогично переиндексации:
>>> df.loc[[("bar", "two"), ("qux", "one")]] # A B C # first second # bar two 0.805244 0.813850 1.607920 # qux one -1.170299 1.130127 0.974466
Примечание. Важно отметить, что когда дело доходит до индексации, то в
pandas
кортежи и списки не обрабатываются одинаково. Кортеж интерпретируется как один многоуровневый ключ, а список используется для указания нескольких ключей. Другими словами, кортежи идут горизонтально (обход уровней), списки идут вертикально (уровни сканирования).
Важно отметить, что список кортежей индексирует несколько полных ключей MultiIndex
, а кортеж списков относится к нескольким значениям внутри уровня:
s = pd.Series( [1, 2, 3, 4, 5, 6], index=pd.MultiIndex.from_product([["A", "B"], ["c", "d", "e"]]), ) # список кортежей >>> s.loc[[("A", "c"), ("B", "d")]] # A c 1 # B d 5 # dtype: int64 # кортеж списков >>> s.loc[(["A", "B"], ["c", "d"])] # A c 1 # d 2 # B c 4 # d 5 # dtype: int64
slice()
и pandas.IndexSlice
для выполнения срезов с MultiIndex
Вы можете , предоставив несколько индексаторов.
Для выполнения срезов с MultiIndex
можно предоставить любой из селекторов, как если бы вы индексировали по метке, включая срезы, списки меток, а так-же метки и логические операторы.
Чтобы выбрать все содержимое какого-то уровня, то для него необходимо использовать вызов slice(None), при этом не нужно указывать более глубокие уровни, они будут подразумеваться.
Предупреждение. В спецификации
DataFrame.loc
необходимо указать все оси, то есть для индекса и столбцов. Есть некоторые неоднозначные случаи, когда переданный индексатор может быть ошибочно интерпретирован как индексирующий обе оси, а не как, скажем,MultiIndex
для строк.# ВНИМАНИЕ псевдокод # используется полная спецификация df.loc[(slice("A1", "A3"), ...), :] # не следует этого делать: df.loc[(slice("A1", "A3"), ...)]
Для примеров выбора данных срезом подготовим новый DataFrame
c MultiIndex
def mklbl(prefix, n): return ["%s%s" % (prefix, i) for i in range(n)] miindex = pd.MultiIndex.from_product( [mklbl("A", 4), mklbl("B", 2), mklbl("C", 4), mklbl("D", 2)] ) micolumns = pd.MultiIndex.from_tuples( [("a", "foo"), ("a", "bar"), ("b", "foo"), ("b", "bah")], names=["lvl0", "lvl1"] ) dfmi = ( pd.DataFrame( np.arange(len(miindex) * len(micolumns)).reshape( (len(miindex), len(micolumns)) ), index=miindex, columns=micolumns, ) .sort_index() .sort_index(axis=1) ) >>> dfmi # 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 # ... ... ... ... ... # A3 B1 C1 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
с использованием функции slice()
, списков и индексных меток.
>>> dfmi.loc[(slice("A1", "A3"), slice(None), ["C1", "C3"]), :] # 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 # ... ... ... ... ... # A3 B0 C3 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]
Можно использовать pandas.IndexSlice
для обеспечения более естественного синтаксиса (:
), вместо использования slice(None)
.
>>> idx = pd.IndexSlice >>> dfmi.loc[idx[:, :, ["C1", "C3"]], idx[:, "foo"]] # 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 # ... ... ... # A3 B0 C3 D1 220 222 # B1 C1 D0 232 234 # D1 236 238 # C3 D0 248 250 # D1 252 254 # # [32 rows x 2 columns]
Используя этот метод, можно выполнять довольно сложную выборку по нескольким осям одновременно..
>>> dfmi.loc["A1", (slice(None), "foo")] # 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 # ... ... ... # B1 C1 D1 108 110 # C2 D0 112 114 # D1 116 118 # C3 D0 120 122 # D1 124 126 # # [16 rows x 2 columns] >>> dfmi.loc[idx[:, :, ["C1", "C3"]], idx[:, "foo"]] # 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 # ... ... ... # A3 B0 C3 D1 220 222 # B1 C1 D0 232 234 # D1 236 238 # C3 D0 248 250 # D1 252 254 # # [32 rows x 2 columns]
Используя логические операторы, можно сделать выборку данных, связанный со значениями.
>>> mask = dfmi[("a", "foo")] > 200 >>> dfmi.loc[idx[mask, :, ["C1", "C3"]], idx[:, "foo"]] # 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
Для DataFrame.loc
также можно указать аргумент оси axis
.
>>> dfmi.loc(axis=0)[:, :, ["C1", "C3"]] # 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 # ... ... ... ... ... # A3 B0 C3 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]
Кроме того, можно устанавливать/изменять значения элемента, используя следующие методы.
# сделаем копию `DataFrame` >>> df2 = dfmi.copy() >>> df2.loc(axis=0)[:, :, ["C1", "C3"]] = -10 >>> df2 # 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 # ... ... ... ... ... # A3 B1 C1 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]
Можно использовать правую часть выравниваемого объекта.
# сделаем копию `DataFrame` >>> df2 = dfmi.copy() >>> df2.loc[idx[:, :, ["C1", "C3"]], :] = df2 * 1000 >>> df2 # 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 # ... ... ... ... ... # A3 B1 C1 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]
Чтобы упростить выбор данных на определенном уровне MultiIndex
можно использовать метод DataFrame.xs()
, который дополнительно принимает аргумент уровня level
.
# используем ранее созданный `DataFrame` >>> df # 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 >>> df.xs("one", level="second") # 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
Делаем то-же самое, с использованием среза pandas
и функции slice()
.
>>> df.loc[(slice(None), "one"), :] # 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
Можно выбрать столбцы с помощью метода DataFrame.xs()
, указав аргумент axis
.
# транспонируем `DataFrame` >>> df = df.T >>> df.xs("one", level="second", axis=1) # 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
Делаем то-же самое, с использованием среза pandas
и функции slice()
. Обратите внимание, что здесь сохраняется метка выбранного уровня 'one'
>>> df.loc[:, (slice(None), "one")] # 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
Метод DataFrame.xs()
позволяет выбирать данные с помощью нескольких ключей.
>>> df.xs(("one", "bar"), level=("second", "first"), axis=1) # first bar # second one # A 0.895717 # B 0.410835 # C -1.413681
Делаем то-же самое, с использованием среза pandas
. Обратите внимание, что в этом случае метки уровней пропадают.
>>> df.loc[:, ("bar", "one")] # A 0.895717 # B 0.410835 # C -1.413681 # Name: (bar, one), dtype: float64
При использовании метода DataFrame.xs()
, для сохранения выбранного уровня, нужно в передать аргумент drop_level=False
.
# аргумент `drop_level=True` (по умолчанию) >>> df.xs("one", level="second", axis=1, drop_level=True) # 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 # аргумент `drop_level=False` >>> df.xs("one", level="second", axis=1, drop_level=False) # 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
MultiIndex
Использование аргумента level
в методах DataFrame.reindex()
и DataFrame.align()
объектов pandas
полезно для передачи значений по уровню. Например:
# создаем `MultiIndex` midx = pd.MultiIndex( levels=[["zero", "one"], ["x", "y"]], codes=[[1, 1, 0, 0], [1, 0, 1, 0]] ) # теперь `DataFrame` с `MultiIndex` >>> df = pd.DataFrame(np.random.randn(4, 2), index=midx) >>> df # 0 1 # one y 1.519970 -0.493662 # x 0.600178 0.274230 # zero y 0.132885 -0.023688 # x 2.410179 1.450520 # создаем `df2` из `df` при помощи группировки по уровню >>> df2 = df.groupby(level=0).mean() >>> df2 # 0 1 # one 1.060074 -0.109716 # zero 1.271532 0.713416 # переиндексируем `df2` с индексом `df` >>> df2.reindex(df.index, level=0) # 0 1 # one y 1.060074 -0.109716 # x 1.060074 -0.109716 # zero y 1.271532 0.713416 # x 1.271532 0.713416 # выравниваем `df` с `df2` по уровню `level=0` >>> df_aligned, df2_aligned = df.align(df2, level=0) >>> df_aligned # 0 1 # one y 1.519970 -0.493662 # x 0.600178 0.274230 # zero y 0.132885 -0.023688 # x 2.410179 1.450520 >>> df2_aligned # 0 1 # one y 1.060074 -0.109716 # x 1.060074 -0.109716 # zero y 1.271532 0.713416 # x 1.271532 0.713416
DataFrame.swaplevel()
Метод DataFrame.swaplevel()
может переключать порядок двух уровней:
>>> df # 0 1 # one y 1.519970 -0.493662 # x 0.600178 0.274230 # zero y 0.132885 -0.023688 # x 2.410179 1.450520 >>> df.swaplevel(0, 1, axis=0) # 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
DataFrame.reorder_levels()
Метод DataFrame.reorder_levels()
обобщает метод DataFrame.swaplevel()
, позволяя переставлять уровни MultiIndex
за один шаг:
>>> df.reorder_levels([1, 0], axis=0) # 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
Index
или MultiIndex
Метод DataFrame.rename()
используется для переименования меток MultiIndex
и обычно используется для переименования столбцов DataFrame
. Аргумент columns
позволяет указать словарь, включающий только те столбцы, которые необходимо переименовать.
>>> df.rename(columns={0: "col0", 1: "col1"}) # col0 col1 # one y 1.519970 -0.493662 # x 0.600178 0.274230 # zero y 0.132885 -0.023688 # x 2.410179 1.450520
Этот метод также можно использовать для переименования определенных меток основного индекса DataFrame
.
>>> df.rename(index={"one": "two", "y": "z"}) # 0 1 # two z 1.519970 -0.493662 # x 0.600178 0.274230 # zero z 0.132885 -0.023688 # x 2.410179 1.450520
Метод DataFrame.rename_axis()
используется для переименования меток Index
или MultiIndex
. В частности, можно указать имена уровней MultiIndex
, что полезно, если позже используется функция DataFrame.reset_index()
для перемещения значений из MultiIndex
в столбец.
>>> df.rename_axis(index=["abc", "def"]) # 0 1 # abc def # one y 1.519970 -0.493662 # x 0.600178 0.274230 # zero y 0.132885 -0.023688 # x 2.410179 1.450520
Обратите внимание, что столбцы DataFrame
являются индексом, поэтому использование DataFrame.rename_axis()
с аргументом columns
изменит имя этого индекса.
>>> df.rename_axis(columns="Cols").columns # RangeIndex(start=0, stop=2, step=1, name='Cols')
Методы DataFrame.rename()
и DataFrame.rename_axis()
поддерживают передачу словаря, Series
или функции для сопоставления меток/имен с новыми значениями.
При работе с объектом Index
напрямую, а не через DataFrame
, для изменения меток/имен можно использовать метод Index.set_names()
.
>>> mi = pd.MultiIndex.from_product([[1, 2], ["a", "b"]], names=["x", "y"]) >>> mi # MultiIndex([(1, 'a'), # (1, 'b'), # (2, 'a'), # (2, 'b')], # names=['x', 'y']) >>> mi2 = mi.rename("new name", level=0) >>> mi2 # MultiIndex([(1, 'a'), # (1, 'b'), # (2, 'a'), # (2, 'b')], # !=> метка изменилась # names=['new name', 'y'])
НЕЛЬЗЯ установить имена меток MultiIndex
через уровень.
>>> mi.levels[0].name = "name via level" # Traceback (most recent call last): # ... # RuntimeError: Cannot set name on a level of a MultiIndex. # Use 'MultiIndex.set_names' instead.
MultiIndex
Чтобы объекты с MultiIndex
можно было эффективно индексировать и нарезать, их необходимо отсортировать. Как и в случае с любым индексом, для этого можно использовать DataFrame.sort_index()
.
>>> import random >>> random.shuffle(tuples) >>> s = pd.Series(np.random.randn(8), index=pd.MultiIndex.from_tuples(tuples)) >>> s # baz two 0.206053 # one -0.251905 # qux two -2.213588 # bar one 1.063327 # foo two 1.266143 # bar two 0.299368 # qux one -0.863838 # foo one 0.408204 # dtype: float64 >>> s.sort_index() # bar one 1.063327 # two 0.299368 # baz one -0.251905 # two 0.206053 # foo one 0.408204 # two 1.266143 # qux one -0.863838 # two -2.213588 # dtype: float64 >>> s.sort_index(level=0) # bar one 1.063327 # two 0.299368 # baz one -0.251905 # two 0.206053 # foo one 0.408204 # two 1.266143 # qux one -0.863838 # two -2.213588 # dtype: float64 >>> s.sort_index(level=1) # bar one 1.063327 # baz one -0.251905 # foo one 0.408204 # qux one -0.863838 # bar two 0.299368 # baz two 0.206053 # foo two 1.266143 # qux two -2.213588 # dtype: float64
Аргумент level
метода DataFrame.sort_index()
также принимает имя метки уровня, если уровни MultiIndex
имеют имена.
# дадим имена уровням `MultiIndex` в созданной ранее `Series` >>> s.index = s.index.set_names(["L1", "L2"]) >>> s.sort_index(level="L1") # L1 L2 # bar one 1.063327 # two 0.299368 # baz one -0.251905 # two 0.206053 # foo one 0.408204 # two 1.266143 # qux one -0.863838 # two -2.213588 # dtype: float64 >>> s.sort_index(level="L2") # L1 L2 # bar one 1.063327 # baz one -0.251905 # foo one 0.408204 # qux one -0.863838 # bar two 0.299368 # baz two 0.206053 # foo two 1.266143 # qux two -2.213588 # dtype: float64
На объектах больших объектах с MultiIndex
можно сортировать любые другие оси по уровню:
# например, через смену осей >>> df.T.sort_index(level=1, axis=1) # one zero one zero # x x y y # 0 0.600178 2.410179 1.519970 0.132885 # 1 0.274230 1.450520 -0.493662 -0.023688
Индексирование будет работать, даже если данные не отсортированы, но будет довольно неэффективно (будет отображаться предупреждение о производительности). При этом будет возвращаться копия данных, а не представление:
dfm = pd.DataFrame( {"jim": [0, 0, 1, 1], "joe": ["x", "x", "z", "y"], "jolie": np.random.rand(4)} ) >>> dfm = dfm.set_index(["jim", "joe"]) >>> dfm # jolie # jim joe # 0 x 0.490671 # x 0.120248 # 1 z 0.537020 # y 0.110968 >>> dfm.loc[(1, 'z')] # PerformanceWarning: indexing past lexsort depth may impact performance. # jolie # jim joe # 1 z 0.53702
Более того, если попытаться выбрать какие-то данные, которые не полностью отсортированы, это может привести к:
>>> dfm.loc[(0, 'y'):(1, 'z')] # Traceback (most recent call last): # ... # pandas.errors.UnsortedIndexError: # 'Key length (2) was greater than MultiIndex lexsort depth (1)'
Проверить, отсортирован ли MultiIndex
поможет метод MultiIndex.is_monotonic_increasing()
:
>>> dfm.index.is_monotonic_increasing # False >>> dfm = dfm.sort_index() >>> dfm # jolie # jim joe # 0 x 0.490671 # x 0.120248 # 1 y 0.110968 # z 0.537020 >>> dfm.index.is_monotonic_increasing # True
И теперь выбор данных работает как положено.
>>> dfm.loc[(0, 'y'):(1, 'z')] # jolie # jim joe # 1 y 0.110968 # z 0.537020