Pandas处理EAV格式分组数据 无需透视新增商品平均单价属性实现方法
Pandas EAV长表新增计算属性的无透视实现方案
已知条件
假设拥有如下结构的Pandas DataFrame:
import pandas as pd import numpy as np data = [[5123, '2021-01-01 00:00:00', 'cash','sales$', 105], [5123, '2021-01-01 00:00:00', 'cash','items', 20], [5123, '2021-01-01 00:00:00', 'card','sales$', 355], [5123, '2021-01-01 00:00:00', 'card','items', 50], [5123, '2021-01-02 00:00:00', 'cash','sales$', np.nan], [5123, '2021-01-02 00:00:00', 'cash','items', np.nan], [5123, '2021-01-02 00:00:00', 'card','sales$', 170], [5123, '2021-01-02 00:00:00', 'card','items', 35]] columns = ['Store', 'Date', 'Payment Method', 'Attribute', 'Value'] df = pd.DataFrame(data = data, columns = columns)
原始数据样例:
| Store | Date | Payment Method | Attribute | Value |
|---|---|---|---|---|
| 5123 | 2021-01-01 00:00:00 | cash | sales$ | 105 |
| 5123 | 2021-01-01 00:00:00 | cash | items | 20 |
| 5123 | 2021-01-01 00:00:00 | card | sales$ | 355 |
| 5123 | 2021-01-01 00:00:00 | card | items | 50 |
| 5123 | 2021-01-02 00:00:00 | cash | sales$ | NaN |
| 5123 | 2021-01-02 00:00:00 | cash | items | NaN |
| 5123 | 2021-01-02 00:00:00 | card | sales$ | 170 |
| 5123 | 2021-01-02 00:00:00 | card | items | 35 |
需求说明
- 按【Store/Date/Payment Method】三个维度分组
- 每个分组新增属性
average item price,值为该分组下sales$值除以items值,保留两位小数 - 最终输出仍为EAV格式长表,预期结果如下:
| Store | Date | Payment Method | Attribute | Value |
|---|---|---|---|---|
| 5123 | 2021-01-01 00:00:00 | cash | sales$ | 105 |
| 5123 | 2021-01-01 00:00:00 | cash | items | 20 |
| 5123 | 2021-01-01 00:00:00 | cash | average item price | 5.25 |
| 5123 | 2021-01-01 00:00:00 | card | sales$ | 355 |
| 5123 | 2021-01-01 00:00:00 | card | items | 50 |
| 5123 | 2021-01-01 00:00:00 | card | average item price | 7.10 |
| 5123 | 2021-01-02 00:00:00 | cash | sales$ | NaN |
| 5123 | 2021-01-02 00:00:00 | cash | items | NaN |
| 5123 | 2021-01-02 00:00:00 | cash | average item price | NaN |
| 5123 | 2021-01-02 00:00:00 | card | sales$ | 170 |
| 5123 | 2021-01-02 00:00:00 | card | items | 35 |
| 5123 | 2021-01-02 00:00:00 | card | average item price | 4.86 |
无需透视的实现方案
核心思路:直接按分组维度对原长表做分组计算,每组内提取对应属性值计算后,构造新行拼接回原分组即可,无需做透视/逆透视的结构转换。
实现代码
def calc_avg_price(group): # 提取当前分组的销售额、销售件数 sales = group.loc[group['Attribute'] == 'sales$', 'Value'].iloc[0] items = group.loc[group['Attribute'] == 'items', 'Value'].iloc[0] # 异常判断:空值、除数为0时返回NaN avg_price = sales / items if pd.notna(sales) and pd.notna(items) and items != 0 else np.nan # 构造新属性行 new_row = group.iloc[0].copy() new_row['Attribute'] = 'average item price' new_row['Value'] = round(avg_price, 2) # 拼接原行和新行返回 return pd.concat([group, new_row.to_frame().T], ignore_index=True) # 分组计算得到最终结果 result = df.groupby(['Store', 'Date', 'Payment Method'], group_keys=False).apply(calc_avg_price).reset_index(drop=True)
方案优势
- 避免了两次表结构转换的开销,处理大体积数据时性能优于透视方案
- 逻辑直观,直接基于原始EAV结构操作,不需要维护中间转换状态
内容的提问来源于stack exchange,提问作者edutt
相关产品推荐
相关产品推荐

