8  现金流分析与企业价值评估

各位同学,欢迎来到《商业大数据分析与应用》的全新章节。在本章中,我们将深入探讨一个在商业分析和投资决策中至关重要的领域——现金流分析。俗话说,“利润是观点,现金是事实”(Profit is an opinion, cash is a fact)。一家公司可能在账面上看起来盈利丰厚,但如果无法产生健康的现金流,它依然可能陷入困境。

本章我们将学习如何运用Python和Tushare财经数据接口,系统地分析企业的现金流状况。我们将从五个核心指标入手,层层递进,最终能够为一家企业绘制出精准的“现金流画像”,并将其与同行业竞争者进行横向对比。这五个核心武器分别是:

  1. 经营活动产生的现金流量净额 / 净利润:衡量企业利润的“含金量”。
  2. 销售商品、提供劳务收到的现金 / 营业收入:检验企业收入的真实性和回款能力。
  3. 期末现金及现金等价物余额 vs. 有息负债:评估企业的短期偿债能力与财务风险。
  4. 自由现金流:判断企业在维持运营和再投资后,能为股东和债权人创造多少真实价值。
  5. 现金流画像:综合分析经营、投资、筹资三大活动的现金流组合,洞察企业所处的发展阶段和战略意图。

通过本章的学习,大家不仅能掌握这些关键财务指标的计算和解读,更能熟练运用Python进行数据获取、处理、计算和可视化,将理论知识与实践操作紧密结合,为未来的商业决策和价值投资打下坚实的基础。

8.1 经营活动现金流量净额 / 净利润

我们首先来学习第一个关键指标:经营活动产生的现金流量净额与净利润的比率。这个指标是检验企业盈利质量的“试金石”。

8.1.1 理论讲解

定义 8.1 盈利质量 (Quality of Earnings) 盈利质量是指公司财报中利润的真实性、稳定性和可持续性。高质量的盈利应主要由可持续的主营业务驱动,并且有充足的现金流作为支撑。

净利润(Net Income)是利润表(Income Statement)的最终结果,它反映了企业在一定时期内的经营成果。然而,由于会计准则采用的是权责发生制(Accrual Basis),利润的确认并不完全等同于现金的实际流入。例如,一笔销售计入收入并产生利润时,可能形成的是应收账款,现金尚未到手。

而经营活动产生的现金流量净额(Net Cash Flow from Operating Activities, NCFO)来自现金流量表(Cash Flow Statement),它遵循收付实现制(Cash Basis),真实记录了企业在日常经营中实际收到和付出的现金差额。

因此,通过计算 NCFO / 净利润 的比率,我们可以: - 评估利润的“含金量”:比率接近或大于1,通常意味着企业赚到的每一块钱利润,都有实实在在的现金流入作为支撑,盈利质量高。 - 识别潜在风险:如果该比率远小于1,甚至为负,可能意味着企业存在大量的应收账款、存货积压,或者使用了激进的会计政策来“创造”利润,这是一个需要警惕的信号。

8.1.2 案例分析:贵州茅台

接下来,我们将以A股市场的知名企业——贵州茅台(600519.SH)为例,利用Python来计算并分析其2019年至2023年这五年的盈利质量。

8.1.2.1 数据获取与准备

首先,我们需要导入必要的Python库,并初始化Tushare Pro接口。Tushare是一个提供丰富财经数据的Python库,非常适合进行量化分析。

列表 8.1
import pandas as pd
import tushare as ts
import numpy as np
import time
import matplotlib.pyplot as plt

# 设置matplotlib正常显示中文和负号
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['font.sans-serif'] = ['SimHei']

# 输入你的Tushare Pro API Token
# 同学们可以去Tushare官网注册获取自己的Token
pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

接下来,我们定义要分析的时间范围,并使用循环从Tushare获取贵州茅台每年的现金流量表数据,提取我们需要的“经营活动产生的现金流量净额” (n_cashflow_act)。

列表 8.2
# 需要获取的年份
years = ['20191231', '20201231', '20211231', '20221231', '20231231']

# 创建一个空的DataFrame用于存放现金流量数据
df1 = pd.DataFrame()
# 循环获取每年的数据
for i in years:
    df1 = df1.append(pro.cashflow(ts_code='600519.SH', period=i, fields='ts_code,n_cashflow_act,end_date'),ignore_index=True) # 使用参数ignore_index=True可以让构建的新表忽略旧索引,以新顺序构建索引

金融数据API有时可能会返回重复的数据行,这是一个好的编程习惯,在获取数据后进行去重处理。

列表 8.3
df1 = df1.drop_duplicates(ignore_index=True)

同样地,我们从利润表中获取同期的“净利润” (n_income) 数据。

列表 8.4
# 创建一个空的DataFrame用于存放利润表数据
df2 = pd.DataFrame()
for i in ['20191231', '20201231', '20211231', '20221231', '20231231']:
    df2 = df2.append(pro.income(ts_code='600519.SH', period=i, fields='ts_code,n_income,end_date'), ignore_index=True)
# 对获取的利润数据进行去重
df2 = df2.drop_duplicates(ignore_index=True)

现在我们有了两份数据:df1 包含现金流数据,df2 包含净利润数据。我们需要将它们合并到一个DataFrame中,以便进行计算。pd.concat函数可以帮助我们实现这一目标,axis=1表示按列(横向)拼接。

列表 8.5
# 横向拼接df1和df2
df = pd.concat([df1, df2['n_income']], axis=1)

8.1.2.2 计算并展示指标

数据准备就绪后,我们就可以计算核心指标了。我们新建一列ratio1,存放计算结果,并使用round()函数保留两位小数,使结果更易读。

表 8.1: 计算贵州茅台的NCFO/净利润比率
# 计算指标1:“经营活动产生的现金流量净额/净利润”,结果保留2位小数
df['ratio1'] = round(df['n_cashflow_act'] / df['n_income'], 2)
# 显示最终结果
df

表 tbl-ratio1-moutai 表格中,我们可以清晰地看到贵州茅台近五年的数据。n_cashflow_actn_income都是以科学计数法表示的大额数值,而ratio1列则直观地展示了二者的比率。

8.1.2.3 可视化与解读

为了更直观地理解数据趋势,我们将使用matplotlib库进行可视化。首先,我们绘制经营活动现金流量净额与净利润的趋势对比图。

# 计算两个指标的5年均值,用于绘制参考线
df['n_cashflow_act_mean'] = np.mean(df['n_cashflow_act'])
df['n_income_mean'] = np.mean(df['n_income'])

# 绘制折线图
plt.plot(years,df['n_cashflow_act'],label='经营活动现金流量净额')
plt.plot(years,df['n_income'],label='净利润')
plt.plot(years,df['n_cashflow_act_mean'],linestyle='--',label = '经营活动现金流量净额均值')
plt.plot(years,df['n_income_mean'],linestyle='--',label = '净利润均值')

# 添加图例
plt.legend(loc = 'upper left')
# 显示图像
plt.show()
图 8.1

图 fig-ncfo-ni-trend 趋势图中可以发现: - 贵州茅台的净利润(橙色线)呈现出非常稳健的增长态势。 - 经营活动现金流量净额(蓝色线)在绝大多数年份都高于净利润,并且其5年均值线(绿色虚线)也高于净利润的5年均值线(红色虚线)。这充分说明了贵州茅台的利润基本上都是以现金形式实现的,盈利质量极高。 - 值得注意的是,2022年其现金流量净额出现了一个明显的下降,甚至低于当年的净利润。通过查阅公司年报可以发现,这主要是由于当年支付的各项税费大幅增加,以及存放中央银行和同业款项净增加额的现金大幅增加。这可能与贵州茅台旗下财务子公司的金融活动有关,属于特定年份的特殊情况。

接下来,我们直接将ratio1指标绘制成条形图,更直观地观察其每年的波动。

