【问题标题】:Pythonic / Panda Way to Create Function to GroupbyPythonic / Panda 方式创建函数到 Groupby
【发布时间】:2019-02-11 22:59:48
【问题描述】:

我对编程相当陌生,正在寻找一种更 Python 的方式来实现一些代码。这是虚拟数据:

 df = pd.DataFrame({
'Category':np.random.choice( ['Group A','Group B'], 10000),
'Sub-Category':np.random.choice( ['X','Y','Z'], 10000),
'Sub-Category-2':np.random.choice( ['G','F','I'], 10000),
'Product':np.random.choice( ['Product 1','Product 2','Product 3'], 10000),
'Units_Sold':np.random.randint(1,100, size=(10000)),
'Dollars_Sold':np.random.randint(100,1000, size=10000), 
'Customer':np.random.choice(pd.util.testing.rands_array(10,25,dtype='str'),10000),
'Date':np.random.choice( pd.date_range('1/1/2016','12/31/2018',  
                  freq='D'), 10000)})

我有很多这样的交易数据,我在上面执行各种 Groupby。我目前的解决方案是像这样制作一个 master groupby:

master = df.groupby(['Customer','Category','Sub-Category','Product',pd.Grouper(key='Date',freq='A')])['Units_Sold'].sum()\
.unstack()

从那里,我使用 .groupby(level=) 函数执行各种分组,以我正在寻找的方式聚合信息。我通常在每个级别做一个总结。此外,我使用以下代码的一些变体在每个级别创建小计。

y = master.groupby(level=[0,1,2]).sum()
y.index = pd.MultiIndex.from_arrays([
    y.index.get_level_values(0),
    y.index.get_level_values(1),
    y.index.get_level_values(2) + ' Total',
    len(y.index)*['']
])

y1 = master.groupby(level=[0,1]).sum()
y1.index = pd.MultiIndex.from_arrays([
    y1.index.get_level_values(0),
    y1.index.get_level_values(1)+ ' Total',
    len(y1.index)*[''],
    len(y1.index)*['']
])

y2 = master.groupby(level=[0]).sum()
y2.index = pd.MultiIndex.from_arrays([
    y2.index.get_level_values(0)+ ' Total',
    len(y2.index)*[''],
    len(y2.index)*[''],
    len(y2.index)*['']
])

pd.concat([master,y,y1,y2]).sort_index()\
    .assign(Diff = lambda x: x.iloc[:,-1] - x.iloc[:,-2])\
    .assign(Diff_Perc = lambda x: (x.iloc[:,-2] / x.iloc[:,-3])- 1)\
    .dropna(how='all')\

这只是一个示例 - 我可以执行相同的练习,但以不同的顺序执行 groupby。例如 - 接下来我可能想按“类别”、“产品”和“客户”分组,所以我必须这样做: master.groupby(level=[1,3,0).sum()

然后我将不得不重复上述小计的整个练习。我还经常更改时间段 - 可能是特定月份的年终,可能是年初至今,可能是季度等。

根据我目前在编程中学到的知识(很明显,这很少见!),您应该在每次重复代码时编写一个函数。显然我在这个例子中一遍又一遍地重复代码。

有没有一种方法可以构建一个函数,您可以在其中向 Groupby 提供级别以及时间范围,同时创建一个用于对每个级别进行小计的函数?

在此先感谢您提供任何指导。这是非常赞赏。

【问题讨论】:

  • 你的y2 真的是你的意思吗,还是应该有level=[0]
  • 您是对的,我已经对其进行了编辑以反映这一点。谢谢!

标签: python python-3.x pandas


【解决方案1】:

对于 DRY-er 解决方案,请考虑将您当前的方法推广到一个已定义的模块中,该模块按日期范围过滤原始数据框并运行聚合,接收传入参数的 group_by 级别和日期范围(后者是可选的):

方法

def multiple_agg(mylevels, start_date='2016-01-01', end_date='2018-12-31'):

    filter_df = df[df['Date'].between(start_date, end_date)]

    master = (filter_df.groupby(['Customer', 'Category', 'Sub-Category', 'Product', 
                     pd.Grouper(key='Date',freq='A')])['Units_Sold']
                .sum()
                .unstack()
              )

    y = master.groupby(level=mylevels[:-1]).sum()
    y.index = pd.MultiIndex.from_arrays([
        y.index.get_level_values(0),
        y.index.get_level_values(1),
        y.index.get_level_values(2) + ' Total',
        len(y.index)*['']
    ])

    y1 = master.groupby(level=mylevels[0:2]).sum()
    y1.index = pd.MultiIndex.from_arrays([
        y1.index.get_level_values(0),
        y1.index.get_level_values(1)+ ' Total',
        len(y1.index)*[''],
        len(y1.index)*['']
    ])

    y2 = master.groupby(level=mylevels[0]).sum()
    y2.index = pd.MultiIndex.from_arrays([
        y2.index.get_level_values(0)+ ' Total',
        len(y2.index)*[''],
        len(y2.index)*[''],
        len(y2.index)*['']
    ])

    final_df = (pd.concat([master,y,y1,y2])
                         .sort_index()
                         .assign(Diff = lambda x: x.iloc[:,-1] - x.iloc[:,-2])
                         .assign(Diff_Perc = lambda x: (x.iloc[:,-2] / x.iloc[:,-3])- 1)
                         .dropna(how='all')
                         .reorder_levels(mylevels)
                )

    return final_df

聚合运行 (不同级别和日期范围)

agg_df1 = multiple_agg([0,1,2,3])

agg_df2 = multiple_agg([1,3,0,2], '2016-01-01', '2017-12-31')

agg_df3 = multiple_agg([2,3,1,0], start_date='2017-01-01', end_date='2018-12-31')

测试 final_df 是 OP 的 pd.concat() 输出)

