用 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. [Contract] Solidity 合约发布到测试网 ropsten 的作用

    当我们本地完成了一系列测试以后,接下来就是准备上线了. 关于合约部署可以参考这篇:Solidity 合约使用 truffle 部署到测试网和主网 你可能有一个疑问,在上主网之前,先上测试网的作用是什么 ...

  2. 【人脸识别】OpenCV获取自己的图像

    思路:先获取10000张自己的图像,然后通过CNN神经网络进行学习. 第一步:先获取自己的脸的数据.如何做? 代码如下: import cv2 import os import sys import ...

  3. linux-centos7.6 硬盘挂载

    目录 一 .功能 二.VM中设置硬盘 2.1 系统关机状态下 2.2 添加硬盘 三.系统中挂载硬盘 3.1 查看硬盘信息 3.2 硬盘分区 3.3 格式化硬盘 3.4 临时挂载硬盘 3.4 开机自动挂 ...

  4. git checkout 命令图文详解

    目录 git checkout branchname (切换本地分支) 切换远程分支 放弃修改 git checkout . git checkout – filename git checkout ...

  5. vue安装tinyMCE

    目录 [参考视频] [参考文章] 官网: https://www.tiny.cloud/auth/signup/ 资源下载 tinymce 官方为 vue 项目提供了一个组件tinymce-vue n ...

  6. java8用Stream一行代码实现数据分组统计、排序、最大值、最小值、平均值、总数、合计

    getAverage(): 它返回所有接受值的平均值. getCount(): 它计算所有元素的总数. getMax(): 它返回最大值. getMin(): 它返回最小值. getSum(): 它返 ...

  7. Java面试题:线程池内“闹情绪”的线程,怎么办?

    在Java中,线程池中工作线程出现异常的时候,默认会把异常往外抛,同时这个工作线程会因为异常而销毁,我们需要自己去处理对应的异常,异常处理的方法有几种: 在传递的任务中去处理异常,对于每个提交到线程池 ...

  8. C 语言编程 — 输入/输出与文件操作

    目录 文章目录 目录 前文列表 输入/输出 scanf() 和 printf() getchar() 和 putchar() 文件操作 打开文件 关闭文件 写入文件 读取文件 二进制 I/O 函数 前 ...

  9. go 通过指针修改结构体小写字段的值

    package main import ( "fmt" "unsafe" ) type W struct { b int32 c int64 } func ma ...

  10. OceaBase 分区表创建技巧

    最近遇在干个核心的金融项目,规模很大,客户主要是用oracle数据库,现在需要适配ob,原来在oracle就是分区表的迁来ob以后需要进行改造. oracle默认使用是堆表(ht),而ob使用的是索引 ...