plt.bar(years, df['ratio1'])
plt.show()
图 8.2

图 fig-ratio1-bar 清晰地显示,除了2022年(比率为0.56),其他所有年份的比率都大于1,再次印证了其优秀的盈利质量。

8.1.3 动手实践:分析苏宁易购

案例背景

通过对贵州茅台的分析,我们看到了一个盈利质量极高的范例。现在,请同学们学以致用,对另一家知名零售企业——苏宁易购(股票代码:002024.SZ)进行同样的分析。通过对比两家公司,大家可以更深刻地理解不同商业模式和经营状况对现金流指标的影响。

实验要求

请根据下面的代码模板,补全空缺部分,完成对苏宁易购2019-2023年“经营活动现金流量净额/净利润”指标的计算和可视化。

  1. 要求1: 调用ts.pro_api函数,传入你的Tushare Token字符串以初始化接口。
  2. 要求2: 在循环中,使用df1.append()方法和pro.cashflow()函数获取苏宁易购的经营活动现金流量净额。
  3. 要求3: 使用df2.drop_duplicates()方法并设置ignore_index=True,对获取的净利润数据进行去重和重置索引。
  4. 要求4: 使用pd.concat()函数,将df1df2['n_income']按列(axis=1)合并成一个新的DataFrame df
  5. 要求5: 计算df['n_cashflow_act']df['n_income']的比值,使用round()函数保留两位小数,并将结果赋值给新列df['ratio1']
  6. 要求6: 使用plt.plot()函数,绘制经营活动现金流量净额的5年均值线,注意设置线型为虚线'--'和相应的标签。

代码模板

列表 8.6
import pandas as pd
import tushare as ts
import numpy as np
import time
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']   # 用黑体显示中文
plt.rcParams['axes.unicode_minus']=False     # 正常显示负号

# pro =                              #见要求1

# 获取相关数据,计算指标
# 需要获取的年份
years = ['20191231', '20201231', '20211231', '20221231', '20231231']

# 得到2019-2023经营活动产生的现金流量净额--合并报表
df1 = pd.DataFrame()
for i in years:
  # df1 =                            #见要求2
  pass # 占位符
df1 = df1.drop_duplicates(ignore_index=True)

# 得到2019-2023净利润
df2 = pd.DataFrame()
for i in ['20191231', '20201231', '20211231', '20221231', '20231231']:
  df2 = df2.append(pro.income(ts_code='002024.SZ', period=i, fields='ts_code,n_income,end_date'), ignore_index=True)
# df2 =                              #见要求3

# 横向拼接df1和df2
# df =                               #见要求4

# 计算指标1:“经营活动产生的现金流量净额/净利润”,结果保留2位小数
# df['ratio1']                       #见要求5

# ## 五年指标变化情况可视化
df['n_cashflow_act_mean'] = np.mean(df['n_cashflow_act'])
df['n_income_mean'] = np.mean(df['n_income'])
plt.figure(figsize=(16,8))
plt.plot(years,df['n_cashflow_act'],label='经营活动现金流量净额')
plt.plot(years,df['n_income'],label='净利润')
#                                    #见要求6
plt.plot(years,df['n_income_mean'],linestyle='--',label = '净利润均值')
plt.legend(loc = 'best')
plt.savefig("1.png")
plt.close()

plt.figure(figsize=(12,8))
plt.bar(years, df['ratio1'])
plt.savefig("2.png")
plt.close()
列表 8.7
import pandas as pd
import tushare as ts
import numpy as np
import time
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']   # 用黑体显示中文
plt.rcParams['axes.unicode_minus']=False     # 正常显示负号

pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

# 获取相关数据,计算指标
# 需要获取的年份
years = ['20191231', '20201231', '20211231', '20221231', '20231231']

# 得到2019-2023经营活动产生的现金流量净额--合并报表
df1 = pd.DataFrame()
for i in years:
  df1 = df1.append(pro.cashflow(ts_code='002024.SZ', period=i, fields='ts_code,n_cashflow_act,end_date'),ignore_index=True) # 使用参数ignore_index=True可以让构建的新表忽略旧索引,以新顺序构建索引
df1 = df1.drop_duplicates(ignore_index=True)

# 得到2019-2023净利润
df2 = pd.DataFrame()
for i in ['20191231', '20201231', '20211231', '20221231', '20231231']:
  df2 = df2.append(pro.income(ts_code='002024.SZ', period=i, fields='ts_code,n_income,end_date'), ignore_index=True)
df2 = df2.drop_duplicates(ignore_index=True)

# 横向拼接df1和df2
df = pd.concat([df1, df2['n_income']], axis=1)

# 计算指标1:“经营活动产生的现金流量净额/净利润”,结果保留2位小数
df['ratio1'] = round(df['n_cashflow_act'] / df['n_income'], 2)

# ## 五年指标变化情况可视化
df['n_cashflow_act_mean'] = np.mean(df['n_cashflow_act'])
df['n_income_mean'] = np.mean(df['n_income'])
plt.figure(figsize=(16,8))
plt.plot(years,df['n_cashflow_act'],label='经营活动现金流量净额')
plt.plot(years,df['n_income'],label='净利润')
plt.plot(years,df['n_cashflow_act_mean'],linestyle='--',label = '经营活动现金流量净额均值')
plt.plot(years,df['n_income_mean'],linestyle='--',label = '净利润均值')
plt.legend(loc = 'best')
plt.savefig("1.png")
plt.close()

plt.figure(figsize=(12,8))
plt.bar(years, df['ratio1'])
plt.savefig("2.png")
plt.close()

8.2 销售商品、提供劳务收到的现金 / 营业收入

上一节我们探讨了利润的质量,现在我们把目光投向利润的源头——收入。销售商品、提供劳务收到的现金 / 营业收入 这个比率,是衡量企业营业收入质量的核心指标。

8.2.1 理论讲解

与净利润一样,营业收入(Revenue)在会计上同样基于权责发生制确认。只要商品或服务已经提供,无论现金是否收到,企业就可以确认收入。这就引出了一个问题:账面上的高收入,背后是真的“财源滚滚”,还是堆积如山的“白条”(应收账款)?

这个指标正是要回答这个问题。它直接比较了: - 分子:现金流量表中的“销售商品、提供劳- 务收到的现金” (c_fr_sale_sg),这是企业通过主营业务实实在在收回的现金。 - 分母:利润表中的“营业收入”,这是企业按照会计准则确认的收入。

一个健康的企业,其比率应该接近或大于1。这意味着大部分甚至全部的营业收入都转化为了现金。如果这个比率持续低于1,可能表明企业的回款能力较弱,对下游客户的议价能力不强,或者为了扩大市场份额而采取了过于宽松的信用政策,这些都可能积累坏账风险。

8.2.2 案例分析:贵州茅台

我们继续以贵州茅台为例,看看这家公司的收入质量如何。

8.2.2.1 数据获取与计算

我们采用与上一节类似的方法,分别从现金流量表和利润表中获取所需数据,然后合并计算比率。

表 8.2: 获取并计算贵州茅台的收入质量比率
# 需要获取的年份
years = ['20191231', '20201231', '20211231', '20221231', '20231231']

# 得到2019-2023销售商品、提供劳务收到的现金-合并报表
df3 = pd.DataFrame()
for i in years:
    df3 = df3.append(pro.cashflow(ts_code='600519.SH', period=i, fields='ts_code,c_fr_sale_sg,end_date'),
                     ignore_index=True)
df3 = df3.drop_duplicates(keep='last', ignore_index=True)

# 得到2019-2023营业收入
df4 = pd.DataFrame()
for i in ['20191231', '20201231', '20211231', '20221231', '20231231']:
    df4 = df4.append(pro.income(ts_code='600519.SH', period=i, fields='ts_code,revenue,end_date'), ignore_index=True)
df4 = df4.drop_duplicates(ignore_index=True)

