35.上市公司净利润增长率的计算

⭐ 本章学习目标

  • 理解净利润增长率在价值投资中的核心作用
  • 掌握 pct_change() 计算增长率的方法
  • 学会计算复合年均增长率(CAGR)
  • 了解 PEG比率 在估值中的应用
  • 能够对增长率进行可视化趋势分解

⭐ 为什么关注净利润增长率?

净利润增长率是衡量上市公司盈利能力成长性的核心指标。

  • 成长性评估: 识别高增长企业(成长股)
  • 估值依据: DCF模型、PEG比率的关键输入
  • 投资决策: 区分价值股 vs 成长股
  • 业绩预警: 增长率放缓可能预示业绩拐点

⭐ 戈登增长模型:增长率如何决定股价

根据戈登增长模型(Gordon Growth Model):

\[P_0 = \frac{D_1}{r - g}\]

  • \(P_0\): 当前股票价格
  • \(D_1\): 下一期预期股息
  • \(r\): 必要收益率(股权成本)
  • \(g\): 股息永续增长率

关键洞见: \(g\) 越接近 \(r\),股价越高!

⭐ 增长率的三个核心认知

认知 含义
增长率越高,价值越大 \(g\) 越接近 \(r\),戈登模型中 \(P_0\) 越高
增长质量重于数量 可持续的增长优于短期爆发
增长率递减 随着企业成熟,增长率会逐步下降

⭐ 净利润增长率的四大金融意义

维度 应用
成长性评估 区分周期性增长与趋势性增长
估值依据 DCF、PEG比率的核心输入
投资决策 价值投资 / 成长投资 / GARP策略
业绩预警 负增长需警惕企业衰退

⭐ 中国上市公司财务报告披露体系

报告类型 披露时间 特点
季度报告(Q1-Q3) 季度结束后1个月内 简化报表,不需审计
年度报告 会计年度结束后4个月内 完整三张报表,必须审计
业绩快报 早于正式报告 实际数据但未经审计
业绩预告 早于正式报告 预计数据范围

⭐ 利润表的层次结构

一、营业收入
减:营业成本、销售费用、管理费用、财务费用
───────────────────────────
二、营业利润 (Operating Profit) ← 本章使用
加:营业外收入  减:营业外支出
───────────────────────────
三、利润总额 (Total Profit)
减:所得税费用
───────────────────────────
四、净利润 (Net Profit) ← 更常用指标

⭐ 第一步:读取上市公司财务数据

Listing 1
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 从远程URL读取Excel文件
url = 'https://huoran.oss-cn-shenzhen.aliyuncs.com/20230410/xls/1645298865123385344.xls'
df = pd.read_excel(url)

print('原始数据:')
print(df.head(10))
print(f'\n数据类型: {type(df)}')
print(f'\n数据形状: {df.shape}')
原始数据:
     Unnamed: 0  2022年年报  2021年年报 2020年年报 2019年年报 2018年年报 2017年年报 2016年年报  \
0       上市前/上市后      上市后      上市后     上市后     上市后     上市后     上市后     上市后   
1          报表类型     合并报表     合并报表    合并报表    合并报表    合并报表    合并报表    合并报表   
2          公司类型       通用       通用      通用      通用      通用      通用      通用   
3   一、营业总收入(亿元)  1275.54  1094.64  979.93  888.54  771.99  610.63  401.55   
4      营业收入(亿元)     1241   1061.9  949.15   854.3  736.39  582.18  388.62   
5      利息收入(亿元)    34.54    32.74   30.78   34.24    35.6   28.44   12.93   
6  手续费及佣金收入(亿元)      NaN      NaN     NaN       0    0.01    0.01       0   
7    其他业务收入(亿元)      NaN      NaN     NaN     NaN     NaN     NaN     NaN   
8   二、营业总成本(亿元)   397.48   347.77  313.05  298.12  258.66  221.23  158.89   
9      营业成本(亿元)   100.93    89.83   81.54    74.3   65.23    59.4    34.1   

  2015年年报 2014年年报  ... 2007年年报 2006年年报 2005年年报 2004年年报 2003年年报 2002年年报  \
