9  杜邦分析法:洞察企业价值创造的核心

各位同学,欢迎来到《商业大数据分析与应用》的新章节。在本章中,我们将一同探讨一个在财务分析领域久负盛名、历久弥新的经典工具——杜邦分析法 (DuPont Analysis)。这个方法由美国杜邦公司在20世纪初首创并成功应用,其强大的之处在于,它没有发明任何新的财务指标,而是巧妙地将企业最核心的财务比率——净资产收益率 (ROE) ——进行层层分解,揭示出驱动企业价值创造的三个核心引擎:盈利能力、运营效率和财务杠杆。

对于商学院的学生而言,掌握杜邦分析法不仅是学习一项财务技能,更是培养一种系统性的商业思维方式。它能够帮助我们超越单一财务数据的局限,从更宏观、更系统的视角审视一家企业,理解其商业模式的本质。在本章的学习中,我们不仅会深入剖析杜邦分析的理论框架,更将利用Python作为工具,对真实的上市公司数据进行分析,亲手实践如何筛选优质企业,并洞察其背后的业绩驱动模式。

9.1 核心指标:净资产收益率 (ROE)

在深入杜邦分析的体系之前,我们必须首先牢固掌握其核心——净资产收益率 (Return on Equity, ROE)。

9.1.1 什么是ROE?为何它如此重要?

净资产收益率(ROE)是“股神”沃伦·巴菲特在选择投资标的时最为推崇的财务指标之一。简单来说,ROE衡量的是公司利用自有资本(即股东投入的钱和历年累积的利润)赚钱的效率。它的计算公式直观地表达了这一思想:

\[ \text{ROE} = \frac{\text{净利润}}{\text{净资产}} \tag{9.1}\]

这个比率告诉我们,股东每投入一单位的资本,公司能为他们带来多少净利润。一个持续保持高ROE的公司,通常意味着它拥有强大的护城河、卓越的管理能力和可持续的盈利模式。

9.1.2 理解“归母净利润”与“归母净资产”

在分析真实的上市公司财报时,我们会发现“净利润”和“净资产”通常会带有“归母”的前缀,即“归属于母公司股东的净利润”和“归属于母公司股东的净资产”。理解这个概念对于精确计算ROE至关重要。

当一家母公司(例如,集团总部)持有其子公司(例如,某个业务分部)的股份不足100%时,就会产生“少数股东”的概念。这些少数股东拥有子公司的一部分权益,因此,子公司的净利润和净资产也有一部分是属于他们的,不能完全归于母公司的股东。

为了让大家更好地理解,我们来看一个生活化的例子:

案例:华小智包子铺与华小美奶茶店

假设我们熟悉的“华小智包子铺”发展壮大,投资并控股了“华小美奶茶店”80%的股份。今年,“华小美奶茶店”实现了100万元的净利润。

在这种情况下,虽然在合并报表上会体现这100万的利润,但其中只有80%(即80万元)是真正归属于“华小智包子铺”的股东的。剩下的20%(20万元)则属于持有奶茶店另外20%股份的少数股东。

因此,在计算“华小智包子铺”的ROE时,分子必须使用这80万元的归母净利润,而分母也应使用与之对应的归母净资产

所以,我们在进行严谨的财务分析时,使用的ROE公式应为:

\[ \text{ROE} = \frac{\text{归母净利润}}{\text{归母净资产}} \tag{9.2}\]

其中,归母净资产即股东权益中扣除了少数股东权益的部分。

9.1.3 ROE的不同计算口径

在实践中,由于净资产是一个存量指标(在特定时间点的值),而净利润是一个流量指标(在一段时间内产生的值),为了更公允地反映ROE,衍生出了多种计算口径。

9.1.3.1 (全面)摊薄ROE

这是最简单直接的计算方式,直接使用期末的净资产作为分母。 \[ \text{摊薄ROE} = \frac{\text{报告期归母净利润}}{\text{期末归母净资产}} \tag{9.3}\]

