news 2026/9/2 15:59:24

第七十四篇:数据转换:Pandas的groupby、聚合、合并与重塑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第七十四篇:数据转换:Pandas的groupby、聚合、合并与重塑

引言

在数据清洗与预处理的坚实基础上,我们迎来了数据分析流程中最富创造力、最具洞察力的阶段——数据转换。如果说数据清洗是“整理原料”,那么数据转换就是“精心烹饪”,它将分散、原始的数据转化为结构化、有意义的洞察。在Pandas的武器库中,groupby、聚合、合并与重塑是数据转换的“四大神器”,掌握它们意味着你能够:

  1. 从细节中发现模式:通过分组与聚合,从海量数据中提取关键统计信息

  2. 连接分散的信息孤岛:通过合并操作,整合多个数据源形成完整视图

  3. 自由变换数据视角:通过重塑操作,为不同分析需求准备合适的数据结构

想象一下这样的场景:你手中有客户的交易数据、产品信息和用户 demographics 数据,你需要回答“哪些产品类别在不同年龄段的客户中最受欢迎?”或“各地区的销售趋势如何?”。没有数据转换技能,这些问题将遥不可及;掌握本章技能,你将能轻松应对。

让我们开始探索Pandas数据转换的艺术,这些技能将直接决定你从“数据操作员”成长为“数据分析师”的关键一步。

第一章:分组艺术:深入理解groupby机制

1.1 groupby的本质:拆分-应用-组合模式

Pandas的groupby操作遵循着经典的“拆分-应用-组合”模式,理解这一模式是掌握分组操作的关键:

importpandasaspdimportnumpyasnpimportmatplotlib.pyplotasplt# 创建示例数据集:电商销售数据np.random.seed(42)n_records=1000sales_data={'订单ID':[f'ORD{10000+i}'foriinrange(n_records)],'日期':pd.date_range('2023-01-01',periods=n_records,freq='D'),'产品类别':np.random.choice(['电子产品','家居用品','服装','食品','书籍'],n_records),'子类别':np.random.choice(['手机','电脑','厨具','家具','男装','女装','零食','饮料','小说','专业书籍'],n_records),'地区':np.random.choice(['华东','华北','华南','华中','西南','西北','东北'],n_records),'销售员':np.random.choice(['张三','李四','王五','赵六','钱七'],n_records),'销售额':np.random.uniform(100,5000,n_records).round(2),'利润':np.random.uniform(10,1000,n_records).round(2),'数量':np.random.randint(1,10,n_records)}sales_df=pd.DataFrame(sales_data)sales_df['月份']=sales_df['日期'].dt.month sales_df['季度']=sales_df['日期'].dt.quarter sales_df['星期']=sales_df['日期'].dt.day_name()print("电商销售数据集(前10行):")print(sales_df.head(10))print(f"\n数据集形状:{sales_df.shape}")print(f"时间范围:{sales_df['日期'].min()}{sales_df['日期'].max()}")

图1:groupby的拆分-应用-组合模式示意图

原始DataFrame ↓ 按指定键"拆分" ↓ ┌─────────┬─────────┬─────────┐ │ 分组1 │ 分组2 │ 分组3 │ │ (键值A) │ (键值B) │ (键值C) │ └─────────┴─────────┴─────────┘ ↓ 对每个分组"应用"函数 (聚合、转换、过滤等) ↓ "组合"结果到新数据结构 ↓ 输出结果

1.2 单键与多键分组

分组操作可以从简单的单键分组到复杂的多键层级分组:

# 1. 单键分组:按产品类别分组category_group=sales_df.groupby('产品类别')print(f"按产品类别分组:共{len(category_group)}个组")print("分组键值:",list(category_group.groups.keys()))# 查看一个分组的内容print("\n'电子产品'分组的前5行:")print(category_group.get_group('电子产品').head())# 2. 多键分组:按产品和地区分组multi_group=sales_df.groupby(['产品类别','地区'])print(f"\n按产品和地区分组:共{len(multi_group)}个组")# 查看分组详情print("\n分组大小(前10个):")fori,(name,group)inenumerate(multi_group):ifi<10:print(f" 分组{name}:{len(group)}条记录")else:break# 3. 查看分组统计group_sizes=sales_df.groupby(['产品类别','地区']).size()print("\n各产品-地区组合的记录数量:")print(group_sizes.head(15))

1.3 groupby对象的内部结构与迭代

了解groupby对象的内部结构有助于更高效地使用它:

# 深入探索groupby对象print("groupby对象类型:",type(category_group))print("\ngroupby对象属性:")# 查看分组信息print("1. 分组键:",category_group.keys)print("2. 分组数:",category_group.ngroups)print("3. 分组大小:")print(category_group.size())# 迭代groupby对象print("\n迭代groupby对象(前3个组):")fori,(group_name,group_data)inenumerate(category_group):ifi<3:print(f"\n组名:{group_name}")print(f" 记录数:{len(group_data)}")print(f" 销售额均值:{group_data['销售额'].mean():.2f}")print(f" 数据形状:{group_data.shape}")# 访问特定分组print("\n访问特定分组:")electronics_group=category_group.get_group('电子产品')print(f"'电子产品'组:{len(electronics_group)}条记录")print(f"平均销售额:{electronics_group['销售额'].mean():.2f}")