# 合并数据
df_2 = pd.concat([df3, df4['revenue']], axis=1)

# 计算指标2:“销售商品、提供劳务收到现金/营业收入”,结果保留2位小数
df_2['ratio2'] = round(df_2['c_fr_sale_sg'] / df_2['revenue'], 2)
df_2

表 tbl-ratio2-moutai 的结果可以看到,茅台公司每年的ratio2都大于1.1,这说明它收到的现金甚至超过了当期确认的营业收入。这在消费品行业中体现了极强的品牌力和对下游经销商的议价能力,通常意味着“先款后货”的销售模式,是非常健康的信号。

8.2.2.2 可视化与解读

我们同样将这两个核心数据进行可视化对比。

import matplotlib.pyplot as plt
plt.rcParams['axes.unicode_minus'] = False  # 解决负号'-'显示为方块的问题
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签

# 计算均值用于绘制参考线
df_2['c_fr_sale_sg_mean'] = np.mean(df_2['c_fr_sale_sg'])
df_2['revenue_mean'] = np.mean(df_2['revenue'])

# 绘制图形
plt.figure(figsize=(12,8))
plt.plot(years, df_2['c_fr_sale_sg'],label='销售商品、提供劳务收到的现金')
plt.plot(years,df_2['revenue'],label='营业收入')
plt.plot(years,df_2['c_fr_sale_sg_mean'],linestyle='--',label = '销售商品、提供劳务收到的现金均值')
plt.plot(years,df_2['revenue_mean'],linestyle='--',label = '营业收入均值')
plt.legend(loc = 'upper left',fontsize=14)
plt.show()
图 8.3

图 fig-sales-cash-revenue-trend 再次有力地证明了我们的判断。代表“销售收现”的蓝色线始终稳稳地压在代表“营业收入”的橙色线之上,显示出极高的收入质量。

我们再将比率ratio2单独用条形图展示。

plt.figure(figsize=(12,8))
plt.bar(years, df_2['ratio2'])
plt.show()
图 8.4

图 fig-ratio2-bar 可以看出,比率非常稳定,且始终在1.1以上。

8.2.3 动手实践:分析苏宁易购

案例背景

苏宁易购作为中国领先的综合零售企业,业务涵盖线上线下,其收入质量直接关系到其经营业绩的真实性和可持续性。分析苏宁易购的销售收现对收入的覆盖情况,有助于深入了解其经营模式和回款效率。

实验要求

请根据下面的代码模板,补全空缺部分,完成对苏宁易购收入质量的分析。

  1. 要求1: 导入matplotlib.pyplot库,并使用别名plt
  2. 要求2: 在数据准备好后,计算ratio2,即c_fr_sale_sg列除以revenue列,结果保留两位小数。
  3. 要求3: 使用np.mean()函数计算revenue列的平均值,并赋值给新列revenue_mean
  4. 要求4: 使用plt.plot()绘制revenue_mean的虚线图,并添加正确的标签。
  5. 要求5: 使用plt.legend()函数设置图例,要求位置在图的左上角(‘upper right’),字体大小为14。

代码模板

列表 8.8
import pandas as pd
import tushare as ts
import numpy as np
import time
plt.rcParams['font.sans-serif']=['SimHei']   # 用黑体显示中文
plt.rcParams['axes.unicode_minus']=False     # 正常显示负号

#                                                 #见要求1 

pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

# 需要获取的年份
years = ['20191231', '20201231', '20211231', '20221231', '20231231']

# 得到2019-2023销售商品、提供劳务收到的现金-合并报表
df3 = pd.DataFrame()
for i in years:
  df3 = df3.append(pro.cashflow(ts_code='002024.SZ', period=i, fields='ts_code,c_fr_sale_sg,end_date'),
           ignore_index=True)
df3 = df3.drop_duplicates(keep='last', ignore_index=True)

# 得到2019-2023营业收入
df4 = pd.DataFrame()
for i in ['20191231', '20201231', '20211231', '20221231', '20231231']:
  df4 = df4.append(pro.income(ts_code='002024.SZ', period=i, fields='ts_code,revenue,end_date'), ignore_index=True)
df4 = df4.drop_duplicates(ignore_index=True)

df_2 = pd.concat([df3, df4['revenue']], axis=1)

#                                                 #见要求2

df_2['c_fr_sale_sg_mean'] = np.mean(df_2['c_fr_sale_sg'])
#                                                 #见要求3
plt.figure(figsize=(12,8))
plt.plot(years, df_2['c_fr_sale_sg'],label='销售商品、提供劳务收到的现金')
plt.plot(years,df_2['revenue'],label='营业收入')
plt.plot(years,df_2['c_fr_sale_sg_mean'],linestyle='--',label = '销售商品、提供劳务收到的现金均值')
#                                                 #见要求4
#                                                 #见要求5
plt.savefig("1.png")

plt.figure(figsize=(12,8))
plt.bar(years, df_2['ratio2'])
plt.savefig("2.png")
列表 8.9
import pandas as pd
import tushare as ts
import numpy as np
import time
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']   # 用黑体显示中文
plt.rcParams['axes.unicode_minus']=False     # 正常显示负号

pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

# 需要获取的年份
years = ['20191231', '20201231', '20211231', '20221231', '20231231']

# 得到2019-2023销售商品、提供劳务收到的现金-合并报表
df3 = pd.DataFrame()
for i in years:
  df3 = df3.append(pro.cashflow(ts_code='002024.SZ', period=i, fields='ts_code,c_fr_sale_sg,end_date'),
           ignore_index=True)
df3 = df3.drop_duplicates(keep='last', ignore_index=True)

# 得到2019-2023营业收入
df4 = pd.DataFrame()
for i in ['20191231', '20201231', '20211231', '20221231', '20231231']:
  df4 = df4.append(pro.income(ts_code='002024.SZ', period=i, fields='ts_code,revenue,end_date'), ignore_index=True)
df4 = df4.drop_duplicates(ignore_index=True)

df_2 = pd.concat([df3, df4['revenue']], axis=1)

# 计算指标2:“销售商品、提供劳务收到现金/营业收入”,结果保留2位小数
df_2['ratio2'] = round(df_2['c_fr_sale_sg'] / df_2['revenue'], 2)

df_2['c_fr_sale_sg_mean'] = np.mean(df_2['c_fr_sale_sg'])
df_2['revenue_mean'] = np.mean(df_2['revenue'])
plt.figure(figsize=(12,8))
plt.plot(years, df_2['c_fr_sale_sg'],label='销售商品、提供劳务收到的现金')
plt.plot(years,df_2['revenue'],label='营业收入')
plt.plot(years,df_2['c_fr_sale_sg_mean'],linestyle='--',label = '销售商品、提供劳务收到的现金均值')
plt.plot(years,df_2['revenue_mean'],linestyle='--',label = '营业收入均值')
plt.legend(loc = 'upper right',fontsize=14)
plt.savefig("1.png")

plt.figure(figsize=(12,8))
plt.bar(years, df_2['ratio2'])
plt.savefig("2.png")

8.3 期末现金及现金等价物余额 vs. 有息负债

在评估了企业的盈利和收入质量后,我们需要进一步考察其财务的稳健性,即偿债能力和风险水平。一个简单而有力的指标就是直接比较企业账上的“现金”和需要支付利息的“债务”。

8.3.1 理论讲解

期末现金及现金等价物余额(Cash and Cash Equivalents at year end) 是资产负债表或现金流量表中的一个项目,代表企业可以立即动用的资金,是企业抵御风险的第一道防线。

有息负债(Interest-bearing Debt) 是指企业需要为其支付利息的债务,主要包括: - 短期借款 (st_borr) - 长期借款 (lt_borr) - 应付债券 (bond_payable)

如果一家公司的期末现金余额大于其有息负债的总和,我们通常认为它财务状况非常健康,几乎没有偿债压力。反之,如果债务远高于现金,尤其是有息负债,那么企业将面临较大的利息支出压力和偿债风险,在经济下行周期中可能尤为脆弱。

