【Python 零基础入门】第六课 Numpy

  • 概述
  • 什么是 Numpy?
    • Numpy 与 Python 数组的区别
    • Numpy 在数据科学中的重要性
  • Numpy 底层区别
    • 并发 vs 并行
    • 单线程 vs 多线程
    • GIL
    • 内存存储
    • ndarray 如何存储数据
    • 图解区别
  • Numpy 安装
    • Anaconda
    • 导包
  • ndarray
    • np.array 创建
    • 数组属性
    • np.zeros 创建
    • np.ones 创建
  • 数组的切片和索引
    • 基本索引
    • 切片操作
    • 数组运算
  • 常用函数
    • reshape
    • flatten
    • 聚合函数
  • Numpy 的高级功能
    • 广播
    • 矩阵计算
  • Numpy 实际应用
    • 统计分析
    • 图像处理
    • 解方程
  • 结论
  • 练习
    • 练习1
    • 练习2
    • 练习3
  • 参考答案
    • 练习1
    • 练习2
    • 练习3

概述

在众多 Python 的数据处理库中, Numpy 是一个非常强大的存在. Numpy 为我们提供了高性能的多维数组, 以及这些数组对象上的各种操作. 但是, 作为一个刚入门 Python 的新手, 你可能会问: “为什么我需要 Numpy, 而不是直接使用Python 的内置列表?”在这篇文章的开篇, 我们就来探讨这个问题.

什么是 Numpy” />Numpy 与 Python 数组的区别

虽然 Python 的内置列表很灵活, 能存储任意类型的数据. 但当我们需要进行大量的数值运算时 (线性代数, 统计), Python 的内置列表效率并不高. Numpy 数组相比之下, 是在连续的内存块上存储的, 这使得访问速度更快, 效率更高. 而且 Numpy 是用 C 语言编写的, 其内部迭代计算比 Python 的内置循环要快很多.

例子:

Numpy 在数据科学中的重要性

在现代数据科学领域, 数据处理, 清晰, 统计分析, 特征工厂, 机器学习等各个领域都离不开数值计算. Numpy 为我们提供了一套完整, 高效的工具, 使得我们的任务变得简单. 几乎所有的 Python 数据处理库, 如 Pandas, Scipy 等, 都是基于 Numpy 构建的. 所以我们非常有必要要熟悉掌握 Numpy 库.

Numpy 底层区别

并发 vs 并行

并发 vs 并行

  • 并发 (Concurrency): 是指系统能够处理多个任务在同一时间段内交替执行, 但不一定同时
  • 并行 (Parallelism): 并行是指多个任务或多个数据在同一时刻被执行


举个例子:

  • 并发: 类似一个单线程的服务器, 可以在短时间内处理多个请求, 但是一次只能处理一个请求. 当等待一个请求数据时, 可以切换到另一个请求
  • 并行: 想象成一个多线程的计算任务, 每个线程在多核 CPU 不同核心上同时执行

举个生活中的例子:

小白吃饭吃到一半, 电话来了, 我一直到吃完了以后才去接, 这就说明你不支持并发也不支持并行.小白吃饭吃到一半, 电话来了, 你停了下来接了电话, 接完后继续吃饭, 这说明你支持并发.小白吃饭吃到一半, 电话来了, 你一边听电话一边吃饭, 这说明你支持并行.

应用:

  • 并发: 进行任务之间的协调 & 同步, 难点在有效地处理资源争用 & 死锁
  • 并行: 同时进行多个任务, 难点在于负载均衡和通信开销

单线程 vs 多线程

单线程 vs 多线程:

  • 单线程: 在同一时间处理一个任务
  • 多线程: 在同一时间处理多个任务

GIL

GIL (Global Interpreter Lock) 全局解释器, 来源是 Python设计之初的考虑, 为了数据安全所做的决定.

