Python数据处理学习笔记 - ndarray篇

这是我阅读《用Python进行数据分析》一书的笔记、实验和总结。总的来说,这本书主要介绍了Python的ndarray这一矢量化数据结构,基于numpy的pandas这一自动化表格处理(数据聚合、过滤、分析)工具,高质量2D出版物matplotlab这一图像生成工具。

这三大工具适合于对网络爬虫、网站监测等获取并保存在SQL、NoSQL甚至SQLite的数据进行处理。通俗来讲,它类似于表格处理工具Excel,可以进行数据过滤、分析和处理,也可以进行图像绘制和结果生成。不过显然,这三大工具不论是从操作方便性、简洁性、自动化还是图像质量、语言一致性上都远远超过VBA、宏和Excel。

第一篇文章主要介绍numpy的ndarray这一多维数组对象,这是Python进行数据分析的基石,也是保证大量数据处理速度的必要条件。这是第一篇文章。

1.一个简单的例子

如下所示,使用numpy创建随机数,可以看到,其区别于Python的random.random在于其多维属性。

import numpy as np
data1 = np.random.randn(2) # 0维数组就是一个随机数,1维为一个列表...
print(data1)
data = np.random.randn(2,3,4) 
# randn返回一个正态分布数组,2-3-4 shaped,3维数组,
# 第一个维度为2个,第二个维度为3个,第三个维度为4个
# 最后一个维度表现为数字,其前面有三个括号
data
[-0.61465898  0.24995068]

array([[[-2.68468581, -1.79182648,  1.74420883,  0.03535622],
        [-1.47181406, -1.37387988, -1.58428428,  1.04392948],
        [ 0.22234194,  0.27470697,  1.04808109,  0.85817855]],

       [[-0.47442726,  1.58605052, -1.66793281, -1.02318851],
        [ 0.35110236, -1.10114907,  0.09977712,  0.08188061],
        [ 1.13610052, -1.10023828, -0.44927535,  0.96697125]]])

数据可以进行元素级别的加减乘除等运算,区别于Matlab,其所有算术运算都会广播道元素执行。

data * 10
data + data # 维度不变,算术运算对于各个维度子项目单独进行
array([[[-0.62805328,  0.13746167, -0.56001024, -4.62583189],
        [-1.4894299 , -0.62518287,  2.44624749, -3.24258603],
        [ 0.17639528,  1.11677586,  1.23353211, -2.11002009]],

       [[-3.8480865 ,  1.52970949, -0.69938998, -1.6610395 ],
        [-1.33075899, -0.69120282,  2.99064208, -1.11083815],
        [ 1.42852814, -0.7106241 ,  1.15223994,  2.69708766]]])

ndarray有三个重要的方法,shape方法返回结构信息,dtype方法返回元素类型,ndim返回维度信息。

print(data.shape) #(2, 3, 4) # 表示三个维度,每个维度有元素2,3,4
print(data.dtype) #float64
# 两个常用方法是模型和类型 dtype常见的还有int64和float64 除此之外还有Object和字符串
print(data.ndim) # ndim返回维度 3

2.ndarray对象概要

创建ndarray时可以声明其类型,使用dtype参数即可,但numpy会自动根据数据生成一个合适的类型,你也可以使用astype进行数据的转换。同样的,维度一般也会在声明时就确定,但是你亦然可以在创建后进行调整。

你可以使用list或者random、range、zeros等方法或者函数创建ndarray对象。

对于创建的对象可以使用ndarray.shape进行维度查看,使用reshape进行维度变换,比如ndarray.reshape((3,3))。使用ndarray.transpose()进行转置。

2.1 通过list创建ndarray

mydata = [1,2,3,3.5,5]
print(mydata,type(mydata))

data_new = np.array(mydata) # 可以使用np.array来将list转换成为ndarray对象
print(data_new,type(data_new))

mydata = [1,2,3,3.5,5,[3,1]]# 但是不规则数组难以转换
print(mydata,type(mydata))

try:
    np.array(mydata)
except Exception as e:
    print("不能转换=> ",e)
[1, 2, 3, 3.5, 5] <class 'list'>
[1.  2.  3.  3.5 5. ] <class 'numpy.ndarray'>
[1, 2, 3, 3.5, 5, [3, 1]] <class 'list'>
不能转换=>  setting an array element with a sequence.

2.2 特殊方法创建ndarray

常用的有zeros、empty、ones、arange、random.randn。

print(np.zeros(10)) # zeros可以创建全为0的ndarray,可以定义其维度,10为一维10个,3,6为2维
print(np.zeros((3, 6),dtype=int))
# 多维数组的创建需要放在一个元组中,注意不能写错,比如 np.zeros(3,6) 错误 应该为 np.zeros((3,6))
print(np.empty((2, 3, 2)))# empty创建一个填充垃圾数据的数组
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

[[0 0 0 0 0 0]
 [0 0 0 0 0 0]
 [0 0 0 0 0 0]]

[[[0. 0.]
  [0. 0.]
  [0. 0.]]

 [[0. 0.]
  [0. 0.]
  [0. 0.]]]
