Преимущества мультииндекса панды?

Итак, я узнал, что могу использовать DataFrame.groupby, не имея MultiIndex для выполнения подвыборки / сечения.

С другой стороны, когда у меня есть MultiIndex на DataFrame, мне все еще нужно использовать DataFrame.groupby для создания подвыборок / сечений.

Итак, для чего нужен MultiIndex, кроме довольно полезного и красивого отображения иерархий при печати?

4 ответа

Решение

Иерархическая индексация (также называемая "многоуровневой" индексацией) была введена в выпуске pandas 0.4.

Это открывает двери для довольно сложного анализа и манипулирования данными, особенно для работы с многомерными данными. По сути, это позволяет эффективно хранить и манипулировать данными произвольно большого размера, например, в двумерной табличной структуре (DataFrame).

Представьте себе создание информационного кадра с использованием MultiIndex как это:-

import pandas as pd
import numpy as np

np.arrays = [['one','one','one','two','two','two'],[1,2,3,1,2,3]]

df = pd.DataFrame(np.random.randn(6,2),index=pd.MultiIndex.from_tuples(list(zip(*np.arrays))),columns=['A','B'])

df  # This is the dataframe we have generated

          A         B
one 1 -0.732470 -0.313871
    2 -0.031109 -2.068794
    3  1.520652  0.471764
two 1 -0.101713 -1.204458
    2  0.958008 -0.455419
    3 -0.191702 -0.915983

это df это просто структура данных двух измерений

df.ndim

2

Но мы можем представить это, глядя на результат, как трехмерную структуру данных.

  • one с 1 с данными -0.732470 -0.313871,
  • one с 2 с данными -0.031109 -2.068794,
  • one с 3 с данными 1.520652 0.471764,

Ака: "эффективно хранить и манипулировать данными произвольно большого размера в двухмерной табличной структуре"

Это не просто "симпатичный дисплей". Преимущество заключается в простоте поиска данных, поскольку теперь у нас есть иерархический индекс.

Например.

In [44]: df.ix["one"]
Out[44]: 
          A         B
1 -0.732470 -0.313871
2 -0.031109 -2.068794
3  1.520652  0.471764

даст нам новый фрейм данных только для группы данных, принадлежащих "одному".

И мы можем сузить наш выбор данных, сделав это:

In [45]: df.ix["one"].ix[1]
Out[45]: 
A   -0.732470
B   -0.313871
Name: 1

И, конечно, если мы хотим конкретное значение, вот пример:

In [46]: df.ix["one"].ix[1]["A"]
Out[46]: -0.73247029752040727

Таким образом, если у нас есть еще больше индексов (помимо двух индексов, показанных в примере выше), мы можем существенно развернуть и выбрать набор данных, который нас действительно интересует, без необходимости groupby,

Мы даже можем получить поперечное сечение (строки или столбцы) из нашего кадра данных...

По строкам: -

In [47]: df.xs('one')
Out[47]: 
          A         B
1 -0.732470 -0.313871
2 -0.031109 -2.068794
3  1.520652  0.471764

По столбцам: -

In [48]: df.xs('B', axis=1)
Out[48]: 
one  1   -0.313871
     2   -2.068794
     3    0.471764
two  1   -1.204458
     2   -0.455419
     3   -0.915983
Name: B

Отличный пост @Calvin Cheng, но подумал, что я тоже попробую.

Когда использовать MultiIndex:

  1. Когда значение одного столбца недостаточно для однозначной идентификации строки.
  2. Когда данные логически иерархичны - это означает, что они имеют несколько измерений или "уровней".

Почему (ваш основной вопрос) - по крайней мере, это самые большие преимущества IMO:

  1. Простое манипулирование с помощью stack() и unstack()
  2. Простая математика, когда есть несколько уровней столбцов
  3. Синтаксический сахар для нарезки / фильтрации

Пример:

                                                       Dollars  Units
Date       Store   Category Subcategory UPC EAN
2018-07-10 Store 1 Alcohol  Liqour      80480280024    154.77      7
           Store 2 Alcohol  Liqour      80480280024     82.08      4
           Store 3 Alcohol  Liqour      80480280024    259.38      9
           Store 1 Alcohol  Liquor      80432400630    477.68     14
                                        674545000001   139.68      4
           Store 2 Alcohol  Liquor      80432400630    203.88      6
                                        674545000001   377.13     13
           Store 3 Alcohol  Liquor      80432400630    239.19      7
                                        674545000001   432.32     14
           Store 1 Beer     Ales        94922755711     65.17      7
                                        702770082018   174.44     14
                                        736920111112    50.70      5
           Store 2 Beer     Ales        94922755711    129.60     12
                                        702770082018   107.40     10
                                        736920111112    59.65      5
           Store 3 Beer     Ales        94922755711    154.00     14
                                        702770082018   137.40     10
                                        736920111112   107.88     12
           Store 1 Beer     Lagers      702770081011   156.24     12
           Store 2 Beer     Lagers      702770081011   137.06     11
           Store 3 Beer     Lagers      702770081011   119.52      8    

