判断是否为足球场图及Matplotlib实现方案(含数据示例)
问题
我拥有一个包含特定列的DataFrame,列名格式为MotivX_MotivatorY_InnerDriverZ_PR,其中"PR"代表Percentile Rank(百分位排名,取值0-100)。每个动机(如MotivA)包含四个激励因子,每个激励因子对应InnerDriverA_PR和InnerDriverB_PR两个数值。
我希望生成的图表样式参考示例图,请问该图表是否为"Football Field Chart"?如何用Matplotlib实现该图表?
附最小可复现代码:
import pandas as pd import random import matplotlib.pyplot as plt import seaborn as sns # Create example data columns = [ 'MotivA_MotivatorA_InnerDriverA_PR', 'MotivA_MotivatorA_InnerDriverB_PR', 'MotivA_MotivatorB_InnerDriverA_PR', 'MotivA_MotivatorB_InnerDriverB_PR', 'MotivA_MotivatorC_InnerDriverA_PR', 'MotivA_MotivatorC_InnerDriverB_PR', 'MotivA_MotivatorD_InnerDriverA_PR', 'MotivA_MotivatorD_InnerDriverB_PR', 'MotivB_MotivatorA_InnerDriverA_PR', 'MotivB_MotivatorA_InnerDriverB_PR', 'MotivB_MotivatorB_InnerDriverA_PR', 'MotivB_MotivatorB_InnerDriverB_PR', 'MotivB_MotivatorC_InnerDriverA_PR', 'MotivB_MotivatorC_InnerDriverB_PR', 'MotivB_MotivatorD_InnerDriverA_PR', 'MotivB_MotivatorD_InnerDriverB_PR' ] df = pd.DataFrame(columns=columns) for i in range(1, 6): df.loc[f'Subject_{i}'] = [random.randint(0, 100) for _ in range(len(columns))] #──────────────────────────────────────────────── def create_horizontal_bar_chart(df, proband): motives = sorted(set(col.split('_')[0] for col in df.columns)) for motive in motives: columns = [col for col in df.columns if col.startswith(motive)] data = df.loc[proband, columns].reset_index() data['Motivator'] = data['index'].apply(lambda x: x.split('_')[1]) data['InnerDriver'] = data['index'].apply(lambda x: x.split('_')[2]) data['Value'] = data[proband] data = data.drop(['index', proband], axis=1) plt.figure(figsize=(10, 6)) sns.barplot(x='Value', y='Motivator', hue='InnerDriver', data=data) plt.title(f'{proband} - {motive}') plt.show() create_horizontal_bar_chart(df, 'Subject_1')
当前代码生成分组水平条形图,与目标图表效果差距较大。
解答
1. 图表类型判断
你提到的参考图表不是Football Field Chart。Football Field Chart主要用于展示多类别在不同分位数区间的分布(常见于金融估值场景),而你的目标图表属于带背景区间的并列横向条形图,核心是将每个激励因子对应的两个InnerDriver数值并列展示,同时通过背景色块区分百分位层级,直观体现数值所处的排名区间。
2. Matplotlib实现方案
以下是修改后的代码,可实现类似目标图表的效果:
import pandas as pd import random import matplotlib.pyplot as plt import numpy as np # 生成示例数据(复用原代码) columns = [ 'MotivA_MotivatorA_InnerDriverA_PR', 'MotivA_MotivatorA_InnerDriverB_PR', 'MotivA_MotivatorB_InnerDriverA_PR', 'MotivA_MotivatorB_InnerDriverB_PR', 'MotivA_MotivatorC_InnerDriverA_PR', 'MotivA_MotivatorC_InnerDriverB_PR', 'MotivA_MotivatorD_InnerDriverA_PR', 'MotivA_MotivatorD_InnerDriverB_PR', 'MotivB_MotivatorA_InnerDriverA_PR', 'MotivB_MotivatorA_InnerDriverB_PR', 'MotivB_MotivatorB_InnerDriverA_PR', 'MotivB_MotivatorB_InnerDriverB_PR', 'MotivB_MotivatorC_InnerDriverA_PR', 'MotivB_MotivatorC_InnerDriverB_PR', 'MotivB_MotivatorD_InnerDriverA_PR', 'MotivB_MotivatorD_InnerDriverB_PR' ] df = pd.DataFrame(columns=columns) for i in range(1, 6): df.loc[f'Subject_{i}'] = [random.randint(0, 100) for _ in range(len(columns))] def create_target_chart(df, proband): # 获取所有动机类别 motives = sorted(set(col.split('_')[0] for col in df.columns)) for motive in motives: # 筛选当前动机的列 motive_cols = [col for col in df.columns if col.startswith(motive)] # 整理数据:拆分列名,提取数值 data = df.loc[proband, motive_cols].reset_index() data[['Motiv', 'Motivator', 'InnerDriver', 'PR']] = data['index'].str.split('_', expand=True) data['Value'] = data[proband] data = data.drop(['index', proband, 'Motiv', 'PR'], axis=1) # 准备绘图数据:按Motivator分组,获取两个InnerDriver的数值 motivators = sorted(data['Motivator'].unique()) driver_a_vals = [data[(data['Motivator'] == m) & (data['InnerDriver'] == 'InnerDriverA')]['Value'].values[0] for m in motivators] driver_b_vals = [data[(data['Motivator'] == m) & (data['InnerDriver'] == 'InnerDriverB')]['Value'].values[0] for m in motivators] # 绘图设置 fig, ax = plt.subplots(figsize=(12, 6)) y_pos = np.arange(len(motivators)) bar_width = 0.35 # 绘制背景百分位区间色块 ax.axvspan(0, 25, color='#f8d7da', alpha=0.5) ax.axvspan(25, 50, color='#fff3cd', alpha=0.5) ax.axvspan(50, 75, color='#d4edda', alpha=0.5) ax.axvspan(75, 100, color='#d1ecf1', alpha=0.5) # 绘制并列横向条形图 bar1 = ax.barh(y_pos - bar_width/2, driver_a_vals, bar_width, label='InnerDriverA') bar2 = ax.barh(y_pos + bar_width/2, driver_b_vals, bar_width, label='InnerDriverB') # 添加数值标签 for bar in bar1: width = bar.get_width() ax.text(width + 1, bar.get_y() + bar.get_height()/2, f'{int(width)}', va='center') for bar in bar2: width = bar.get_width() ax.text(width + 1, bar.get_y() + bar.get_height()/2, f'{int(width)}', va='center') # 设置坐标轴与标题 ax.set_yticks(y_pos) ax.set_yticklabels(motivators) ax.set_xlabel('Percentile Rank (0-100)') ax.set_title(f'{proband} - {motive}') ax.set_xlim(0, 105) ax.legend() plt.tight_layout() plt.show() # 生成Subject_1的图表 create_target_chart(df, 'Subject_1')
代码关键点说明
- 数据整理:通过
str.split拆分列名,提取出Motivator和InnerDriver维度,将数据转换为适合绘图的结构 - 背景区间:用
ax.axvspan绘制四个百分位区间(0-25、25-50、50-75、75-100)的色块,模拟参考图的层级背景 - 并列条形:通过调整y轴位置实现两个InnerDriver的条形并列,同时添加数值标签增强可读性
- 样式优化:设置x轴范围、调整布局,让图表更贴近目标效果
内容的提问来源于stack exchange,提问作者Chris B.
相关产品推荐
相关产品推荐

