【Pandas学习笔记Task01】:python基础
前言
第一次参与组对学习,第一次写博客,不足之处也希望各位大佬批评指正。
python基础
列表推导式与条件赋值
列表推导式是用来简化代码书写的,需要有表达式,用于列表赋值;需要for循环,用于提供值。他还可以结合相关条件赋值语句(如if)进行滤值。
通常列表推导式有三种形式:
# 1、将for循环遍历的值i传给前面的表达式从而给列表赋值。
num = [i * 2 for i in range(5)] # [0, 2, 4, 6, 8]
# 在1基础上通过if过滤奇数。
num = [i * 2 for i in range(5) if i % 2 == 0] # [0, 4, 8]
# 在1基础上通过if-else将奇数置-1而不是扩大二倍。
num = [i*2 if i % 2 == 0 else -1 for i in range(5)] # [0, -1, 4, -1, 8]
这里需要注意:if-else只能出现在for前面;而for后面只能有if。这就决定了不同位置的条件赋值存在不同的作用。
隐匿函数和map()
隐匿函数lambda与定义def相比是简洁的,而且不需要在意函数名,只需要关注映射关系。它的格式可以用下面简单的x+y理解:
lambda x , y : x+y # lambda 参数: 对参数执行的操作
(lambda x, y :x + y)(3, 4) # 7
而map()会根据提供的函数对指定序列做映射,返回的是map对象,可以用list()转化为列表。所以可以结合map与隐匿函数使用。
# 将list的元素分别代入func中,得到一个map对象,可以用list转为列表
map(func,list)
list(map(lambda x: x *2, range(5))) # [0, 2, 4, 6, 8]
zip()与enumerate()
zip()用于将多个迭代对象相同位置(此位置的标准是按照原迭代对象低一维来算的)的元素连接成一个新的元组,返回zip对象。所以同样地,可以用list实现转换为列表的格式。
a = [[1,2],[3,4]]
b = [[2,2],[3,3]]
c = [[2,2],[3,3]]
list(zip(a, b, c)) # [([1, 2], [2, 2], [2, 2]), ([3, 4], [3, 3], [3, 3])]
zip()可以实现一个压缩结合的操作,而zip(*)可以实现解压操作,所以可用来求矩阵的转置,不过里面一维是按元组来存的。
a = [[1,2,3],[4,5,6]]
list(zip(*a)) # [(1, 4), (2, 5), (3, 6)]
enumerate()是可以实现在遍历过程中,不仅遍历值,还可以得到值所在的索引。
L = ['a', 'b', 'c', 'd']
for index, value in enumerate(L):
print(index, value)
# 0 a
# 1 b
# 2 c
# 3 d
numpy基础
numpy是一个支持大量的维度数组与矩阵运算的库,用它来算数美滋滋。
构造np数组
np.array([1,2,3])
np.linspace(1,5,11,endpoint = True) #起点,终点,样本个数。(包括endpoint,但可以设置endpoint=False去掉endpoint.)
np.arange(1,5,2) # (起点=0),终点,(步长=1)。(不包括endpoint.)
arrange 适用于知道序列中相邻两数之间的间隔的情况下,比如生成一定范围内奇数或者偶数的序列。linspace
适合序列长度和序列取值范围已知的情况。比如采样频率为1200 Hz, 也就是说 0~1s 之间有1200 个点。
还有一些特殊的矩阵。如全零,全1,单位矩阵等。
np.zeros((3, 3)) # 3行3列全0矩阵
np.ones((3, 3)) # 3行3列全1矩阵
np.eye(3) # 3行3列的单位矩阵
还有一些随机矩阵。
np.random.rand() # (0-1均匀分布)这里不要传元组,直接指定不同维度的个数就行
np.random.randn() # 0~1标准正态分布
np.random.randint(low,high,size) # 指定生成随机整数的最小值最大值和维度大小
np.random.choice() # 可以从给定的列表中,以一定概率和方式抽取结果,当不指定概率时为均匀采样,默认抽取方式为有放回抽样
np.random.seed(0) # 设置种子,就相当是设定了随机值,之后每次随机都一样
np数组的转置与合并
(1) 转置为T
num = np.zeros((2, 3)).T # 这是转置矩阵
(2) 行合并与列合并
'''
np.r_[a,b] 连接行,就是将a与b对应的行连接;np.c_[a,b]连接列,就是将a与b对应的列连接。
如果是二维的a与b,就将一维元素作为单位进行连接;如果是一维的a与b,就将每个元素作为单位进行连接。
这里和之前的zip()合并时选取元素的规则有点相似。
我的理解是:
保持其合并前后的维度相同。就比如两个一维的合并,还是一维;两个二维的合并还是二维。不同维度合并会报错。
'''
# 以1维和2维举例如下:
# np.c_ 按列合并
a = np.array([[1, 2, 3],[7,8,9]])
b=np.array([[4,5,6],[1,2,3]])
c=np.c_[a,b] # array([[1, 2, 3, 4, 5, 6],[7, 8, 9, 1, 2, 3]])
d= np.array([7,8,9])
e=np.array([1, 2, 3])
f=np.c_[d,e] # array([[7, 1],[8, 2],[9, 3]])
# np.r_ 按行合并
a = np.array([[1, 2, 3],[7,8,9]])
b=np.array([[4,5,6],[1,2,3]])
c=np.r_[a,b] # [[1, 2, 3],[7,8,9],[4,5,6],[1,2,3]]
d= np.array([7,8,9])
e=np.array([1, 2, 3])
f=np.r_[d,e] # [7,8,9,1,2,3]
(3)维度变换 reshape
num = np.ones((3,1))
# 这里因为reshape后元素个数要相匹配,所以最后一维是定的,所以可以在最后一维指定-1.
num = num.reshape(-1) # 这是将n行1列列表转换为1维的方法
np数组的切片与索引
就是利用一种简便的方法来选择数组中的元素值或者得到它的索引。这在预处理数据集时需要用到。
target = np.arange(9).reshape(3, 3)
target1 = target[:-1, [0, 2]] # 不包括最后一行,选择每一行的第0号和第2号元素。
target2 = target[np.ix_([0, 1], [True, False, True])] # 以布尔索引和值索引形式给出,选择第0行和第1行,每一列的第0号元素和第2号元素。
np数组的常用函数
(1)where
np.where(condition, yes, no) # 根据condition,true执行yes,否则执行no
np.where(condition) # 就一个条件,返回的是符合条件的索引
# 返回索引的特点(下同):原数组几维索引tuple就几维。组合每一维的对应位置就是符合条件的元素坐标
where的例子:
a = np.arange(7)
print(np.where(a <= 5, a, 5)) # 前提是a要为np生成的,否则会报错,把列表当做整体而无法执行列表内元素的比较。
# [0 1 2 3 4 5 5]
print(np.where(a > 5)) # 返回的是a列表的索引
# (array([6], dtype=int64),)
print(a[np.where(a <= 5)]) # 等价于 a[a<=5]
# [0 1 2 3 4 5]
b = np.arange(27).reshape(3, 3, 3)
print(np.where(b > 5)) # np.where(condition)返回的是索引。原数组几维索引tuple就几维。组合每一维的对应位置就是符合条件的元素坐标
# (array([0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2],dtype=int64),array([2, 2, 2, 0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 2, 2, 2],dtype=int64), array([0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2],dtype=int64))
(2)argmax,argmin,nonzero,cumsum, diff
a = np.array([[1, -1, 0], [-3, 3, 4]])
print(np.diff(a)) # 求与前一个元素差,len比原数组小1.全局方法,只能调用np来实现访问diff.
print(a.nonzero()) # (array([0, 0, 1, 1, 1], dtype=int64), array([0, 1, 0, 1, 2], dtype=int64))
# 这里最大最小的索引如果不指明,默认是按行来遍历索引,比如第二行第3列是4,最大,所以返回索引5.
#如果指明0或者1,就代表按列或者按行来找。
print(a.argmax()) # 5 返回最大数的索引
print(a.argmin()) # 3 返回最小数的索引
print(a.cumsum()) # 累加 [ 1 0 0 -3 0 4]
print(a.sum(0)) # [-2 2 4]
print(a.sum(1)) # [0 4]
练习
EX1 利用列表推导式写矩阵乘法
一般的矩阵乘法根据公式,可以由三重循环写出:
np.random.seed(0)
M1 = np.random.randint(1, 9, (2, 3))
M2 = np.random.randint(1, 9, (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
print((np.abs((M1.dot(M2) - res) < 1e-15)).all()) # True 所有元素都符合
print(res) # [[110. 70. 56. 114.][ 56. 24. 26. 56.]]
利用列表推导式实现:
res = [sum(M1[i] * M2.T[j]) for i in range(M1.shape[0]) for j in range(M2.shape[1])]
# [110, 70, 56, 114, 56, 24, 26, 56]
这里我将M1的行当做一个整体,M2的列当做整体来直接进行一维数组的相乘,得到的还是一维数组,然后求和得到值。因为numpy不利用点乘的话,相乘就是逐位相乘的。但实现过程中发现,计算的数值是正确的,但是无法分出维度来。因为惯性思维,先遍历i后遍历j我就先for i然后for j。看到参考答案后,才发现原来是这么玩的:
res = [[sum(M1[i] * M2.T[j]) for j in range(M2.shape[1])] for i in range(M1.shape[0])]
# [[110, 70, 56, 114], [56, 24, 26, 56]]
先遍历i不是嘛,那就让i在后面,让j在一个维度内遍历。这样当一个i对应的j遍历结束后,就代表当前第i行与所有的j列都计算完成了,也就是得到了(i,0),(i,1)…(i,n)的值,那正好这就是一个维度,用[]框住。太妙了。学习了~
EX2 更新矩阵
设矩阵 Am×n ,现在对 A 中的每一个元素进行更新生成矩阵 B ,更新方法是 Bij= B i j = A i j ∑ k = 1 n 1 A i k \displaystyle B_{ij}=A_{ij}\sum_{k=1}^n\frac{1}{A_{ik}} Bij=Aijk=1∑nAik1,例如下面的矩阵为 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 高效实现。
A = np.arange(1, 10).reshape(3, -1)
B = np.empty((A.shape[0], A.shape[1]))
# # 一般方法
for i in range(A.shape[0]):
for j in range(A.shape[1]):
B[i][j] = A[i][j] * sum(1.0/A[i])
print(B)
# 利用numpy,太妙了
B = A * (1/A).sum(1).reshape(3, -1) # 这里reshape的是(1/A).sum(1),然后利用广播机制,实现A *() 的逐位相乘,这是numpy的特性。
print(B.shape) # (3, 3)
print(B)
利用numpy的更新方法很妙。一开始没明白reshape的作用。因为本来维度是(3, ),这里要转换成3行1列,代表二维数组。否则(1/A).sum(1)其实是一个长度为3的一维数组。在广播的时候会出问题(正确的应该是向右,而一维会向下广播)。
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
∑
j
=
1
n
A
i
j
B_{ij} = \frac{(\sum_{i=1}^mA_{ij})\times (\sum_{j=1}^nA_{ij})}{\sum_{i=1}^m\sum_{j=1}^nA_{ij}}
Bij=∑i=1m∑j=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 计算
χ
2
\chi^2
χ2
np.random.seed(0)
A = np.random.randint(10, 20, (8, 5))
B = np.empty((A.shape[0], A.shape[1]), dtype=list)
# B = A.sum(0)*A.sum(1).reshape(-1, 1)/A.sum() # 答案方法
# print(B)
B = np.array([[np.sum(A[i])*np.sum(A.T[j])/np.sum(A) for j in range(A.shape[1])] for i in range(A.shape[0])]) # 我的代码略微复杂了
x2 = np.sum(np.square(A - B) / B)
print(x2)
我的想法就是两步,先求B再计算卡方。因为从EX1中得到学习,知道了怎么计算某元素行与列相乘,就用上了(列表推导式,这回记得将i的迭代放后面了)。做完还得意了一会儿,但看了答案,emm,不得不说给的太妙了,一句搞定,但对初学者简直很painful。
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 函数)
h = input('please input a series of numbers:\n',) # 可以用split实带现空格或者逗号的输入,从而分隔
h_list = np.array(list(map(int, h)))
f = lambda x : np.diff(np.nonzero(np.r_[1, np.diff(x) != 1, 1])).max()
print(f(h_list))
这算是一种简单方法吧,我能想到的是枚举然后找连续(我能想到的大家都能想到,大家想到的我想不到,一个字:菜),O(n^2)复杂度,太难了。完全没想到,利用numpy可以如此简单的来做。主要是觉得这种思维很妙。
首先将差为1(代表连续)的元素在diff()后置为0,然后得到不为0的元素的索引(这里就是前后多加个1的妙处,可以保持元素的绝对位置),也就可以代表不连续的数字的位置,再利用diff求差(这里相当于求连续的长度),得到最大值那就是最大连续整数的长度了。太妙了~差距体现的淋漓尽致。
总结:第一次参与组对学习的第一篇博客,也是第一次写博客,很多不足的地方,如果有大佬看到还请多多批评指正。
通过练习感受到,numpy的强大和简便之处。不过距离那种程度还有很长的路要走,单是理解起来都需要一段时间,更别提自己去按照那种方法实现了。只能说日后努力学习吧。