一、你要解决的问题

做数据分析的人经常遇到一个需求:根据几个经济指标(CPI、工资指数、生产力因素),按一个公式算出某个价格该不该调、调多少。

这公式本身不复杂,但当你加入负担能力上限、延期释放、多年叠加这些条件后,代码就开始有意思了。

正好港铁有个每年公开的票价调整公式,我们就拿它当案例——不是让大家研究港铁,是让你看完之后自己的业务里能直接用这个思路。

先看结果:公式算出来该涨2.85%,但因为触发"负担能力上限",最终是0%。那2.85%去哪了?往后分摊到2027和2028年。

这就是我们代码要做的事:实现公式 → 计算 → 处理边界条件 → 可视化。

二、票价调整公式长什么样

港铁的公式是公开的,每年3月按这个算一次:

票价调整幅度 = (0.5 × CPI变动) + (0.5 × 工资指数变动) - 生产力因素 + 上年转拨

但如果算出来的结果超过"家庭月入中位数变动",就按中位数变动来——这叫负担能力上限。没执行的部分不会消失,会延后到之后年份分摊释放。

2026年的真实数据(来源:港铁官方 + 政府统计处2026年3月公布):

参数 数值 数据来源
综合消费物价指数按年变动 +1.4% 政府统计处2025年12月
运输业名义工资指数按年变动 +3.0% 政府统计处2025年12月
生产力因素 -0.8% 港铁物业发展利润挂钩
2025/26年度转拨 +1.45% 上年度未执行的涨幅
公式计算结果 +2.85%
家庭月入中位数按年变动 0% 政府统计处2025年Q4
最终票价调整 0%(冻结) 触发负担能力上限

三、Python实现:单年度计算

先把基础公式写出来。用字典存参数,函数直接算:

# 第一步:定义参数和基础公式

def calculate_fare_adjustment(cpi_change, wage_change, productivity, carryover):
    """
    港铁票价调整机制公式
    cpi_change: 综合消费物价指数按年变动 (如 0.014 表示 +1.4%)
    wage_change: 运输业名义工资指数按年变动
    productivity: 生产力因素扣减
    carryover: 上年度转拨幅度
    返回: 计算结果 (正数=涨价, 0或负数=冻结/降价)
    """
    raw_result = (0.5 * cpi_change) + (0.5 * wage_change) + productivity + carryover
    return round(raw_result, 4)

# 2026年真实数据
params_2026 = {
    'cpi_change': 0.014,      # +1.4%
    'wage_change': 0.030,     # +3.0%
    'productivity': -0.008,   # -0.8%
    'carryover': 0.0145       # +1.45%
}

raw = calculate_fare_adjustment(**params_2026)
print(f"公式计算结果: {raw*100:.2f}%")
# 输出: 公式计算结果: 2.85%

输出结果跟官方公布的一致:+2.85%

但实际没涨——因为有负担能力上限:

# 第二步:加入负担能力上限判断

def apply_affordability_cap(raw_result, income_change):
    """
    负担能力上限:票价涨幅不能超过家庭月入中位数变动
    income_change: 家庭月入中位数按年变动
    """
    if raw_result > income_change:
        capped = income_change
        deferred = raw_result - income_change
        print(f"触发上限!公式算 {raw_result*100:.2f}%,上限 {income_change*100:.2f}%")
        print(f"延期金额: {deferred*100:.2f}% → 往后分摊")
        return capped, deferred
    else:
        return raw_result, 0

income_change_2026 = 0.0  # Q4家庭月入中位数变动 = 0%

final_rate, deferred_2026 = apply_affordability_cap(raw, income_change_2026)
print(f"实际调整: {final_rate*100:.2f}%")
print(f"延期释放: {deferred_2026*100:.2f}%")
# 输出:
# 触发上限!公式算 2.85%,上限 0.00%
# 延期金额: 2.85% → 往后分摊
# 实际调整: 0.00%
# 延期释放: 2.85%

