panda的多索引的好处?

Posted

技术标签:

【中文标题】panda的多索引的好处?【英文标题】:Benefits of panda's multiindex? 【发布时间】:2012-10-24 22:19:16 【问题描述】:

所以我了解到我可以使用 DataFrame.groupby 而不需要 MultiIndex 来进行子采样/横截面。

另一方面,当我在 DataFrame 上有一个 MultiIndex 时,我仍然需要使用 DataFrame.groupby 来进行子采样/横截面。

那么,除了在打印时非常有用且漂亮地显示层次结构之外,MultiIndex 还有什么用处?

【问题讨论】:

除了好处之外,我也会对使用 MultiIndex 的计算成本或其他类型的成本感到好奇。 【参考方案1】:

在 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

但是我们可以把它想象成一个 3 维数据结构。

one1 与数据 -0.732470 -0.313871one2 和数据 -0.031109 -2.068794one3 和数据 1.520652 0.471764

A.k.a.:“在二维表格结构中有效地存储和操作任意高维数据”

这不仅仅是一个“漂亮的展示”。由于我们现在有一个层次索引,因此它具有易于检索数据的好处。

例如。

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

只会为属于“one”的数据组提供一个新的数据框。

我们可以通过这样做进一步缩小我们的数据选择范围:-

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

因此,如果我们有更多索引(除了上面示例中显示的 2 个索引),我们基本上可以向下钻取并选择我们真正感兴趣的数据集,而无需 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

【讨论】:

您对 3 维数据结构 (one with 1 with data -0.790620 0.229276. ...) 的第一次描述中的 3 对数据似乎与您的实际示例中的任何数据都不对应。 当 from_arrays 方法可用时,是否有任何特殊原因使用 MultiIndex.from_tuples(list(zip(*np.arrays))?df = pd.DataFrame(np.random.randn(6, 2), index=pd.MultiIndex.from_arrays(np.arrays),columns=['A','B']) 一篇不错的文章,但我认为这不能很好地回答 为什么 多索引有用。此处提到的每个操作都可以在具有 AB 列的数据帧上执行,几乎不需要额外的努力。【参考方案2】:

@Calvin Cheng 的帖子很棒,但我想我也会尝试一下。

何时使用 MultiIndex:

    当单个列的值不足以唯一标识一行时。 当数据在逻辑上是分层的 - 这意味着它具有多个维度或“级别”。

为什么(您的核心问题) - 至少这些是 IMO 的最大好处:

    通过 stack() 和 unstack() 轻松操作 有多个列级别时的简单数学运算 用于切片/过滤的语法糖

例子:

                                                       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))

更多示例可以在我整理的这个tutorial notebook中找到。

【讨论】:

【参考方案3】:

使用多索引的替代方法是使用数据框的多列存储数据。人们会期望多索引能够提供比原始列存储更高的性能,但从 Pandas v 1.1.4 开始,情况似乎并非如此。

计时

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 的好处在于语法糖、自记录数据以及来自 @ZaxR 的回答中提到的 unstack() 等函数的小便利;性能不是好处,这似乎是一个真正错失的机会。

【讨论】:

pandas index(single or multi)在速度方面的好处只有在索引唯一时才会观察到;在您的测试中,您选择的是单个级别,这不是唯一的……如果您在索引中进行选择,例如 product_id 100 和 store_id 100,您将看到额外的速度优势。这个SO link 提供了更多的信息。看看 pandas 源代码也可以了解更多

以上是关于panda的多索引的好处?的主要内容,如果未能解决你的问题,请参考以下文章

具有多索引的 Pandas 数据透视表小计

Pandas:具有多索引的 fillna() 方法 - NaN 填充了错误的列

Pandas Pivot Table - 重新组织多索引的顺序

具有多索引的 Pandas 子数据透视表和总数据透视表

Pandas groupby(),agg() - 如何在没有多索引的情况下返回结果?

如何将 2 个未对齐的 Pandas 系列相乘并接收具有多索引的产品系列