np.arange(3,18,2,dtype=int) # numpy.arange([start, ]stop, [step, ]dtype=None)
array([ 3,  5,  7,  9, 11, 13, 15, 17])

2.3 ndarray数据类型及转换

ndarray甚至和Python内置的列表都很不相同,因为ndarray需要保证所有的元素类型相同,而Python list则无此限制。ndarray需要保证每个元素长度相等,而Python list则可以不等长,支持任意嵌套。

ndarray常见的数据类型有np.int_(等同于C中long) float_(float64简写) string_ bool_ unicode(固定长度的unicode类型) etc. numpy的一大特点就是兼容C系的语言,因此有很多其他的类型以方便跨语言调用,比如float有float32,float64、int有int32,int64等,其中intc等同于C的int。

一般使用ndarray.dtype()进行数据类型的查看,使用ndarray.astype(type)进行类型转化。

arr1 = np.array([1, 2, 3], dtype=np.float64);arr2 = np.array([1, 2, 3], dtype=np.int32)
print(arr1.dtype);print(arr2.dtype) #     float64 、int32
arr = np.array([1, 2, 3, 4, 5])
print(arr.dtype)
float_arr = arr.astype(np.float64) 
# ndarray.astype(dtype, order='K', casting='unsafe', subok=True, copy=True)
print(float_arr.dtype)  # int32 、float64
# ndarray.astype 可以拷贝一份新的格式转换的ndarray
arr = np.ones((2,3),dtype=np.int);print(arr)
arr2 = arr.astype(np.float);print(arr2)
[[1 1 1]
 [1 1 1]]
[[1. 1. 1.]
 [1. 1. 1.]]
numeric_strings = np.array(['1.25', '-9.6', '42'], dtype=np.string_)
print(numeric_strings)
numeric_strings.astype(float)
[b'1.25' b'-9.6' b'42']

array([ 1.25, -9.6 , 42.  ])
int_array = np.arange(10)
calibers = np.array([.22, .270, .357, .380, .44, .50], dtype=np.float64)
int_array.astype(calibers.dtype) 
# Returns array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])

3.索引和切片

对于一个数据结构,在内存中其占有一片空间区域。一般来说,我们能够对其进行的,就是使用FOR进行遍历。切片和索引是一种简化版的遍历方式,这提高了我们调用数据结构中某个元素的速度和方便程度。对于ndarray而言,索引和切片是处理其内部元素的一个重要构成部分。简要来说:

  • 如果我们需要选取某一行列的内容,使用索引[n]。
  • 如果要选取某几行列,并且这些行列连续,则使用切片[:]。
  • 如果要选取某几行列,并且这些行列不连续,则使用花式索引[[m,n],[m,n]]。
  • 如果需要按照某种条件进行索引,如果精确判断到元素级别,则使用bool型索引ndarray[bool判断表达式]。
  • 如果需要按照某种条件进行索引,但是精确到行或者列或者需要对全或无进行判断,使用bool型索引后,使用any()和all()进行进一步判断。

3.1 基础用法

对于一维数组而言,其和list类型一样,[m:n]表示从m取到n,前包后不包,切片也是如此。所有的操作均是对原始array进行的。

对于多维数组而言,可以将多维数组看作是内嵌的列表,其操作基本相同,但有一点不同的地方是,不同维度的选取可以使用 [维度1] [维度2],还可以使用 [维度1,维度2]。冒号的作用和Python原生切片一样,也就是说,可以在各维度再进行冒号的元素级别选取。

对于元素级别的选取,其支持[a:b:c],其中a为起始元素位置,b为结束元素位置,包含a而不包含b,c为间隔选取距离。如果选取的元素位置不连续,那么使用[[0,2]]这样进行index为0和2的选取。比如第一个维度间隔2选取选取,而只选取第二个维度的0,2位置元素:[::2,[0,2]]

arr = np.array([[1,3,23],[23,33,211]]);print(arr)
print("\n")
print(arr[0,1]) 
# 切片可以使用逗号,表示从不同维度取值,对于一个二维数组,
# 只有一个切片,则返回此维度的内嵌维度所有元素。
print("\n",arr[0])
[[  1   3  23]
 [ 23  33 211]]

3

 [ 1  3 23]
arr[0] = 233 
# 可以就地修改索引处的值,如果替代的是一个数组,但是只有一个字符串,则默认全部复制,
# 如果..arr[0] = [1,2,3,4] # 这个就会出错
print(arr)
[[233 233 233]
 [ 23  33 211]]
arr2d = np.array([[1,2,23],[3,34,111]])
print(arr2d[0][2]) # 23
print(arr2d[0, 2]) # 23 分号切片和list的嵌套列表切片本质上是一样的

Python默认索引有些简略的写法,比如冒号不写前面的则表示从头开始,不写后面的表示直到最后一个。

arr
arr[1:6]

arr2d
arr2d[:2]

arr2d[:2, 1:]

arr2d[1, :2]

arr2d[:2, 2]