这种方法简单快捷,适合快速估算,尤其是在计算季度ROE时。例如,第一季度的摊薄ROE就是用第一季度的净利润除以第一季度末的净资产。

9.1.3.2 平均ROE

为了更好地匹配“期间产生的利润”和“期间使用的资产”,平均ROE使用了期初和期末净资产的平均值作为分母,这在一定程度上平滑了资产波动的影响。 \[ \text{平均ROE} = \frac{\text{报告期归母净利润}}{(\text{期初归母净资产} + \text{期末归母净资产}) / 2} \tag{9.4}\]

该指标在计算上比摊薄ROE稍复杂,但因为它包含了更多的动态信息,所以在数据分析和快速测算中非常常用。

9.1.3.3 加权平均ROE

这是最为公允和复杂的一种计算方式,通常出现在上市公司的年报中,由注册会计师审计后得出。它考虑了报告期内因增发、回购、分红等行为导致的净资产变动,并根据这些变动发生的时间点给予不同的权重。其公式较为复杂:

\[ \text{加权平均ROE} = \frac{P}{E_0 + NP/2 + \sum E_i \times M_i/M_0 - \sum E_j \times M_j/M_0} \tag{9.5}\]

其中,\(P\)为报告期利润,\(E_0\)为期初净资产,\(NP\)为报告期净利润,\(E_i\)为新增净资产,\(E_j\)为减少的净资产,\(M_i\)\(M_j\)是变动资产影响的月份数,\(M_0\)是报告期总月份数。对于本科阶段的学习,我们了解其原理即可,日常分析中更多使用平均ROE。

9.1.3.4 扣非平均ROE

在分析企业持续盈利能力时,我们还需要警惕“非经常性损益”对净利润的干扰。非经常性损益是指企业偶然发生的、与主营业务关系不大的收益或损失,例如变卖固定资产、政府补贴、股票投资收益等。这些收益不具备可持续性,可能会“美化”当年的利润数据。

因此,一个更具洞察力的指标是扣非ROE,即从净利润中扣除非经常性损益后再计算ROE。 \[ \text{扣非归母净利润} = \text{归母净利润} - \text{非经常性损益} \tag{9.6}\]

\[ \text{扣非平均ROE} = \frac{\text{扣非归母净利润}}{(\text{期初归母净资产} + \text{期末归母净资产}) / 2} \tag{9.7}\]

案例警示:苏宁易购的“利润魔术”

观察 图 fig-suning-roe 中苏宁易购的数据,我们可以发现一个有趣的现象。在2016至2019年间,其加权平均ROE均为正值,2018年甚至高达16.83%,表面看起来盈利能力不错。然而,其扣非平均ROE却均为负值。

图 9.1: 苏宁易购加权平均ROE与扣非平均ROE对比

这背后的原因正是苏宁易购频繁利用非经常性损益来“修饰”净利润。例如,通过出售线下门店、卖掉持有的阿里巴巴股票、剥离苏宁金服股权等方式,获得了巨额的投资收益,从而掩盖了其主营业务的亏损。这种操作虽然能让短期财报变得好看,但无法持续。最终,当资产“卖无可卖”时,公司的真实经营状况便暴露无遗。这个案例深刻地提醒我们,在分析ROE时,必须关注“扣非”指标,以探究企业盈利的真实质量。

9.2 经典的杜邦三因素分解

掌握了ROE的计算之后,我们便可以进入杜邦分析的核心。杜邦分析法的精髓在于将ROE (式 eq-roe-attributable) 分解为三个关键的财务比率的乘积,如 式 eq-dupont 所示:

\[ \begin{aligned} \text{ROE} &= \frac{\text{净利润}}{\text{股东权益}} \\ &= \left( \frac{\text{净利润}}{\text{营业收入}} \right) \times \left( \frac{\text{营业收入}}{\text{总资产}} \right) \times \left( \frac{\text{总资产}}{\text{股东权益}} \right) \\ &= \text{营业净利率} \times \text{总资产周转率} \times \text{权益乘数} \end{aligned} \tag{9.8}\]

