数据分析三剑客之特征值提取(七)

奇异值分解

有一个矩阵M,可以分解为3个矩阵U、S、V,使得U x S x V等于M。U与V都是正交矩阵(乘以自身的转置矩阵结果为单位矩阵)。那么S矩阵主对角线上的元素称为矩阵M的奇异值,其它元素均为0。

import numpy as np
M = np.mat('4 11 14; 8 7 -2')
print(M)
U, sv, V = np.linalg.svd(M, full_matrices=False)
print(U * U.T)
print(V * V.T)
print(sv)
S = np.diag(sv)
print(S)
print(U * S * V)

案例:读取图片的亮度矩阵,提取奇异值与两个正交矩阵,保留部分奇异值,重新生成新的亮度矩阵,绘制图片。

original = sm.imread('../data/lily.jpg', True)
#提取奇异值  sv 	
U, sv, V = np.linalg.svd(original)
print(U.shape, sv.shape, V.shape)
sv[50:] = 0
original2 = np.mat(U) * np.mat(np.diag(sv)) * np.mat(V)
mp.figure("Lily Features")
mp.subplot(221)
mp.xticks([])
mp.yticks([])
mp.imshow(original, cmap='gray')

mp.subplot(222)
mp.xticks([])
mp.yticks([])
mp.imshow(original2, cmap='gray')
mp.tight_layout()

快速傅里叶变换模块(fft)

什么是傅里叶定理?

法国科学家傅里叶提出,任何一条周期曲线,无论多么跳跃或不规则,都能表示成一组光滑正弦曲线叠加之和。

什么是傅里叶变换?

即是基于傅里叶定理对一条周期曲线进行拆解的过程,最终得到一组光滑的正弦曲线。

傅里叶变换的目的是可将时域(即时间域)上的信号转变为频域(即频率域)上的信号,随着域的不同,对同一个事物的了解角度也就随之改变,因此在时域中某些不好处理的地方,在频域就可以较为简单的处理。这就可以大量减少处理信号存储量。

例如:弹钢琴

假设有一时间域函数:y = f(x),根据傅里叶的理论它可以被分解为一系列正弦函数的叠加,他们的振幅A,频率ω或初相位φ不同:
y = A 1 s i n ( ω 1 x + ϕ 1 ) + A 2 s i n ( ω 2 x + ϕ 2 ) + A 2 s i n ( ω 2 x + ϕ 2 ) + R y = A_1sin(\omega_1x+\phi_1) + A_2sin(\omega_2x+\phi_2) + A_2sin(\omega_2x+\phi_2) + R
所以傅里叶变换可以把一个比较复杂的函数转换为多个简单函数的叠加,看问题的角度也从时间域转到了频率域,有些的问题处理起来就会比较简单。

傅里叶变换相关函数

导入快速傅里叶变换所需模块

import numpy.fft as nf

通过采样数与采样周期求得傅里叶变换分解所得曲线的频率序列

freqs = nf.fftfreq(采样数量, 采样周期)

通过原函数值的序列j经过快速傅里叶变换得到一个复数数组,复数的模代表的是振幅,复数的辐角代表初相位

nf.fft(原函数值序列) -> 目标函数值序列(复数)

通过一个复数数组(复数的模代表的是振幅,复数的辐角代表初相位)经过逆向傅里叶变换得到合成的函数值数组

nf.ifft(目标函数值序列(复数))->原函数值序列

案例:针对合成波做快速傅里叶变换,得到一组复数序列;再针对该复数序列做逆向傅里叶变换得到新的合成波并绘制。

ffts = nf.fft(sigs6)
sigs7 = nf.ifft(ffts).real
mp.plot(times, sigs7, label=r'$\omega$='+str(round(1 / (2 * np.pi),3)), alpha=0.5, linewidth=6)

案例:针对合成波做快速傅里叶变换,得到分解波数组的频率、振幅、初相位数组,并绘制频域图像。

# 得到分解波的频率序列
freqs = nf.fftfreq(times.size, times[1] - times[0])
# 复数的模为信号的振幅(能量大小)
ffts = nf.fft(sigs6)
pows = np.abs(ffts)

