从0到1:Python数据分析
上QQ阅读APP看书,第一时间看更新

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. 对于随机数组来说,什么情况下包含结束值,什么情况下不包含结束值呢?

对于随机数组来说,绝大多数情况下都是不包含结束值的。读者暂且可以先这样简单地记忆。