使用pandas DataFrame.plot()绘制双Y轴体重差柱状图与卡路里折线图
问题分析与解决
核心问题排查
- 变量名拼写错误:代码中定义的卡路里列表变量是
iest_cals,但后续调用add_nans和赋值给df['calories']时误用了未定义的est_cals,导致calories列全为NaN,自然无法绘制折线图。 - X轴对齐问题:使用日期ordinal值作为X轴时,柱状图的X轴被识别为离散分类,折线图的数值型X轴容易出现对齐偏差,导致折线无法正常显示。
修正后的代码
import pandas as pd import numpy as np from datetime import date import matplotlib.pyplot as plt from mplcursors import cursor from statistics import mean def add_nans(cals: list, dates: pd.Series) -> list: # 避免修改原列表,创建新列表补充NaN new_cals = cals.copy() for _ in range(len(dates) - len(cals)): new_cals.append(np.nan) return new_cals # Weight tracking real_weight = [ 223.8, 222, 221.2, 220.8, 221.4, 219.4, 219.8, 219.2, 219.6, 218.2, 221.6, 220.2, 219.4, 218.8, 218, 217.4, 218.6, 216.8, 217.6, 215.8, 216, 216, 219.6, 216.8, 216.2, 215.6, 215.8, 217.2, 214.6, 219.6, 217.4, 216.0, 215.2, 214.2, 214.4, 216.4, 215.4, 215, 214.2, 214.4, 216, 214.2 ] # 修正变量名拼写 est_cals = [ 2129, 2323, 2298, 1984, 2020, 2102, 1980, 2386, 2146, 2377, 2500, 2265, 2150, 1840, 2073, 2070, 2050, 2108, 2130, 2770, 2170, 2270, 1955, 2030, 2020, 2500, 1910, 2150, 2695, 1980, 2110, 2190, 2940, 1970, 2750, 1870, 2470, 2200, 2400, 4000, 2200, 1916 ] lw = len(real_weight) dates = pd.date_range(start='01/11/2024', end='07/11/2024') # 传递正确的变量名给add_nans real_weight = add_nans(cals=real_weight, dates=dates) est_cals = add_nans(cals=est_cals, dates=dates) difference = [0] + [round(j-i,4) for i, j in zip(real_weight[:-1], real_weight[1:])] goalw, startw = 185, real_weight[0] lin_weight = np.linspace(startw, goalw, len(dates)) lpw = round(7 * (lin_weight[0] - lin_weight[1]), 2) df = pd.DataFrame(data={'date': dates, 'lin_weight': lin_weight, 'real_weight': real_weight}) # 赋值正确的卡路里数据 df['calories'] = est_cals df['day'] = df.date df['difference'] = difference df['ordinal'] = pd.to_datetime(df['date']).apply(lambda date: date.toordinal()) df['idx'] = df.index df = df.set_index('date') startdt, enddt = df.index[0].date(), df.index[-1].date() avg_dif = df.difference.abs().mean() # 清洗数据:仅删除体重和卡路里均为空的行 df_clean = df.dropna(subset=['real_weight', 'calories'], how='all') day = [f'day {i+1}' for i in range(len(df_clean.index))] df_clean['day_1'] = day max_day = df_clean.loc[df_clean['difference'] == df_clean.difference.max(), 'day_1'].item() min_day = df_clean.loc[df_clean['difference'] == df_clean.difference.min(), 'day_1'].item() # 绘图逻辑:用日期索引保证双Y轴对齐 fig, ax1 = plt.subplots(figsize=(12,6)) # 绘制体重差值柱状图 ax1.bar(df_clean.index, df_clean['difference'], color='magenta', label='Daily Weight Difference (lbs)') ax1.axhline(y=0, linewidth=1, c='black', linestyle='-') ax1.set_xlabel('Days into Diet') ax1.set_ylabel('Difference in Daily Weight (lbs)') ax1.set_title(f'Daily Weight Difference vs Calorie Intake ({startdt} to {df_clean.tail(1).index.item().date()})\n') # 设置自定义X轴刻度标签 ax1.set_xticks(df_clean.index) ax1.set_xticklabels(day, rotation=45) # 绘制卡路里折线图(双Y轴) ax2 = ax1.twinx() ax2.plot(df_clean.index, df_clean['calories'], color='darkorange', linestyle='-', marker='o', label='Daily Calorie Intake (kcal)') ax2.set_ylabel('Daily Calorie Intake (kcal)') # 合并双Y轴图例 lines1, labels1 = ax1.get_legend_handles_labels() lines2, labels2 = ax2.get_legend_handles_labels() ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper right') cursor(hover=True) plt.tight_layout() plt.show()
关键修正说明
- 变量名统一:将拼写错误的
iest_cals改为est_cals,确保卡路里数据能正确写入DataFrame。 - NaN处理优化:修改
add_nans函数避免破坏原数据,同时调整dropna逻辑,仅删除两列数据均为空的行,保留有效记录。 - X轴对齐修复:直接使用DataFrame的日期索引作为X轴,彻底解决柱状图与折线图的X轴错位问题。
- 图例优化:手动合并双Y轴的图例,让图表信息展示更清晰。
内容的提问来源于stack exchange,提问作者DrakeMurdoch
相关产品推荐
相关产品推荐