mp.subplot(122)
mp.title('Frequency Domain', fontsize=16)
mp.xlabel('Frequency', fontsize=12)
mp.ylabel('Power', fontsize=12)
mp.tick_params(labelsize=10)
mp.grid(linestyle=':')
mp.plot(freqs[freqs >= 0], pows[freqs >= 0], c='orangered', label='Frequency Spectrum')
mp.legend()
mp.tight_layout()
mp.show()

基于傅里叶变换的频域滤波

含噪信号是高能信号与低能噪声叠加的信号,可以通过傅里叶变换的频域滤波实现降噪。

通过FFT使含噪信号转换为含噪频谱,去除低能噪声,留下高能频谱后再通过IFFT留下高能信号。

案例:基于傅里叶变换的频域滤波为音频文件去除噪声。

  1. 读取音频文件,获取音频文件基本信息:采样个数,采样周期,与每个采样的声音信号值。绘制音频时域的:时间/位移图像。
import numpy as np
import numpy.fft as nf
import scipy.io.wavfile as wf
import matplotlib.pyplot as mp

sample_rate, noised_sigs = wf.read('../data/noised.wav')
noised_sigs = noised_sigs / 2 ** 15
44100
[ 15575  -9795  -7942 ... -11926 -11328   1971]
times = np.arange(len(noised_sigs)) / sample_rate
mp.figure('Filter', facecolor='lightgray')
mp.subplot(221)
mp.title('Time Domain', fontsize=16)
mp.ylabel('Signal', fontsize=12)
mp.tick_params(labelsize=10)
mp.grid(linestyle=':')
mp.plot(times[:178], noised_sigs[:178],c='orangered', label='Noised')
mp.legend()
mp.show()

  1. 基于傅里叶变换,==获取音频频域信息,==绘制音频频域的:频率/能量图像。
#通过采样数与采样周期求得傅里叶变换分解所得曲线的**频率序列
freqs = nf.fftfreq(times.size, 1 / sample_rate)
#通过原函数值的序列j经过快速傅里叶变换得到一个复数数组,复数的模代表的是振幅,复数的辐角代表初相位
noised_ffts = nf.fft(noised_sigs)
noised_pows = np.abs(noised_ffts)
mp.subplot(222)
mp.title('Frequency Domain', fontsize=16)
mp.ylabel('Power', fontsize=12)
mp.tick_params(labelsize=10)
mp.grid(linestyle=':')
mp.semilogy(freqs[freqs >= 0],noised_pows[freqs >= 0], c='limegreen',label='Noised')
mp.legend()

  1. 将低能噪声去除后绘制音频频域的:频率/能量图像。
fund_freq = freqs[noised_pows.argmax()]
noised_indices = np.where(freqs != fund_freq)
filter_ffts = noised_ffts.copy()
filter_ffts[noised_indices] = 0
filter_pows = np.abs(filter_ffts)

mp.subplot(224)
mp.xlabel('Frequency', fontsize=12)
mp.ylabel('Power', fontsize=12)
mp.tick_params(labelsize=10)
mp.grid(linestyle=':')
mp.plot(freqs[freqs >= 0], filter_pows[freqs >= 0],c='dodgerblue', label='Filter')
mp.legend() 

  1. 基于逆向傅里叶变换,生成新的音频信号,绘制音频时域的:时间/位移图像。
filter_sigs = nf.ifft(filter_ffts).real
mp.subplot(223)
mp.xlabel('Time', fontsize=12)
mp.ylabel('Signal', fontsize=12)
mp.tick_params(labelsize=10)
mp.grid(linestyle=':')
mp.plot(times[:178], filter_sigs[:178],c='hotpink', label='Filter')
mp.legend()

  1. 重新生成音频文件。
wf.write('../../data/filter.wav',sample_rate,(filter_sigs * 2 ** 15).astype(np.int16))

随机数模块(random)

生成服从特定统计规律的随机数序列。

二项分布(binomial)

二项分布就是重复n次独立事件的伯努利试验。在每次试验中只有两种可能的结果,而且两种结果发生与否互相对立,并且相互独立,事件发生与否的概率在每一次独立试验中都保持不变。

# 产生size个随机数,每个随机数来自n次尝试中的成功次数,其中每次尝试成功的概率为p。
np.random.binomial(n, p, size)

二项分布可以用于求如下场景的概率的近似值:

  1. 某人投篮命中率为0.3,投10次,进5个球的概率。