# EQUALITY TESTING OF FIRST 10 ROWS
print(final_df.head(10).eq(agg_df1.head(10)))

# Date                                        2016-12-31 00:00:00  2017-12-31 00:00:00  2018-12-31 00:00:00  Diff  Diff_Perc
# Customer   Category Sub-Category Product                                                                                  
# 45mhn4PU1O Group A  X            Product 1                 True                 True                 True  True       True
#                                  Product 2                 True                 True                 True  True       True
#                                  Product 3                 True                 True                 True  True       True
#                     X Total                                True                 True                 True  True       True
#                     Y            Product 1                 True                 True                 True  True       True
#                                  Product 2                 True                 True                 True  True       True
#                                  Product 3                 True                 True                 True  True       True
#                     Y Total                                True                 True                 True  True       True
#                     Z            Product 1                 True                 True                 True  True       True
#                                  Product 2                 True                 True                 True  True       True

【讨论】:

  • 非常感谢您的帮助!我试图更好地理解该函数 - 当我运行不同的聚合时, agg_df1 返回我所期望的,包括小计。当我运行 aagg_df2 和 agg_df3 时,它不会返回我所期望的 - 即使您通过不同的级别,订单仍与 agg_df1 中的相同?此外,小计部分丢失了。最后,我似乎无法让您的测试位工作。非常感谢您的帮助!
  • 另外两个 agg_dfs 正在演示如何根据需要更改参数。所以不,它们不会匹配您发布的任何内容。根据您的特定需求进行调整。您需要将 pd.concat() 输出分配给 final_df 变量。并确保在顶部添加np.random.seed(####) 以重现相同的随机数。
  • 再次感谢!抱歉不清楚,我的意思是运行 'multiple_agg([0,1,2])' 或 'multiple_agg([1,3,0], '2016-01-01', '2017-12-31' )' 或 'multiple_agg([2,3,1], start_date='2017-01-01', end_date='2018-12-31')' 返回相同的多索引(客户,然后是类别,子类别,然后是 Product'。我在想多索引顺序会根据传入的级别的顺序而改变。
  • 我明白了。它归结为函数结束时的pd.concat。由于 master 是第一项,它根据其索引顺序追加。但是,您可以通过 mylevels 参数使用reorder_levels。但是您现在必须传递一个包含 4 个数字的列表,其中最后一个从未在聚合中使用。见编辑。
  • @Parfait 不错的解决方案。
【解决方案2】:

我认为您可以使用 sumlevel 参数来做到这一点:

master = df.groupby(['Customer','Category','Sub-Category','Product',pd.Grouper(key='Date',freq='A')])['Units_Sold'].sum()\
.unstack()
s1 = master.sum(level=[0,1,2]).assign(Product='Total').set_index('Product',append=True)
s2 = master.sum(level=[0,1])

# Wanted to use assign method but because of the hyphen in the column name you can't.
# Also use the Z in front for sorting purposes
s2['Sub-Category'] = 'ZTotal'
s2['Product'] = ''
s2 = s2.set_index(['Sub-Category','Product'], append=True)

s3 = master.sum(level=[0])
s3['Category'] = 'Total'
s3['Sub-Category'] = ''
s3['Product'] = ''
s3 = s3.set_index(['Category','Sub-Category','Product'], append=True)

master_new = pd.concat([master,s1,s2,s3]).sort_index()
master_new

输出:

Date                                        2016-12-31  2017-12-31  2018-12-31
Customer   Category Sub-Category Product                                      
30XWmt1jm0 Group A  X            Product 1       651.0       341.0       453.0
                                 Product 2       267.0       445.0       117.0
                                 Product 3       186.0       280.0       352.0
                                 Total          1104.0      1066.0       922.0
                    Y            Product 1       426.0       417.0       670.0
                                 Product 2       362.0       210.0       380.0
                                 Product 3       232.0       290.0       430.0
                                 Total          1020.0       917.0      1480.0
                    Z            Product 1       196.0       212.0       703.0
                                 Product 2       277.0       340.0       579.0
                                 Product 3       416.0       392.0       259.0
                                 Total           889.0       944.0      1541.0
                    ZTotal                      3013.0      2927.0      3943.0
           Group B  X            Product 1       356.0       230.0       407.0
                                 Product 2       402.0       370.0       590.0
                                 Product 3       262.0       381.0       377.0
                                 Total          1020.0       981.0      1374.0
                    Y            Product 1       575.0       314.0       643.0
                                 Product 2       557.0       375.0       411.0
                                 Product 3       344.0       246.0       280.0
                                 Total          1476.0       935.0      1334.0
                    Z            Product 1       278.0       152.0       392.0
                                 Product 2       149.0       596.0       303.0
                                 Product 3       234.0       505.0       521.0
                                 Total           661.0      1253.0      1216.0
                    ZTotal                      3157.0      3169.0      3924.0
           Total                                6170.0      6096.0      7867.0
3U2anYOD6o Group A  X            Product 1       214.0       443.0       195.0
                                 Product 2       170.0       220.0       423.0
                                 Product 3       111.0       469.0       369.0
...                                                ...         ...         ...
somc22Y2Hi Group B  Z            Total           906.0      1063.0       680.0
                    ZTotal                      3070.0      3751.0      2736.0
           Total                                6435.0      7187.0      6474.0
zRZq6MSKuS Group A  X            Product 1       421.0       182.0       387.0
                                 Product 2       359.0       287.0       331.0
                                 Product 3       232.0       394.0       279.0
                                 Total          1012.0       863.0       997.0
                    Y            Product 1       245.0       366.0       111.0
                                 Product 2       377.0       148.0       239.0
                                 Product 3       372.0       219.0       310.0
                                 Total           994.0       733.0       660.0
                    Z            Product 1       280.0       363.0       354.0
                                 Product 2       384.0       604.0       178.0
                                 Product 3       219.0       462.0       366.0
                                 Total           883.0      1429.0       898.0
                    ZTotal                      2889.0      3025.0      2555.0
           Group B  X            Product 1       466.0       413.0       187.0
                                 Product 2       502.0       370.0       368.0
                                 Product 3       745.0       480.0       318.0
                                 Total          1713.0      1263.0       873.0
                    Y            Product 1       218.0       226.0       385.0
                                 Product 2       123.0       382.0       570.0
                                 Product 3       173.0       572.0       327.0
                                 Total           514.0      1180.0      1282.0
                    Z            Product 1       480.0       317.0       604.0
                                 Product 2       256.0       215.0       572.0
                                 Product 3       463.0        50.0       349.0
                                 Total          1199.0       582.0      1525.0
                    ZTotal                      3426.0      3025.0      3680.0
           Total                                6315.0      6050.0      6235.0

[675 rows x 3 columns]

【讨论】:

  • 谢谢,斯科特。这是一个很好的解决方案,我将使用它来创建小计。我希望把它变成一个类似解决方案的功能,我可以将级别和日期传递给 - 那里有什么建议吗?
猜你喜欢
  • 1970-01-01
  • 2016-06-05
  • 1970-01-01
  • 2010-10-29
  • 2013-09-02
  • 1970-01-01
  • 2015-12-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多