1.4 分组键的多种形式

分组键不仅可以是列名,还可以是更复杂的表达式:

# 1. 列名作为分组键(最常用)by_column=sales_df.groupby('产品类别')# 2. Series作为分组键by_series=sales_df.groupby(sales_df['产品类别'])# 3. 函数作为分组键# 按销售额是否超过平均值分组defhigh_sales_flag(row):return'高销售额'ifrow['销售额']>sales_df['销售额'].mean()else'低销售额'# 注意:使用apply或transform时,函数应用到每行sales_df['销售额等级']=sales_df.apply(high_sales_flag,axis=1)by_function=sales_df.groupby('销售额等级')print("\n按销售额等级分组统计:")print(by_function['销售额'].describe())# 4. 字典或Series映射# 创建产品类别到部门的映射category_to_dept={'电子产品':'数码部','家居用品':'家居部','服装':'服装部','食品':'食品部','书籍':'图书部'}sales_df['部门']=sales_df['产品类别'].map(category_to_dept)by_mapping=sales_df.groupby('部门')print("\n按部门分组统计:")print(by_mapping.agg({'销售额':'sum','利润':'sum','数量':'sum'}))# 5. 多个不同形式的键组合complex_group=sales_df.groupby(['部门',sales_df['日期'].dt.month,'销售额等级'])print(f"\n复杂分组键的分组数:{complex_group.ngroups}")

第二章:聚合操作:从分组到洞察

2.1 基本聚合方法

聚合是groupby最常用的操作,Pandas提供了多种聚合方式:

# 1. 单聚合函数print("按产品类别的销售额统计:")category_stats=sales_df.groupby('产品类别')['销售额'].agg('sum')print(category_stats)# 2. 多聚合函数print("\n按产品类别的多指标统计:")multi_agg=sales_df.groupby('产品类别').agg({'销售额':['sum','mean','std','count'],'利润':['sum','mean'],'数量':'sum'})print(multi_agg)# 3. 命名聚合结果(Pandas 0.25+)named_agg=sales_df.groupby('产品类别').agg(总销售额=('销售额','sum'),平均销售额=('销售额','mean'),销售次数=('销售额','count'),总利润=('利润','sum'),利润率=('利润',lambdax:x.sum()/sales_df.loc[x.index,'销售额'].sum()))print("\n命名聚合结果:")print(named_agg)

2.2 自定义聚合函数

当内置聚合函数不满足需求时,可以定义自己的聚合函数:

# 1. 简单的自定义聚合函数defsales_range(series):"""计算销售范围(最大值-最小值)"""returnseries.max()-series.min()defprofit_margin(group):"""计算利润率:总利润/总销售额"""total_profit=group['利润'].sum()total_sales=group['销售额'].sum()returntotal_profit/total_salesiftotal_sales>0else0# 应用自定义聚合函数custom_agg=sales_df.groupby('产品类别').agg({'销售额':['sum','mean',sales_range],'利润':['sum',profit_margin]})print("自定义聚合函数结果:")print(custom_agg)# 2. 返回多个值的聚合函数defdescribe_custom(series):"""类似describe但返回自定义统计量"""returnpd.Series({'总和':series.sum(),'均值':series.mean(),'中位数':series.median(),'标准差':series.std(),'变异系数':series.std()/series.mean()ifseries.mean()>0else0,'最小值':series.min(),'最大值':series.max(),'范围':series.max()-series.min()})# 应用返回多个值的聚合函数detailed_stats=sales_df.groupby('产品类别')['销售额'].apply(describe_custom)print("\n详细统计信息:")print(detailed_stats)

2.3 条件聚合与分组筛选

有时我们需要基于条件进行聚合,或在聚合前进行筛选:

# 1. 分组后条件聚合# 计算每个产品类别中高销售额(>2000)的比例defhigh_sales_proportion(group):high_sales=group[group['销售额']>2000]returnlen(high_sales)/len(group)iflen(group)>0else0conditional_agg=sales_df.groupby('产品类别').apply(lambdag:pd.Series({'总记录数':len(g),'高销售额记录数':len(g[g['销售额']>2000]),'高销售额比例':high_sales_proportion(g),'高销售额总额':g[g['销售额']>2000]['销售额'].sum()}))print("条件聚合结果:")print(conditional_agg)# 2. 分组前筛选# 只考虑销售额大于500的记录filtered_agg=sales_df[sales_df['销售额']>500].groupby('产品类别').agg({'销售额':['sum','mean','count']})print("\n筛选后的聚合结果:")print(filtered_agg)# 3. 分组后筛选(使用filter方法)# 筛选出记录数大于100的分组large_groups=sales_df.groupby('产品类别').filter(lambdax:len(x)>100)print(f"\n筛选后数据形状:{large_groups.shape}")print("剩余分组:",large_groups['产品类别'].unique())

图2:聚合操作流程图

原始分组 ↓ 选择聚合方式 ├── 内置聚合函数 (sum, mean, count...) ├── 自定义聚合函数 └── 多函数聚合 ↓ 应用聚合操作 ↓ 输出聚合结果 ├── Series (单列聚合) ├── DataFrame (多列聚合) └── 多级索引DataFrame (多键分组)

2.4 时间序列分组聚合

对于时间序列数据,时间维度分组尤为重要:

