Numpy

Numpy 介绍

Numpy 是一个开源的科学计算库,用于处理多维数组。其可以处理常见的数组和矩阵。其最大的优势是比 Python 原生处理这些数据的速度更快。

安装 Numpy

pip install numpy

安装完成之后可以在 ipython普通shell解释器 使用下面代码进行测试:

import numpy as np
n.__version__

普通 python shell 环境

import numpy as np
print(np.__version__)

ndarray 数据结构

ndarray 是什么

Numpy 主要使用 ndarray 对象处理多维数组,可以理解为 Numpy 的一种数据容器,是 n 维数组类型 ,是相同类型的数据集合。

比如学生成绩:

语文数学英语
809095
788997
577899
767889

这个数据可以使用 ndarray 类型来存储,它是二维的,其次它的数据都是整数类型,使用 Python 原生的数据来行来描述的话则是二维列表。

np.array()

array() 函数可以接受任意序列型对象,生成一个新的 Numpy 数组( ndarray 对象,有时候也称 ndarray 数组)。

生成数据测试

jupyter notebook 或者 jupyter lab 环境中:

import numpy as np

# 创建一个 Python 二维列表
x_list = [
    [80, 90, 95],
    [78, 89, 97],
    [57, 78, 99],
    [76, 78, 89]
]

# 使用 numpy 创建 ndarray 数组
x = np.array(x_list)
x

运行结果:

image-20240513142514021

可以使用 type() 函数查看数据类型

type(x)

运行结果:

image-20240513142724264

ndarray 相比 list 的优势

使用 Python 原生的列表也可以实现相同的效果,但是效率上是没有 ndarray 高的。

这是因为俩者内存的存储方式是不一样的,Python 原生的 list 是可以同时存储不同类型数据的,这就导致 list 的底层实现是通过存储地址实现的,相当于查找数据时先找到地址,再找到具体数据。而 ndarray 存储的元素类型是相同的,因此底层实现的时候可以使用数组实现,它的内存地址是连续的,因此在访问数据的时候更方便。

在数据科学中,需要处理的数据量一般都比较大,使用 ndarray 可以省掉很多循环,这样就占优势了。

np.shape

  • 解释:ndarray 的一个属性

  • 作用:查看数组维度的元素

代码测试:(这里接上面的代码)

x.shape

运行结果:

image-20240513144635122

结果显示 (4, 3) ,表示数据是 4行3列。

np.ndim

  • 解释:ndarray 的一个属性

  • 作用:查看数组的维度

代码测试:

x.ndim

运行结果:

image-20240513145114095

结果显示 2 ,表示这是一个二维数组。

np.size

  • 解释:ndarray 的一个属性

  • 作用:查看数组中的元素数量

代码测试:

x.size

运行结果:

image-20240513145530390

结果显示 12 ,代表数组中包含了12个元素,对应前面的 4行3列4x3 可以计算出元素数量

np.dtype

  • 解释:ndarray 的一个属性
  • 作用:查看数组元素的数据类型

代码测试:

x.dtype

运行结果:

image-20240513150204329

结果显示 int64 ,代表数组中元素是 int64 类型, int64 类型占用 8 个字节

np.itemsize

解释:ndarray 的一个属性

作用:查看数组中一个元素所占的空间(字节)

代码测试:

x.itemsize

运行结果:

image-20240513150447685

ndarray 数据类型

ndarray 数组可以使用不同的数据类型,比如上面的 int64 ,常见的数据类型有以下这些:

类型名解释
np.int64整数,取值范围在 -2^63 到 2^63-1
np.int32整数,取值范围在 -2^31 到 2^31-1
np.int16整数,取之范围在 -2^15 到 2^15-1 (-32768 到 32767)
np.int8整数,取值范围在 -128 到 127
np.uint8无符号整数,0 到 255
np.uint16无符号整数,0 到 2^16-1 (0到65535)
np.uint32无符号整数,0 到 2^32-1
np.uint64无符号整数,0 到 2^64-1
np.float16半精度浮点数,16位
np.float32单精度浮点数,32位
np.float64双精度浮点数,64位
np.bool布尔类型,一个字节存储
np.complex64复数
np.conplex128复数
np.string_字符串
np.unicode_unicode类型
np.object_python对象