arr2d[:, :1]
array([[1],
       [3]])

3.2 布尔型索引

Bool型索引就是纯Bool类型的一个ndarray,其一个好玩之处在于单个元素可以和整个数组进行==比较判断,而Python会自动判断每个元素和其是否相等并返回一个bool型的索引数组。也就是说,运算符可以生成bool型索引,而是用此索引可以从多维数组中获得元素。

本质上来说,numpy在进行ndarray和标量之间的算术运算时,重载了其魔术方法,如果你试图比较ndarray > 4,那么会自动广播到ndarray每一个元素进行比较,之后返回bool型数组。对于 == 也是这样。

bool型索引本质上是一个和ndarray等维的ndarray,其提供了很方便的根据条件对元素进行调整的办法。

names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = ((np.random.randn(7, 4)+3)*10).astype(int)
print(names)
print(data)
['Bob' 'Joe' 'Will' 'Bob' 'Will' 'Joe' 'Joe']
[[13 31 43 47]
 [27 17 30 41]
 [58 18 31 43]
 [40 12 21 26]
 [ 4 28 24 33]
 [41 32 52 19]
 [45 26 17 30]]
names == 'Bob' # 这个很好玩,因为对于Python标准数组来说,只能用 'Bob' in names
n_list = ['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe']; 
print("Bob in names?",'Bob' in n_list,'\n','Bob == list?','Bob' == n_list)
print(names == 'Bob') # 但是问题是,这是个表达式,谁生成了一个新数组?
Bob in names? True 
 Bob == list? False
[ True False False  True False False False]

一个bool列表可以对数组进行更为精细的操作,还可以叠加别的维度的操作

data[names == 'Bob'] # 相当于 data[0]和data[3]的合体,
# bool索引对于多维数组的可操作性更高,因为取出后必然要降维,
# 而是用一般的Python分片索引,取出后则很难继续将不同维度的数据合并。其类似于:
data[[True,False,False,False,False,False,True]] # 这是个诡异的写法,索引中嵌套了一个列表
array([[13, 31, 43, 47],
       [45, 26, 17, 30]])
print(data[names == 'Bob', 2:]) # 同样的,bool型数组可以作为第一维,可以继续对其他维度进行操作
print(data[names == 'Bob', 3])
[[43 47]
 [21 26]]
[47 26]

bool型列表可以保存,可以取反,甚至可以与逻辑操作符合起来使用

print(names != 'Bob')
print(data[~(names == 'Bob')]) # 取反只需要类似于RE中的波浪线标志即可
cond = names == 'Bob' # 这个bool列表可以保存起来下次继续用
print(data[~cond])
[False  True  True False  True  True  True]
[[27 17 30 41]
 [58 18 31 43]
 [ 4 28 24 33]
 [41 32 52 19]
 [45 26 17 30]]
[[27 17 30 41]
 [58 18 31 43]
 [ 4 28 24 33]
 [41 32 52 19]
 [45 26 17 30]]
print(names == 'Bob')
print(names == 'Will')
mask = (names == 'Bob') | (names == 'Will') # 逻辑或操作也可以
#mask = (names == 'Bob') & (names == 'Will') # 逻辑和操作也可以
print(mask)
data[mask]
[ True False False  True False False False]
[False False  True False  True False False]
[ True False  True  True  True False False]

array([[13, 31, 43, 47],
       [58, 18, 31, 43],
       [40, 12, 21, 26],
       [ 4, 28, 24, 33]])

bool型索引不仅仅可以由==,!=运算符生成,还可以由< > 等比较运算符生成

配合索引赋值,可以有效的操作数组的数据

data[data < 30] = 0 # 可以将data < 30 也看做一个bool型索引,比如:
res = (data < 30);print(res,res.dtype,res.shape)
data[res] = 0;print("\n",data)
[[False False False False]
 [False False False False]
 [False False False False]
 [False False False False]
 [False False False False]
 [False False False False]
 [False False False False]] bool (7, 4)

 [[23333    31    43    47]
 [23333 23333    30    41]
 [   58 23333    31    43]
 [   40 23333 23333 23333]
 [23333 23333 23333    33]
 [   41    32    52 23333]
 [   45 23333 23333    30]]
data[names != 'Joe'] = 7
data
array([[    7,     7,     7,     7],
       [23333, 23333,    30,    41],
       [    7,     7,     7,     7],
       [    7,     7,     7,     7],
       [    7,     7,     7,     7],
       [   41,    32,    52, 23333],
       [   45, 23333, 23333,    30]])

3.3 布尔型索引的全部/存在判断

arr = np.random.randn(100);
print((arr > 0).sum()) # Returns 51
arr2 = np.array([True,False,False])
print(arr2.all()) # all为所有都是True吗? Returns False
print(arr2.any()) # any为存在为True的数吗? Returns True

all()可以指定axis轴以进行更为精确地判断

3.4 花式索引

区别于普通切片只是数组的引用,花式索引会返回一个新数组