8.3.2 案例分析:贵州茅台

我们来检验一下贵州茅台的财务风险状况。

8.3.2.1 数据获取与计算

首先,我们从现金流量表中获取“期末现金及现金等价物余额”(c_cash_equ_end_period)。然后,从资产负债表(balancesheet)中获取短期借款、长期借款和应付债券的数据。

表 8.3: 获取并计算贵州茅台的现金与有息负债
# 得到2019-2023期末现金及现金等价物余额-合并报表
df5 = pd.DataFrame()
for i in years:
    df5 = df5.append(pro.cashflow(ts_code='600519.SH', period=i, fields='ts_code,c_cash_equ_end_period,end_date'), ignore_index=True)
df5 = df5.drop_duplicates(ignore_index=True)

# 得到2019-2023短期借款,长期借款,应付债券的数据
df6 = pd.DataFrame()
for i in years:
    df6 = df6.append(pro.balancesheet(ts_code='600519.SH', period=i, fields='ts_code,st_borr,lt_borr,bond_payable,end_date'), ignore_index=True)
df6 = df6.drop_duplicates(ignore_index=True)

# 合并数据
df_3 = pd.concat([df5, df6[['st_borr', 'lt_borr', 'bond_payable']]], axis=1)

# 把空值(NaN)替换为0,因为没有借款时Tushare可能返回空值
df_3 = df_3.fillna(0)
# 计算有息负债总和
df_3['sum'] = df_3['st_borr'] + df_3['lt_borr'] + df_3['bond_payable']
# 计算有息负债的均值
df_3['sum_mean'] = np.mean(df_3['sum'])  

df_3

表 tbl-cash-debt-moutai 的结果令人印象深刻:贵州茅台在过去五年中,所有的有息负债项目均为0。这是一家完全没有有息负债经营的公司,财务风险极低。

8.3.2.2 可视化与解读

通过可视化图表可以更清晰地看到这一点。

import matplotlib.pyplot as plt
plt.rcParams['axes.unicode_minus'] = False  # 解决负号'-'显示为方块的问题
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签

# 计算现金余额的均值
df_3['c_cash_equ_end_period_mean'] = np.mean(df_3['c_cash_equ_end_period']) # 通过numpy库的mean()函数求均值

# 绘制图形
plt.plot(years, df_3['c_cash_equ_end_period'],label='期末现金及现金等价物余额')
plt.plot(years,df_3['c_cash_equ_end_period_mean'],linestyle='--',label = '期末现金及现金等价物余额均值')
plt.plot(years, df_3['sum'],label='应付债券+短期借款+长期借款')
plt.plot(years,df_3['sum_mean'],linestyle='--',label = '应付债券+短期借款+长期借款均值')
plt.legend(loc = 'best')
plt.show()
图 8.5

图 fig-cash-debt-trend 图中,代表有息负债的橙色线和红色虚线完全贴合在x轴的0刻度上,而代表现金余额的蓝色线则高高在上。这直观地表明,贵州茅台拥有极为充裕的现金储备,且无需通过借债来发展业务,其强大的盈利能力和现金创造能力是其稳健经营的基石。

8.3.3 动手实践:分析苏宁易购

案例背景

苏宁易购作为家电和零售巨头,其商业模式通常需要大量的资金来支持库存、物流和门店扩张。因此,分析其现金储备与有息负债的关系,对于评估其财务风险和长期发展稳定性至关重要。

实验要求

请根据下面的代码模板,补全空缺部分,完成对苏宁易购财务风险的分析。

  1. 要求1: 在循环中,使用df5.append()pro.cashflow()获取苏宁易购的期末现金及现金等价物余额数据。
  2. 要求2: 使用df_3.fillna(0)将数据框中的所有NaN(缺失值)替换为0。
  3. 要求3: 使用plt.plot()函数,绘制有息负债总额的5年均值线(sum_mean),线型为虚线,并添加正确的图例标签。
  4. 要求4: 使用plt.savefig("1.png")将生成的图表保存为名为”1.png”的图片文件。

代码模板

列表 8.10
import pandas as pd
import tushare as ts
import numpy as np
import time
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']   # 用黑体显示中文

pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

# 需要获取的年份
years = ['20191231', '20201231', '20211231', '20221231', '20231231']
# 得到2019-2023期末现金及现金等价物余额-合并报表
df5 = pd.DataFrame()
for i in years:
  # df5 =                             #见要求1
  pass # 占位符
df5 = df5.drop_duplicates(ignore_index=True)

# 得到2019-2023短期借款,长期借款,应付债券的数据
df6 = pd.DataFrame()
for i in years:
  df6 = df6.append(pro.balancesheet(ts_code='002024.SZ', period=i, fields='ts_code,st_borr,lt_borr,bond_payable,end_date'), ignore_index=True)
df6 = df6.drop_duplicates(ignore_index=True)

df_3 = pd.concat([df5, df6[['st_borr', 'lt_borr', 'bond_payable']]], axis=1)

# 把空值替换为0
# df_3 =                              #见要求2
# 计算总和(短期借款,长期借款,应付债券)
df_3['sum'] = df_3['st_borr'] + df_3['lt_borr'] + df_3['bond_payable']
df_3['sum_mean'] = np.mean(df_3['sum']) # 通过numpy库的mean()函数求sum列的均值
df_3['c_cash_equ_end_period_mean'] = np.mean(df_3['c_cash_equ_end_period'])

plt.plot(years, df_3['c_cash_equ_end_period'],label='期末现金及现金等价物余额')
plt.plot(years,df_3['c_cash_equ_end_period_mean'],linestyle='--',label = '期末现金及现金等价物余额均值')
plt.plot(years, df_3['sum'],label='应付债券+短期借款+长期借款')
#                                    #见要求3
plt.legend(loc = 'best')
#                                    #见要求4
列表 8.11
import pandas as pd
import tushare as ts
import numpy as np
import time
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']   # 用黑体显示中文

pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

# 需要获取的年份
years = ['20191231', '20201231', '20211231', '20221231', '20231231']
# 得到2019-2023期末现金及现金等价物余额-合并报表
df5 = pd.DataFrame()
for i in years:
  df5 = df5.append(pro.cashflow(ts_code='002024.SZ', period=i, fields='ts_code,c_cash_equ_end_period,end_date'), ignore_index=True)

df5 = df5.drop_duplicates(ignore_index=True)

# 得到2019-2023短期借款,长期借款,应付债券的数据
df6 = pd.DataFrame()
for i in years:
  df6 = df6.append(pro.balancesheet(ts_code='002024.SZ', period=i, fields='ts_code,st_borr,lt_borr,bond_payable,end_date'), ignore_index=True)
df6 = df6.drop_duplicates(ignore_index=True)

df_3 = pd.concat([df5, df6[['st_borr', 'lt_borr', 'bond_payable']]], axis=1)

# 把空值替换为0
df_3 = df_3.fillna(0)
# 计算总和(短期借款,长期借款,应付债券)
df_3['sum'] = df_3['st_borr'] + df_3['lt_borr'] + df_3['bond_payable']
df_3['sum_mean'] = np.mean(df_3['sum']) # 通过numpy库的mean()函数求sum列的均值
df_3['c_cash_equ_end_period_mean'] = np.mean(df_3['c_cash_equ_end_period'])

plt.plot(years, df_3['c_cash_equ_end_period'],label='期末现金及现金等价物余额')
plt.plot(years,df_3['c_cash_equ_end_period_mean'],linestyle='--',label = '期末现金及现金等价物余额均值')
plt.plot(years, df_3['sum'],label='应付债券+短期借款+长期借款')
plt.plot(years,df_3['sum_mean'],linestyle='--',label = '应付债券+短期借款+长期借款均值')
plt.legend(loc = 'best')
plt.savefig("1.png")

