numpy快速入门
numpy快速入门
在numpy中阵列维度称为轴axes,axes中的每一个轴称为axis,每个axis可以有不同的长度
e.g.
[[1, 0, 0],
[0, 1, 2]]
# 其中,axes=2,first axis 长度为2(行),second axis 长度为3(列)
numpy’s array 类被称为 ndarray,通常也用别名 array称呼。有如下重要性质:
.ndim: array的axes数 .shape: array的阵列维数,整型值的元祖,n行m列的矩阵,其shape就是(n, m),shape的len就是axes的值,即等同于.ndim值 .size: array中元素的总数,等于shape元祖中元素的乘积。 .dtype: array中元素的数据类型,可以用标准pyhton数据类型进行创建或指定,也可以用numpy自身的数据类型进行创建或指定。 .itemsize: array中每个元素的字节大小,等价于ndarray.dtype.itemsize .data: 包含数array的实际元素的缓存。通常很少使用这个性质,更有意义的是我们使用array的索引来对array中的元素进行计算。
创建array
有多种创建ndarray的方式:
-
方式一:从常规的Python’s list or tuple利用array函数创建,生成结果array的元素类型由基础序列中元素的类型推导而来(也可以利用dtype强制指定)。
在调用array函数时,传入参数应该是一个列表(元组)对象,否则会引发错误:np.array(1, 2, ,3) # raise a error np.array([1, 2, 3]) # right,由源数据推测数据类型 np.array([1, 2, 3], dtype=np.int16) # right,指定数据类型
对于序列为元素的序列,将其作为array的元素对象,有几层嵌套则生成几维数组:
np.array([(1, 2, 3, 4), (6, 7, 4, 3)]) or np.array([[1, 2, 3, 4], [6, 7, 4, 3]) result:二维数组 shape=(2, 2) np.array([([1, 2], [3, 4]), ([6, 7], [4, 3])]) or np.array([[[1, 2], [3, 4]], [[6, 7], [4, 3]]]) result:三维数组 shape=(2, 2, 2)
-
几种特殊array 函数:
.zeros, # 全0矩阵 .ones, # 全1矩阵 .empty, # 空矩阵,“空”仅是一种描述,实际生成矩阵会有值作为占位。
注意默认生成数据类型是‘float64’,其他数据类型需要手动指定:
np.zeros(shape=(3, 4), dtype=int)
-
生成数字序列:范围内指定步长生成满足条件的全部值(数量未知) and 指定范围内生成指定数量的值(步长未知);
------------------------------------------------------------------------------------ # 范围内指定步长生成满足条件的全部值(数量未知) np.arange(start, end, [step], [dtype]): 类似于range函数,返回arrays np.arange(2, 5, 1) ------------------------------------------------------------------------------------ # 指定范围内生成指定数量的值(步长未知) np.linspace(start, end, num) np.linspace(0, 2, 9) # 返回0-2范围内的9个数
打印array
如果array过大,numpy仅打印array的角落数值,跳过其它值。
如果想全部打印,可以利用set_printoptions来设置:
np.set_printoptions(threshold=np.nan)
基本运算
数组上的算术计算应用在元素级(elementwise),创建一个新的数组并用算术计算结果填充它。
numpy同其它矩阵语言不同之处:
-
array间的*运算是对应元素的乘积
-
array或矩阵乘积需要使用运算符@或者dot函数
a = np.array([[a, b], [c, d]]) b = np.array([[e, f], [g, h]]) a * b = array([[a*e, b*f], [c*g, d*h]]) # elementwise product a @ b = array([[a*e+b*g, a*f+b*h], [c*e+d*g, c*f+d*h]]) # matrix product a.dot(b) = array([[a*e+b*g, a*f+b*h], [c*e+d*g, c*f+d*h]]) # anther matrix product
对于类似+=或者*=这样的累计运算,结果是在当前数组上进行修改而不是创建新的数组。
array_a = np.ones((3, 3), dtype=np.int64)
array_b = np.random.random((3, 3))
print(array_a, array_b)
array_a *= 2
print(array_a)
array_b += a
print(b)
array_a += array_b # 发生类型错误,array_b is not automatically converted to integer type
but:array_a + array_b 可以运行,结果生成新的数组,结果数组数据类型对应于更一般的数组 如 int + float = float
当操作不同类型的数组时,结果数组的类型对应于更一般或更精确的数组(称为向上转换行为)。
array_c = array_a + array_b
array_c.dtype.name # out:'float64'
诸如sum这样的对array中所有元素进行计算的一元运算函数,这类函数都是作为ndarray类的方法来实现的。
array_a.sum() # array_a 是一个ndarray
array_b.max() # array_b 是一个ndarray
默认情况下,sum()这样的函数对array进行计算时,将array假想成一个数值list,而忽略array的形状。
但是,通过指定axis参数,可以强制计算沿着制定的axis而进行。
array_b.sum(axis=1) # sum of each row
array_b.sum(axis=0) # sum of each column
通用函数(universal functions)
numpy提供了一些诸如三角函数这样的常用函数,在numpy中,将其称为“universal functions(ufunc)”.
在numpy内部,这些函数在array中执行elementwise(元素级)的运算,生成一个新的array作为输出。
索引 切片和迭代
一维array可以像python中的list等序列对象一样,进行索引切片和迭代操作。
a = np.arange(10)**3
a[2] # 取特定索引位的值,索引位=2
a[2:5] # 取特定索引范围的值,索引范围[2:5],不含index=5的值
a[:6:2] = -1000 # 等价于a[0:6:2]=-1000,从start索引位到end索引位以间隔为2将对应索引位的值置为-1000
a[::-1] # 将array颠倒顺序,反转。
多维array每一个axis是一个索引值,这些索引在一个以逗号分隔的元组中给出。
def f(x, y):
return 10*x+y
b = np.fromfunction(f, (5, 4), dtype=int)
b[2, 3] # 取第1个axis(row)的第2个索引位和第2个axis(column)的第3个索引位的值
b[0:5, 1] # 取b中0-4行上index=1的列上的值
b[:, 1] # 等价于上一行(数组b的shape=(5, 4))
b[1:3, :] # 取b中aixs=1的1,2索引位对应的axis=2的所有索引位的值(取第2,3行与对应的全部列的值)
当比array中axes少的索引值被提供时,缺失的索引被认为是完整的切片。
b[-1] # b中最后一行,等价于b[-1, :]
多维数组的迭代是关于第一个轴(axis)的:
for row in b:
print b
但是,如果要对数组中的每个元素执行操作,可以使用flat属性,它是数组中所有元素的迭代器:
for element in b.flat:
print element # 会将数组b中的每一个元素打印出来。
数组的shape操作
改变array的shape
一个array的shape由array中每个轴axis上元素数量决定。
a = np.floor(10*np.random.random((3, 4)))
a.shape # out:(3, 4)
array的shape可由多种方式改变,以下三种方式将返回一个修改了的array,同时不会修改源array.
a.ravel() # 返回新的array,将源array中的值进行拉伸成一维array,like list
a.reshape(6, 2) or a.reshape((6, 2)) # 返回源array被用指定数值修改shape的后新的array
a.T # 返回源array转置后的新的array
ndarray.reshape 和 ndarray.resize 方法的区别:
.shape返回修改后的新array,不对源array进行修改
.resize修改源数组自身。
a # out: array([[0., 7., 2., 2.],
# [5., 1., 5., 5.],
# [5., 4., 8., 9.]])
a.resize((2, 6)) # out: array([[0., 7., 2., 2., 5., 1.],
# [5., 5., 5., 4., 8., 9.]])
如果在reshape中任一维度值被置为-1,则其他的维度将会自动计算,被指定为-1的维度会被自动推测为合理的值。
a.reshape((3, -1)) # out: array([[0., 7., 2., 2.],
# [5., 1., 5., 5.],
# [5., 4., 8., 9.]])
数组叠加
数组可以沿着不同的维(axes)堆叠在一起:
>>> a = np.floor(10*np.random.random((2,2)))
>>> a
array([[ 8., 8.],
[ 0., 0.]])
>>> b = np.floor(10*np.random.random((2,2)))
>>> b
array([[ 1., 8.],
[ 0., 4.]])
>>> np.vstack((a,b)) # 纵向叠加
array([[ 8., 8.],
[ 0., 0.],
[ 1., 8.],
[ 0., 4.]])
>>> np.hstack((a,b)) # 水平叠加
array([[ 8., 8., 1., 8.],
[ 0., 0., 0., 4.]])
函数column_stack将1D-array以列的形式叠加成2D-array,等价于2Darray中的hstack方法。
>>> a = np.floor(10*np.random.random((2, 2)))
>>> b = np.floor(10*np.random.random((2, 2)))
>>> a
array([[3., 9.],
[1., 9.]])
>>> b
array([[3., 6.],
[9., 0.]])
>>> np.column_stack((a, b)) # 操作2Darray
array([[3., 9., 3., 6.],
[1., 9., 9., 0.]])
>>> a = np.array([4., 2.])
>>> b = np.array([3., 8.])
>>> np.column_stack((a, b)) # 返回一个2Darray
array([[4., 3.],
[2., 8.]])
>>> np.hstack((a, b)) # 与上一条的结果会不一致。
array([4., 2., 3., 8.])
>>> a[:,np.newaxis] # 将一维换成二维
>>> np.column_stack((a[:, np.newaxis], b[:, np.newaxis]))
>>> np.hstack((a[:, np.newaxis], b[:, np.newaxis])) #与上一条命令结果一致,此时等价关系
与column_stack相对的是row_stack函数,row_stack等价于vstack方法。通常,对于那些二维以上的array,
hstack沿着array的第二维(axes)进行叠加,vstack沿着array的第一个维(axes)进行叠加,
concatenate函数允许一个可选参数,给出连接(concatenation)应该沿着哪个轴进行。
切记:复杂情况下,r_和c_方法是一种通过沿着一个轴axis叠加数字来创建array.允许接收范围符号(“:”)
np.r_[1:4, 0, 4, 3] # out: array([1, 2, 3, 0, 4, 3])
分割数组
hsplit:沿着水平轴进行split,参数:目标array和要分割成的子数组数目或指定在那几列后面进行分割,
分割后生成list,子数组作为list的元素。
>>> a = np.floor(10*np.random.random((2, 12)))
array([[9., 1., 6., 9., 3., 3., 5., 2., 2., 1., 8., 7.],
[3., 7., 6., 4., 4., 5., 7., 5., 5., 3., 2., 5.]])
>>> np.hsplit(a, 3) # 将a分割成3个子数组
[array([[9., 1., 6., 9.],
[3., 7., 6., 4.]]),
array([[3., 3., 5., 2.],
[4., 5., 7., 5.]]),
array([[2., 1., 8., 7.],
[5., 3., 2., 5.]])]
>>> np.hsplit(a, (3, 4)) # 将a在第3和第4列后分割(分割点分别在第3和第4列后)
[array([[9., 1., 6.],
[3., 7., 6.]]),
array([[9.],
[4.]]),
array([[3., 3., 5., 2., 2., 1., 8., 7.],
[4., 5., 7., 5., 5., 3., 2., 5.]])]
>>> np.hsplit(a, (3, 7)) #
[array([[9., 1., 6.],
[3., 7., 6.]]),
array([[9., 3., 3., 5.],
[4., 4., 5., 7.]]),
array([[2., 2., 1., 8., 7.],
[5., 5., 3., 2., 5.]])]
vsplit:沿着垂直轴axis分割数组。
array_aplit:接收指定哪条轴axis来进行分割操作。
数组的副本与视图
在对array进行处理时,array内的数据有时会被复制进一个新的array中进行处理,有时则不会,以下分别就几种情况进行介绍:
-
不做复制 (no copy at all)
简单的复制操作不会对array对象或者array的值进行复制。>>> a = np.arange(9) >>> b = a # 不会有新的对象b被创建 >>> b is a # out: True, a and b 是同一个ndarray对象的两个名称 >>> b.shape = 3, 3 # change the shape of a, 与b.reshape((3, 3))(.shape修改对象本身;.reshape返回新对象不修改对象本身), a, b同时指向同一个对象,所以修改b也就是修改a >>> a.shape # out: (3, 4)
Python将可变对象作为引用传递,因此函数调用不进行复制。
>>> def f(x): print(id(x)) # id 是一个对象的唯一标识符 >>> id(a) # out:379790104 >>> f(a) # out:379790104
-
视图或者是浅层复制 (view or shallow copy)
浅层复制:对象间元素的一一对应复制,新建立的对象的指针指向被复制对象所指向的内存单元,两个对象指向同一个内存单元,与之相对应的是深层复制(deep copy)
不同的数组对象可以共享相同的数据。view方法创建一个新的数组对象,该对象查看相同的数据。>>> c = a.view() >>> c is a # out:false >>> c.base is a # out:true, c是a拥有的数据的视图 >>> c.flags.owndata # out:false >>> c.shape=(3,3) # 修改对象c的shape,对象a的shape维持不变;同理此时修改对象a的shape,对象c的shape维持不变 >>> c[0, 0] = 1024 # 修改对象c中的某位置的值,会同时修改对象a中对应位置的值(当c.shape时,视图c与母体a是互为独立的,当修改c中某个值时视图c与母体a同时改变, 同理修改母体a中某个值时视图c也会做相应改变)
数组切片返回一个视图。修改视图的值会同时修改母体对象的值。
>>> s = a[:, 2] # s是对象a的一个切片 >>> s[:] = 10 # s[:]是对象s的一个视图。
-
深层复制 (deep copy)
copy方法对数组及其数据进行一次完整复制。相对于view(or 浅层复制),copy创建新的内存空间,新对象和源对象指向不同的内存单元。>>> d = a.copy() # 创建一个包含新数据的新数组对象 >>> d is a # out:false >>> d.base is a # out:false copy后的新对象和母体对象a之间不共用任何值 >>> d[0, 0] = 100 # 仅对新对象d进行修改,不会对源对象a做任何修改,新对象d和源对象a互为独立不存在"d引用a的情况",分别指向不同的内存单元,有不同的唯一id标识。
建议:当切片完成后源array不会再被使用时,切片调用copy方法ndarray[start:end].copy()。比如,array_a是一个很大的对象,由array_a切片后生成一个很小的array_b对象,利用切片操作生成array_b后对切片调用copy方法,然后就可以利用del()方法删除不会被再次读取的array_a,释放内存。
>>> a = np.arange(int(1e8)) >>> b = a[:100].copy() >>> del a or del(a) # 删除对象a,释放对象a所占用的内存。
若b=a[:100],则由于b引用了对象a即使执行了del(a),虽然对象已经不存在但是其值仍会驻留在内存中,内存未能成功释放。
写在最后
对于基于python的数据科学笔者还是小学生一枚,不敢高谈教化,只希冀将自己的理解能解释清楚。
读到此处的您,如果我的理解对解答您的问题有所帮助,那我将是很开心的。
能力一般,水平有限,可优化的地方千千…请指正!
祝好!