指定类型创建数组

在使用 np.array() 方法创建 ndarray 数组时,使用 dtype=数据类型 就可以指定创建的数组元素类型了。

代码测试:

import numpy as np

# 创建一个 Python 二维列表
x_list = [
    [80, 90, 95],
    [78, 89, 97],
    [57, 78, 99],
    [76, 78, 89]
]
# 使用 numpy 创建 ndarray 数组
x = np.array(x_list, dtype=np.float64)
x

运行结果:

image-20240513152815138

可以从运行结果中看到数组中的数字后面多了一个点,证明他们是浮点数。

代码测试:

y = np.array(["qwer", "d", "f", "a"], dtype=np.string_)
y

运行结果:

image-20240513153259593

运行结果中有 dtype='|S4' 它是指在数组中有一个字符串是四个元素的。

**备注:**数据类型如果不指定则默认是 int64 和 float64 。

生成数组的方法

生成数组的方法有很多,这里列举一些比较常用的,详细可以参考 >>官方文档open in new window

np.ones(shape, dtype)

  • 作用:生成元素均为 1 的数组。

示例1:

ones = np.ones([3, 4])
ones

运行结果:

image-20240513155617762

从运行结果可以看到:如果不设置 dtype ,默认是 float64

示例2:

ones2 = np.ones([3, 4], dtype=np.int64)
ones2
image-20240513160025466

这里设置了 dtype ,可以看到生成的数组元素为 1 。

np.zeros(shape, dtype)

  • 作用:生成元素均为 0 的数组。

示例1:

zeros = np.zeros([3, 4])
zeros

运行结果:

image-20240513160535410

示例2:

zeros2 = np.zeros([3, 4], dtype=np.int64)
zeros2

运行结果:

image-20240513160648254

np.asarray(array, dtype)

  • 作用:从现有数组生成新的数组

np.array() 也可以使用现有数组生成新的数组。

示例1:

# 使用 array() 创建一个数组 x
x = np.array([[1, 2, 3],[4, 5, 6]])
# array() 使用现有数组生成新的数组
x1 = np.array(x)
x1

运行结果:

image-20240513162325860

示例2:

# 使用 asarray() 创建一个数组
x2 = np.asarray(x)
x2

运行结果:

image-20240513162833282

这俩个 numpy 的方法生成的数据看上去没有任何区别。

但实际上它们俩是有区别的,修改一下 x 中的某个数据再分别输出 x1 和 x2 查看结果:

x[0, 0] = 10
x

运行结果:

image-20240513163329563

从结果看到,x 中的 第0列0行 的元素修改为 10 后,x 中的数据修改了,使用 array() 创建的 x1 中的数据没有变化,但是使用 asarray() 创建的 x2 中的数据跟着修改了。这说明 array() 利用原有数组创建新的数组时是深拷贝,而 asarray() 利用原有数组创建新的数组时是浅拷贝。

np. linspace(start, stop, num, endpoint)

  • 作用:创建等差数组,可以指定数量。
  • start:序列的起始值
  • stop:序列的终止值
  • num:要生成的样本数量,默认为50
  • endpoint:序列中是否包含 stop 值,默认为 true

示例:

arr = np.linspace(0, 100, 21)
arr

运行结果:

image-20240513164557491

结果显示可以看到生成了 21 个数据,范围是 0 到 100 之间的等差数列。

dtypefloat64 ,它是根据 stratstop 推断出来的,也可以在后面追加指定的 dtype

np.arange(start, stop, step)

  • 作用:创建等差数组,可以指定步长。
  • strat:起始值
  • stop:终止值
  • step:步长

示例:

arr = np.arange(0, 100, 5)
arr

运行结果:

image-20240513165244321

从结果可以看到,生成的数组中不包含 stop 值,步长为 5。

不指定 dtype 的情况下, 根据其他输入参数推断数据类型 。

np.logspace(start, stop, num)

  • 作用:创建等比数列。
  • start:起始值
  • stop:终止值
  • num:要生成的样本数量,默认为 50

示例:

arr = np.logspace(2, 3, 4, dtype=np.float64)
arr

运行结果:

image-20240513172432702

这里需要注意startstop 分别是 10 的次方数。 示例代码中实际上在 100 到 1000 之间生成一个等比数列。

更多生成数组的方法可以查看 >>官方文档open in new window

统计学相关概念

方差

  • 衡量一组数据的离散程度
  • 公式:

方差(σ2)=(x1μ)2+(x2μ)2+(x3μ)2n \text{方差}(σ^2) = \frac{(x1-μ)^2 + (x2-μ)^2 + (x3-μ)^2}{n}

  • x1:具体的数据
  • μ:平均值
  • n:数据的数量

标准差

方差开根号即标准差

标准差(σ)=1ni=1n(xiμ)2 \text{标准差}(σ) = \sqrt{\frac{1}{n}\sum_{i=1}^{n}{(x_i-μ)^2}}

标准差越小,数据分布越紧凑,标准差越大,数据分布越分散

正态分布

正态分布是一种连续概率分布。

它有两个参数决定,第一个参数是均值(上面公式中的 μ),第二个参数是标准差(σ),正态分布一般记做 𝒩(μ, σ)。

μ 决定了正态分布图形的位置,标准差 σ 决定定了分布的幅度,μ = 0,σ = 1 时称为标准正态分布。

Normal_Distribution_PDF

小提示:

这里的图片在 黑暗 模式下看不清,可以点击右上角图标调整一下。

生成正态分布数据

np.random.randn(d0, d1, ... , dn)

  • 作用:从标准正态分布中返回一个或多个样本值

np.random.standard_normal(size=None)

  • 作用:返回指定形状的标准正态分布的数组

np.random.normal(loc=0.0, scale=1.0, size=None)

  • loc:概率分布的均值,(上图中的 μ ), (概率分布的中心)
  • scale:概率分布的标准差,(上图中的 σ )(概率分布的宽度,越大越矮胖)
  • size:输出的 shape, 默认为 None

使用第三种方式可以实现上面两种,因此学会第三种方式的操作即可,前面两种有印象即可。

示例:

x = np.random.normal(0, 1, (3, 4))
x

运行结果:

image-20240514165936718

生成三行四列的正态分布的随机数据。

生成均匀分布数据

np.random.rand(d0, d1, ... , dn)

  • 作用:返回 [0.0, 1.0) 内的一组均匀分布的数据。

np.random.uniform(low=0.0, high=1.0, size=None)

  • 作用:从一个均匀分布的数据集 [low, high) 中随机采样。返回 ndarray 类型。
  • low:采样下界,默认值为 0 。
  • high:采样上界,默认值为 1 。
  • size:输出样本数量,为 int 或者 tuple 类型。输出的 shape 由此设定。

np.random.randint(low, high=None, size=None)

  • 作用:从一个均匀分布的数据集中采样生成数组。
  • 取值范围:high 不为 None 时,取 [low, high) 之间的随机数,否则取 [0, low) 之间的随机数。

示例:

x = np.random.uniform(0, 1, (3, 4))
x

运行结果:

image-20240514171527434

数组操作

索引访问

索引&索引访问

数组的索引和 Python 中的索引一样,都是从 0 开始的。可以通过索引对数组进行访问和切片操作。

二维数组因为有两个维度,所以无论是索引访问还是切片也都有两个维度,其他维度的数组同理。

注意:切片时,包含 : 前面的索引,不包含 : 后面的索引

示例:

# 生成一个 ndarray 类型 3行4列 的二维数组
x = np.random.normal(0, 1, (3, 4))
# 查看数据
print(x)
print("---------------------------")
# 访问 第3行 第4列 的数据
print(x[2, 3])  # 注意索引从 0 开始,3行4列对应的索引是 2和 3
print("---------------------------")
# 访问 第2行 第2 到 第3列的数据  
print(x[2, 1:3]) # 注意切片包含 : 前面的索引 不包含 : 后面的索引

运行结果:

image-20240514173057583

ndarray.reshape(shape, order)

  • 作用:返回相同数据、形状不同的视图
  • 注意:行、列不进行互换(整体压平,重新排列)