这个公式巧妙地将企业的财务状况与经营成果联系起来:

  1. 营业净利率 (Net Profit Margin): 净利润 / 营业收入。这个比率衡量的是企业的盈利能力。它回答了这样一个问题:“公司每做一块钱的生意,能赚取多少净利润?” 高的净利率通常意味着公司拥有强大的品牌、核心技术或成本控制能力。它主要概括了利润表的信息。

  2. 总资产周转率 (Total Asset Turnover): 营业收入 / 总资产。这个比率衡量的是企业的资产运营效率。它回答了这样一个问题:“公司每投入一块钱的资产,能产生多少营业收入?” 高的总资产周转率表明公司能够高效地利用其资产来创造销售,即“薄利多销”的能力。它巧妙地将利润表(营业收入)和资产负债表(总资产)联系了起来。

  3. 权益乘数 (Equity Multiplier): 总资产 / 股东权益。这个比率衡量的是企业的财务杠杆。由于 总资产 = 负债 + 股东权益,所以权益乘数也可以看作是企业使用了多少倍于自有资本的外部资金(主要是负债)。它回答了这样一个问题:“公司的资产中有多少是‘借来的’?” 较高的权益乘数意味着公司更多地依靠负债来驱动增长,这会放大盈利,但同时也放大了风险。它主要概括了资产负债表的信息。

通过 式 eq-dupont 的分解,我们可以清晰地看到,要提升股东的回报(ROE),一家公司可以通过三种途径努力:提高盈利能力加快资产周转加大财务杠杆。不同的商业模式和行业特征,会侧重于不同的驱动因素。

9.3 实践应用(一):利用杜邦分析筛选优质公司

理论学习的最终目的是为了应用。接下来,我们将运用Python,基于杜邦分析的核心指标ROE,从A股市场中筛选出具有持续盈利能力的优质上市公司。

我们的筛选标准是: - 标准一:连续3年(2020-2022年)的ROE都高于20%。 - 标准二:连续5年(2018-2022年)的ROE都高于15%。

设定多年连续标准是为了排除那些依靠偶然的非经常性损益来美化单年ROE的公司,从而找到真正具备稳定经营实力的企业。

下面的代码将引导我们完成这一任务。我们首先需要从预先准备好的Excel文件 原始数据.xlsx 中加载A股上市公司的财务数据,然后进行数据清洗和杜邦三因素的计算,最后根据我们的标准筛选出符合条件的公司。

请同学们根据代码中的注释和要求,思考并补全代码,完成我们的筛选任务。

列表 9.1
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tushare as ts
pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

# 首先获取上交所所有正常上市公司的股票代码
code_sheet = pro.stock_basic(exchange = 'SSE') # 获取上交所股票信息,存为表格
code_list = code_sheet['ts_code'] # 提取股票代码这一列存为列表code_list备用

# 要求1:使用Pandas读取名为 "原始数据.xlsx" 的文件,并将'end_date'列作为字符串读取
# data = pd.read_excel(...)

# 清除重复数据
data.drop_duplicates(inplace = True)
# 只保留特定年份的年报数据
data = data[data['end_date'].isin(['20171231','20181231','20191231','20201231','20211231','20221231'])]
# 对列索引重命名,方便后续调用
data = data.rename(columns = {'ts_code':'股票代码','end_date':'报告日','revenue':'营业收入','n_income':'净利润','total_assets':'总资产','total_hldr_eqy_inc_min_int':'股东权益'})

# 计算杜邦分析各比率
# 净资产报酬率(ROE)
data['roe'] = data['净利润']/data['股东权益']

# 要求2:计算营业净利率
# data['营业净利率'] = ...

# 总资产周转率
data['总资产周转率'] = data['营业收入']/data['总资产']

# 权益乘数
data['权益乘数'] = data['总资产']/data['股东权益']