如果说bool型索引是ndarray的亮点之一的话,那么花式索引则是ndarray的重中之重,因为传统的Python列表无法使用切片取出多维数据,而花式索引则解决了矩阵结构的不同维度数据存取问题。

arr = np.empty((8, 4))
for i in range(8):
    arr[i] = i
arr
array([[0., 0., 0., 0.],
       [1., 1., 1., 1.],
       [2., 2., 2., 2.],
       [3., 3., 3., 3.],
       [4., 4., 4., 4.],
       [5., 5., 5., 5.],
       [6., 6., 6., 6.],
       [7., 7., 7., 7.]])
arr[[4, 3, 0, 6]]
array([[4., 4., 4., 4.],
       [3., 3., 3., 3.],
       [0., 0., 0., 0.],
       [6., 6., 6., 6.]])
arr[[-3, -5, -7]] #反向取值可以从尾部开始取,和python原生的[-1:]是取倒数第一行类似
array([[5., 5., 5., 5.],
       [3., 3., 3., 3.],
       [1., 1., 1., 1.]])
print(np.arange(32))
arr = np.arange(32).reshape((8, 4)) # 等同于numpy.reshape(a, newshape, order='C') 
# a = np.arange(6).reshape((3, 2)); 
# np.reshape(a, (2, 3)) 分别返回 array([[0, 1],[2, 3],[4, 5]])和array([[0, 1, 2],[3, 4, 5]])
print(arr)
arr[[1, 5, 7, 2], [0, 3, 1, 2]] # 叠加的花式索引
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 24 25 26 27 28 29 30 31]
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]
 [20 21 22 23]
 [24 25 26 27]
 [28 29 30 31]]

array([ 4, 23, 29, 10])

3.5 花式索引的叠加和变维

花式索引不仅能够处理第一个维度的数据,还能够处理其余维度的数据,比如[:,[1,2,3]]这种写法

print(arr[[1,5,7,2]])
print(arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]]) #类似于:
print(arr[[1,5,7,2]][[0,3,1,2]]) 
# 其实不是,这个写法只是两次花式索引,也就是调了两次位置,维度没有变化
# 要想维度变化的索引,可以使用: [;,[0,3,1,2]] 这种深入维度的索引,
# 其直接到了第三个维度,也就是元素维度进行操作
[[ 4  5  6  7]
 [20 21 22 23]
 [28 29 30 31]
 [ 8  9 10 11]]
[[ 4  7  5  6]
 [20 23 21 22]
 [28 31 29 30]
 [ 8 11  9 10]]
[[ 4  5  6  7]
 [ 8  9 10 11]
 [20 21 22 23]
 [28 29 30 31]]
[[ 0  3  1  2]
 [ 4  7  5  6]
 [ 8 11  9 10]
 [12 15 13 14]
 [16 19 17 18]
 [20 23 21 22]
 [24 27 25 26]
 [28 31 29 30]]
arr = np.arange(24).reshape((2,3,4));print(arr)
arr[:,:,[0,2,1,3]] 
# 可以直接调整第三个维度,前两个保持复制,但是 arr[[1,0],:,[0,2,13]]就不可以,
# 相反,使用此替代:
arr2 = arr[[1,0]][:,:,[0,2,1,3]];print(arr2)
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]

[[[12 14 13 15]
  [16 18 17 19]
  [20 22 21 23]]

 [[ 0  2  1  3]
  [ 4  6  5  7]
  [ 8 10  9 11]]]

注意上面的写法,我们首先进行了一个花式索引,之后又对全新的数组进行了一个花式索引,直接调整第三个维度的数组,这里虽然可以直接调整第三个维度,但是不能直接在一个花式索引中完成所有步骤,因此分开做。

4.多维数组的排序

注意:ndarray.sort()是就地排序,而np.sort()则是复制一份新数据

arr = np.random.randn(6);print(arr)
arr.sort();print(arr)
[ 0.35565611  1.16969213 -1.32968207  0.69269737 -0.81785189  0.20410329]
[-1.32968207 -0.81785189  0.20410329  0.35565611  0.69269737  1.16969213]
arr = np.random.randn(5, 3);print(arr)
arr.sort(1);print(arr)
[[-0.61301185 -0.59273517  0.8498737 ]
 [ 0.7429723  -1.61620148  2.43280083]
 [ 1.12530568  0.05152429 -0.0077932 ]
 [ 0.1519599   0.37766685 -0.38625678]
 [-0.11491053 -0.39637008  0.51807785]]
[[-0.61301185 -0.59273517  0.8498737 ]
 [-1.61620148  0.7429723   2.43280083]
 [-0.0077932   0.05152429  1.12530568]
 [-0.38625678  0.1519599   0.37766685]
 [-0.39637008 -0.11491053  0.51807785]]
arr = np.array([[1,4],[3,1]]);print(arr);arr2 = arr.copy() 
# 须知:使用copy才能复制一个数组,而使用[:]切片则不会
arr2.sort(axis=0);print(arr2);
arr.sort(axis=1);print(arr);
[[1 4]
 [3 1]]
[[1 1]
 [3 4]]
[[1 4]
 [1 3]]

