2026-02-01 22:36:56 +08:00
|
|
|
|
"""
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-02 21:47:52 +08:00
|
|
|
|
# ============== 图表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
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-01 22:36:56 +08:00
|
|
|
|
# ============== 主程序 ==============
|
|
|
|
|
|
|
|
|
|
|
|
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)
|