SciPy 是 Python 里处理科学计算 (scientific computing) 的包,使用它遇到问题可访问它的官网 (https://www.scipy.org/). 去找答案。 在使用 scipy 之前,需要引进它,语法如下:
import scipy
这样你就可以用 scipy 里面所有的内置方法 (build-in methods) 了,比如插值、积分和优化。
numpy.interpolatenumpy.integratenumpy.optimize
但是每次写 scipy 字数有点多,通常我们给 scipy 起个别名 sp,用以下语法,这样所有出现 scipy 的地方都可以用 sp 替代。
import scipy as sp
SciPy 是建立 NumPy 基础上的,很多关于线性代数的矩阵运算在 NumPy 都能做,因此就不重复在这里讲了。此外在〖数组计算之 NumPy (下)〗也说过,数组计算比矩阵计算更通用,
本章换一种写法,我们专门针对科学计算中三个具体问题来介绍 SciPy,它们就是插值 (interpolation)
积分 (integration)
优化 (optimization)
对于以上每一个知识点我都会介绍一个简单例子来明晰 SciPy 里各种函数的用法
和金融相关的实际例子计算远期利率:在零息曲线中插值折现因子
计算期权价格:将期望写成积分并数值求解
配置资产权重:优化「风险平价」模型权重
1.插值
给定一组 (xi, yi),其中 i = 1, 2, ..., n,而且 xi 是有序的,称为「标准点」。插值就是对于任何新点 xnew,计算出对应的 ynew。换句话来说,插值就是在标准点之间建立分段函数 (piecewise function),把它们连起来。这样给定任意连续 x 值,带入函数就能计算出任意连续 y 值。
在 SciPy 中有个专门的函数 scipy.interpolate 是用来插值的,首先引进它并记为 spi。
import scipy.interpolate as spi简单例子
用 scipy.interpolate 来插值函数 sin(x) + 0.5x。
基本概念
首先定义 x 和函数 f(x):
x = np.linspace(-2 * np.pi, 2 * np.pi, 11)f = lambda x: np.sin(x) + 0.5 * xf(x)array([ -3.14159265, -1.56221761, -1.29717034, -1.84442231,
-1.57937505, 0. , 1.57937505, 1.84442231,
1.29717034, 1.56221761, 3.14159265])
接下来介绍 scipy.interpolate 里面两大杀器:splrep 和 splev。两个函数名称都是以 spl 开头,全称 spline (样条),可以理解这两个函数都和样条有关。不同的是,两个函数名称以 rep 和 ev 结尾,它们的意思分别是:rep:representation 的缩写,那么 splrep 其实生成的是一个「表示样条的对象」
ev:evaluation 的缩写,那么 splev 其实用于「在样条上估值」
splrep 和 splev 像是组合拳 (one two punch)前者将 x, y 和插值方式转换成「样条对象」tck
后者利用它在 xnew 上生成 ynew
一图胜千言:
接下来仔细分析一下 tck。
tck = spi.splrep( x, f(x), k=1 )tck
tck 就是样条对象,以元组形式返回,tck 这名字看起来很奇怪,实际指的是元组 (t, c, k) 里的三元素:t - vector of knots (节点)
c - spline cofficients (系数)
k - degree of spline (阶数)
对照上图,tck 确实一个元组,包含两个 array 和一个标量 1,其中第一个 array 是节点,即标准点,注意到一开始 x 只有 11 个,但现在是 13 个,首尾都往外补了一个和首尾一样的 x
第二个 array 是系数,注意它就是 y 在尾部补了两个 0
标量 1 是阶数,因为在调用 splrep 时就把 k 设成 1
注:前两个 array 我只是发现这个规律,但解释不清楚为什么这样。它和 matlab 里面的 spline() 的产出不太一样,希望懂的读者可以留言区解释一下。
虽然解释不清楚前两个 array,那就把 tck 当成是个黑匣子 (black-box) 直接用了。比如可用 PPoly.from_spline 来查看每个分段函数的系数。
pp = spi.PPoly.from_spline(tck)pp.c.Tarray( [[ 1.25682673, -3.14159265],
[ 1.25682673, -3.14159265],
[ 0.21091791, -1.56221761],
[-0.43548928, -1.29717034],
[ 0.21091791, -1.84442231],
[ 1.25682673, -1.57937505],
[ 1.25682673, 0. ],
[ 0.21091791, 1.57937505],
[-0.43548928, 1.84442231],
[ 0.21091791, 1.29717034],
[ 1.25682673, 1.56221761],
[ 1.25682673, 3.14159265]])
t ck 的系数数组里有 13 个元素,而上面数组大小是 (12, 2),12 表示 12 段,2 表示每段线性函数由 2 个系数确定。
把 x 和 tck 丢进 splev 函数,我们可以插出在 x 点对应的值 iy。
iy = spi.splev( x, tck )iyarray([ -3.14159265, -1.56221761, -1.29717034, -1.84442231,
-1.57937505, 0. , 1.57937505, 1.84442231,
1.29717034, 1.56221761, 3.14159265])
用 Matplotlib 来可视化插值的 iy 和原函数 f(x) 发现 iy 都在 f(x) 上。Matplotlib 是之后的课题,现在读者可忽略其细节。
除了可视化,我们还可以用具体的数值结果来评估插值的效果:
np.allclose(f(x), iy)np.sum((f(x) - iy) ** 2) / len(x)True
0.0
第一行 allclose 的结果都是 True 证明插值和原函数值完全吻合,第二行就是均方误差 (mean square error, MSE),0.0 也说明同样结果。
上面其实做的是在「标准点 x」上插值,那得到的结果当然等于「标准点 y」了。这种插值确实意义不大,但举这个例子只想让大家明晰 splrep 和 splev 是怎么运作的
如何可视化插出来的值和原函数的值
如何用 allclose 来衡量插值和原函数值之间的差异
一旦弄明白了这些基础,接下来就可以秒懂更实际的例子了。
正规例子
上面在「标准点 x」上插值有点作弊,现在我们在 50 个「非标准点 xd」上线性插值得到 iyd。
xd = np.linspace( 1.0, 3.0, 50 )iyd = spi.splev( xd, tck )print( xd, '\n\n', iyd )
密密麻麻的数字啥都看不出来,可视化一下把。