numpy(7)专用函数

第 7 章 专用函数

Posted by Hilda on February 10, 2025

前6章以及其他补充已经整理如下:

第1章NumPy入门

第2章NumPy基础

第3章常用函数

广播与广播机制

第4章便捷函数

第5章矩阵和通用函数

第6章深入学习NumPy模块


作为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

image-20250209213702535

1
print(["%s %s" % (datetime.date.fromordinal(dates[i]), closes[i]) for i in indices] )

此时按照书上的代码,出现了报错:TypeError: integer argument expected, got float

image-20250209224946782

因为dates数组每一项是float64的类型,但是需要整数类型。那么多进行一步转换就可以。

修改如下:

1
print(["%s %s" % (datetime.date.fromordinal(int(dates[i])), closes[i]) for i in indices])

image-20250209225208359

刚才做了些什么 : 我们使用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)

image-20250209225549558

np.lexsort((array2, array1)):这是根据 array1array2 进行排序的。排序优先级是先按照 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)

image-20250209225957495

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),它指定了随机数生成器的种子值。
  • 如果 seedNone,那么 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))  # 结果与上次相同

image-20250210155402889


补充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)

image-20250210155602394

(2) 调用sort_complex函数对上面生成的复数进行排序:

1
2
# 排序
print("Sorted\n", np.sort_complex(complex_numbers))

image-20250210155733457

刚才做了些什么 : 我们生成了随机的复数并使用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))

image-20250210160354423


突击测验:生成随机数

问题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)

image-20250210160636513

nanargmax函数提供相同的功能,但忽略NaN值。

1
2
n = np.array([10, np.nan, 8])
np.nanargmax(n)

image-20250210160727571

argmin和nanargmin函数的功能类似,只不过换成了最小值。

argwhere函数根据条件搜索非零的元素,并分组返回对应的下标。

1
2
n = np.array([10, 4, 8])
np.argwhere(n > 4)

image-20250210160836095

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}秒")

image-20250210162210437


extract函数返回满足指定条件的数组元素。


补充:extract函数的使用方法

numpy.extract() 是一个用于从数组中提取满足指定条件的元素的函数。它返回一个新的一维数组,包含原始数组中所有满足给定条件的元素。

函数签名:

1
numpy.extract(condition, arr)

参数说明:

  1. condition: 一个布尔型数组或布尔表达式。该数组必须与 arr 的形状相同,或者能够广播到与 arr 相同的形状。它表示选择哪些元素为 True,从而被提取到结果数组中。
  2. 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

image-20250210162459130

(2) 现在,我们来调用searchsorted函数:

1
2
indices = np.searchsorted(a, [-2, 7])
indices

image-20250210162558533

(3) 使用insert函数构建完整的数组:

1
np.insert(a, indices, [-2, 7])

image-20250210162653710

刚才做了些什么 : 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)

image-20250210162919159

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

image-20250210163224757

(2) 生成选择偶数元素的条件变量:

1
condition = (a % 2) == 0

(3) 使用extract函数基于生成的条件从数组中抽取元素:

1
np.extract(condition, a)

image-20250210163405738

如果用where的话:

1
np.where(condition)

image-20250210163605979

(4) 使用nonzero函数抽取数组中的非零元素:

1
np.nonzero(a)

image-20250210163656051

刚才做了些什么 : 我们使用extract函数根据一个指定的布尔条件从数组中抽取了偶数元素。


补充:where函数的使用方法

numpy.where() 是一个非常强大的函数,用于根据条件选择数组元素。它可以根据条件数组的真假值,返回符合条件的元素的索引,或者直接基于条件选择数组中的值。

函数签名:

1
numpy.where(condition, [x, y])

参数:

  1. condition: 布尔数组或布尔表达式。其形状需要与 xy 的形状相同,或者能够广播到相同的形状。这个条件数组的值决定了从 xy 中选择哪些元素:
    • True:从 x 中选择元素。
    • False:从 y 中选择元素。
  2. x(可选): 如果指定了 x,它表示条件为 True 时选择的值。如果没有提供 xwhere 将返回满足条件的元素的索引。
  3. y(可选): 如果指定了 y,它表示条件为 False 时选择的值。如果没有提供 ywhere 只会返回满足条件的元素的索引。

返回值:

  • 如果提供了 xy,返回一个数组,满足条件 True 的位置取 x 的值,False 的位置取 y 的值。
  • 如果只提供了 conditionwhere() 返回的是符合条件的元素的索引。

看例子:

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']

再如:

image-20250210165105921

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