# 时间序列分组聚合示例print("时间序列分组聚合:")# 1. 按月份聚合monthly_sales=sales_df.groupby(sales_df['日期'].dt.to_period('M')).agg({'销售额':'sum','利润':'sum','数量':'sum','订单ID':'count'}).rename(columns={'订单ID':'订单数'})print("月度销售统计:")print(monthly_sales)# 2. 按季度和产品类别聚合quarterly_by_category=sales_df.groupby([sales_df['日期'].dt.to_period('Q'),'产品类别']).agg({'销售额':'sum'}).unstack()print("\n分季度、产品类别的销售额:")print(quarterly_by_category)# 3. 按周几聚合weekday_sales=sales_df.groupby('星期').agg({'销售额':['sum','mean','count']}).reindex(['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'])print("\n星期销售统计:")print(weekday_sales)# 4. 滚动时间窗口聚合# 创建时间索引sales_time=sales_df.set_index('日期').sort_index()# 计算7天滚动平均销售额rolling_7d=sales_time['销售额'].rolling('7D').mean()print("\n滚动窗口聚合示例:")print("原始数据形状:",sales_time.shape)print("7天滚动平均形状:",rolling_7d.shape)print("\n滚动平均(前10天):")print(rolling_7d.head(10))

第三章:数据合并:连接多源信息

3.1 合并操作基础:merge与join

Pandas提供了多种合并数据的方法,最常用的是merge()和join():

# 创建多个相关数据集# 1. 订单详情表orders=sales_df[['订单ID','日期','产品类别','销售额','利润','数量']].copy()# 2. 产品信息表products=pd.DataFrame({'产品类别':['电子产品','家居用品','服装','食品','书籍'],'部门':['数码部','家居部','服装部','食品部','图书部'],'负责人':['张三','李四','王五','赵六','钱七'],'毛利率目标':[0.25,0.30,0.40,0.20,0.35]# 目标毛利率})# 3. 地区信息表regions=pd.DataFrame({'地区':['华东','华北','华南','华中','西南','西北','东北'],'区域经理':['经理A','经理B','经理C','经理D','经理E','经理F','经理G'],'地区级别':['一线','一线','一线','二线','二线','三线','二线']})# 4. 销售员信息表salespersons=pd.DataFrame({'销售员':['张三','李四','王五','赵六','钱七'],'入职日期':['2020-01-15','2019-03-20','2021-06-10','2018-11-05','2022-02-28'],'级别':['高级','中级','初级','高级','初级'],'所属地区':['华东','华北','华南','华中','西南']})# 转换日期格式salespersons['入职日期']=pd.to_datetime(salespersons['入职日期'])print("各数据表信息:")print(f"订单表:{orders.shape}")print(f"产品表:{products.shape}")print(f"地区表:{regions.shape}")print(f"销售员表:{salespersons.shape}")

3.2 合并类型详解

SQL风格的合并操作在Pandas中通过how参数实现:

# 1. 内连接(inner join) - 默认inner_merged=pd.merge(orders,products,on='产品类别',how='inner')print("内连接结果形状:",inner_merged.shape)print("内连接示例(前5行):")print(inner_merged.head())# 2. 左连接(left join)left_merged=pd.merge(orders,products,on='产品类别',how='left')print(f"\n左连接结果形状:{left_merged.shape}")print("左连接保留所有订单记录:",left_merged.shape[0]==orders.shape[0])# 3. 右连接(right join)right_merged=pd.merge(orders,products,on='产品类别',how='right')print(f"\n右连接结果形状:{right_merged.shape}")print("右连接保留所有产品记录:",right_merged.shape[0]>=products.shape[0])# 4. 外连接(outer join)outer_merged=pd.merge(orders,products,on='产品类别',how='outer')print(f"\n外连接结果形状:{outer_merged.shape}")print("外连接保留所有记录:",outer_merged.shape[0]>=max(orders.shape[0],products.shape[0]))# 5. 多键合并# 先给orders添加一个虚拟的地区列用于演示orders_sample=orders.head(100).copy()orders_sample['地区']=np.random.choice(regions['地区'],100)multi_key_merged=pd.merge(orders_sample,salespersons,left_on=['产品类别','地区'],right_on=['所属地区','销售员'],how='inner')print(f"\n多键合并结果形状:{multi_key_merged.shape}")

图3:合并类型可视化示意图

内连接(inner) 左连接(left) 右连接(right) 外连接(outer) A B A B A B A B ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ │1│ │1│ │1│←→│1│ │1│←→│1│ │1│←→│1│ │2│←→│2│ │2│ │2│ │2│←→│2│ │2│←→│2│ │3│ │4│ │3│ └─┘ └─┘ │3│ │3│ │3│ └─┘ └─┘ └─┘ │4│ └─┘ │4│ 交集部分←→ A全保留←→ B全保留←→ 全部保留←→

3.3 高级合并技巧