sum(np.random.binomial(10, 0.3, 200000) == 5) / 200000

  1. 某人打客服电话,客服接通率是0.6,一共打了3次,都没人接的概率。
sum(np.random.binomial(3, 0.6, 200000) == 0) / 200000

超几何分布(hypergeometric)

# 产生size个随机数,每个随机数t为在总样本中随机抽取nsample个样本后好样本的个数,总样本由ngood个好样本和nbad个坏样本组成
np.random.hypergeometric(ngood, nbad, nsample, size)

正态分布(normal)

# 产生size个随机数,服从标准正态(期望=0, 标准差=1)分布。
np.random.normal(size)
# 产生size个随机数,服从正态分布(期望=1, 标准差=10)。
np.random.normal(loc=1, scale=10, size)

: e x 2 2 2 π 标准正态分布概率密度: \frac{e^{-\frac{x^2}{2}}}{\sqrt{2\pi}}

a=np.random.normal(0,1,10)
b=np.random.normal(1,10,10)
print(a)
print(b)
[ 1.65608741 -0.15390372  1.23973367 -0.27418606  0.02613672 -0.51448946
  0.25605895  0.6672572  -0.53338181  0.42945752]
[-19.6526103   -4.72170518  -7.20283119   4.63398311  11.05561184
  -6.97525378  -1.41342134   9.17045372  -3.09025638  11.02179837]

杂项功能

排序

np.msort(closing_prices)

联合间接排序

联合间接排序支持为待排序列排序,若待排序列值相同,则利用参考序列作为参考继续排序。最终返回排序过后的有序索引序列。

indices = numpy.lexsort((次排序序列, 主排序序列))

案例:先按价格排序,再按销售量倒序排列。

import numpy as np
prices = np.array([92,83,71,92,40,12,64])
volumes = np.array([100,251,4,12,709,34,75])
print(volumes)
names = ['Product1','Product2','Product3','Product4','Product5','Product6','Product7']
ind = np.lexsort((volumes*-1, prices)) 
print(ind)
for i in ind:
	print(names[i], end=' ')

复数数组排序

按照实部的升序排列,对于实部相同的元素,参考虚部的升序,直接返回排序后的结果数组。

numpy.sort_complex(复数数组)

插入排序

若有需求需要向有序数组中插入元素,使数组依然有序,numpy提供了searchsorted方法查询并返回可插入位置数组。

indices = numpy.searchsorted(有序序列, 待插序列)

调用numpy提供了insert方法将待插序列中的元素,按照位置序列中的位置,插入到被插序列中,返回插入后的结果。

numpy.insert(被插序列, 位置序列, 待插序列)

案例:

import numpy as np
#             0  1  2  3  4  5  6
a = np.array([1, 2, 4, 5, 6, 8, 9])
b = np.array([7, 3])
c = np.searchsorted(a, b)#找到要插入的位置
print(c) 
[6 2]
d = np.insert(a, c, b)
print(d)
[1 2 3 3 4 5 6 7 7 8 9]

插值

实现离散数据连续化

scipy提供了常见的插值算法可以通过一组散点得到一个符合一定规律插值器函数。若我们给插值器函数更多的散点x坐标序列,该函数将会返回相应的y坐标序列。

func = si.interp1d(
    离散水平坐标, 
    离散垂直坐标,
    kind=插值算法(缺省为线性插值)
)

案例:

# scipy.interpolate
import scipy.interpolate as si

# 原始数据 11组数据
min_x = -50
max_x = 50
dis_x = np.linspace(min_x, max_x, 11)
dis_y = np.sinc(dis_x)

# 通过一系列的散点设计出符合一定规律插值器函数,使用线性插值(kind缺省值)
linear = si.interp1d(dis_x, dis_y)
lin_x = np.linspace(min_x, max_x, 200)
lin_y = linear(lin_x)

# 三次样条插值 (CUbic Spline Interpolation) 获得一条光滑曲线
cubic = si.interp1d(dis_x, dis_y, kind='cubic')
cub_x = np.linspace(min_x, max_x, 200)
cub_y = cubic(cub_x)

积分

直观地说,对于一个给定的正实值函数,在一个实数区间上的定积分可以理解为坐标平面上由曲线、直线以及轴围成的曲边梯形的面积值(一种确定的实数值)。

速度不定的时候,在一定时间段走了多远

利用微元法认识什么是积分。