# 筛选标准一:选择2020~2022年连续3年ROE > 20% 的公司
code_3year = [] # 新建一个list,存放满足条件的公司代码
# 要求3:首先从总数据data中筛选出报告日为2020-2022年的数据
# data_3 = data[...]

# 对每个上市公司分别判断三年的roe是否都大于20%,且数据不少于3年
for i in range(len(code_list)):
    a = data_3[data_3['股票代码']==code_list[i]]
    if len(a) == 3: # 确保公司有完整的3年数据
        b = 1
        for j in range(3): # 依次检查每年的ROE
            # 要求4:判断第j年的roe是否小于等于0.2
            if # ...
                b = b - 1 # 如果不满足,则将标志b减1
                break
        if b == 1: # 如果循环结束后b仍然为1,说明3年都满足条件
            code_3year.append(code_list[i])
# 筛选出这些公司的基本信息并打印
roe_3 = code_sheet[code_sheet['ts_code'].isin(code_3year)]
print("连续3年roe高于20%的公司信息:\n",roe_3)

# 筛选标准二:选择2018~2022年连续5年ROE > 15% 的公司
code_5year = [] # 新建一个list,存放满足条件的公司代码
# 首先只保留2018~2022年的数据
data_5 = data[data['报告日'].isin(['20181231','20191231','20201231','20211231','20221231'])]

# 对每个上市公司分别判断五年的roe是否都大于15%,且数据不少于5年
for i in range(len(code_list)):
    a = data_5[data_5['股票代码']==code_list[i]]
    if len(a) == 5:
        b = 1
        for j in range(5):
            if a.iloc[j]['roe'] <= 0.15:
                b = b - 1
                break
        if b == 1:
            code_5year.append(code_list[i])
            
# 要求5:从code_sheet中筛选出ts_code在code_5year列表中的公司信息
# roe_5 = code_sheet[...]
print("连续5年roe高于15%的公司信息:\n",roe_5)
列表 9.2
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tushare as ts
pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

# 首先获取上交所所有正常上市公司的股票代码
code_sheet = pro.stock_basic(exchange = 'SSE') # 获取上交所股票信息,存为表格
code_list = code_sheet['ts_code'] # 提取股票代码这一列存为列表code_list备用

data = pd.read_excel('原始数据.xlsx', dtype={'end_date':str})

# 清除重复数据
data.drop_duplicates(inplace = True)
# 只保留年报数据,且只保留2010年12月31日及以后的
data = data[data['end_date'].isin(['20171231','20181231','20191231','20201231','20211231','20221231'])]
# 对列索引重命名
data = data.rename(columns = {'ts_code':'股票代码','end_date':'报告日','revenue':'营业收入','n_income':'净利润','total_assets':'总资产','total_hldr_eqy_inc_min_int':'股东权益'})

# 计算杜邦分析各比率
# 净资产报酬率
data['roe'] = data['净利润']/data['股东权益']

# 营业净利率
data['营业净利率'] = data['净利润']/data['营业收入']

# 总资产周转率
data['总资产周转率'] = data['营业收入']/data['总资产']

# 权益乘数
data['权益乘数'] = data['总资产']/data['股东权益']

# 选择2020~2022年连续3年净资产收益率大于20%的公司
code_3year = [] # 新建一个list,存放连续3年roe>20%的公司
# 首先只保留2020~2022年的数据
data_3 = data[data['报告日'].isin(['20201231','20211231','20221231'])]

# 对每个上市公司分别判断三年的roe是否都大于20%,且数据不少于3年
for i in range(len(code_list)):
    a = data_3[data_3['股票代码']==code_list[i]]
    if len(a) == 3: # 判断数据是否不少于3年
        b = 1
        for j in range(3): # 依次判断每条数据的roe是否小于0.2,一旦有小于0.2的数据则b由1变为0,同时跳出循环
            if a.iloc[j]['roe'] <= 0.2:
                b = b - 1
                break
        if b == 1:
            code_3year.append(code_list[i])