# 1. 处理重复列名(suffixes参数)# 创建有重叠列名的DataFramesales_summary=orders.groupby('产品类别').agg({'销售额':'sum','利润':'sum'}).reset_index()profit_summary=orders.groupby('产品类别').agg({'利润':'mean',# 平均利润'数量':'sum'}).reset_index()# 合并时自动添加后缀merged_with_suffix=pd.merge(sales_summary,profit_summary,on='产品类别',suffixes=('_总计','_平均'))print("添加后缀的合并结果:")print(merged_with_suffix)# 2. 根据索引合并# 设置索引后合并products_indexed=products.set_index('产品类别')orders_with_index=orders.set_index('产品类别')merged_by_index=pd.merge(orders_with_index,products_indexed,left_index=True,right_index=True,how='left')print(f"\n按索引合并结果形状:{merged_by_index.shape}")# 3. 使用join方法(基于索引的合并)joined=orders_with_index.join(products_indexed,how='left')print(f"\n使用join方法结果形状:{joined.shape}")# 4. 合并多个DataFramefromfunctoolsimportreducedataframes=[orders,products,regions]# 注意:这里需要实际可合并的列,仅为示例print("\n合并多个DataFrame示例:")# 实际应用中需要确保有共同的键

3.4 性能优化与大型数据集合并

# 大型数据集合并优化技巧importtime# 创建大型数据集np.random.seed(42)n_large=100000large_orders=pd.DataFrame({'订单ID':range(n_large),'产品ID':np.random.randint(1,1000,n_large),'销售额':np.random.uniform(10,1000,n_large)})large_products=pd.DataFrame({'产品ID':range(1,1001),'产品名称':[f'产品_{i}'foriinrange(1,1001)],'类别':np.random.choice(['A','B','C','D'],1000)})print("大型数据集信息:")print(f"订单表:{large_orders.shape}")print(f"产品表:{large_products.shape}")# 1. 基准合并start=time.time()basic_merge=pd.merge(large_orders,large_products,on='产品ID',how='left')basic_time=time.time()-startprint(f"\n基准合并时间:{basic_time:.4f}秒")# 2. 优化1:设置适当的数据类型large_orders_opt=large_orders.copy()large_products_opt=large_products.copy()# 将ID列转换为更小的数据类型large_orders_opt['产品ID']=large_orders_opt['产品ID'].astype('int32')large_products_opt['产品ID']=large_products_opt['产品ID'].astype('int32')start=time.time()opt1_merge=pd.merge(large_orders_opt,large_products_opt,on='产品ID',how='left')opt1_time=time.time()-startprint(f"优化1(数据类型)合并时间:{opt1_time:.4f}秒,提升:{basic_time/opt1_time:.1f}倍")# 3. 优化2:先筛选再合并# 如果只需要某些类别的产品start=time.time()filtered_products=large_products[large_products['类别'].isin(['A','B'])]opt2_merge=pd.merge(large_orders,filtered_products,on='产品ID',how='inner')opt2_time=time.time()-startprint(f"优化2(先筛选)合并时间:{opt2_time:.4f}秒,结果形状:{opt2_merge.shape}")

第四章:数据重塑:变换数据视角

4.1 宽表与长表转换:melt与pivot

数据重塑的核心是改变数据的形状而不改变其内容:

# 创建宽格式数据wide_data=sales_df.pivot_table(index='日期',columns='产品类别',values='销售额',aggfunc='sum').fillna(0)print("宽格式数据(日期×产品类别):")print(wide_data.head())print(f"形状:{wide_data.shape}")# 1. melt:宽表转长表long_data=wide_data.reset_index().melt(id_vars='日期',value_vars=['电子产品','家居用品','服装','食品','书籍'],var_name='产品类别',value_name='销售额')print("\n宽表转长表结果:")print(long_data.head())print(f"形状:{long_data.shape}")# 2. pivot:长表转宽表# 注意:pivot不能处理重复值,需要先聚合pivot_data=sales_df.pivot_table(index='日期',columns='产品类别',values=['销售额','利润'],# 多值列aggfunc='sum').fillna(0)print("\n长表转宽表结果(多值列):")print(pivot_data.head())print(f"形状:{pivot_data.shape}")print("列结构:",pivot_data.columns.tolist()[:10])

4.2 多层索引操作:stack与unstack

多层索引是处理高维数据的强大工具:

# 创建多层索引DataFramemulti_index_df=sales_df.groupby(['日期','产品类别','地区']).agg({'销售额':'sum','利润':'sum'}).unstack(['产品类别','地区'])print("多层索引DataFrame:")print(multi_index_df.head())print(f"形状:{multi_index_df.shape}")print("列索引级别:",multi_index_df.columns.nlevels)print("行索引级别:",multi_index_df.index.nlevels)# 1. stack:将列索引的层级旋转为行索引stacked=multi_index_df.stack(['产品类别','地区'])print("\nstack操作后:")print(stacked.head())print(f"形状:{stacked.shape}")# 2. unstack:将行索引的层级旋转为列索引unstacked=stacked.unstack(['产品类别','地区'])print("\nunstack操作后(恢复原状):")print(unstacked.head())print(f"形状:{unstacked.shape}")# 3. 选择性stack/unstackpartial_unstack=stacked.unstack('产品类别')print("\n只unstack产品类别:")print(partial_unstack.head())print(f"形状:{partial_unstack.shape}")

4.3 数据透视表:pivot_table高级功能

pivot_table是比pivot更强大的工具,支持数据聚合:

