如何用Pandas的groupby/crosstab/pivot_table实现指定统计表格?
使用Pandas实现指定统计汇总表
需求说明
给定如下输入数据:
| Gender | SQ2_1 | SQ2_2 | SQ2_3 |
|---|---|---|---|
| M | 1 | 2 | 3 |
| F | 1 | NaN | 3 |
| F | 1 | 2 | 3 |
| F | NaN | 2 | 3 |
| M | 1 | 2 | NaN |
| M | NaN | 2 | 3 |
需要生成包含两类统计的汇总表:
All行:统计总样本数(Base)、男性(M)和女性(F)的样本数SQ2_*行:对应列非空值占对应分组总数的百分比(保留1位小数)
最终输出:
| Base | M | F | |
|---|---|---|---|
| All | 6 | 3 | 3 |
| SQ2_1 | 66.0 | 66.0 | 66.0 |
| SQ2_2 | 83.0 | 100.0 | 66.0 |
| SQ2_3 | 83.0 | 66.0 | 100.0 |
实现方法(基于Pandas)
步骤1:构造输入DataFrame
先把原始数据转为Pandas的DataFrame:
import pandas as pd import numpy as np df = pd.DataFrame({ 'Gender': ['M', 'F', 'F', 'F', 'M', 'M'], 'SQ2_1': [1, 1, 1, np.nan, 1, np.nan], 'SQ2_2': [2, np.nan, 2, 2, 2, 2], 'SQ2_3': [3, 3, 3, 3, np.nan, 3] })
步骤2:计算「All」行的统计数据
直接统计总样本数和各性别的样本数:
# 生成All行数据 all_row = pd.Series({ 'Base': len(df), 'M': df['Gender'].value_counts().get('M', 0), 'F': df['Gender'].value_counts().get('F', 0) }).to_frame('All').T
步骤3:计算各SQ2列的百分比统计
遍历所有SQ2开头的列,按性别分组统计非空值占比:
# 筛选出所有SQ2相关列 sq_columns = [col for col in df.columns if col.startswith('SQ2_')] percent_rows = pd.DataFrame() for col in sq_columns: # 按性别统计该列非空值数量 group_non_null = df.groupby('Gender')[col].count() # 计算各性别占比,乘以100后保留1位小数 group_pct = (group_non_null / all_row.loc['All', ['M', 'F']]) * 100 # 计算整体占比 overall_pct = (df[col].count() / all_row.loc['All', 'Base']) * 100 # 整理为当前列的统计行 current_row = pd.Series({ 'Base': round(overall_pct, 1), 'M': round(group_pct.get('M', 0), 1), 'F': round(group_pct.get('F', 0), 1) }, name=col) percent_rows = pd.concat([percent_rows, current_row.to_frame().T])
步骤4:合并结果并输出
把All行和各SQ2百分比行合并,调整索引格式:
# 合并所有行 final_result = pd.concat([all_row, percent_rows]) # 重置索引列名,让索引列显示为空 final_result.index.name = '' # 打印结果 print(final_result)
运行后即可得到目标汇总表。
另一种方法:用pivot_table简化分组统计
如果偏好使用pivot_table,可以通过标记非空值来实现:
# 把各SQ2列的非空值标记为1,空值标记为0 df_non_null = df[sq_columns].notna().astype(int) df_non_null['Gender'] = df['Gender'] # 用pivot_table统计各性别下的非空总数 pivot_stats = pd.pivot_table(df_non_null, index=df_non_null.columns[:-1], columns='Gender', aggfunc='sum') # 调整结构为宽表 pivot_wide = pivot_stats.unstack().reset_index(name='count').pivot(index='level_0', columns='Gender', values='count').fillna(0) # 计算百分比并添加Base列 pivot_pct = pivot_wide.div(all_row.loc['All', ['M', 'F']], axis=1) * 100 pivot_pct['Base'] = (df[sq_columns].count() / all_row.loc['All', 'Base']) * 100 pivot_pct = pivot_pct[['Base', 'M', 'F']].round(1) # 合并All行得到最终结果 final_result = pd.concat([all_row, pivot_pct]) final_result.index.name = '' print(final_result)
内容的提问来源于stack exchange,提问作者sm6
相关产品推荐
相关产品推荐

