2.2 创建数组
NumPy就是用来操作数组的,我们学习NumPy,其实就是学习数组的各种操作。了解了这一点,可以让后续的学习方向变得更加清晰。
在Python中,可以使用NumPy来创建一个数组。数组和列表很相似,但是它们之间有本质上的区别。
➤ 列表的元素可以是不同数据类型,而数组的元素必须是同一数据类型。
➤ 列表不可以进行四则运算,而数组可以进行四则运算。
对于NumPy中的数组,读者可以简单地把它看成“增强版的列表”,因为它的功能比列表强大得多。
2.2.1 基本方法
在NumPy中,创建数组的方法有很多,常用的方法如表2-1所示。
表2-1 创建数组的常用方法
除了上表列出的方法,NumPy还有一个empty()方法。empty()在实际开发中用得并不多,这里就不展开介绍了。
1.array()
在NumPy中,可以使用array()来创建一个数组。array()是NumPy中最基础也是最常用的一个方法。
语法
np.array(列表或元组)
说明
array()的参数可以是一个列表,也可以是一个元组。array()其实就是将一个列表转换为一个数组,或者将一个元组转换为一个数组。
举例:将列表转换为数组
import numpy as np arr = np.array([1, 2, 3, 4, 5]) print(arr) print(type(arr)) print(arr.dtype)
输出结果如下:
[1 2 3 4 5] <class 'numpy.ndarray'> int32
分析
从输出结果可以看出,数组元素之间是用空格隔开的,而不是用逗号隔开的,这一点和列表不一样。dtype属性用于获取数组元素的类型,这个在下一节会详细介绍。
array()会自动根据“列表元素的类型”来确定“数组元素的数据类型”。np.array()返回的是一个ndarray对象,这个ndarray对象非常重要,我们在后面会大量接触到。
可能一些读者会问:为什么要将列表转换成数组?原因很简单,对于列表,只能使用列表的方法来对其中的元素进行操作,但是将列表转换成数组之后,就可以使用数组提供的丰富方法来对其中的元素进行操作了。
举例:将元组转换为数组
import numpy as np arr = np.array((1, 2, 3, 4, 5)) print(arr) print(type(arr)) print(arr.dtype)
输出结果如下:
[1 2 3 4 5] <class "numpy.ndarray"> int32
分析
np.array()除了可以将列表转换成数组,还可以将元组转换成数组。不过在实际开发中,我们更多是将一个列表转换成数组。
2.arange()
在NumPy中,可以使用arange()来创建元素值在“指定范围”内的一维数组。需要特别注意的是,arange()只能创建一维数组,而不能创建多维数组。
语法
np.arange(start, end, step)
说明
start是开始值,end是结束值,step是步长。np.arange()的取值范围为[start, end),也就是包含开始值,但是不包含结束值。
➤ 当np.arange()有一个参数时,表示只有结束值,此时开始值是0。
➤ 当np.arange()有两个参数时,表示只有开始值和结束值,此时开始值是start。
➤ 当np.arange()有3个参数时,表示有开始值、结束值和步长。所谓步长,指的是元素递增的量。
举例:一个参数
import numpy as np arr = np.arange(10) print(arr)
输出结果如下:
[0 1 2 3 4 5 6 7 8 9]
分析
np.arange(10)表示结束值为10,也就是取值范围为[0, 10)。从结果可以看出,结束值是没有包含进去的。读者记住这一点就可以了:不管np.arange()的参数是多少个,结束值都不会被包含进去。
对于这个例子来说,下面3种方式是等价的,读者可以思考一下为什么:
# 方式1 np.arange(10) # 方式2 np.arange(0, 10) # 方式3 np.arange(0, 10, 1)
举例:两个参数
import numpy as np arr = np.arange(5, 10) print(arr)
输出结果如下:
[5 6 7 8 9]
分析
np.arange(5, 10)表示开始值为5、结束值为10,也就是取值范围为[5, 10)。对于这个例子来说,下面两种方式是等价的:
# 方式1 np.arange(5, 10) # 方式2 np.arange(5, 10, 1)
举例:3个参数
import numpy as np arr = np.arange(10, 30, 3) print(arr)
输出结果如下:
[10 13 16 19 22 25 28]
分析
np.arange(10, 30, 3)的取值范围为[10, 30),步长为3。np.arange()不仅可以用于创建元素的类型是整型的数组,还可以用于创建元素的类型是浮点型的数组。
举例:元素是浮点数
import numpy as np arr = np.arange(1.5, 10.5, 2) print(arr)
输出结果如下:
[1.5 3.5 5.5 7.5 9.5]
分析
np.arange(1.5, 10.5, 2)的取值范围为[1.5, 10.5),步长为2。
举例:步长是浮点数
import numpy as np arr = np.arange(1, 10, 1.5) print(arr)
输出结果如下:
[1. 2.5 4. 5.5 7. 8.5]
分析
np.arange(1, 10, 1.5)的取值范围为[1, 10),步长为1.5。np.arange()和range()非常相似,但是它们之间也有一定的区别:range()的步长只能是整数,而np.arange()的步长可以是任意数。
此外,arange()只能创建一维数组。如果想要创建多维数组,可以结合“2.5 数组操作”这一节介绍的reshape()来实现。
3.linspace()
除了arange()之外,还可以使用linspace()来创建元素值在“指定范围”内的一维数组。
语法
np.linspace(start, end, num, endpoint=True或False)
说明
start是开始值,end是结束值,num是个数。默认情况下,linspace()的取值范围为[start, end],也就是包含开始值和结束值。但是可以使用endpoint=False,使得它的取值范围为[start, end)。
举例:包含结束值
import numpy as np arr = np.linspace(0, 10, 20) print(arr)
输出结果如下:
[ 0. 0.52631579 1.05263158 1.57894737 2.10526316 2.63157895 3.15789474 3.68421053 4.21052632 4.73684211 5.26315789 5.78947368 6.31578947 6.84210526 7.36842105 7.89473684 8.42105263 8.94736842 9.47368421 10. ]
分析
np.linspace(0, 10, 20)表示开始值为0,结束值为10,元素个数为20。需要注意的是,np.linspace(0, 10, 20)的取值范围为[0, 10],也就是包含结束值10。如果不希望把结束值包含进去,可以使用endpoint=False。
举例:不包含结束值
import numpy as np arr = np.linspace(0, 10, 20, endpoint=False) print(arr)
输出结果如下:
[0. 0.5 1. 1.5 2. 2.5 3. 3.5 4. 4.5 5. 5.5 6. 6.5 7. 7.5 8. 8.5 9. 9.5]
4.zeros()和ones()
在NumPy中,可以使用zeros()来创建一个元素值全为0的数组,也可以使用ones()来创建一个元素值全为1的数组。
语法
np.zeros((a, b, ..., n), dtype=int或float) np.ones((a, b, ..., n), dtype=int或float)
说明
zeros()和ones()都可以接收两个参数。第1个参数是一个元组,它是一个必选参数,表示创建一个a×b×…×n的数组;第2个参数是一个可选参数,它用于定义元素的类型,默认是float类型。
举例:默认情况
import numpy as np arr1 = np.zeros((3, 3)) arr2 = np.ones((3, 3)) print(arr1) print(arr2)
输出结果如下:
[[0. 0. 0.] [0. 0. 0.] [0. 0. 0.]] [[1. 1. 1.] [1. 1. 1.] [1. 1. 1.]]
分析
实际上,np.zeros((3, 3))是一种简写方式,它等价于np.zeros(shape=(3,3))。shape是数组的形状,下一节会详细介绍。
对于shape参数来说,它的值是一个元组或列表,所以下面两种方式是等价的。这两种方式在实际开发中都会出现,我们需要了解一下:
# 方式1 arr1 = np.zeros((3, 3)) arr2 = np.ones((3, 3)) # 方式2 arr1 = np.zeros([3, 3]) arr2 = np.ones([3, 3])
默认情况下,使用zeros()和ones()创建的数组的元素都是float类型,以下两种方式是等价的:
# 方式1 arr1 = np.zeros((3, 3)) arr2 = np.ones((3, 3)) # 方式2 arr1 = np.zeros((3, 3), dtype=float) arr2 = np.ones((3, 3), dtype=float)
举例:int类型
import numpy as np arr1 = np.zeros((3, 3), dtype=int) arr2 = np.ones((3, 3), dtype=int) print(arr1) print(arr2)
输出结果如下:
[[0 0 0] [0 0 0] [0 0 0]] [[1 1 1] [1 1 1] [1 1 1]]
分析
如果想要将数组元素定义成int类型,可以使用dtype=int。
2.2.2 随机数组
在实际开发中,有时手动创建的数据并不能满足开发需求,所以我们还得借助一种特殊数组,那就是“随机数组”。
在NumPy中,可以使用random模块来创建一个随机数组。创建随机数组的方法主要有3种,如表2-2所示。
表2-2 创建随机数组的方法
1.randint()
在NumPy中,可以使用random模块的randint()方法来创建一个整型随机数组。
语法
np.random.randint(start, end, size=元组或整数)
说明
元素的取值范围为[start, end),包含start但不包含end。size的取值分为以下两种情况。
➤ 当size是一个元组时,size=(m)表示创建一个m行的一维数组,size=(m, n)表示创建一个m×n的二维数组,以此类推。
➤ 当size是一个整数时,size=m表示创建一个m行的一维数组。实际上,size=(m)和size=m是等价的,因为对于一个数来说,使用“()”和不使用“()”是一样的。
举例:一维数组
import numpy as np arr = np.random.randint(10, 20, size=(5)) print(arr)
输出结果如下:
[14 11 17 16 12]
分析
randint(10, 20, size=(5))表示创建一个包含5个元素的一维随机数组,元素都是[10, 20)内的整数。
对于这个例子来说,下面3种方式是等价的。这3种方式我们都要了解一下,因为很多地方都会出现:
# 方式1 arr = np.random.randint(10, 20, size=(5)) # 方式2 arr = np.random.randint(10, 20, size=5) # 方式3 arr = np.random.randint(10, 20, 5)
举例:二维数组
import numpy as np arr = np.random.randint(10, 20, size=(2, 5)) print(arr)
输出结果如下:
[[14 14 19 12 19] [17 12 15 14 12]]
分析
randint(10, 20, size=(2, 5))表示创建一个2×5的二维随机数组,元素都是[10, 20)内的整数。2×5也就是2行5列,如图2-3所示。
图2-3
对于这个例子来说,以下两种方式是等价的:
# 方式1 arr = np.random.randint(10, 20, size=(2, 5)) # 方式2 arr = np.random.randint(10, 20, (2, 5))
2.rand()
在NumPy中,可以使用random模块的rand()方法来创建一个浮点型随机数组,其元素的取值范围为[0,1)。
语法
np.random.rand(m, n)
说明
当rand()有一个参数时,例如rand(m),表示创建一个包含m个元素的一维随机数组。当rand()有两个参数时,例如rand(m, n),表示创建一个m×n的二维随机数组。
举例:一维数组
import numpy as np arr = np.random.rand(5) print(arr)
输出结果如下:
[0.52039969 0.44537686 0.66946674 0.91937385 0.6516376]
分析
在这个例子中,rand(5)表示创建一个包含5个元素的一维随机数组。其中,元素都是[1,0)内的浮点数。
举例:二维数组
import numpy as np arr = np.random.rand(2, 5) print(arr)
输出结果如下:
[[0.23881446 0.78932247 0.58749747 0.86061954 0.65791401] [0.59769419 0.12579491 0.79234194 0.74421893 0.27629203]]
分析
在这个例子中,rand(2, 5)表示创建一个2×5的二维随机数组。
3.randn()
在NumPy中,可以使用random模块的randn()方法来生成一个随机数组,该数组的元素符合正态分布。
语法
np.random.randn(m, n)
说明
当randn()有一个参数时,例如randn(m),表示创建一个包含m个元素的一维随机数组。当randn()有两个参数时,例如randn(m, n),表示创建一个m×n的二维随机数组。
randn()和rand()的参数是一样的,只是生成的元素的值不一样。
举例:一维数组
import numpy as np arr = np.random.randn(5) print(arr)
输出结果如下:
[-1.05723124 -0.00507968 -0.20048986 0.0417768 1.51705339]
分析
在这个例子中,randn(5)表示创建一个包含5个元素的一维随机数组。
举例:二维数组
import numpy as np arr = np.random.randn(2, 5) print(arr)
输出结果如下:
[[-2.0953303 0.03843233 -0.63218314 1.22316454 0.21490433] [-0.5237318 -1.01258678 0.58363956 0.11605207 1.39723533]]
分析
在这个例子中,randn(2, 5)表示创建一个2×5的二维随机数组。需要说明的是,随机数组在后面的学习中会大量用到,所以randint()、rand()、randn()这几个方法,读者要重点掌握。
2.2.3 数组与列表的区别
到这里,读者可能会有这样的一个疑问:为什么有了列表之后,还要搞一个数组出来呢?它们之间有什么区别吗?其实,数组和列表(见图2-4)最本质的区别在于:它们在内存中的存储方式不同。
图2-4
列表可以存储不同类型的元素,元素可以是数字、字符串、列表、字典等。对于列表中的每一个元素来说,需要为元素分配一定的内存空间来保存元素的类型信息。当所有元素都是同一种类型时,本来只需要存储一次类型信息就可以了,但是却要为每一个元素分配“额外的内存”来保存类型信息。此时这些信息是冗余的,并且白白浪费了内存空间。
为了解决这个问题,最好的办法就是使用只包含相同类型元素的数组。当元素个数非常庞大(例如上百万)时,列表和数组的性能差异就变得非常明显了。在对大量数据进行数学操作时,当然也可以使用列表来操作,只是并不推荐这样去做,因为此时列表的性能太差了。
NumPy中数组的元素要比列表中的元素“纯洁”得多,元素的类型只能是同一种数据类型。对于NumPy中的数组来说,元素的类型大多数情况下都是整型或浮点型,很少情况下是其他类型。
举例:不同类型
import numpy as np arr = np.array([1, "2", True, "4", 5]) print(arr) print(arr.dtype) # 输出数组的类型
输出结果如下:
["1" "2" "True" "4" "5"] <U11
分析
np.array()用于将列表转换为数组,一般要求列表的元素是相同的类型。当列表元素是不同类型时,程序不会报错,但是并不推荐这样去做。读者在使用NumPy数组时,要避免出现这种问题。
【常见问题】
1. 为什么不用[1 2 3 4 5]这样的方式来创建数组,而是采用np.array()这种更加复杂的方式呢?
这是因为在Python中,中括号已经用于创建列表了。如果数组也使用中括号来创建,那么Python就无法判断此时创建的到底是列表还是数组了。
2. 为什么NumPy要提供那么多个创建数组的方式呢?
原因其实很简单,NumPy的核心就是数组。所以不管是创建数组还是操作数组,它都提供了尽可能多的方式,以便我们更好地灵活操作。
3. 对于随机数组来说,什么情况下包含结束值,什么情况下不包含结束值呢?
对于随机数组来说,绝大多数情况下都是不包含结束值的。读者暂且可以先这样简单地记忆。