# 高级pivot_table应用print("高级数据透视表示例:")# 1. 多级行列、多值透视advanced_pivot=sales_df.pivot_table(index=['季度','地区'],columns=['产品类别','销售额等级'],values=['销售额','利润'],aggfunc={'销售额':['sum','mean','count'],'利润':'sum'},fill_value=0,margins=True,# 添加总计margins_name='总计')print("多级透视表结构:")print(f"行索引:{advanced_pivot.index.names}")print(f"列索引:{advanced_pivot.columns.names}")print(f"形状:{advanced_pivot.shape}")# 2. 使用不同的聚合函数custom_pivot=sales_df.pivot_table(index='产品类别',columns='销售额等级',values=['销售额','利润'],aggfunc={'销售额':['sum','mean',lambdax:x.std()/x.mean()],# 变异系数'利润':['sum','mean','count']},fill_value=0)print("\n自定义聚合函数透视表:")print(custom_pivot)# 3. 处理缺失值pivot_with_na=sales_df.pivot_table(index='地区',columns='产品类别',values='销售额',aggfunc='mean',fill_value=None,# 保留NaNdropna=False# 不删除全为NaN的列)print("\n保留NaN的透视表:")print(pivot_with_na)

图4:数据重塑操作转换示意图

原始长格式数据 日期 类别 销售额 2023-01-01 A 100 2023-01-01 B 200 2023-01-02 A 150 pivot/unstack ↓ 宽格式数据 日期 A B 2023-01-01 100 200 2023-01-02 150 NaN melt/stack ↓ 长格式数据 日期 类别 销售额 2023-01-01 A 100 2023-01-01 B 200 2023-01-02 A 150 2023-01-02 B NaN

4.4 交叉表:crosstab快速频率分析

crosstab是专门用于计算频率交叉表的工具:

# crosstab应用print("交叉表示例:")# 1. 基本交叉表basic_crosstab=pd.crosstab(index=sales_df['产品类别'],columns=sales_df['地区'],margins=True,# 添加总计margins_name='合计')print("产品类别×地区交叉表:")print(basic_crosstab)# 2. 多级交叉表multi_crosstab=pd.crosstab(index=[sales_df['产品类别'],sales_df['销售额等级']],columns=sales_df['地区'],margins=True)print("\n多级交叉表:")print(multi_crosstab.head(10))# 3. 带值的交叉表(类似透视表)crosstab_with_values=pd.crosstab(index=sales_df['产品类别'],columns=sales_df['地区'],values=sales_df['销售额'],aggfunc='mean',normalize=False# 不标准化).round(2)print("\n带平均销售额的交叉表:")print(crosstab_with_values)# 4. 标准化交叉表normalized_crosstab=pd.crosstab(index=sales_df['产品类别'],columns=sales_df['地区'],normalize='index'# 按行标准化).round(3)print("\n按行标准化的交叉表:")print(normalized_crosstab)

第五章:综合实战:电商数据分析全流程

5.1 业务场景与数据准备

让我们通过一个完整的电商数据分析案例,综合应用本章所学技能:

# 综合实战:电商数据分析print("="*60)print("综合实战:电商数据分析全流程")print("="*60)# 准备完整数据集# 1. 销售事实表fact_sales=sales_df.copy()# 2. 产品维度表dim_products=pd.DataFrame({'产品类别':['电子产品','家居用品','服装','食品','书籍'],'部门':['数码部','家居部','服装部','食品部','图书部'],'负责人':['张三','李四','王五','赵六','钱七'],'采购成本率':[0.60,0.50,0.40,0.70,0.45],# 成本占售价比例'库存周转目标':[4.0,3.0,6.0,12.0,2.0]# 目标年周转次数})# 3. 时间维度表dates=pd.DataFrame({'日期':pd.date_range('2023-01-01','2023-12-31'),'年份':lambdadf:df['日期'].dt.year,'季度':lambdadf:df['日期'].dt.quarter,'月份':lambdadf:df['日期'].dt.month,'星期':lambdadf:df['日期'].dt.day_name(),'是否周末':lambdadf:df['日期'].dt.dayofweek>=5,'是否节假日':lambdadf:df['日期'].isin(['2023-01-01','2023-01-22','2023-01-23','2023-01-24',# 元旦、春节'2023-04-05','2023-05-01','2023-06-22','2023-09-29',# 清明、劳动、端午、中秋'2023-10-01','2023-10-02','2023-10-03'# 国庆])})# 4. 地区维度表dim_regions=pd.DataFrame({'地区':['华东','华北','华南','华中','西南','西北','东北'],'区域经理':['经理A','经理B','经理C','经理D','经理E','经理F','经理G'],'地区级别':['一线','一线','一线','二线','二线','三线','二线'],'GDP等级':['高','高','高','中','中','低','中']})print("数据表准备完成:")print(f"销售事实表:{fact_sales.shape}")print(f"产品维度表:{dim_products.shape}")print(f"时间维度表:{dates.shape}")print(f"地区维度表:{dim_regions.shape}")

5.2 数据整合与多维分析

# 数据整合:创建星型模型# 1. 连接事实表与维度表enriched_sales=pd.merge(fact_sales,dim_products,on='产品类别',how='left')enriched_sales=pd.merge(enriched_sales,dim_regions,on='地区',how='left')# 添加时间维度属性enriched_sales=enriched_sales.merge(dates,left_on='日期',right_on='日期',how='left')print("整合后的数据:")print(f"形状:{enriched_sales.shape}")print("列:",enriched_sales.columns.tolist())# 2. 计算衍生指标enriched_sales['毛利率']=enriched_sales['利润']/enriched_sales['销售额']enriched_sales['客单价']=enriched_sales['销售额']/enriched_sales['数量']enriched_sales['是否达标']=enriched_sales['毛利率']>enriched_sales['采购成本率']print("\n添加衍生指标后的数据(前5行):")print(enriched_sales[['订单ID','产品类别','销售额','利润','毛利率','客单价','是否达标']].head())