每个 CPU 在同一时间只能执行一个线程 (在单核 CPU 下的多线程其实都只是并发, 不是并行, 并发和并行从宏观上来讲都是同时处理多路请求的概念. 但并发和并行又有区别, 并行是指两个或者多个事件在同一时刻发生, 而并发是指两个或多个事件在同一时间间隔内发生.

内存存储

Python 内置列表:

  • Python 内置列表是一个东岱数组, 容纳不同类型的元素. 每个远古三都是一个 Python 对象, 包括指针, 类型, 信息, 引用计数等等. 所以 Python 内置的列表内存开销较大, 而且元素在内存中可能是分散的

Numpy ndarray:

  • ndarray 是一个多维数组, 通常包含同类型的元素. ndarray 在内存中是连续的, 所以可以被 CPU 更高效的访问. 而且, 由于蒜素都是相同的数据类型, 所以 ndarray 不需要为每个元素存储额外的类型信息

例子:

我们可以看到存储相同数据的情况下, python 内置列表使用了超过 ndarray 2 倍的内存.

ndarray 如何存储数据

进一步说明, 我们来看一下 ndarray 源代码:

/* * The main array object structure.*//* This struct will be moved to a private header in a future release */typedef struct tagPyArrayObject_fields {PyObject_HEAD/* Pointer to the raw data buffer */char *data;/* The number of dimensions, also called 'ndim' */int nd;/* The size in each dimension, also called 'shape' */npy_intp *dimensions;/* * Number of bytes to jump to get to the * next element in each dimension*/npy_intp *strides;PyObject *base;/* Pointer to type structure */PyArray_Descr *descr;/* Flags describing array -- see below */int flags;/* For weak references */PyObject *weakreflist;} PyArrayObject_fields;

在上述实验中, 我们发现 10,000,000 个元素的列表 Numpy 占用的内存是 40,000,096 字节, 这是因为我们存储的元素为 int32 类型, 也就是 4 个字节, 加上Numpy 数组存储的一些指针, 维度, PyObject_HEAD, 为 96 字节.

对比 int32 数组和 int64 数组:

  • int32 占用 32 bit (4 byte) 4 字节, 4*100 + 96 = 496
  • int64 占用 64 bit (8 byte) 8 字节, 8*100 + 96 = 896

图解区别

Numpy 安装

安装命令:

pip install numpypip3 install numpy

Anaconda

Anaconda 是一个计算科学库, 可以为我们提供便利的 Python 环境.

安装:
Anaconda 官网

导包

导入 Numpy 包:

# 导包import numpy as npprint(np.__version__)

ndarray

ndarray 是 Numpy 最重要的一个特点. ndarray 是一个 N 维数组对象.

np.array 创建

np.array可以帮助我们创建一 ndarray.

格式:

numpy.array(object, dtype=None, *, copy=True, order='K', subok=False, ndmin=0, like=None)

参数:

  • object: 类数组
  • dtype: 数据类型, 可选

例子:

# 导包import numpy as np# 创建ndarrayarray1 = np.array([1, 2, 3])# 通过lsit创建array2 = np.array([1, 2, 3], dtype=float)# 调试输出print(array1, type(array1))print(array2, type(array2))

输出结果:

[1 2 3] [1. 2. 3.] 

数组属性

创建 Numpy 数组后, 我们可以进一步查询 ndarray 的属性, 如形状, 维度, 数据类型等:

  • shape: 返回数组的形状
  • dtype: 返回数组中元素的数据类型
  • ndim: 返回数组的维度
  • size: 返回数组的元素总数

例子:

"""@Module Name: Numpy 数组属性.py@Author: CSDN@我是小白呀@Date: October 13, 2023Description:Numpy 数组属性"""import numpy as np# 创建 ndarrayarr = np.array([[1, 2, 3], [4, 5, 6]])print(arr)# 输出数组属性print(arr.shape)# 输出 [2, 3] (两行, 三列)print(arr.dtype)# 输出 int32 (整型)print(arr.ndim)# 输出 2 (二维数组)print(arr.size)# 输出 6 (2*3, 6个元素)

np.zeros 创建

np.zeros可以帮助我们创建指定形状的全 0 数组.

格式:

numpy.zeros(shape, dtype=float, order='C', *, like=None)

参数:

  • shape: 数组形状
  • detype: 默认为 float, 浮点型

例子:

import numpy as np# 创建全0的ndarrayarray = np.zeros((3, 3), dtype=int)print(array)

输出结果:

[[0 0 0] [0 0 0] [0 0 0]]

np.ones 创建

np.zeros可以帮助我们创建指定形状的全 1 数组.

格式:

numpy.ones(shape, dtype=float, order='C', *, like=None)

参数:

  • shape: 数组形状
  • detype: 默认为 float, 浮点型

例子:

import numpy as np# 创建全1的ndarrayarray = np.ones((3, 3), dtype=int)print(array)print(type(array))

输出结果:

[[1 1 1] [1 1 1] [1 1 1]]

数组的切片和索引

Numpy 数组支持 Python 的索引和切片操作, 并提供了更为丰富的功能.

格式 1:

数组[起始索引:结束索引]
  • 起始索引: 取的到
  • 结束索引: 取不到

格式 2:

数组[起始索引:结束索引:间隔]
  • 起始索引: 取的到
  • 结束索引: 取不到
  • 间隔: 间隔几个数

基本索引

import numpy as np# 创建 ndarrayarr = np.array([1, 2 ,3 ,4 ,5])# 切片, 取索引 0 对应的元素print("输出第一个元素:", arr[0])

输出结果:

输出第一个元素: 1

切片操作

例子:

import numpy as np# 创建 ndarrayarr = np.array([1, 2 ,3 ,4 ,5])# 切片数组前三个元素print("前三个素:", arr[:3])# 切片数组 2-3print("2-3 元素:", arr[1:3])# 切片最后一个元素print("最后一个元素:", arr[-1])# 切片奇数索引print("奇数元素:", arr[::2])# 切片反转print("反转数组:", arr[::-1])

输出结果:

前三个素: [1 2 3]2-3 元素: [2 3]最后一个元素: 5奇数元素: [1 3 5]反转数组: [5 4 3 2 1]

数组运算

与 Python 的内置列表不同, Numpy 数组支持元素级别的运算. 我们可以对 ndarray 进行加, 减, 乘, 除等操作.

例子:

常用函数

reshape

通过reshape()我们可以改变数组形状.

格式:

numpy.reshape(arr, newshape, order='C')

参数:

  • arr: 需要改变形状的数组
  • newshape: 新的形状

例子:

import numpy as np# 创建ndarrayarray = np.zeros(9)print(array)# reshapearray = array.reshape((3,3))print(array)print(array.shape)# 调试输出数组形状

输出结果:

[0. 0. 0. 0. 0. 0. 0. 0. 0.][[0. 0. 0.] [0. 0. 0.] [0. 0. 0.]](3, 3) 

flatten

通过flatten()我们可以将多维数组摊平成1 维数组.

例子:

import numpy as np# 创建多维数组array = np.zeros((3, 3))print(array)# flatten转变为一维数组array = array.flatten()print(array)

输出结果:

[[0. 0. 0.] [0. 0. 0.] [0. 0. 0.]][0. 0. 0. 0. 0. 0. 0. 0. 0.]

聚合函数

常见的聚合函数:

  • np.sum(): 求和
  • np.min(): 求最小值
  • np.max(): 求最大值
  • np.mean(): 计算平均值
  • np.median(): 计算中位数

例子:

import numpy as np# 创建 ndarrayarr = np.array([1, 2, 3, 4, 5])# 调用常用聚合函数print(np.sum(arr))print(np.min(arr))print(np.max(arr))print(np.mean(arr))print(np.median(arr))

输出结果:

15153.03.0

Numpy 的高级功能

下面我们来讲一下 Numpy 的高级功能. Numpy 的高级功能可以帮助我们有效的处理数据, 进行科学计算, 以便帮我们更好地处理数据.

广播

广播 (Broadcasting) 是 Numpy 的一个强大功能, 可以帮助我们进行不同形状数组的的运算. Numpy 中广播的规则是从尾部的维度开始对比.

例子:

import numpy as np# 广播a = np.array([1, 2, 3])b = np.array([[10], [20], [30]])print(a + b)

输出结果:

[[11 12 13] [21 22 23] [31 32 33]]

矩阵计算

例子:

import numpy as np# 定义矩阵mat1 = np.array([[1, 2], [3, 4]])mat2 = np.array([[2, 0], [1, 3]])# 矩阵乘法# 1*2 + 2*1 = 2 # 1*1 + 2*3 = 6# 3*2 + 4*1 = 10# 3*0 + 4*3 = 12result = np.dot(mat1, mat2) print(result)

输出结果:

[[ 46] [10 12]]

Numpy 实际应用

当我们已经掌握了 Numpy 的基础用法和高级功能后, 小白我来带大家了解一下 Numpy 的实际应用.

统计分析

求数组平均数和标准差:

import numpy as np# 定义数组data = np.array([23, 45, 56, 78, 12, 9])# 计算平均值和标准差print("平均值:", np.mean(data))print("标准差:", np.std(data))

输出结果:

3.14

图像处理

利用 Numpy, 我们可以将图像转化为数组进行处理.

例子:

import numpy as npfrom PIL import Image# 将图像转化为数据image = Image.open('path_to_image.jpg')image_array = np.array(image)print(image_array.shape)

输出结果:

(1707, 2560, 3)

解方程

例子:

import numpy as npfrom numpy.linalg import solve# 创建 ndarraya = np.array([[3, 1], [1, 2]])# 3x + y = 9 b = np.array([9, 8])# x + 2y = 8# 解方程x = solve(a, b)# x = 2, y = 3print(x)

输出结果:

[2. 3.]

结论

在本篇文章中, 我们深入地探讨了 Numpy, 这是 Python 中用于数值计算和数据分析的核心库. 从数组的基本操作, 数组的形状和维度, 高级数组操作, 到 Numpy 的最佳实践和常见误区, 我们尝试为读者提供了一个全面且深入的视角.

Numpy 的真正威力在于其高效性和灵活性. 它为我们提供了大量的功能, 能帮助我们轻松处理大规模的数值数据. 但与此同时, 也需要注意其特定的工作原理, 避免常见的陷阱.

对于初学者来说, 可能需要一些时间来适应 Numpy 的思维方式, 特别是它的广播机制和向量化操作. 但一旦你习惯了这种方式, 你会发现自己的数据处理能力大大增强.

无论你是数据分析师, 科学家还是工程师, 掌握 Numpy 都将是你数据处理技能的重要组成部分. 希望这篇文章能为你在 Python 数据处理之路上提供一些有用的指导.

练习

练习1

数组创建与基础操作:

  • 创建一个形状为 (5, 5) 的数组,其中所有元素都为整数1。
  • 创建一个长度为 20 的一维随机整数数组,范围在 1 到 100 之间。
  • 将上述一维数组重新塑形为 (5, 4) 的二维数组。

练习2

数组索引与切片:

  • 创建一个形状为 (10, 10) 的随机整数数组,范围在 1 到 100 之间。提取出其中的第 3 到 8 行,第 4 到 9 列的子数组。
  • 从上述数组中,提取出所有的偶数元素。

练习3

数组操作与数学运算:

  • 创建两个形状为 (3, 3) 的随机整数数组 A 和 B,范围在 1 到 10 之间。计算 A 与 B 的点积。
  • 计算上述数组 A 的逆矩阵(如果存在)。

参考答案

练习1

import numpy as nparray = np.ones([5,5], dtype=int)print(array)array = np.random.randint(1, 101, size=20)print(array)array = array.reshape((5, 4))print(array)

输出结果:

[[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]][22 13 20 675 91 26 64 84 85 59 66 44 83 41 63 44 23 76 35][[22 13 20 67] [ 5 91 26 64] [84 85 59 66] [44 83 41 63] [44 23 76 35]]

练习2

import numpy as nparray = np.random.randint(1, 101, size=(10, 10)).reshape((10,10))print(array)array = array[2:8, 3:9]print(array)array = array[array % 2 == 0]print(array)

输出结果:

[[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]][ 32 69148638187281925209397 1007077 346 100 7][[ 32 69148] [ 63818728] [ 19252093] [ 97 1007077] [346 100 7]][[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]][71 636 50 59 69 14 18 80 88 68 54 35 97 51 82 86 50 619][[71 636 50] [59 69 14 18] [80 88 68 54] [35 97 51 82] [86 50 619]]

练习3

import numpy as npa = np.random.randint(1, 11, size=(3, 3))b = np.random.randint(1, 11, size=(3, 3))print(a)print(b)result = np.dot(a, b)print(result)det_a = np.linalg.det(a)if det_a == 0:print("矩阵 A 不可逆")else:inverse_a = np.linalg.inv(a)print("A 的逆矩阵为: \n", inverse_a)

输出结果:

[[ 864] [1055] [ 779]][[ 729] [1096] [ 571]][[13698 112] [145 100 125] [164 140 114]]A 的逆矩阵为:[[-9.09090909e-022.36363636e-01 -9.09090909e-02] [ 5.00000000e-01 -4.00000000e-01 -7.93016446e-18] [-3.18181818e-011.27272727e-011.81818182e-01]]