1) Если мы хотим легко сравнить продажи по магазинам, мы можем использовать df.unstack('Store') чтобы выстроить все в ряд:

                                             Dollars                   Units
Store                                        Store 1 Store 2 Store 3 Store 1 Store 2 Store 3
Date       Category Subcategory UPC EAN
2018-07-10 Alcohol  Liqour      80480280024   154.77   82.08  259.38       7       4       9
                    Liquor      80432400630   477.68  203.88  239.19      14       6       7
                                674545000001  139.68  377.13  432.32       4      13      14
           Beer     Ales        94922755711    65.17  129.60  154.00       7      12      14
                                702770082018  174.44  107.40  137.40      14      10      10
                                736920111112   50.70   59.65  107.88       5       5      12
                    Lagers      702770081011  156.24  137.06  119.52      12      11       8

2) Мы также можем легко сделать математику на нескольких столбцах. Например, df['Dollars'] / df['Units'] затем разделим доллары каждого магазина на его единицы для каждого магазина без нескольких операций:

Store                                         Store 1  Store 2  Store 3
Date       Category Subcategory UPC EAN
2018-07-10 Alcohol  Liqour      80480280024     22.11    20.52    28.82
                    Liquor      80432400630     34.12    33.98    34.17
                                674545000001    34.92    29.01    30.88
           Beer     Ales        94922755711      9.31    10.80    11.00
                                702770082018    12.46    10.74    13.74
                                736920111112    10.14    11.93     8.99
                    Lagers      702770081011    13.02    12.46    14.94

3) Если затем мы хотим фильтровать только определенные строки, вместо использования

df[(df[col1] == val1) and (df[col2] == val2) and (df[col3] == val3)]

формат, мы можем вместо.xs или.query (да, они работают для обычных dfs, но это не очень полезно). Синтаксис вместо этого будет:

df.xs((val1, val2, val3), level=(col1, col2, col3))

Больше примеров можно найти в этом учебном пособии, которое я собрал.

Альтернативой использованию мультииндекса является хранение ваших данных с использованием нескольких столбцов фрейма данных. Можно было бы ожидать, что multiindex обеспечит прирост производительности по сравнению с обычным хранилищем столбцов, но с Pandas v 1.1.4, похоже, это не так.

Timinigs

import numpy as np
import pandas as pd

np.random.seed(2020)
inv = pd.DataFrame({
    'store_id': np.random.choice(10000, size=10**7),
    'product_id': np.random.choice(1000, size=10**7),
    'stock': np.random.choice(100, size=10**7),
})
# Create a DataFrame with a multiindex
inv_multi = inv.groupby(['store_id', 'product_id'])[['stock']].agg('sum')
print(inv_multi)
                     stock
store_id product_id       
0        2              48
         4              18
         5              58
         7             149
         8             158
...                    ...
9999     992           132
         995           121
         996           105
         998            99
         999            16

[6321869 rows x 1 columns]
# Create a DataFrame without a multiindex
inv_cols = inv_multi.reset_index()
print(inv_cols)
         store_id  product_id  stock
0               0           2     48
1               0           4     18
2               0           5     58
3               0           7    149
4               0           8    158
...           ...         ...    ...
6321864      9999         992    132
6321865      9999         995    121
6321866      9999         996    105
6321867      9999         998     99
6321868      9999         999     16

[6321869 rows x 3 columns]
%%timeit
inv_multi.xs(key=100, level='store_id')
10 loops, best of 3: 20.2 ms per loop

%%timeit
inv_cols.loc[inv_cols.store_id == 100]
The slowest run took 8.79 times longer than the fastest. This could mean that an intermediate result is being cached.
100 loops, best of 3: 11.5 ms per loop

%%timeit
inv_multi.xs(key=100, level='product_id')
100 loops, best of 3: 9.08 ms per loop

%%timeit
inv_cols.loc[inv_cols.product_id == 100]
100 loops, best of 3: 12.2 ms per loop