复制一个数组,sort的numpy方法可以生成新的。使用copy也可以,使用花式索引也行,但是使用Python的[:]则不行——起码对于多维数组无效

arr = np.array([[1,4],[3,1]]);print("原始的arr值为\n",arr)
arr2 = arr[:];arr.sort();
print("对arr进行sort后,切片复制的arr2因为arr的改变而改变了\n",arr2);
print("现在的arr为:\n",arr)
arr3 = arr.copy(); print("但是使用copy命令的arr3则不变\n",arr3)
原始的arr值为
 [[1 4]
 [3 1]]
对arr进行sort后,切片复制的arr2因为arr的改变而改变了
 [[1 4]
 [1 3]]
现在的arr为:
 [[1 4]
 [1 3]]
但是使用copy命令的arr3则不变
 [[1 4]
 [1 3]]

5.多维数组计算和统计基础

5.1 数组和数组、标量间的运算

数组和数组间的运算,如果维度相同则应用到元素级别;数组和标量之间的运算,则自动扩大到元素级别

arr = np.array([[1,2,3],[9,8,7]]);print(arr)
print(arr*arr,arr-0.5*arr,arr*0,sep="\n\n")
[[1 2 3]
 [9 8 7]]
[[ 1  4  9]
 [81 64 49]]

[[0.5 1.  1.5]
 [4.5 4.  3.5]]

[[0 0 0]
 [0 0 0]]

和Matlab不同的是,numpy中使用np.dot(A,B)来计算A和B的乘积,而A*B则会对每个对应元素进行广播并求积。

5.2 通用函数:数学和统计方法

ndarray有很多通用函数,比如数学计算类,统计类和随机、步进生成类函数,这些函数使用很方便。

arr = np.arange(10)
arr
np.sqrt(arr)
np.exp(arr)
x = np.random.randn(8)
y = np.random.randn(8)
x
y
np.maximum(x, y)
arr = np.random.randn(7) * 5
arr
remainder, whole_part = np.modf(arr)
remainder
whole_part
arr
np.sqrt(arr)
np.sqrt(arr, arr)
arr

比如add()、sqrt()、sin()等数学方法,以及sun()、std()、mean()这种统计方法。所有的方法都可以直接对ndarray使用(面向对象),也可以调用np.method_name(ndarray)使用。

arr = np.random.randn(5, 4);print(arr)
print(arr.mean())
print(np.mean(arr))
print(arr.sum())
[[ 0.14869538  0.97301266  0.01436791 -0.35922877]
 [-1.25616574  0.54107061  0.43904889  0.68787756]
 [-1.45833961  0.50582051 -0.75305018  1.36064269]
 [ 1.43912466 -1.2979654  -1.13902023  0.07277661]
 [-2.19636042  1.14267716 -0.37483159 -0.71086429]]
-0.11103557942956728
-0.11103557942956728
-2.2207115885913455

可以对轴进行单独的计算和统计,类似于Excel的表格计算统计数据

arr = np.array([[0,1,2],[1,2,3],[0,1,1]]);print(arr)
print("\n",arr.sum(axis=0))
print("\n",arr.sum(axis=1))
print("\n",arr.sum(axis=(0,1))) 
# 0表示第一个轴,1表示第二个轴,传入一个tuple对象表示计算两轴总和
arr2 = np.array([[[0,1],[2,3]],[[4,5],[1,2]]]);
print(arr2,arr2.shape)
print("\n","第一个轴之和为\n",arr2.sum(axis=0)) 
# [0 1 2 3] + [4 5 1 2] 好玩的是其结果并非[4 6 3 5]而是[[4 6][3 5]],这点需要注意
print("\n","第二个轴之和为\n",arr2.sum(axis=1))
print("\n","第三个轴之和为\n",arr2.sum(axis=2))
[[0 1 2]
 [1 2 3]
 [0 1 1]]

 [1 4 6]

 [3 6 2]

 11
[[[0 1]
  [2 3]]

 [[4 5]
  [1 2]]] (2, 2, 2)

 第一个轴之和为
 [[4 6]
 [3 5]]

 第二个轴之和为
 [[2 4]
 [5 7]]

 第三个轴之和为
 [[1 5]
 [9 3]]
arr = np.array([0, 1, 2, 3, 4, 5, 6, 7])
arr.cumsum()
  • cumsum / cumprod 返回累积和和累积乘积
  • std / var 返回标准差和方差
  • min / max 返回最大最小值
  • mean 返回平均数
  • sum 返回总和
  • argmax / argmin 返回索引最大最小值
arr = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
arr
arr.cumsum(axis=0)
arr.cumprod(axis=1)

5.3 数组转置

使用ndarray的T方法,transpose方法或者swapaxes方法可以转置一个数据

所有的转置都是直接变换,不进行复制

arr = np.arange(15).reshape((3, 5));print(arr)
arr.T
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]]

array([[ 0,  5, 10],
       [ 1,  6, 11],
       [ 2,  7, 12],
       [ 3,  8, 13],
       [ 4,  9, 14]])
