如何在 MultiIndex Groupby 中高效地执行乘法

Posted

技术标签:

【中文标题】如何在 MultiIndex Groupby 中高效地执行乘法【英文标题】:How to Efficiently Perform Multiplication within MultiIndex Groupby 【发布时间】:2021-06-20 12:25:28 【问题描述】:

我正在尝试使用我的两个二级索引来计算第三个索引。但是,我找不到一种惯用的方式来做到这一点。

如何从其他两个二级索引中计算一个二级索引?每个组都有相同的二级索引。

我的代码


import numpy as np
import pandas as pd

# Create index
tickers = ['A', 'B', 'C']
line_items = ['sales', 'ebitda_margin', 'ebitda', 'other_field']
index = pd.MultiIndex.from_product([tickers, line_items], names=['ticker', 'line_item'])

df = pd.DataFrame([100, 0.3, np.nan, 7, 200, 0.2, np.nan, 8, 300, 0.1, np.nan, 9], 
                  index=index, 
                  columns=[0])

# Let's assume 10% sales growth for all companies
# This is included to show that I am doing this calculation for multiple years (columns)
df[1] = df[0]
df.loc[pd.IndexSlice[:, 'sales'], 1] *= 1.1

这会产生以下数据框:

                          0      1
ticker line_item                  
A      sales          100.0  110.0
       ebitda_margin    0.3    0.3
       ebitda           NaN    NaN
       other_field      7.0    7.0
B      sales          200.0  220.0
       ebitda_margin    0.2    0.2
       ebitda           NaN    NaN
       other_field      8.0    8.0
C      sales          300.0  330.0
       ebitda_margin    0.1    0.1
       ebitda           NaN    NaN
       other_field      9.0    9.0

我有什么

请注意,我知道我需要对索引进行一些工作才能使以下内容正常工作,但如果存在的话,我宁愿找到更好的方法,而不是使用此代码。

df.apply(lambda x: x.loc[pd.IndexSlice[:, 'sales']] * x.loc[pd.IndexSlice[:, 'ebitda_margin']])

有没有更好的方法来做到这一点?

【问题讨论】:

df.stack().unstack('line_item').eval('ebitda = ebitda_margin * sales').stack().unstack(1)? 这很好用@piRSquared。有没有办法让它变成多行并用其他东西替换 eval 语句?编辑:我在几行中找到了一种方法。感谢您的洞察力。 是的!我想。请发布您希望最终数据框的外观。另外,不要害怕 pd.DataFrame.eval 这不是你的普通 Python eval 我发布了一个引用您评论的答案。再次感谢您,@piRSquared。 【参考方案1】:

尝试xs 作为pd.IndexSlice 的替代品,您可以在其中删除一个级别,然后mul 在相乘时允许级别对齐:

df.loc[pd.IndexSlice[:, 'sales'],:] = (df.loc[pd.IndexSlice[:, 'sales'],:]
                                         .mul(df.xs('ebitda_margin', level='line_item'), level=0)
                                      )

输出:

                         0
ticker line_item          
A      sales          30.0
       ebitda_margin   0.3
       ebitda          NaN
       other_field     7.0
B      sales          40.0
       ebitda_margin   0.2
       ebitda          NaN
       other_field     8.0
C      sales          30.0
       ebitda_margin   0.1
       ebitda          NaN
       other_field     9.0

【讨论】:

有没有办法将 ebitda 设置为 sales * ebitda_margin。我认为这是弄乱了索引,导致设置不正确。 没关系,如果我只是获取值并使用索引切片进行设置,则可以。谢谢!【参考方案2】:
df.loc[df.index.get_level_values(1) == "sales"] = (
    df.loc[df.index.get_level_values(1) == "sales"].values
    * df.loc[df.index.get_level_values(1) == "ebitda_margin"].values
)

print(df)

打印:

                         0     1
ticker line_item                
A      sales          30.0  33.0
       ebitda_margin   0.3   0.3
       ebitda          NaN   NaN
       other_field     7.0   7.0
B      sales          40.0  44.0
       ebitda_margin   0.2   0.2
       ebitda          NaN   NaN
       other_field     8.0   8.0
C      sales          30.0  33.0
       ebitda_margin   0.1   0.1
       ebitda          NaN   NaN
       other_field     9.0   9.0

【讨论】:

有没有办法让我将 ebitda 指数设置为 sales * ebitda_margin? @JackMoody 只需将“sales”更改为“ebidta”即可。试试df.loc[df.index.get_level_values(1) == "ebitda"] = ...the same...【参考方案3】:

感谢@piRSquared 帮助我解决这个问题。以下为我做了:


piR cmets: 关键是将行项目放入列中以使数学更容易。如果数据框是面向演示的并且很小,那么stack/unstack/pivot 可以更方便地进行计算,然后stack/unstack/pivot 就足够了。


df_restack = df.stack().unstack('line_item')
df_restack['ebitda'] = df_restack['sales'] * df_restack['ebitda_margin']
df = df_restack.stack().unstack(1)

这会产生以下数据框:

                          0      1
ticker line_item                  
A      ebitda          30.0   33.0
       ebitda_margin    0.3    0.3
       other_field      7.0    7.0
       sales          100.0  110.0
B      ebitda          40.0   44.0
       ebitda_margin    0.2    0.2
       other_field      8.0    8.0
       sales          200.0  220.0
C      ebitda          30.0   33.0
       ebitda_margin    0.1    0.1
       other_field      9.0    9.0
       sales          300.0  330.0

我会注意到,当数据框较大时,Quang 和 Andrej 的答案要快得多,所以我选择了 Quang 的答案,因为他首先回答。

【讨论】:

以上是关于如何在 MultiIndex Groupby 中高效地执行乘法的主要内容,如果未能解决你的问题,请参考以下文章

Pandas Multiindex Groupby 列

带有 pandas groupby multiindex 的箱线图,用于来自 multiindex 的指定子级别

不同长度的pandas groupby元组-ValueError:在通过级别中找不到值:MultiIndex

Pandas Multiindex 和 Groupby 返回奇怪的行为

Pandas Multiindex Groupby 聚合列与另一列的值

Pandas Groupby Plotting MultiIndex 按***分组