介绍
NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。
NumPy 的前身 Numeric 最早是由 Jim Hugunin 与其它协作者共同开发,2005 年,Travis Oliphant 在 Numeric 中结合了另一个同性质的程序库 Numarray 的特色,并加入了其它扩展而开发了 NumPy。NumPy 为开放源代码并且由许多协作者共同维护开发。
NumPy 是一个运行速度非常快的数学库,主要用于数组计算,包含:
一个强大的N维数组对象 ndarray
- 广播功能函数
- 整合 C/C++/Fortran 代码的工具
- 线性代数、傅里叶变换、随机数生成等功能
Ndarray
NumPy 最重要的一个特点是其 N 维数组对象 ndarray,它是一系列同类型数据的集合,以 0 下标为开始进行集合中元素的索引。
ndarray 对象是用于存放同类型元素的多维数组。
ndarray 中的每个元素在内存中都有相同存储大小的区域。
ndarray 内部由以下内容组成:
- 一个指向数据(内存或内存映射文件中的一块数据)的指针。
- 数据类型或 dtype,描述在数组中的固定大小值的格子。
- 一个表示数组形状(shape)的元组,表示各维度大小的元组。
- 一个跨度元组(stride),其中的整数指的是为了前进到当前维度下一个元素需要"跨过"的字节数。
ndarray 的内部结构:
跨度可以是负数,这样会使数组在内存中后向移动,切片中 obj[::-1] 或 obj[:,::-1] 就是如此。
创建一个 ndarray 只需调用 NumPy 的 array 函数即可:
numpy.array(object, dtype = None, copy = True, order = None, subok = False, ndmin = 0)
名称 | 描述 |
---|---|
object | 数组或嵌套的数列 |
dtype | 数组元素的数据类型,可选 |
copy | 对象是否需要复制,可选 |
order | 创建数组的样式,C为行方向,F为列方向,A为任意方向(默认) |
subok | 默认返回一个与基类类型一致的数组 |
ndmin | 指定生成数组的最小维度 |
import numpy as np
score = np.array([[80, 89, 86, 67, 79],
[78, 97, 89, 67, 81],
[90, 94, 78, 67, 74],
[91, 91, 90, 67, 69],
[76, 87, 75, 67, 86],
[70, 79, 84, 67, 84],
[94, 92, 93, 67, 64],
[86, 85, 83, 67, 80]])
print(score)
print(type(score))
输出
[[80 89 86 67 79]
[78 97 89 67 81]
[90 94 78 67 74]
[91 91 90 67 69]
[76 87 75 67 86]
[70 79 84 67 84]
[94 92 93 67 64]
[86 85 83 67 80]]
<class 'numpy.ndarray'>
ndarray与Python原生list运算效率对比
# ndarray和list效率对比
python_list = []
start_time = datetime.datetime.now()
for i in range(100000000):
python_list.append(random.random())
end_time = datetime.datetime.now()
print("list的运行时间:", end_time-start_time)
start_time = datetime.datetime.now()
ndarray_list = np.array(python_list)
end_time = datetime.datetime.now()
print("ndarray的运行时间:", end_time-start_time)
输出
list的运行时间: 0:00:22.598421
ndarray的运行时间: 0:00:04.590315
啧啧啧,这能比???
为啥Ndarray这么快
-
内存块风格
ndarray到底跟原生python列表有什么不同呢,请看一张图:
从图中我们可以看出ndarray在存储数据的时候,数据与数据的地址都是连续的,这样就给使得批量操作数组元素时速度更快。
这是因为ndarray中的所有元素的类型都是相同的,而Python列表中的元素类型是任意的,所以ndarray在存储元素时内存可以连续,而python原生list就只能通过寻址方式找到下一个元素,这虽然也导致了在通用性能方面Numpy的ndarray不及Python原生list,但在科学计算中,Numpy的ndarray就可以省掉很多循环语句,代码使用方面比Python原生list简单的多。
-
ndarray支持并行化运算(向量化运算)
numpy内置了并行运算功能,当系统有多个核心时,做某种计算时,numpy会自动做并行计算
-
效率远高于纯Python代码
Numpy底层使用C语言编写,内部解除了GIL(全局解释器锁),其对数组的操作速度不受Python解释器的限制,所以,其效率远高于纯Python代码。
Ndarray的属性
属性 | 说明 |
---|---|
ndarray.ndim | 秩,即轴的数量或维度的数量 |
ndarray.shape | 数组的维度,对于矩阵,n 行 m 列 |
ndarray.size | 数组元素的总个数,相当于 .shape 中 n*m 的值 |
ndarray.dtype | ndarray 对象的元素类型 |
ndarray.itemsize | ndarray 对象中每个元素的大小,以字节为单位 |
ndarray.flags | ndarray 对象的内存信息 |
ndarray.real | ndarray元素的实部 |
ndarray.imag | ndarray 元素的虚部 |
ndarray.data | 包含实际数组元素的缓冲区,由于一般通过数组的索引获取元素,所以通常不需要使用这个属性。 |
score = np.array([[80, 89, 86, 67, 79],
[78, 97, 89, 67, 81],
[90, 94, 78, 67, 74],
[91, 91, 90, 67, 69],
[76, 87, 75, 67, 86],
[70, 79, 84, 67, 84],
[94, 92, 93, 67, 64],
[86, 85, 83, 67, 80]])
print(type(score))
print(score.shape) # (8, 5)
print(score.ndim) # 2
print(score.size) # 40
print(score.dtype) # int32 默认
print(score.itemsize) # 4
输出
<class 'numpy.ndarray'>
(8, 5)
2
40
int32
4
Ndarray数据类型
名称 | 描述 |
---|---|
bool_ | 布尔型数据类型(True 或者 False) |
int_ | 默认的整数类型(类似于 C 语言中的 long,int32 或 int64) |
intc | 与 C 的 int 类型一样,一般是 int32 或 int 64 |
intp | 用于索引的整数类型(类似于 C 的 ssize_t,一般情况下仍然是 int32 或 int64) |
int8 | 字节(-128 to 127) |
int16 | 整数(-32768 to 32767) |
int32 | 整数(-2147483648 to 2147483647) |
int64 | 整数(-9223372036854775808 to 9223372036854775807) |
uint8 | 无符号整数(0 to 255) |
uint16 | 无符号整数(0 to 65535) |
uint32 | 无符号整数(0 to 4294967295) |
uint64 | 无符号整数(0 to 18446744073709551615) |
float_ | float64 类型的简写 |
float16 | 半精度浮点数,包括:1 个符号位,5 个指数位,10 个尾数位 |
float32 | 单精度浮点数,包括:1 个符号位,8 个指数位,23 个尾数位 |
float64 | 双精度浮点数,包括:1 个符号位,11 个指数位,52 个尾数位 |
complex_ | complex128 类型的简写,即 128 位复数 |
complex64 | 复数,表示双 32 位浮点数(实数部分和虚数部分) |
complex128 | 复数,表示双 64 位浮点数(实数部分和虚数部分) |
numpy 的数值类型实际上是 dtype 对象的实例,并对应唯一的字符,包括 np.bool_,np.int32,np.float32,等等。
import numpy as np
data = np.array([1.1, 1.2, 1.3], dtype=np.float32)
print(data)
print(data.dtype)
输出
[1.1 1.2 1.3]
float32
Ndarray常用操作
生成0和1数组
zero = np.zeros([3, 4])
print(zero)
print("元素类型", zero.dtype)
print("------------------")
one = np.ones([3, 4], dtype=np.int_)
print(one)
输出
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
元素类型 float64
------------------
[[1 1 1 1]
[1 1 1 1]
[1 1 1 1]]
从现有的数组中生成
print("-----array(深拷贝)-----")
data01[0, 3] = 100
print("原始数据:", score[0])
print("array产生的数组修改后的数据(data01):", data01[0])
print("array产生的数组修改后的数据(score):", score[0])
print("-----asarray(浅拷贝)-----")
data02[0, 3] = 110
print("原始数据:", score[0])
print("array产生的数组修改后的数据(data02):", data02[0])
print("array产生的数组修改后的数据(score):", score[0])
print("-----copy(深拷贝)-----")
data03[0, 3] = 120
print("原始数据:", score[0])
print("array产生的数组修改后的数据(data02):", data03[0])
print("array产生的数组修改后的数据(score):", score[0])
输出
-----array(深拷贝)-----
原始数据: [80 89 86 67 79]
array产生的数组修改后的数据(data01): [ 80 89 86 100 79]
array产生的数组修改后的数据(score): [80 89 86 67 79]
-----asarray(浅拷贝)-----
原始数据: [ 80 89 86 110 79]
array产生的数组修改后的数据(data02): [ 80 89 86 110 79]
array产生的数组修改后的数据(score): [ 80 89 86 110 79]
-----copy(深拷贝)-----
原始数据: [ 80 89 86 110 79]
array产生的数组修改后的数据(data02): [ 80 89 86 120 79]
array产生的数组修改后的数据(score): [ 80 89 86 110 79]
生成固定范围的数组
arr = np.linspace(0, 10, 5) # [0, 10] 等距离,num默认是50
print(arr)
arr = np.arange(0, 10, 2.5) # [a, b) c是步长,step默认是1
print(arr)
输出
[ 0. 2.5 5. 7.5 10. ]
[0. 2.5 5. 7.5]
均匀分布
data = np.random.uniform(-1, 1, 1000000)
plt.figure(figsize=(20, 8), dpi=80)
plt.hist(data, bins=1000)
plt.show()
正态分布
data = np.random.normal(1.75, 0.1, 100000)
plt.figure(figsize=(20, 8), dpi=80)
plt.hist(data, bins=1000)
plt.show()
案例:随机生成8只股票2周的交易日涨幅数据
stock_change = np.random.normal(loc=0, scale=1, size=(8, 10))
print(stock_change)
# 获取第一个股票的前3个交易日的涨跌幅数据
stock_change[0, :3]
输出
[[-0.03469926, 1.68760014, 0.05915316, 2.4473136 , -0.61776756,
-0.56253866, -1.24738637, 0.48320978, 1.01227938, -1.44509723],
[-1.8391253 , -1.10142576, 0.09582268, 1.01589092, -1.20262068,
0.76134643, -0.76782097, -1.11192773, 0.81609586, 0.07659056],
[-0.74293074, -0.7836588 , 1.32639574, -0.52735663, 1.4167841 ,
2.10286726, -0.21687665, -0.33073563, -0.46648617, 0.07926839],
[ 0.45914676, -0.78330377, -1.10763289, 0.10612596, -0.63375855,
-1.88121415, 0.6523779 , -1.27459184, -0.1828502 , -0.76587891],
[-0.50413407, -1.35848099, -2.21633535, -1.39300681, 0.13159471,
0.65429138, 0.32207255, 1.41792558, 1.12357799, -0.68599018],
[ 0.3627785 , 1.00279706, -0.68137875, -2.14800075, -2.82895231,
-1.69360338, 1.43816168, -2.02116677, 1.30746801, 1.41979011],
[-2.93762047, 0.22199761, 0.98788788, 0.37899235, 0.28281886,
-1.75837237, -0.09262863, -0.92354076, 1.11467277, 0.76034531],
[-0.39473551, 0.28402164, -0.15729195, -0.59342945, -1.0311294 ,
-1.07651428, 0.18618331, 1.5780439 , 1.31285558, 0.10777784]]
[ 0.75372007 0.37205739 -0.45823456]
形状修改
print(stock_change.reshape((10, 8)).shape)
stock_change.resize((10, 8))
print(stock_change.shape)
print(stock_change.T.shape)
输出
(10, 8)
(10, 8)
(8, 10)
序列化
print(stock_change.tostring())
输出
b'\x1cDY5\xba\xf6\xfd?\x1f!\xa4"H\x12\xd3\xbf\x8a\xb8\n"n\x86\xe8?V\xc8E\x01\x0c\xba\xe4?#\xa5\xcd4\xfd8\xdc?-\xfb\x01\x9by\xee\xb5?\x97\xca\xc6\xd3\xaf;\xe0?\x9b;\x1f\xd9d\x17\xfb\xbfY\x10\xca\x9c\x99D\xe5?d\xbf\x93\xfe\xfbr\xf1\xbfQ\x89\x0f\xca\x88:\xf7\xbfo\xdd22=\x9d\xf2\xbf\xa1,\xbc\xc7>B\xf0\xbfl\x8d\......省略
数组去重
data = np.array([[1,2,3,4],[3,4,5,6]])
print(np.unique(data))
print(set(data.flatten()))
输出
[1 2 3 4 5 6]
{1, 2, 3, 4, 5, 6}
Ndarray运算
逻辑运算
import numpy as np
stock_change = np.random.normal(1.5, 2, (8, 10))
# 逻辑判断, 如果涨跌幅大于等于0.5就标记为True 否则为False
print(stock_change >= 0.5)
# 修改所有涨幅小于0.5的为0.5
stock_change[stock_change < 0.5] = 0.5
输出
[[False True True True False True False True False True]
[False True True True True True False True True False]
[ True True True False True True False True False False]
[ True True False True True True False False True False]
[ True True True False True True True False False True]
[ True True True True True True True True True False]
[ True True True True True True True True True True]
[ True True True True True True True True True True]]
where运算
temp = stock_change[:4, :4]
print(temp)
print(np.where(temp > 0.5, 1, 0))
# 判断前四个股票前四天的涨跌幅 大于0.5并且小于1的,换为1,否则为0
# 判断前四个股票前四天的涨跌幅 大于0.5或者小于-0.5的,换为1,否则为0
print(np.where(np.logical_and(temp > 0.5, temp < 1), 1, 0))
print(np.where(np.logical_or(temp > 0.5, temp < -0.5), 1, 0))
输出
[[ 2.11334994 2.75371732 -1.45919878 -0.54644526]
[ 2.50898042 2.49334823 3.46757975 1.67814258]
[-1.63742684 2.21757841 0.51642822 -1.10437119]
[ 1.08439702 0.2920416 0.31753524 2.20334352]]
[[1 1 0 0]
[1 1 1 1]
[0 1 1 0]
[1 0 0 1]]
[[0 0 0 0]
[0 0 0 0]
[0 0 1 0]
[0 0 0 0]]
[[1 1 1 1]
[1 1 1 1]
[1 1 1 1]
[1 0 0 1]]
最大值最小值
print(np.max(stock_change))
# 0代表列,1代表行
print(np.max(stock_change, axis=0))
print(np.max(stock_change, axis=1))
# 最大值的位置
print(np.argmax(stock_change, axis=0))
print(np.argmax(stock_change, axis=1))
输出
6.693146605729187
[6.69314661 2.82844572 5.81205157 5.35317406 4.01567427 5.66452237
2.61476055 3.99797746 4.19418698 5.17729721]
[4.48925181 4.29683647 3.99797746 2.09293751 5.81205157 5.35317406
5.17729721 6.69314661]
[7 4 4 5 6 7 2 2 5 6]
[5 0 7 1 2 3 9 0]
广播机制
简单理解:对两个数组,分别比较他们的每一个维度(若其中一个数组没有当前维度则忽略),满足:
- 数组拥有相同形状。
- 当前维度的值相等。
- 当前维度的值有一个是 1
A (3d array): 256 × 256 × 3
B (1d array): 3
Result (3d array): 256 × 256 × 3
A (4d array): 9 × 1 × 7 × 1
B (3d array): 8 × 1 × 5
Result (4d array): 9 × 8 × 7 × 5
A (2d array): 5 × 4
B (1d array): 1
Result (2d array): 5 × 4
A (3d array): 15 × 3 × 5
B (3d array): 15 × 1 × 1
Result (3d array): 15 × 3 × 5
矩阵运算
两种方法存储矩阵
- ndarray 二维数组
- matrix数据结构
data = np.array([[80, 86],
[82, 80],
[85, 78],
[90, 90],
[86, 82],
[82, 90],
[78, 80],
[92, 94]])
# matrix存储矩阵
data_mat = np.mat([[80, 86],
[82, 80],
[85, 78],
[90, 90],
[86, 82],
[82, 90],
[78, 80],
[92, 94]])
weights = np.array([[2], [3]])
weights_mat = np.mat([[2], [3]])
# 矩阵乘法
print(np.matmul(data, weights))
print(np.dot(data, weights))
print(data @ weights)
print(data_mat * weights_mat)
输出
[[418]
[404]
[404]
[450]
[418]
[434]
[396]
[466]]
.....都是一样的,就省略了
合并
data_1 = np.array([1, 2, 3])
data_2 = np.array([5, 6, 7])
print(np.hstack((data_1, data_2))) # 水平拼接 horizontally
print(np.vstack((data_1, data_2))) # 竖直拼接 vertically
data_1 = np.array([[1, 2, 3], [2, 3, 4], [3, 4, 5]])
data_2 = np.array([[5, 6, 7]])
print(np.concatenate((data_1, data_2), axis=0)) # 自定义合并
print(np.concatenate((data_1, data_2.T), axis=1)) # 自定义合并
输出
[1 2 3 5 6 7]
[[1 2 3]
[5 6 7]]
[[1 2 3]
[2 3 4]
[3 4 5]
[5 6 7]]
[[1 2 3 5]
[2 3 4 6]
[3 4 5 7]]
分割
print("-----分割-----")
data = np.arange(0, 10)
print(np.split(data, 2))
print(np.split(data, [4, 6]))
输出
[array([0, 1, 2, 3, 4]), array([5, 6, 7, 8, 9])]
[array([0, 1, 2, 3]), array([4, 5]), array([6, 7, 8, 9])]
IO操作和数值处理
文件内容
id,value1,value2,value3
1,123,1.4,23
2,110,,18
3,,2.1,19
代码
test = np.genfromtxt("test.csv", delimiter=",")
print(test)
print(type(test[0, 0]))
输出
[[ nan nan nan nan]
[ 1. 123. 1.4 23. ]
[ 2. 110. nan 18. ]
[ 3. nan 2.1 19. ]]
<class 'numpy.float64'>
我们可以看到,出现了缺失值nan。
为什么呢?当我们读取本地的文件为float的时候,如果数据类型不对(或者为None),就会出现nan
上方的[0,0]明明是字符串,但是是按float读取的,就出现了nan。这是由ndarray的数据结构决定的