案例:

  1. 在[-5, 5]区间绘制二次函数y=2x2+3x+4的曲线:
import numpy as np
import matplotlib.pyplot as mp
import matplotlib.patches as mc

def f(x):
    return 2 * x ** 2 + 3 * x + 4

a, b = -5, 5
x1 = np.linspace(a, b, 1001)
y1 = f(x1)
mp.figure('Integral', facecolor='lightgray')
mp.title('Integral', fontsize=20)
mp.xlabel('x', fontsize=14)
mp.ylabel('y', fontsize=14)
mp.tick_params(labelsize=10)
mp.grid(linestyle=':')
mp.plot(x1, y1, c='orangered', linewidth=6,label=r'$y=2x^2+3x+4$', zorder=0)
mp.legend()
mp.show()

  1. 微分法绘制函数在与x轴还有[-5, 5]所组成的闭合区域中的小梯形。
n = 50
x2 = np.linspace(a, b, n + 1)
y2 = f(x2)
area = 0
for i in range(n):
    area += (y2[i] + y2[i + 1]) * (x2[i + 1] - x2[i]) / 2
print(area)
for i in range(n):
    mp.gca().add_patch(mc.Polygon([
        [x2[i], 0], [x2[i], y2[i]],
        [x2[i + 1], y2[i + 1]], [x2[i + 1], 0]],
        fc='deepskyblue', ec='dodgerblue',
        alpha=0.5))

调用scipy.integrate模块的quad方法计算积分:

import scipy.integrate as si
# 利用quad求积分 给出函数f,积分下限与积分上限[a, b]   返回(积分值,最大误差)
area = si.quad(f, a, b)[0]
print(area)

金融相关

import numpy as np
# 终值 = np.fv(利率, 期数, 每期支付, 现值)
# 将1000元以1%的年利率存入银行5年,每年加存100元,
# 到期后本息合计多少钱?
fv = np.fv(0.01, 5, -100, -1000)
print(round(fv, 2))
# 现值 = np.pv(利率, 期数, 每期支付, 终值)
# 将多少钱以1%的年利率存入银行5年,每年加存100元,
# 到期后本息合计fv元?
pv = np.pv(0.01, 5, -100, fv)
print(pv)
# 净现值 = np.npv(利率, 现金流)
# 将1000元以1%的年利率存入银行5年,每年加存100元,
# 相当于一次性存入多少钱?
npv = np.npv(0.01, [
    -1000, -100, -100, -100, -100, -100])
print(round(npv, 2))
fv = np.fv(0.01, 5, 0, npv)
print(round(fv, 2))
# 内部收益率 = np.irr(现金流)
# 将1000元存入银行5年,以后逐年提现100元、200元、
# 300元、400元、500元,银行利率达到多少,可在最后
# 一次提现后偿清全部本息,即净现值为0元?
irr = np.irr([-1000, 100, 200, 300, 400, 500])
print(round(irr, 2))
npv = np.npv(irr, [-1000, 100, 200, 300, 400, 500])
print(npv)
# 每期支付 = np.pmt(利率, 期数, 现值)
# 以1%的年利率从银行贷款1000元,分5年还清,
# 平均每年还多少钱?
pmt = np.pmt(0.01, 5, 1000)
print(round(pmt, 2))
# 期数 = np.nper(利率, 每期支付, 现值)
# 以1%的年利率从银行贷款1000元,平均每年还pmt元,
# 多少年还清?
nper = np.nper(0.01, pmt, 1000)
print(int(nper))
# 利率 = np.rate(期数, 每期支付, 现值, 终值)
# 从银行贷款1000元,平均每年还pmt元,nper年还清,
# 年利率多少?
rate = np.rate(nper, pmt, 1000, 0)
print(round(rate, 2))

1.np.random.seed

在使用numpy时,难免会用到随机数生成器。我一直对np.random.seed(),随机数种子搞不懂。很多博客也就粗略的说,利用随机数种子,每次生成的随机数相同。

两次利用随机数种子后,即便是跳出循环后,生成随机数的结果依然是相同的。第一次跳出while循环后,进入第二个while循环,得到的两个随机数组确实和加了随机数种子不一样。但是,后面的加了随机数种子的,八次循环中的结果和前面的结果是一样的。说明,随机数种子对后面的结果一直有影响。同时,加了随机数种子以后,后面的随机数组都是按一定的顺序生成的。