arr = np.random.randn(6, 3);print(arr,arr.T,sep="\n\n")
np.dot(arr.T, arr) # 矩阵计算乘积
[[-0.84396988  0.20737763  0.45249126]
 [-1.580369    1.93471643  0.77584114]
 [-0.79764225  0.18855741 -0.8959513 ]
 [ 0.32286382  0.07846277 -0.81797861]
 [ 0.64822643 -0.6630227  -1.51781549]
 [ 0.2819024   1.34665744  1.7859746 ]]

[[-0.84396988 -1.580369   -0.79764225  0.32286382  0.64822643  0.2819024 ]
 [ 0.20737763  1.93471643  0.18855741  0.07846277 -0.6630227   1.34665744]
 [ 0.45249126  0.77584114 -0.8959513  -0.81797861 -1.51781549  1.7859746 ]]

array([[ 4.44999201, -3.40781779, -1.63786897],
       [-3.40781779,  6.08092881,  4.77319213],
       [-1.63786897,  4.77319213,  7.77196467]])
arr = np.arange(16).reshape((2, 2, 4));print(arr)
arr.transpose((1, 0, 2))
[[[ 0  1  2  3]
  [ 4  5  6  7]]

 [[ 8  9 10 11]
  [12 13 14 15]]]

array([[[ 0,  1,  2,  3],
        [ 8,  9, 10, 11]],

       [[ 4,  5,  6,  7],
        [12, 13, 14, 15]]])
arr
arr.swapaxes(1, 2)

6.多元数组表达式基础

6.1 迭代,切分和合并

ndarray使用for循环迭代结果如下,可以看到,一次迭代返回行,二次迭代返回行元素。

a = array(
   [[92, 94, 73, 65],
   [94, 52, 97, 64],
   [46, 88, 43, 14],
   [51, 50, 69, 46]])

for x in a:
    print(x) #x为[92 94 73 65]
    for y in x:
        print(y) #y为92
        break
    break

#使用ndarray.flat可以拍平数组为1维,然后变成一个列表,直接输出。
for x in a.flat:
    print(x) #92 94 73 65 94 52 ...

对于合并而言,使用全局函数np.v(h)stack进行栈在不同方向上的合并:

a = np.zeros((3,3))
b = np.ones((3,3))
c = np.random.randint(10,100,size=(3,4))

np.vstack((a,b)) #a,c不能垂直合并
array([[0., 0., 0.],
   [0., 0., 0.],
   [0., 0., 0.],
   [1., 1., 1.],
   [1., 1., 1.],
   [1., 1., 1.]])

np.hstack((a,c,b)) #注意stack接受的值要放在一个tuple中。

array([[ 0.,  0.,  0., 99., 77., 21., 15.,  1.,  1.,  1.],
    [ 0.,  0.,  0., 92., 74., 48., 31.,  1.,  1.,  1.],
    [ 0.,  0.,  0., 66., 16., 66., 70.,  1.,  1.,  1.]])

使用split进行切分:

numpy.split(ary, indices_or_sections, axis=0)
#其中ary为需要切分的原数组,
#第二个参数为切片,比如[2,4]是切三片,第二片为[2,4)
#第三个参数指定工作的轴。
#除了使用split,还可以使用hsplit、vsplit进行水平和垂直切分。

d=[[ 0.  0.  0. 99. 77. 21. 15.  1.  1.  1.]
  [ 0.  0.  0. 92. 74. 48. 31.  1.  1.  1.]
  [ 0.  0.  0. 66. 16. 66. 70.  1.  1.  1.]]

np.split(d,[3,6],axis=1)

[   array([
    [0., 0., 0.],
    [0., 0., 0.],
    [0., 0., 0.]]), 

    array([
    [99., 77., 21.],
    [92., 74., 48.],
    [66., 16., 66.]]), 
    
    array([
    [15.,  1.,  1.,  1.],
    [31.,  1.,  1.,  1.],
    [70.,  1.,  1.,  1.]])
] #切片结果为一个list。

6.2 条件逻辑的矢量化运算:np.where

使用np.where可以将 x if cond else y 表示成 np.where(cond,x,y)

其中cond可以为bool型索引,也可以为表达式,比如 [T,F,F,T] or arr > 5.

另外,np.where可以嵌套,比如 arr=np.were(cond1,np.where(cond2,x,y),z)

xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
cond = np.array([True, False, True, True, False])
result = np.where(cond, xarr, yarr)
result
array([1.1, 2.2, 1.3, 1.4, 2.5])
arr = np.random.rand(4,4);print(arr)
arr2 = np.where(arr > 0.5,0,arr*10);print(arr2)
[[0.53360993 0.75904481 0.41544517 0.82982399]
 [0.99958328 0.26709263 0.68368172 0.98439953]
 [0.57353012 0.63552816 0.27177872 0.75717192]
 [0.55308005 0.83160888 0.55769017 0.13227596]]
[[0.         0.         4.1544517  0.        ]
 [0.         2.67092631 0.         0.        ]
 [0.         0.         2.71778721 0.        ]
 [0.         0.         0.         1.32275961]]

