前6章以及其他补充已经整理如下:
作为NumPy用户,我们有时会发现自己在金融计算或信号处理方面有一些特殊的需求。幸运的是,NumPy能满足我们的大部分需求。本章将讲述NumPy中的部分专用函数。
本章涵盖以下内容:
- 排序和搜索;
- 特殊函数;
- 金融函数;
- 窗口函数。
7.1 排序
NumPy提供了多种排序函数,如下所示:
- sort函数返回排序后的数组;
- lexsort函数根据键值的字典序进行排序;
- argsort函数返回输入数组排序后的下标;
- ndarray类的sort方法可对数组进行原地排序;
- msort函数沿着第一个轴排序;
- sort_complex函数对复数按照先实部后虚部的顺序进行排序。
在上面的列表中,argsort和sort函数可用来对NumPy数组类型进行排序。
7.2 动手实践:按字典序排序
NumPy中的lexsort函数返回输入数组按字典序排序后的下标。我们需要给lexsort函数提供排序所依据的键值数组或元组。步骤如下。
(1) 回顾一下第3章中我们使用的AAPL股价数据,现在我们要将这些很久以前的数据用在完全不同的地方。我们将载入收盘价和日期数据。是的,处理日期总是很复杂,我们为其准备了专门的转换函数。
1
2
3
4
5
6
7
import datetime
def datestr2num(s):
return datetime.datetime.strptime (s, "%d-%m-%Y").toordinal()
# 载入数据,AAPL.csv数据可以从https://github.com/sundaygeek/numpy-beginner-guide/blob/master/ch7code/AAPL.csv下载
dates,closes = np.loadtxt("AAPL.csv", delimiter=",", unpack=True, usecols=(1, 6), converters={1: datestr2num}, encoding="utf-8")
注:和之前一样,如果按照书上的代码,出现报错ValueError: could not convert string '28-01-2011' to float64 at row 0, column 2.
,需要在loadtxt函数中加上encoding="utf-8"
。
(2) 使用lexsort函数排序。数据本身已经按照日期排序,不过我们现在优先按照收盘价 排序:
1
2
indices = np.lexsort((dates,closes))
indices
1
print(["%s %s" % (datetime.date.fromordinal(dates[i]), closes[i]) for i in indices] )
此时按照书上的代码,出现了报错:TypeError: integer argument expected, got float
因为dates数组每一项是float64的类型,但是需要整数类型。那么多进行一步转换就可以。
修改如下:
1
print(["%s %s" % (datetime.date.fromordinal(int(dates[i])), closes[i]) for i in indices])
刚才做了些什么 : 我们使用NumPy中的lexsort函数对AAPL的收盘价数据进行了排序。该函数返回了排序后的数组下标。
完整代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
import datetime
import numpy as np
# 这个函数的目的是将一个日期字符串 s 转换为 整数,表示从公元 1 年 1 月 1 日(即公元元年第一天)到指定日期的天数。
# 比如:date(2020,4,14).toordinal(),返回:737529
def datestr2num(s):
return datetime.datetime.strptime (s, "%d-%m-%Y").toordinal()
# 载入数据,AAPL.csv数据可以从https://github.com/sundaygeek/numpy-beginner-guide/blob/master/ch7code/AAPL.csv下载
dates,closes = np.loadtxt("AAPL.csv", delimiter=",", unpack=True, usecols=(1, 6), converters={1: datestr2num}, encoding="utf-8")
indices = np.lexsort((dates,closes))
print(["%s %s" % (datetime.date.fromordinal(int(dates[i])), closes[i]) for i in indices])
补充:lexsort函数的使用方法
numpy.lexsort()
是 NumPy 中的一个函数,用于对多个数组进行排序,且可以根据多个键的优先级进行排序。这个函数可以实现基于多个列(或多个数组)进行排序的功能,类似于 SQL 中的多重排序。
函数签名:
1
numpy.lexsort(keys)
参数:
keys
:一个包含多个数组的序列。每个数组都作为排序的依据,排序将按照提供的数组顺序依次进行。keys
中的每个数组都代表一个排序键。
返回值:
- 返回一个索引数组,该数组表示在多个数组上按顺序排序后的索引。
numpy.lexsort()
的工作原理:
lexsort()
将首先根据数组keys[0]
进行排序,然后在相同的keys[0]
值上,按照数组keys[1]
排序,以此类推。- 这个函数的名字来自于字典的排序(lexicographical sort),即首先按照第一个键排序,若相等则按照第二个键排序,以此类推。
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np
# 定义两个数组
array1 = np.array([3, 1, 2, 4])
array2 = np.array([10, 20, 10, 40])
# 使用 lexsort 进行排序
sorted_indices = np.lexsort((array2, array1))
# 使用返回的索引对原数组排序
sorted_array1 = array1[sorted_indices]
sorted_array2 = array2[sorted_indices]
print("Sorted indices:", sorted_indices)
print("Sorted array1:", sorted_array1)
print("Sorted array2:", sorted_array2)
np.lexsort((array2, array1))
:这是根据 array1
和 array2
进行排序的。排序优先级是先按照 array1
排序,如果有相等的元素,则根据 array2
排序。
勇敢出发:尝试不同的排序次序
我们按照收盘价和日期的顺序进行了排序。请尝试不同的排序次序。使用我们在上一章中学习的random模块生成随机数并用lexsort对它们进行排序。
参考:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np
# 生成两个随机数组,每个数组包含10个随机数
array1 = np.random.random(10)
array2 = np.random.random(10)
# 打印生成的随机数
print("Array1:", array1)
print("Array2:", array2)
# 使用 np.lexsort 根据 array1 和 array2 进行排序
# 先按 array1 排序,如果 array1 中的元素相等,则按 array2 排序
indices = np.lexsort((array2, array1))
# 按照排序后的索引重新排列 array1 和 array2
sorted_array1 = array1[indices]
sorted_array2 = array2[indices]
print("\nSorted Array1:", sorted_array1)
print("Sorted Array2:", sorted_array2)
7.3 复数
复数包含实数部分和虚数部分。如同在前面的章节中提到的,NumPy中有专门的复数类型,使用两个浮点数来表示复数。
这些复数可以使用NumPy的sort_complex
函数进行排序。该函数按照先实部后虚部的顺序排序。
补充1:np.random.seed
函数的使用方法
注:在计算机中,虽然我们称之为“随机数”,但其实这些数是伪随机的,即它们是通过某种算法生成的。
np.random.seed()
就是设置这个伪随机数生成器的起始状态(种子)。如果每次用相同的种子,生成的伪随机数序列就会相同。随机数生成算法(如线性同余法等)通常依赖于一个初始值(种子)来启动。不同的种子会导致随机数序列的不同。种子相同,就会产生相同的序列。因此,设置种子是控制随机数生成的关键。
np.random.seed()
是 NumPy 中用于设置随机数生成器的种子值的函数。它的主要作用是确保每次生成的随机数序列都是一样的。通过设置相同的种子值,可以在不同的程序运行或者不同的环境中生成相同的随机数序列,这对于调试、实验复现等非常重要。
函数签名:
1
numpy.random.seed(seed=None)
参数:seed
:整数或 None
,默认为 None
。
- 如果
seed
是 整数(例如 42),它指定了随机数生成器的种子值。 - 如果
seed
是None
,那么 NumPy 会使用系统时间或其他熵源来生成一个种子值,确保每次运行时生成不同的随机数序列。- 熵源:提供随机性(即熵)的来源
- 注意:如果传入相同的种子,每次生成的随机数序列都会相同。传入不同的种子,则生成不同的随机数序列。
返回值:np.random.seed()
是一个 无返回值 的函数。它只会设置随机数生成器的种子,不返回任何内容。
看一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np
# 设置种子为42
np.random.seed(42)
# 生成5个随机数
print(np.random.rand(5)) # 每次运行结果都相同
# 重复设置相同的种子,生成相同的随机数
np.random.seed(42)
print(np.random.rand(5)) # 结果与上次相同
补充2:numpy.sort_complex()
的使用方法:
numpy.sort_complex()
不是 NumPy 中的标准函数,实际上,应该是指 numpy.sort()
在处理 复数数组 时的行为。在 NumPy 中,复数数组会按照 复数的模(绝对值)进行排序。复数的模是通过以下公式计算的:\(\vert z \vert = \sqrt{a^2 +b^2}\)
其中,复数\(z = a+ b i\),\(a\)是实部,\(b\)是虚部。
如果两个复数的模相同,则排序会根据它们的实部和虚部来区分。
函数签名:
1
numpy.sort(a, axis=-1, kind='quicksort', order=None)
a:输入的数组。
axis:指定排序的轴,默认值为 -1
,即对最后一个轴进行排序。
kind:排序算法,可以是 'quicksort'
、'mergesort'
、'heapsort'
或 'stable'
,默认为 'quicksort'
。
order:当数组是结构化数组时,指定排序的字段。对于普通数组忽略。
7.4 动手实践:对复数进行排序
我们将创建一个复数数组并进行排序,步骤如下。
(1) 生成5个随机数作为实部,5个随机数作为虚部。设置随机数种子为42:
1
2
3
4
5
# 设置种子,保证随机数序列一致
np.random.seed(42)
complex_numbers = np.random.random(5) + 1j * np.random.random(5)
print("Complex numbers\n", complex_numbers)
(2) 调用sort_complex
函数对上面生成的复数进行排序:
1
2
# 排序
print("Sorted\n", np.sort_complex(complex_numbers))
刚才做了些什么 : 我们生成了随机的复数并使用sort_complex
函数对它们进行了排序。
完整代码如下:
1
2
3
4
5
6
7
8
9
import numpy as np
# 设置种子,生成随机数5个复数
np.random.seed(42)
complexNums = np.random.random(5) + 1j * np.random.random(5)
print("复数:", complexNums)
# 排序
print("排序后(按照模):", np.sort_complex(complexNums))
突击测验:生成随机数
问题1 以下哪一个NumPy模块可以生成随机数?
(1) randnum (2) random (3) randomutil (4) rand
答案给的是(2),但是,其实(2)和(4)几乎差不多。
7.5 搜索
NumPy中有多个函数可以在数组中进行搜索,如下所示。
argmax函数返回数组中最大值对应的下标。
1
2
n = np.array([2, 4, 8])
np.argmax(n)
nanargmax函数提供相同的功能,但忽略NaN值。
1
2
n = np.array([10, np.nan, 8])
np.nanargmax(n)
argmin和nanargmin函数的功能类似,只不过换成了最小值。
argwhere函数根据条件搜索非零的元素,并分组返回对应的下标。
1
2
n = np.array([10, 4, 8])
np.argwhere(n > 4)
searchsorted函数可以为指定的插入值寻找维持数组排序的索引位置。该函数使用二分搜索算法,计算复杂度为O(log(n))。我们随后将具体学习这个函数。
补充:searchsorted函数的使用
numpy.searchsorted()
是一个用于查找数组中元素位置的函数。它的作用是返回一个索引数组,该数组表示每个元素应该插入到给定的排序数组中的位置,保持数组的顺序。
函数签名:
1
numpy.searchsorted(sorted_array, values, side='left', sorter=None)
参数:
-
sorted_array
: 必须是一个已经排序的数组。这个数组是你要在其中查找插入位置的参考数组。 -
values
: 可以是一个标量(单个元素)或者数组。如果是数组,searchsorted()
会对每个元素查找插入位置。 -
side
: 可选,字符串,默认值是'left'
。它控制插入位置的选择方式:-
'left'
:返回一个位置,所有相等的元素都插入到该位置的左边(即插入到相等元素的前面)。 -
'right'
:返回一个位置,所有相等的元素都插入到该位置的右边(即插入到相等元素的后面)。
-
-
sorter
: 可选,整数数组。如果提供了sorter
,它是一个索引数组,指示如何对sorted_array
进行排序。这个参数可以提高效率,尤其当你已经对sorted_array
做过排序并且保存了排序的索引时。
返回值:返回一个整数数组,表示每个 values
中的元素应该插入到 sorted_array
中的索引位置。
例子1:
1
2
3
4
5
6
7
# 已排序的数组
sorted_array = np.array([1, 3, 5, 7, 9])
# 查找插入位置
values = np.array([4, 6, 8])
indices = np.searchsorted(sorted_array, values)
print(indices) # 输出:[2 3 4]
例子2:side='right'
1
2
3
4
5
6
sorted_array = np.array([1, 3, 5, 7, 9])
# 查找插入位置,使用 side='right'
values = np.array([5, 7])
indices_right = np.searchsorted(sorted_array, values, side='right')
print(indices_right) # 输出[3 4]
例子3:使用 sorter
参数
1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np
# 原始数组和排序索引
sorted_array = np.array([10, 20, 30, 40])
sorter = np.array([0, 1, 2, 3]) # 索引已经按升序排列
# 查找插入位置,指定 sorter 参数
values = np.array([25, 35])
indices = np.searchsorted(sorted_array, values, sorter=sorter)
print(indices) # 输出[2 3]
sorter
参数可以优化排序过的数组的查找过程,避免重新排序。
调研:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy as np
import time
# 创建一个大的已排序数组
sorted_array = np.arange(100000000)
# 生成一些值用于查找插入位置
values = np.random.randint(0, 1000000, 1000)
# 测试不使用 sorter
start_time = time.time()
indices_no_sorter = np.searchsorted(sorted_array, values)
end_time = time.time()
print(f"没有使用 sorter 的时间:{end_time - start_time:.6f}秒")
# 生成 sorter 索引(假设 sorted_array 已排序)
sorter = np.argsort(sorted_array)
# 测试使用 sorter
start_time = time.time()
indices_with_sorter = np.searchsorted(sorted_array, values, sorter=sorter)
end_time = time.time()
print(f"使用 sorter 的时间:{end_time - start_time:.6f}秒")
extract函数返回满足指定条件的数组元素。
补充:extract函数的使用方法
numpy.extract()
是一个用于从数组中提取满足指定条件的元素的函数。它返回一个新的一维数组,包含原始数组中所有满足给定条件的元素。
函数签名:
1
numpy.extract(condition, arr)
参数说明:
condition
: 一个布尔型数组或布尔表达式。该数组必须与arr
的形状相同,或者能够广播到与arr
相同的形状。它表示选择哪些元素为True
,从而被提取到结果数组中。arr
: 输入数组,可以是任何形状的 NumPy 数组,包含了你要从中提取元素的数组。
返回值:返回一个一维数组,包含 arr
中所有对应 condition
中为 True
的元素。返回的数组不会保留原数组的形状。
例如:
1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np
# 创建一个数组
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
# 条件:提取数组中所有大于 5 的元素
condition = arr > 5
# 使用 extract 函数提取
result = np.extract(condition, arr)
print(result) # 输出 [6 7 8 9]
7.6 动手实践:使用 searchsorted 函数
searchsorted函数为指定的插入值返回一个在有序数组中的索引位置,从这个位置插入可以保持数组的有序性。下面的例子可以解释得更清楚。请完成如下步骤。
(1) 我们需要一个排序后的数组。使用arange函数创建一个升序排列的数组:
1
2
a = np.arange(5)
a
(2) 现在,我们来调用searchsorted函数:
1
2
indices = np.searchsorted(a, [-2, 7])
indices
(3) 使用insert函数构建完整的数组:
1
np.insert(a, indices, [-2, 7])
刚才做了些什么 : searchsorted函数为7和-2返回了索引5和0。用这些索引作为插入位置,我们生成了数组[-2, 0, 1, 2, 3, 4, 7]
,这样就维持了数组的排序。
完整代码如下:
1
2
3
4
5
6
7
8
import numpy as np
a = np.arange(5)
print("数组a:", a)
indices = np.searchsorted(a, [-2, 7])
a_inserted = np.insert(a, indices, [-2, 7])
print("排序之后的a:", a_inserted)
7.7 数组元素抽取
NumPy的extract函数可以根据某个条件从数组中抽取元素。该函数和我们在第3章中遇到过的where函数相似。nonzero函数专门用来抽取非零的数组元素。
1
2
3
numpy.extract(condition, arr)
numpy.where(condition, [x, y]) # x表示条件为 True ,y 表示条件为 False 时选择的值 x和y都是可选的
numpy.nonzero(a)
7.8 动手实践:从数组中抽取元素
我们要从一个数组中抽取偶数元素,步骤如下。
(1) 使用arange函数创建数组:
1
2
a = np.arange(7)
a
(2) 生成选择偶数元素的条件变量:
1
condition = (a % 2) == 0
(3) 使用extract函数基于生成的条件从数组中抽取元素:
1
np.extract(condition, a)
如果用where的话:
1
np.where(condition)
(4) 使用nonzero函数抽取数组中的非零元素:
1
np.nonzero(a)
刚才做了些什么 : 我们使用extract函数根据一个指定的布尔条件从数组中抽取了偶数元素。
补充:where函数的使用方法
numpy.where()
是一个非常强大的函数,用于根据条件选择数组元素。它可以根据条件数组的真假值,返回符合条件的元素的索引,或者直接基于条件选择数组中的值。
函数签名:
1
numpy.where(condition, [x, y])
参数:
condition
: 布尔数组或布尔表达式。其形状需要与x
和y
的形状相同,或者能够广播到相同的形状。这个条件数组的值决定了从x
和y
中选择哪些元素:True
:从x
中选择元素。False
:从y
中选择元素。
x
(可选): 如果指定了x
,它表示条件为True
时选择的值。如果没有提供x
,where
将返回满足条件的元素的索引。y
(可选): 如果指定了y
,它表示条件为False
时选择的值。如果没有提供y
,where
只会返回满足条件的元素的索引。
返回值:
- 如果提供了
x
和y
,返回一个数组,满足条件True
的位置取x
的值,False
的位置取y
的值。 - 如果只提供了
condition
,where()
返回的是符合条件的元素的索引。
看例子:
1
2
3
4
5
6
7
8
9
10
import numpy as np
# 创建一个数组
arr = np.array([1, 2, 3, 4, 5, 6])
# 使用 where 函数选择:如果元素大于 3,则选择 'High',否则选择 'Low'
result = np.where(arr > 3, 'High', 'Low')
print(result) # 输出['Low' 'Low' 'Low' 'High' 'High' 'High']
再如:
7.9 金融函数
NumPy中有很多金融函数,如下所示。
- fv函数计算所谓的终值(future value),即基于一些假设给出的某个金融资产在未来某一时间点的价值。
- pv函数计算现值(present value),即金融资产当前的价值。
- npv函数返回的是净现值(net present value),即按折现率计算的净现金流之和。 pmt函数根据本金和利率计算每期需支付的金额。
- irr函数计算内部收益率(internal rate of return)。内部收益率是是净现值为0时的有效利率,不考虑通胀因素。
- mirr函数计算修正后内部收益率(modified internal rate of return),是内部收益率的改进版本。
- nper函数计算定期付款的期数。
- rate函数计算利率(rate of interest)。
补充1:fv函数
numpy.fv()
是一个在 NumPy 中用于计算未来价值(Future Value, FV)的金融函数。它根据给定的利率、期数、付款金额等参数计算未来的财务值。这是金融数学中常用的一个工具,特别是在投资、贷款等场景下。
函数签名:
1
numpy.fv(rate, nper, pmt, pv, when='end')
参数:
rate
:
每期的利率(例如,年利率或者月利率)。如果利率为年利率,需要根据周期的不同调整。
nper
:
总期数。即投资或贷款的总期数,通常表示为期数(例如,年数或月数)。
pmt
:
每期的付款金额。通常是负值,因为它表示付款(支出),而不是收入。对于定期付款的投资或贷款,pmt
表示每期的付款金额。
pv
:
当前价值(Present Value)。即投资或贷款的当前金额,通常为负值表示你当前投资的金额,或者贷款的金额。
when
(可选):
表示付款时机。它有两个选项:
'end'
:付款在期末进行(默认值)。'begin'
:付款在期初进行。
返回值:返回未来价值(Future Value)。根据给定的输入条件计算出未来的金额。
fv()
的计算基于以下公式:\(FV = PV*(1+r)^n+PMT*\frac{(1+r)^n-1}{r}\)
其中:
FV
:未来价值。PV
:现值(现在的金额)。PMT
:每期付款金额。r
:每期利率。n
:期数。
例如:假设你每月存入 1000 元,年利率为 5%,储蓄 10 年,计算 10 年后的未来价值。
注:
numpy.fv()
在 NumPy 版本 1.20 之后已经被移除。这个函数曾经是 NumPy 库的一部分,但它现在已被移到numpy-financial
库中。如果你希望继续使用金融计算函数,包括fv()
,你需要安装并使用numpy-financial
库。安装命令:(在Jupyter notebook中)
1 !pip install numpy-financial
注意,下面的代码如果还是报错
ModuleNotFoundError: No module named 'numpy_financial'
,可以执行:
1 2 import sys !{sys.executable} -m pip install numpy-financial
1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy_financial as npf
# 参数
rate = 0.05 / 12 # 月利率
nper = 12 * 10 # 总期数(10年)
pmt = -1000 # 每月存款1000元,负号表示支出
pv = 0 # 初始现值为0
# 计算未来价值
fv = npf.fv(rate, nper, pmt, pv)
print(f"10年后的未来价值: {fv:.2f} 元") # 输出10年后的未来价值: 155282.28 元
补充2:pv函数
numpy_financial.pv()
是 numpy-financial
库中的一个金融函数,用于计算现值(Present Value, PV)。现值是指一项未来支付流(如定期支付或收入)根据给定利率折现后,等价于当前时刻的价值。
函数签名:
1
numpy_financial.pv(rate, nper, pmt, fv=0.0, when='end')
参数:
rate
: 每期的利率(例如年利率、月利率等)。如果按月计算,则为月利率。nper
: 总期数,即总的支付期数,通常是贷款的期限或者投资的期限。pmt
: 每期的支付金额。一般来说,这是负值,因为它表示支出(例如贷款的支付或投资的缴款)。fv
(可选,默认为 0): 未来值,即在最后期末所需的金额。如果是贷款问题,它通常是 0;如果是投资问题,可能是某个特定的目标值。when
(可选,默认为 ‘end’): 指定付款时机:'end'
:付款发生在期末(默认)。'begin'
:付款发生在期初。
返回值:返回现值,即投资或贷款当前时刻的等价金额。该值是根据未来的支付、利率和期数等计算出来的。
计算公式:\(PV = \frac{PMT*(1-(1+r)^{-n})}{r}+\frac{FV}{(1+r)^n}\)
其中:
PV
是现值(即现在的金额)。PMT
是每期的支付金额。r
是每期的利率。n
是期数。FV
是未来值。
例如,假设你有一个贷款,每月支付 1000 元,利率为 5% 年利率,贷款期为 10 年,计算贷款的现值(即你需要贷款的金额)。
1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy_financial as npf
# 参数
rate = 0.05 / 12 # 月利率
nper = 12 * 10 # 总期数(10年)
pmt = -1000 # 每月支付1000元,负号表示支出
fv = 0 # 未来值为0(贷款没有到期时的余额)
# 计算现值(贷款金额)
pv = npf.pv(rate, nper, pmt, fv)
print(f"贷款的现值(需要借款的金额):{pv:.2f} 元") # 输出:贷款的现值(需要借款的金额):94281.35 元
也就是说,贷款的现值是 94,281.35 元,而你最终实际还款的总额是 120,000 元
补充3:npv函数
numpy_financial.npv()
是 numpy-financial
库中的一个函数,用于计算净现值(Net Present Value,NPV)。净现值是评估投资项目的一个重要指标,表示未来一系列现金流按指定的贴现率折算回当前时点后的总和。
函数签名:
1
numpy_financial.npv(rate, values)
参数:
rate
:
贴现率或利率。它是将未来现金流折算到现值所使用的利率,通常是一个百分比(例如 5% 表示 0.05)。
values
:
一组现金流(values),通常是一个数组或列表。每个值代表在该时间点的现金流,可以是正数(收入或收益)或负数(支出或投资)。通常,第一个值是初期投资,通常为负值,后续的值为未来的收益或支出。
返回值:返回一个浮动值,表示净现值(NPV)。净现值是通过将每期的现金流按照给定的贴现率折现到现值后,进行累加的结果。
计算公式:\(NPV = \sum_{t=0}^{n} \frac{C_t}{(1+r)^t}\)
其中:
- \(C_t\) 是第 \(t\) 期的现金流。
- \(r\) 是每期的贴现率(利率)。
- \(t\)是期数(通常从 0 开始,表示当前时刻)。
如果 NPV 大于 0,说明项目有赚钱的潜力,通常可以认为是一个有利可图的项目。
如果 NPV 小于 0,说明项目可能会带来亏损,通常建议不要进行这个投资。
如果 NPV 等于 0,说明这个投资刚好是收支平衡,没有盈利也没有亏损。
例如:假设你有一个投资项目,初期投资为 100,000 元,预计未来 5 年的现金流分别为 20,000、30,000、40,000、50,000 和 60,000 元,贴现率为 8%,计算这个投资项目的净现值(NPV)。
1
2
3
4
5
6
7
8
9
10
import numpy_financial as npf
# 参数
rate = 0.08 # 贴现率 8%
cash_flows = [-100000, 20000, 30000, 40000, 50000, 60000] # 现金流,初期投资为负值,后续为收益
# 计算净现值
npv = npf.npv(rate, cash_flows)
print(f"投资项目的净现值(NPV)为: {npv:.2f} 元") # 输出:投资项目的净现值(NPV)为: 53578.46 元
NPV = 53,578.46 元,也就是说,如果你现在进行这个投资,未来的收入和支出折算到现在的价值,总共会比你初期投资的 100,000 元多出 53,578.46 元。
因此,这个项目是盈利的,因为 NPV 是正数。通俗来讲:你现在投资 100,000 元,按 8% 年利率的折现后,最终你会赚到 53,578.46 元,这表示投资是有回报的,值得考虑。
补充4:irr函数
numpy_financial.irr()
是 numpy-financial
库中的一个函数,用于计算投资项目的 内部收益率(IRR)。
内部收益率(IRR) 是一个衡量投资项目回报率的指标。它告诉你,如果你进行这个投资,未来每年的回报率是多少。
内部收益率(IRR,Internal Rate of Return) 是使得投资项目的净现值(NPV)为零的贴现率。换句话说,IRR 是一个项目能够带来零净现值的利率,它反映了投资的回报率。
- 如果 IRR > 投资的资本成本,项目通常被认为是有利可图的。
- 如果 IRR < 投资的资本成本,项目通常不值得投资。
- 如果 IRR = 投资的资本成本,项目的回报刚好等于成本,通常认为是“无效的”。
函数签名:
1
numpy_financial.irr(values)
参数:values
:
一个现金流的序列(列表或数组)。现金流的顺序应与时间顺序一致,第一个数值通常是初期投资(负值),后续的数值是未来的现金流(通常是正值,表示收入或收益)。
例如,[-100000, 20000, 30000, 40000]
表示你初期投资了 100,000 元,然后每年收入 20,000 元、30,000 元和 40,000 元。
返回值:返回一个浮动值,表示内部收益率(IRR),通常为一个小数值。例如,0.12 表示 12% 的年回报率。
例如,假设你有一个投资项目,初期投资为 100,000 元,预计未来 4 年的现金流分别为 20,000、30,000、40,000 和 50,000 元,计算该项目的内部收益率(IRR)。
1
2
3
4
5
6
7
8
9
10
import numpy_financial as npf
# 参数
cash_flows = [-100000, 20000, 30000, 40000, 50000] # 初期投资为负值,后续为收益
# 计算内部收益率(IRR)
irr = npf.irr(cash_flows)
print(f"投资项目的内部收益率(IRR)为: {irr:.2%}") # 输出:投资项目的内部收益率(IRR)为: 12.83%
12.83% 是内部收益率,表示这个投资项目每年的回报率是 12.83%。也就是说,假设你现在投资了 100,000 元,每年你会得到大约 12.83% 的收益。
补充5:mirr函数
numpy_financial.mirr()
是 numpy-financial
库中的一个函数,用于计算投资项目的 修正内部收益率(MIRR)。
修正内部收益率(MIRR,Modified Internal Rate of Return) 是一种对 IRR 的改进方法。它考虑了两个方面:
- 现金流的再投资率:IRR 假设所有的现金流(特别是中期的现金流)都按照 IRR 自身的收益率进行再投资,但这可能不现实。MIRR 允许你指定一个 再投资率,表示你将这些现金流再投资所能得到的回报率。
- 融资成本:IRR 假设所有负现金流(例如初期投资)按照 IRR 自身的利率进行融资,但实际中可能会有不同的融资利率。MIRR 允许你指定一个 融资成本,表示你从外部融资所需支付的利率。
MIRR 在计算过程中分别使用 再投资率 和 融资成本,使得它比传统的 IRR 更加贴近实际情况。MIRR 通常能避免 IRR 所存在的多个解问题,并且更加稳定。
如果 MIRR 大于资本成本,说明项目是盈利的;如果小于资本成本,说明项目可能不值得投资。
函数签名:
1
numpy_financial.mirr(values, finance_rate, reinvest_rate)
参数:
values
:
一个包含现金流的数组或列表,现金流的顺序应当与时间顺序一致。负数表示支出(投资),正数表示收入(收益)。
finance_rate
:
融资成本的贴现率,通常是一个小数。例如,5% 可以表示为 0.05
。
reinvest_rate
:
再投资的贴现率,同样是一个小数。例如,8% 可以表示为 0.08
。
返回值:返回一个浮动值,表示修正内部收益率(MIRR),通常为一个小数。例如,0.12 表示 12% 的年回报率。
例如:假设你有一个投资项目,初期投资为 100,000 元,预计未来 4 年的现金流分别为 20,000、30,000、40,000 和 50,000 元。你假设融资成本是 5%,再投资的回报率是 8%,你可以通过 mirr()
函数来计算修正后的内部收益率。
1
2
3
4
5
6
7
8
9
10
11
12
import numpy_financial as npf
# 参数
cash_flows = [-100000, 20000, 30000, 40000, 50000] # 初期投资为负值,后续为收益
finance_rate = 0.05 # 融资成本 5%
reinvest_rate = 0.08 # 再投资回报率 8%
# 计算修正内部收益率(MIRR)
mirr = npf.mirr(cash_flows, finance_rate, reinvest_rate)
print(f"投资项目的修正内部收益率(MIRR)为: {mirr:.2%}") # 投资项目的修正内部收益率(MIRR)为: 11.29%
融资成本(5%): 假设你需要向银行借款时,银行的利率是 5%。
再投资回报率(8%): 假设你收到的现金流(20,000、30,000 等)可以按照 8% 的回报率再投资。
计算结果 11.29% 代表,如果你进行这个投资项目,考虑到融资成本和再投资回报率,你的年回报率是 11.29%。
MIRR = 11.29% 表示你进行这个投资后,考虑到融资成本和再投资回报率,你的年回报率是 11.29%。
这个回报率可以用来衡量这个项目是否值得投资:如果你期望的最低回报率大于 11.29%(比如你希望每年能赚 10% 以上),那么这个项目是值得投资的。
补充6:nper函数
numpy_financial.nper()
函数用于计算 贷款或投资的期数(nper),即在给定利率、每期支付金额和现值的情况下,完成全部支付或达到目标金额所需的期数。
函数签名:
1
numpy_financial.nper(rate, pmt, pv, fv=0, when='end')
参数:
rate
:
每期的利率。可以是年利率、月利率等,具体取决于现金流的频率(例如,月利率为年利率除以 12)。
pmt
:
每期的支付金额。通常是负数,表示每期支出的金额。如果是投资,也可以是正数,表示收入。
pv
:
当前的现值,也就是你现在的本金或投资额。通常是负数,表示你需要支付的初始金额。
fv
(可选):
未来价值,默认值为 0。表示在最后一笔支付时你期望得到的金额,通常是正数表示收入。
when
(可选):
指定支付的时间。可以是:
'end'
(默认值):表示支付发生在每期的末尾。'begin'
:表示支付发生在每期的开始。
返回值:返回一个数字,表示在给定条件下完成所有支付所需的期数(nper)。返回值可以是正数(表示贷款期数)或负数(表示投资期数)。
假设你想要借款 10,000 元,年利率是 5%,每月还款 1,000 元,目标是计算需要多少个月才能还清贷款。
1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy_financial as npf
# 参数
rate = 0.05 / 12 # 月利率
pmt = -1000 # 每月还款 1000 元,负号表示支出
pv = 10000 # 贷款金额
fv = 0 # 最终贷款余额为 0
# 计算期数(贷款需要多少期才能还清)
nper = npf.nper(rate, pmt, pv, fv)
print(f"需要 {nper:.2f} 期(月)还清贷款。") # 需要 10.24 期(月)还清贷款。
假设你借了 10,000 元,每个月都按 1,000 元 的金额进行偿还。
在利率的影响下,贷款的每月还款会稍微增多,因此你每月需要还更多的钱。
经过 10.24 个月 的还款,你就能把这笔贷款还清
补充7:rate函数
numpy_financial.rate()
函数用于计算投资或贷款中每期的利率(也称为 利息率 或 贴现率),在给定其他参数(如期数、每期付款金额、现值和未来值)的情况下。
函数签名:
1
numpy_financial.rate(nper, pmt, pv, fv=0, when='end')
参数:
nper
:
总期数(即投资或贷款的期数)。例如,如果你每个月还款 12 次,那么总期数就是 12。
pmt
:
每期的付款金额,通常是负数表示支出,正数表示收入。例如每月偿还的贷款金额。
pv
:
当前的现值,也就是你现在的本金或投资额。通常是负数,表示你现在借入的金额或需要投入的金额。
fv
(可选):
未来值,默认值为 0。表示在最后一笔支付时你期望得到的金额。通常是正数表示收入,负数表示支出。
when
(可选):
支付时间,默认为 'end'
。可以是:
'end'
:表示支付发生在每期的末尾(默认)。'begin'
:表示支付发生在每期的开始。
返回值:返回每期的利率(即利息率)。返回值是一个浮动的值,表示每期的利率,通常为一个小数。
例如,假设你借了 10,000 元,每月还款 1,000 元,并且你计划 12 个月 完成还款,目标是计算每月的利率。
1
2
3
4
5
6
7
8
9
10
11
12
import numpy_financial as npf
# 参数
nper = 12 # 总期数为 12 个月
pmt = -1000 # 每月还款 1000 元,负号表示支出
pv = 10000 # 贷款金额 10000 元
# 计算每期利率,给定未来值 fv=0
rate = npf.rate(nper, pmt, pv, fv=0)
print(f"每期的利率是: {rate:.4f}") # 每期的利率是: 0.0292
7.10 动手实践:计算终值
终值是基于一些假设给出的某个金融资产在未来某一时间点的价值。终值决定于4个参 数——利率、期数、每期支付金额以及现值。在本节的教程中,我们以利率3%、每季度支付金额10、存款周期5年以及现值1 000为参数计算终值。
使用正确的参数调用fv函数,计算终值:
1
npf.fv(0.03/4, 5 * 4, -10, -1000)
这相当于利率3%的5年期存款并且每季度额外存入10个单位的资金。如果我们改变存款的年数并保持其他参数不变,将得到如下的散点图。
绘制图的代码:
1
2
3
4
5
6
7
from matplotlib.pyplot import plot
from matplotlib.pyplot import show
fvals = []
for i in range(1, 10):
fvals.append(npf.fv(.03/4, i * 4, -10, -1000))
plot(fvals, 'bo')
show()
刚才做了些什么 :我们以利率3%、每季度支付金额10、存款周期5年以及现值1 000为参数,使用NumPy中的fv函数计算了终值。我们针对不同的存款周期绘制了终值的散点图。
完整代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import numpy_financial as npf
from matplotlib.pyplot import plot
from matplotlib.pyplot import show
# 参数
nper = 12 # 总期数为 12 个月
pmt = -1000 # 每月还款 1000 元,负号表示支出
pv = 10000 # 贷款金额 10000 元
# 计算每期利率,给定未来值 fv=0
rate = npf.rate(nper, pmt, pv, fv=0)
print(f"每期的利率是: {rate:.4f}") # 每期的利率是: 0.0292
fvals = []
for i in range(1, 10):
fvals.append(npf.fv(.03/4, i * 4, -10, -1000))
plot(fvals, 'bo')
show()
7.11 现值
现值(present value)是指资产在当前时刻的价值。NumPy中的pv函数可以计算现值。该函数和fv函数是镜像对称的,同样需要利率、期数、每期支付金额这些参数,不过这里输入为终值,输出为现值。
7.12 动手实践:计算现值
我们来进行逆向计算——使用前一节教程中的数值计算现值。 使用7.10节使用的数值来计算现值。
1
npf.pv(0.03/4, 5 * 4, -10, 1376.0963320407982)
由于我们计算的是支出的现金流,因此结果前面有一个负号。
刚才做了些什么 : 我们对上一节教程中的终值进行了逆向计算,得到了现值。这是用NumPy中的pv函数完成的。
7.13 净现值
净现值(net present value)定义为按折现率计算的净现金流之和。NumPy中的npv函数返回净现值。该函数需要两个参数,即利率和一个表示现金流的数组。
7.14 动手实践:计算净现值
我们将为一组随机生成的现金流计算净现值。步骤如下。
(1) 生成5个随机数作为现金流的取值。插入-100
作为初始值。
1
2
3
cashflows = np.random.randint(100, size = 5)
cashflows = np.insert(cashflows, 0, -100)
cashflows
(2) 根据上一步生成的现金流数据,调用npv函数计算净现值。利率按3%计算。
1
npf.npv(0.03, cashflows)
刚才做了些什么 : 我们使用npv函数为一组随机生成的现金流数据计算了净现值。
7.15 内部收益率
内部收益率(internal rate of return)是净现值为0时的有效利率,不考虑通胀因素。NumPy中的irr函数根据给定的现金流数据返回对应的内部收益率。
7.16 动手实践:计算内部收益率
这里我们复用7.14节中的现金流数据。
使用之前教程中生成的现金流数组,调用irr函数。
1
npf.irr([-100, 73, 82, 16, 84, 77])
刚才做了些什么 : 我们使用之前的“动手实践”教程中生成的现金流数据,计算了对应的内部收益率。这是用NumPy中的irr函数完成的。
7.17 分期付款
NumPy中的pmt函数可以根据利率和期数计算贷款每期所需支付的资金。
7.18 动手实践:计算分期付款
假设你贷款100万,年利率为10%,要用30年时间还完贷款,那么每月你必须支付多少资金呢?我们来计算一下。
使用刚才提到的参数值,调用pmt函数。
1
npf.pmt(0.10/12, 12 * 30, 1000000)
刚才做了些什么 : 我们计算了贷款100万、年利率10%的情况下的月供金额。设定还款时间为30年,pmt函数告诉我们每月需要偿还的资金为8 775.715 700 89。
7.19 付款期数
NumPy中的nper函数可以计算分期付款所需的期数。所需的参数为贷款利率、固定的月供以及贷款额。
7.20 动手实践:计算付款期数
考虑贷款9 000,年利率10%,每月固定还款为100的情形。 通过nper函数计算出付款期数。
1
npf.nper(0.10/12, -100, 9000)
刚才做了些什么 :我们计算了贷款9000、年利率10%、每月固定还款100的情形下所需的付款期数。结果为167个月。
7.21 利率
NumPy中的rate函数根据给定的付款期数、每期付款资金、现值和终值计算利率。
7.22 动手实践:计算利率
我们使用7.20节中的数值进行逆向计算,由其他参数得出利率。 填入之前教程中的数值作为参数。
1
12*npf.rate(167, -100, 9000, 0)
如我们所料,计算出的利率约为10%。
刚才做了些什么 : 我们使用了NumPy的rate函数和之前的“动手实践”教程中的数值,计算了贷款利率。忽略舍入误差,我们得到了原先的利率10%。
7.23 窗函数
窗函数(window function)是信号处理领域常用的数学函数,相关应用包括谱分析和滤波器设计等。这些窗函数除在给定区间之外取值均为0。NumPy中有很多窗函数,如bartlett、blackman、hamming、hanning和kaiser。关于hanning函数的例子可以在第4章和第3章中找到。
7.24 动手实践:绘制巴特利特窗
巴特利特窗(Bartlett window)是一种三角形平滑窗。按如下步骤绘制巴特利特窗。
(1) 调用NumPy中的bartlett函数,以计算巴特利特窗。
1
window = np.bartlett(42)
(2) 使用Matplotlib绘制巴特利特窗,非常简单。
1
2
plot(window)
show()
刚才做了些什么 : 我们使用NumPy中的bartlett函数绘制了巴特利特窗。
7.25 布莱克曼窗
布莱克曼窗(Blackman window)形式上为三项余弦值的加和:\(w(n) = 0.42- 0.5cos(2 \pi n/M)+0.08cos(4 \pi n/M)\)
NumPy中的blackman函数返回布莱克曼窗。该函数唯一的参数为输出点的数量。如果数量为0或小于0,则返回一个空数组。
7.26 动手实践:使用布莱克曼窗平滑股价数据
我们对AAPL股价的小数据文件中的收盘价数据进行平滑处理。完成如下步骤。
(1) 将数据载入NumPy数组。调用blackman函数生成一个平滑窗并用它来平滑股价数据。
1
2
3
4
5
6
7
8
import sys
closes = np.loadtxt("AAPL.csv", delimiter=",", usecols=(6, ), unpack=True)
# N = int(sys.argv[1])
N = 5
window = np.blackman(N)
smoothed = np.convolve(window/window.sum(), closes, mode='same')
smoothed
(2) 使用Matplotlib绘制平滑后的股价图。在这个例子中,我们将省略最前面5个和最后面5个数据点。这是由于存在很强的边界效应。
1
2
3
4
plot(smoothed[N:-N], lw=2, label="smoothed")
plot(closes[N:-N], label="closes")
legend(loc='best')
show()
刚才做了些什么 : 我们使用NumPy中的blackman函数生成的布莱克曼窗对AAPL收盘价数据进行了平滑处理,并用Matplotlib绘制了平滑前后的股价图。
完整代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sys
import numpy as np
from matplotlib.pyplot import plot
from matplotlib.pyplot import show
from matplotlib.pyplot import legend
closes = np.loadtxt("AAPL.csv", delimiter=",", usecols=(6, ), unpack=True)
# N = int(sys.argv[1])
N = 5
window = np.blackman(N)
smoothed = np.convolve(window/window.sum(), closes, mode='same')
plot(smoothed[N:-N], lw=2, label="smoothed")
plot(closes[N:-N], label="closes")
legend(loc='best')
show()
7.27 汉明窗
汉明窗(Hamming window)形式上是一个加权的余弦函数。公式:\(w(n) = 0.54+0.64cos(\frac{2 \pi n}{M-1}),0 \le n \le M-1\)
NumPy中的hamming函数返回汉明窗。该函数唯一的参数为输出点的数量。如果数量为0或小于0,则返回一个空数组。
7.28 动手实践:绘制汉明窗
我们来绘制汉明窗。完成如下步骤。
(1) 调用hamming函数,以计算汉明窗:
1
2
window = np.hamming(42)
window
(2) 使用Matplotlib绘制汉明窗:
1
2
plot(window)
show()
刚才做了些什么 : 我们使用NumPy中的hamming函数绘制了汉明窗。
7.29 凯泽窗
凯泽窗(Kaiser window)是以贝塞尔函数(Bessel function)定义的,公式如下所示。
\[w(n) = I_0 \left( \beta \sqrt{ 1 - \frac{4n^2}{(M - 1)^2}} \right) / I_0(\beta)\]这里的\(I_0\)即为零阶的贝塞尔函数。NumPy中的kaiser函数返回凯泽窗。该函数的第一个参数为输出点的数量。如果数量为0或小于0,则返回一个空数组。第二个参数为\(\beta\)值。
7.30 动手实践:绘制凯泽窗
我们来绘制凯泽窗。完成如下步骤。
(1) 调用kaiser函数,以计算凯泽窗:
1
2
window = np.kaiser(42, 14)
window
(2) 使用Matplotlib绘制凯泽窗:
1
2
plot(window)
show()
刚才做了些什么 : 我们使用NumPy中的kaiser函数绘制了凯泽窗。
7.31 专用数学函数
我们将以一些专用数学函数结束本章的内容。
贝塞尔函数(Bessel function)是贝塞尔微分方程的标准解函数(详见http://en.wikipedia.org/wiki/Bessel_function)。
在NumPy中,以i0 表示第一类修正的零阶贝塞尔函数。
sinc函数在NumPy中有同名函数sinc,并且该函数也有一个二维版本。
sinc是一个三角函数,更多详细内容请访问http://en.wikipedia.org/wiki/Sinc_function。
7.32 动手实践:绘制修正的贝塞尔函数
我们来看看第一类修正的零阶贝塞尔函数绘制出来是什么形状。
(1) 使用NumPy的linspace函数生成一组均匀分布的数值。
1
2
x = np.linspace(0, 4, 100)
x
(2) 调用i0函数进行计算:
1
2
vals = np.i0(x)
vals
(3) 使用Matplotlib绘制修正的贝塞尔函数:
1
2
plot(x, vals)
show()
刚才做了些什么 : 我们使用NumPy中的i0函数绘制了第一类修正的零阶贝塞尔函数。
7.33 sinc 函数
sinc函数在数学和信号处理领域被广泛应用。NumPy中有同名函数sinc,并且也存在一个二维版本。
7.34 动手实践:绘制 sinc 函数
我们将绘制sinc函数。完成如下步骤。
(1) 使用NumPy的linspace函数生成一组均匀分布的数值。
1
x = np.linspace(0, 4, 100)
(2) 调用sinc函数进行计算:
1
vals = np.sinc(x)
(3) 使用Matplotlib绘制sinc函数:
1
2
plot(x, vals)
show()
sinc2d函数需要输入一个二维数组。我们可以用outer函数生成二维数组,便得到下图。
1
2
3
4
5
6
7
import numpy as np
from matplotlib.pyplot import imshow, show
x = np.linspace(0, 4, 100)
xx = np.outer(x, x)
vals = np.sinc(xx)
imshow(vals)
show()
7.35 本章小结
本章介绍了一些专用性较强的NumPy功能,包括排序和搜索、专用函数、金融函数以及窗函数等。 在下一章中,我们将学习非常重要的程序测试方面的知识。