李翔-大数据技术

Big data technology!

第2章 科学计算库NumPy

第2章 科学计算库NumPy


第2章 科学计算库NumPy2.1 认识NumPy数组2.1.1 NumPy数组的相关概念2.2 创建数组2.2.1 创建数组的基本方式2.2.2 创建数组的其他方式2.3 数组的数据类型与属性2.3.1 创建时指定数组元素的类型2.3.2 查看数据类型2.3.3 转换数据类型2.3.4 NumPy数组的属性2.4 数组的索引和切片2.4.1 数组的索引方式2.4.2 切片2.4.3 花式索引2.4.4 布尔索引2.5 数组的算术运算2.5.1 形状相同的数组间的算术运算2.5.2 形状不同的数组间的算术运算2.5.3 数组与标量的算术运算2.6 通用函数2.6.1 常见一元通用函数2.6.2 常见二元通用函数2.7 数组的重塑与转置2.7.1 数组的重塑2.7.2 数组的转置2.8 数组的其他操作2.8.1 条件逻辑2.8.2 统计运算2.8.3 数组元素排序2.8.4 检索数组元素是否满足条件2.8.5 查找数组的唯一元素2.8.6 判断元素是否在其他数组中2.9 线性代数模块2.9.1 矩阵点积的条件2.9.2 常见函数2.10 随机数模块2.10.1 生成随机数数组2.10.2 seed()函数实验一:销售数据分析实验实验目标1. 数据准备2. 读取与创建数据3. 数据的基本操作3.1 数据的基本属性3.2 数据的切片3.3 利用布尔索引查找匹配的数据4. 条件筛选与排序4.1 数组排序4.2 查找唯一元素4.3 判断元素是否在其他数组中5. 数据统计分析5.1 订单金额的统计分析5.2 统计各商品类别的总销售金额6. 矩阵运算与线性代数6.1 矩阵乘法6.2 使用线性回归预测未来销售实验二:综合案例分析一、数据导入与基本数组操作二、基本数组操作三、统计分析四、数据清洗与处理 五、线性代数操作六、高级数组操作实验总结实验三:



img

NumPy 是 Python 中进行科学计算和数据处理的基础库,提供了高效的数组和矩阵运算功能,使得处理大规模数据变得更加简单和高效。


2.1 认识NumPy数组

2.1.1 NumPy数组的相关概念

  • 数组:数组是编程语言中重要且复杂的数据结构,它是由相同类型元素按照一定的顺序排列的集合

    1. 数组具有固定的大小,不会动态地增长。

    2. 数组中所有元素必须是相同的类型

    3. 数组操作大量数据的执行效率更高,代码量更少。

  • 维度:维度又称为维数,维度是指数据在不同方向上的扩展。简单来说,维度描述了数据的形状和结构。

    • 零维是一个无限小的点,没有长度;

    • 一维是一条无限长的直线,只有长度

    • 二维是一个平面,由长度和宽度组成;

    • 三维是一个立方体,由长度、宽度和高度组成。

      image-20240611094833055

    • 维度为k的数组通常被称为k维数组。数组按维度可以分为一维数组、二维数组、多维数组,常接触的多维数组是三维数组。

      image-20240611094852719

  • :轴(axis)是NumPy数组中十分重要的概念,它其实就代表维度。

    • 数组的维度不同,它对应的轴的数量也不同。一维数组只有一个轴,轴的编号为0

    • 二维数组有沿行方向和列方向的两个轴,其中沿行方向的轴编号为0,沿列方向的轴编号为1

    • 三维数组有沿着“深度”或“层”的方向、行、列方向的三个轴,这三个轴的编号分别为0、1、2

      image-20240611094913078



2.2 创建数组

2.2.1 创建数组的基本方式

NumPy中提供了多种创建数组的方式,其中最基本的方式就是通过array()函数创建数组,在使用该函数时直接传入列表或元组即可。

  • 在 NumPy 中,一维、二维和三维数组是由嵌套的方括号 [] 来决定的。每增加一层嵌套就增加一个新的维度。

# 导包
import numpy as np

# 创建一维数组
arr1d = np.array([1, 2, 3])

# 创建二维数组
arr2d = np.array([[1, 2, 3], [4, 5, 6]])

# 创建三维数组
arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])

三维数组的内容布局方式

image-20240611104412045


2.2.2 创建数组的其他方式

image-20240828120809875

  1. 通过zeros()函数可以创建元素值都是0的数组。

    np.zeros 函数用于生成一个数组,数组的每个元素都初始化为 0。

    请注意,np.zeros 函数生成的数组元素默认是浮点数 0.0,因此输出会显示为 0. 而不是整数 0。如果需要整数类型的数组,可以在函数中指定数据类型,例如 np.zeros(3, dtype=int)

    # 创建一维数组
    np.zeros(3)   # 创建一个包含3个元素、每个元素都为0的一维数组
    # 运行结果
    # array([0., 0., 0.])

    # 创建二维数组
    np.zeros((3, 3))
    # 运行结果
    # array([[0., 0., 0.],
    #        [0., 0., 0.],
    #        [0., 0., 0.]])
  2. 通过ones()函数可以创建元素值都为1的数组。

    # 创建一维数组
    np.ones(3)   # 创建一个包含3个元素、每个元素都为1的一维数组
    # 运行结果
    # array([1., 1., 1.])

    # 创建二维数组
    np.ones((3, 3))
    # 运行结果
    # array([[1., 1., 1.],
    #        [1., 1., 1.],
    #        [1., 1., 1.]])
  3. 通过full()函数可以创建一个全部元素为指定值的数组。

    # 创建一维数组
    np.full(5, 7)   # 创建一个包含5个元素、每个元素都为7的一维数组
    # 运行结果
    # array([7, 7, 7, 7, 7])

    # 创建二维数组
    np.full((2, 3), 7)  # 2行3列的数组,所有元素为7
    # 运行结果
    # array([[7, 7, 7],
    #        [7, 7, 7]])


  4. 通过arange()函数可以创建一个等差数组,它的功能类似于range(),只不过arange()函数返回的结果是一维数组,而不是列表。

    解释

    np.arange(start, stop, step) 生成一个从 startstop(不包括 stop)的数组,步长为 step

    image-20240828121156125

    np.arange(1, 21, 5)

    # 运行结果
    # array([ 1,  6, 11, 16])



    数组重塑

    np.arange(12).reshape(3, 4)

    # 运行结果
    # array([[ 0,  1,  2,  3],
    #        [ 4,  5,  6,  7],
    #        [ 8,  9, 10, 11]])

    解释

    np.arange(12) 生成一个一维数组,包含从 0 开始到 11 结束的整数。数组是 [0, 1, 2, ..., 11]

    .reshape(3, 4) 将前面生成的一维数组重塑为一个3行4列的二维数组。reshape 函数重新安排数组的形状而不改变其数据。


  5. 通过linspace()函数也可以创建一个等差数组,不同于arange()函数,linspace()函数需要指定数组中元素的数量,而不需要指定步长。

    image-20240828122641248

    解释

    np.linspace(start, stop, num) 生成一个从 startstop 之间的 num 个均匀分布的值的数组。

    np.linspace(1, 21, 5)

    # 运行结果
    # array([ 1.,  6., 11., 16., 21.])
    • 从 1 开始,到 21 结束,生成 5 个均匀分布的值。因此得到的数组是 [1.0, 6.0, 11.0, 16.0, 21.0]。每个值之间的间隔是 (21 - 1) / (5 - 1) = 20 / 4 = 5

arange和linspace区别总结

  • np.arange 按照指定的步长生成数组,不保证数组的最后一个元素是终点值。

  • np.linspace 按照指定的数量生成均匀分布的数组,确保数组的第一个元素是起点值,最后一个元素是终点值。


  1. 使用随机数创建数组

    • np.random.rand: 创建一个在 [0, 1) 范围内的均匀分布随机数组

      np.random.rand(2, 3)  # 2行3列的随机数组

      # 运行结果
      # array([[0.15379912, 0.91836724, 0.65965694],
      #       [0.56133933, 0.96184835, 0.6517046 ]])
    • np.random.randint: 创建一个整数随机数组

      np.random.randint(0, 10, (3, 3))  # 从0到10的3x3随机整数数组

      # 运行结果
      # array([[4, 7, 9],
      #        [8, 3, 2],
      #        [2, 1, 4]])




2.3 数组的数据类型与属性

2.3.1 创建时指定数组元素的类型

在使用前面介绍的函数创建数组时,可以通过dtype参数显式地指明元素的类型。

np.array([1, 2, 3], dtype=np.float32)

np.ones((3, 3), dtype=np.int32)
# 等同于下面的语句
np.ones((3, 3), dtype='int32')

image-20240611104839931


2.3.2 查看数据类型

如果要获取数组中元素数据类型的名称,则需要先通过数组访问dtype属性得到numpy.dtype类型的对象,再通过该对象访问name属性进行获取。

arr_one = np.array([[1, 2, 3], [4, 5, 6]])
arr_one.dtype.name  # 返回 'int32'

image-20240611105011114

注意事项:

  • 如果在创建数组时,没有显式地指明数据的类型,那么解释器会根据列表或元组中元素的类型推导出来。

  • 默认情况下,通过zeros()、ones()、empty()函数创建的数组中元素的数据类型为float64


常用数据类型

数据类型说明简写
bool布尔类型,值为True或Falseb
int8、uint8有符号和无符号的8位整数i或u
int16、uint16有符号和无符号的16位整数i2或u2
int32、uint32有符号和无符号的32位整数i4或u4
int64、uint64有符号和无符号的64位整数i8或u8


数据类型说明简写
float16半精度浮点数(16位,其中正负号1位,指数5位,精度10位)f2
float32单精度浮点数(32位,其中正负号1位,指数8位,精度23位)f4
float64双精度浮点数(64位,其中正负号1位,指数11位,精度52位)f8
complex64复数,分别用两个32位浮点数表示实部和虚部c8
complex128复数,分别用两个64位浮点数表示实部和虚部c16
object_Python对象O
string_固定长度的字符串类型S


2.3.3 转换数据类型

NumPy中提供了astype()方法可以将数组中元素的数据类型转换为其他的数据类型。

注意事项

  • 全数组转换astype() 方法会对整个数组的元素进行数据类型转换,而不能只对数组的某一部分(例如某一列)进行转换。

  • 新数组astype() 方法返回一个新的数组,原数组保持不变。

  • 类型检查:确保转换后的数据类型是可行的。例如,将浮点型数据转换为整数型时,小数部分将被舍弃。

import numpy as np

# 创建一个二维整数类型的数组
arr = np.array([[1, 2, 3], [4, 5, 6]])
print("原始数组的数据类型:", arr.dtype)

# 运行结果
# 原始数组的数据类型:int32

# 1: 全数组转换
# 将整个数组中的元素类型转换为浮点型
float_arr = arr.astype(np.float64)
print("转换后的数组:\n", float_arr)
print("转换后的数组的数据类型:\n", float_arr.dtype)