6.3 唯一、存在的判断:unique,is1d,isin

names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
print(np.unique(names),names) #unique仅复制而不改变原有数组
ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4])
np.unique(ints)
['Bob' 'Joe' 'Will'] ['Bob' 'Joe' 'Will' 'Bob' 'Will' 'Joe' 'Joe']

array([1, 2, 3, 4])
sorted(set(names)) => ['Bob', 'Joe', 'Will']

np.in1d() 测试一个list在另外一个list中是否存在,对被比较list的每个值进行比较,返回一个bool型数组

values = np.array([6, 0, 0, 3, 2, 5, 6])
np.in1d(values, [2, 3, 6])
array([ True, False, False,  True,  True, False,  True])

np.isin()可以判断一个元素是否在另一个元素内,返回一个bool型数组。

element = array([[0, 2],
                [4, 6]])

test_elements = [1, 2, 4, 8]

mask = np.isin(element, test_elements)

#结果是;
array([[ False,  True],
    [ True,  False]])

7.矢量化运算基础

7.1 广播和矩阵

使用数组表达式代替循环的方法称之为矢量化,其速度更快。

meshgrid为广播,其接受两个一维数组并产生两个T变换的二维矩阵,这两个二维矩阵对应所有的(x,y)对。

points = np.arange(-5, 5, 0.01) # 1000 equally spaced points
print(points.shape)
xs, ys = np.meshgrid(points, points) # 将一维数组扩充为2维数组,在第二个维度单纯复制。
# xs为一维数组横向形式,纵向赋值,ys为纵向形式,横向复制
print(xs,ys,sep="\n\n\n");print(xs.dtype,xs.shape)
(1000,)
[[-5.   -4.99 -4.98 ...  4.97  4.98  4.99]
 [-5.   -4.99 -4.98 ...  4.97  4.98  4.99]
 [-5.   -4.99 -4.98 ...  4.97  4.98  4.99]
 ...
 [-5.   -4.99 -4.98 ...  4.97  4.98  4.99]
 [-5.   -4.99 -4.98 ...  4.97  4.98  4.99]
 [-5.   -4.99 -4.98 ...  4.97  4.98  4.99]]


[[-5.   -5.   -5.   ... -5.   -5.   -5.  ]
 [-4.99 -4.99 -4.99 ... -4.99 -4.99 -4.99]
 [-4.98 -4.98 -4.98 ... -4.98 -4.98 -4.98]
 ...
 [ 4.97  4.97  4.97 ...  4.97  4.97  4.97]
 [ 4.98  4.98  4.98 ...  4.98  4.98  4.98]
 [ 4.99  4.99  4.99 ...  4.99  4.99  4.99]]
float64 (1000, 1000)
z = np.sqrt(xs ** 2 + ys ** 2)
z
array([[7.07106781, 7.06400028, 7.05693985, ..., 7.04988652, 7.05693985,
        7.06400028],
       [7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
        7.05692568],
       [7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
        7.04985815],
       ...,
       [7.04988652, 7.04279774, 7.03571603, ..., 7.0286414 , 7.03571603,
        7.04279774],
       [7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
        7.04985815],
       [7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
        7.05692568]])
%matplotlib inline
import matplotlib.pyplot as plt
plt.imshow(z, cmap=plt.cm.gray); plt.colorbar()
plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values")
Text(0.5,1,'Image plot of $\\sqrt{x^2 + y^2}$ for a grid of values')

png

plt.draw()
plt.close('all')

7.2 线性代数相关

python numpy提供的线性代数包为numpy.linalg。和matlab有区别,numpy的*是元素级别的,而np.dot(x,y)或者x.dot(y)才是矩阵乘积

x = np.array([[1., 2., 3.], [4., 5., 6.]])
y = np.array([[6., 23.], [-1, 7], [8, 9]])
print(x)
print(y)
x.dot(y)
[[1. 2. 3.]
 [4. 5. 6.]]
[[ 6. 23.]
 [-1.  7.]
 [ 8.  9.]]

array([[ 28.,  64.],
       [ 67., 181.]])
np.dot(x, y)
np.dot(x, np.ones(3))
x @ np.ones(3)
from numpy.linalg import inv, qr
X = np.random.randn(5, 5)
mat = X.T.dot(X)
inv(mat)
mat.dot(inv(mat))
q, r = qr(mat)
r

7.3 随机数生成

可以使用numpy.random.normal(loc=0.0, scale=1.0, size=None)生成标准分布随机数,比如:

s = np.random.normal(0,0.1,(4,4)) # 平均数为0,标准差为0.1 4*4维数组
s
array([[-0.03907376,  0.12275294,  0.05882021, -0.01106293],
       [-0.04382717,  0.03827646,  0.12300646, -0.07722948],
       [ 0.00270246,  0.10688825, -0.04299091, -0.09184912],
       [-0.01651154,  0.21520002, -0.01527374, -0.09653402]])