# 连续3年roe高于20%的公司信息
roe_3 = code_sheet[code_sheet['ts_code'].isin(code_3year)]
print("连续3年roe高于20%的公司信息:\n",roe_3)

# 选择2018~2022年连续5年净资产收益率大于15%的公司
code_5year = [] # 新建一个list,存放连续5年roe>15%的公司
# 首先只保留2018~2022年的数据
data_5 = data[data['报告日'].isin(['20181231','20191231','20201231','20211231','20221231'])]

# 对每个上市公司分别判断三年的roe是否都大于15%,且数据不少于5年
for i in range(len(code_list)):
    a = data_5[data_5['股票代码']==code_list[i]]
    if len(a) == 5:
        b = 1
        for j in range(5):
            if a.iloc[j]['roe'] <= 0.15:
                b = b - 1
                break
        if b == 1:
            code_5year.append(code_list[i])
# 连续5年roe高于15%的公司信息
roe_5 = code_sheet[code_sheet['ts_code'].isin(code_5year)]
print("连续5年roe高于15%的公司信息:\n",roe_5)

代码执行后,我们将得到两个列表,分别包含了满足我们两个筛选标准的“绩优股”。这是我们进行下一步分析的基础。

9.4 实践应用(二):识别企业的三种业绩驱动模式

找到了这些“绩优股”之后,一个更深层次的问题是:它们为什么优秀? 它们的成功是由高利润率、高运营效率还是高财务杠杆驱动的?这就是杜邦分析大显身手的地方。

我们将对上一节筛选出的“连续5年ROE高于15%”的公司进行深入分析,通过计算它们5年平均的营业净利率、总资产周转率和权益乘数,来识别它们各自的业绩驱动模式。

  1. 高净利率驱动模式:这类公司的核心竞争力在于其强大的盈利能力,通常拥有品牌、技术或规模优势,能够维持较高的售价或较低的成本。例如,高端消费品、创新药企等。
  2. 高周转率驱动模式:这类公司擅长“薄利多销”,通过高效的供应链管理和快速的库存周转来创造价值。典型的例子是零售业、快消品行业。
  3. 高杠杆率驱动模式:这类公司大量使用外部资金(负债)来扩大经营规模,从而放大股东回报。这种模式在资本密集型且现金流稳定的行业较为常见,如金融、房地产、公用事业等。但需要注意的是,高杠杆也意味着高风险。

现在,让我们通过Python代码来探索这些公司的驱动模式。接下来的代码将计算这些公司5年各项指标的平均值,并按不同指标进行排序,帮助我们直观地识别出不同模式的代表性公司。

列表 9.3
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tushare as ts
pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')
# 要求1:首先获取上交所所有正常上市公司的股票代码
# code_sheet = ...
 
data_5year = pd.read_excel("data_5year.xlsx") # 假设data_5year是上一步筛选出的5年数据
# 要求2:按“股票代码”分组,计算各指标的5年平均值
# ratio_5year = ...

# 把公司名称、行业等基本信息合并到数据表中
a = code_sheet[['ts_code','name','industry']] 
a = a.rename(columns = {'ts_code':'股票代码','name':'公司名称','industry':'行业'})
# 要求3:将公司基本信息表 a 合并到 ratio_5year 中
# ratio_5year = pd.merge(...)           
# 调整列的顺序,方便查看
ratio_5year = ratio_5year[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数','营业收入','净利润','总资产','股东权益']]

# 按平均roe从高到低重新排序,查看ROE最高的10家公司
a = ratio_5year.sort_values(by='roe', ascending=False)
a_10 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].head(10)
print("----------------------------------------------------------------------------------------")
print("ROE最高的10家公司:\n",a_10)
print("----------------------------------------------------------------------------------------")

# 分析高利率驱动模式
# 要求4:将ratio_5year按“营业净利率”降序排序
# a = ratio_5year.sort_values(...)
# 选择平均净利率排行前5的股票
a_head_5 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].head(5)
print("平均营业净利率最高的5家公司:\n",a_head_5)
print("----------------------------------------------------------------------------------------")
# 选择平均净利率排行后5的股票
a_tail_5 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].tail(5)
print("平均营业净利率最低的5家公司:\n",a_tail_5)
print("----------------------------------------------------------------------------------------")