8.4 自由现金流

自由现金流(Free Cash Flow, FCF) 是公司价值评估中最重要的概念之一,尤其是在使用贴现现金流(DCF)模型时。它代表了企业在满足了所有运营和再投资需求后,能够自由分配给所有资本供应者(包括股东和债权人)的现金。

8.4.1 理论讲解

通俗地讲,自由现金流就是企业在“养活自己”并为“未来发展”投入必要资金后,真正剩下的、可以自由支配的钱。这些钱可以用来: - 偿还债务 - 向股东分红 - 回购股票 - 进行非核心业务的投资或并购

自由现金流的计算公式有多种,这里我们采用一个简化的、但非常实用的公式:

\[ \text{自由现金流 (FCF)} = \text{经营活动现金流净额 (NCFO)} - \text{投资活动产生的现金流出额} \tag{8.1}\]

在Tushare的数据字段中,投资活动产生的现金流出额可以用“购建固定资产、无形资产和其他长期资产所支付的现金”来近似,但一个更全面的指标是“投资活动现金流出小计”(stot_out_inv_act)。因此,我们的计算公式对应为:FCF = n_cashflow_act - stot_out_inv_act

一个持续为正且不断增长的自由现金流是企业健康和具有投资价值的强烈信号。

8.4.2 案例分析:贵州茅台

我们来计算贵州茅台的自由现金流。

8.4.2.1 数据获取与计算

我们需要从现金流量表中同时获取“经营活动现金流净额”(n_cashflow_act)和“投资活动现金流出小计”(stot_out_inv_act)。

表 8.4: 计算贵州茅台的自由现金流 (2019-2023)
# 得到2019-2023期末经营活动现金流量净额、投资活动现金流出
df_fc = pd.DataFrame()
for i in years:
    df_fc = df_fc.append(pro.cashflow(ts_code='600519.SH', period=i, fields='ts_code,n_cashflow_act,stot_out_inv_act,end_date'), ignore_index=True)

# 计算自由现金流(简化公式为:自由现金流 = 经营活动现金流净额 - 投资活动现金流流出额)
df_fc['自由现金流'] = df_fc['n_cashflow_act'] - df_fc['stot_out_inv_act']
df_fc = df_fc.drop_duplicates()
df_fc

表 tbl-fcf-moutai 的计算结果看,贵州茅台每年的自由现金流都为非常可观的正数,说明其强大的经营现金流在覆盖了所有投资需求后仍有大量剩余。

8.4.2.2 可视化与解读

将自由现金流的变化趋势绘制成图表。

import matplotlib.pyplot as plt
plt.rcParams['axes.unicode_minus'] = False  # 解决负号'-'显示为方块的问题
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签

# 计算均值
df_fc['自由现金流_mean'] = np.mean(df_fc['自由现金流'])
# 绘制图形
plt.plot(years, df_fc['自由现金流'],label='自由现金流')
plt.plot(years,df_fc['自由现金流_mean'],linestyle='--',label = '自由现金流均值')
plt.legend(loc = 'upper left')
plt.show()
图 8.6

图 fig-fcf-trend 显示,贵州茅台的自由现金流十分充沛,虽然在2022年有所下降(与我们之前分析的经营现金流下降原因一致),但整体依然保持在极高水平,并呈现上升趋势,表现良好。

8.4.3 动手实践:分析苏宁易购

案例背景

苏宁易购作为重资产的零售企业,其投资活动(如开设新店、建设物流中心)会消耗大量现金。因此,分析其自由现金流是判断其商业模式是否可持续的关键。若自由现金流为正,则说明公司运营状况良好,能够自我造血支持扩张和回报股东。

实验要求

请根据下面的代码模板,补全空缺部分,完成对苏宁易购自由现金流的分析。

  1. 要求1: 使用pd.DataFrame()创建一个名为df_fc的空数据框,用于存储后续获取的数据。
  2. 要求2: 根据 式 eq-fcf 公式,计算df_fc的“自由现金流”列,即用n_cashflow_act列减去stot_out_inv_act列。
  3. 要求3: 使用plt.plot()函数绘制自由现金流的均值线(自由现金流_mean),线型为虚线,并添加正确的标签。

代码模板

列表 8.12
import pandas as pd
import tushare as ts
import numpy as np
import time
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']   # 用黑体显示中文
plt.rcParams['axes.unicode_minus']=False     # 正常显示负号

pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

# 需要获取的年份
years = ['20191231', '20201231', '20211231', '20221231', '20231231']

# 得到2019-2023期末经营活动现金流量净额、投资活动现金流出
#                                       #见要求1
for i in years:
  df_fc = df_fc.append(pro.cashflow(ts_code='002024.SZ', period=i, fields='ts_code,n_cashflow_act,stot_out_inv_act,end_date'), ignore_index=True)

# 计算自由现金流
#                                       #见要求2
df_fc = df_fc.drop_duplicates()

df_fc['自由现金流_mean'] = np.mean(df_fc['自由现金流'])
plt.plot(years, df_fc['自由现金流'],label='自由现金流')
#                                       #见要求3
plt.legend(loc = 'upper left')
plt.savefig("1.png")
列表 8.13
import pandas as pd
import tushare as ts
import numpy as np
import time
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']   # 用黑体显示中文
plt.rcParams['axes.unicode_minus']=False     # 正常显示负号

pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

# 需要获取的年份
years = ['20191231', '20201231', '20211231', '20221231', '20231231']

# 得到2019-2023期末经营活动现金流量净额、投资活动现金流出
df_fc = pd.DataFrame()
for i in years:
  df_fc = df_fc.append(pro.cashflow(ts_code='002024.SZ', period=i, fields='ts_code,n_cashflow_act,stot_out_inv_act,end_date'), ignore_index=True)

# 计算自由现金流(简化公式为:自由现金流 = 经营活动现金流净额 - 投资活动现金流流出额)
df_fc['自由现金流'] = df_fc['n_cashflow_act'] - df_fc['stot_out_inv_act']
df_fc = df_fc.drop_duplicates()

df_fc['自由现金流_mean'] = np.mean(df_fc['自由现金流'])
plt.plot(years, df_fc['自由现金流'],label='自由现金流')
plt.plot(years,df_fc['自由现金流_mean'],linestyle='--',label = '自由现金流均值')
plt.legend(loc = 'upper left')
plt.savefig("1.png")

8.5 现金流画像

通过前面的学习,我们已经掌握了几个关键的现金流指标。现在,我们将进入一个更综合、也更有趣的分析层面——为企业绘制“现金流画像”。

8.5.1 理论讲解

企业的现金流量表主要由三部分构成: 1. 经营活动现金流 (Operating Cash Flow): 企业的核心造血能力。 2. 投资活动现金流 (Investing Cash Flow): 反映企业的扩张或收缩战略。 3. 筹资活动现金流 (Financing Cash Flow): 体现企业与资本市场的互动,如借款、还债、分红、股权融资等。

这三项现金流的净额有正有负,它们的组合(2*2*2=8种)能够像一幅素描一样,勾勒出企业当前所处的发展阶段和经营状态。我们将经营活动现金流作为首要判断依据,因为它是一家企业立身之本。我们用 + 表示现金流为正(流入),- 表示为负(流出),可以得到如下 表 tbl-cashflow-profiles 所示的8种企业类型。

表 8.5: 现金流画像分类表
序号 类型 经营活动现金流 投资活动现金流 筹资活动现金流
1 妖精型 + + +
2 老母鸡型 + + -
3 蛮牛型 + - +
4 奶牛型 + - -
5 骗吃骗喝型 - + +
6 混吃等死型 - + -
7 赌徒型 - - +
8 大出血型 - - -