5.3 多维度业务分析

# 业务分析1:各产品类别的销售表现print("\n1. 各产品类别销售表现分析")category_performance=enriched_sales.groupby(['部门','产品类别']).agg({'销售额':['sum','mean','count'],'利润':['sum','mean'],'毛利率':'mean','客单价':'mean'}).round(2)category_performance.columns=['_'.join(col).strip()forcolincategory_performance.columns.values]category_performance=category_performance.rename(columns={'销售额_sum':'总销售额','销售额_mean':'平均订单额','销售额_count':'订单数','利润_sum':'总利润','利润_mean':'平均利润','毛利率_mean':'平均毛利率','客单价_mean':'平均客单价'})print("产品类别表现:")print(category_performance)# 业务分析2:时间趋势分析print("\n2. 销售时间趋势分析")# 月度趋势monthly_trend=enriched_sales.groupby(['年份','月份']).agg({'销售额':'sum','利润':'sum','订单ID':'count'}).rename(columns={'订单ID':'订单数'})print("月度销售趋势:")print(monthly_trend)# 周内模式weekday_pattern=enriched_sales.groupby('星期').agg({'销售额':['sum','mean'],'订单ID':'count'}).rename(columns={'订单ID':'订单数'})print("\n星期销售模式:")print(weekday_pattern)# 业务分析3:地区分析print("\n3. 地区表现分析")region_performance=enriched_sales.groupby(['地区级别','地区']).agg({'销售额':'sum','利润':'sum','毛利率':'mean','订单ID':'count'}).round(2)region_performance=region_performance.sort_values(['地区级别','销售额'],ascending=[True,False])print("地区表现排名:")print(region_performance)

5.4 高级分析:透视与钻取

# 创建高级数据立方体print("\n4. 高级数据立方体分析")# 创建数据透视立方体sales_cube=pd.pivot_table(enriched_sales,index=['部门','产品类别'],columns=['季度','地区级别'],values=['销售额','利润'],aggfunc={'销售额':'sum','利润':'sum'},fill_value=0,margins=True,margins_name='总计')print("销售数据立方体(部分):")# 只显示部分数据以避免输出过长print(sales_cube.iloc[:5,:10])# 向下钻取:查看特定维度的详细数据print("\n5. 向下钻取分析:电子产品部门详情")electronics_detail=enriched_sales[enriched_sales['部门']=='数码部'].groupby(['产品类别','地区']).agg({'销售额':'sum','利润':'sum','毛利率':'mean'}).round(2)print("电子产品部门详情:")print(electronics_detail)# 向上卷起:汇总到更高层级print("\n6. 向上卷起:部门级别汇总")department_summary=enriched_sales.groupby('部门').agg({'销售额':'sum','利润':'sum','毛利率':lambdax:x.mean(),'订单ID':'count'}).round(2)department_summary=department_summary.rename(columns={'订单ID':'订单数'})department_summary['销售额占比']=(department_summary['销售额']/department_summary['销售额'].sum()*100).round(1)department_summary=department_summary.sort_values('销售额',ascending=False)print("部门级别汇总:")print(department_summary)

5.5 可视化呈现分析结果

# 可视化分析结果importmatplotlib.pyplotasplt fig,axes=plt.subplots(2,2,figsize=(15,12))# 1. 产品类别销售额占比(饼图)sales_by_category=enriched_sales.groupby('产品类别')['销售额'].sum()axes[0,0].pie(sales_by_category.values,labels=sales_by_category.index,autopct='%1.1f%%')axes[0,0].set_title('各产品类别销售额占比')# 2. 月度销售趋势(折线图)monthly_sales_trend=enriched_sales.groupby('月份')['销售额'].sum()axes[0,1].plot(monthly_sales_trend.index,monthly_sales_trend.values,marker='o',linewidth=2)axes[0,1].set_title('月度销售趋势')axes[0,1].set_xlabel('月份')axes[0,1].set_ylabel('销售额')axes[0,1].grid(True,alpha=0.3)# 3. 地区销售额(柱状图)sales_by_region=enriched_sales.groupby('地区')['销售额'].sum().sort_values(ascending=False)axes[1,0].bar(range(len(sales_by_region)),sales_by_region.values)axes[1,0].set_title('各地区销售额')axes[1,0].set_xlabel('地区')axes[1,0].set_ylabel('销售额')axes[1,0].set_xticks(range(len(sales_by_region)))axes[1,0].set_xticklabels(sales_by_region.index,rotation=45)# 4. 产品类别利润率(水平柱状图)margin_by_category=enriched_sales.groupby('产品类别')['毛利率'].mean().sort_values()axes[1,1].barh(range(len(margin_by_category)),margin_by_category.values)axes[1,1].set_title('各产品类别平均毛利率')axes[1,1].set_xlabel('毛利率')axes[1,1].set_yticks(range(len(margin_by_category)))axes[1,1].set_yticklabels(margin_by_category.index)plt.tight_layout()plt.show()# 保存分析结果analysis_results={'category_performance':category_performance,'monthly_trend':monthly_trend,'region_performance':region_performance,'department_summary':department_summary}# 保存到Excel的不同sheetwithpd.ExcelWriter('sales_analysis_report.xlsx')aswriter:forsheet_name,dfinanalysis_results.items():df.to_excel(writer,sheet_name=sheet_name[:31])# Excel sheet名最多31字符print("\n分析报告已保存到 'sales_analysis_report.xlsx'")