# 分析高周转率驱动模式
a = ratio_5year.sort_values(by='总资产周转率', ascending=False)
# 选择平均周转率排行前5的股票
a_head_5 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].head(5)
print("平均周转率排行前5家公司:\n",a_head_5)
print("----------------------------------------------------------------------------------------")
# 选择平均周转率排行后5的股票
a_tail_5 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].tail(5)
# 要求5:打印平均周转率排行后5的公司信息
# print(...)
print("----------------------------------------------------------------------------------------")

# 分析高杠杆率驱动模式
a = ratio_5year.sort_values(by='权益乘数', ascending=False)
# 选择平均权益乘数排行前5的股票
a_head_5 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].head(5)
print("权益乘数排行前5家公司:\n",a_head_5)
print("----------------------------------------------------------------------------------------")
# 选择平均权益乘数排行后5的股票
a_tail_5 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].tail(5)
print("权益乘数排行后5家公司:\n",a_tail_5)
print("----------------------------------------------------------------------------------------")
列表 9.4
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tushare as ts
pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')
# 首先获取上交所所有正常上市公司的股票代码
code_sheet = pro.stock_basic(exchange = 'SSE') 

data_5year = pd.read_excel("data_5year.xlsx")
ratio_5year = data_5year.groupby("股票代码").mean().reset_index("股票代码")

# 把公司名称合并到数据表中
a = code_sheet[['ts_code','name','industry']] 
a = a.rename(columns = {'ts_code':'股票代码','name':'公司名称','industry':'行业'})
ratio_5year = pd.merge(ratio_5year,a,how = 'left') 
# 把公司名称放到第一列
ratio_5year = ratio_5year[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数','营业收入','净利润','总资产','股东权益']]

# 按roe从高到低重新排序
a = ratio_5year.sort_values(by='roe', ascending=False)
a_10 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].head(10)
print("----------------------------------------------------------------------------------------")
print("ROE最高的10家公司:\n",a_10)
print("----------------------------------------------------------------------------------------")

#高利率驱动
#在上述连续5年ROE超过15%的公司中,分别选择平均营业净利率(5年的平均值)排行前5和后5的公司进行分析
a = ratio_5year.sort_values(by='营业净利率', ascending=False)
# 选择平均净利率排行前5的股票
a_head_5 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].head(5)
print("平均营业净利率最高的5家公司:\n",a_head_5)
print("----------------------------------------------------------------------------------------")
a_tail_5 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].tail(5)
print("平均营业净利率最低的5家公司:\n",a_tail_5)
print("----------------------------------------------------------------------------------------")

#高周转率驱动
#在上述连续5年ROE超过15%的公司中,分别选择平均总资产周转率排行前5和后5的公司进行分析
a = ratio_5year.sort_values(by='总资产周转率', ascending=False)
# 选择平均周转率排行前5的股票
a_head_5 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].head(5)
print("平均周转率排行前5家公司:\n",a_head_5)
print("----------------------------------------------------------------------------------------")
a_tail_5 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].tail(5)
print("平均周转率排行后5家公司:\n",a_tail_5)
print("----------------------------------------------------------------------------------------")

#高杠杆率驱动
#在上述连续5年ROE超过15%的公司中,分别选择权益乘数排行前5和后5的公司进行分析
a = ratio_5year.sort_values(by='权益乘数', ascending=False)
# 选择平均权益乘数排行前5的股票
a_head_5 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].head(5)
print("权益乘数排行前5家公司:\n",a_head_5)
print("----------------------------------------------------------------------------------------")
a_tail_5 = a[['公司名称','股票代码','行业','roe','营业净利率','总资产周转率','权益乘数']].tail(5)
print("权益乘数排行后5家公司:\n",a_tail_5)
print("----------------------------------------------------------------------------------------")

