第一章 预备知识
一、python基础
1、列表推导式与条件赋值
推导式(comprehensions),又称解析式。利用列表推导式、字典推导式、集合推导式可以从一个数据对象构建另一个新的数据对象。列表推导式是python开发时用得最多的技术之一,表示对可迭代对象(Iterable)的元素进行遍历、过滤或再次计算,生成满足条件的新列表。
在逻辑上等价于循环语句,但是形式上更简洁,并且由于python内部对列表推导式做了大量优化,运行速度更快。
- 语法形式:
[function/expression for value1 in Interable1 if condition1
for value2 in Interable2 if condition2
for value3 in Interable3 if condition3
...]
- 例子:
[m+'_'+n for m in ['a', 'b'] for n in ['c', 'd']]
# ['a_c', 'a_d', 'b_c', 'b_d']
除了列表推导式,另一个语法糖是条件赋值
- 语法形式:
value = a if condition else b
- 例子:
value = 'cat' if 2>1 else 'dog'
value
# "cat"
L = [1, 2, 3, 4, 5, 6, 7]
[i if i <= 5 else 5 for i in L]
# [1, 2, 3, 4, 5, 5, 5]
2、匿名函数与map方法
有时候函数的定义具有清晰简单的映射关系,并且不需要多次调用,这时候就可以使用匿名函数的方法简洁地表示。
例子:
[(lambda x: 2*x)(i) for i in range(5)]
# [0, 2, 4, 6, 8]
python内置的map函数可以把一个函数依次映射到可迭代对象(Iterable)的每个元素上,返回一个map对象,map对象是一个迭代器对象(Iterator),迭代器对象是一种可迭代对象。
例子:
list(map(lambda x: 2*x, range(5)))
# [0, 2, 4, 6, 8]
# 多个输入值的函数映射,可以通过追加可迭代对象实现
list(map(lambda x, y: str(x)+'_'+y, range(5), list('abcde')))
# ['0_a', '1_b', '2_c', '3_d', '4_e']
3、zip对象与enumerate方法
zip函数可以将多个可迭代对象中的元素打包成一个元组构成的可迭代对象,返回一个zip对象,zip对象是一个迭代器对象,迭代器对象是一种可迭代对象。
- zip方法常常用于循环迭代。
- 当需要对两个列表建立字典映射时,可以利用
zip
对象
例子:
L1, L2, L3 = list('abc'), list('def'), list('hij')
list(zip(L1, L2, L3))
# [('a', 'd', 'h'), ('b', 'e', 'i'), ('c', 'f', 'j')]
tuple(zip(L1, L2, L3))
# (('a', 'd', 'h'), ('b', 'e', 'i'), ('c', 'f', 'j'))
for i, j, k in zip(L1, L2, L3):
print(i, j, k)
# a d h
# b e i
# c f j
dict(zip(L1, L2))
# {1: 2, 4: 4}
#######
# 解压
#######
list(zip(L1, L2, L3))
# [('a', 'd', 'h'), ('b', 'e', 'i'), ('c', 'f', 'j')]
list(zip(*zip(L1,L2,L3)))
# [('a', 'b', 'c'), ('d', 'e', 'f'), ('h', 'i', 'j')]
enumerate方法是一种特殊的打包,它返回下标和值绑定的enumerate对象,enumerate是一个可迭代对象。常用在循环迭代。
例子:
L = list('abcd')
for index, value in enumerate(L):
print(index, value)
# 0 a
# 1 b
# 2 c
# 3 d
二、Numpy基础
1、np数组的构造
一般方法np.array
一些特殊数组的生成方式:
-
等差序列
np.linspace
起始、终止(包含)、样本个数np.arange
起始、终止(不包含)、步长
-
零矩阵
np.zeros
传入元组表示各维度大小 -
单位阵
np.eye(n)
n*n的单位阵np.eye(n, k=1)
向右偏移主对角线一个单位的伪单位阵
-
填充数组
np.full((2,3), 10)
元组传入大小,10表示填充数值np.full((2,3), [1,2,3])
通过传入列表填充每列的值
-
随机矩阵
np.random.rand(3)
生成服从0-1均匀分布的三个随机数np.random.rand(3, 3)
注意这里传入的不是元组,每个维度大小分开输入np.random.randn(3)
生成服从正态分布的三个随机数np.random.randint(low, high, size)
生成随机整数np.random.choice(my_list, size, replace=False, p=[0.1, 0.7, 0.1 ,0.1])
可以从给定的列表中,以一定概率和方式抽取结果,当不指定概率时为均匀采样,默认抽取方式为有放回抽样
当返回的元素个数与原列表相同且无放回时,等价于使用
permutation
函数,即打散原列表。
2、np数组的变形与合并
- 转置:
T
- 合并:
r_
,c_
- 维度变换:
reshape
3、np数组的切片与索引
start:end:step
np.ix_()
可以使用布尔索引,数组为一维时不需要,可直接索引
4、常用函数
假设下述函数输入的数组都是一维的
where
条件函数,可以指定满足条件与不满足条件位置对应的填充值
a = np.array([-1,1,-1,0])
np.where(a>0, a, 5) # 对应位置为True时填充a对应元素,否则填充5
nonzero
,argmax
,argmin
这三个函数返回的都是索引,nonzero
返回非零数的索引,argmax
, argmin
分别返回最大和最小数的索引,返回的是一个包含索引array的tuple!不是array。
any
,all
any指当序列至少 存在一个 True或非零元素时返回True,否则返回False
all指当序列元素 全为 True或非零元素时返回True,否则返回False
cumprod
,cumsum
,diff
cumprod
, cumsum
分别表示累乘和累加函数,返回同长度的数组,diff
表示和前一个元素做差,由于第一个元素为缺失值,因此在默认参数情况下,返回长度是原数组减1
- 统计函数
max, min, mean, median, std, var, sum, quantile, cov, corrcoef
⭐对于含有缺失值的数组,它们返回的结果也是缺失值,如果需要略过缺失值,必须使用nan*
类型的函数,上述的几个统计函数都有对应的nan*
函数,如np.nanmax()
⭐二维Numpy
数组中统计函数的axis
参数,它能够进行某一个维度下的统计特征计算,当axis=0
时结果为列的统计指标,当axis=1
时结果为行的统计指标
5、广播机制
不同形状(shape)的数组原则上不可以直接进行算术运算,但broadcasting机制可以使得满足一些条件的数组直接进行算术运算,使得他们具有兼容的形状。 Broadcasting需要满足的条件:两个数组的各维度兼容,也就是两个数组的每一维等长,或者其中一个数组为一维,那么numpy会使得维度为一的数组broad来匹配维度大的数组。
6、向量与矩阵的计算
-
向量内积:
dot
a ⋅ b = ∑ i a i b i \rm \mathbf{a}\cdot\mathbf{b} = \sum_ia_ib_i a⋅b=i∑aibi -
向量范数和矩阵范数:
np.linalg.norm
ord | norm for matrices | norm for vectors |
---|---|---|
None | Frobenius norm | 2-norm |
‘fro’ | Frobenius norm | / |
‘nuc’ | nuclear norm | / |
inf | max(sum(abs(x), axis=1)) | max(abs(x)) |
-inf | min(sum(abs(x), axis=1)) | min(abs(x)) |
0 | / | sum(x != 0) |
1 | max(sum(abs(x), axis=0)) | as below |
-1 | min(sum(abs(x), axis=0)) | as below |
2 | 2-norm (largest sing. value) | as below |
-2 | smallest singular value | as below |
other | / | sum(abs(x)ord)(1./ord) |
- 矩阵乘法:
@
[ A m × p B p × n ] i j = ∑ k = 1 p A i k B k j \rm [\mathbf{A}_{m\times p}\mathbf{B}_{p\times n}]_{ij} = \sum_{k=1}^p\mathbf{A}_{ik}\mathbf{B}_{kj} [Am×pBp×n]ij=k=1∑pAikBkj
三、练习
Ex1:利用列表推导式写矩阵乘法
一般的矩阵乘法根据公式,可以由三重循环写出,请将其改写为列表推导式的形式。
M1 = np.random.rand(2,3)
M2 = np.random.rand(3,4)
res = np.empty((M1.shape[0],M2.shape[1]))
for i in range(M1.shape[0]):
for j in range(M2.shape[1]):
item = 0
for k in range(M1.shape[1]):
item += M1[i][k] * M2[k][j]
res[i][j] = item
((M1@M2 - res) < 1e-15).all() # 排除数值误差
# True
#------------------------------------------
res1 = [sum([M1[i][k]*M2[k][j] for k in range(M1.shape[1])]) for i in range(M1.shape[0]) for j in range(M2.shape[1])]
res1 = np.array(res1).reshape(2,4)
((res1-M1@M2)<1e-15).all()
# True
res2 = [[sum(M1[i][k]*M2[k][j]) for k in range(M1.shape[1]) for j in range(M2.shape[0])] for i in range(M1.shape[0])]
((res2-M1@M2)<1e-15).all()
# True
注1:当有多个for循环时,如果是嵌套循环如res1的构造,那么for循环从左到右依次进行,得到的元素依次排列;如果是多层推导式的循环,那么for循环由外而内依次进行,即从右到左。
注2:列表推导式得到的结果一定是列表,但是可以是多层列表
Ex2:更新矩阵
设矩阵 A m × n A_{m×n} Am×n ,现在对 A A A 中的每一个元素进行更新生成矩阵 B B B ,更新方法是 B i j = A i j ∑ k = 1 n 1 A i k B_{ij}=A_{ij}\sum_{k=1}^n\frac{1}{A_{ik}} Bij=Aij∑k=1nAik1 ,例如下面的矩阵为 A A A ,则 B 2 , 2 = 5 × ( 1 4 + 1 5 + 1 6 ) = 37 12 B_{2,2}=5\times(\frac{1}{4}+\frac{1}{5}+\frac{1}{6})=\frac{37}{12} B2,2=5×(41+51+61)=1237 ,请利用 Numpy
高效实现。
$$
\begin{split}A=\left[ \begin{matrix} 1 & 2 &3\4&5&6\7&8&9 \end{matrix} \right]\end{split}
$$
a = np.array([[1,2,3],[4,5,6],[7,8,9]])
res1 = (1/a).sum(axis=1)[:,np.newaxis]*a
# array([[1.83333333, 3.66666667, 5.5 ],
# [2.46666667, 3.08333333, 3.7 ],
# [2.65277778, 3.03174603, 3.41071429]])
# or
res2 = (1/a).sum(1).reshape(-1,1)*a
注:reshape(-1,1)的-1表示自动填充
Ex3:卡方统计量
设矩阵 A m × n A_{m\times n} Am×n,记 B i j = ( ∑ i = 1 m A i j ) × ( ∑ j = 1 n A i j ) ∑ i = 1 m ∑ i = 1 n A i j B_{ij} = \frac{(\sum_{i=1}^mA_{ij})\times (\sum_{j=1}^nA_{ij})}{\sum_{i=1}^m\sum_{i=1}^nA_{ij}} Bij=∑i=1m∑i=1nAij(∑i=1mAij)×(∑j=1nAij),定义卡方值如下:
χ 2 = ∑ i = 1 m ∑ j = 1 n ( A i j − B i j ) 2 B i j \chi^2 = \sum_{i=1}^m\sum_{j=1}^n\frac{(A_{ij}-B_{ij})^2}{B_{ij}} χ2=i=1∑mj=1∑nBij(Aij−Bij)2
请利用Numpy
对给定的矩阵 A A A计算 χ 2 \chi^2 χ2
np.random.seed(0)
A = np.random.randint(10, 20, (8, 5))
B = A.sum(1).reshape(-1,1)*A.sum(0)/A.sum()
((A-B)**2/B).sum()
Ex4:改进矩阵计算的性能
设 Z Z Z为 m × n m×n m×n的矩阵, B B B和 U U U分别是 m × p m×p m×p和 p × n p×n p×n的矩阵, B i B_i Bi为 B B B的第 i i i行, U j U_j Uj为 U U U的第 j j j列,下面定义 R = ∑ i = 1 m ∑ j = 1 n ∥ B i − U j ∥ 2 2 Z i j \displaystyle R=\sum_{i=1}^m\sum_{j=1}^n\|B_i-U_j\|_2^2Z_{ij} R=i=1∑mj=1∑n∥Bi−Uj∥22Zij,其中 ∥ a ∥ 2 2 \|\mathbf{a}\|_2^2 ∥a∥22表示向量 a a a的分量平方和 ∑ i a i 2 \sum_i a_i^2 ∑iai2。
现有某人根据如下给定的样例数据计算 R R R的值,请充分利用Numpy
中的函数,基于此问题改进这段代码的性能。
两个求和可以使用np.sum,要点是计算范式。
KaTeX parse error: No such environment: split at position 8: \begin{̲s̲p̲l̲i̲t̲}̲Y_{ij} &= \|B_i…
np.random.seed(0)
m, n, p = 100, 80, 50
B = np.random.randint(0, 2, (m, p))
U = np.random.randint(0, 2, (p, n))
Z = np.random.randint(0, 2, (m, n))
def solution(B=B, U=U, Z=Z):
L_res = []
for i in range(m):
for j in range(n):
norm_value = ((B[i]-U[:,j])**2).sum()
L_res.append(norm_value*Z[i][j])
return sum(L_res)
solution(B, U, Z)
# 100566
# =============================================
(((B**2).sum(1).reshape(-1,1)+(U**2).sum(0)-2*B@U)*Z).sum()
# 100566
Ex5:连续整数的最大长度
输入一个整数的Numpy
数组,返回其中递增连续整数子数组的最大长度,正向是指递增方向。例如,输入[1,2,5,6,7],[5,6,7]为具有最大长度的连续整数子数组,因此输出3;输入[3,2,1,2,3,4,6],[1,2,3,4]为具有最大长度的连续整数子数组,因此输出4。请充分利用Numpy
的内置函数完成。(提示:考虑使用nonzero, diff
函数)
a = [1,2,5,6,7]
b = [3,2,1,2,3,4,6]
def max_continuousint_length(a):
interval = np.append(np.nonzero(np.diff(a)-1)[0],(len(a)-1))
return(max(np.diff(interval))
print(max_continuousint_length(a))
print(max_continuousint_length(b))
# 3
# 4
# =====================
f = lambda x:np.diff(np.nonzero(np.r_[1,np.diff(x)!=1,1])).max()
注: numpy.nonzero
返回包含一个表示非零元素的index的array的tuple
参考:
datawhale组队学习文档
python高级程序设计