图5:数据转换在分析工作流中的位置

第六章:性能优化与最佳实践

6.1 groupby性能优化

# groupby性能优化技巧print("groupby性能优化技巧:")# 1. 使用合适的分组键顺序# 当分组键的基数(唯一值数量)差异大时,将基数小的键放在前面print("1. 分组键顺序优化:")key_cardinality={'产品类别':enriched_sales['产品类别'].nunique(),'地区':enriched_sales['地区'].nunique(),'季度':enriched_sales['季度'].nunique()}print(" 各键基数:",key_cardinality)print(" 建议顺序:",sorted(key_cardinality,key=key_cardinality.get))# 2. 仅选择需要的列print("\n2. 列选择优化:")# 不好的做法:选择所有列start=time.time()slow_groupby=enriched_sales.groupby('产品类别').mean()slow_time=time.time()-start# 好的做法:只选择需要的列start=time.time()fast_groupby=enriched_sales[['产品类别','销售额','利润']].groupby('产品类别').mean()fast_time=time.time()-startprint(f" 全列分组时间:{slow_time:.4f}秒")print(f" 选择列分组时间:{fast_time:.4f}秒")print(f" 性能提升:{slow_time/fast_time:.1f}倍")# 3. 使用as_index参数print("\n3. as_index参数优化:")# 当需要将分组键作为列而不是索引时grouped_as_column=enriched_sales.groupby('产品类别',as_index=False).agg({'销售额':'sum'})print(" 分组键作为列:",'产品类别'ingrouped_as_column.columns)

6.2 合并操作优化

# 合并操作优化print("\n合并操作优化:")# 创建测试数据df1=pd.DataFrame({'key':range(10000),'value1':np.random.randn(10000)})df2=pd.DataFrame({'key':range(5000,15000),# 部分重叠'value2':np.random.randn(10000)})# 1. 使用合适的合并类型print("1. 合并类型选择:")# 内连接通常最快inner_time=time.time()result_inner=pd.merge(df1,df2,on='key',how='inner')inner_time=time.time()-inner_time# 外连接较慢outer_time=time.time()result_outer=pd.merge(df1,df2,on='key',how='outer')outer_time=time.time()-outer_timeprint(f" 内连接时间:{inner_time:.4f}秒,结果形状:{result_inner.shape}")print(f" 外连接时间:{outer_time:.4f}秒,结果形状:{result_outer.shape}")# 2. 设置索引加速合并print("\n2. 索引优化:")# 无索引合并df1_no_index=df1.copy()df2_no_index=df2.copy()start=time.time()merge_no_index=pd.merge(df1_no_index,df2_no_index,on='key')time_no_index=time.time()-start# 设置索引后合并df1_indexed=df1.set_index('key')df2_indexed=df2.set_index('key')start=time.time()merge_indexed=pd.merge(df1_indexed,df2_indexed,left_index=True,right_index=True)time_indexed=time.time()-startprint(f" 无索引合并时间:{time_no_index:.4f}秒")print(f" 有索引合并时间:{time_indexed:.4f}秒")

6.3 内存使用优化

# 内存使用优化print("\n内存使用优化:")# 1. 使用category类型减少内存print("1. 分类数据类型优化:")# 转换前的内存使用original_memory=enriched_sales.memory_usage(deep=True).sum()/1024**2# 转换分类列categorical_cols=['产品类别','部门','地区','地区级别','GDP等级','星期']enriched_sales_opt=enriched_sales.copy()forcolincategorical_cols:ifcolinenriched_sales_opt.columns:enriched_sales_opt[col]=enriched_sales_opt[col].astype('category')optimized_memory=enriched_sales_opt.memory_usage(deep=True).sum()/1024**2print(f" 原始内存使用:{original_memory:.2f}MB")print(f" 优化后内存使用:{optimized_memory:.2f}MB")print(f" 内存减少:{(1-optimized_memory/original_memory)*100:.1f}%")# 2. 使用合适的数据类型print("\n2. 数值数据类型优化:")# 检查数值列的数据类型numeric_cols=enriched_sales.select_dtypes(include=[np.number]).columnsprint(" 数值列数据类型:")forcolinnumeric_cols[:5]:# 只显示前5个dtype=enriched_sales[col].dtypeprint(f"{col}:{dtype}")

结语

核心能力总结