# 运行结果
# 转换后的数组:
#  [[1. 2. 3.]
#  [4. 5. 6.]]
# 转换后的数组的数据类型:
#  float64

# 2: 类型转换
# 创建一个浮点类型的数组
float_arr = np.array([1.1, 2.5, 3.8, 4.4, 5.9])
# 将数组中的元素类型转换为整数型
int_arr = float_arr.astype(np.int64)
print("转换后的数组的数据类型:\n", int_arr.dtype)

# 运行结果
# 转换后的数组 (浮点型转整数型):
#  [1 2 3 4 5]
# 转换后的数组的数据类型:
#  int64


2.3.4 NumPy数组的属性

注意:下面的ndarray 就是 代指NumPy 的数组

  • ndarray.ndim:数组轴的个数

  • ndarray.shape:数组维度的元组,元组中各个元素表示每个维度上数组的大小

    • shape属性的值是一个元组,元组里面有多少个元素取决于数组的维度。

  • ndarray.size:数组元素的总个数,等于shape属性中元组元素的乘积

  • ndarray.dtype:描述数组中元素类型的对象,既可以使用标准的Python类型创建或指定,也可以使用NumPy特有的数据类型来指定


在 NumPy 中,一维、二维和三维数组是由嵌套的方括号 [] 来决定的。每增加一层嵌套就增加一个新的维度。


一维数组:

定义:一维数组是一条线性排列的元素序列,只有一个维度。

创建方式:用一个方括号包裹所有元素。

image-20240611095656521

# 导包
import numpy as np

# 创建一维数组
arr1d = np.array([1, 2, 3])

# 获取数组的维度(轴)的个数
num_axes = arr1d.ndim
print("数组的轴的个数(维度数):", num_axes)  # 输出: 1

# 数组的形状(shape)是一个表示各维度大小的元组。
# 获取数组的形状(维度元组)
shape = arr1d.shape
print("数组的形状:", shape)  # 输出: (3,)
# 对于一维数组,形状是 (3,),表示数组有一个维度,这个维度的长度是 3。
# 逗号 `,` 是为了明确表示这是一个元组,而不是一个单独的整数。
# 如果没有逗号,单独的整数 `4` 可能被误解为一个标量【单一的数值】,而不是一个形状。

# 获取数组的元素总个数
total_elements = arr1d.size
print("数组的元素总个数:", total_elements)  # 输出: 3

# 获取数组的元素类型
element_type = arr1d.dtype
print("数组的元素类型:", element_type)  # 输出: int32(具体类型取决于系统)


二维数组:

定义:二维数组是一个矩阵,由行和列组成,有两个维度。可以将二维数组看作是一维数组的数组,每个一维数组代表矩阵中的一行。

创建方式:使用嵌套的方括号表示行和列。

image-20240611095914639

import numpy as np

# 创建二维数组
arr2d = np.array([[1, 2, 3], [4, 5, 6]])

# 获取数组的轴的个数
num_axes = arr2d.ndim
print("数组的轴的个数(维度数):", num_axes)  # 输出: 2

# 获取数组的形状(维度元组)
shape = arr2d.shape
print("数组的形状:", shape)  # 输出: (2, 3)

# 获取数组的元素总个数
total_elements = arr2d.size
print("数组的元素总个数:", total_elements)  # 输出: 6

# 获取数组的元素类型
element_type = arr2d.dtype
print("数组的元素类型:", element_type)  # 输出: int64(具体类型取决于系统)


三维数组:

定义:三维数组是多个二维数组的集合,有三个维度。

创建方式:用三个层级的方括号,每个内层方括号表示一个二维数组。

image-20240719130552487

在 NumPy 中,数组(ndarray)可以具有多个维度。三维数组是一个具有三个维度的数组。其形状由一个三元组表示,三维数组 (p, m, n):包含 pmn 列的二维数组。

import numpy as np

# 创建三维数组
arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])

# 获取数组的轴的个数
num_axes = arr3d.ndim
print("数组的轴的个数(维度数):", num_axes)  # 输出: 3

# 获取数组的形状(维度元组)
shape = arr3d.shape
print("数组的形状:", shape)  # 输出: (2, 2, 3)

# 获取数组的元素总个数
total_elements = arr3d.size
print("数组的元素总个数:", total_elements)  # 输出: 12

# 获取数组的元素类型
element_type = arr3d.dtype
print("数组的元素类型:", element_type)  # 输出: int32(具体类型取决于系统)



2.4 数组的索引和切片

2.4.1 数组的索引方式

数组是通过索引的方式标记元素的位置,数组的类型不同,索引方式也会有一些区别。

  • 一维数组的索引方式:每个元素对应两种索引,分别是正向索引和反向索引。正向索引从左向右依次递增,反向索引从右向左依次递减。

    image-20240611105346256

import numpy as np

# 创建一个一维数组
arr = np.array([10, 6, 5, 11, 18, 16, 9, 0, 3, 20])

# 获取第一个元素
print(arr[0])  # 输出: 10

# 获取第三个元素
print(arr[2])  # 输出: 5

# 获取第六个元素
print(arr[5])  # 输出: 16

# 获取最后一个元素【倒数第一个元素】
print(arr[-1])  # 输出: 20

# 获取倒数第二个元素
print(arr[-2])  # 输出: 3

# 获取倒数第四个元素
print(arr[-4])  # 输出: 9

# 使用正向索引获取第五个元素
print(arr[4])  # 输出: 18


  • 二维数组的索引方式:每个元素对应行索引和列索引,其中行索引和列索引可以是正向索引或反向索引。

image-20240611105402358

import numpy as np

# 创建一个二维数组
arr = np.array([
   [1, 6, 11, 16, 21],
   [2, 7, 12, 17, 22],
   [3, 8, 13, 18, 23],
   [4, 9, 14, 19, 24],
   [5, 10, 15, 20, 25]
])

# 获取第一行第一列的元素
print(arr[0, 0])  # 输出: 1

# 获取第三行第三列的元素
print(arr[2, 2])  # 输出: 13

# 获取最后一行最后一列的元素
print(arr[-1, -1])  # 输出: 25

# 获取第二行倒数第二列的元素
print(arr[1, -2])  # 输出: 17

# 获取倒数第三行第一列的元素
print(arr[-3, 0])  # 输出: 3

# 获取第一行第三列的元素
print(arr[0, 2])  # 输出: 11

# 获取倒数第一行倒数第四列的元素
print(arr[-1, -4])  # 输出: 10

# 获取第四行倒数第三列的元素
print(arr[3, -3])  # 输出: 14

# 获取最后一行第一列的元素
print(arr[-1, 0])  # 输出: 5

# 获取第2行元素
print(arr[1])  # 输出:[2, 7, 12, 17, 22]

# 获取第2列元素
print(arr[:,1])  # 输出:[ 6  7  8  9 10]

整数索引总结

  • arr2d[i, j] 选择第 i 行,第 j 列的单个元素。

  • arr2d[i]arr2d[i, :] 选择第 i 行的所有元素。

  • arr2d[:, j] 选择所有行的第 j 列的元素。


2.4.2 切片

基本概念: 切片是通过指定起始位置、结束位置和步长来获取数组的一个连续子集。

一维数组语法:array[start:stop:step]

二维数组语法: array[start_row:stop_row:step_row, start_col:stop_col:step_col]

  • 行切片 (start_row:stop_row:step_row): 用于选择数组的行。start_row 是起始行索引,stop_row 是结束行索引(不包含),step_row 是步长。

  • 列切片 (start_col:stop_col:step_col): 用于选择数组的列。start_col 是起始列索引,stop_col 是结束列索引(不包含),step_col 是步长。

特点:

  • 返回的视图是原数组的一部分,因此对切片的修改会影响到原数组。

  • 支持多维数组的切片操作,例如array[1:3, 2:4]获取二维数组的一个子矩阵。

  • 切片只能处理连续的元素。无法选择不连续的单个元素或索引。


一维数组切片

import numpy as np

arr1d = np.array([1, 6, 11, 16, 21])

常见切片操作

在 Python 中,数组的索引从0开始。

  1. 基本切片:访问一个特定范围的元素。

    # 切片从索引1到索引3(不包括3)
    slice1 = arr1d[1:3]  
    print(slice1) # 输出: [6 11]
  2. 从开始到某个位置的切片

    # 从开始到索引3(不包括3)
    slice2 = arr1d[:3]
    print(slice2)  # 输出: [1 6 11]
  3. 从某个位置到结束的切片

    # 从索引2到结束
    slice3 = arr1d[2:]
    print(slice3)  # 输出: [11 16 21]
  4. 步长切片:每隔一定步长取一个元素。

    # start: 默认从索引 0 开始。
    # stop: 默认直到数组的末尾。
    # step = 2: 这表示每隔两个索引取一个元素,即选择一个元素,然后跳过一个元素,再选择下一个元素。

    slice4 = arr1d[::2]
    print(slice4)
    # 输出: [1 11 21]
  5. 负索引切片:使用负索引从数组的末尾开始切片。

    # 从末尾倒数第4个元素到倒数第1个元素(不包括倒数第1个元素)
    slice5 = arr1d[-4:-1]
    print(slice5)
    # 输出: [6 11 16]
  6. 带步长的负索引切片:从数组末尾开始,以步长为负值进行切片。

    # 步长为 -1 进行切片,切片从最后一个元素开始,从后向前逐步访问数组的每一个元素。
    slice6 = arr1d[::-1]  
    print(slice6)
    # 输出: [21 16 11 6 1]


二维数组切片

二维数组语法: array[start_row:stop_row:step_row, start_col:stop_col:step_col]

  • 行切片 (start_row:stop_row:step_row): 用于选择数组的行。start_row 是起始行索引,stop_row 是结束行索引(不包含),step_row 是步长。

  • 列切片 (start_col:stop_col:step_col): 用于选择数组的列。start_col 是起始列索引,stop_col 是结束列索引(不包含),step_col 是步长。


  • 获取二维数组的一行元素:

    import numpy as np
    arr2d = np.array([
                     [1, 6, 11, 16, 21],
                     [2, 7, 12, 17, 22],
                     [3, 8, 13, 18, 23],
                     [4, 9, 14, 19, 24],
                     [5, 10, 15, 20, 25]
                    ])

    arr2d[2, :]      # 等同于整数索引arr2d[2]  

    # 运行结果
    # array([ 3,  8, 13, 18, 23])


切片的基本用法

如果希望获取二维数组的多行元素,则可以通过“数组[行索引的切片]”的形式实现。

image-20240611105512637

  • 获取二维数组的多行元素:

    arr2d[:2]

    # 运行结果
    # array([[ 1,  6, 11, 16, 21],
    #        [ 2,  7, 12, 17, 22]])

    切片解释

    arr2d[:2] 表示从二维数组 arr2d 中切出前 2 行的所有列。具体来说:

    因此,arr2d[:2] 等价于 arr2d[0:2,:]


    • : 表示选择所有元素。

    • :2 表示选择从索引 0 到索引 2(不包括 2)之间的所有行。

  • 获取二维数组的部分元素:

    image-20240611105533190

    arr2d[:2, :2]

    # 运行结果
    # array([[1, 6],
    #       [2, 7]])

    切片解释

    arr2d[:2, :2] 表示从二维数组 arr2d 中切出前 2 行,前2列。具体来说:


    • : 表示选择所有元素。

    • :2 表示选择从索引 0 到索引 2(不包括 2)之间的所有行。

    • :2 同理,第二个 :2 表示从第0列到第2列(不包括第2列),即选择第0列和第1列。

