用 Python 绘制现金流量图

最近在学习工程经济学,经常要绘制现金流量图。希望能用 Python 更方便地绘制现金流量图。但是我在网上找了一圈,发现网上的教程画出来的现金流量图根课本里的不太一样。在网上看到的常见的教程里面告诉你的方法都是直接把现金流量图绘制成柱状图或者折线图的形式,但是学校非要我们把现金流量图画成课本上的箭头状

没办法,只好自己来写一下 Python 的实现了。也不知道这样搞是不是很有意义。

现金流量图是一种反映经济系统资金运动状态的图式,即把经济系统的现金流量绘入一时间坐标图中,表示出各现金流入、流出与相应时间的对应关系。运用现金流量图,就可全面、形象、直观地表达经济系统的资金运动状态。

现金流量图是描述现金流量作为时间函数的图形,它能表示资金在不同时间点流入与流出的情况。它是经济分析的有效工具,其重要有如力学计算中的结构力学图。

我们课本上的现金流量图是这个样子的:(图片来源:MBA 智库 · 百科 :现金流量图

显然,常见的绘图库,比如 MatPlotLib 或者 SeaBorn 里面根本没有提供这类图的现成的实现。

Python 实现

首先,我们先在 Jupyter 中导入相应的库(数据分析御三家)。

' 导入相应的库 '
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

然后来设置一下绘图选项:

plt.rcParams.update( {
"font.sans-serif":'SimHei', # 防止中文乱码
"axes.unicode_minus":False, # 中文负号显示
} )

实现原理

我们画这个现金流量图,本质上就是要画箭头。

MatPlotLib 中提供了画箭头的方法 matplotlib.axes.Axes.arrow()。这个方法可用的参数非常多,常用到的参数如下:

参数 数据类型 默认值 含义
x, y float 绘制箭头的起点
dx, dy float 箭头的终点
fc, ec char 'black' 箭头和箭头杆的颜色
width float 0.001 箭头尾巴的宽度
length_includes_head bool False 计算长度的时候是否包含箭头部分
head_width float or None 3*width 箭头部分的总宽度
head_length float or None 1.5*head_width 箭头部分的长度
shape 'full' 箭头样式
overhang float 0 箭头角后掠的程度

调用这个方法的一个逻辑就是要把箭头杆和箭头尖尖当作两个部分分别加以处理。

具体代码

我们定义下面的函数来绘制现金流箭头。函数传入一个可迭代对象 cf 表示现金流,可以是 list 列表、numpy.array 数组或者 pandas.Series 序列。同时传入一个用于绘图的坐标系 ax

这里设置了一个参数 distance,用来表示绘制的图形的离散尺度。这是因为在实践中发现画图的时候这个箭头的大小、图的范畴很难控制,不是画得很大就是画得很小。于是干脆设置一个变量作为参数来手动控制,如果画的箭头太大就把 distance 改得小一点;如果箭头画得太小就把 distance 改得大一点。 一般情况下,当我们绘制一个特定的图 ax 的时候,会为 ax 设定一个统一的 distance

如果没有给定 distance,则 distance 取序列中元素最大值与序列长度的比值。

在每次循环中,使用ax.arrow函数绘制箭头。箭头起点位置为 (i, 0),(i 表示第 i 年,i 从 0 遍历到 n,n 为总年数);终点位置为 (i, cf[i])cf[i] 是第 i 年的现金流量),箭头颜色为传入的参数 arrow_color,箭头长度为 distance,箭头宽度为 0.1

  • 如果现金流元素大于 0,则使用 ax.text() 函数在箭头上方显示该现金流值。
  • 如果现金流元素小于 0,则使用 ax.text() 函数在箭头下方显示该现金流值
def plot_CashFlow_Arrow(
cf, # CashFlow,一段现金流
distance = None, # 图表离散尺度
arrow_color = 'black', # 箭头颜色
ax = None # 绘图的坐标系
):
cf = np.array(cf).flatten()
if distance == None : # 如果图表元素离散尺度未给定
distance = cf.max() / len(cf) # 就取现金流最大值除以总年份为尺度
for i in range( 0, len(cf) ):
ax.arrow(
i, 0, 0, (cf[i]),
fc = arrow_color, ec = arrow_color,
length_includes_head = True,
head_width = 0.1,
head_length = distance,
overhang = 0.5
)
if cf[i] > 0:
ax.text(
i + len(cf)*0.01,
cf[i] + distance,
str( round(cf[i], 2) )
)
elif cf[i] < 0:
ax.text(
i + len(cf)*0.01,
cf[i] - distance,
str( round(cf[i], 2) )
)
return ax