0     上市后     上市后  ...     上市后     上市后     上市后     上市后     上市后     上市后   
1    合并报表    合并报表  ...    合并报表    合并报表    合并报表    合并报表    合并报表    合并报表   
2      通用      通用  ...      通用      通用      通用      通用      通用      通用   
3  334.47  322.17  ...   72.37   49.03   39.31    30.1   24.02   18.35   
4   326.6  315.74  ...   72.37   49.03   39.31    30.1   24.01   18.35   
5    7.87    6.43  ...     NaN     NaN     NaN     NaN     NaN     NaN   
6    0.01       0  ...     NaN     NaN     NaN     NaN     NaN     NaN   
7     NaN     NaN  ...     NaN     NaN     NaN     NaN    0.01     NaN   
8  112.92  101.17  ...   27.14   24.18   20.12   15.22   14.27   11.89   
9   25.38   23.39  ...    8.72    7.87    6.87    5.35    4.77    3.41   

  2001年年报 2000年年报 1999年年报 1998年年报  
0     上市后     上市前     上市前     上市前  
1    合并报表    合并报表    合并报表    合并报表  
2      通用      通用      通用      通用  
3   16.18   11.14    8.91    6.28  
4   16.18   11.14    8.91    6.28  
5     NaN     NaN     NaN     NaN  
6     NaN     NaN     NaN     NaN  
7     NaN     NaN     NaN     NaN  
8   10.09    6.66    5.38     4.1  
9    2.88    1.97    1.29    0.77  

[10 rows x 26 columns]

数据类型: <class 'pandas.core.frame.DataFrame'>

数据形状: (54, 26)

pd.read_excel() 核心参数

参数 说明 默认值
sheet_name 指定读取的Sheet 第一个Sheet
header 指定列名所在行 0(第一行)
index_col 指定索引列 None
  • 支持 HTTP/HTTPS URL 直接读取远程文件
  • 自动处理编码问题
  • 返回 DataFrame 对象

⭐ 平台任务代码

Listing 2
# 注:该代码块读取远程平台数据文件,且变量名与下方代码不匹配,渲染时无法正确执行
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库

#读取excel文件
df = pd.read_excel('https://huoran.oss-cn-shenzhen.aliyuncs.com/20230410/xls/1645298865123385344.xls')

#把index设置为Unnamed: 0
df.set_index('Unnamed: 0', inplace=True)

#提取index为"四、营业利润(亿元)"的行
df = df.loc['四、营业利润(亿元)']

#输出数据格式
print(type(df))

#修改index为当前index的前4个字符
df.index = df.index.str[:4]

#将index转换为int类型
df.index = df.index.astype(int)

#将index从小到大排序
df.sort_index(inplace=True)

#计算净利润增长率
result = df.pct_change()

#输出结果
print(result)

⭐ 平台代码逐步解析(1/3)

数据索引设置与行提取

# 将第一列设为索引
df.set_index('Unnamed: 0', inplace=True)

# 按标签提取"营业利润"行
df = df.loc['四、营业利润(亿元)']
  • set_index(): 将某列设为 DataFrame 的索引
  • loc[]: 基于标签的索引器,精确定位某一行
  • 提取后 df 变为 Series 对象

⭐ 平台代码逐步解析(2/3)

索引清洗与类型转换

# 截取前4个字符(如 '2018年' → '2018')
df.index = df.index.str[:4]

# 转换为整数类型
df.index = df.index.astype(int)

# 按年份从小到大排序
df.sort_index(inplace=True)
  • .str[:4]: 字符串切片,提取年份数字部分
  • .astype(int): 类型转换,便于后续数值操作
  • sort_index(): 确保时间序列按正确顺序排列

⭐ 平台代码逐步解析(3/3)

增长率计算

result = df.pct_change()
  • pct_change() 计算百分比变化: \((X_t - X_{t-1}) / X_{t-1}\)
  • 默认 periods=1,即与前一期相比
  • 第一个值为 NaN(因无上期数据)

⭐ 增长率的三种计算方法