8种画像解读:

  • 妖精型 (+,+,+): 经营良好,但同时在收回投资(或变卖资产)和大规模融资。这种情况比较少见,需要警惕。可能是为超大规模并购做准备,但也可能是利用上市公司地位圈钱,输送给关联方。
  • 老母鸡型 (+,+,-): 经营良好,不断收回投资(或出售资产),同时还在还债或分红。这通常是业务进入成熟期、不再扩张的稳定型企业,是价值投资者的最爱。
  • 蛮牛型 (+,-,+): 经营良好,同时大规模投资扩张,并且通过融资来支持扩张。这是典型的成长型企业,如同一头向前猛冲的蛮牛。风险与机遇并存,需要仔细判断其投资项目的未来前景。
  • 奶牛型 (+,-,-): 经营良好,用自己赚的钱去投资,同时还能还债或分红。这是最健康、最优质的成长型企业,如同能持续产奶的奶牛。
  • 骗吃骗喝型 (-,+,+): 主业不赚钱,靠变卖资产和外部融资维持生存。需要警惕其持续经营的能力。
  • 混吃等死型 (-,+,-): 主业不赚钱,无法从外部融资,只能靠变卖资产和偿还旧债度日,离困境不远。
  • 赌徒型 (-,-,+): 主业亏钱,却还在“烧钱”投资扩张,完全依赖外部融资续命。这是风险极高的企业类型,一旦融资链断裂,将迅速崩溃。
  • 大出血型 (-,-,-): 所有活动都在净流出现金,企业处于失血状态,非常危险,可能濒临破产。

8.5.2 案例分析:贵州茅台

我们来为贵州茅台绘制2019至2023年的年度现金流画像。

8.5.2.1 数据获取与准备

我们需要从现金流量表中获取经营、投资、筹资三项活动的现金流量净额。

表 8.6: 获取贵州茅台三项活动现金流净额
# 获取经营、投资、筹资活动产生的现金流量净额
years = ['20191231', '20201231', '20211231', '20221231', '20231231']
df_profile = pd.DataFrame()
for i in years:
    df_profile = df_profile.append(pro.cashflow(ts_code='600519.SH', period=i,fields='ts_code,n_cashflow_act,n_cash_flows_fnc_act,n_cashflow_inv_act,end_date'), ignore_index=True)

df_profile = df_profile.drop_duplicates(ignore_index=True)
# 为了方便阅读,我们重命名列
df_profile = df_profile.rename(columns={'n_cashflow_act': '经营活动现金流', 'n_cashflow_inv_act': '投资活动现金流', 'n_cash_flows_fnc_act': '筹资活动现金流'})
df_profile

表 tbl-profile-data-moutai 展示了我们需要的所有原始数据。可以看到,经营活动现金流为正,而投资和筹资活动现金流均为负。

8.5.2.2 画像分析与解读

我们通过编写一个循环和条件判断语句,来自动为每一年份确定其画像类型并给出分析。

列表 8.14
# 判断现金流肖像类型并给出评析
for num in range(len(df_profile)):
    if df_profile['经营活动现金流'][num] > 0:
        if df_profile['投资活动现金流'][num] > 0 and df_profile['筹资活动现金流'][num] > 0:
            print('20' + str(num + 19) + '年度,' + '此公司为妖精型')
            print('此公司或者即将展开大规模的对内对外投资活动,正筹本金;或者借上市公司躯壳获得资金转给关联企业或关联人使用')
            print('建议投资者关注企业是否未来将有大规模投资,如果没有,建议远离。')
        elif df_profile['投资活动现金流'][num] > 0 and df_profile['筹资活动现金流'][num] < 0:
            print('20' + str(num + 19) + '年度,' + '此公司为老母鸡型')
            print('若投资现金不是变卖家当所得,可以初步认定这是一家健康发展的企业,不再继续扩张。')
            print('只要有低市盈率和高股息率,投资者值得持有。')
        elif df_profile['投资活动现金流'][num] < 0 and df_profile['筹资活动现金流'][num] > 0:
            print('20' + str(num + 19) + '年度,' + '此公司为蛮牛型')
            print('此公司急于扩张,企业可能高速增长,亦可能一落千丈。')
            print('投资者需要思考,该企业投资项目的发展前景,以及企业是否会面临资金链断裂的问题。')
        elif df_profile['投资活动现金流'][num] < 0 and df_profile['筹资活动现金流'][num] < 0:
            print('20' + str(num + 19) + '年度,' + '此公司为奶牛型')
            print('企业依靠经营现金流入,实施投资并同时清偿债务或回报股东。')
            print('如果经营现金流入大于投资现金流出和筹资现金流出总和,说明企业是一家优质成长股。')

    else:
        print('20' + str(num + 19) + '年度,' + '目前经营现金流为负数,需要警惕。')

列表 lst-profile-analysis-moutai 的输出结果可以看出,贵州茅台在2019至2023年这五年间,每年都属于“奶牛型”企业。这是一个非常优秀的画像:公司依靠强大的主营业务(经营活动现金流为正)创造了充裕的现金,这些现金不仅足够支撑其发展所需的投资(投资活动现金流为负),还有余力持续回报股东和偿还少量债务(筹资活动现金流为负)。这是一家典型的优质成长型企业。

8.5.3 动手实践:分析苏宁易购

案例背景

苏宁易购作为一家长期处于激烈竞争和快速扩张中的零售企业,其现金流画像可以清晰地反映出它的战略重点和财务状况。通过对其画像的分析,我们可以洞察其发展模式的优势与挑战。

实验要求

请根据下面的代码模板,补全空缺部分,完成对苏宁易购现金流画像的分析。

  1. 要求1: 使用df_profile.drop_duplicates()函数对数据进行去重,并设置ignore_index=True
  2. 要求2: 使用df_profile.rename()函数,并传入一个columns字典,将原始的列名映射为更易读的中文列名。
  3. 要求3: 补全if条件语句,使其能够判断当经营活动现金流为正时,投资活动现金流和筹资活动现金流是否同时为正,以识别“妖精型”企业。

代码模板

列表 8.15
import pandas as pd
import tushare as ts
import numpy as np
import time
import matplotlib.pyplot as plt

pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

# 获取经营、投资、筹资活动产生的现金流量净额
years = ['20191231', '20201231', '20211231', '20221231', '20231231']
df_profile = pd.DataFrame()
for i in years:
  df_profile = df_profile.append(pro.cashflow(ts_code='002024.SZ', period=i, fields='ts_code,n_cashflow_act,n_cash_flows_fnc_act,n_cashflow_inv_act,end_date'), ignore_index=True)

# df_profile =                                                         #见要求1
# df_profile =                                                         #见要求2

# 判断现金流肖像类型并给出评析
for num in range(len(df_profile)):
  if df_profile['经营活动现金流'][num] > 0:
    # if                                                               #见要求3
    #   pass # 占位符
      print('20' + str(num + 19) + '年度,' + '此公司为妖精型')
      print('此公司或者即将展开大规模的对内对外投资活动,正筹本金;或者借上市公司躯壳获得资金转给关联企业或关联人使用')
      print('建议投资者关注企业是否未来将有大规模投资,如果没有,建议远离。')
    elif df_profile['投资活动现金流'][num] > 0 and df_profile['筹资活动现金流'][num] < 0:
      print('20' + str(num + 19) + '年度,' + '此公司为老母鸡型')
      print('若投资现金不是变卖家当所得,可以初步认定这是一家健康发展的企业,不再继续扩张。')
      print('只要有低市盈率和高股息率,投资者值得持有。')
    elif df_profile['投资活动现金流'][num] < 0 and df_profile['筹资活动现金流'][num] > 0:
      print('20' + str(num + 19) + '年度,' + '此公司为蛮牛型')
      print('此公司急于扩张,企业可能高速增长,亦可能一落千丈。')
      print('投资者需要思考,该企业投资项目的发展前景,以及企业是否会面临资金链断裂的问题。')
    elif df_profile['投资活动现金流'][num] < 0 and df_profile['筹资活动现金流'][num] < 0:
      print('20' + str(num + 19) + '年度,' + '此公司为奶牛型')
      print('企业依靠经营现金流入,实施投资并同时清偿债务或回报股东。')
      print('如果经营现金流入大于投资现金流出和筹资现金流出总和,说明企业是一家优质成长股。')

  else:
    print('20' + str(num + 19) + '年度,' + '目前经营现金流为负数,需要警惕。')