通过分析代码输出的几个表格,我们可以清晰地看到不同公司的特点。例如,在“平均营业净利率最高的5家公司”表格中,我们可能会看到像贵州茅台这样的公司,其净利率极高,但周转率相对较低。而在“平均周转率排行前5家公司”中,则可能出现一些商业贸易或零售企业。这些结果直观地验证了杜邦分析法在揭示企业商业模式上的强大能力。

9.5 杜邦分析的延伸:投入资本回报率 (ROIC)

杜邦分析的核心ROE是一个非常出色的指标,但它主要从股东的角度出发。然而,一家公司的资本不仅仅来自股东,还来自债权人(如银行贷款、发行债券等)。债权人和股东共同为企业提供了运营所需的“弹药”。因此,一个更全面的衡量企业整体盈利能力的指标——投入资本回报率 (Return on Invested Capital, ROIC) 应运而生。

ROIC衡量的是企业使用所有投入的资本(包括股东权益和有息债务)所创造的回报。它的计算公式如下:

\[ \text{ROIC} = \frac{\text{息前税后利润 (EBIAT)}}{\text{投入资本 (IC)}} = \frac{\text{息税前利润 (EBIT)} \times (1 - \text{税率})}{\text{有息债务} + \text{股东权益}} \tag{9.9}\]

我们来解析一下这个公式的各个部分: - 息前税后利润 (EBIAT): 这是公司在不考虑融资结构(即不扣除利息)和税收影响下的核心经营利润。我们可以通过利润表中的数据推算:EBIT = 净利润 + 所得税费用 + 财务费用。 - 投入资本 (IC): 这是所有投资者(股东和债权人)真正投入企业期望获得回报的资金。它等于 股东权益 加上所有 有息债务(如短期借款、长期借款等)。之所以不包含应付账款等经营性负债,是因为它们是企业在经营中无息占用上下游的资金,并非投资者主动投入的。

ROIC能够更真实地反映企业核心业务的“造血”能力,因为它剔除了财务杠杆的影响。一家ROIC远高于其资本成本(WACC)的公司,才是在真正地为所有投资者创造价值。

接下来,我们将以“贵州茅台”和“苏宁易购”为例,计算并比较它们的ROIC,看看这个指标能为我们带来哪些新的洞见。

列表 9.5
import numpy as np
import pandas as pd
import tushare as ts
import warnings;warnings.simplefilter("ignore")
pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

comps = ['贵州茅台','苏宁易购']
# 要求1:定义一个列表变量codes,包含两家公司的股票代码
# codes = [...]
years = [2019,2018,2017,2016,2015]

# 定义一个函数,用于将Tushare API返回的英文列名重命名为中文
def rename_col(data): 
  rename_sheet = pd.read_excel('重命名.xlsx')
  eng = rename_sheet['名称'].tolist()
  chi = rename_sheet['描述'].tolist()
  # 要求2:创建一个字典 re_dict,将英文名映射到中文名
  # re_dict = ...
  data = data.rename(columns = re_dict)      
  return data

for i in range(len(comps)):
  # 读取预先处理好的重构资产负债表数据
  df_balance = pd.read_excel(comps[i]+'_资产负债表重构.xlsx',header=0,index_col=0)
  df_balance = df_balance.iloc[:,0:5]
  # 通过Tushare API获取利润表数据
  df_income = pd.DataFrame()
  for year in years:
    df0 = pro.income(ts_code=codes[i],period=str(year)+'1231',ignore_index=True)[-1:]
    df_income = df_income.append(df0,ignore_index=True)
  df_income = rename_col(df_income)
  df_income.to_excel(comps[i]+'_利润表重构.xlsx',index = False)
   
  # 要求3:计算息税前利润 (EBIT)
  # df_income['ebit']=...
  
  df_income['实际税率']=df_income['所得税费用']/df_income['利润总额']
  df_income = df_income[['报告期','ebit','实际税率']]
  df_income['ebit'] = df_income['ebit']/10000 #单位改为万元   
  df_income = df_income.set_index(["报告期"]) # 把年份作为索引
  df_income = df_income.T #转置
  # 将利润表计算结果与资产负债表拼接
  df = df_balance.append(df_income)
  
  # 要求4:根据 @eq-roic 公式计算 ROIC
  # roic = ...
  
  roic = round(roic*100,2)
  df_roic = pd.DataFrame(roic).rename(columns={0:'ROIC'})
  print(f"{comps[i]}的ROIC为:\n{df_roic.T}")
  print("---------------------------------------------------------------------------------")