方法 公式 适用场景
简单增长率 \(g_t = \frac{X_t - X_{t-1}}{X_{t-1}}\) 相邻两期对比
CAGR \(\left(\frac{X_n}{X_0}\right)^{1/n} - 1\) 长期平均增长
对数差分 \(\ln(X_t) - \ln(X_{t-1})\) 连续复利假设

⭐ 简单增长率:pct_change() 的原理

\[g_t = \frac{X_t - X_{t-1}}{X_{t-1}}\]

Listing 3
# 构造演示数据
demo_profit = pd.Series(
    [100, 120, 108, 130, 150],
    index=[2019, 2020, 2021, 2022, 2023],
    name='营业利润(亿元)'
)
growth_demo = demo_profit.pct_change() * 100

for year, rate in growth_demo.items():
    if not pd.isna(rate):
        print(f'{year}年增长率: {rate:+.2f}%')
2020年增长率: +20.00%
2021年增长率: -10.00%
2022年增长率: +20.37%
2023年增长率: +15.38%

⭐ 手动验证 pct_change() 的结果

Listing 4
# 手动逐年计算增长率
manual_growth = pd.Series(index=demo_profit.index, dtype=float)
for i in range(1, len(demo_profit)):
    manual_growth.iloc[i] = (
        (demo_profit.iloc[i] - demo_profit.iloc[i-1])
        / demo_profit.iloc[i-1]
    )

# 验证两种方法一致性
print(f'两种方法一致: {np.allclose(growth_demo.dropna()/100, manual_growth.dropna())}')
两种方法一致: True

⭐ 复合年均增长率(CAGR)

CAGR 假设投资按恒定增长率复利增长:

\[\text{CAGR} = \left(\frac{V_{\text{终值}}}{V_{\text{初值}}}\right)^{\frac{1}{n}} - 1\]

  • 消除短期波动影响
  • 更符合长期投资视角
  • 通常 CAGR < 简单平均(除非增长率恒定)

⭐ CAGR 计算实例

Listing 5
start_value = demo_profit.iloc[0]   # 期初值
end_value = demo_profit.iloc[-1]    # 期末值
n_years = len(demo_profit) - 1      # 年份数

cagr_demo = (end_value / start_value) ** (1 / n_years) - 1
avg_growth = (growth_demo.dropna() / 100).mean()

print(f'期初值: {start_value:.0f}亿元')
print(f'期末值: {end_value:.0f}亿元')
print(f'年份数: {n_years}年')
print(f'CAGR: {cagr_demo*100:.2f}%')
print(f'简单平均增长率: {avg_growth*100:.2f}%')
print(f'差异: {(cagr_demo - avg_growth)*100:.2f}个百分点')
期初值: 100亿元
期末值: 150亿元
年份数: 4年
CAGR: 10.67%
简单平均增长率: 11.44%
差异: -0.77个百分点

⭐ 增长率统计分析

Listing 6
valid_growth = growth_demo.dropna()
positive_count = (valid_growth > 0).sum()
negative_count = (valid_growth < 0).sum()

print(f'有效年份: {len(valid_growth)}')
print(f'平均增长率: {valid_growth.mean():.2f}%')
print(f'最大增长率: {valid_growth.max():.2f}%')
print(f'最小增长率: {valid_growth.min():.2f}%')
print(f'标准差: {valid_growth.std():.2f}%')
print(f'正增长年份: {positive_count}年 ({positive_count/len(valid_growth)*100:.0f}%)')
print(f'负增长年份: {negative_count}年 ({negative_count/len(valid_growth)*100:.0f}%)')
有效年份: 4
平均增长率: 11.44%
最大增长率: 20.37%
最小增长率: -10.00%
标准差: 14.47%
正增长年份: 3年 (75%)
负增长年份: 1年 (25%)

⭐ 净利润与增长率双轴图

fig, ax1 = plt.subplots(figsize=(10, 6))

# 左轴:营业利润
ax1.set_xlabel('年份', fontsize=12)
ax1.set_ylabel('营业利润(亿元)', fontsize=12, color='steelblue')
ax1.plot(demo_profit.index, demo_profit.values,
         color='steelblue', marker='o', linewidth=2.5, label='营业利润')