1.np.random.seed():

这个函数控制着随机数的生成。当你将seed值设为某一定值,则np.random下随机数生成函数生成的随机数永远是不变的。更清晰的说,即当你把设置为seed(0),则你每次运行代码第一次用np.random.rand()产生的随机数永远是0.5488135039273248;第二次用np.random.rand()产生的随机数永远是0.7151893663724195,以此类推。具体例子见以下代码:

import numpy as np
 
np.random.seed(0)
for i in range(6):
    print(np.random.rand())
 
0.5488135039273248
0.7151893663724195
0.6027633760716439
0.5448831829968969
0.4236547993389047
0.6458941130666561
 
np.random.seed(0)
for i in range(3):
    print(np.random.rand())
 
0.5488135039273248
0.7151893663724195
0.6027633760716439
由代码易知,相同的code第二次运行的时候,生成的前三个随机数没变。
值得一提的是,numpy.random.seed()和numpy.random.RandomState()这两个在数据处理中比较常用的函数,两者实现的作用是一样的,都是使每次随机生成数一样。

2.clip

在numpy中,clip函数的原型为clip(self, min=None, max=None, out=None),意思是把小于min的数全部置换为min,大于max的数全部置换为max,在[min,max]之间的数则不变。out返回的是一个数组,这个数值必须和原数值维度相同,不然会报错。

调用clip函数的两种方式,设存在两个numpy.ndarray类型数组t,t1

1.numpy.clip(t, 0, 1, t1) # 这种调用方式,t的值不会改变,修改后的数组存储在t1中

2.t1 = t.clip(1, 2) #这种调用方式比较简洁明了,把修改后的数组存在t1中。。推荐使用这种方式

测试代码如下:

import numpy as np
# 随机生成一个3行四列的矩阵,范围是1--16
np.random.seed(10)
t = np.random.randint(1, 16,(3, 4), dtype=int)
t1 = np.arange(12).reshape(3, 4)
t2 = np.arange(12).reshape(3, 4)
print(t)
print("*"*30)
# 采用第一种方式,在t这个矩阵中,小于5的数改为5,大于12的数改为12,在【5,12】之间的数不变,修改后的数据存储在t1中
np.clip(t, 5, 12, t1)
print(t1)
print("*"*30)

# 采用第二种方式
t2 = t.clip(5, 12)
print(t2)

[[10 14  5  1]
 [ 2 12 13 10]
 [14  1 14  2]]
******************************
[[10 12  5  5]
 [ 5 12 12 10]
 [12  5 12  5]]
******************************
[[10 12  5  5]
 [ 5 12 12 10]
 [12  5 12  5]]

3.argpartition

numpy.argpartition(a, kth, axis=-1, kind=‘introselect’, order=None)

在快排算法中,有一个典型的操作:partition。这个操作指:根据一个数值x,把数组中的元素划分成两半,使得index前面的元素都不大于x,index后面的元素都不小于x。

numpy中的argpartition()函数就是起的这个作用。对于传入的数组a,先用O(n)复杂度求出第k大的数字,然后利用这个第k大的数字将数组a划分成两半。

此函数不对原数组进行操作,它只返回分区之后的下标。一般numpy中以arg开头的函数都是返回下标而不改变原数组。

此函数还有另外两个参数:

  • kind:用于指定partition的算法
  • order:表示排序的key,也就是按哪些字段进行排序

当我们只关心topK时,我们不需要使用np.sort()对数组进行全量排序,np.argpartition()已经够用了。

import numpy as np
a = np.array([9, 4, 4, 3, 3, 9, 0, 4, 6, 0])
print(np.argpartition(a, 4)) #将数组a中所有元素(包括重复元素)从小到大排列,比第5大的元素小的放在前面,大的放在后面,输出新数组索引
print(a[b])
[6 9 4 3 7 2 1 5 8 0]
[0 0 3 3 4 4 4 9 6 9]
注意,排序规则是从0开始排序,即,第0大的元素为“0”,第1大的元素还为“0”,第2大的元素为“3”,以此类推。
如果想输出Top5:
a[np.argpartition(a, -5)[-5:]]    

发布了13 篇原创文章 · 获赞 0 · 访问量 224

猜你喜欢

转载自blog.csdn.net/jaffe507/article/details/105072279