列表 8.16
import pandas as pd
import tushare as ts
import numpy as np
import time
import matplotlib.pyplot as plt

pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')

# 获取经营、投资、筹资活动产生的现金流量净额
years = ['20191231', '20201231', '20211231', '20221231', '20231231']
df_profile = pd.DataFrame()
for i in years:
  df_profile = df_profile.append(pro.cashflow(ts_code='002024.SZ', period=i, fields='ts_code,n_cashflow_act,n_cash_flows_fnc_act,n_cashflow_inv_act,end_date'), ignore_index=True)

df_profile = df_profile.drop_duplicates(ignore_index=True)
df_profile = df_profile.rename(columns={'n_cashflow_act': '经营活动现金流', 'n_cashflow_inv_act': '投资活动现金流', 'n_cash_flows_fnc_act': '筹资活动现金流'}) # 改变列名

# 判断现金流肖像类型并给出评析
for num in range(len(df_profile)):
  if df_profile['经营活动现金流'][num] > 0:
    if df_profile['投资活动现金流'][num] > 0 and df_profile['筹资活动现金流'][num] > 0:
      print('20' + str(num + 19) + '年度,' + '此公司为妖精型')
      print('此公司或者即将展开大规模的对内对外投资活动,正筹本金;或者借上市公司躯壳获得资金转给关联企业或关联人使用')
      print('建议投资者关注企业是否未来将有大规模投资,如果没有,建议远离。')
    elif df_profile['投资活动现金流'][num] > 0 and df_profile['筹资活动现金流'][num] < 0:
      print('20' + str(num + 19) + '年度,' + '此公司为老母鸡型')
      print('若投资现金不是变卖家当所得,可以初步认定这是一家健康发展的企业,不再继续扩张。')
      print('只要有低市盈率和高股息率,投资者值得持有。')
    elif df_profile['投资活动现金流'][num] < 0 and df_profile['筹资活动现金流'][num] > 0:
      print('20' + str(num + 19) + '年度,' + '此公司为蛮牛型')
      print('此公司急于扩张,企业可能高速增长,亦可能一落千丈。')
      print('投资者需要思考,该企业投资项目的发展前景,以及企业是否会面临资金链断裂的问题。')
    elif df_profile['投资活动现金流'][num] < 0 and df_profile['筹资活动现金流'][num] < 0:
      print('20' + str(num + 19) + '年度,' + '此公司为奶牛型')
      print('企业依靠经营现金流入,实施投资并同时清偿债务或回报股东。')
      print('如果经营现金流入大于投资现金流出和筹资现金流出总和,说明企业是一家优质成长股。')

  else:
    print('20' + str(num + 19) + '年度,' + '目前经营现金流为负数,需要警惕。')

8.6 行业横向对比分析

对单个企业进行深入的纵向(时间序列)分析固然重要,但若想全面评估其市场地位和竞争力,就必须将其置于整个行业的坐标系中进行横向对比。本节,我们将综合运用前面学到的所有指标,对白酒行业的所有上市公司进行一次全面的横向对比分析。

8.6.1 理论讲解

行业横向对比,也称为“同行分析”(Peer Analysis),其核心目的在于: - 建立基准:了解行业内的平均水平和领先水平是怎样的。 - 识别优势与劣势:通过对比,可以发现目标公司在哪些指标上优于或劣于竞争对手。 - 验证商业模式:同一行业内的公司,其现金流特征往往具有相似性。若一家公司显著偏离行业常规,可能意味着其拥有独特的竞争优势,也可能隐藏着特殊的风险。

在本案例中,我们将获取A股所有“白酒”行业上市公司的最新年度财务数据,并对我们之前学习的五个核心维度进行可视化对比。

8.6.2 动手实践:白酒行业大比拼

案例背景

白酒是中国独特的消费品类,行业内既有贵州茅台这样的巨头,也有众多各具特色的区域性品牌。通过一次全面的横向对比,我们可以清晰地看到行业格局,并发现哪些公司在现金流管理方面表现突出。

实验要求

这是一个综合性实践,请根据下面的代码模板和分步要求,完成整个白酒行业的横向对比分析。

  1. 要求1: 使用pro.stock_basic()函数获取上海证券交易所(exchange='SSE')的所有股票基本信息,存入code_sheet1
  2. 要求2: 使用布尔索引,从合并后的code_sheet中筛选出industry列为'白酒'的所有公司,结果存入code_bj
  3. 要求3: 在计算完所有指标后,使用df.replace()函数将DataFrame中可能存在的None值替换为0,确保后续计算和绘图不会出错。inplace=True参数表示直接在原数据上修改。
  4. 要求4: 使用plt.bar()函数,绘制指标2(销售商品、提供劳务收到现金/营业收入)的行业对比条形图。
  5. 要求5: 在绘制指标3的堆叠条形图后,使用plt.xticks()函数将x轴的刻度标签(公司名称)旋转45度,并设置字体大小为14,以防标签重叠。

代码模板

列表 8.17
import pandas as pd
import tushare as ts
import numpy as np
import time
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']   # 用黑体显示中文
plt.rcParams['axes.unicode_minus']=False     # 正常显示负号

pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')
years = ['20191231', '20201231', '20211231', '20221231', '20231231']

# 首先获取上交所所有正常上市公司的股票代码
#                                                   #见要求1
# 再获取深交所所有正常上市公司的股票代码
code_sheet2 = pro.stock_basic(exchange='SZSE') # 获取深交所股票信息,存为表格
# 合并2张表
code_sheet = pd.concat([code_sheet1, code_sheet2])

# 筛选出行业为“白酒”的企业名单
#                                                   #见要求2

# 获取2023年所有白酒企业的相关数据
df1=pd.DataFrame()
df2=pd.DataFrame()

# 获取现金流量表数据
for i in range(len(code_bj)):
  df1 = df1.append(pro.cashflow(ts_code=code_bj.iloc[i]['ts_code'],period='20231231',fields='ts_code,n_cashflow_act,stot_out_inv_act,c_fr_sale_sg,c_cash_equ_end_period,end_date,n_cashflow_inv_act,n_cash_flows_fnc_act'),ignore_index=True)
df1=df1.drop_duplicates(ignore_index=True)
# 获取利润表数据
for i in range(len(code_bj)):
  df2 = df2.append(pro.income(ts_code=code_bj.iloc[i]['ts_code'],period='20231231',fields='ts_code,n_income,revenue,end_date'),ignore_index=True)
df2=df2.drop_duplicates(ignore_index=True)
#得到2019-2023短期借款,长期借款,应付债券的数据
df6=pd.DataFrame()
for i in years:
  df6 = df6.append(pro.balancesheet(ts_code='600276.SH',period=i,fields='ts_code,st_borr,lt_borr,bond_payable,end_date'),ignore_index=True)
df6=df6.drop_duplicates(ignore_index=True)

# 横向拼接df1和df2
df=pd.concat([df1,df2[['n_income','revenue']]],axis=1)
df=pd.concat([df,df6[['st_borr','lt_borr','bond_payable']]],axis = 1)

# 把公司名称合并到数据表中
df['name'] = code_bj['name'].tolist()
df['ratio1'] = round(df['n_cashflow_act']/df['n_income'],2)
df['ratio2'] = round(df['c_fr_sale_sg']/df['revenue'],2)
df['自由现金流']=df['n_cashflow_act']-df['stot_out_inv_act']
#                                                    #见要求3