ax1.tick_params(axis='y', labelcolor='steelblue')
ax1.grid(True, alpha=0.3, linestyle='--')

# 右轴:增长率
ax2 = ax1.twinx()
ax2.set_ylabel('增长率(%)', fontsize=12, color='coral')
ax2.plot(growth_demo.index, growth_demo.values,
         color='coral', marker='s', linewidth=2.5, label='增长率')
ax2.tick_params(axis='y', labelcolor='coral')
ax2.axhline(y=0, color='gray', linestyle='--', linewidth=1, alpha=0.7)

# 标记负增长区间
ax2.fill_between(growth_demo.index, 0, growth_demo.values,
                 where=(growth_demo.values < 0),
                 color='red', alpha=0.2, label='负增长区间')

plt.title('营业利润与增长率趋势', fontsize=14, pad=15)
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left')
fig.tight_layout()
plt.show()
Figure 1: 营业利润与增长率趋势双轴图

⭐ 双轴图的关键技术

技术点 说明
ax1.twinx() 创建共享x轴的第二个y轴
fill_between() 条件填充,标记负增长区域
annotate() 标注极值点(最高/最低)
get_legend_handles_labels() 合并两个轴的图例

适用场景: 两个变量量纲不同但需对比分析时

⭐ 增长趋势分解:理论基础

时间序列可以分解为:

\[Y_t = T_t + C_t + S_t + \varepsilon_t\]

成分 含义 说明
\(T_t\) 趋势项 (Trend) 长期方向
\(C_t\) 循环项 (Cycle) 多年波动
\(S_t\) 季节项 (Seasonal) 年内波动
\(\varepsilon_t\) 随机项 (Noise) 不可预测成分

⭐ 使用移动平均分解趋势

fig, axes = plt.subplots(2, 1, figsize=(10, 8))

# 子图1:原始增长率 vs 移动平均
growth_ma = growth_demo.rolling(window=3, center=True).mean()
axes[0].plot(growth_demo.index, growth_demo.values,
             marker='o', linewidth=2, label='原始增长率', alpha=0.7)
axes[0].plot(growth_ma.index, growth_ma.values,
             linewidth=3, label='3年移动平均', color='red')
axes[0].axhline(y=0, color='gray', linestyle='--', linewidth=1)
axes[0].set_title('增长率趋势分解', fontsize=14)
axes[0].set_ylabel('增长率(%)')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# 子图2:利润趋势 + 二次拟合线
axes[1].plot(demo_profit.index, demo_profit.values,
             marker='s', linewidth=2.5, color='steelblue')
z = np.polyfit(demo_profit.index, demo_profit.values, 2)
p = np.poly1d(z)
axes[1].plot(demo_profit.index, p(demo_profit.index),
             '--', color='red', linewidth=2, label='趋势线(二次)')