索引与切片的混合用法

  • 混合使用切片与整数索引:

    image-20240611105553697

    arr2d[:, 2]

    # 运行结果
    # array([11, 12, 13, 14, 15])


常见的二维数组切片1:

image-20240828171442800

import numpy as np

# 创建一个 3x4 的二维数组
a = np.array([[1, 2, 3, 4],
             [5, 6, 7, 8],
             [9, 10, 11, 12]])

# 1. 获取第1行,第2列的元素
result_1 = a[1, 2]
print("a[1, 2] =", result_1)
# 输出: a[1, 2] = 7

# 2. 获取第1行的所有元素
result_2 = a[1, :]
print("a[1, :] =", result_2)
# 输出: a[1, :] = [5 6 7 8]

# 3. 获取第2列的所有元素
result_3 = a[:, 2]
print("a[:, 2] =", result_3)
# 输出: a[:, 2] = [ 3  7 11]

# 4. 获取第1到2列之间的所有列
result_4 = a[:, 1:3]
print("a[:, 1:3] =\n", result_4)
# 输出:
# a[:, 1:3] =
# [[ 2  3]
#  [ 6  7]
#  [10 11]]

# 5. 获取最后两行的最后两列
result_5 = a[-2:, -2:]
print("a[-2:, -2:] =\n", result_5)
# 输出:
# a[-2:, -2:] =
# [[ 7  8]
#  [11 12]]

# 6. 获取从第0行到第2行,每隔一行取一次,从第0列到第3列,每隔两列取一次
result_6 = a[::2, 1::2]
print("a[::2, 1::2] =\n", result_6)
# 输出:
# a[::2, 1::2] =
# [[ 2  4]
#  [10 12]]


常见的二维数组切片2

import numpy as np

# 创建一个二维数组
arr2d = np.array([
   [1, 6, 11, 16, 21],
   [2, 7, 12, 17, 22],
   [3, 8, 13, 18, 23],
   [4, 9, 14, 19, 24],
   [5, 10, 15, 20, 25]
])

# 1. 提取第一行的所有元素
row1 = arr2d[0, :]
print("第一行的所有元素:", row1)
# 输出: [ 1  6 11 16 21]

# 2. 提取前两行的所有元素
first_two_rows = arr2d[0:2, :]
print("前两行的所有元素:\n", first_two_rows)
# 输出:
# [[ 1  6 11 16 21]
#  [ 2  7 12 17 22]]

# 3. 提取所有行的第一列元素
first_col = arr2d[:, 0]
print("所有行的第一列元素:", first_col)
# 输出: [1 2 3 4 5]

# 4. 提取第2到第4行,第3到第5列的子矩阵
sub_matrix = arr2d[1:4, 2:5]
print("第2到第4行,第3到第5列的子矩阵:\n", sub_matrix)
# 输出:
# [[12 17 22]
#  [13 18 23]
#  [14 19 24]]

# 5. 提取最后两行和最后两列的子矩阵
last_rows_cols = arr2d[-2:, -2:]
print("最后两行和最后两列的子矩阵:\n", last_rows_cols)
# 输出:
# [[19 24]
#  [20 25]]

# 6. 提取奇数行的所有元素(行索引为1, 3)
odd_rows = arr2d[1::2, :]
print("奇数行的所有元素:\n", odd_rows)
# 输出:
# [[ 2  7 12 17 22]
#  [ 4  9 14 19 24]]

# 7. 提取所有行的奇数列元素(列索引为1, 3)
odd_cols = arr2d[:, 1::2]
print("所有行的奇数列元素:\n", odd_cols)
# 输出:
# [[ 6 16]
#  [ 7 17]
#  [ 8 18]
#  [ 9 19]
#  [10 20]]


2.4.3 花式索引

基本概念: 通过一个数组或列表作为索引,来指定任意位置的元素,并返回一个包含这些元素的新数组。

语法: array[[i1, i2, ...]]array[[[i1, j1], [i2, j2]]] 对于二维数组

特点:

  • 返回的是原数组的一个副本,因此对花式索引结果的修改不会影响原数组。

  • 可以选择不连续的元素,允许在不同位置选取元素。

  • 支持多维数组,通过提供多个索引数组来实现。

基本用法

  • 使用花式索引操作一维数组:

    import numpy as np

    # 创建一个一维数组
    arr = np.array([10, 6, 5, 11, 18, 16, 9, 0, 3, 20])

    # 使用花式索引,通过列表或数组指定索引位置
    # 假设我们想获取索引为 0, 3, 9 的元素
    indexed_with_list = arr[[0, 3, 9]]
    print(f"使用列表进行花式索引的结果: {indexed_with_list}")
    # 运行结果:使用列表进行花式索引的结果: [10 11 20]

    # 使用 NumPy 数组进行花式索引,获取相同的位置
    indexed_with_array = arr[np.array([0, 3, 9])]
    print(f"使用数组进行花式索引的结果: {indexed_with_array}")
    # 运行结果:使用数组进行花式索引的结果: [10 11 20]

    image-20240611105743241

    • 花式索引使用索引数组中的每个整数作为索引,获取对应位置的元素。

  • 使用花式索引操作二维数组:

    image-20240611105854004

    import numpy as np

    # 创建一个5x5的二维数组
    arr2d = np.array([[1, 6, 11, 16, 21],
                      [2, 7, 12, 17, 22],
                      [3, 8, 13, 18, 23],
                      [4, 9, 14, 19, 24],
                      [5, 10, 15, 20, 25]])

    # 1. 使用花式索引选择第2行和第5行
    result_1 = arr2d[[1, 4]]
    print("arr2d[[1, 4]] =\n", result_1)
    # 输出:
    # arr2d[[1, 4]] =
    # [[ 2  7 12 17 22]
    #  [ 5 10 15 20 25]]

    # 2. 使用花式索引选择第2列和第5列
    result_2 = arr2d[:, [1, 4]]
    print("arr2d[:, [1, 4]] =\n", result_2)
    # 输出:
    # arr2d[:, [1, 4]] =
    # [[ 6 21]
    #  [ 7 22]
    #  [ 8 23]
    #  [ 9 24]
    #  [10 25]]


    • 花式索引使用索引中的每个整数作为行索引,获取对应行的元素。

  • 使用花式索引操作二维数组的部分元素:

    如果想要访问二维数组中的部分元素,而不是整行元素,则需要通过两个花式索引完成,

    其中:

    第一个花式索引中的整数会被作为行索引,

    第二个花式索引中的整数会被作为列索引。

    # 选择第1行第2列和第4行第3列的元素
    result = arr2d[[1,4], [2, 3]]
    result

    # 运行结果
    array([12, 20])

    索引数组的解释

    索引位置的匹配

    image-20240728121427138

    # 选择第2行第3列的数据
    result = arr2d[[1], [2]]
    result

    # 运行结果
    array([12])


    • 第 1 行的第 2 列位置:arr2d[1, 2] 对应的元素是 12

    • 第 4 行的第 3 列位置:arr2d[4, 3] 对应的元素是 20

    • 第一个索引数组 [1, 4]:表示要从二维数组的第2行和第 5 行选择元素。

    • 第二个索引数组 [2, 3]:表示要从对应行中选择的第3列和第4列的位置。

切片和花式索引的区别

切片

切片是一种用于选择连续区域的方法。它使用冒号 : 来指定范围,并可以应用于一维和多维数组。

特点:

  • 只能选择连续的元素。

  • 返回的是原数组的一个视图(view),不创建新的数组,修改切片会影响原数组。【因为视图和原数组共享相同的数据存储。】

示例:

import numpy as np

# 创建一个5x5的二维数组
arr2d = np.array([[1, 6, 11, 16, 21],
                 [2, 7, 12, 17, 22],
                 [3, 8, 13, 18, 23],
                 [4, 9, 14, 19, 24],
                 [5, 10, 15, 20, 25]])

# 使用切片选择第2到第4列【选择从第 2 列(索引 1)到第 4 列(索引 3)之间的所有列,但不包括第 5 列(索引 4)。因此,它选择的是第 2 到第 4 列。】
result = arr2d[:, 1:4]
print("切片结果:")
print(result)
#切片结果:
# [[ 6 11 16]
#  [ 7 12 17]
#  [ 8 13 18]
#  [ 9 14 19]
#  [10 15 20]]

# 修改切片中的一个值
result[0, 0] = 99

# 打印修改后的切片
print("\n修改后的切片结果:")
print(result)
# 修改后的切片结果:
# [[99 11 16]
#  [ 7 12 17]
#  [ 8 13 18]
#  [ 9 14 19]
#  [10 15 20]]

# 打印修改后的原数组
print("\n修改后的原数组:")
print(arr2d)
# 修改后的原数组:
# [[ 1 99 11 16 21]
#  [ 2  7 12 17 22]
#  [ 3  8 13 18 23]
#  [ 4  9 14 19 24]
#  [ 5 10 15 20 25]]


花式索引

花式索引允许通过指定索引列表或数组来选择任意位置的元素。它可以选择非连续的元素,并返回一个新的数组。

特点:

  • 可以选择非连续的元素。

  • 返回的是一个新的数组(副本),修改新数组不会影响原数组。

示例:

import numpy as np

# 创建一个5x5的二维数组
arr2d = np.array([[1, 6, 11, 16, 21],
                 [2, 7, 12, 17, 22],
                 [3, 8, 13, 18, 23],
                 [4, 9, 14, 19, 24],
                 [5, 10, 15, 20, 25]])

# 使用花式索引选择第2到第4列【索引是1-3】的所有行
cols = [1, 2, 3]
result = arr2d[:, cols]
print("花式索引结果:")
print(result)
# 切片结果:
# [[ 6 11 16]
#  [ 7 12 17]
#  [ 8 13 18]
#  [ 9 14 19]
#  [10 15 20]]

# 修改花式索引生成的新数组
result[0, 0] = 99

# 打印修改后的新数组
print("\n修改后的新数组:")
print(result)
# 修改后的切片结果:
# [[99 11 16]
#  [ 7 12 17]
#  [ 8 13 18]
#  [ 9 14 19]
#  [10 15 20]]

# 打印原数组
print("\n原数组:")
print(arr2d)
#修改后的原数组:
# [[ 1  6 11 16 21]
#  [ 2  7 12 17 22]
#  [ 3  8 13 18 23]
#  [ 4  9 14 19 24]
#  [ 5 10 15 20 25]]


区别总结

  1. 选择范围

    • 切片:选择连续的元素(使用 :)。

    • 花式索引:选择非连续的任意元素(使用列表或数组)。

  2. 返回结果

    • 切片:返回原数组的视图,修改会影响原数组。

    • 花式索引:返回新数组,修改不会影响原数组。