示例:

# 生成一个随机的 3行4列的数据
x = np.random.normal(0, 1, (3, 4))
# 输出数据查看
print(x)
print("---------------------------")
# 修改形状
y = x.reshape([2, 6])
print(y)

运行结果:

image-20240514190057439

注意

注意修改 ndarray 的形状时,要保持数据量一致。

小技巧

第一个参数可以写为 -1 ,代表自动计算生成几行,比如 ndarray.reshape(-1, 2) 这代表了转换为2列,行由数据量自动根据列数量计算。反之亦可。

示例:

# 生成一个随机的 3行4列的数据
x = np.random.normal(0, 1, (3, 4))
# 输出数据查看
print(x)
print("---------------------------")
# 修改形状
y = x.reshape([-1, 2])
print(y)

运行结果:

image-20240516111743781

ndarray.resize(new_shape)

  • 作用:修改数组本身的形状
  • 注意:行、列不进行互换

示例:

x.resize([2, 6])
x

运行结果:

image-20240514190746550

reshape 和 resize 的区别

reshape 是返回一个新的数据 ( y ),不会修改原本数据 ( x )。

resize 会修改原本的数据 (x) 。

ndarray.T

  • 作用:数组转制, 将数组的行和列互换。
  • 返回数组,不修改原数组。

示例:

# 生成一个随机的 3行4列的数据
x = np.random.normal(0, 1, (3, 4))
# 输出数据查看
print(x)
print("---------------------------")
# 将数组的行和列互换
y = x.T
y

运行结果:

image-20240514191414809
提示

这里由于代码是在 jupyter 环境下写的,因此最后一行只写了 y 也是可以打印的,

而上面写 print(x) ,是因为不写则 jupyter 只会打印最后的 y ,

这样可以看到原本的数组和修改之后的数组,方便观察。

ndarray.astype()

  • 作用:修改数组类型,返回新的数组

示例:

# 创建 int32 类型数组
x = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)
# 输出数据查看
print(x, x.dtype)
print("---------------------------")
# 将数组的类型换成 int64
y = x.astype(np.int64)
print(y, y.dtype)

运行结果:

image-20240515084847049

数组运算

比较运算

数组可以针对数组中的元素进行判断并修改值

示例:

# 生成一组平均分布的学生成绩,5个学生,3个学科
arr = np.random.uniform(40, 100, (5, 3))
# 显示原数据
print(arr)
# 成绩大于60,显示为 true
print(arr>60)
# 成绩小于60的全部改为60
arr[arr<60] = 60
arr

运行结果:

image-20240515090910032

np.all()

  • 作用:判断被查询的数据是否全部满足条件。
  • 成立判断成立则返回 true,否则返回 false 。

示例:

# 生成一组平均分布的学生成绩,5个学生,3个学科
score = np.random.uniform(40, 100, (5, 3))
# 显示原数据
print(score)
# 判断查询的数据是否全部大于 60
np.all(score>60)

运行结果:

image-20240515091447202
它和直接使用数据判断的区别

直接判断则是会判断每一个数据,成立输出为 true, 不成立输出为 false。

# 生成一组平均分布的学生成绩,5个学生,3个学科
score = np.random.uniform(40, 100, (5, 3))
# 显示原数据
print(score)
print("------------------------------------")
# 判断查询的数据是否全部大于 60
res = np.all(score>60)
print(res)
print("------------------------------------")
score > 60

运行结果:

image-20240515091855307

np.any()

  • 作用:判断被查询的数据是否有满足条件的。
  • 满足返回 true, 不满足返回 false。

示例:

# 生成一组平均分布的学生成绩,5个学生,3个学科
score = np.random.uniform(40, 100, (5, 3))
# 显示原数据
print(score)
print("------------------------------------")
# 判断查询的数据是否有大于 60 的
res = np.any(score[4, 0:3]>60)  # 这里切片了最后一行的数据进行判断
print(res)

运行结果:

image-20240515092832321

注意 all() 和 any() 的区别

all() 是查询的数据都要满足条件。

any() 则是查询的数据只要有满足条件的即返回 true 。

np.where()

  • 作用:三元运算

示例:

# 生成一组平均分布的学生成绩,5个学生,3个学科
score = np.random.uniform(40, 100, (5, 3))
# 显示原数据
print(score)
print("------------------------------------")
# 判断查询的数据是否有大于 60 的,大于60的,修改为 1,否则修改为 0
np.where(score>60, 1, 0)

运行结果:

image-20240515094609345

np.logical_and()

  • 作用:做逻辑运算,and

示例:

# 生成一组平均分布的学生成绩,5个学生,3个学科
score = np.random.uniform(40, 100, (5, 3))
# 显示原数据
print(score)
print("------------------------------------")
# 判断查询的数据在 60-80之间的, 满足修改为 1,否则修改为 0
np.where(np.logical_and(score>60, score<80), 1, 0)

运行结果:

image-20240515095009538

np.logical_or()

  • 作用:逻辑运算 or

np.min(a, axis)

  • 作用:返回数组的最小值,或沿轴的最小值。

np.max(a, axis)

  • 作用:返回数组的最大值,或沿轴的最大值。

np.median(a, axis)

  • 作用:沿指定轴计算中值

np.mean(a, axis, dtype)

  • 作用:沿指定轴计算平均值

np.std(a, axis, dtype)

  • 作用:沿指定轴计算标准差

np.var(a, axis, dtype)

  • 作用:沿指定轴计算方差

示例:

# 生成一组员工薪资数据
salary = np.random.randint(6000, 50000, (5, 5))
# 打印查看
print(salary)
print("---------------------------------------")

# 查看最小值
s_min = np.min(salary)
print(s_min)
print("---------------------------------------")

# 查看最大值
s_max = np.max(salary)
print(s_max)
print("---------------------------------------")

# 按列查看中值
s_median = np.median(salary, axis=0)
print(s_median)
print("---------------------------------------")

# 按行查看平均值
s_mean = np.mean(salary, axis=1)
print(s_mean)
print("---------------------------------------")

# 按行计算标准差
s_std = np.std(salary, axis=1)
print(s_std)
print("---------------------------------------")

# 按行计算方差
s_var = np.var(salary, axis=1)
print(s_var)
image-20240515103312860

数组间运算

算数运算

示例:

# 创建一个数组
arr = np.array([[1,2,3], [4, 5, 6]])
arr1 = arr + 1
print(arr1)
arr2 = arr / 2
print(arr2)

运行结果:

image-20240515104333715

数组之间运算

  • 数组和数组进行运算

示例:

arr1 = np.array([[1, 2, 3], [4, 5, 6]])
arr2 = np.array([[2, 3, 4], [5, 6, 7]])
arr1 + arr2

运行结果:

image-20240515122500441

计算规则:

image-20240515134512333

广播机制

数组在进行矢量运算时,要求数组形状相等。

当形状不相等时,广播机制就发挥作用了,它可以扩展数组,使得数组的 shape 属性相同。

以此来进行矢量运算。

广播机制有两个条件(二者满足其中一个即可)

  • 数组的某个维度等长
  • 其中一个数组的某个维度是 1

示例:

arr1 = np.array([[1], [2], [3]])
arr2 = np.array([4, 5])
arr1 + arr2

运行结果:

image-20240515140549989

扩展图示:

image-20240515140505108

矩阵和向量

矩阵

注意

矩阵必须是二维的(array不一定,可以任意维)。

示例:

234567 \begin{matrix} 2 & 3 & 4 \\ 5 & 6 & 7 \\ \end{matrix}

这是一个 2x3 的矩阵,即 2行3列 ,矩阵的维数即 行数x列数

其他表示

矩阵的表示方法大体上是这样的,很多地方也会使用括号之类的包裹起来,比如下面这些

中括号

[234567] \begin{bmatrix} 2 & 3 & 4 \\ 5 & 6 & 7 \\ \end{bmatrix}

向量

向量是一种特殊的矩阵,一般列向量巨多

示例:

[123] \begin{bmatrix} & 1 &\\ & 2 &\\ & 3 &\\ \end{bmatrix}

矩阵加法

行和列数量相等时可以相加。

示例:

[123446]+[123446]=[2468812] \begin{bmatrix} & 1 & 2 & 3 & \\ & 4 & 4 & 6 & \\ \end{bmatrix} + \begin{bmatrix} & 1 & 2 & 3 & \\ & 4 & 4 & 6 & \\ \end{bmatrix} = \begin{bmatrix} & 2 & 4 & 6 & \\ & 8 & 8 & 12 & \\ \end{bmatrix}

矩阵和标量乘法

矩阵和标量相乘

示例:

3[123446]=[369121218] 3 * \begin{bmatrix} & 1 & 2 & 3 & \\ & 4 & 4 & 6 & \\ \end{bmatrix} = \begin{bmatrix} & 3 & 6 & 9 & \\ & 12 & 12 & 18 & \\ \end{bmatrix}

提示

每一个都要相乘

矩阵和向量乘法

示例:

[123456][12]=[51117] \begin{bmatrix} & 1 & 2 & \\ & 3 & 4 & \\ & 5 & 6 & \\ \end{bmatrix} * \begin{bmatrix} & 1 & \\ & 2 & \\ \end{bmatrix} = \begin{bmatrix} & 5 & \\ & 11 & \\ & 17 & \\ \end{bmatrix}

计算规则

(M行,N列) * (N行,L列)= (M行, L列)

1 * 1 + 2 * 2 = 5
3 * 1 + 4 * 2 = 11
5 * 1 + 6 * 2 = 17

矩阵和矩阵乘法

规则:行乘以列

示例:

[1234][5678]=[19224350] \begin{bmatrix} & 1 & 2 & \\ & 3 & 4 & \\ \end{bmatrix} * \begin{bmatrix} & 5 & 6 & \\ & 7 & 8 & \\ \end{bmatrix} = \begin{bmatrix} & 19 & 22 & \\ & 43 & 50 & \\ \end{bmatrix}

计算方法

[15+2716+2835+4736+48] \begin{bmatrix} & 1*5+2*7 && 1*6+2*8 & \\ & 3*5+4*7 && 3*6+4*8 & \\ \end{bmatrix}

注意

A矩阵 x B矩阵 不等于 B矩阵 x A矩阵

补充

单位矩阵:从左上角到右下角的对角线上均为 1 ,其他均为 0

[1001][100010001][1000010000100001] \begin{bmatrix} & 1 & 0 & \\ & 0 & 1 & \\ \end{bmatrix} \begin{matrix} & & \\ & & \\ \end{matrix} \begin{bmatrix} & 1 & 0 & 0 & \\ & 0 & 1 & 0 & \\ & 0 & 0 & 1 & \\ \end{bmatrix} \begin{matrix} & & \\ & & \\ \end{matrix} \begin{bmatrix} & 1 & 0 & 0 & 0 & \\ & 0 & 1 & 0 & 0 & \\ & 0 & 0 & 1 & 0 & \\ & 0 & 0 & 0 & 1 & \\ \end{bmatrix}

矩阵的逆和转置

逆矩阵概念:一个矩阵 A 乘以另外一个矩阵 B 等于单位矩阵,则矩阵 A 和 矩阵 B 互为逆矩阵。

矩阵转置:原本的第一列变成第一行,其它类推,即为转置

矩阵运算

案例

大学学习时,很多科目的最终成绩是由平时成绩(到课、平时作业等) 和 最终的期末考试成绩共同组成的,比如平时成绩

占最终成绩的 30%, 期末开始成绩占最终成绩的 70%,则大概有这样的一个表:

StudentId平时成绩期末成绩
180100
25070
36080
47060
58090
610060

请问如何使用矩阵计算?

答案

np.dot

作用:矩阵乘法计算

示例:

x1 = np.array([[1, 2], [3, 4]])
x2 = np.array([[5, 6], [7, 8]])
np.dot(x1, x2)

运行结果:

image-20240516134132673

案例答案

答案

x1 = np.array([[80, 100], 
              [50, 70],
              [60, 80],
              [70, 60],
              [80, 90],
              [100, 60]])
x2 = np.array([0.3, 0.7])
np.dot(x1, x2)

运行结果:

image-20240516134552135
Last Updated:
Contributors: leemiao