df.to_excel('行业横向对比_白酒行业数据.xlsx',index = False)

# 指标1:经营活动产生的现金流量净额/净利润 和 指标2:销售商品、提供劳务收到现金/营业收入
plt.figure(figsize = (16,8))
plt.bar(df['name'], df['ratio1'],label = "指标1:经营活动现金流量净额/净利润")
plt.savefig("1.png")
plt.close()

plt.figure(figsize = (16,8))
#                                                     #见要求4
plt.savefig("2.png")
plt.close()

# 指标3:期末现金及现金等价物余额>有息负债(应付债券+短期借款+长期借款)
plt.figure(figsize = (16,8))
plt.bar(df['name'], df['c_cash_equ_end_period'],label = "期末现金及现金等价物余额")
plt.bar(df['name'], df['st_borr'],label = "短期借款",color = 'red')
plt.bar(df['name'], df['lt_borr'],label = "长期借款",color = 'green')
plt.bar(df['name'], df['bond_payable'],label = "应付债券" , color = 'grey')
#                                                      #见要求5
plt.yticks(fontsize=14)
plt.savefig("3.png")
plt.close()

# 指标4:自由现金流:经营活动现金流净额 - 投资活动现金流流出额
plt.figure(figsize = (20,10))
plt.bar(df['name'], df['自由现金流'],label = "自由现金流")
plt.xticks(rotation=45,fontsize=14)
plt.yticks(fontsize=14)
plt.savefig("4.png")
plt.close()

# 指标5:现金流画像
# 改变列名
df = df.rename(columns={'n_cashflow_act': '经营活动现金流', 'n_cashflow_inv_act': '投资活动现金流', 'n_cash_flows_fnc_act': '筹资活动现金流'})
# 判断现金流肖像类型并给出评析
for comp in range(len(df)):
  if df['经营活动现金流'][comp] > 0:
    if df['投资活动现金流'][comp] > 0 and df['筹资活动现金流'][comp] > 0:
      print(df['name'][comp] + '为妖精型')
    elif df['投资活动现金流'][comp] > 0 and df['筹资活动现金流'][comp] < 0:
      print(df['name'][comp] + '为老母鸡型')
    elif df['投资活动现金流'][comp] < 0 and df['筹资活动现金流'][comp] > 0:
      print(df['name'][comp] + '为蛮牛型')
    elif df['投资活动现金流'][comp] < 0 and df['筹资活动现金流'][comp] < 0:
      print(df['name'][comp] + '为奶牛型')
  else:
    print(df['name'][comp] + '目前经营现金流为负数,需要警惕。')
列表 8.18
import pandas as pd
import tushare as ts
import numpy as np
import time
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']   # 用黑体显示中文
plt.rcParams['axes.unicode_minus']=False     # 正常显示负号

pro = ts.pro_api('ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474')
years = ['20191231', '20201231', '20211231', '20221231', '20231231']

# 首先获取上交所所有正常上市公司的股票代码
code_sheet1 = pro.stock_basic(exchange='SSE') # 获取上交所股票信息,存为表格
# 再获取深交所所有正常上市公司的股票代码
code_sheet2 = pro.stock_basic(exchange='SZSE') # 获取深交所股票信息,存为表格
# 合并2张表
code_sheet = pd.concat([code_sheet1, code_sheet2])

# 筛选出行业为“白酒”的企业名单
code_bj = code_sheet[code_sheet['industry'] == '白酒']

# 获取2023年所有白酒企业的相关数据
df1=pd.DataFrame()
df2=pd.DataFrame()

# 获取现金流量表数据
for i in range(len(code_bj)):
  df1 = df1.append(pro.cashflow(ts_code=code_bj.iloc[i]['ts_code'],period='20231231',fields='ts_code,n_cashflow_act,stot_out_inv_act,c_fr_sale_sg,c_cash_equ_end_period,end_date,n_cashflow_inv_act,n_cash_flows_fnc_act'),ignore_index=True)
df1=df1.drop_duplicates(ignore_index=True)
# 获取利润表数据
for i in range(len(code_bj)):
  df2 = df2.append(pro.income(ts_code=code_bj.iloc[i]['ts_code'],period='20231231',fields='ts_code,n_income,revenue,end_date'),ignore_index=True)
df2=df2.drop_duplicates(ignore_index=True)
#得到2019-2023短期借款,长期借款,应付债券的数据
df6=pd.DataFrame()
for i in years:
  df6 = df6.append(pro.balancesheet(ts_code='600276.SH',period=i,fields='ts_code,st_borr,lt_borr,bond_payable,end_date'),ignore_index=True)
df6=df6.drop_duplicates(ignore_index=True)

# 横向拼接df1和df2
df=pd.concat([df1,df2[['n_income','revenue']]],axis=1)
df=pd.concat([df,df6[['st_borr','lt_borr','bond_payable']]],axis = 1)

# 把公司名称合并到数据表中
df['name'] = code_bj['name'].tolist()
df['ratio1'] = round(df['n_cashflow_act']/df['n_income'],2)
df['ratio2'] = round(df['c_fr_sale_sg']/df['revenue'],2)
df['自由现金流']=df['n_cashflow_act']-df['stot_out_inv_act']
df.replace(to_replace=[None],value=0,inplace=True)

df.to_excel('行业横向对比_白酒行业数据.xlsx',index = False)

# 指标1:经营活动产生的现金流量净额/净利润 和 指标2:销售商品、提供劳务收到现金/营业收入
plt.figure(figsize = (16,8))
plt.bar(df['name'], df['ratio1'],label = "指标1:经营活动现金流量净额/净利润")
plt.savefig("1.png")
plt.close()

plt.figure(figsize = (16,8))
plt.bar(df['name'], df['ratio2'],label = "指标2:销售商品、提供劳务收到现金/营业收入")
plt.savefig("2.png")
plt.close()

# 指标3:期末现金及现金等价物余额>有息负债(应付债券+短期借款+长期借款)
plt.figure(figsize = (16,8))
plt.bar(df['name'], df['c_cash_equ_end_period'],label = "期末现金及现金等价物余额")
plt.bar(df['name'], df['st_borr'],label = "短期借款",color = 'red')
plt.bar(df['name'], df['lt_borr'],label = "长期借款",color = 'green')
plt.bar(df['name'], df['bond_payable'],label = "应付债券" , color = 'grey')
plt.xticks(rotation=45,fontsize=14)
plt.yticks(fontsize=14)
plt.savefig("3.png")
plt.close()

# 指标4:自由现金流:经营活动现金流净额 - 投资活动现金流流出额
plt.figure(figsize = (20,10))
plt.bar(df['name'], df['自由现金流'],label = "自由现金流")
plt.xticks(rotation=45,fontsize=14)
plt.yticks(fontsize=14)
plt.savefig("4.png")
plt.close()

# 指标5:现金流画像
# 改变列名
df = df.rename(columns={'n_cashflow_act': '经营活动现金流', 'n_cashflow_inv_act': '投资活动现金流', 'n_cash_flows_fnc_act': '筹资活动现金流'})
# 判断现金流肖像类型并给出评析
for comp in range(len(df)):
  if df['经营活动现金流'][comp] > 0:
    if df['投资活动现金流'][comp] > 0 and df['筹资活动现金流'][comp] > 0:
      print(df['name'][comp] + '为妖精型')

    elif df['投资活动现金流'][comp] > 0 and df['筹资活动现金流'][comp] < 0:
      print(df['name'][comp] + '为老母鸡型')

    elif df['投资活动现金流'][comp] < 0 and df['筹资活动现金流'][comp] > 0:
      print(df['name'][comp] + '为蛮牛型')

    elif df['投资活动现金流'][comp] < 0 and df['筹资活动现金流'][comp] < 0:
      print(df['name'][comp] + '为奶牛型')
  else:
    print(df['name'][comp] + '目前经营现金流为负数,需要警惕。')