%%timeit
inv_multi.xs(key=(100, 100), level=('store_id', 'product_id'))
10 loops, best of 3: 29.8 ms per loop

%%timeit
inv_cols.loc[(inv_cols.store_id == 100) & (inv_cols.product_id == 100)]
10 loops, best of 3: 28.8 ms per loop

Заключение

Преимущества использования MultiIndex заключаются в синтаксическом сахаре, самодокументировании данных и небольших удобствах таких функций, как unstack()как упоминалось в ответе @ZaxR; Производительность - это не преимущество, которое кажется упущенной возможностью.

Судя по комментарию к этому ответу, эксперимент был ошибочным. Вот моя попытка правильного эксперимента.

Тайминги

      import pandas as pd
import numpy as np
from timeit import timeit


random_data = np.random.randn(16, 4)

multiindex_lists = [["A", "B", "C", "D"], [1, 2, 3, 4]]
multiindex = pd.MultiIndex.from_product(multiindex_lists)

dfm = pd.DataFrame(random_data, multiindex)
df = dfm.reset_index()
print("dfm:\n", dfm, "\n")
print("df\n", df, "\n")

dfm_selection = dfm.loc[("B", 4), 3]
print("dfm_selection:", dfm_selection, type(dfm_selection))

df_selection = df[(df["level_0"] == "B") & (df["level_1"] == 4)][3].iat[0]
print("df_selection: ", df_selection, type(df_selection), "\n")

print("dfm_selection timeit:",
      timeit(lambda: dfm.loc[("B", 4), 3], number=int(1e6)))
print("df_selection timeit: ",
      timeit(
          lambda: df[(df["level_0"] == "B") & (df["level_1"] == 4)][3].iat[0],
          number=int(1e6)))
      dfm:
             0         1         2         3
A 1 -1.055128 -0.845019 -2.853027  0.521738
  2  0.397804  0.385045 -0.121294 -0.696215
  3 -0.551836 -0.666953 -0.956578  1.929732
  4 -0.154780  1.778150  0.183104 -0.013989
B 1 -0.315476  0.564419  0.492496 -1.052432
  2 -0.695300  0.085265  0.701724 -0.974168
  3 -0.879915 -0.206499  1.597701  1.294885
  4  0.653261  0.279641 -0.800613  1.050241
C 1  1.004199 -1.377520 -0.672913  1.491793
  2 -0.453452  0.367264 -0.002362  0.411193
  3  2.271958  0.240864 -0.923934 -0.572957
  4  0.737893 -0.523488  0.485497 -2.371977
D 1  1.133661 -0.584973 -0.713320 -0.656315
  2 -1.173231 -0.490667  0.634677  1.711015
  3 -0.050371 -0.175644  0.124797  0.703672
  4  1.349595  0.122202 -1.498178  0.013391

df
    level_0  level_1         0         1         2         3
0        A        1 -1.055128 -0.845019 -2.853027  0.521738
1        A        2  0.397804  0.385045 -0.121294 -0.696215
2        A        3 -0.551836 -0.666953 -0.956578  1.929732
3        A        4 -0.154780  1.778150  0.183104 -0.013989
4        B        1 -0.315476  0.564419  0.492496 -1.052432
5        B        2 -0.695300  0.085265  0.701724 -0.974168
6        B        3 -0.879915 -0.206499  1.597701  1.294885
7        B        4  0.653261  0.279641 -0.800613  1.050241
8        C        1  1.004199 -1.377520 -0.672913  1.491793
9        C        2 -0.453452  0.367264 -0.002362  0.411193
10       C        3  2.271958  0.240864 -0.923934 -0.572957
11       C        4  0.737893 -0.523488  0.485497 -2.371977
12       D        1  1.133661 -0.584973 -0.713320 -0.656315
13       D        2 -1.173231 -0.490667  0.634677  1.711015
14       D        3 -0.050371 -0.175644  0.124797  0.703672
15       D        4  1.349595  0.122202 -1.498178  0.013391 
      dfm_selection: 1.0502406808918188 <class 'numpy.float64'>
df_selection:  1.0502406808918188 <class 'numpy.float64'> 

dfm_selection timeit: 63.92458086000079
df_selection timeit:  450.4555013199997

Заключение

Извлечение одного значения MultiIndex более чем в 7 раз быстрее, чем обычное извлечение одного значения кадра данных.

Синтаксис извлечения MultiIndex намного чище.

Другие вопросы по тегам