通过本章的学习,你已经掌握了Pandas数据转换的四大核心能力:

  1. 分组艺术(groupby):深刻理解了"拆分-应用-组合"模式,能够灵活运用单键、多键及复杂条件分组,将海量数据按需分割为有意义的子集。

  2. 聚合智慧:掌握了从简单统计到复杂自定义聚合的全套方法,能够将分组数据转化为具有业务洞察力的汇总信息,发现数据中的模式和趋势。

  3. 合并技巧:熟练运用多种合并策略连接分散的数据源,理解不同连接类型(内连、左连、右连、外连)的适用场景,能够构建完整的数据视图。

  4. 重塑能力:掌握了数据结构的自由变换技巧,能够在宽表与长表之间灵活转换,利用多层索引处理高维数据,满足不同分析需求的数据格式要求。

最佳实践建议

  1. 理解业务需求再选择技术:数据转换不是目的,而是手段。始终从业务问题出发,选择最适合的转换方法。

  2. 分层构建复杂转换:对于复杂的转换需求,分步骤、分层级地构建,每步都验证结果,确保转换的正确性。

  3. 性能与可读性平衡:在追求性能优化的同时,保持代码的可读性和可维护性,复杂操作添加必要注释。

  4. 充分利用向量化操作:避免在groupby等操作中使用Python循环,充分利用Pandas的向量化能力提升性能。

  5. 内存敏感处理大数据:处理大型数据集时,注意数据类型优化和内存使用,适时使用分块处理或抽样分析。

  6. 保存中间结果:对于耗时的复杂转换,保存关键中间结果,便于调试和复现分析过程。

持续学习路径

  1. 深入高级分组技术:学习使用transform和apply进行更复杂的分组操作,掌握分组后的数据标准化、排名等高级技巧。

  2. 掌握时间序列分组:深入学习基于时间窗口的分组操作,如滚动窗口、扩展窗口和指数加权移动平均等时间序列特定技术。

  3. 学习Dask等分布式计算:当数据量超出单机内存时,学习使用Dask等分布式计算框架处理Pandas-like操作。

  4. 数据库集成技能:掌握将Pandas操作转换为SQL语句的能力,学习直接与数据库交互进行大数据处理。

  5. 可视化集成:学习将数据转换结果无缝集成到可视化流程中,掌握使用Plotly、Seaborn等库进行高级可视化。

  6. 实战项目积累:在Kaggle、天池等平台参与真实项目,将数据转换技能应用于更复杂、更真实的业务场景中。

数据转换是数据分析师从"数据工人"成长为"数据艺术家"的关键一步。它不仅仅是技术的堆砌,更是对业务理解、逻辑思维和创造力的综合考验。每一行转换代码背后,都是对业务问题的深刻思考;每一个转换结果,都可能是新洞察的起点。继续精进这些技能,你将在数据世界中游刃有余,从数据中发现的不仅是信息,更是价值。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 14:15:04

9 个 MBA 课堂汇报工具推荐,AI 降重免费网站合集

9 个 MBA 课堂汇报工具推荐&#xff0c;AI 降重免费网站合集 论文如山压顶&#xff0c;降重成了最头疼的“隐形任务” MBA课堂汇报和论文写作&#xff0c;是每一位MBA学生在求学过程中必须面对的重要挑战。无论是案例分析、商业计划书&#xff0c;还是文献综述和实证研究&#…

作者头像 李华
网站建设 2026/9/1 20:59:05

poi-tl-ext实战指南:Java文档自动化的完整解决方案

poi-tl-ext实战指南&#xff1a;Java文档自动化的完整解决方案 【免费下载链接】poi-tl-ext Extensions for poi-tl 项目地址: https://gitcode.com/gh_mirrors/po/poi-tl-ext 在现代企业应用中&#xff0c;Word文档自动化处理已成为提升工作效率的关键技术。poi-tl-ext…

作者头像 李华
网站建设 2026/9/2 22:22:27

Wan2.2-T2V-A14B如何控制镜头晃动感?手持摄影风格模拟

Wan2.2-T2V-A14B 如何实现电影级手持摄影风格&#xff1f; 在影视制作中&#xff0c;一个简单的镜头晃动往往能瞬间拉近观众与画面的距离——那种轻微的、不规则的抖动感&#xff0c;像是有人真的举着摄像机冲进了现场。它不属于完美构图&#xff0c;却承载着真实的情绪张力。而…

作者头像 李华
网站建设 2026/9/3 0:41:48

ModernWMS开源仓库管理系统:中小企业智能仓储解决方案

ModernWMS开源仓库管理系统&#xff1a;中小企业智能仓储解决方案 【免费下载链接】ModernWMS The open source simple and complete warehouse management system is derived from our many years of experience in implementing erp projects. We stripped the original comm…

作者头像 李华
网站建设 2026/9/3 0:43:41

waifu2x-caffe终极指南:AI图像放大与智能降噪技术深度解析

waifu2x-caffe终极指南&#xff1a;AI图像放大与智能降噪技术深度解析 【免费下载链接】waifu2x-caffe lltcggie/waifu2x-caffe: Waifu2x-Caffe 是一个用于图像放大和降噪的 Python 库&#xff0c;使用了 Caffe 深度学习框架&#xff0c;可以用于图像处理和计算机视觉任务&…

作者头像 李华
网站建设 2026/9/1 14:42:14

LLM工具调用终极指南:SGLang结构化生成技术的深度实践

LLM工具调用终极指南&#xff1a;SGLang结构化生成技术的深度实践 【免费下载链接】sglang SGLang is a structured generation language designed for large language models (LLMs). It makes your interaction with models faster and more controllable. 项目地址: https…

作者头像 李华