2.4.4 布尔索引

定义: 布尔索引是一种基于条件筛选 NumPy 数组元素的方式。它使用一个布尔数组(由 TrueFalse 组成)来选择数组中的特定元素。

语法:

  • 基本语法: array[boolean_array]

    • array 是要被筛选的数组。

    • boolean_array 是一个与 array 形状相同的布尔数组,或者是一个表达式,用于生成布尔数组。

  • 条件筛选: array[condition]

    • condition 是一个返回布尔值的条件表达式。

特点:

  1. 基于条件筛选:布尔索引根据条件表达式的结果来筛选元素,返回满足条件的元素组成的新数组。

  2. 返回新数组:布尔索引返回的是一个新数组,不影响原数组的数据。

  3. 灵活性高:可以基于任意逻辑条件(如大小比较、相等性检查等)来选择数组元素。


1. 基本示例

假设我们有一个数组,我们希望选择所有大于某个值的元素:

import numpy as np

# 创建一个数组
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

# 布尔索引选择所有大于5的元素
result = arr[arr > 5]
print(result)  # 输出: [ 6  7  8  9 10]

解释:

条件判断

  • arr > 5:生成一个布尔数组,表示 arr 中哪些元素大于 5。

  • 结果是 [False, False, False, False, False, True, True, True, True, True]

布尔索引

  • arr[arr > 5]:使用布尔数组作为索引,从 arr 中选择对应位置为 True 的元素。

  • 结果是 [6, 7, 8, 9, 10]


2. 多条件布尔索引

你可以使用多个条件来进行选择:

# 选择所有大于5且小于8的元素
result = arr[(arr > 5) & (arr < 8)]
print(result)  # 输出: [6 7]


3. 在二维数组中的应用

布尔索引同样适用于多维数组:

在NumPy中,布尔索引会返回一个一维数组,这是因为布尔索引会将所有符合条件的元素提取出来并放入一个新的数组中,而不会保留原数组的形状。这使得结果更加紧凑和易于处理。

# 创建一个二维数组
arr_2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 选择所有大于5的元素
result = arr_2d[arr_2d > 5]
print(result)  # 输出: [6 7 8 9]



2.5 数组的算术运算

2.5.1 形状相同的数组间的算术运算

在NumPy中,形状相同的数组之间进行任何算术运算时都会应用到元素级,即将位置相同的元素进行算术运算,所得的运算结果组成一个新的数组。

image-20240611110120080

import numpy as np

# 创建数组 arr_one
arr_one = np.array([
   [1, 2, 3, 4],
   [5, 6, 7, 8],
   [9, 10, 11, 12]
])

# 创建数组 arr_two
arr_two = np.array([
   [2, 3, 4, 5],
   [6, 7, 8, 9],
   [10, 11, 12, 13]
])

# 计算两个数组的和
arr_res = arr_one + arr_two

# 打印结果
print("arr_one:")
print(arr_one)
print("\narr_two:")
print(arr_two)
print("\narr_res:")
print(arr_res)


# 运行结果
arr_one:
[[ 1  2  3  4]
[ 5  6  7  8]
[ 9 10 11 12]]

arr_two:
[[ 2  3  4  5]
[ 6  7  8  9]
[10 11 12 13]]

arr_res:
[[ 3  5  7  9]
[11 13 15 17]
[19 21 23 25]]


2.5.2 形状不同的数组间的算术运算

在NumPy中,形状不同的数组在执行算术计算时可能会触发广播机制,该机制会对参与运算的数组进行扩展,使扩展后的数组具有相同的形状,这样就可以对数组进行算术运算了。

什么是形状兼容

以两个数组为例,这两个数组的形状右对齐,之后沿着从右向左的顺序逐个比较同一维度是否满足以下任意一种情况:

判断两个数组是否形状兼容的条件:

  • 从右到左逐维度比较两个数组的形状。

  • 如果某个维度的长度相同,或者其中一个数组在该维度的长度为 1,则该维度是兼容的。

  • 如果不满足上述条件,则数组形状不兼容,无法进行广播操作。

如果数组的形状的每个维度都满足上述任意一种情况,说明两个数组的形状兼容。

形状兼容示例

# A (2d array): 5 x 4
# B (1d array): 4
# Result (2d array): 5 x 4

解释:
二维数组 A 的形状是 (5, 4),一维数组 B 的形状是 (4)
在进行运算时,一维数组 B 会被扩展为形状 (5, 4),以便与 A 进行兼容。

# C (4d array): 8 x 1 x 6 x 1
# D (3d array): 7 x 1 x 5
# Result (4d array): 8 x 7 x 6 x 5

image-20240611110240152

image-20240611110336954


广播机制的具体规则:

  • 如果两个数组的维度数不同,将维度较小的数组的形状前面填充 1,直到两个数组的维度数相同

  • 从尾部(即从最后一个维度)开始,比较每一个维度

    • 如果两个维度相等,继续比较下一个维度。

    • 如果两个维度中有一个是 1,则将该维度扩展为与另一个数组相同的维度。

    • 如果两个维度既不相等,也不为 1,则无法进行广播。


让我们用一个简单的示例来说明NumPy的广播机制。

假设我们有两个数组:

  • 数组 A:形状为 (2, 3)

  • 数组 B:形状为 (3,)

我们希望对这两个数组进行加法运算。

示例:

import numpy as np

A = np.array([[1, 2, 3],
             [4, 5, 6]])  # 形状 (2, 3)

B = np.array([10, 20, 30])  # 形状 (3,)

# 执行加法运算
result = A + B
print(result)

解释:

  1. 确定形状

    • 数组 A 的形状是 (2, 3)

    • 数组 B 的形状是 (3,)

  2. 维度补齐

    • 为了使维度数相同,NumPy 会在 B 的形状前面补齐 1,使得 B 的形状变为 (1, 3)

  3. 维度兼容检查

    • 第一维度:2 和 1,满足条件(其中一个维度长度为 1)

    • 第二维度:3 和 3,满足条件(维度长度相等)

    • A 的形状是 (2, 3)

    • 补齐后的 B 的形状是 (1, 3)

    • 比较每个维度:

  4. 广播

    • B 被扩展为与 A 形状相同,即 (2, 3)

    • B 的内容变为:

      [[10, 20, 30],
      [10, 20, 30]]
  5. 进行加法运算

    • A 和 B 对应元素相加:

      [[ 1 + 10,  2 + 20,  3 + 30],
      [ 4 + 10,  5 + 20,  6 + 30]]
    • 结果为:

      [[11, 22, 33],
      [14, 25, 36]]

输出结果

[[11 22 33]
[14 25 36]]

这个示例展示了如何通过广播机制使两个形状不同的数组进行加法运算。


2.5.3 数组与标量的算术运算

在NumPy中,数组与标量之间的算术运算十分方便。数组中的每个元素会自动与标量进行运算,这种操作被称为广播(broadcasting)。广播机制使得我们能够以简洁的代码实现复杂的数值计算。以下是一些示例来展示数组与标量之间的加法、减法、乘法和除法运算。

加法运算

数组中的每个元素与标量相加:

import numpy as np

arr = np.array([1, 2, 3])
result_add = arr + 2
print(result_add)  # 输出: [3, 4, 5]

减法运算

数组中的每个元素与标量相减:

result_subtract = arr - 2
print(result_subtract)  # 输出: [-1, 0, 1]

乘法运算

数组中的每个元素与标量相乘:

result_multiply = arr * 2
print(result_multiply)  # 输出: [2, 4, 6]

除法运算

数组中的每个元素与标量相除:

result_divide = arr / 2
print(result_divide)  # 输出: [0.5, 1.0, 1.5]

取余运算

数组中的每个元素与标量进行取余操作:

result_mod = arr % 2
print(result_mod)  # 输出: [1, 0, 1]

幂运算

数组中的每个元素与标量进行幂运算:

result_power = arr ** 2
print(result_power)  # 输出: [1, 4, 9]


应用示例:标准化数据

假设我们有一个表示考试成绩的数组,我们希望将其标准化(即每个元素减去平均值,再除以标准差):

注:标准化是将数据调整到一个新范围,使得数据的平均值为0,标准差为1。

import numpy as np

# 定义一个包含四个分数的数组
scores = np.array([70, 80, 90, 100])

# 计算分数的均值(平均值)
mean = np.mean(scores)  # mean = (70 + 80 + 90 + 100) / 4 = 85

# 计算分数的标准差:标准差表示数据点与平均值之间的距离的平均水平。
# 标准差是通过计算数据点与均值的差值平方的平均数(方差),然后取平方根得到的。
std_dev = np.std(scores)  # 标准差反映数据的分散程度

# 使用标准化公式将分数标准化
# 标准化公式: (每个分数 - 均值) / 标准差
standardized_scores = (scores - mean) / std_dev

# 打印标准化后的分数
print(standardized_scores)  # 输出: [-1.34164079 -0.4472136  0.4472136  1.34164079]


应用示例:图像处理

在图像处理领域,常常需要对像素值进行操作。例如,将每个像素的值增加50,以增加图像的亮度:

image = np.array([[100, 150], [200, 250]])
brightened_image = image + 50
print(brightened_image)  # 输出: [[150, 200], [250, 300]]

通过这些示例,我们可以看到NumPy的广播机制在数组与标量运算中提供了强大的功能,使得数值计算变得更加简洁和高效。在实际应用中,这些操作可以大大简化代码,提高工作效率。



2.6 通用函数

通用函数是一些针对ndarray对象中的逐个元素进行运算的函数,这些函数都会产生一个新的数组。通常情况下,我们将接收一个数组参数的函数称为一元通用函数,接收两个数组参数的函数称为二元通用函数。

2.6.1 常见一元通用函数

  • abs(x):计算数组x中各元素的绝对值

  • fabs(x):计算数组x中各元素的绝对值,绝对值都是浮点数

  • sqrt(x):计算数组x中各元素的平方根

  • square(x):计算数组x中各元素的平方

  • exp(x):计算数组x中各元素的指数

  • log(x):计算数组x中各元素e为底数的对数

  • log10(x):计算数组x中各元素10为底数的对数

  • log2(x):计算数组x中各元素2为底数的对数

  • sign(x):返回数组x中各元素的符号值,包括1、0、-1,其中1表示正数,-1表示负数

  • ceil(x):计算数组x中各元素的ceilling值,即大于或等于该值的最小整数

  • floor(x):计算数组x中各元素的floor值,即小于等于该值的最大整数

  • rint(x):将数组x中各元素四舍五入到的整数

  • modf(x):将数组x中各元素的小数部分和整数部分以两个独立数组的形式返回

  • isnan(x):判断数组x中各元素的值是否为NaN

  • isfinite(x):返回表示数组x中各元素是否有限的布尔型数组

  • isinf(x):返回表示数组x中各元素是否无限的布尔型数组

  • sin(x):计算数组x中各元素的正弦值

  • sinh(x):计算数组x中各元素的双曲正弦值

  • cos(x):计算数组x中各元素的余弦值

  • cosh(x):计算数组x中各元素的双曲余弦值

  • tan(x):计算数组x中各元素的正切值

  • tanh(x):计算数组x中各元素的双曲正切值

  • arccos(x):计算数组x中各元素的反余弦值

  • arccosh(x):计算数组x中各元素的反双曲余弦值

  • arcsin(x):计算数组x中各元素的反正弦值

  • diff():计算沿给定轴的n阶离散差,返回一个由相邻元素的差值构成的数组

  • std(x):计算数组x中各元素的标准差