上述坐标系只是绘制某一个特定的现金流向上、向下的箭头。但与此同时,我们也可以看出这种形式的现金流量图具有以下的特点:

  1. x 轴及其坐标的标签是位于图中央的,没有四个边框
  2. 坐标值的间距均为 1,x 范围从 0 开始,到现金流量年份的最大值多一点点出头

我们编写下面的函数将坐标图的形式转化为我们想要的现金流量图的标准形式:

def set_ax_FlowChart_form(
ax,
length,
distance = None,
axis_color = "black"
):
if distance == None : # 如果图表元素离散尺度未给定
distance = 10 * length # 就取 10 倍年份为 distance
# 设置四个坐标轴不可见
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" )
# left 表示 X 坐标数据标签向左对齐
# 否则箭头会挡住数字
plt.arrow( # 中央 x 轴箭头
-0.1, 0, length + 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 范围,防止绘图区太大或太小
# 0.1, 1.4 和 15 都是反复试出来的
# 因为这样效果好,没什么原因
ax.set_xlim(-0.1, length + 1.4)
ax.set_ylim(-15 * distance, 15 * distance)
return ax

使用示例 1:根据现金流量表绘制现金流量图

假设现在有如下现金流量表:

项目 – t年 0 1 2 3 4 5 6
投资 600
收入 350 350 450 450 450 450
经营成本 200 200 250 250 250 250

希望根据这一现金流量表绘制相应的先进流量图。

首先,我们利用字典读取现金流序列为 pandas.Dataframe,设置投资这一列的值为负值表示支出。数据也可以从 .csv 或者 .xlsx 之类的表格格式的文件中读取。

dict = { # 投资,收入和经营成本
"invs":[ 600, 0, 0, 0, 0, 0 ],
"incs":[ 0, 350, 350, 450, 450, 450 ],
"cost":[ 0, 200, 200, 250, 250, 250 ]
}
df = pd.DataFrame.from_dict(dict, orient='index').T.astype(float)
df[['invs']] = - df[['invs']]
df
.dataframe tbody tr th:only-of-type { vertical-align: middle }
\3c pre>\3c code>.dataframe tbody tr th { vertical-align: top }
.dataframe thead th { text-align: right }

invs incs cost
0 -600.0 0.0 0.0
1 -0.0 350.0 200.0
2 -0.0 350.0 200.0
3 -0.0 450.0 250.0
4 -0.0 450.0 250.0
5 -0.0 450.0 250.0

接下来,我们根据表格中数值的大小估算设置一个 distance

# 图表元素离散程度
# 方便在数值变更的时候调整图表分布
distance = 50

遍历 DataFrame,将每一列作为 Series 传入写好的函数快速绘图:

fig, ax = plt.subplots()
ax = set_ax_FlowChart_form( ax, len(df),
distance = distance)
for columns in df:
plot_CashFlow_Arrow( df[[columns]], ax = ax,
distance = distance,)
ax.set_ylabel("现金(万元)")
ax.set_title("现金流量图")
Text(0.5, 1.0, '现金流量图')

使用示例 2:绘制等额、等差、等比序列现金流量图

等额序列现金流量图

首先生成一个等额序列:

distance=2.5
# 用列表保存现金流量的值
A = []
A.append(-30)
for i in range(0, 7):
A.append(10)
A
[-30, 10, 10, 10, 10, 10, 10, 10]

然后绘图:

fig, ax = plt.subplots()
ax = set_ax_FlowChart_form( ax, len(A), distance = distance )
plot_CashFlow_Arrow( cf = A, distance = distance, ax = ax )
# 画出水平线
x = np.arange(1,8)
y = 0*x + 10
plt.plot(x, y, c='r', ls='--')
plt.title("等额序列现金流量图")
plt.ylabel("资金(万元)")
Text(0, 0.5, '资金(万元)')

等差序列现金流量图

生成一个等差序列:

distance=2.5
# 用列表保存现金流量的值
A = []
A.append(-30)
# 生成差差序列
for i in range(0, 7):
A.append(10 + 2*i)
A
[-30, 10, 12, 14, 16, 18, 20, 22]

同理,绘图:

fig, ax = plt.subplots()
ax = set_ax_FlowChart_form( ax, len(A), distance = distance )
plot_CashFlow_Arrow( cf = A, distance = distance, ax = ax )
# 画出等差线
x = np.arange( 1, 8 )
y = 10 + 2*(x - 1) # 第一年没挣钱,要 -1
plt.plot(x, y, c='b', ls='--')
plt.title("等差序列现金流量图")
plt.ylabel("资金(万元)")
Text(0, 0.5, '资金(万元)')

等比序列现金流量图

对于等比序列现金流量图的绘制,可以使用类似下面的循环,可以用循环生成等比序列,并添加到列表 A 的第一行,同时绘制一条等比曲线。

distance=2.5
# 用列表保存现金流量的值
A = []
A.append(-30)
# 生成等比序列
for i in range(0, 7):
A.append(10 * (1.2) ** i)
A
[-30,
10.0,
12.0,
14.399999999999999,
17.279999999999998,
20.735999999999997,
24.883199999999995,
29.85983999999999]

绘图:

fig, ax = plt.subplots()
ax = set_ax_FlowChart_form( ax, len(A), distance = distance )
plot_CashFlow_Arrow( cf = A, distance = distance, ax = ax )
# 画出等比曲线
x = np.arange(1,8)
y = 10*(1.2**(x-1))
plt.plot(x, y, c='g', ls='--')
plt.title("等比序列现金流量图")
plt.ylabel("资金(万元)")
Text(0, 0.5, '资金(万元)')

3. 结合各种绘图方法绘制更加复杂的现金流量图

假设我们现在存在一个两年后更新设备的现金流方案,我们写出其现金流量,根据这一现金流量进行绘图:

dist = {
"旧设备收入" : [ 0, 80000, 80000, 0 ],
"旧设备成本" : [ -100000, -30000, -30000, 0 ],
"旧设备残值" : [ 0, 0, 30000, 0 ],
"新设备成本" : [ 0, 0, -300000, -50000 ],
"新设备收入" : [ 0, 0, 0, 150000 ],
"新设备残值" : [ 0, 0, 0, 240000 ]
}
A = pd.DataFrame(dist).astype(float)
A
.dataframe tbody tr th:only-of-type { vertical-align: middle }
\3c pre>\3c code>.dataframe tbody tr th { vertical-align: top }
.dataframe thead th { text-align: right }

旧设备收入 旧设备成本 旧设备残值 新设备成本 新设备收入 新设备残值
0 0.0 -100000.0 0.0 0.0 0.0 0.0
1 80000.0 -30000.0 0.0 0.0 0.0 0.0
2 80000.0 -30000.0 30000.0 -300000.0 0.0 0.0
3 0.0 0.0 0.0 -50000.0 150000.0 240000.0

我们在这里设置一个颜色列表,colorlist,用文本形式保存每一列想要的颜色。添加一个循环变量 i,让变量 i 遍历列表的下标,将列表元素作为函数的参数 arrow_color 传入,给每一列着色。

因为我们这里调用的不是一个典型的 MatPlotLib 绘图方法,所以无法自动调整图例。这里我们需要自行地强行设置图例项内容

distance = 20000
colorlist = [ 'green', 'green', 'green', 'red', 'red', 'red' ]
fig, ax = plt.subplots()
ax = set_ax_FlowChart_form( ax, 4, distance = distance )
i = 0
for columns in A:
plot_CashFlow_Arrow(
A[columns],
distance = distance,
arrow_color = colorlist[i],
ax = ax
)
i = i + 1
plt.title("方案三:2年后更新设备的现金流量图")
plt.ylabel("资金(元)")
## 自行设置图例
# plt.plot 返回值为元组
# 需要在 line1 line2 后添加逗号
# 表示这是一个只有一个元素的元组
line1, = plt.plot(1,1, 'g', label='旧设备现金流')
line2, = plt.plot(2,2, 'r', label='新设备现金流')
plt.legend(handles=[line1, line2], loc='lower right')
<matplotlib.legend.Legend at 0x27c66c4aeb0>

用 Python 绘制现金流量图的更多相关文章

  1. Python绘制面积图

    一.Python绘制面积图对应代码如下图所示 import matplotlib.pyplot as plt from pylab import mpl mpl.rcParams['font.sans ...

  2. Python绘制折线图

    一.Python绘制折线图 1.1.Python绘制折线图对应代码如下图所示 import matplotlib.pyplot as pltimport numpy as np from pylab ...

  3. python绘制疫情图

    python中进行图表绘制的库主要有两个:matplotlib 和 pyecharts, 相比较而言: matplotlib中提供了BaseMap可以用于地图的绘制,但是个人觉得其绘制的地图不太美观, ...

  4. python绘制三维图

    作者:桂. 时间:2017-04-27  23:24:55 链接:http://www.cnblogs.com/xingshansi/p/6777945.html 本文仅仅梳理最基本的绘图方法. 一. ...

  5. 如何用 Python 绘制玫瑰图等常见疫情图

    新冠疫情已经持续好几个月了,目前,我国疫情已经基本控制住了,而欧美国家正处于爆发期,我们会看到很多网站都提供了多种疫情统计图,今天我们使用 Python 的 pyecharts 框架来绘制一些比较常见 ...

  6. Python绘制雷达图(俗称六芒星)

    原文链接:https://blog.csdn.net/Just_youHG/article/details/83904618 背景 <Python数据分析与挖掘实战> 案例2–航空公司客户 ...

  7. 使用Python绘制漫步图

    代码如下: import matplotlib.pyplot as plt from random import choice class RandomWalk(): def __init__(sel ...

  8. python绘制动态图

    1.需要注意的问题 解决 MatplotlibDeprecationWarning: Using default event loop until function specific to this ...

  9. 用python绘制趋势图

    import matplotlib.pyplot as plt #plt用于显示图片 import matplotlib.image as mping #mping用于读取图片 import date ...

  10. 【python】pandas & matplotlib 数据处理 绘制曲面图

    Python matplotlib模块,是扩展的MATLAB的一个绘图工具库,它可以绘制各种图形 建议安装 Anaconda后使用 ,集成了很多第三库,基本满足大家的需求,下载地址,对应选择pytho ...

随机推荐

  1. 读书笔记 dotnet 的字符串在内存是如何存放

    本文是读伟民哥翻译的 .NET内存管理宝典 这本书的笔记,我认为读书的过程也需要实践,这样对一知半解的知识也有较为清晰的了解.在阅读到 string 在内存的布局时,我看到 RuntimeHelper ...

  2. WPF dotnet core 的 Blend SDK Behaviors 库

    之前版本是通过安装 Blend SDK 支持 Behaviors 库的,但是这个方法都是通过引用 dll 的方式,不够优雅.在升级到 dotnet core 3.0 的时候就需要使用 WPF 官方团队 ...

  3. Oracle和达梦:获取表是否被锁定

    1.获取表是否被锁定 select "V$SESSIONS".SESS_ID,"V$SESSIONS".SQL_TEXT,"V$SESSIONS&qu ...

  4. centos中普通用户使用sudo报错:centos is not in the sudoers file. This incident will be reported.

    centos中普通用户使用sudo报错:centos is not in the sudoers file. This incident will be reported. su - chmod u+ ...

  5. windows系统桌面壁纸切换的三种csharp办法,兼容win10及旧版,还有一个现成桌面小程序

    我自己用这些代码做的小app如下: 最新版本已经改成了服务的方式,也可以选择性添加系统的右键菜单,并且我自己使用的源码库已经开源到了nuget,大家可以直接拿来做二次开发, 新版的下载地址为:http ...

  6. 04.1 go-admin自动化上线到生产环境 nginx配置上线vue和go

    目录 简介 基于Gin + Vue + Element UI的前后端分离权限管理系统 一. 上线思路 1.1 首先确保项目前后端在本地可以都可以正常跑起来,如果不会可以去看一下作者的视频教程 1.2 ...

  7. 第十届山东省大学生程序设计竞赛题解(A、F、M、C)

    部分代码define了long long,请记得开long long A. Calandar 把年份.月份.单个的天数全都乘以对应的系数转化成单个的天数即可,注意最后的结果有可能是负数,要转化成正数. ...

  8. LLM实战:LLM微调加速神器-Unsloth + LLama3

    1. 背景 五一结束后,本qiang~又投入了LLM的技术海洋中,本期将给大家带来LLM微调神器:Unsloth. 正如Unsloth官方的对外宣贯:Easily finetune & tra ...

  9. 谈谈 JVM 垃圾回收机制

    前言 垃圾回收需要思考三件事情,哪些内存需要回收?什么时候回收?如何回收? 一.哪些内存需要回收 JVM 的内存区域中,程序计数器.虚拟机栈和本地方法栈的生命周期是随线程而生,随线程而灭的.这几个区域 ...

  10. 全球厂商之最,华为17篇论文入选国际数据库顶会ICDE

    本文分享自华为云社区<全球厂商之最,华为GaussDB&GeminiDB,17篇论文入选国际数据库顶会ICDE> ,作者:GaussDB 数据库. 5月13-17日,国际数据库顶级 ...