【问题标题】:Is there any nicer way to aggregate multiple columns on same grouped pandas dataframe?有没有更好的方法来聚合同一分组熊猫数据帧上的多个列?
【发布时间】:2021-05-10 05:00:13
【问题描述】:

我试图弄清楚我应该如何操作我的数据,以便我可以聚合多个列,但对于相同的分组熊猫数据。我这样做的原因是,我需要获取堆叠折线图,该折线图从同一分组数据的不同聚合中获取数据。我们怎样才能以某种紧凑的方式做到这一点?任何人都可以建议在熊猫中这样做的可能方法吗?有什么想法吗?

我目前的尝试

import pandas as pd
import matplotlib.pyplot as plt

url = "https://gist.githubusercontent.com/adamFlyn/4657714653398e9269263a7c8ad4bb8a/raw/fa6709a0c41888503509e569ace63606d2e5c2ff/mydf.csv"
df = pd.read_csv(url, parse_dates=['date'])

df_re = df[df['retail_item'].str.contains("GROUND BEEF")]
df_rei = df_re.groupby(['date', 'retail_item']).agg({'number_of_ads': 'sum'})
df_rei = df_rei.reset_index(level=[0,1])
df_rei['week'] = pd.DatetimeIndex(df_rei['date']).week
df_rei['year'] = pd.DatetimeIndex(df_rei['date']).year
df_rei['week'] = df_rei['date'].dt.strftime('%W').astype('uint8')

df_ret_df1 = df_rei.groupby(['retail_item', 'week'])['number_of_ads'].agg([max, min, 'mean']).stack().reset_index(level=[2]).rename(columns={'level_2': 'mm', 0: 'vals'}).reset_index()

同样,我也需要像这样进行数据聚合:

df_re['price_gap'] = df_re['high_price'] - df_re['low_price']
dff_rei1 = df_re.groupby(['date', 'retail_item']).agg({'price_gap': 'mean'})
dff_rei1 = dff_rei1.reset_index(level=[0,1])
dff_rei1['week'] = pd.DatetimeIndex(dff_rei1['date']).week
dff_rei1['year'] = pd.DatetimeIndex(dff_rei1['date']).year
dff_rei1['week'] = dff_rei1['date'].dt.strftime('%W').astype('uint8')

dff_ret_df2 = dff_rei1.groupby(['retail_item', 'week'])['price_gap'].agg([max, min, 'mean']).stack().reset_index(level=[2]).rename(columns={'level_2': 'mm', 0: 'vals'}).reset_index()

问题

当我进行数据聚合时,这些行是相似的:

df_rei = df_re.groupby(['date', 'retail_item']).agg({'number_of_ads': 'sum'})
df_ret_df1 = df_rei.groupby(['retail_item', 'week'])['number_of_ads'].agg([max, min, 'mean']).stack().reset_index(level=[2]).rename(columns={'level_2': 'mm', 0: 'vals'}).reset_index()

dff_rei1 = df_re.groupby(['date', 'retail_item']).agg({'price_gap': 'mean'})
 dff_ret_df2 = dff_rei1.groupby(['retail_item', 'week'])['price_gap'].agg([max, min, 'mean']).stack().reset_index(level=[2]).rename(columns={'level_2': 'mm', 0: 'vals'}).reset_index()

我认为更好的方法可能是我必须使用*arg**kwargs 制作自定义函数来进行转换以聚合列,但是我应该如何显示 y 轴显示不同数量的堆叠折线图。在pandas 中这样做可行吗?

线图

我做了如下折线图:

for g, d in df_ret_df1.groupby('retail_item'):
    fig, ax = plt.subplots(figsize=(7, 4), dpi=144)
    sns.lineplot(x='week', y='vals', hue='mm', data=d,alpha=.8)
    y1 = d[d.mm == 'max']
    y2 = d[d.mm == 'min']
    plt.fill_between(x=y1.week, y1=y1.vals, y2=y2.vals)
    
    for year in df['year'].unique():
        data = df_rei[(df_rei.date.dt.year == year) & (df_rei.retail_item == g)]
        sns.lineplot(x='week', y='price_gap', ci=None, data=data, palette=cmap,label=year,alpha=.8)

我想最小化这些,这样我就可以在不同的列上进行聚合并制作堆叠折线图,它们共享 x 轴作为周,y 轴分别显示广告数量和 price_range。我不知道有没有更好的方法来做到这一点。我这样做是因为堆积折线图(两个垂直子图),一个显示 y 轴上的广告数量,另一个显示 52 周内相同商品的价格范围。任何人都可以提出任何可能的方法吗?有什么想法吗?

【问题讨论】:

  • 这里有很多数据框。我只是想确保我没有错过任何东西:对于每个产品和周,您需要添加的最大、最小和平均数量以及“price_gap”的最大、最小和平均值,即“high_price”-“low_price “?
  • @HenryEcker 我正在查看 52 周的窗口。是的,你是对的。

标签: python pandas matplotlib


【解决方案1】:

