全文字数:10146字
阅读时间:25分钟
前言
我们都知道在Python中有一个list的数据类型,list拥有强大的功能,它是元素的集合并且它里面的元素可以是任何Python数据类型,list可以很方便的对它里面的元素进行增删改查的操作。但是对于科学计算来说需要满足下面两点:
heigh = [1.74,1.68,1.71]
weight = [65.4,59.2,63.6]
ibm = heigh/weight**2
print(ibm)
Traceback (most recent call last):
File "G:/Python源码/numpy_test/numpy_test.py", line 58, in <module>
ibm = heigh/weight**2
TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'int'
从上面的代码可以看出: list并不能直接对整个集合进行数学的相关操作,因为list中的元素可以是任意类型,如果进行数学运算的话list不知道元素的数据类型。
我们的解决方案就是我们的NumPy模块。NumPy(Numerical Python的简称)是高性能科学计算和数据分析的基础包。由于NumPy提供了一个简单易用的C API,因此很容易将数据传递给低级语言编写的外部库,外部库也能以NumPy数组的形式将数据返回给Python。这个功能使Python成为一种包装C/C++/Fortran历史代码库的选择,并使被包装库拥有一个动态的、易用的接口。numpy数组是Python中list数据类型的一个替代品,它能够对整个数组(集合)进行数学的操作。
import numpy as np
np_heigh = np.array([1.74,1.68,1.71])
np_weight = np.array([65.4,59.2,63.6])
ibm = np_heigh/np_weight**2
print(ibm)
[ 0.00040681 0.00047936 0.00042275]
从上面代码可以看出: 我们可以很轻松的对numpy ndarray进行集合的数学操作。
我们说完了对于科学计算很重要的能直接对集合进行数学操作。那速度是如何体现出来的呢?
#在list中
list = [1.0,'is',True]
print(list)
[1.0, 'is', True]
#在numpy中
import numpy as np
np_a = np.array([1.0,'is',True])
print(np_a)
['1.0' 'is' 'True']
从上面代码可以看出:
a
Numpy的ndarray:一种多维数组对象
我们从上面了解到,我们使用array函数创建的对象都是ndarray,其实这也是NumPy的最重要的一个特点N维数组对象,这个对象是一个快速而灵活的大数据集容器。我们通过上面对集合进行数学运算时候也看到了,我们可以利用ndarray这种数组对整块的数据执行一些数学运算。当然他的语法和标量元素之间的运算是一样的。
还有一点我们上面也提到了,就是ndarray是一个通用的同构数据多维容器,也就是说,其中所有元素必须是相同类型的(与Python中的list很明显的区别)
b
Numpy数组可以表示的类型
你肯定会说这里又是数组有是ndarray对象的,他们有什么区别呢?其实我们可以认为他们是等同的。我们从上面知道了对于list数据类型的替代品numpy ndarray。其实ndarray就是n维的一个array,我们可以通过numpy的array函数来创建一个ndarray对象。ndarray可以有任意数量的维度。由于他可以存储任意数量的维度,所以我们可以使用ndarray来表示我们所熟知的任意数据的类型。下面我就详细的介绍用numpy ndarray来表示标量、向量、矩阵以及张量。
当然在介绍能表示的数据之前,我们需要先了解ndarray对象的shape属性以及说明数组数据类型的dtype对象(说白了就是数组中元素的类型)。shape他返回的是一个表示各个维度大小的元组,当然如果想要改变形状,可以使用ndarray.shape = 元组进行更改,也可以使用asshape函数。dtype说明数组数据类型的对象,由于我们上面说过,我们的数组是一个通用的数据多维容器,所以他里面的元素的类型都是一样的,也就是说对于我们每个数组来说,他的dtype只有一个。
import numpy as np
array = np.array([1,2,3])
print(array)
print(array.shape)
print(array.dtype)
print(type(array))
[1,2,3]
(3,)
int32
<class 'numpy.ndarray'>
我们都知道在Python中有int、float、string...这些基本的数据类型,所以能表示的标量可以是整数、浮点数以及字符...类型。但在NumPy中他能表示的标量的类型比Python所能表示的还要多。NumPy 可以让你指定有符号和无符号的类型以及不同的大小。因此,你可以使用 uint8、int8、uint16、int16、uint32、int32、uint64、int64、float8、float16、float32、float64等类型,后面的8、16、32以及64是代表位数。后面讲dtype时候在详细的讲解他们的类型。
import numpy as np
scalar = np.array(5)
print(scalar)
print(scalar.dtype)
print(scalar.shape)
print(type(scalar))
print("---------------")
scalar = scalar + 15
print(scalar)
print(scalar.dtype)
print(scalar.shape)
print(type(scalar))
5
int32
()
<class 'numpy.ndarray'>
---------------
20
int32
()
<class 'numpy.int32'>
从上面的代码可以看出:
import numpy as np
vector = np.array([1,2,3,4,5])
print(vector)
print(vector.dtype)
print(type(vector))
print(vector.shape)
[1 2 3 4 5]
int32
<class 'numpy.ndarray'>
(5,)
从上面的代码可以看出:
a = (5)
print(a)
print(type(a))
print("----------")
b = (5,)
print(b)
print(type(b))
5
<class 'int'>
----------
(5,)
<class 'tuple'>
import numpy as np
matrices = np.array([[1,2,3],
[4,5,6],
[7,8,9]])
print(matrices)
print(matrices.dtype)
print(matrices.shape)
print(type(matrices))
[[1 2 3]
[4 5 6]
[7 8 9]]
int32
(3,3)
<class 'numpy.ndarray'>
c
创建数组(ndarray)
上面的代码使用了创建数组最简单的函数array。他接受一切的序列型的对象(序列型的对象可以是list,tuple...其他序列类型,当然这个函数参数也可以是另一个ndarray)。我们使用list序列对象作为array函数的参数为例:
import numpy as np
array = np.array([1,2,3])
print(array)
print(array.dtype)
[1 2 3]
int32
从上面代码可以看出: 我这里并没有给数组中的元素指定一个类型值,但是我的np.array会尝试为新建的这个数组推断出一个较为合适的数据类型本例中是int32。数据类型保存在一个特殊的dtype对象中。
当然也就是说数组中的元素类型不一致,并且我们没有进行显示的给dtype参数赋值的话(当然我们可以在创建ndarray对象的时候给dtype赋值指定数据类型),np.array就会尝试为新建的这个数组推断出一个较为合适的数据类型。数据类型保存在一个特殊dtype对象。一会我们会单独拿出一个部分来说这个dtype对象。
▲创建数组的相关函数
#使用array函数,参数为序列类型
#这里尤为要说明的是他的参数可以是另一个数组(ndarray)
import numpy as np
array = np.array([1,2,3])
array2 = np.array(array)#这里我的array函数参数是ndarray
print(array)
print(array2)
print("----------")
print(type(array))
print(type(array2))
print("----------")
#is判断的是a对象是否就是b对象,是通过id来判断的
print(array is array2)
[1 2 3]
[1 2 3]
----------
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
----------
False
从上面代码可以看出: 我们使用is关键字来查看是否为同一个对象,我们可以看出array和array2是不同的对象,也就是说,当我们使用一个ndarray对象作为array函数的参数的时候得到的另一个ndarray对象的时候,这另一个数组是对参数中数组的一个copy。直白点说就是他们占用不同的内存相互之间没有任何关系。
#使用asarray,其实这个函数和array函数的功能一样
#但是有一点不一样:如果参数是另一个数组(ndarray)
# 1.array函数,创建的另一个对象不是同一个
# 2.asarray函数,创建的另一个对象是同一个
import numpy as np
array = np.asarray([1,2,3])
array2 = np.asarray(array)
print(array)
print(array2)
print("----------")
print(type(array))
print(type(array2))
print("----------")
#is判断的是a对象是否就是b对象,是通过id来判断的
print(array is array2)
[1 2 3]
[1 2 3]
----------
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>
----------
True
从上面代码可以看出: 我们可以看出如果对于asarray函数来说参数不是ndarray的话,与array函数没有什么区别,但是如果是ndarray的话,通过asarray不进行复制,也就是他返回的是同一个对象。也就是通过is关键字,返回的结果是True。直白点说就是他们共享同一块内存空间。
import numpy as np
#这里arange函数用的还是比较多的,所以单拿出来
#np.arange(start,end,step)
array = np.arange(4)
print(array)
[0 1 2 3]
array2 = np.arange(1,10,2)
print(array2)
[1 3 5 7 9]
#如果我想通过arange生成矩阵,怎么办呢?
#注意这里生成的行*列一定要等于生成元素的个数
#如果不满足行*列等于元素个数
#会抛出ValueError: total size of new array must be unchanged
array3 = np.arange(1,10,2).reshape((5,1))
print(array3)
[[1]
[3]
[5]
[7]
[9]]
#这里因为函数中的参数start>end,并且没有指定步长step
#所以输出结果为[]
array4 = np.arange(14,2)
print(array4)
[]
#这里虽然指定了步长,但是步长为>0的一个正数,
#所以这里输出结果也会是[]
array5 = np.arange(14,2,2)#这里的步长>0(用于测试)
print(array5)
[]
array6 = np.arange(14,2,-1)#这里的步长<0(用于测试)
print(array6)
[14 13 12 11 10 9 8 7 6 5 4 3]
import numpy as np
array = np.ones(4)
array2 = np.ones((4,))#
array3 = np.ones((4,4))
#参数是数组或者序列类型,
#返回的根据参数的形状和dtype创建一个为1的数组
array4 = np.ones_like([1,2,3])
array5 = np.ones_like([[1,2,3],
[4,5,6]])
print(array)
print(array2)
print(array3)
print("----------")
print(array4)
print(array5)
print("----------")
print(array.dtype)
print(array2.dtype)
print(array3.dtype)
[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]
[[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]]
----------
[1 1 1]
[[1 1 1]
[1 1 1]]
----------
float64
float64
float64
import numpy as np
array = np.zeros(4)
array2 = np.zeros((4,))#
array3 = np.zeros((4,4))
#参数是数组或者序列类型,
#返回的根据参数的形状和dtype创建一个为1的数组
array4 = np.zeros_like([1,2,3])
array5 = np.zeros_like([[1,2,3],
[4,5,6]])
print(array)
print(array2)
print(array3)
print("----------")
print(array4)
print(array5)
print("----------")
print(array.dtype)
print(array2.dtype)
print(array3.dtype)
[ 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]]
----------
float64
float64
float64
import numpy as np
array = np.empty(4)
array2 = np.empty((4,))#
array3 = np.empty((4,4))
#参数是数组或者序列类型,
#返回的根据参数的形状和dtype创建一个为1的数组
array4 = np.empty_like([1,2,3])
array5 = np.empty_like([[1,2,3],
[4,5,6]])
print(array)
print(array2)
print(array3)
print("----------")
print(array4)
print(array5)
print("----------")
print(array.dtype)
print(array2.dtype)
print(array3.dtype)
[ 9.90263869e+067 8.01304531e+262 2.60799828e-310 9.48818959e+077]
[ 9.90263869e+067 8.01304531e+262 2.60799828e-310 1.57027689e-312]
[[ 6.23042070e-307 1.89146896e-307 1.37961302e-306 1.05699242e-307]
[ 8.01097889e-307 1.78020169e-306 7.56601165e-307 1.02359984e-306]
[ 1.33510679e-306 2.22522597e-306 1.24611674e-306 1.29061821e-306]
[ 6.23057349e-307 1.86920193e-306 9.34608432e-307 2.56765117e-312]]
----------
[0 0 0]
[[0 0 0]
[0 0 0]]
----------
float64
float64
float64
从上面代码可以看出:
import numpy as np
array = np.eye(4)
array2 = np.identity(4)
print(array)
print(array2)
[[ 1. 0. 0. 0.]
[ 0. 1. 0. 0.]
[ 0. 0. 1. 0.]
[ 0. 0. 0. 1.]]
[[ 1. 0. 0. 0.]
[ 0. 1. 0. 0.]
[ 0. 0. 1. 0.]
[ 0. 0. 0. 1.]]
从上面代码可以看出: 就目前来看他们的效果都是一样的,参数都是一个整数,然后创建的是一个方阵。
linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
在指定的间隔内返回均匀间隔的数字。
返回num均匀分布的样本,在[start, stop]。
Parament:
start:序列的起点
stop:序列的结束点,除非endpoint被设置为False,stop被排除
num:int, optional(可选)
生成的样本数,默认是50。必须是非负。
endpoint:如果是真,则一定包括stop,如果为False,一定不会有stop
retstep : bool, optional
If True, return (samples, step), where step is the spacing between samples.(array([ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.]), 1.0)
dtype : dtype, optional
输出数组的类型。如果没有给出,从其他输入参数的数据类型推断
logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None):
在指定的间隔内返回等比数列。
Parament:
start:序列的起点
stop:序列的结束点,除非endpoint被设置为False,stop被排除
num:int, optional(可选)
生成的样本数,默认是50。必须是非负。
endpoint:如果是真,则一定包括stop,如果为False,一定不会有stop
base:这里是指数的底数,默认是10,也就是说是 ,可以改成2
dtype : dtype, optional
输出数组的类型。如果没有给出,从其他输入参数的数据类型推断
d
ndarray的数据类型
其实如果直白的说,ndarray的数据类型就是我们创建数组时候的各个元素的数据类型,由于ndarray是一个通用的同构数据多维容器,所以每个ndarray对象有唯一的一个dtype值。当然这里的数据类型都是NumPy中的类型。
dtype(数据类型)是一个特殊的对象,他含有ndarray将一块内存解释为特定数据类型所需的信息。dtype是NumPy如此强大和灵活的原因之一。多数情况下,他们直接映射到相应的机器表示,这使得"读写磁盘上的二进制数据流"以及"集成低级语言代码(如C)"等工作变得更加的简单。标准的双精度浮点值(即Python中的float对象)需要占用8个字节(即64位)。因此,该类型在NumPy中就记作float64。
▲Numpy的数据类型
我们在创建数组的时候可以显式的指定dtype,同时我们也可以不进行指定,他会为新的数组推断出一个合适的数据类型。那么如果我们后期需要再对数组中的数据类型进行转换的话,那怎么办呢?这个时候我们可以通过ndarray数组对象的astype方法显示的转换为其他的dtype。
import numpy as np
#显示的指定dtype
array = np.array([1,2,3],dtype = np.float64)
#隐式的指定dtype
array2 = np.array(['1','2','3'])
print(array.dtype)
print(array2.dtype)
print("----------")
array3 = array2.astype(np.float64)
print(array2.dtype)
print(array3.dtype)
float64
<U1
----------
<U1
float64
我们从上面的(NumPy的数据类型)表格看到了有一个列叫做类型代码,我们其实可以不使用np.数据类型来指定类型,也可以使用对应参数的类型代码来简洁的表示。但是对于数据类型这么多的NumPy,个人建议还是不要使用类型代码这种方式。
import numpy as np
array = np.array([1,2,3],dtype= 'S')
print(array)
print(array.dtype)
[b'1' b'2' b'3']
|S1
这里需要进行三点说明的是:
import numpy as np
array = np.array(['h',1,2],dtype = np.float64)
print(array)
Traceback (most recent call last):
File "G:/Python源码/numpy_test/numpy_test.py", line 639, in <module>
array = np.array(['h',1,2],dtype = np.float64)
ValueError: could not convert string to float: 'h'
import numpy as np
array = np.array([1,2,3])
array2 = array.astype(float)
print(array.dtype)
print(array2.dtype)
int32
float64
import numpy as np
array = np.array([1.1,2.3,4,7.8],dtype = np.float64)
array2 = array.astype(np.int64)
print(array)
print(array2)
print("-----打印类型-----")
print(array.dtype)
print(array2.dtype)
print("-----判断是否为同一个对象-----")
array2[0] = 10000
print(array)
print(array2)
[ 1.1 2.3 4. 7.8]
[1 2 4 7]
-----打印类型-----
float64
int64
-----判断是否为同一个对象-----
[ 1.1 2.3 4. 7.8]
[10000 2 4 7]
总结(是否是对源数据的拷贝):
▲总结
e
Numpy数组索引
NumPy数组的索引是一个内容丰富的主题,因为选取数据子集或者是单个元素的方式有很多。对于一维数组来说,他和Python中的list的功能差不太多。
#对于一维数组来说
import numpy as np
array = np.arange(8)
print(array)
print("-----选取单个元素-----")
print(array[3])
print(array[-1])
print("-----选取数据子集-----")
print(array[0:5])
print(array[1:-2])
[0 1 2 3 4 5 6 7]
-----选取单个元素-----
3
7
-----选取数据子集-----
[0 1 2 3 4]
[1 2 3 4 5]
这里要多说几句:
▲数组索引
import numpy as np
array = np.array([1,2,3,4,5,6,7])
array2 = array[1:]
print("-----原始的数据-----")
print(array)
print(array2)
print("-----更改后的数据-----")
array2[0] = 10000
print(array)
print(array2)
-----原始的数据-----
[1 2 3 4 5 6 7]
[2 3 4 5 6 7]
-----更改后的数据-----
[ 1 10000 3 4 5 6 7]
[10000 3 4 5 6 7]
那对于高维数组来说,能做的事情就更多了。在一个二维数组中,各索引位置上的元素不再是标量而是一维数组。
import numpy as np
array = np.arange(1,13).reshape((4,3))
print(array)
print("-----获取单个元素的两种方式-----")
print(array[0][1])
print(array[0,1])
print(array[-1][2])
print(array[-1,2])
print("-----对多维数组进行切片-----")
#因为这里只是一个矩阵,所以有两个轴,也就是说能指定两个位置
print(array[:2])#只有一个值,也就是只有第一个轴的方向,也就是axis=0
print(array[:2,:1])
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
-----获取单个元素的两种方式-----
2
2
12
12
-----对多维数组进行切片-----
[[1 2 3]
[4 5 6]]
[[1]
[4]]
▲二维数组的展示图
布尔型索引是NumPy特有的功能,他的功能非常的强大。并且应用的场景也非常的多。比如:下表是几个学生的一年中期末期中的语数英三科的考试成绩:
▲三个同学的期中期末成绩
先初始化我们的数据:
import numpy as np
names_array = np.array(['KC','XC','XC','LC','LC','KC'],dtype = np.string_)
grades_array = np.array([[80,98,78],
[66,80,77],
[68,88,90],
[77,61,70],
[70,65,80],
[82,70,80]],dtype = np.int32)
我们要选出"KC"这名学生的期中期末的成绩:
names_array2 = (names_array == 'KC')
print("-----筛选满足条件的行-----")
print(names_array is names_array2)
print(names_array2)
print("-----进行筛选-----")
print(grades_array[names_array2])
-----筛选满足条件的行-----
False
[ True False False False False True]
-----进行筛选-----
[[80 98 78]
[82 70 80]]
这里注意:
那如果我们要获取除了'KC'这位同学的其他同学的成绩:
print(names_array == 'KC')
print("-----方式一-----")
print(names_array != 'KC')
print("-----方式二-----")
print(-(names_array == 'KC'))
[ True False False False False True]
-----方式一-----
[False True True True True False]
-----方式二-----
[False True True True True False]
当然我们操作还不仅仅是这些,我们还可以组合应用多个布尔条件,使用&(和)、|(或)之类的布尔算术运算符即可(当然这里要注意了,我们不能使用Python中的and和or关键字):
print("-----获取'KC'或者'LC'同学的成绩-----")
print((names_array == 'KC') | (names_array == 'LC'))
print(grades_array[(names_array == 'KC') | (names_array == 'LC')])
-----获取'KC'或者'LC'同学的成绩-----
[ True False False True True True]
[[80 98 78]
[77 61 70]
[70 65 80]
[82 70 80]]
当然因为我们是二维的一个数组,所以我们对应的是两个轴,那我们还可以对列进行筛选,这里其实很灵活的,我们可以使用一个标量来获取整个列,也可以传入一个分片来获取部分列,当然我们对于列的筛选也可以布尔型的索引:
print("-----获取'KC'同学的数学成绩-----")
print(grades_array[(names_array == 'KC'),1])
print(grades_array[names_array == 'KC'][grades_array[names_array == 'KC'] >= 80])
-----获取'KC'同学的数学成绩-----
[98 70]
[80 98 82 80]
花式索引(Fancy indexing)是一个NumPy术语,他指的是利用整数数组进行索引。下面是对于二维数组的例子:
import numpy as np
array = np.arange(32).reshape((8,4))
print(array)
print("-----获取第一行和第三行-----")
print(array[[0,2]])
print("-----获取最后两行-----")
print(array[[-1,-2]])
print("-----获取元素值-----")
print(array[[0,2],[0,1]])
print(array[[0,2]][:,[0,1]])
print(array[np.ix_([0,2],[0,1])])
[[ 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]
[ 8 9 10 11]]
-----获取最后两行-----
[[28 29 30 31]
[24 25 26 27]]
-----获取元素值-----
[0 9]
[[0 1]
[8 9]]
[[0 1]
[8 9]]
这里要注意的是:
总结(是否是对源数据的拷贝):
▲总结
这里所说的源数据的视图说的就是如果我们修改其中一个数组,另一个源数组也会发生变化,他们操作的对象是同一个。当然如果不是的话就是说明我们在操作的时候又重新创建了一个新的数组,这个数组是对源数组的一个拷贝,这个时候去任何一个数组都不会对另一个数组产生影响。