跟官方公布的结果完全吻合:算出来2.85%,但因为家庭收入没涨,全部冻住。

到这里你可能觉得"就这?几行代码就完事了"——真正有意思的在第四步。

在这里插入图片描述

四、真正的难点:延期释放 + 多年叠加

刚才那2.85%不是消失了,是往后两年分摊:

  • 2027年7月释放:+1.43%(今年的一半)
  • 2028年7月释放:+1.42%(今年的另一半)

但这还没完,之前还有别的年份延下来的涨幅也在排队。港铁官方公布2026年还有一笔+1.96%是之前延到2026年但又被推到2027年的。这就是多年叠加的复杂之处。

代码这样写:

# 第三步:延期释放队列管理

class FareAdjustmentQueue:
    """管理多年延期释放的涨幅队列"""
    
    def __init__(self):
        self.deferred_queue = {}  # {年份: 待释放金额}
    
    def add_deferred(self, year, amount):
        """往某一年叠加延期涨幅"""
        if year in self.deferred_queue:
            self.deferred_queue[year] += amount
        else:
            self.deferred_queue[year] = amount
    
    def get_future_burden(self):
        """查看未来各年累计待释放金额"""
        return dict(sorted(self.deferred_queue.items()))

# 模拟2026年已知的延期队列
queue = FareAdjustmentQueue()

# 之前延到2026年的 +1.96%,现在被推到2027
queue.add_deferred(2027, 0.0196)

# 2026年压住的 +2.85%,分两年释放
queue.add_deferred(2027, 0.0143)  # 一半
queue.add_deferred(2028, 0.0142)  # 另一半

future = queue.get_future_burden()
for year, amount in future.items():
    print(f"{year}年累计待释放: +{amount*100:.2f}%")
# 输出:
# 2027年累计待释放: +3.39%
# 2028年累计待释放: +1.42%

注意:2027年的+3.39%还不包括2027年当年的新公式结果。如果2027年经济数据也指向加价,数字会继续往上叠。

五、数据可视化:一图看懂"不加价背后的账"

用matplotlib把延期释放的累积效应画出来:

# 第四步:可视化 —— 延期释放累积效应

import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False

years = ['2026\n(今年)', '2027\n(明年)', '2028\n(后年)']
# 当年的延期释放会在下一年叠加
deferred_values = [0, 3.39, 1.42]
cumulative = [0, 3.39, 4.81]

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

# 左图:每年待释放金额
colors = ['#2E86AB', '#A23B72', '#F18F01']
bars = ax1.bar(years, deferred_values, color=colors, width=0.6, edgecolor='white', linewidth=1.5)
ax1.set_title('各年待释放的延期涨幅', fontsize=14, fontweight='bold', pad=15)
ax1.set_ylabel('待释放幅度 (%)', fontsize=12)
for bar, val in zip(bars, deferred_values):
    ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.1, 
             f'+{val:.2f}%', ha='center', fontsize=13, fontweight='bold')

# 右图:累积效应
ax2.plot(['2026', '2027', '2028'], cumulative, 'o-', linewidth=3, 
         markersize=12, color='#A23B72', markerfacecolor='#F18F01')
ax2.fill_between(['2026', '2027', '2028'], cumulative, alpha=0.15, color='#A23B72')
ax2.set_title('延期涨幅累积效应', fontsize=14, fontweight='bold', pad=15)
ax2.set_ylabel('累积待释放 (%)', fontsize=12)
for i, (x, y) in enumerate(zip(['2026', '2027', '2028'], cumulative)):
    ax2.annotate(f'{y:.2f}%', (x, y), textcoords="offset points", 
                xytext=(0, 18), ha='center', fontsize=12, fontweight='bold')

plt.tight_layout()
plt.savefig('mtr_fare_deferral.png', dpi=150, bbox_inches='tight')
plt.show()

在这里插入图片描述

