咨询:基于pH分组的聚合DataFrame适用可视化图表及实现方法
问题与解决方案
问题背景
我是数据分析新手,通过以下代码用groupby和agg生成了包含多指标(温度、浊度等)min/max/mean统计值的多层索引DataFrame:
df_ph = df2.groupby('pH').agg(['min','max','mean'])
该DataFrame的结构如下:
{('Temperature (°C)', 'min'): {6.83: 22.5, 6.89: 23.6, 6.9: 22.5, 6.92: 21.4, 6.94: 23.2}, ('Temperature (°C)', 'max'): {6.83: 22.5, 6.89: 23.6, 6.9: 22.5, 6.92: 21.7, 6.94: 23.2}, ('Temperature (°C)', 'mean'): {6.83: 22.5, 6.89: 23.6, 6.9: 22.5, 6.92: 21.549999999999997, 6.94: 23.2}, ('Turbidity (NTU)', 'min'): {6.83: 3.3, 6.89: 4.6, 6.9: 4.2, 6.92: 4.7, 6.94: 4.0}, ('Turbidity (NTU)', 'max'): {6.83: 3.3, 6.89: 4.6, 6.9: 4.2, 6.92: 4.9, 6.94: 4.0}, ('Turbidity (NTU)', 'mean'): {6.83: 3.3, 6.89: 4.6, 6.9: 4.2, 6.92: 4.800000000000001, 6.94: 4.0}, ('Dissolved Oxygen (mg/L)', 'min'): {6.83: 6.1, 6.89: 7.2, 6.9: 6.0, 6.92: 6.3, 6.94: 6.6}, ('Dissolved Oxygen (mg/L)', 'max'): {6.83: 6.1, 6.89: 7.2, 6.9: 6.0, 6.92: 6.8, 6.94: 6.6}, ('Dissolved Oxygen (mg/L)', 'mean'): {6.83: 6.1, 6.89: 7.2, 6.9: 6.0, 6.92: 6.55, 6.94: 6.6}, ('Conductivity (µS/cm)', 'min'): {6.83: 348, 6.89: 320, 6.9: 357, 6.92: 362, 6.94: 348}, ('Conductivity (µS/cm)', 'max'): {6.83: 348, 6.89: 320, 6.9: 357, 6.92: 363, 6.94: 348}, ('Conductivity (µS/cm)', 'mean'): {6.83: 348.0, 6.89: 320.0, 6.9: 357.0, 6.92: 362.5, 6.94: 348.0}}
尝试用以下代码绘图未得到合理结果:
df.plot(xticks=df2['pH'], ylabel='pH')
需要以pH为核心的嵌套式或更优的可视化方案(基于matplotlib/seaborn)。
核心思路:先规整数据格式
多层索引的DataFrame不直接适合绘图,第一步要转成长格式(melt),让每个行对应一个(pH, 指标, 统计量, 值)的组合:
import pandas as pd import matplotlib.pyplot as plt import seaborn as sns # 转成长格式 df_melt = df_ph.reset_index().melt( id_vars='pH', var_name=['Metric', 'Stat'], value_name='Value' )
方案1:子图矩阵(每个指标单独展示统计值)
适合对比不同pH下,单个指标的min/max/mean变化,逻辑清晰,可读性强。
Matplotlib实现
metrics = df_melt['Metric'].unique() n_metrics = len(metrics) # 创建子图布局 fig, axes = plt.subplots(nrows=n_metrics, ncols=1, figsize=(8, 4*n_metrics), sharex=True) for ax, metric in zip(axes, metrics): # 筛选当前指标数据 subset = df_melt[df_melt['Metric'] == metric] # 绘制每个统计量的折线 for stat in ['min', 'max', 'mean']: stat_data = subset[subset['Stat'] == stat] ax.plot(stat_data['pH'], stat_data['Value'], marker='o', label=stat) # 设置子图样式 ax.set_title(f'{metric} 随pH的变化') ax.set_ylabel(metric.split('(')[1].strip(')')) ax.legend() plt.xlabel('pH') plt.tight_layout() plt.show()
Seaborn简化实现
用FacetGrid自动生成子图,代码更简洁:
g = sns.FacetGrid(df_melt, col='Metric', col_wrap=2, height=4, sharey=False) g.map(sns.lineplot, 'pH', 'Value', 'Stat', marker='o') g.add_legend(title='统计量') g.set_xlabels('pH') g.set_titles('{col_name}') plt.tight_layout() plt.show()
方案2:分组柱状图(同图展示所有指标的统计值)
适合在一张图里对比同一pH下,不同指标的min/max/mean差异,视觉冲击力强。
# 先将统计量转为列,方便分组 df_pivot = df_melt.pivot(index=['pH', 'Metric'], columns='Stat', values='Value').reset_index() fig, ax = plt.subplots(figsize=(12, 6)) bar_width = 0.25 x_base = range(len(df_pivot['pH'].unique())) metrics = df_pivot['Metric'].unique() # 遍历每个统计量绘制柱状图 for i, stat in enumerate(['min', 'max', 'mean']): # 整理每个pH下所有指标的统计值 values = [] for ph in df_pivot['pH'].unique(): values.extend(df_pivot[df_pivot['pH'] == ph][stat].values) # 计算柱子的x位置 x_pos = [pos + i*bar_width for pos in x_base for _ in metrics] ax.bar(x_pos, values, width=bar_width, label=stat) # 设置x轴标签 ax.set_xticks([pos + bar_width for pos in x_base]) ax.set_xticklabels([f'pH={ph}' for ph in df_pivot['pH'].unique()]) # 添加指标分组的图例 from matplotlib.patches import Patch metric_legend = [Patch(facecolor=f'C{j}', label=metric) for j, metric in enumerate(metrics)] stat_legend = ax.get_legend_handles_labels() ax.legend(handles=metric_legend + stat_legend[0], loc='upper right', bbox_to_anchor=(1.2, 1)) ax.set_ylabel('数值') ax.set_title('不同pH下各指标的统计值对比') plt.tight_layout() plt.show()
方案3:误差区间图(突出波动范围)
如果更关注指标在每个pH下的波动(min-max范围),可以用填充区间或误差棒展示:
# 以温度指标为例 temp_subset = df_melt[df_melt['Metric'] == 'Temperature (°C)'] ph_values = temp_subset['pH'].unique() # 提取各统计量的值 mean_vals = temp_subset[temp_subset['Stat'] == 'mean']['Value'].values min_vals = temp_subset[temp_subset['Stat'] == 'min']['Value'].values max_vals = temp_subset[temp_subset['Stat'] == 'max']['Value'].values fig, ax = plt.subplots(figsize=(8, 4)) ax.plot(ph_values, mean_vals, marker='o', label='平均值') ax.fill_between(ph_values, min_vals, max_vals, alpha=0.2, label='波动范围(Min-Max)') ax.set_title('温度随pH的变化及波动范围') ax.set_xlabel('pH') ax.set_ylabel('温度 (°C)') ax.legend() plt.show()
为什么之前的代码失效?
- 多层索引的DataFrame直接调用
plot()会导致列名混乱,绘图逻辑不明确; xticks=df2['pH']用了原始数据中的重复pH值,导致x轴刻度重叠、错乱。转成长格式后可以彻底避免这类问题。
内容的提问来源于stack exchange,提问作者jugunnu
相关产品推荐
相关产品推荐