abs(x) 示例

abs(x) 或者 np.abs(x) 函数用于计算数组 x 中每个元素的绝对值。

示例代码

import numpy as np

# 创建一个包含正数和负数的一维数组
arr = np.array([-1, -2, -3, 4, 5, -6])

# 使用 abs 函数计算每个元素的绝对值
abs_arr = np.abs(arr)

print("原数组:", arr)
print("绝对值数组:", abs_arr)

输出

原数组: [-1 -2 -3  4  5 -6]
绝对值数组: [1 2 3 4 5 6]


2.6.2 常见二元通用函数

  • add(x1, x2):将数组x1和x2中位置对应的元素相加,相当于x1 + x2

  • subtract(x1, x2):将数组x1和x2中位置对应的元素相减,相当于x1 - x2

  • multiply(x1, x2):将数组x1和x2中位置对应的元素相乘,相当于x1 * x2

  • divide(x1, x2):将数组x1和x2中位置对应的元素相除,相当于x1 / x2

  • floor_divide(x1, x2):将数组x1和x2中位置对应的元素整除,相当于x


add(x1, x2) 示例

add(x1, x2) 或者 np.add(x1, x2) 函数用于将数组 x1x2 中位置对应的元素相加,相当于 x1 + x2

示例代码

import numpy as np

# 创建两个一维数组
arr1 = np.array([1, 2, 3, 4, 5])
arr2 = np.array([10, 20, 30, 40, 50])

# 使用 add 函数将两个数组对应元素相加
sum_arr = np.add(arr1, arr2)

print("数组1:", arr1)
print("数组2:", arr2)
print("相加后的数组:", sum_arr)

输出

数组1: [1 2 3 4 5]
数组2: [10 20 30 40 50]
相加后的数组: [11 22 33 44 55]



2.7 数组的重塑与转置

2.7.1 数组的重塑

数组的重塑是指重新将数组的形状变成指定的形状。重塑前后数组中元素的总数量是不变的。

方法

NumPy中提供了重塑数组的reshape()方法,该方法既可以改变具有相同维度的数组形状,又可以改变不同维度的数组形状。

  • 一维数组重塑为二维数组

    import numpy as np

    # 创建一个包含从 1 到 12 的整数序列(一维数组)
    array_1d = np.arange(1, 13)

    # 打印原数组的形状和内容
    print('原数组的形状:', array_1d.shape)
    print('原数组:', array_1d)
    # 输出: 原数组的形状: (12,)
    #      原数组: [ 1  2  3  4  5  6  7  8  9 10 11 12]

    # 将一维数组重塑为 6 行 2 列的二维数组
    array_2d = array_1d.reshape((6, 2))

    # 打印新数组的形状和内容
    print('新数组的形状:', array_2d.shape)
    print('新数组:\n', array_2d)
    # 输出:
    # 新数组的形状: (6, 2)
    # 新数组:
    # [[ 1  2]
    #  [ 3  4]
    #  [ 5  6]
    #  [ 7  8]
    #  [ 9 10]
    #  [11 12]]
  • 二维数组重塑为不同形状的二维数组

    在下例中,将二维数组形状从 (6, 2) 更改为 (3, 4)

    # 将二维数组 (6, 2) 重塑为 (3, 4)
    new_array_2d = array_2d.reshape((3, 4))

    # 打印新数组的形状和内容
    print('新数组的形状:', new_array_2d.shape)
    print('新数组:\n', new_array_2d)
    # 输出:
    # 新数组的形状: (3, 4)
    # 新数组:
    # [[ 1  2  3  4]
    #  [ 5  6  7  8]
    #  [ 9 10 11 12]]


2.7.2 数组的转置

数组的转置是指交换数组的行和列

方式一:通过T属性实现数组的转置操作

import numpy as np

# 创建一个 3x4 的二维数组
arr = np.arange(12).reshape(3, 4)
print('原数组:\n', arr)
# 输出:
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

# 使用 T 属性转置数组
transposed_arr = arr.T
print('转置后的数组:\n', transposed_arr)
# 输出:
# [[ 0  4  8]
#  [ 1  5  9]
#  [ 2  6 10]
#  [ 3  7 11]]


方式二:通过 transpose() 方法实现数组的转置操作

# 使用 transpose() 方法转置数组
transposed_arr = arr.transpose()
print('转置后的数组:\n', transposed_arr)
# 输出:
# [[ 0  4  8]
#  [ 1  5  9]
#  [ 2  6 10]
#  [ 3  7 11]]




2.8 数组的其他操作

2.8.1 条件逻辑

在 NumPy 中,where() 函数用于根据条件在数组中选择元素,相当于三元表达式 x if condition else y 的矢量化版本。

语法:

numpy.where(condition, x, y)
  • condition:布尔数组或条件表达式,表示哪些位置满足条件。

  • x:当条件为真时返回的元素。

  • y:当条件为假时返回的元素。

示例:

将低于 0 度的温度替换为 0 度:

import numpy as np

# 温度数据数组
temp = np.array([12, -5, 7, -3, 15, 0, -8, 10])

# 使用 where() 函数替换低于 0 度的温度
change_temp = np.where(temp < 0, 0, temp)

print("原始温度数据:", temp)
print("修正后的温度数据:", change_temp)

解释:

np.where(temp < 0, 0, temp) 根据条件 temp < 0 对数组 temp 中的元素进行替换:

  • 满足条件(温度小于 0)的元素替换为 0

  • 不满足条件的元素保持原值。

通过 where() 函数,可以轻松地对数组进行条件选择和修改。


2.8.2 统计运算

统计运算的方法

  • sum():对数组中全部或某个轴向的元素求和

  • mean():算术平均值

  • min():计算数组中的最小值

  • max():计算数组中的最大值

  • argmin():表示最小值的索引

  • argmax():表示最大值的索引

  • cumsum():所有元素的累计和

  • cumprod():所有元素的累计积


2.8.3 数组元素排序

在 NumPy 中,可以使用 sort() 方法对数组中的元素进行排序。

一维数组排序

import numpy as np

# 创建一个未排序的数组
arr = np.array([12, -5, 7, -3, 15, 0, -8, 10])

# 对数组进行排序
sorted_arr = np.sort(arr)

print("原始数组:", arr)
print("排序后的数组:", sorted_arr)
# 输出:
# 原始数组: [12 -5  7 -3 15  0 -8 10]
# 排序后的数组: [-8 -5 -3  0  7 10 12 15]

二维数组排序

import numpy as np

# 创建一个未排序的二维数组
arr = np.array([[12, -5, 7], [-3, 15, 0], [-8, 10, 2]])

# 按行排序
sorted_arr = np.sort(arr)

print("原始二维数组:\n", arr)
print("按行排序后的二维数组:\n", sorted_arr)
# 输出:
# 原始二维数组:
# [[12 -5  7]
#  [-3 15  0]
#  [-8 10  2]]
# 按行排序后的二维数组:
# [[-5  7 12]
#  [-3  0 15]
#  [-8  2 10]]

# 按列排序
sorted_arr_axis0 = np.sort(arr, axis=0)

print("按列排序后的二维数组:\n", sorted_arr_axis0)
# 输出:
# 按列排序后的二维数组:
# [[-8 -5  0]
#  [-3 10  2]
#  [12 15  7]]

sort() 方法默认按行排序。通过指定 axis=0 可以按列排序。


2.8.4 检索数组元素是否满足条件

在 NumPy 中,all()any() 函数非常有用,用于判断数组元素是否满足特定条件。

  • numpy.all():如果数组中的所有元素都满足条件,则返回 True,否则返回 False

  • numpy.any():如果数组中至少有一个元素满足条件,则返回 True,否则返回 False

import numpy as np

# 创建一个数组
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])

# 使用 all() 判断数组中的所有元素是否都大于0
all_greater_than_zero = np.all(arr > 0)
print("数组中的所有元素都大于0:", all_greater_than_zero)
# 运行结果:
# 数组中的所有元素都大于0: True

# 使用 all() 判断数组中的所有元素是否都小于5
all_less_than_five = np.all(arr < 5)
print("数组中的所有元素都小于5:", all_less_than_five)
# 运行结果:
# 数组中的所有元素都小于5: False

# 使用 any() 判断数组中是否有元素大于5
any_greater_than_five = np.any(arr > 5)
print("数组中是否有元素大于5:", any_greater_than_five)
# 运行结果:
# 数组中是否有元素大于5: True

# 使用 any() 判断数组中是否有元素小于0
any_less_than_zero = np.any(arr < 0)
print("数组中是否有元素小于0:", any_less_than_zero)
# 运行结果:
# 数组中是否有元素小于0: False


2.8.5 查找数组的唯一元素

NumPy针对一维数组提供了unique()函数,该函数用于找出数组中的唯一值,并返回一个升序排列的数组。

arr = np.array([12, 11, 34, 23, 12, 8, 11])
np.unique(arr)

# 运行结果【交互式环境会自动输出最后一个表达式的结果。】
# array([ 8, 11, 12, 23, 34])


2.8.6 判断元素是否在其他数组中

在NumPy中,in1d()函数用于判断一个一维数组中的元素是否存在于另一个数组中。该函数返回一个布尔数组,布尔数组的长度与第一个数组相等,每个元素对应第一个数组中的元素是否在第二个数组中。

in1d(ar1, ar2, assume_unique=False, invert=False)

参数说明

  1. ar1:待判断的数组。这个数组中的每个元素会在ar2数组中查找是否存在。

  2. ar2:判断ar1的每个元素是否存在的依据数组。ar1中的每个元素会在ar2中查找。

  3. assume_unique:是否假设数组ar1ar2中的元素是唯一的。如果设置为True,会加快计算速度,但如果数组中的元素不是唯一的,结果可能会不准确。默认值是False

  4. invert:是否对结果进行取反。如果设置为True,结果数组中的值将表示元素不在ar2中。默认值是False

示例

import numpy as np

ar1 = np.array([0, 1, 2, 5, 0])
ar2 = np.array([0, 2])

result = np.in1d(ar1, ar2)
print(result)

# 运行结果
# [ True False  True False  True]


2.9 线性代数模块

线性代数是数学运算中的一个重要工具,它在图形信号处理、音频信号处理中起非常重要的作用。numpy.linalg模块中有一组标准的矩阵分解运算以及诸如逆和行列式之类的方法,例如矩阵相乘,如果我们通过“*”对两个数组相乘的话,得到的是一个元素级的积,而不是一个矩阵点积。

NumPy中提供了一个用于矩阵乘法的dot()方法。


