实例1
%time 列表推导式 range
In [7]: import numpy as np
In [8]: my_arr = np.arange(1000000)
In [9]: my_list = list(range(1000000))
In [10]: %time for _ in range(10): my_arr2 = my_arr * 2
CPU times: user 20 ms, sys: 50 ms, total: 70 ms
Wall time: 72.4 ms
In [11]: %time for _ in range(10): my_list2 = [x * 2 for x in my_list]
CPU times: user 760 ms, sys: 290 ms, total: 1.05 s
Wall time: 1.05 s
CPU times: user 20 ms, sys: 50 ms, total: 70 ms
:user
:CPU 执行 Python 代码(如将my_arr
乘以 2)所花费的时间。这里是 20 毫秒。sys
:CPU 花费在系统级操作(如内存管理、输入输出等)上的时间。这里是 50 毫秒。total
:CPU 花费的总时间(用户时间 + 系统时间)。总计 70 毫秒。Wall time: 72.4 ms
:- Wall time 表示实际经过的时间,也就是你用秒表测量到的时间,它包括所有其他开销(如等待资源、CPU 空闲时间等)。这里是 72.4 毫秒,表示整个操作从开始到完成的总时间。
my_arr
和内置my_list
主要区别:
- NumPy 数组 (
my_arr
) vs Python 列表 (my_list
) 的效率:
-
- NumPy 数组 对于数值计算进行了高度优化,
my_arr * 2
操作是 向量化 的,这意味着它通过底层的 C 代码实现操作,因此可以非常快速地完成。 - Python 列表 在进行类似的元素级操作时效率较低,因为列表操作需要在 Python 中逐个元素地进行循环,这种方式的性能较差。
- NumPy 数组 对于数值计算进行了高度优化,
my_list2 = [x * 2 for x in my_list]
这种写法是 列表推导式(List Comprehension)的一种形式,它是 Python 中用来创建新列表的一种简洁且高效的方法。
列表推导式
my_list2 = [x * 2 for x in my_list]
这段代码的含义是:
- 对
my_list
中的每个元素x
执行操作x * 2
,然后将结果放入一个新的列表my_list2
中。 - 列表推导式 是一种迭代遍历列表、过滤元素并在一个表达式中生成新列表的方式。其基本结构是:
new_list = [expression for item in iterable]
其中:
-
iterable
是你要遍历的可迭代对象(如列表、元组、字典等)。expression
是你对每个元素执行的操作。
传统for循环
my_list2 = []
for x in my_list:
my_list2.append(x * 2)
扩展条件过滤
my_list2 = [x * 2 for x in my_list if x > 2]
- 列表推导式 是 Python 中用于创建新列表的简洁方式,特别适合进行简单的转换和过滤操作。
%time 在正常python代码里面能用吗
%time
是 IPython 和 Jupyter Notebook 中的魔法命令(magic command),它是专门为这些交互式环境设计的,不能直接在标准的 Python 脚本或命令行中使用。
主要特点:
- IPython(包括 Jupyter Notebook)提供了许多魔法命令,这些命令通常以
%
或%%
开头,用来简化或扩展某些功能。例如,%time
用于测量代码的执行时间。 - 这些命令是在 IPython 环境中有效的,而在常规 Python 脚本中(例如
.py
文件或普通 Python 解释器)无法直接使用。
例子:
在 Jupyter Notebook 或 IPython 中,你可以直接使用:
%time my_arr2 = my_arr * 2
这会返回代码执行的时间。
但如果你在普通的 Python 脚本或命令行中运行这行代码,Python 会报错:
SyntaxError: invalid syntax
在普通 Python 环境中如何做性能测试:
如果你在标准的 Python 环境中想要测量代码执行时间,可以使用 time
模块或 timeit
模块。
使用 time
模块:
import time
start_time = time.time() # 记录开始时间
my_arr2 = my_arr * 2
end_time = time.time() # 记录结束时间
print(f"Execution time: {end_time - start_time} seconds")
使用 timeit
模块:
timeit
是一个专门用于性能测试的模块,它可以帮助你多次运行代码并返回平均时间,这对于精确测试非常有用。
import timeit
# 测试单行代码的执行时间
execution_time = timeit.timeit("my_arr * 2", setup="my_arr = [1, 2, 3, 4]", number=1000)
print(f"Execution time: {execution_time} seconds")
总结:
%time
只能在 IPython 或 Jupyter Notebook 中使用。- 在 标准 Python 环境 中,使用
time
或timeit
模块来进行性能测试。
实例2 ndarray
ndarray是一个通用的同构数据多维容器,也就是说,其中的所有元素必须是相同类型的。每个数 组都有一个shape(一个表示各维度大小的元组)和一个dtype(一个用于说明数组数据类型的对 象)
In [15]:data = np.random.randn(2, 3)
In [16]:type(data) # 类型是numpy.ndarray
out [16]:numpy.ndarray
In [17]: data.shape
Out[17]: (2, 3)
In [18]: data.dtype
Out[18]: dtype('float64')
上面的例子np.arange(1000000)返回的也是numpy.ndarray
numpy.random
:这是 NumPy 中用于生成随机数的子模块。它包含了多种生成随机数的函数,比如:rand()
:生成均匀分布的随机数。randn()
:生成标准正态分布(均值为 0,标准差为 1)的随机数。randint()
:生成整数的随机数。choice()
:生成从给定序列中随机选取的元素。randn
:表示 "random normal",即从标准正态分布中生成随机数。n
代表的是 normal distribution(正态分布)。
import numpy as np
# 一维数组
arr1 = np.array([1, 2, 3, 4])
print(arr1.ndim) # 输出 1,因为是一个一维数组
# 二维数组
arr2 = np.array([[1, 2, 3], [4, 5, 6]])
print(arr2.ndim) # 输出 2,因为是一个二维数组
# 三维数组
arr3 = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(arr3.ndim) # 输出 3,因为是一个三维数组
ndim
是 "number of dimensions" 的缩写,表示 维度的数量。
多维数组的创建
传入普通数组
zeros,ones,empty方法传入一个表示形状的元组
np.array()
:总是返回一个新的 NumPy 数组,数据会被复制(除非copy=False
)。np.asarray()
:如果输入已经是 NumPy 数组,则返回原始数组的视图,不进行数据复制;如果输入是其他类型,则像np.array()
一样创建一个新的数组。
In [31]: np.empty((2, 3, 2))
Out[31]:
array([[[ 0., 0.],
[ 0., 0.],
[ 0., 0.]],
[[ 0., 0.],
[ 0., 0.],
[ 0., 0.]]])
(2, 3, 2) 这个元组有 3 个数字,它表示数组的 三维形状,具体解释如下
- 2:数组的 第 1 维(也叫 "轴 0")的大小,即数组有 2 个“层”(2 个二维矩阵)。
- 3:数组的 第 2 维(也叫 "轴 1")的大小,即每个二维矩阵有 3 行。
- 2:数组的 第 3 维(也叫 "轴 2")的大小,即每一行有 2 列。
数组类型
# 查看数据类型
arr.dtype
# 转成指定数据类型
arr.astype(np.float64)
# 创建的时候制订数据类型
np.array(['1.25', '-9.6', '42'], dtype=np.string_)
# 用代码表示数据类型
empty_uint32 = np.empty(8, dtype='u4')
数组运算
不用编写循环即可对数据执行批量运算。NumPy用户称其为矢量化(vectorization)
In [51]: arr = np.array([[1., 2., 3.], [4., 5., 6.]])
In [52]: arr
Out[52]:
array([[ 1., 2., 3.],
[ 4., 5., 6.]])
In [53]: arr * arr
Out[53]:
array([[ 1., 4., 9.],
[ 16., 25., 36.]])
In [54]: arr - arr
Out[54]:
array([[ 0., 0., 0.],
[ 0., 0., 0.]])
数组与标量的算术运算会将标量值传播到各个元素
In [56]: arr ** 0.5 ## ** 0.5 是开平方的意思 2 ** 0.5 ≈ 1.414
Out[56]:
array([[ 1. , 1.4142, 1.7321],
[ 2. , 2.2361, 2.4495]])
大小相同的数组之间的比较会生成布尔值数组:
不同大小的数组之间的运算叫做广播(broadcasting)暂不讨论
切片和索引
切片的是视图,而不是复制操作,如果你想要得到的是ndarray切片的一份副本而非视图,就需要明确地进行复制操作, 例如 arr[5:8].copy() 。
在多维数组中,如果省略了后面的索引,则返回对象会是一个维度低一点的ndarray(它含有高一 级维度上的所有数据)。因此,在2×2×3数组arr3d中
In [76]: arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
In [77]: arr3d
Out[77]:
array([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]]])
arr3d[0]是一个2×3数组:
In [78]: arr3d[0]
Out[78]:
array([[1, 2, 3],
[4, 5, 6]])
实例3基本索引和切片
arr3d = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
old_values = arr3d[0].copy
arr3d[0] = 44
arr3d[0] = old_values
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[75], line 1
----> 1 arr3d[0] = old_values
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'builtin_function_or_method'
- 错误的原因是
arr3d[0].copy
没有加上括号来调用copy()
方法。 - 修正代码后,通过
arr3d[0].copy()
来正确获取数组的副本。
实例4切片索引
In [90]: arr2d
Out[90]:
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
In [91]: arr2d[:2]
Out[91]:
array([[1, 2, 3],
[4, 5, 6]])
In [92]: arr2d[:2, 1:]
Out[92]:
array([[2, 3],
[5, 6]])
In [93]: arr2d[1, :2]
Out[93]: array([4, 5])
In [94]: arr2d[:2, 2]
Out[94]: array([3, 6])
In [95]: arr2d[:, :1]
Out[95]:
array([[1],
[4], [7]])
In [96]: arr2d[:2, 1:] = 0
In [97]: arr2d
Out[97]:
array([[1, 0, 0],
[4, 0, 0],
[7, 8, 9]])
实例5
import numpy as np
# 创建一个包含字符串的数组,每个字符串最大长度为 4
arr = np.array(['apple', 'banana', 'cherry'], dtype='<U4')
print(arr)
print(arr.dtype)
# 输出
['appl' 'bana' 'cher']
<U4
dtype='<U4'
是 NumPy 数组的 数据类型(dtype),表示该数组的数据类型是 Unicode 字符串,并且每个字符串的最大长度为 4 个字符。
解释:
<
:表示字节顺序(endianess)。<
表示 小端字节顺序(little-endian),即低位字节存储在内存的低地址。对于 Unicode 字符串来说,通常情况下是默认使用小端字节顺序。U
:表示该数据类型是 Unicode 字符串(U
是 "Unicode" 的缩写)。4
:表示每个字符串的最大长度为 4 个字符,即每个元素可以存储最多 4 个 Unicode 字符。
- 在
dtype='<U4'
中,<
表示小端字节顺序,U
表示字符串类型,4
表示每个字符串的最大长度为 4 个字符。 - 所以,即使字符串
'apple'
的实际长度为 5 个字符,NumPy 会将其截断为前 4 个字符'appl'
,同理'banana'
被截断为'bana'
,'cherry'
被截断为'cher'
。
总结:
dtype='<U4'
表示一个 Unicode 字符串数组,其中每个字符串的最大长度为 4 个字符。- 如果尝试存储一个长度大于 4 的字符串,NumPy 会自动进行截断。
实例6 数组转置和轴对换
T属性
In [126]: arr = np.arange(15).reshape((3, 5))
In [127]: arr
Out[127]:
array([[ 0, 1, 2, 3, 4],
[5, 6, 7, 8, 9], [10, 11, 12, 13, 14]])
In [128]: arr.T
Out[128]:
array([[ 0, 5, 10],
[1, 6,11], [2, 7,12], [3, 8,13], [ 4, 9, 14]])
reshape()
方法用于改变数组的形状,不会改变数据的内容。- 可以使用
-1
来让 NumPy 自动推导某一维度的大小。 reshape()
返回的是一个新数组,原始数组的元素不发生变化。
np.dot计算矩阵内积:
In [129]: arr = np.random.randn(6, 3)
In [130]: arr
Out[130]:
array([[-0.8608, 0.5601, -1.2659],
[ 0.1198, -1.0635, 0.3329],
[-2.3594, -0.1995, -1.542 ],
[-0.9707, -1.307 , 0.2863],
[ 0.378 , -0.7539, 0.3313],
[ 1.3497, 0.0699, 0.2467]])
In [131]: np.dot(arr.T, arr)
Out[131]:
array([[ 9.2291, 0.9394, 4.948 ],
[ 0.9394, 3.7662, -1.3622],
[ 4.948 , -1.3622, 4.3437]])
transpose
对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置(比较费脑 子):
这里,第一个轴被换成了第二个,第二个轴被换成了第一个,最后一个轴不变。
In [132]: arr = np.arange(16).reshape((2, 2, 4))
In [133]: arr
Out[133]:
array([[[ 0, 1, 2, 3],
[4, 5, 6, 7]], [[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
In [134]: arr.transpose((1, 0, 2))
Out[134]:
array([[[ 0, 1, 2, 3],
[ 8, 9, 10, 11]], [[4, 5, 6, 7],
[12, 13, 14, 15]]])
swapaxes
需要接 受一对轴编号:
In [135]: arr
Out[135]:
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
In [136]: arr.swapaxes(1, 2)
Out[136]:
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]],
[[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])