上一次我发了博客文章 用 Python 绘制现金流量图
有人在后台里私信问我:
我们是想把csv文件读取入,列表
…
将csv文件读取入列表,不同的是我们的现金流量表包括更多行的信息,包括所得税 折旧等,没办法使用你的方法做图,我们想先计算出年净现金流然后计算,想咨询一下如何实现。
安排。
用到的库
' 导入相应的库 '
import matplotlib.pyplot as plt
import numpy as np
import sys
import os
从 .csv 格式的文件里读取现金流
导入 CSV 文件
原现金流量表如下:
序号 | 项目 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 现金流入 | 8895.00 | 7750.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 11367.66 | |||
1.1 | 营业收入 | 6050.00 | 7750.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | 8590.00 | |||
1.2 | 回收固定资产余值 | 2777.66 | |||||||||||||||
1.3 | 回收流动资金 | 2845.00 | |||||||||||||||
2 | 现金流出 | 1390.00 | 1990.00 | 1190.00 | 5428.55 | 6655.63 | 7244.70 | 7209.02 | 7173.35 | 7137.67 | 7101.99 | 7066.32 | 7030.64 | 6994.96 | 6450.15 | 6450.15 | 8773.08 |
2.1 | 项目资本金 | 1390.00 | 1990.00 | 1190.00 | |||||||||||||
2.2 | 借款本金偿还 | 509.14 | 509.14 | 509.14 | 509.14 | 509.14 | 509.14 | 509.14 | 509.14 | 509.14 | 509.14 | 2845.00 | |||||
2.3 | 借款利息支付 | 758.76 | 711.19 | 663.62 | 616.06 | 568.49 | 520.92 | 473.35 | 425.78 | 378.21 | 330.65 | 283.08 | 283.08 | 283.08 | |||
2.4 | 经营成本 | 3630.00 | 4650.00 | 5155.00 | 5155.00 | 5155.00 | 5155.00 | 5155.00 | 5155.00 | 5155.00 | 5155.00 | 5155.00 | 5155.00 | 5155.00 | |||
2.5 | 营业税金及附加 | 345.00 | 442.00 | 490.00 | 490.00 | 490.00 | 490.00 | 490.00 | 490.00 | 490.00 | 490.00 | 490.00 | 490.00 | 490.00 | |||
2.6 | 所得税 | 185.65 | 343.29 | 426.93 | 438.82 | 450.72 | 462.61 | 474.50 | 486.39 | 498.28 | 510.18 | 522.07 | 522.07 | 0.00 | |||
2.7 | 维持运营费用 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | |||
3 | 净现金流量 | -1390.00 | -1990.00 | -1190.00 | 3466.45 | 1094.37 | 1345.30 | 1380.98 | 1416.65 | 1452.33 | 1488.01 | 1523.68 | 1559.36 | 1595.04 | 2139.85 | 2139.85 | 2594.58 |
Text 表格显示看不清的话可以看下面的图片,在Excel中显示如下:
首先,将数据导入 CSV 表格并转置,转置是为了方便 Python 读取数据。
注意表头尽量使用英文,这样不容易给 Python 造成困扰。
如果一定要使用中文也行,但是我没试过,不知道可能出现什么问题。
data.csv
文件内容如下:
Year,operating_income,residual_value_of_fixed_assets_recovered,working_capital_recovered,project_capital,repayment_of_loan_principal,payment_of_loan_interest,operating_costs,business_taxes_and_surcharges,income_tax,maintain_operating_expenses
1,0,0,0,1390,0,0,0,0,0,0
2,0,0,0,1990,0,0,0,0,0,0
3,0,0,0,1190,0,0,0,0,0,0
4,6050,0,2845,0,509.142,758.761574,3630,345,185.6479034,0
5,7750,0,0,0,509.142,711.1931666,4650,442,343.2900052,0
6,8590,0,0,0,509.142,663.6247592,5155,490,426.9321071,0
7,8590,0,0,0,509.142,616.0563518,5155,490,438.8242089,0
8,8590,0,0,0,509.142,568.4879444,5155,490,450.7163108,0
9,8590,0,0,0,509.142,520.919537,5155,490,462.6084126,0
10,8590,0,0,0,509.142,473.3511296,5155,490,474.5005145,0
11,8590,0,0,0,509.142,425.7827222,5155,490,486.3926163,0
12,8590,0,0,0,509.142,378.2143148,5155,490,498.2847182,0
13,8590,0,0,0,509.142,330.6459074,5155,490,510.17682,0
14,8590,0,0,0,0,283.0775,5155,490,522.0689219,0
15,8590,0,0,0,0,283.0775,5155,490,522.0689219,0
16,8590,2777.65825,0,0,2845,283.0775,5155,490,0,0
读取 CSV 文件
读取 CSV 文件,常用的方法有 panda.csv_read()
和 numpy.loadtxt()
。
这里使用后者,因为前者我尝试过不太行。(原因不明)
参考这篇博文:关于python中numpy.loadtxt()的详细用法
参数 | 作用 |
---|---|
fname | 被读取的文件名(文件的相对地址或者绝对地址) |
dtype | 指定读取后数据的数据类型 |
comments | 跳过文件中指定参数开头的行(即不读取) |
delimiter | 指定读取文件中数据的分割符 |
converters | 对读取的数据进行预处理 |
skiprows | 选择跳过的行数 |
usecols | 指定需要读取的列 |
unpack | 选择是否将数据进行向量输出 |
encoding | 对读取的文件进行预编码 |
显然,我们这里要跳过第一行的表头分别读取每一列。
故有 skiprows=1
和 usecols=[你要读取的列数]
我在这里的示例数据是已经计算好营业税金、所得税的。
但是考虑到可能会需要单独用利率计算营业税的情形,这里作为示例选择把每一列单独作为一个变量读取。
代码如下:
" 从 csv 数据表中读取相应的数据序列 "
# 这里把每一列数据分别导入
# 方便在有必要的情况下
# 针对各个现金流做单独的分析
# 年份
Year = np.loadtxt(open("data.csv","rb"),delimiter=',',
skiprows=1,
usecols=[0])
# 营业收入
operating_income = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[1])
# 回收固定资产余值
residual_value_of_fixed_assets_recovered = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[2])
# 回收流动资金
working_capital_recovered = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[3])
# 项目资本金
project_capital = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[4])
# 借款本金偿还
repayment_of_loan_principal = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[5])
# 借款利息支付
payment_of_loan_interest = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[6])
# 经营成本
operating_costs = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[7])
# 营业税金及附加
business_taxes_and_surcharges = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[8])
# 所得税
income_tax = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[9])
# 维持运营费用
maintain_operating_expenses = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[10])
解决文件路径问题
如果你和我一样使用 Visual Stdio Code 之类的开发环境,可能会遇到执行代码的时候由于终端 shell 不在代码的执行路径下而报错的情形。我在这篇博文里找到了解决方案:
解决的代码如下:
import sys
import os
" 搜索文件的绝对路径 "
# Python 日常抽风,找不到 data.csv 的文件路径
# 用这段代码寻找
# 参考 https://blog.csdn.net/wuzhongqiang/article/details/118332777
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(BASE_DIR))
sys.path.insert(0, os.path.join(BASE_DIR, 'data'))
# 把data所在的绝对路径加入到了搜索路径,这样也可以直接访问dataset.csv文件了
# 这句代码进行切换目录
os.chdir(BASE_DIR)
接下来就可以进行现金流的计算了。
现金流的计算
分别计算现金流入、流出,还有净现金流量。
注意这里有一个格式转换的问题:
通过 numpy.loadtxt()
读取的数据为 numpy.array
类型的,这里利用 .tolist()
方法转化为列表格式。
" 现金流的计算 "
# 现金流入
cash_inflow = operating_income + \
residual_value_of_fixed_assets_recovered + \
working_capital_recovered
CI = cash_inflow.tolist()
# 现金流出
cash_outflow = project_capital + \
repayment_of_loan_principal + \
payment_of_loan_interest + \
operating_costs + \
business_taxes_and_surcharges + \
income_tax
CO = cash_outflow.tolist()
# 净现金流量
net_cash_flow = cash_inflow - cash_outflow
NCF = net_cash_flow.tolist()
这里也可以作税率计算之类的操作。篇幅有限在这里就不演示了。
最后把数据 Append 到我们的二阶列表里面来:
# 用二阶列表保存现金流量的值
A = []
A.append(CI)
A.append(CO)
A.append(NCF)
绘图设置
分别为现金的流入、流出和净现金流量、坐标轴和标题设置颜色。
" 颜色设置 "
arrow_color = [ 'green', 'red', 'black' ]
axis_color = 'black'
title_color = 'black'
因为这一次画的图表是极其复杂的,所以要单独为数字和标题设置字体大小,防止图标元素混杂在一起,看不清。
" 调整字体大小 "
numberFontSize = 8
titleFontSize = 15
上次的文章里面没说清楚的一件事情:这里有一个 distance
变量,用于调整图表元素的离散程度。
" 调整图表元素离散程度 "
distance=1000
如果图标元素太紧凑,可以适当减小 distance
的值。相反,如果图表元素已经超出绘图区看不见了,则增大它的值。
完整代码及绘图效果
其余的代码和上一次的文章里面基本一致。
完整代码如下:
""" 绘制资金流量图 """
" 颜色设置 "
arrow_color = [ 'green', 'red', 'black' ]
axis_color = 'black'
title_color = 'black'
" 调整字体大小 "
numberFontSize = 8
titleFontSize = 15
" 调整图表元素离散程度 "
distance=1000
' 导入相应的库 '
import matplotlib.pyplot as plt
import numpy as np
import sys
import os
" 搜索文件的绝对路径 "
# Python 日常抽风,找不到 data.csv 的文件路径
# 用这段代码寻找
# 参考 https://blog.csdn.net/wuzhongqiang/article/details/118332777
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(BASE_DIR))
sys.path.insert(0, os.path.join(BASE_DIR, 'data'))
# 把data所在的绝对路径加入到了搜索路径,这样也可以直接访问dataset.csv文件了
# 这句代码进行切换目录
os.chdir(BASE_DIR)
" 从 csv 数据表中读取相应的数据序列 "
# 这里把每一列数据分别导入
# 方便在有必要的情况下
# 针对各个现金流做单独的分析
# 年份
Year = np.loadtxt(open("data.csv","rb"),delimiter=',',
skiprows=1,
usecols=[0])
# 营业收入
operating_income = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[1])
# 回收固定资产余值
residual_value_of_fixed_assets_recovered = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[2])
# 回收流动资金
working_capital_recovered = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[3])
# 项目资本金
project_capital = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[4])
# 借款本金偿还
repayment_of_loan_principal = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[5])
# 借款利息支付
payment_of_loan_interest = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[6])
# 经营成本
operating_costs = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[7])
# 营业税金及附加
business_taxes_and_surcharges = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[8])
# 所得税
income_tax = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[9])
# 维持运营费用
maintain_operating_expenses = np.loadtxt(open("data.csv","rb"),
delimiter=',',
skiprows=1,
usecols=[10])
" 现金流的计算 "
# 现金流入
cash_inflow = operating_income + \
residual_value_of_fixed_assets_recovered + \
working_capital_recovered
CI = cash_inflow.tolist()
# 现金流出
cash_outflow = project_capital + \
repayment_of_loan_principal + \
payment_of_loan_interest + \
operating_costs + \
business_taxes_and_surcharges + \
income_tax
CO = cash_outflow.tolist()
# 净现金流量
net_cash_flow = cash_inflow - cash_outflow
NCF = net_cash_flow.tolist()
' 设置绘图标题 '
plt.title("企业的现金流入、现金流出及净的现金流量图", c=title_color, fontsize=titleFontSize)
plt.ylabel("资金(万元)")
# 用二阶列表保存现金流量的值
A = []
A.append(CI)
A.append(CO)
A.append(NCF)
' 根据列表绘制资金流量图 '
# 区分正负值
# 是为了让负值的标签位置偏下不影响观感
for j in range(0,len(A)):
for i in range(0,len(A[j])):
if A[j][i] > 0:
plt.arrow(i, 0, 0, A[j][i]-distance,
fc=arrow_color[j],
ec=arrow_color[j],
shape="full",
head_width=0.1,
head_length=distance,
overhang=0.5)
plt.text(i+len(A)*0.01,
A[j][i]+distance,
str(round(A[j][i],2)),
fontsize=numberFontSize)
elif A[j][i] < 0:
plt.arrow(i, 0, 0, A[j][i]+distance,
fc=arrow_color[j],
ec=arrow_color[j],
shape="full",
head_width=0.1,
head_length=distance,
overhang=0.5)
plt.text(i+len(A)*0.01,
A[j][i]-distance,
str(round(A[j][i],2)),
fontsize=numberFontSize)
# 添加一些标签
plt.text(0, 250000, r'利率 $i_{c} = 10 %$')
' 设置图表中各个元素的特征 '
# 下面这些其实没啥,主要都是美化图表
ax = plt.gca()
# 设置四个坐标轴不可见
ax.spines['top'].set_visible(False) # 设置坐标轴,下同
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(False)
# 把 X 轴及其数据标签挪到图表当中
ax.spines['bottom'].set_position(('data',0))
plt.setp( ax.xaxis.get_majorticklabels(), ha="left" ) # right 表示 X 坐标数据标签向右对齐
for tick in ax.xaxis.get_major_ticks():
tick.label1.set_fontsize(numberFontSize)
plt.arrow(-0.1, 0, len(A[0])+1.2, 0,
fc=axis_color,
ec=axis_color,
shape="full",
head_width=distance*0.5,
head_length=0.3,
overhang=0.5)
# 隐藏 y 坐标
plt.yticks([])
# 设置 X 轴的刻度为1
x_major_locator=plt.MultipleLocator(1) # 把x轴的刻度间隔设置为1,并存在变量里
ax.xaxis.set_major_locator(x_major_locator) # 把x轴的主刻度设置为1的倍数
# 设置图表 X Y 范围,防止绘图区太大或太小
plt.xlim(-0.1, len(A[0])+1.4)
plt.ylim(-15*distance,15*distance)
'添加图例'
line1, = plt.plot(1,1, 'g',
label='现金流入')
line2, = plt.plot(2,2, 'r',
label='现金流出')
plt.legend(handles=[line1, line2],
loc='lower right')
' 调整中文显示 '
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
' 绘图 '
plt.show()
最后绘制的现金流量图效果如下:
完美。