axes[1].set_title('营业利润趋势', fontsize=14)
axes[1].set_xlabel('年份')
axes[1].set_ylabel('营业利润(亿元)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
Figure 2: 增长率趋势分解

⭐ 增长阶段识别

移动平均增长率 判定阶段
> 20% 高速增长期
10% ~ 20% 稳定增长期
0% ~ 10% 缓慢增长期
< 0% 衰退期
  • 均值回归: 极高增长率难以持续
  • 基数效应: 基数小时增长率容易偏高
  • 行业周期: 成长期 → 成熟期 → 衰退期

⭐ 外推预测的基本思路

forecast_years = 3
recent_growth_rate = (growth_demo.dropna().tail(3).mean()) / 100
last_value = demo_profit.iloc[-1]

# 预测未来3年
forecast_values = [last_value * (1 + recent_growth_rate) ** i
                   for i in range(1, forecast_years + 1)]
forecast_index = np.arange(demo_profit.index[-1] + 1,
                           demo_profit.index[-1] + forecast_years + 1)

fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(demo_profit.index, demo_profit.values,
        marker='o', linewidth=2.5, label='历史数据', color='steelblue')
ax.plot(forecast_index, forecast_values,
        marker='s', linewidth=2.5, linestyle='--',
        label='预测数据', color='coral')

# 置信区间 (±20%)
upper = [v * 1.2 for v in forecast_values]
lower = [v * 0.8 for v in forecast_values]
ax.fill_between(forecast_index, lower, upper,
                color='coral', alpha=0.2, label='预测区间(±20%)')

ax.set_title('营业利润趋势外推预测', fontsize=14)
ax.set_xlabel('年份')
ax.set_ylabel('营业利润(亿元)')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Figure 3: 营业利润趋势外推预测

⭐ 外推预测的局限性

  • 假设历史模式延续: 未来可能截然不同
  • 增长率恒定假设: 实际增长率会波动
  • 忽略外部因素: 宏观经济、政策变化、行业竞争
  • ⚠️ 仅供参考,不构成投资建议

⭐ PEG比率:成长股估值利器

由传奇投资者彼得·林奇提出:

\[\text{PEG} = \frac{\text{PE}}{\text{增长率}} = \frac{P/E}{g}\]

PEG 值 估值判断
< 1 被低估
≈ 1 合理估值
> 1 被高估

⭐ PEG比率的优势与局限

优势:

  • 估值增长结合考量
  • 适合比较不同成长性的公司
  • 避免’价值陷阱’

局限:

  • 增长率预测困难
  • 负增长不适用
  • 忽略了增长质量

⭐ PEG情景分析

stock_price = 50   # 假设股价
current_eps = 2    # 假设每股收益
pe_ratio = stock_price / current_eps

scenarios = [5, 10, 15, 20, 25, 30]
peg_data = []
for g in scenarios:
    peg_val = pe_ratio / g
    status = '低估' if peg_val < 1 else ('合理' if peg_val < 1.2 else '高估')
    peg_data.append({'增长率(%)': g, 'PE': pe_ratio, 'PEG': round(peg_val, 2), '判断': status})

pd.DataFrame(peg_data)
Table 1: 不同增长率下的PEG比率
增长率(%) PE PEG 判断
0 5 25.0 5.00 高估
1 10 25.0 2.50 高估
2 15 25.0 1.67 高估
3 20 25.0 1.25 高估
4 25 25.0 1.00 合理
5 30 25.0 0.83 低估

⭐ 增长质量的五大特征

特征 说明
可持续性 来源于核心竞争力,而非一次性收益
盈利性 增长带来利润率提升
现金流 利润增长伴随现金流改善
稳定性 增长率波动较小
内源性 靠自身造血,而非外延并购

⭐ 增长质量综合评估框架

Listing 7
growth_pct = growth_demo.dropna()

# 三个维度的评分
stability = 100 - min(growth_pct.std(), 50) / 50 * 100
consistency = (growth_pct > 0).sum() / len(growth_pct) * 100
momentum = max(min(growth_pct.iloc[-1], 30) / 30 * 100, 0)

# 加权综合得分
total = stability * 0.3 + consistency * 0.4 + momentum * 0.3

grade = ('优秀(A)' if total >= 80 else
         '良好(B)' if total >= 60 else
         '一般(C)' if total >= 40 else '较差(D)')

print(f'稳定性得分: {stability:.1f}/100  (权重30%)')
print(f'持续性得分: {consistency:.1f}/100  (权重40%)')
print(f'动能得分:   {momentum:.1f}/100  (权重30%)')
print(f'综合得分:   {total:.1f}/100')
print(f'增长质量评级: {grade}')
稳定性得分: 71.1/100  (权重30%)
持续性得分: 75.0/100  (权重40%)
动能得分:   51.3/100  (权重30%)
综合得分:   66.7/100
增长质量评级: 良好(B)

⭐ 本章核心知识点回顾

概念 要点
增长率计算 pct_change() 计算简单增长率
CAGR 长期平均增长率,消除短期波动
双轴图 twinx() 同时展示利润与增长率
趋势分解 移动平均 + 多项式拟合
PEG比率 PE / 增长率,评估成长股估值
增长质量 稳定性 + 持续性 + 动能综合评估