2.9.1 矩阵点积的条件

矩阵乘法是线性代数中的基本运算之一。要计算两个矩阵的乘积,需要满足特定的条件。。

矩阵点积的条件

对于两个矩阵 A 和 B:

  • 条件:矩阵 A 的列数必须等于矩阵 B的行数。

  • 假设:如果矩阵 A 是一个 m×p 的矩阵,而矩阵 B 是一个 p×n 的矩阵。

  • 结果:矩阵 A 和矩阵 B 的乘积 AB 将是一个 m×n 的矩阵。

arr_A = np.array([[1, 2, 3], [4, 5, 6]])
arr_B = np.array([[1, 2], [3, 4], [5, 6]])
result = arr_A.dot(arr_B)
print("矩阵点积结果:\n", result)

# 运行结果
[[22 28]
[49 64]]

image-20240611173553937

2.9.2 常见函数

  • diag():从一个数组中提取对角线元素或构造一个对角矩阵

  • trace():计算对角线元素和

  • det():计算矩阵的行列式

  • eig():计算矩阵的特征值和特征向量

  • inv():计算矩阵的逆

  • qr():计算矩阵的QR分解。QR分解将矩阵分解为一个正交矩阵Q和一个上三角矩阵R

  • svd():对矩阵进行奇异值(SVD)分解

  • solve():解线性方程组Ax=b,其中A是一个矩阵,b是一个向量

  • lstsq():计算Ax=b的最小二乘解



2.10 随机数模块

与Python的random模块相比,NumPy的random模块功能更多,它增加了一些可以高效生成多种概率分布样本值的函数。

2.10.1 生成随机数数组

import numpy as np
np.random.rand(3, 3)  # 生成3行3列、元素都是随机数的二维数组

# 运行结果
array([[0.41247257, 0.93596629, 0.99278386],
      [0.53655536, 0.95540039, 0.58288281],
      [0.99471366, 0.19286158, 0.57777512]])

np.random.rand(2, 3, 3)  # 生成2*3*3、元素都是随机数的三维数组
# 运行结果
array([[[0.21671284, 0.10763233, 0.84115674],
       [0.8666198 , 0.34562081, 0.34505571],
       [0.15378178, 0.69221458, 0.43818176]],

      [[0.72104517, 0.38006898, 0.38303114],
       [0.98217497, 0.44238914, 0.12194885],
       [0.09489134, 0.01523481, 0.97291143]]])


常见函数

函数说明
seed()生成随机数的种子
rand()产生均匀分布的样本值
randint()从给定的上下限范围内随机选取整数
normal()产生正态分布的样本值
beta()产生Beta分布的样本值
uniform()产生在[0,1]中的均匀分布的样本值


2.10.2 seed()函数

seed() 函数用于初始化随机数生成器,这样可以确保每次生成的随机数序列是可预测和可重复的。这在进行科学实验或需要相同结果的测试时非常有用。

为什么使用 seed()

  • 可重复性:在机器学习、数据分析和科学计算中,我们经常需要确保实验结果是可重复的。使用 seed() 可以让我们在每次运行代码时生成相同的随机数序列。

  • 调试:如果代码中的某些部分依赖于随机数,设置种子可以帮助我们进行调试,因为我们可以确保每次运行产生的随机数相同。

函数语法

np.random.seed(seed=None)
  • seed 参数:一个整数,用来初始化随机数生成器。使用相同的 seed 值将产生相同的随机数序列。


举例说明

示例 1:使用相同种子值

import numpy as np

# 第一次使用种子值 42
np.random.seed(42)
random_numbers_1 = np.random.rand(5)
print("第一次生成的随机数:", random_numbers_1)

# 第二次使用相同的种子值 42
np.random.seed(42)
random_numbers_2 = np.random.rand(5)
print("第二次生成的随机数:", random_numbers_2)

输出:

第一次生成的随机数: [0.37454012 0.95071431 0.73199394 0.59865848 0.15601864]
第二次生成的随机数: [0.37454012 0.95071431 0.73199394 0.59865848 0.15601864]

解释:

  • 当你设置相同的种子值(如 42),即使在不同的时间运行代码,生成的随机数序列也会完全相同。

  • np.random.rand(5) 生成了 5 个介于 0 和 1 之间的随机数。在这两次调用中,由于使用了相同的种子值,生成的随机数完全一致。


示例 2:不同种子值

import numpy as np

# 使用种子值 42 生成随机数
np.random.seed(42)
random_numbers_1 = np.random.rand(5)
print("使用种子 42 生成的随机数:", random_numbers_1)

# 使用不同的种子值 7 生成随机数
np.random.seed(7)
random_numbers_2 = np.random.rand(5)
print("使用种子 7 生成的随机数:", random_numbers_2)

输出:

使用种子 42 生成的随机数: [0.37454012 0.95071431 0.73199394 0.59865848 0.15601864]
使用种子 7 生成的随机数: [0.07630829 0.77991879 0.43840923 0.72346518 0.97798951]

解释:

  • 种子值 42:首次调用 np.random.seed(42) 初始化了随机数生成器,然后生成了 5 个随机数。这些随机数是固定的且可预测的。

    种子值 7:第二次调用 np.random.seed(7) 使用不同的种子重新初始化随机数生成器。由于种子不同,生成的随机数序列与上一个完全不同。


示例 3:无种子值

如果不设置种子值(或设置为 None),每次运行代码生成的随机数序列都会不同。

import numpy as np

# 第一次生成随机数,不设置种子值
np.random.seed(None)
random_numbers_1 = np.random.rand(5)
print("无种子值生成的随机数(第一次调用):", random_numbers_1)

# 第二次生成随机数,不设置种子值
np.random.seed(None)
random_numbers_2 = np.random.rand(5)
print("无种子值生成的随机数(第二次调用):", random_numbers_2)

输出:

无种子值生成的随机数(第一次调用): [0.76303814 0.5796431  0.2665043  0.54639207 0.60949325]
无种子值生成的随机数(第二次调用): [0.23220371 0.6177482  0.0478483  0.72928164 0.24352112]

解释:

  • 无种子值:当 seedNone 时,随机数生成器使用当前时间或其他随机因素作为种子,因此每次运行时随机数序列都会有所不同。

    不同的序列:因为没有固定的种子,生成器的起始状态不再是固定的,所以生成的序列不再一致,每次调用会产生不同的随机数


总结

  • 可重复性与随机性:使用 np.random.seed() 能确保生成的随机数序列是可重复的,这对于需要再现的实验和测试至关重要。

  • 相同种子值:相同的种子值会导致相同的随机数序列。

  • 无种子值:不设置种子值或设置为 None 会使得每次运行时产生不同的随机数序列,适合需要真正随机性和不可预测性的应用场合。



本章小结

本章主要针对科学计算库NumPy进行了介绍,包括NumPy数组、创建数组、数组的数据类型、数组的索引和切片、算术运算操作、通用函数、数组的重塑与转置、数组的其他操作,以及NumPy提供的线性代数模块和随机数模块。通过本章的学习,希望大家能熟练使用NumPy库,为后面章节的学习奠定扎实的基础。



综合实验

实验一:销售数据分析实验

实验目标

通过本次实验,学生将掌握如何使用NumPy进行销售数据的分析,包括数据的读取与创建、基本操作、统计分析、矩阵运算等内容。


1. 数据准备

下载销售数据: http://www.lx-blog.top/zb_users/upload/2024/09/sales_info.zip 

数据示例

2024-09-16_165133

image-20240916165623824

  1. Store_ID(商店编号): 标识不同的商店,当前数据中的值都是“1”,意味着所有销售记录都来自同一家商店。

  2. Sale_ID(销售编号): 售出商品的唯一标识,表示每次销售的具体交易编号,例如6、87、420等。

  3. Date(日期): 表示销售发生的日期,格式为“YYYY/MM/DD”。在此数据中,日期为2017年1月1日和2017年1月8日。

  4. Product_ID(产品编号): 商品的唯一编号,此处所有销售的商品编号为“29”。

  5. Units(数量): 每次销售的商品数量。该列显示的都是1,表示每次销售中售出一件商品。

  6. Product_Name(产品名称): 商品的名称,此数据中所有商品都是“Splash Balls”。

  7. Product_Category(产品类别): 商品的分类,这里所有商品属于“Sports & Outdoors(运动与户外)”类别。

  8. Product_Cost(产品成本): 该商品的成本价格,此商品的成本是9.82美元。

  9. Product_Price(产品售价): 商品的销售价格,此商品的售价是12.82美元。

整体来说,这张表格显2017年全年的商品的详细销售记录。


2. 读取与创建数据

import numpy as np

# 从本地 CSV 文件读取数据
data = np.genfromtxt('./sales_info.csv', delimiter=',',
                    dtype=str, encoding='utf-8', skip_header=1)

# 显示读取的数据
print(data)

解释:

i4:32位整数(4字节)。f8:64位浮点数(8字节)。

./orders.csv: CSV 文件的路径。

delimiter=',': 指定 CSV 文件中的分隔符是逗号。

dtype=str: 指定加载的数据类型为字符串。

names=True: 表示 CSV 文件的第一行包含列名。

encoding='utf-8': 指定文件的编码为 UTF-8。

skip_header=1:跳过文件的第一行


3. 数据的基本操作

3.1 数据的基本属性

# 数据的维度
print("数据的维度:", data.ndim)

# 数据的形状
print("数据的形状:", data.shape)

# 数据的数据类型
print("数据的数据类型:", data.dtype)

# 运行结果
# 数据的维度: 2
# 数据的形状: (284872, 9)
# 数据的数据类型: <U10            # <U10 表示每个数据元素是一个最多包含 10 个字符的字符串。


3.2 数据的切片

# 获取所有销售价格,即取第8列, 并转换为浮点数
order_amounts = data[:, 8].astype(float)
print("所有订单金额:", order_amounts)

运行结果报错:ValueError: could not convert string to float: ''

解释:不能把空字符串' '转换为浮点数

# 查看有多少空字符串''
print(np.sum(data[:, 8] == ''))

# 将数组中的空字符串替换为 np.nan,表示数值缺失
data[data == ''] = np.nan

【知识点】

  • np.nan 是 NumPy 中用于表示缺失值无效数值的特殊浮点数。它的全称是 "Not a Number"。很多 NumPy 和 Pandas 函数(如均值、总和)会自动忽略 np.nan,避免影响统计结果。

  • 布尔掩码是一个由布尔值(TrueFalse)组成的数组,用来标识哪些元素满足特定的条件。它可以用于筛选数组中的元素。


3.3 利用布尔索引查找匹配的数据

# 查看产品售价大于40的记录及数量

# 将数组中的空字符串替换为 np.nan,表示数值缺失
data[data == ''] = np.nan

# 提取第9列(订单金额列,索引为8),并转换为浮点数
Product_Price = data[:, 8].astype(float)  # 将这一列转换为浮点数

# 获取订单金额大于30的订单的布尔掩码
high_value_mask = Product_Price > 30

# 根据布尔掩码筛选出符合条件的订单
high_value_orders = data[high_value_mask]