收藏本文,下次遇到类似的多变量价格调整公式 + 延期释放机制,直接把这段代码拿去改参数就能用。

六、附送:8条通勤路线的成本对比

既然已经在算港铁的账,顺手把港漂最常坐的8条通勤路线也做进了pandas。数据来源是港铁官网2026年6月八达通成人票价:

# 第五步:8条通勤路线成本分析

import pandas as pd

routes = [
    ('上水 → 金钟', 12.8, 510, '上水/乌溪沙-尖东全月通'),
    ('屯门 → 尖沙咀', 21.5, 545, '屯门-南昌全月通'),
    ('将军澳 → 中环', 12.8, 445, '都会票(40程/30天)'),
    ('荃湾 → 旺角', 10.3, None, '短途无需月票'),
    ('东涌 → 香港站', 23.6, 420, '东涌-香港全月通'),
    ('大围 → 九龙塘', 7.5, None, '短途无需月票'),
    ('粉岭 → 尖东', 14.5, 510, '上水/乌溪沙-尖东全月通'),
    ('落马洲 → 金钟', 49.6, None, '跨境段不适用月票'),
]

data = []
workdays = 22  # 每月工作日

for route, single, monthly, note in routes:
    monthly_cost = single * 2 * workdays  # 来回 × 22天
    saving = monthly_cost - monthly if monthly else 0
    data.append({
        '路线': route,
        '单程(HKD)': single,
        '22天来回': round(monthly_cost),
        '月票价格': monthly if monthly else '—',
        '月省金额': round(saving) if saving > 0 else '—',
        '备注': note
    })

df = pd.DataFrame(data)
print(df.to_string(index=False))

输出结果:

路线 单程 22天来回 月票价格 月省金额 备注
上水→金钟 $12.8 $563 $510 $53 全月通
屯门→尖沙咀 $21.5 $946 $545 $401 屯门-南昌全月通
将军澳→中环 $12.8 $563 $445 $118 都会票
荃湾→旺角 $10.3 $453 短途无需月票
东涌→香港站 $23.6 $1,038 $420 $618 东涌-香港全月通
大围→九龙塘 $7.5 $330 短途无需月票
粉岭→尖东 $14.5 $638 $510 $128 全月通
落马洲→金钟 $49.6 $2,182 跨境不适用

屯门人用全月通一个月能省$401,东涌人更夸张——省$618,正常坐要$1,038。而跨境通勤(落马洲出发)一个月两千多块,没有月票能省。

在这里插入图片描述

七、环境信息

项目 版本
Python 3.10+
pandas 2.0+
numpy 1.24+
matplotlib 3.7+
数据来源 港铁官网(2026年6月) + 政府统计处(2025年12月/2025年Q4) + 星岛头条(2026-03-27)

八、总结

我们做了五件事:

  1. 实现了港铁票价调整公式——4行代码还原官方计算
  2. 加了负担能力上限的判断——公式算2.85%,触发上限→0%
  3. 管理了延期释放队列——2.85%不会消失,后年要还
  4. 做了数据可视化——一眼看出累积效应
  5. 附送了通勤成本分析——同一组数据,pandas直接出结果

这个案例的价值不在港铁本身,在于它是一个完整的经济指标驱动公式的Python实现范式。你改一下参数,就能用到其他场景——电费调价、物业费调整、租金年检公式,思路是一样的。

如果这篇对你有用,收藏+点赞,你的支持让我继续写这类全流程数据分析教程。下一期打算写港铁延误数据的Python爬取+ARIMA预测,感兴趣的评论区说一声。


参考链接:

  1. 港铁票价调整机制官方页面:https://www.mtr.com.hk/fare-adjustment-mechanism/chi/
  2. 星岛头条报道《港铁今年冻结票价》:2026年3月27日

数据来源:港铁官网2026年6月票价、政府统计处2025年12月经济数据。代码在Python 3.10 + pandas 2.0环境下测试通过。

在这里插入图片描述

更多推荐