image-20250210165652661

注意,下面的代码如果还是报错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 的改进方法。它考虑了两个方面:

  1. 现金流的再投资率:IRR 假设所有的现金流(特别是中期的现金流)都按照 IRR 自身的收益率进行再投资,但这可能不现实。MIRR 允许你指定一个 再投资率,表示你将这些现金流再投资所能得到的回报率。
  2. 融资成本: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) 

image-20250210183729231

这相当于利率3%的5年期存款并且每季度额外存入10个单位的资金。如果我们改变存款的年数并保持其他参数不变,将得到如下的散点图。

image-20250210183829674

绘制图的代码:

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)

image-20250210191835522

由于我们计算的是支出的现金流,因此结果前面有一个负号。

刚才做了些什么 : 我们对上一节教程中的终值进行了逆向计算,得到了现值。这是用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

image-20250210192203345

(2) 根据上一步生成的现金流数据,调用npv函数计算净现值。利率按3%计算。

1
npf.npv(0.03, cashflows)

image-20250210192235576

刚才做了些什么 : 我们使用npv函数为一组随机生成的现金流数据计算了净现值。


7.15 内部收益率

内部收益率(internal rate of return)是净现值为0时的有效利率,不考虑通胀因素。NumPy中的irr函数根据给定的现金流数据返回对应的内部收益率。

7.16 动手实践:计算内部收益率

这里我们复用7.14节中的现金流数据。

使用之前教程中生成的现金流数组,调用irr函数。

1
npf.irr([-100,   73,   82,   16,   84,   77])

image-20250210192527604

刚才做了些什么 : 我们使用之前的“动手实践”教程中生成的现金流数据,计算了对应的内部收益率。这是用NumPy中的irr函数完成的。

7.17 分期付款

NumPy中的pmt函数可以根据利率和期数计算贷款每期所需支付的资金。

7.18 动手实践:计算分期付款

假设你贷款100万,年利率为10%,要用30年时间还完贷款,那么每月你必须支付多少资金呢?我们来计算一下。

使用刚才提到的参数值,调用pmt函数。

1
npf.pmt(0.10/12, 12 * 30, 1000000)

image-20250210192640899

刚才做了些什么 : 我们计算了贷款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)

image-20250210192758638

刚才做了些什么 :我们计算了贷款9000、年利率10%、每月固定还款100的情形下所需的付款期数。结果为167个月。

7.21 利率

NumPy中的rate函数根据给定的付款期数、每期付款资金、现值和终值计算利率。

7.22 动手实践:计算利率

我们使用7.20节中的数值进行逆向计算,由其他参数得出利率。 填入之前教程中的数值作为参数。

1
12*npf.rate(167, -100, 9000, 0)

image-20250210192931420

如我们所料,计算出的利率约为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()

image-20250210193510941

刚才做了些什么 : 我们使用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

image-20250210193924362

(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() 

image-20250210194058426

刚才做了些什么 : 我们使用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

image-20250210194406218

(2) 使用Matplotlib绘制汉明窗:

1
2
plot(window)
show()

image-20250210194437397

刚才做了些什么 : 我们使用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

image-20250210194716707

(2) 使用Matplotlib绘制凯泽窗:

1
2
plot(window)
show()

image-20250210194748479

刚才做了些什么 : 我们使用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

image-20250210194950795

(2) 调用i0函数进行计算:

1
2
vals = np.i0(x)
vals

image-20250210195022981

(3) 使用Matplotlib绘制修正的贝塞尔函数:

1
2
plot(x, vals)
show()

image-20250210195347370

刚才做了些什么 : 我们使用NumPy中的i0函数绘制了第一类修正的零阶贝塞尔函数。

7.33 sinc 函数

sinc函数在数学和信号处理领域被广泛应用。NumPy中有同名函数sinc,并且也存在一个二维版本。

7.34 动手实践:绘制 sinc 函数

我们将绘制sinc函数。完成如下步骤。

(1) 使用NumPy的linspace函数生成一组均匀分布的数值。

1
x = np.linspace(0, 4, 100)

image-20250210195511035

(2) 调用sinc函数进行计算:

1
vals = np.sinc(x)

image-20250210195556698

(3) 使用Matplotlib绘制sinc函数:

1
2
plot(x, vals)
show()

image-20250210195629754

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() 

image-20250210195725055

7.35 本章小结

本章介绍了一些专用性较强的NumPy功能,包括排序和搜索、专用函数、金融函数以及窗函数等。 在下一章中,我们将学习非常重要的程序测试方面的知识。