# 显示筛选结果
print("订单金额大于30的订单:\n", high_value_orders)

# 显示筛选结果的前5行
print("订单金额大于30的订单(前5行):")
for row in high_value_orders[:5]:  # 只显示前5行
   print(row)

# 显示符合条件的记录数量
print("记录总数:", len(high_value_orders))



4. 条件筛选与排序

4.1 数组排序

# 打印排序前的订单金额
print("排序前的订单金额:",Product_Price)

# 对订单金额排序
sorted_amounts = np.sort(Product_Price)
print("排序后的订单金额:", sorted_amounts)


4.2 查找唯一元素

# 查找唯一的商品类别

# 1.提取 Product_Category 列(第7列,索引为6)
product_categories = data[:, 6]

# 2.查找唯一的商品类别
unique_categories = np.unique(product_categories)
print("唯一的商品类别:", unique_categories)


4.3 判断元素是否在其他数组中

# 判断product_categories(商品类别)'Games'和 'Toys'是否在数据中
result = np.in1d(['Games','Toys'], product_categories)
print("product_categories(商品类别)'Games'和 'Toys'是否在数据中:", result)

【知识点】

  • np.in1d 是 NumPy 提供的一个函数,用于测试一个数组中的每个元素是否在另一个数组中。这个函数返回一个布尔数组,其中每个元素表示第一个数组中的相应元素是否存在于第二个数组中。


5. 数据统计分析

5.1 订单金额的统计分析

import numpy as np

# 提取 Units(数量)列(第5列)和 Product_Price(商品售价)列(第9列,索引为8)
units = np.nan_to_num(data[:, 4].astype(float), nan=0.0)  # 使用 np.nan_to_num 替换 NaN
product_prices = np.nan_to_num(data[:, 8].astype(float), nan=0.0)  # 替换 NaN

# 计算每个订单的金额(Units * Product_Price)
order_amounts = units * product_prices

# 使用常规的统计函数,因为 NaN 已被处理
total_amount = np.sum(order_amounts)     # 总金额
average_amount = np.mean(order_amounts)  # 平均金额
max_amount = np.max(order_amounts)       # 最大金额
min_amount = np.min(order_amounts)       # 最小金额
std_amount = np.std(order_amounts)       # 标准差

# 打印统计结果,保留2位小数
print(f"订单总金额: {total_amount:.2f}")
print(f"订单平均金额: {average_amount:.2f}")
print(f"订单最大金额: {max_amount:.2f}")
print(f"订单最小金额: {min_amount:.2f}")
print(f"订单金额的标准差: {std_amount:.2f}")

【知识点1】

如果数组的数据中包含了缺失值或无效值(np.nan),这些值会导致数值运算结果变成 nan。为了解决这个问题,可以使用 np.nan_to_num() 函数来将 np.nan 替换为有效的数值(如 0),以便进行正确的计算。

解释

  1. np.nan_to_num():这个函数用于将数组中的 np.nan 值替换为指定的数值(在此例中,替换为 0)。

  2. nan=0.0:指定将 np.nan 替换为 0,这样在后续的矩阵乘法中,np.nan 不会影响结果。

  3. np.dot():仍然是使用矩阵乘法计算商品数量与成本价的乘积。

【知识点2】

标准差(Standard Deviation)是用来衡量一组数据的离散程度或者数据的波动性的统计指标。简单来说,它表示数据点距离平均值有多远。

直观理解

  • 小标准差:如果标准差很小,意味着大多数数据点都接近平均值,数据比较集中。

  • 大标准差:如果标准差很大,意味着数据点分布得比较分散,有些数据离平均值很远。

例子

假设我们有两个班级的考试成绩:

  • 班级 A 的成绩是:80, 81, 79, 82, 78

  • 班级 B 的成绩是:50, 100, 75, 60, 90

虽然这两个班级的平均分差不多,但班级 A 的成绩非常接近平均分,班级 B 的成绩则差异较大。班级 B 的成绩波动很大,因此标准差也会更大。

image-20240918191853599

标准差的公式就是:

  1. 找出每个数据和平均值的差距

  2. 把这些差距平方(为了消除负数)。

  3. 求这些平方的平均值

  4. 最后再开平方(还原成原来的单位)。

所以,标准差就是用来告诉我们数据和平均值的“平均差距”有多大。


5.2 统计各商品类别的总销售金额

import numpy as np

# 提取商品类别列(第7列,索引为6)
product_categories = data[:, 6]

# 提取 Units(数量)列(第5列)和 Product_Price(售价)列(第9列)
units = data[:, 4].astype(float)  # 转换为浮点数
product_prices = data[:, 8].astype(float)  # 转换为浮点数

# 计算每个订单的金额【保留2位小数】
order_amounts = np.round(units * product_prices, 2)

# 查找唯一的商品类别
unique_categories = np.unique(product_categories)

# 初始化一个字典来存储每个商品类别的总销售额
category_sales = {}

# 计算每个类别的总销售额
for category in unique_categories:
   # 生成一个布尔掩码数组,标识哪些订单的商品类别等于当前遍历的类别(category),为True的位置表示订单属于该类别。
   category_mask = (product_categories == category)

   # 计算该类别的总销售额
   total_sales = np.nansum(order_amounts[category_mask])  # 使用 np.nansum 忽略 NaN

   # 将结果存入字典
   category_sales[category] = total_sales

# 打印每个商品类别的总销售额,保留2位小数
for category, total in category_sales.items():
   print(f"商品类别: {category}, 总销售额: {total:.2f}")


6. 矩阵运算与线性代数

6.1 矩阵乘法

# 使用矩阵乘法统计总成本

# 提取商品数量(Units)列(第5列,索引为4),并将 NaN 值替换为 0
units = np.nan_to_num(data[:, 4].astype(float), nan=0.0)

# 提取商品成本(Product_Cost)列(第8列,索引为7),并将 NaN 值替换为 0
product_costs = np.nan_to_num(data[:, 7].astype(float), nan=0.0)

# 使用矩阵乘法计算总成本额
total_cost = np.dot(units, product_costs)

# 打印总成本额
print(f"总成本额: {total_cost:.2f}")


6.2 使用线性回归预测未来销售

我们已经有了2017年每个月的销售额,据此预测2018年1月的销售额。


1.计算数据的最小和最大日期

import pandas as pd
import numpy as np

# 提取 Date(日期)列(第3列,索引为2)
dates = data[:, 2]

# 使用 pandas.to_datetime 自动解析日期
dates = pd.to_datetime(dates, format='%Y/%m/%d')

# 打印日期范围的天数
print(f"最大日期: {dates.max()} 天")
print(f"最小日期: {dates.min()} 天")


2.计算各月的总销售额

import pandas as pd
import numpy as np

# 假设 data 是包含销售记录的二维数组,提取 Date(日期)列(第3列,索引为2)
dates = data[:, 2]

# 使用 pandas.to_datetime 将字符串形式的日期转换为 pandas 的 datetime 格式,以便进行时间相关的操作
dates = pd.to_datetime(dates, format='%Y/%m/%d')

# 提取 Units(数量)列(第5列,索引为4)和 Product_Price(商品售价,索引为8)
# 这些是你数据中的销售商品数量和商品的单价
units = data[:, 4].astype(float)  # 将数量列转换为浮点数类型
product_prices = data[:, 8].astype(float)  # 将价格列转换为浮点数类型

# 计算每个订单的销售额(数量 * 商品价格),得到每个销售订单的总金额
sales = units * product_prices

# 将日期和计算出的销售额组合成 pandas DataFrame,方便后续操作
df = pd.DataFrame({'Date': dates, 'Sales': sales})

# 按照每个月的最后一天 ('ME' - Month End) 对数据进行分组,并计算每个月的总销售额
# 'ME' 表示按每个月的最后一天进行重采样,并对该月的销售额求和
monthly_sales = df.resample('ME', on='Date').sum()

# 打印每个月的总销售额结果
print(monthly_sales)


3.预测2018年1月的销售额

根据上面的 monthly_sales 数据,我们可以使用最小二乘法计算斜率(b1)和截距(b0),然后基于这些计算结果预测2018年1月的销售额。我们已经有了2017年每个月的销售额,我们将这些月份的数字(1到12)作为 x 变量,销售额作为 y 变量来进行线性回归。

实现步骤

  1. 提取月份和销售额:将月份数值化(1月到12月)作为 x,对应的销售额作为 y

  2. 计算斜率和截距:使用最小二乘法公式计算。

  3. 预测未来销售额:使用公式 y = b0 + b1 * x 预测2018年1月的销售额。

代码实现:

import numpy as np

# 使用已经生成的 monthly_sales 数据
# 提取月份数(从1到12),因为我们有12个月的数据,将月份数字化为1, 2, ..., 12。
months = np.arange(1, len(monthly_sales) + 1)

# 提取销售额数据,将每个月的销售额从 pandas DataFrame 中提取出来并转换为 numpy 数组。
# monthly_sales['Sales'] 提取 "Sales" 列,.values 转换为 numpy 数组。
sales = monthly_sales['Sales'].values

# 计算斜率(b1)和截距(b0)
# 首先计算 months(x) 和 sales(y)的平均值
x_mean = np.mean(months)  # 计算月份的平均值
y_mean = np.mean(sales)   # 计算销售额的平均值

# 根据最小二乘法公式计算斜率 b1
# 斜率公式为:b1 = Σ((x - x_mean) * (y - y_mean)) / Σ((x - x_mean)^2)
b1 = np.sum((months - x_mean) * (sales - y_mean)) / np.sum((months - x_mean)**2)

# 根据公式计算截距 b0
# 截距公式为:b0 = y_mean - b1 * x_mean
b0 = y_mean - b1 * x_mean

# 打印计算出的斜率和截距
print(f"斜率 (b1): {b1}")  # 斜率表示每增加一个月,销售额的变化量。
print(f"截距 (b0): {b0}")  # 截距表示当月份为0时,预测的销售额(理论值)。

# 预测 2018 年 1 月的销售额,即第 13 个月
# 使用回归方程 y = b0 + b1 * x,其中 x 是第 13 个月。
future_month = 13  # 2018 年 1 月是第 13 个月
future_sales = b0 + b1 * future_month  # 根据公式计算预测的销售额

# 打印预测结果
print(f"预测 2018 年 1 月的销售额: {future_sales:.2f}")  # 保留2位小数,输出预测的销售额




最小二乘法学习

最小二乘法是一种简单且常用的线性回归方法,用于找到最佳拟合直线,使得直线与数据点之间的误差(残差)平方和最小化。最小二乘法的目标是找到一个线性方程的斜率(b1)和截距(b0),即:

image-20240916125708784

其中:

  • x 是自变量(如月份、天数等)。

  • y 是因变量(如销售额、价格等)。

  • b1 是斜率,表示 x 每增加 1 单位时,y 的变化量。

  • b0 是截距,表示当 x = 0 时,预测的 y 值。


最小二乘法公式推导

为了找到最佳拟合的斜率(b1)和截距(b0),我们根据最小二乘法公式进行计算。