列表 9.6
import numpy as np
import pandas as pd
import tushare as ts
import warnings;warnings.simplefilter("ignore")
pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

comps = ['贵州茅台','苏宁易购']
codes = ['600519.SH','002024.SZ']
years = [2019,2018,2017,2016,2015]

def rename_col(data): 
  rename_sheet = pd.read_excel('重命名.xlsx')
  eng = rename_sheet['名称'].tolist()
  chi = rename_sheet['描述'].tolist()
  re_dict = dict(zip(eng,chi))
  data = data.rename(columns = re_dict)
  return data

for i in range(len(comps)):
  # 读取重构后的资产负债表(这里由笔者直接给出,推导过程无需深究))
  df_balance = pd.read_excel(comps[i]+'_资产负债表重构.xlsx',header=0,index_col=0)
  df_balance = df_balance.iloc[:,0:5]
  # 获取利润表数据
  df_income = pd.DataFrame()
  for year in years:
    df0 = pro.income(ts_code=codes[i],period=str(year)+'1231',ignore_index=True)[-1:]
    df_income = df_income.append(df0,ignore_index=True)
  df_income = rename_col(df_income)
  df_income.to_excel(comps[i]+'_利润表重构.xlsx',index = False)
   
  df_income['ebit']=df_income['净利润(含少数股东损益)']+df_income['所得税费用']+df_income['减:财务费用']
  df_income['实际税率']=df_income['所得税费用']/df_income['利润总额']
  df_income = df_income[['报告期','ebit','实际税率']]
  df_income['ebit'] = df_income['ebit']/10000 #单位改为万元   
  df_income = df_income.set_index(["报告期"]) # 把年份作为索引
  df_income = df_income.T #转置
  # 拼接两张表
  df = df_balance.append(df_income)
  # 计算 ROIC
  roic = df.loc['ebit']*(1-df.loc['实际税率'])/(df.loc['短期有息债务']+df.loc['长期有息债务']+df.loc['股东权益合计(含少数股东权益)'])
  roic = round(roic*100,2)
  df_roic = pd.DataFrame(roic).rename(columns={0:'ROIC'})
  print(f"{comps[i]}的ROIC为:\n{df_roic.T}")
  print("---------------------------------------------------------------------------------")

从代码运行结果中,我们可以清晰地看到贵州茅台的ROIC常年维持在非常高的水平,而苏宁易购的ROIC则表现不佳。这进一步印证了,真正优秀的企业,其核心业务本身就具有强大的、不依赖于高杠杆的盈利能力。

9.6 本章小结

本章我们系统学习了杜邦分析法。从其核心指标ROE出发,我们探讨了不同的计算口径及其适用场景,并特别强调了“扣非”的重要性。

通过经典的杜邦三因素分解(式 eq-dupont ),我们将ROE拆解为营业净利率总资产周转率权益乘数,揭示了驱动企业价值创造的三种基本模式。更重要的是,我们利用Python对真实的市场数据进行了两项实践应用:一是筛选出具有持续高ROE的优质公司,二是识别这些公司背后的业绩驱动模式。

最后,我们引入了杜邦分析的有力补充——投入资本回报率(ROIC),它从更全面的视角衡量了企业的整体盈利能力。通过本章的学习,希望同学们不仅掌握了杜邦分析的理论和计算,更能将其作为一种强大的思维框架,用于未来的商业分析和投资决策中。