Files
2026_mcm_b/plane/cost_analysis.py
2026-02-02 21:47:52 +08:00

729 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Aviation Cost Structure Analysis
证明:在成熟运输体系中,能源成本与总成本具有强相关性
Data Source: MIT Airline Data Project / US DOT Form 41 via BTS
"""
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
import matplotlib.patches as mpatches
# 设置字体和样式
rcParams['font.sans-serif'] = ['Arial', 'DejaVu Sans', 'Helvetica']
rcParams['axes.unicode_minus'] = False
plt.style.use('seaborn-v0_8-whitegrid')
# ============== 数据加载 ==============
def load_data():
"""加载并清洗所有Excel数据"""
# 1. 燃油费用/ASM (美元)
df_fuel = pd.read_excel('Fuel Expense per ASM.xls', engine='xlrd')
# 2. 总费用/ASM (美分)
df_total = pd.read_excel('System Total Expense per Available Seat Mile (CASM ex Transport Related).xls', engine='xlrd')
# 3. 非人工费用/ASM (美分) - 不含燃油
df_nonlabor = pd.read_excel('System Non-Labor Expense per Available Seat Mile (CASM ex fuel, Transport Related and Labor).xls', engine='xlrd')
# 4. 总维护费用 (百万美元)
df_maint = pd.read_excel('Total Flight Equipment Maintenance Expense.xls', engine='xlrd')
# 提取年份 (第3行从第3列开始)
years_raw = df_fuel.iloc[2, 2:].dropna().values
years = np.array([int(y) for y in years_raw])
n_years = len(years)
# 提取 "Total All Sectors" 行数据 (第28行索引27)
fuel_per_asm = df_fuel.iloc[27, 2:2+n_years].values.astype(float) # 美元
total_per_asm = df_total.iloc[27, 2:2+n_years].values.astype(float) # 美分
nonlabor_per_asm = df_nonlabor.iloc[27, 2:2+n_years].values.astype(float) # 美分
maint_total = df_maint.iloc[27, 2:2+n_years].values.astype(float) # 百万美元
# 统一单位为美分/ASM
fuel_per_asm_cents = fuel_per_asm * 100 # 美元转美分
return {
'years': years,
'fuel_cents': fuel_per_asm_cents,
'total_cents': total_per_asm,
'nonlabor_cents': nonlabor_per_asm,
'maint_millions': maint_total,
}
# ============== 图表1: 燃油占比时间序列 ==============
def plot_fuel_share_trend(data, save_path='fuel_share_trend.png'):
"""绘制燃油成本占总成本的比例趋势"""
years = data['years']
fuel_cents = data['fuel_cents']
total_cents = data['total_cents']
# 计算燃油占比
fuel_share = (fuel_cents / total_cents) * 100
# 创建图表
fig, ax1 = plt.subplots(figsize=(14, 7))
# 绘制燃油占比(左轴)
color1 = '#E74C3C'
ax1.fill_between(years, 0, fuel_share, alpha=0.3, color=color1)
line1, = ax1.plot(years, fuel_share, 'o-', color=color1, linewidth=2.5,
markersize=8, label='Fuel Share (%)')
ax1.set_xlabel('Year', fontsize=13)
ax1.set_ylabel('Fuel as % of Total Operating Cost', fontsize=13, color=color1)
ax1.tick_params(axis='y', labelcolor=color1)
ax1.set_ylim(0, 45)
# 添加平均线
avg_share = np.mean(fuel_share)
ax1.axhline(y=avg_share, color=color1, linestyle='--', linewidth=1.5, alpha=0.7)
ax1.text(years[-1]+0.5, avg_share+1, f'Avg: {avg_share:.1f}%',
fontsize=11, color=color1, fontweight='bold')
# 右轴:绝对成本
ax2 = ax1.twinx()
color2 = '#3498DB'
line2, = ax2.plot(years, total_cents, 's--', color=color2, linewidth=2,
markersize=6, alpha=0.8, label='Total CASM (cents)')
line3, = ax2.plot(years, fuel_cents, '^--', color='#27AE60', linewidth=2,
markersize=6, alpha=0.8, label='Fuel CASM (cents)')
ax2.set_ylabel('Cost per ASM (cents)', fontsize=13, color=color2)
ax2.tick_params(axis='y', labelcolor=color2)
# 标注关键年份
# 2008年油价高峰
idx_2008 = np.where(years == 2008)[0][0]
ax1.annotate(f'2008 Oil Crisis\n{fuel_share[idx_2008]:.1f}%',
xy=(2008, fuel_share[idx_2008]),
xytext=(2005, fuel_share[idx_2008]+8),
fontsize=10, ha='center',
arrowprops=dict(arrowstyle='->', color='gray'),
bbox=dict(boxstyle='round,pad=0.3', facecolor='lightyellow', alpha=0.8))
# 2015年油价低谷
idx_2015 = np.where(years == 2015)[0][0]
ax1.annotate(f'2015 Oil Crash\n{fuel_share[idx_2015]:.1f}%',
xy=(2015, fuel_share[idx_2015]),
xytext=(2017, fuel_share[idx_2015]-8),
fontsize=10, ha='center',
arrowprops=dict(arrowstyle='->', color='gray'),
bbox=dict(boxstyle='round,pad=0.3', facecolor='lightcyan', alpha=0.8))
# 图例
lines = [line1, line2, line3]
labels = [l.get_label() for l in lines]
ax1.legend(lines, labels, loc='upper left', fontsize=11)
plt.title('Aviation Industry: Fuel Cost as Percentage of Total Operating Cost\n'
'(US Domestic Airlines, 1995-2018)', fontsize=15, fontweight='bold')
# 添加数据来源
fig.text(0.99, 0.01, 'Data Source: MIT Airline Data Project / US DOT Form 41',
fontsize=9, ha='right', style='italic', alpha=0.7)
plt.tight_layout()
plt.savefig(save_path, dpi=150, bbox_inches='tight')
print(f"图表已保存: {save_path}")
return fig
# ============== 图表2: 成本收敛曲线 ==============
def plot_cost_convergence(data, save_path='cost_convergence.png'):
"""
绘制成本收敛曲线:证明当复用次数增加时,总成本收敛于能源成本
模型假设 (Boeing 737-800):
- 飞机购置价: $80M
- 设计寿命: 50,000 cycles
- 平均航程: ~1000 miles/cycle
"""
# 飞机参数
AIRCRAFT_PRICE = 80_000_000 # $80M
DESIGN_LIFE_CYCLES = 50_000
AVG_MILES_PER_CYCLE = 1000
AVG_SEATS = 160 # B737-800 座位数
# 从实际数据中提取 2018 年数据作为"成熟状态"参考
idx_2018 = np.where(data['years'] == 2018)[0][0]
fuel_per_asm_cents = data['fuel_cents'][idx_2018]
total_per_asm_cents = data['total_cents'][idx_2018]
nonlabor_per_asm_cents = data['nonlabor_cents'][idx_2018]
# 计算单次飞行cycle的成本
asm_per_cycle = AVG_MILES_PER_CYCLE * AVG_SEATS
fuel_per_cycle = fuel_per_asm_cents / 100 * asm_per_cycle # 美元
ops_per_cycle = (total_per_asm_cents - fuel_per_asm_cents) / 100 * asm_per_cycle # 其他运营成本
print(f"\n=== 2018年单次飞行成本估算 (B737-800) ===")
print(f"ASM per cycle: {asm_per_cycle:,}")
print(f"Fuel cost per cycle: ${fuel_per_cycle:,.0f}")
print(f"Other ops per cycle: ${ops_per_cycle:,.0f}")
print(f"Total ops per cycle: ${fuel_per_cycle + ops_per_cycle:,.0f}")
# 复用次数范围
N = np.logspace(0, np.log10(DESIGN_LIFE_CYCLES * 2), 500) # 1 到 100,000
# 成本模型
amortized_hardware = AIRCRAFT_PRICE / N
energy_cost = np.full_like(N, fuel_per_cycle)
other_ops_cost = np.full_like(N, ops_per_cycle)
total_cost = amortized_hardware + energy_cost + other_ops_cost
# 创建图表
fig, ax = plt.subplots(figsize=(14, 8))
# 填充区域
ax.fill_between(N, 0, energy_cost, alpha=0.4, color='#E74C3C', label='Energy (Fuel) Cost')
ax.fill_between(N, energy_cost, energy_cost + other_ops_cost, alpha=0.3, color='#3498DB', label='Other Operating Cost')
ax.fill_between(N, energy_cost + other_ops_cost, total_cost, alpha=0.3, color='#95A5A6', label='Amortized Hardware')
# 总成本线
ax.plot(N, total_cost, 'k-', linewidth=3, label='Total Cost per Flight')
# 渐进线(能量底限)
asymptote = fuel_per_cycle + ops_per_cycle
ax.axhline(y=asymptote, color='#E74C3C', linestyle='--', linewidth=2, alpha=0.8)
ax.text(1.5, asymptote * 1.05, f'Asymptote: ${asymptote:,.0f}', fontsize=11,
color='#E74C3C', fontweight='bold')
# 标记关键点
# N=1 (一次性)
ax.plot(1, total_cost[0], 'ro', markersize=12, zorder=5)
ax.annotate(f'N=1 (Expendable)\n${total_cost[0]/1e6:.1f}M',
xy=(1, total_cost[0]), xytext=(3, total_cost[0]*0.7),
fontsize=10, ha='left',
arrowprops=dict(arrowstyle='->', color='red'),
bbox=dict(boxstyle='round', facecolor='mistyrose', alpha=0.9))
# N=20 (Falcon 9 现状)
idx_20 = np.argmin(np.abs(N - 20))
ax.plot(20, total_cost[idx_20], 'o', color='orange', markersize=10, zorder=5)
ax.annotate(f'N=20 (Falcon 9)\n${total_cost[idx_20]/1e6:.2f}M',
xy=(20, total_cost[idx_20]), xytext=(50, total_cost[idx_20]*1.5),
fontsize=10, ha='left',
arrowprops=dict(arrowstyle='->', color='orange'),
bbox=dict(boxstyle='round', facecolor='moccasin', alpha=0.9))
# N=50,000 (航空业)
idx_50k = np.argmin(np.abs(N - 50000))
ax.plot(50000, total_cost[idx_50k], 'go', markersize=12, zorder=5)
ax.annotate(f'N=50,000 (Aviation)\n${total_cost[idx_50k]:,.0f}',
xy=(50000, total_cost[idx_50k]), xytext=(10000, total_cost[idx_50k]*3),
fontsize=10, ha='center',
arrowprops=dict(arrowstyle='->', color='green'),
bbox=dict(boxstyle='round', facecolor='honeydew', alpha=0.9))
# 标记"能量主导区"
ax.axvspan(5000, N[-1], alpha=0.1, color='green')
ax.text(20000, ax.get_ylim()[1]*0.15, 'Energy Dominance Zone', fontsize=12,
color='green', fontweight='bold', ha='center', style='italic')
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_xlabel('Number of Reuses (N)', fontsize=13)
ax.set_ylabel('Cost per Flight ($)', fontsize=13)
ax.set_title('Cost Convergence: Hardware Amortization → Energy Dominance\n'
'(Boeing 737-800 Model, 2018 US Airline Data)', fontsize=15, fontweight='bold')
ax.legend(loc='upper right', fontsize=11)
ax.set_xlim(0.8, N[-1]*1.5)
ax.grid(True, which='both', alpha=0.3)
# 添加公式
formula_text = r'$Cost(N) = \frac{C_{hardware}}{N} + C_{fuel} + C_{ops}$'
ax.text(0.02, 0.02, formula_text, transform=ax.transAxes, fontsize=14,
verticalalignment='bottom', bbox=dict(boxstyle='round', facecolor='white', alpha=0.9))
plt.tight_layout()
plt.savefig(save_path, dpi=150, bbox_inches='tight')
print(f"图表已保存: {save_path}")
return fig
# ============== 图表3: 成本结构对比 (饼图) ==============
def plot_cost_structure_comparison(data, save_path='cost_structure_comparison.png'):
"""
对比三种场景的成本结构:
1. 一次性火箭 (N=1)
2. 可复用火箭 (N=20, Falcon 9)
3. 成熟航空业 (N=50,000)
"""
# 参数
AIRCRAFT_PRICE = 80_000_000
ROCKET_PRICE = 60_000_000 # Falcon 9 估算
# 从 2018 数据估算单次成本
idx_2018 = np.where(data['years'] == 2018)[0][0]
fuel_per_asm = data['fuel_cents'][idx_2018] / 100 # 美元
total_per_asm = data['total_cents'][idx_2018] / 100
# 飞机参数
asm_per_flight = 160_000 # 160 seats * 1000 miles
fuel_cost = fuel_per_asm * asm_per_flight
other_ops = (total_per_asm - fuel_per_asm) * asm_per_flight
# 火箭参数 (估算)
rocket_fuel = 500_000 # $500K 燃料
rocket_ops = 1_000_000 # $1M 地面运营
# 三种场景
scenarios = {
'Expendable Rocket\n(N=1)': {
'Hardware': ROCKET_PRICE / 1,
'Fuel/Energy': rocket_fuel,
'Operations': rocket_ops,
},
'Reusable Rocket\n(N=20, e.g. Falcon 9)': {
'Hardware': ROCKET_PRICE / 20,
'Fuel/Energy': rocket_fuel,
'Operations': rocket_ops,
},
'Commercial Aviation\n(N=50,000)': {
'Hardware': AIRCRAFT_PRICE / 50000,
'Fuel/Energy': fuel_cost,
'Operations': other_ops,
},
}
# 创建图表
fig, axes = plt.subplots(1, 3, figsize=(16, 6))
colors = ['#95A5A6', '#E74C3C', '#3498DB'] # 灰色(硬件), 红色(能源), 蓝色(运营)
for idx, (scenario_name, costs) in enumerate(scenarios.items()):
ax = axes[idx]
values = list(costs.values())
labels = list(costs.keys())
total = sum(values)
# 计算百分比
percentages = [v/total*100 for v in values]
# 绘制饼图
wedges, texts, autotexts = ax.pie(
values,
labels=None,
autopct=lambda pct: f'{pct:.1f}%' if pct > 3 else '',
colors=colors,
explode=(0.02, 0.05, 0.02),
startangle=90,
pctdistance=0.6,
textprops={'fontsize': 11, 'fontweight': 'bold'}
)
ax.set_title(f'{scenario_name}\nTotal: ${total:,.0f}', fontsize=12, fontweight='bold')
# 添加能源占比高亮
fuel_pct = percentages[1]
if fuel_pct < 5:
highlight_color = 'lightcoral'
note = 'Energy: Negligible!'
elif fuel_pct < 20:
highlight_color = 'lightyellow'
note = f'Energy: {fuel_pct:.1f}%'
else:
highlight_color = 'lightgreen'
note = f'Energy: {fuel_pct:.1f}%'
ax.text(0, -1.4, note, ha='center', fontsize=11, fontweight='bold',
bbox=dict(boxstyle='round', facecolor=highlight_color, alpha=0.8))
# 添加统一图例
legend_patches = [
mpatches.Patch(color=colors[0], label='Hardware (Amortized)'),
mpatches.Patch(color=colors[1], label='Fuel / Energy'),
mpatches.Patch(color=colors[2], label='Other Operations'),
]
fig.legend(handles=legend_patches, loc='lower center', ncol=3, fontsize=12,
bbox_to_anchor=(0.5, -0.02))
plt.suptitle('Cost Structure Evolution: From Hardware-Dominant to Energy-Dominant\n'
'(Transition as Reusability Increases)', fontsize=15, fontweight='bold', y=1.02)
plt.tight_layout()
plt.savefig(save_path, dpi=150, bbox_inches='tight')
print(f"图表已保存: {save_path}")
return fig
# ============== 图表4: 相关性分析 ==============
def plot_correlation_analysis(data, save_path='energy_cost_correlation.png'):
"""
分析燃油成本与总成本的相关性
证明:在成熟系统中,能源是成本的主要驱动因素
"""
years = data['years']
fuel = data['fuel_cents']
total = data['total_cents']
# 计算相关系数
correlation = np.corrcoef(fuel, total)[0, 1]
# 线性回归
coeffs = np.polyfit(fuel, total, 1)
poly = np.poly1d(coeffs)
fuel_range = np.linspace(fuel.min(), fuel.max(), 100)
# 创建图表
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 左图:散点图 + 回归线
ax1 = axes[0]
scatter = ax1.scatter(fuel, total, c=years, cmap='viridis', s=100, alpha=0.8,
edgecolors='white', linewidth=1.5)
ax1.plot(fuel_range, poly(fuel_range), 'r--', linewidth=2, label=f'Linear Fit (R²={correlation**2:.3f})')
# 颜色条
cbar = plt.colorbar(scatter, ax=ax1)
cbar.set_label('Year', fontsize=11)
ax1.set_xlabel('Fuel Cost per ASM (cents)', fontsize=12)
ax1.set_ylabel('Total Cost per ASM (cents)', fontsize=12)
ax1.set_title(f'Fuel Cost vs Total Cost Correlation\n'
f'Pearson r = {correlation:.3f}', fontsize=13, fontweight='bold')
ax1.legend(fontsize=11)
ax1.grid(True, alpha=0.3)
# 添加相关性解释
ax1.text(0.05, 0.95, f'Strong Positive Correlation\nEnergy drives ~{correlation**2*100:.0f}% of cost variance',
transform=ax1.transAxes, fontsize=10, verticalalignment='top',
bbox=dict(boxstyle='round', facecolor='lightgreen', alpha=0.8))
# 右图:时间序列对比(归一化)
ax2 = axes[1]
# 归一化到基准年 (1995)
fuel_norm = fuel / fuel[0] * 100
total_norm = total / total[0] * 100
ax2.plot(years, fuel_norm, 'o-', color='#E74C3C', linewidth=2.5, markersize=8, label='Fuel Cost (Indexed)')
ax2.plot(years, total_norm, 's-', color='#3498DB', linewidth=2.5, markersize=8, label='Total Cost (Indexed)')
ax2.axhline(y=100, color='gray', linestyle=':', alpha=0.7)
ax2.set_xlabel('Year', fontsize=12)
ax2.set_ylabel('Index (1995 = 100)', fontsize=12)
ax2.set_title('Fuel and Total Cost Co-movement\n(Indexed to 1995)', fontsize=13, fontweight='bold')
ax2.legend(fontsize=11)
ax2.grid(True, alpha=0.3)
# 标注关键时期
ax2.axvspan(2007, 2009, alpha=0.2, color='red', label='Oil Spike')
ax2.axvspan(2014, 2016, alpha=0.2, color='blue', label='Oil Crash')
plt.suptitle('Evidence: Energy Cost Strongly Correlates with Total Operating Cost\n'
'(US Domestic Airlines Industry)', fontsize=14, fontweight='bold', y=1.02)
plt.tight_layout()
plt.savefig(save_path, dpi=150, bbox_inches='tight')
print(f"图表已保存: {save_path}")
# 打印统计信息
print(f"\n=== 相关性分析结果 ===")
print(f"Pearson相关系数: {correlation:.4f}")
print(f"R²决定系数: {correlation**2:.4f}")
print(f"回归方程: Total = {coeffs[0]:.3f} × Fuel + {coeffs[1]:.3f}")
return fig
# ============== 图表5: 综合信息图 ==============
def plot_comprehensive_summary(data, save_path='aviation_cost_summary.png'):
"""
创建一张综合性的信息图,用于论文
"""
fig = plt.figure(figsize=(16, 12))
# 布局
gs = fig.add_gridspec(2, 2, hspace=0.3, wspace=0.25)
years = data['years']
fuel = data['fuel_cents']
total = data['total_cents']
nonlabor = data['nonlabor_cents']
# ========== 左上:燃油占比趋势 ==========
ax1 = fig.add_subplot(gs[0, 0])
fuel_share = (fuel / total) * 100
ax1.fill_between(years, 0, fuel_share, alpha=0.4, color='#E74C3C')
ax1.plot(years, fuel_share, 'o-', color='#E74C3C', linewidth=2.5, markersize=6)
avg_share = np.mean(fuel_share)
ax1.axhline(y=avg_share, color='darkred', linestyle='--', linewidth=1.5)
ax1.text(2017, avg_share+2, f'Avg: {avg_share:.1f}%', fontsize=10, fontweight='bold', color='darkred')
ax1.set_xlabel('Year', fontsize=11)
ax1.set_ylabel('Fuel Share (%)', fontsize=11)
ax1.set_title('(A) Fuel as % of Total Operating Cost', fontsize=12, fontweight='bold')
ax1.set_ylim(0, 45)
ax1.grid(True, alpha=0.3)
# ========== 右上:成本构成堆叠图 ==========
ax2 = fig.add_subplot(gs[0, 1])
labor_cents = total - fuel - nonlabor # 劳动力成本 = 总成本 - 燃油 - 非劳动力
ax2.fill_between(years, 0, fuel, alpha=0.8, color='#E74C3C', label='Fuel')
ax2.fill_between(years, fuel, fuel + nonlabor, alpha=0.8, color='#3498DB', label='Non-Labor (ex Fuel)')
ax2.fill_between(years, fuel + nonlabor, total, alpha=0.8, color='#2ECC71', label='Labor & Related')
ax2.set_xlabel('Year', fontsize=11)
ax2.set_ylabel('CASM (cents)', fontsize=11)
ax2.set_title('(B) Cost Structure Breakdown (Stacked)', fontsize=12, fontweight='bold')
ax2.legend(loc='upper left', fontsize=9)
ax2.grid(True, alpha=0.3)
# ========== 左下:收敛曲线 ==========
ax3 = fig.add_subplot(gs[1, 0])
# 简化的收敛模型
N = np.logspace(0, 5, 200)
hardware = 80_000_000 / N
fuel_cost = 5000 # 单次飞行燃油
ops_cost = 4000 # 其他运营
total_cost = hardware + fuel_cost + ops_cost
ax3.fill_between(N, 0, fuel_cost, alpha=0.5, color='#E74C3C', label='Energy (Fuel)')
ax3.fill_between(N, fuel_cost, fuel_cost + ops_cost, alpha=0.4, color='#3498DB', label='Operations')
ax3.fill_between(N, fuel_cost + ops_cost, total_cost, alpha=0.3, color='#95A5A6', label='Hardware')
ax3.plot(N, total_cost, 'k-', linewidth=2.5)
ax3.axhline(y=fuel_cost + ops_cost, color='#E74C3C', linestyle='--', linewidth=1.5)
# 标记点
ax3.plot(1, total_cost[0], 'ro', markersize=10)
ax3.plot(20, hardware[np.argmin(np.abs(N-20))] + fuel_cost + ops_cost, 'o', color='orange', markersize=10)
ax3.plot(50000, total_cost[np.argmin(np.abs(N-50000))], 'go', markersize=10)
ax3.text(1.5, total_cost[0]*0.6, 'N=1\nExpendable', fontsize=9, ha='left')
ax3.text(25, 5e6, 'N=20\nFalcon 9', fontsize=9, ha='left')
ax3.text(30000, 15000, 'N=50K\nAviation', fontsize=9, ha='left')
ax3.set_xscale('log')
ax3.set_yscale('log')
ax3.set_xlabel('Number of Reuses (N)', fontsize=11)
ax3.set_ylabel('Cost per Flight ($)', fontsize=11)
ax3.set_title('(C) Cost Convergence Model', fontsize=12, fontweight='bold')
ax3.legend(loc='upper right', fontsize=9)
ax3.grid(True, which='both', alpha=0.3)
# ========== 右下:相关性散点图 ==========
ax4 = fig.add_subplot(gs[1, 1])
correlation = np.corrcoef(fuel, total)[0, 1]
coeffs = np.polyfit(fuel, total, 1)
scatter = ax4.scatter(fuel, total, c=years, cmap='plasma', s=80, alpha=0.8, edgecolors='white')
fuel_range = np.linspace(fuel.min(), fuel.max(), 100)
ax4.plot(fuel_range, np.poly1d(coeffs)(fuel_range), 'r--', linewidth=2)
ax4.set_xlabel('Fuel Cost (cents/ASM)', fontsize=11)
ax4.set_ylabel('Total Cost (cents/ASM)', fontsize=11)
ax4.set_title(f'(D) Fuel-Total Cost Correlation\n(r = {correlation:.3f}, R² = {correlation**2:.3f})',
fontsize=12, fontweight='bold')
ax4.grid(True, alpha=0.3)
cbar = plt.colorbar(scatter, ax=ax4)
cbar.set_label('Year', fontsize=10)
# 主标题
fig.suptitle('Aviation Industry Cost Analysis: Evidence for Energy-Cost Correlation\n'
'(US Domestic Airlines, 1995-2018 | Data: MIT Airline Data Project)',
fontsize=16, fontweight='bold', y=1.01)
plt.tight_layout()
plt.savefig(save_path, dpi=150, bbox_inches='tight')
print(f"综合图表已保存: {save_path}")
return fig
# ============== 图表6: 燃油占比趋势(简洁版,中低饱和度)==============
def plot_fuel_share_trend_simple(data, save_path='fuel_share_trend.png'):
"""绘制燃油成本占总成本的比例趋势 - 简洁版,无标题,中低饱和度色调"""
years = data['years']
fuel_cents = data['fuel_cents']
total_cents = data['total_cents']
# 计算燃油占比
fuel_share = (fuel_cents / total_cents) * 100
# 创建图表 - 更小但保持2:1比例
fig, ax1 = plt.subplots(figsize=(10, 5))
# 中低饱和度色调
color1 = '#B87373' # 柔和的红色/玫瑰色
color2 = '#6B8FAF' # 柔和的蓝色
color3 = '#7AAE7A' # 柔和的绿色
# 绘制燃油占比(左轴)
ax1.fill_between(years, 0, fuel_share, alpha=0.25, color=color1)
line1, = ax1.plot(years, fuel_share, 'o-', color=color1, linewidth=2,
markersize=6, label='Fuel Share (%)')
ax1.set_xlabel('Year', fontsize=11)
ax1.set_ylabel('Fuel as % of Total Operating Cost', fontsize=11, color=color1)
ax1.tick_params(axis='y', labelcolor=color1)
ax1.set_ylim(0, 45)
# 添加平均线
avg_share = np.mean(fuel_share)
ax1.axhline(y=avg_share, color=color1, linestyle='--', linewidth=1.2, alpha=0.6)
ax1.text(years[-1]+0.5, avg_share+1, f'Avg: {avg_share:.1f}%',
fontsize=10, color=color1, fontweight='bold')
# 右轴:绝对成本
ax2 = ax1.twinx()
line2, = ax2.plot(years, total_cents, 's--', color=color2, linewidth=1.8,
markersize=5, alpha=0.75, label='Total CASM (cents)')
line3, = ax2.plot(years, fuel_cents, '^--', color=color3, linewidth=1.8,
markersize=5, alpha=0.75, label='Fuel CASM (cents)')
ax2.set_ylabel('Cost per ASM (cents)', fontsize=11, color=color2)
ax2.tick_params(axis='y', labelcolor=color2)
# 标注关键年份
# 2008年油价高峰
idx_2008 = np.where(years == 2008)[0][0]
ax1.annotate(f'2008 Oil Crisis\n{fuel_share[idx_2008]:.1f}%',
xy=(2008, fuel_share[idx_2008]),
xytext=(2005, fuel_share[idx_2008]+8),
fontsize=9, ha='center',
arrowprops=dict(arrowstyle='->', color='#888888'),
bbox=dict(boxstyle='round,pad=0.3', facecolor='#FFF5E6', alpha=0.8))
# 2015年油价低谷
idx_2015 = np.where(years == 2015)[0][0]
ax1.annotate(f'2015 Oil Crash\n{fuel_share[idx_2015]:.1f}%',
xy=(2015, fuel_share[idx_2015]),
xytext=(2017, fuel_share[idx_2015]-8),
fontsize=9, ha='center',
arrowprops=dict(arrowstyle='->', color='#888888'),
bbox=dict(boxstyle='round,pad=0.3', facecolor='#E6F3FA', alpha=0.8))
# 图例
lines = [line1, line2, line3]
labels = [l.get_label() for l in lines]
ax1.legend(lines, labels, loc='upper left', fontsize=10)
# 添加数据来源
fig.text(0.99, 0.01, 'Data Source: MIT Airline Data Project / US DOT Form 41',
fontsize=8, ha='right', style='italic', alpha=0.6)
plt.tight_layout()
plt.savefig(save_path, dpi=150, bbox_inches='tight')
print(f"图表已保存: {save_path}")
return fig
# ============== 图表7: 燃油-总成本相关性散点图(单独版)==============
def plot_fuel_total_correlation_scatter(data, save_path='fuel_total_correlation.png'):
"""绘制燃油成本与总成本的相关性散点图 - 单独版,无标题,中低饱和度色调"""
years = data['years']
fuel = data['fuel_cents']
total = data['total_cents']
# 计算相关系数
correlation = np.corrcoef(fuel, total)[0, 1]
# 线性回归
coeffs = np.polyfit(fuel, total, 1)
poly = np.poly1d(coeffs)
fuel_range = np.linspace(fuel.min(), fuel.max(), 100)
# 创建图表 - 与fuel_share_trend类似的尺寸比例
fig, ax = plt.subplots(figsize=(10, 5))
# 中低饱和度色调 - 与fuel_share_trend类似但不完全相同
scatter_cmap = 'YlGnBu' # 柔和的黄-绿-蓝渐变色
line_color = '#9B7B6B' # 柔和的棕红色
text_bg_color = '#D4E8D4' # 柔和的淡绿色
# 散点图
scatter = ax.scatter(fuel, total, c=years, cmap=scatter_cmap, s=90, alpha=0.85,
edgecolors='white', linewidth=1.2)
# 回归线
ax.plot(fuel_range, poly(fuel_range), '--', color=line_color, linewidth=2,
label=f'Linear Fit (R²={correlation**2:.3f})')
# 颜色条
cbar = plt.colorbar(scatter, ax=ax)
cbar.set_label('Year', fontsize=10)
ax.set_xlabel('Fuel Cost per ASM (cents)', fontsize=11)
ax.set_ylabel('Total Cost per ASM (cents)', fontsize=11)
ax.legend(fontsize=10, loc='lower right')
ax.grid(True, alpha=0.3)
# 添加相关性解释
ax.text(0.05, 0.95, f'Pearson r = {correlation:.3f}\nEnergy drives ~{correlation**2*100:.0f}% of cost variance',
transform=ax.transAxes, fontsize=10, verticalalignment='top',
bbox=dict(boxstyle='round', facecolor=text_bg_color, alpha=0.8))
# 添加数据来源
fig.text(0.99, 0.01, 'Data Source: MIT Airline Data Project / US DOT Form 41',
fontsize=8, ha='right', style='italic', alpha=0.6)
plt.tight_layout()
plt.savefig(save_path, dpi=150, bbox_inches='tight')
print(f"图表已保存: {save_path}")
return fig
# ============== 主程序 ==============
if __name__ == "__main__":
print("=" * 60)
print("Aviation Cost Structure Analysis")
print("证明:成熟运输系统中能源与成本的相关性")
print("=" * 60)
# 加载数据
data = load_data()
print(f"\n数据范围: {data['years'][0]} - {data['years'][-1]}")
print(f"年份数量: {len(data['years'])}")
# 打印关键统计
fuel_share = data['fuel_cents'] / data['total_cents'] * 100
print(f"\n燃油占比统计:")
print(f" 平均值: {np.mean(fuel_share):.1f}%")
print(f" 最大值: {np.max(fuel_share):.1f}% ({data['years'][np.argmax(fuel_share)]})")
print(f" 最小值: {np.min(fuel_share):.1f}% ({data['years'][np.argmin(fuel_share)]})")
# 生成图表
print("\n正在生成图表...")
plot_fuel_share_trend(data)
plot_cost_convergence(data)
plot_cost_structure_comparison(data)
plot_correlation_analysis(data)
plot_comprehensive_summary(data)
print("\n" + "=" * 60)
print("所有图表生成完成!")
print("=" * 60)