首页 > 编程语言 >用 Python 绘制现金流量图

用 Python 绘制现金流量图

时间:2023-09-27 12:45:00浏览次数:48  
标签:distance plt Python 0.0 现金流量 箭头 ax 绘制

用 Python 绘制现金流量图

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

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

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

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

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

image

显然,常见的绘图库,比如 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
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, '现金流量图')

image

使用示例 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, '资金(万元)')

image

等差序列现金流量图

生成一个等差序列:

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, '资金(万元)')

image

等比序列现金流量图

对于等比序列现金流量图的绘制,可以使用类似下面的循环,可以用循环生成等比序列,并添加到列表 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, '资金(万元)')

image

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
旧设备收入 旧设备成本 旧设备残值 新设备成本 新设备收入 新设备残值
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>

image

标签:distance,plt,Python,0.0,现金流量,箭头,ax,绘制
From: https://www.cnblogs.com/BOXonline1396529/p/17732433.html

相关文章

  • Python 基础知识结构
    一、关键字1、return2、if3、elif4、else5、for6、while二、内置函数1、print()2、max()3、min()4、len()5、range()6、enumerate()4、input()5、type()6、int()三、运算符+-*///=+=-=>>===%三目运算符in成员运算符1、算数运算符+加数字与字符串拼接-......
  • python从摄像头读取数据并在网页上显示
    importcv2fromflaskimportFlask,render_template,Responseapp=Flask(__name__)camera=cv2.VideoCapture(0)defgenerate_frames():whileTrue:success,frame=camera.read()ifnotsuccess:breakelse:......
  • python2.7 pip install pyyaml 安装出现错误
    conda环境python2.7 安装pyyaml:pipinstallpyyaml错误如下: ERROR:Commanderroredoutwithexitstatus1:  command:bin/python2.7/python2.7/site-packages/pip/_vendor/pep517/_in_process.pyget_requires_for_build_wheel/tmp/tmp4If62U    估计是......
  • Python模块之logging
    参考:https://zhuanlan.zhihu.com/p/425678081Pythonlogging模块定了为应用程序和库实现灵活的事件日志记录的函数和类。下面是一个示例,安装时间格式记录日志到文件importloggingdeftest_logging():logging.basicConfig(filename='vector.log',format='%(asctime)s%(m......
  • Python分享之序列的方法
    任何的序列都可以引用其中的元素(item)。下面的内建函数(built-infunction)可用于序列(表,定值表,字符串):#s为一个序列len(s)    返回:序列中包含元素的个数min(s)    返回:序列中最小的元素max(s)    返回:序列中最大的元素all(s)    返回:T......
  • 测试开发想通过python面试环节,必须懂得异常原理
    异常定义异常即是一个事件,该事件会在程序执行过程中发生,影响程序的正常执行。python处理异常的方法 我们先通过一个例子来了解代码中引入异常处理的原因。print('Start')a=10b=0print(a/b)print('End')执行结果Traceback(mostrecentcalllast): File"C:/Users/Kevin/Pycha......
  • Python解释器是什么?常见的Python解释器有哪些?
    学习Python时,大家应该都听说过PythonIDE和Python解释器,也经常有人把它们混为一谈,误认为PythonIDE就是Python解释器。其实并不是,PythonIDE和Python解释器在本质上就有很大的区别,那么PythonIDE和解释器有什么区别?下面小编带着大家一起来学习一下。PythonIDEIDE,全......
  • 一文搞定Python面试必问知识点——列表
    Python3有6种标准类型:(Number(数字)、String(字符串)、Tuple(组),List(列表)、Dictionary(字典)、Set(集合))。其中,列表是Python中最基本也是最常用的数据结构。列表中的每个元素都分配一个数字,即它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。在关于python测试开发的面试中,列表是被问及频......
  • 一文读懂Python中的全局变量局部变量和作用域
    局部变量和全局变量是面试热点通常小白在写代码时,只知道引用变量来应对一些基础的编码问题,当面试官问及局部变量和全局变量的具体细节时,就会一脸懵逼,傻傻分不清楚!其实想要彻底了解局部变量和全局变量的关系,本质是大家需要明白何为作用域!这篇文章会带大家彻底搞懂这三者之的唇齿相依......
  • Python 练习实例5
    目:输入三个整数x,y,z,请把这三个数由小到大输出。程序分析:我们想办法把最小的数放到x上,先将x与y进行比较,如果x>y则将x与y的值进行交换,然后再用x与z进行比较,如果x>z则将x与z的值进行交换,这样能使x最小。程序源代码:实例(Python2.0+)#!/usr/bin/python#-*-coding:UTF-8-*-l=[]......