mu = 0;sigma = 0.1
s = np.random.normal(mu,sigma,10000)
import matplotlib.pyplot as plt
count, bins, ignored = plt.hist(s, 30, normed=True)
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
                np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
          linewidth=2, color='r')
plt.show()

png

from random import normalvariate
N = 1000000
%timeit samples = [normalvariate(0, 1) for _ in range(N)]
%timeit np.random.normal(size=N)
1.06 s ± 36.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
45.7 ms ± 1.81 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
np.random.seed(1234)
np.random.rand()
0.1915194503788923

rand()产生随机数,randn()产生标准正态分布,normal()产生高斯正态分布,binormal产生二项分布,randint()产生有边界的随机数

shuffle()对一个序列进行随机排列 chiquare产生卡方分布

np.random.uniform() # 产生[0,1)直接均匀的分布样本值
print(np.random.binomial(10,0.5,100)) # 产生二项分布随机值
arr = np.arange(10); np.random.shuffle(arr); print("\n",arr)
[3 5 6 6 3 5 6 6 3 4 2 6 3 3 2 5 6 6 5 7 7 7 4 7 6 6 3 6 5 7 3 7 8 5 5 4 4
 8 6 5 6 5 6 5 2 5 4 6 6 6 6 7 6 7 1 5 7 5 6 4 2 4 5 5 4 6 7 6 7 7 4 6 4 6
 7 5 4 3 6 2 5 7 6 8 2 6 6 4 3 5 4 3 5 2 4 4 8 5 4 2]

 [3 8 5 0 6 9 4 7 1 2]

8.多维数组文件读写

可以使用 np.load()np.save() 来保存以及加载二进制文件,其格式为npy。使用 savez() 可以保存多个文件到一个压缩文件中,其格式为npz,数组作为参数传递。

arr = np.arange(10)
np.save('some_array', arr)
np.load('some_array.npy')
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.savez("lotofarray", a=arr, b=arr)
arch = np.load('array_archive.npz');print(arch,arch.files)
arch['b'] #类似于字典的方式取出相应参数,使用files查看所包含的参数
<numpy.lib.npyio.NpzFile object at 0x000000CD76D3A358> ['a', 'b']

array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.savez_compressed('arrays_compressed.npz', a=arr, b=arr)

此外,numpy还给出了读取txt以及保存txt文件的方法,可以直接读取为array,比如 loadtxtsavetxt

np.savetxt("savedtxt",arr,delimiter="::")
np.loadtxt("savedtxt",delimiter="::")
array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])

9.例子:随机漫步

1000次漫步

import random
position = 0
walk = [position]
steps = 1000
for i in range(steps):
    step = 1 if random.randint(0, 1) else -1
    position += step
    walk.append(position)
%matplotlib inline
plt.figure()
<matplotlib.figure.Figure at 0xcd7f99cbe0>

<matplotlib.figure.Figure at 0xcd7f99cbe0>
plt.plot(walk[:100])
[<matplotlib.lines.Line2D at 0xcd0368bd30>]

png

np.random.seed(12345)
nsteps = 1000
draws = np.random.randint(0, 2, size=nsteps);#print(draws)
steps = np.where(draws > 0, 1, -1);#print(steps)
walk = steps.cumsum();#print(walk) # numpy.cumsum(a, axis=None, dtype=None, out=None)[source]
# Return the cumulative sum of the elements along a given axis.
plt.plot(walk)
print(walk.min())
print(walk.max())
-21
15

png

(np.abs(walk) >= 10).argmax() # numpy.argmax(a, axis=None, out=None)[source]
# Returns the indices of the maximum values along an axis.
179

5000次模拟1000次漫步

nwalks = 5000
nsteps = 1000
draws = np.random.randint(0, 2, size=(nwalks, nsteps)) # 0 or 1
steps = np.where(draws > 0, 1, -1)
walks = steps.cumsum(1)
print(walks,walks.shape)
[[ -1   0   1 ... -18 -17 -18]
 [ -1  -2  -1 ...   0  -1   0]
 [  1   0   1 ...  18  19  20]
 ...
 [  1   2   1 ...  -2  -3  -2]
 [  1   2   3 ...  22  21  20]
 [ -1  -2  -1 ...  26  25  26]] (5000, 1000)
print(walks.max())
print(walks.min())
138
-133
plt.draw()
plt.plot(walks[2][:1000])
plt.show()

png

hits30 = (np.abs(walks) >= 30).any(1) # 不是所有5000次都达到了30,any表示一次即可
hits30 # Test whether any array element along a given axis evaluates to True.
hits30.sum() # Number that hit 30 or -30
3412
crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1)
crossing_times.mean()
499.0468933177022
steps = np.random.normal(loc=0, scale=0.25,
                         size=(nwalks, nsteps))

更新历史:

2018-02-23 阅读《利用Python进行数据分析》并撰写笔记

2018-03-22 阅读《Python数据分析实战》 并修改笔记,添加了numpy迭代、split和stack的介绍。整理了目录。

2018-03-27 细节调整。增加了argmax、argmin的介绍。

2018-04-25 细节调整。