1. 斜率(b1)公式

image-20240916125906968

该公式的含义是:

image-20240916130030318

总结

  • 斜率 b1b_1b1:描述的是 xy 之间的线性关系。它表示每增加 1 单位的 xy 会相应增加或减少多少。

简单来说,斜率公式通过计算每个数据点与平均值的偏差,来衡量数据的总体趋势。如果斜率为正,说明 x 增加时,y 也增加;如果斜率为负,说明 x 增加时,y 减少。


2. 截距(b0)公式

image-20240916130445305

image-20240916130508290


使用最小二乘法预测销售额

假设我们有一年的月度销售额数据,并且希望通过这些数据预测下一年的销售额。我们可以使用最小二乘法来计算斜率和截距,从而找到月数与销售额之间的线性关系。

假设我们有 12 个月的数据:

  • 自变量 x:月份数,x = [1, 2, 3, ..., 12]

  • 因变量 y:每个月的销售额,y = [358485.07, 351265.98, ..., 610100.45]


步骤

  1. 计算 xy 的平均值

image-20240916125202810

  1. 计算斜率 b1

image-20240916125217018

这是通过 xy 的偏差进行斜率计算的公式。

  1. 计算截距 b0

image-20240916125236605

截距表示当 x = 0 时的预测销售额(理论值)。

  1. 预测未来销售额

例如,预测 2018 年 1 月(第 13 个月)的销售额:

image-20240916125331576


完整代码示例

import numpy as np

# 自变量 x 表示月份数
months = np.arange(1, 13)

# 因变量 y 表示每个月的销售额
sales = np.array([358485.07, 351265.98, 400368.70, 464927.53, 459589.16, 449196.44,
                 393006.03, 330589.69, 378422.00, 427216.51, 450234.36, 610100.45])

# 计算 x 和 y 的平均值
x_mean = np.mean(months)
y_mean = np.mean(sales)

# 使用最小二乘法公式计算斜率 b1
b1 = np.sum((months - x_mean) * (sales - y_mean)) / np.sum((months - x_mean)**2)

# 计算截距 b0
b0 = y_mean - b1 * x_mean

# 打印斜率和截距
print(f"斜率 (b1): {b1}")
print(f"截距 (b0): {b0}")

# 预测第 13 个月(即 2018 年 1 月)的销售额
future_month = 13
future_sales = b0 + b1 * future_month

# 打印预测结果
print(f"预测 2018 年 1 月的销售额: {future_sales:.2f}")

总结

  • 斜率 b1:表示每个月增加时,销售额的变化速度。

  • 截距 b0:表示当月份为 0 时,理论上预测的销售额。

  • 最小二乘法:通过最小化所有数据点与拟合直线之间的残差平方和,找到一条最佳拟合直线。

通过最小二乘法公式,你可以预测未来数据的趋势。这个方法适用于线性关系的场景。如果数据趋势不是线性,可能需要使用其他模型。



实验二:综合案例分析

通过分析学生成绩数据集,逐步学习NumPy的基本概念、数组操作、统计分析、数据清洗与处理、线性代数操作以及高级数组操作。

数据文件 (scores.txt)

学生,数学,语文,英语
张刚,85,78,92
李卫,76,85,80
王红,90,88,94
赵建,65,70,75
陈军,76,85,102
胡君,85,,92
孙国,,70,75

一、数据导入与基本数组操作

目标: 了解如何使用NumPy导入数据,并进行基本的数组操作。

步骤:

  1. 导入NumPy和pandas

    import numpy as np
    import pandas as pd
  2. 导入数据:使用pandas从文本文件中导入数据。

    # 使用pandas读取数据
    data = pd.read_csv('scores.txt')


    print("填充缺失值前的数据表:\n", data)

    # 填充缺失值为科目平均分,并将平均值保留一位小数
    data.fillna(data.mean(numeric_only=True).round(1), inplace=True)

    print("填充缺失值后的数据表:\n", data)
  3. 数据预览:在转换为NumPy数组之前,预览pandas DataFrame。

    # 预览数据
    print("预览数据:\n", data.head())
  4. 转换为numpy数组

    # 转换为numpy数组
    scores_array = data.values

    print("转换为数组后的数据:\n", scores_array)


二、基本数组操作

目标: 学习如何进行基本的数组运算和形状修改。

步骤:

  1. 数组运算:对学生成绩进行基本运算(加法、减法、乘法、除法)。

    # 加10分奖励
    bonus_scores = scores_array + 10
    print("加10分奖励后的成绩:\n", bonus_scores)
  2. 形状检查:检查原数组的形状。

    # 检查数组形状
    print("原数组形状:", scores_array.shape)


三、统计分析

目标: 学习如何计算数组的基本统计量。

步骤:

  1. 数据完整性检查:在计算统计量前,检查数据的完整性。

    # 只选择第2至第4列 (注意索引从0开始,因此选择列1到3)
    subset = scores_array[:, 1:4]

    # 先确保数据是浮点型
    scores_array = subset.astype(np.float64)

    # 检查 NaN 值
    print("数据中的 NaN 值:\n", np.isnan(scores_array).sum())
  2. 计算基本统计量:计算每个学生的总成绩和平均成绩。

    # 计算总成绩
    total_scores = np.nansum(scores_array, axis=1)
    print("每个学生的总成绩:", total_scores)

    # 计算平均成绩
    average_scores = np.nanmean(scores_array, axis=1)
    print("每个学生的平均成绩:", average_scores)
  3. 计算班级统计量:计算每门课程的平均成绩、最大值和最小值。

    # 计算每门课程的平均成绩
    course_avg = np.nanmean(scores_array, axis=0)
    print("每门课程的平均成绩:", course_avg)

    # 计算每门课程的最高分和最低分
    course_max = np.nanmax(scores_array, axis=0)
    course_min = np.nanmin(scores_array, axis=0)
      print("每门课程的最高分:", course_max)
      print("每门课程的最低分:", course_min)


四、数据清洗与处理

  1. 重复值检查:检查数据中的重复记录。

    # 检查重复数据
    print("重复记录:\n", data.duplicated().sum())
  2. 处理异常值:假设存在异常值,进行处理。

    # 定义异常值标准
    print("异常值标准:任何科目分数超过100分")

    # 假设超过100的值为异常值,进行处理
    scores_array[scores_array[:, 1:] > 100] = np.nan
    print("处理异常值后的成绩数据:\n", scores_array)


五、线性代数操作

目标: 学习如何使用NumPy进行加权总成绩和平均成绩的计算。

步骤:

  1. 创建科目权重数组:添加一个包含不同科目对应百分比的一维数组。

    # 创建包含不同科目对应百分比的一维数组
    weights = np.array([0.3, 0.3, 0.4])  # 数学、语文、英语分别占比30%、30%、40%
    print("科目权重:", weights)
  2. 权重解释:解释权重设置的逻辑和背景。

    # 解释权重设置
    print("权重设置依据:根据学科重要性和教学大纲设定")
  3. 计算加权总成绩和平均成绩:使用权重数组计算加权总成绩和平均成绩。

    # 去掉学生姓名列,只保留成绩数据
    scores_without_names = scores_array[:, 1:]

    # 计算加权总成绩
    weighted_scores = np.dot(scores_without_names, weights)
    print("加权总成绩:", weighted_scores)

    # 计算加权平均成绩
    total_weight = np.sum(weights)
    average_weighted_scores = weighted_scores / total_weight
    print("加权平均成绩:", average_weighted_scores)


六、高级数组操作

目标: 掌握NumPy中的高级数组操作,如广播机制、花式索引和条件索引。

步骤:

  1. 广播机制解释:解释广播机制的原理和用途。

    # 广播机制解释
    print("广播机制允许不同形状的数组进行算数运算,如将一维数组与二维数组相加")
  2. 广播机制:对不同科目加上不同的加分项。

    # 创建加分项数组,表示数学、语文、英语分别加5分、10分和15分
    bonus_points = np.array([5, 10, 15])

    # 广播机制运算,对不同科目加上不同的加分项
    broadcast_result = scores_without_names + bonus_points
    print("广播机制运算结果:\n", broadcast_result)
  3. 花式索引:使用整数数组和布尔数组进行索引。

    # 使用整数数组进行索引
    indices = [0, 2]
    fancy_indexed_array = scores_without_names[indices]
    print("花式索引结果:\n", fancy_indexed_array)

    # 使用布尔数组进行索引
    bool_indices = [True, False, True, False, False, False, False]
    boolean_indexed_array = scores_without_names[bool_indices]
    print("布尔索引结果:\n", boolean_indexed_array)
  4. 条件索引:使用条件表达式对数组进行筛选和赋值操作。

    # 条件筛选
    condition = scores_without_names > 80
    filtered_scores = scores_without_names[condition]
    print("条件筛选结果:", filtered_scores)

    # 条件赋值
    scores_without_names[scores_without_names > 80] = 80
    print("条件赋值后的成绩数据:\n", scores_without_names)


实验总结

通过以上实验,我们逐步学习了NumPy的基本概念和各种操作。从数据导入与预处理开始,到基本数组操作、统计分析、数据清洗与处理,再到线性代数操作和高级数组操作,每一步都帮助我们更深入地理解和掌握NumPy的功能和应用。


实验三:

项目描述

分析一个包含学生成绩的数据集,计算每个学生的总成绩、平均成绩以及各科目的最高和最低分数。

数据集示例

假设数据集 scores.csv 的内容如下:

Name,Math,English,Python
Alice,85,92,78
Bob,76,85,88
Charlie,90,87,91
David,65,70,75
Eve,95,100,85

实验内容

import numpy as np

# 使用 numpy 的 genfromtxt 函数读取 CSV 文件,并确保数据是字符串类型
data = np.genfromtxt('scores.csv', delimiter=',', dtype=str, encoding='utf-8', skip_header=1)

print(data.shape)

# 提取学生姓名和成绩
names = data[:, 0]
scores = data[:, 1:].astype(float)

print("学生姓名:\n", names)
print("成绩:\n", scores)

# 计算每个学生的总成绩
total_scores = np.sum(scores, axis=1)

# 计算每个学生的平均成绩
average_scores = np.mean(scores, axis=1)

print("总成绩:\n", total_scores)
print("平均成绩:\n", average_scores)

# 计算各科目的最高分
highest_scores = np.max(scores, axis=0)

# 计算各科目的最低分
lowest_scores = np.min(scores, axis=0)

print("各科目最高分:\n", highest_scores)
print("各科目最低分:\n", lowest_scores)

# 输出每个学生的总成绩和平均成绩
for i in range(len(names)):
   print(f"{names[i]}的总成绩: {total_scores[i]}, 平均成绩: {average_scores[i]}")

# 输出各科目的最高分和最低分
subjects = ['Math', 'English', 'Python']
for i in range(len(subjects)):
   print(f"{subjects[i]}的最高分: {highest_scores[i]}, 最低分: {lowest_scores[i]}")


发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

Powered By Z-BlogPHP 1.7.3

版权:李翔
备案/许可证编号为:新ICP备2024006115号-1