这个答案建立在 Andreas 的答案之上,他已经回答了如何以紧凑的方式生成多列的聚合变量的主要问题。这里的目标是专门针对您的案例实施该解决方案,并举例说明如何从聚合数据中生成单个数字。以下是一些关键点:

  • 原始数据集中的日期已经是每周频率,因此df_ret_df1dff_ret_df2 不需要groupby('week'),这就是为什么它们包含相同的最小值、最大值和平均值的原因。
  • 此示例使用 pandas 和 matplotlib,因此不需要像使用 seaborn 时那样堆叠变量。
  • 聚合步骤为列生成一个 MultiIndex。您可以使用df.xs 访问每个高级变量的聚合变量(最小值、最大值、平均值)。
  • 日期设置为聚合数据框的索引,用作 x 变量。使用 DatetimeIndex 作为 x 变量可以让您更灵活地设置刻度标签的格式,并确保始终按时间顺序绘制数据。
  • 在问题中不清楚应该如何显示不同年份的数据(在单独的图中?)所以这里整个时间序列显示在一个图中。

导入数据集并按需聚合

import pandas as pd              # v 1.2.3
import matplotlib.pyplot as plt  # v 3.3.4

# Import dataset
url = 'https://gist.githubusercontent.com/adamFlyn/4657714653398e9269263a7c8ad4bb8a/\
raw/fa6709a0c41888503509e569ace63606d2e5c2ff/mydf.csv'
df = pd.read_csv(url, parse_dates=['date'])

# Create dataframe containing data for ground beef products, compute
# aggregate variables, and set the date as the index
df_gbeef = df[df['retail_item'].str.contains('GROUND BEEF')].copy()
df_gbeef['price_gap'] = df_gbeef['high_price'] - df_gbeef['low_price']
agg_dict = {'number_of_ads': [min, max, 'mean'],
            'price_gap': [min, max, 'mean']}
df_gbeef_agg = (df_gbeef.groupby(['date', 'retail_item']).agg(agg_dict)
                .reset_index('retail_item'))
df_gbeef_agg


在包含小倍数的单个图中绘制聚合变量

variables = ['number_of_ads', 'price_gap']
colors = ['tab:orange', 'tab:blue']
nrows = len(variables)
ncols = df_gbeef_agg['retail_item'].nunique()

fig, axs = plt.subplots(nrows, ncols, figsize=(10, 5), sharex=True, sharey='row')
for axs_row, var, color in zip(axs, variables, colors):
    for i, (item, df_item) in enumerate(df_gbeef_agg.groupby('retail_item')):
        ax = axs_row[i]
        
        # Select data and plot it
        data = df_item.xs(var, axis=1)
        ax.fill_between(x=data.index, y1=data['min'], y2=data['max'],
                        color=color, alpha=0.3, label='min/max')
        ax.plot(data.index, data['mean'], color=color, label='mean')
        ax.spines['bottom'].set_position('zero')
        
        # Format x-axis tick labels
        fmt = plt.matplotlib.dates.DateFormatter('%W') # is not equal to ISO week
        ax.xaxis.set_major_formatter(fmt)
        
        # Fomat subplot according to position within the figure
        if ax.is_first_row():
            ax.set_title(item, pad=10)
        if ax.is_last_row():
            ax.set_xlabel('Week number', size=12, labelpad=5)
        if ax.is_first_col():
            ax.set_ylabel(var, size=12, labelpad=10)
        if ax.is_last_col():
            ax.legend(frameon=False)

fig.suptitle('Cross-regional weekly ads and price gaps of ground beef products',
             size=14, y=1.02)
fig.subplots_adjust(hspace=0.1);

【讨论】:

  • 感谢您的提醒。我们应该如何期望输出like this?如果我们修改您当前的尝试,这可行吗?
  • 我实际上更清楚地重新发布了这个问题at this new post。谢谢
【解决方案2】:

我不确定这是否完全回答了您的问题,但根据您的标题,我想这一切都归结为:

import pandas as pd

url = "https://gist.githubusercontent.com/adamFlyn/4657714653398e9269263a7c8ad4bb8a/raw/fa6709a0c41888503509e569ace63606d2e5c2ff/mydf.csv"
df = pd.read_csv(url, parse_dates=['date'])

# define which columns to group and in which way
dct = {'low_price': [max, min],
       'high_price': min,
       'year': 'mean'}

# actually group the columns
df.groupby(['region']).agg(dct)

输出:

              low_price       high_price         year
                    max   min        min         mean
region
ALASKA            16.99  1.33       1.33  2020.792123
HAWAII            12.99  1.33       1.33  2020.738318
MIDWEST           28.73  0.99       0.99  2020.690159
NORTHEAST         19.99  1.20       1.99  2020.709916
NORTHWEST         16.99  1.33       1.33  2020.736397
SOUTH CENTRAL     28.76  1.20       1.49  2020.700980
SOUTHEAST         21.99  1.33       1.48  2020.699655
SOUTHWEST         16.99  1.29       1.29  2020.704341

【讨论】:

  • 感谢提醒,但这不是我的意思。任何可能的更新?
  • @kim 是的,可能,您可以尝试将您的问题归结为最基本的部分吗?因为我猜未来的读者可能会遇到同样的问题,但如果问题和样本太大,就不容易找到。
  • 是的,我的意思是我们可以在number_of_ads 列和price_range 列上进行聚合,其中它由dateretail_item 分组。在我的尝试中,我不得不做两次,而不是我们可以做得更好吗?
猜你喜欢
  • 1970-01-01
  • 2020-09-03
  • 2021-06-14
  • 2016-11-09
  • 1970-01-01
  • 2012-06-02
  • 2018-09-04
  • 2015-07-17
  • 2023-03-07
相关资源
最近更新 更多