numpy教程的第一部分涵盖了利用numpy多维数组进行数据操作与分析的核心方面。Numpy是python中用于科学计算与数据操作的最基础而又强力的包。
1. Numpy介绍
如果你想在数据分析与机器学习方面进行工作,那么就肯定需要对numpy有一个强硬的理解。
因为其他数据分析的包( 像pandas)是基于numpy的,又或者是用于构建机器学习应用的scikit-learn包,也与numpy有很大的联系。
所以,numpy提供了什么?
从核心层面来讲,numpy提供了极好的ndarray objects
,n维数组的缩写。
在一个ndarray objects
(又称array
)中,你可以存储相同数据类型的多个值,围绕着它,能使numpy更方便地进行数学与数据操作。
你可能会这么想:我能在python的list
中存储数字跟其他类型对象,并且能通过列表推导式,for循环来运行计算与操作的排序,为什么我还需要一个numpy数组呢?
当然,有很多显著的优点能说明numpy比list更好用。为了理解这一部分,先让我们看看如何创建一个numpy array
2. 创建一个numpy数组
有很多方法创建一个numpy数组。但是最常用一个方式是从list或者一个类似list的对象中通过np.array
方法创建过来。
# Create an 1d array from a list
import numpy as np
list1 = [0,1,2,3,4]
arr1d = np.array(list1)
# Print the array and its type
print(type(arr1d))
arr1d
#> <class 'numpy.ndarray'>
#> array([0, 1, 2, 3, 4])
这就意味着,你可以在数组内的每个值上面应用一个函数方法操作,而不是整个数组对象。
让我们对每个列表中的数值加上2,最直观的方法这样的:
list1 + 2 # error
这个做法在列表上是不存在的,但你可以在多维数组上这么做:
# Add 2 to each element of arr1d
arr1d + 2
#> array([2, 3, 4, 5, 6])
另一个特点是,一旦一个数组被创建,那么你将无法增加它的大小,你只有重建一个数组。而列表就不一样了,你可以随意增加删除里面的值。
虽然如此,可数组有更多的优点,让我们往下看看。
你可以用过一个包含列表的列表创建一个类似于二维数组的矩阵(二维数组也可以称为数学中的矩阵)。
# Create a 2d array from a list of lists
list2 = [[0,1,2], [3,4,5], [6,7,8]]
arr2d = np.array(list2)
arr2d
#> array([[0, 1, 2],
#> [3, 4, 5],
#> [6, 7, 8]])
也可以通过设置dtype
参数来设置数据类型。最常用的几个numpy数据类型:'float', 'int', 'bool', 'str' 和 'object'。为了控制内存分配你可以从‘float32’, ‘float64’, ‘int8’, ‘int16’ or ‘int32’中选择其中一个。
# Create a float 2d array
arr2d_f = np.array(list2, dtype='float')
arr2d_f
#> array([[ 0., 1., 2.],
#> [ 3., 4., 5.],
#> [ 6., 7., 8.]])
小数点是float数据类型的体现,你可以用astype
方法将它转换成其他不同类型。
# Convert to 'int' datatype
arr2d_f.astype('int')
#> array([[0, 1, 2],
#> [3, 4, 5],
#> [6, 7, 8]])
# Convert to int then to str datatype
arr2d_f.astype('int').astype('str')
#> array([['0', '1', '2'],
#> ['3', '4', '5'],
#> ['6', '7', '8']],
#> dtype='U21')
数组中的每个数据必须是同一个类型,这个有别于列表。这是另一个显著的差异。
但是如果你不确定你的数组要用什么类型的数据,或者说你想将数字与字符放在一个数组中,你可以将dtype
设置为'object'
。
# Create a boolean array
arr2d_b = np.array([1, 0, 10], dtype='bool')
arr2d_b
#> array([ True, False, True], dtype=bool)
# Create an object array to hold numbers as well as strings
arr1d_obj = np.array([1, 'a'], dtype='object')
arr1d_obj
#> array([1, 'a'], dtype=object)
最后说明下,你总是可以用tolist()
将一个数组转换成一个python列表。
总结下数组与python列表的主要区别:
- 数组支持矢量运算,而列表不能
- 数组一旦创建就不能修改大小,只能重新创建或者覆盖一个已存在的
- 每个数组有且仅有一个数据类型,所有数据都是这一个类型
- 包含相同数量的数组与列表,数组所占空间更少点
3. 对数组查看以及重塑大小
让我们来看一下一个二维数组。既然同时从一个列表嵌套列表创建而来的,那么它是二维,有行有列,像一个矩阵。
如果从一个列表嵌套列表再嵌套列表,那么它就是三位数组,像一个立方体。
所以我们需要知道一些属性:
- 一维二维还是更多 (ndim)
- 每个维度中有多少项 (shape)
- 它的数据类型是什么 (dtype)
- 总共有多少项数据 (size)
- 如何拿到前几项的数据值 (through indexing)
example:
# Create a 2d array with 3 rows and 4 columns
list2 = [[1, 2, 3, 4],[3, 4, 5, 6], [5, 6, 7, 8]]
arr2 = np.array(list2, dtype='float')
arr2
#> array([[ 1., 2., 3., 4.],
#> [ 3., 4., 5., 6.],
#> [ 5., 6., 7., 8.]])
# shape
print('Shape: ', arr2.shape)
# dtype
print('Datatype: ', arr2.dtype)
# size
print('Size: ', arr2.size)
# ndim
print('Num Dimensions: ', arr2.ndim)
#> Shape: (3, 4)
#> Datatype: float64
#> Size: 12
#> Num Dimensions: 2
4. 提取一个数组中的具体项
arr2
#> array([[ 1., 2., 3., 4.],
#> [ 3., 4., 5., 6.],
#> [ 5., 6., 7., 8.]])
你可以用索引从0开始拿数据,跟python列表一样。
但有些地方有不像列表,数组可以选择性在方括号中接收多个参数,参数数量与维度一致。
# Extract the first 2 rows and columns
arr2[:2, :2]
list2[:2, :2] # error
#> array([[ 1., 2.],
#> [ 3., 4.]])
另外,numpy数组支持布尔索引。布尔索引将会生成一个有True和False值填充且与原来大小一致的数组。
# Get the boolean output by applying the condition to each element.
b = arr2 > 4
b
#> array([[False, False, False, False],
#> [False, False, True, True],
#> [ True, True, True, True]], dtype=bool)
arr2[b]
arr[b].ndim
#> array([ 5., 6., 5., 6., 7., 8.])
#> 1
4.1 倒转行或者整个数组
倒转数组跟倒转列表的操作差不多,但是如果想要对数组完全的倒转你就需要对所有轴(维度)进行倒转。
# Reverse only the row positions
arr2[::-1, ]
#> array([[ 5., 6., 7., 8.],
#> [ 3., 4., 5., 6.],
#> [ 1., 2., 3., 4.]])
# Reverse the row and column positions
arr2[::-1, ::-1]
#> array([[ 8., 7., 6., 5.],
#> [ 6., 5., 4., 3.],
#> [ 4., 3., 2., 1.]])
4.2 代表数据缺失与无穷大
数据缺失可以用np.nan
对象表示。而np.inf
代表无穷大。让我们展示下。
# Insert a nan and an inf
arr2[1,1] = np.nan # not a number
arr2[1,2] = np.inf # infinite
arr2
#> array([[ 1., 2., 3., 4.],
#> [ 3., nan, inf, 6.],
#> [ 5., 6., 7., 8.]])
# Replace nan and inf with -1. Don't use arr2 == np.nan
missing_bool = np.isnan(arr2) | np.isinf(arr2)
arr2[missing_bool] = -1
arr2
#> array([[ 1., 2., 3., 4.],
#> [ 3., -1., -1., 6.],
#> [ 5., 6., 7., 8.]])
4.3 在多维数组计算平均值,最大最小值
多维数组对这些值有各自的计算方法。
# mean, max and min
print("Mean value is: ", arr2.mean())
print("Max value is: ", arr2.max())
print("Min value is: ", arr2.min())
#> Mean value is: 3.58333333333
#> Max value is: 8.0
#> Min value is: -1.0
但是,如果你想逐行逐列计算最小值,那么就要用np.amin
了。
# Row wise and column wise min
print("Column wise minimum: ", np.amin(arr2, axis=0))
print("Row wise minimum: ", np.amin(arr2, axis=1))
#> Column wise minimum: [ 1. -1. -1. 4.]
#> Row wise minimum: [ 1. -1. 5.]
注:这里的amin
函数与min
函数是同一个,你也可以用arr2.min(axis=0)
来求得相同值,关于axis可以看这篇文章
逐行计算最小值很好用。但是如果你想做一些其他逐行的计算/函数那么你就要用到np.apply_over_axis
,如下。
# Cumulative Sum(计算累加值)
np.cumsum(arr2)
#> array([ 1., 3., 6., 10., 13., 12., 11., 17., 22., 28., 35., 43.])
5. 从一个已存在的数组创建新数组
如果你只是想分配数组中的一部分到另一个数组中,那么这个新创建的数组实际上在父数组的内存中。这就意味着,如果你想对新数组进行一些操作,那么它将影响到父数组。所以为了避免打扰到父数组,你需要使用copy()
来进行复制。所有的numpy数组都来自这个方法。
# Assign portion of arr2 to arr2a. Doesn't really create a new array.
arr2a = arr2[:2,:2]
arr2a[:1, :1] = 100 # 100 will reflect in arr2
arr2
#> array([[ 100., 2., 3., 4.],
#> [ 3., -1., -1., 6.],
#> [ 5., 6., 7., 8.]])
# Copy portion of arr2 to arr2b
arr2b = arr2[:2, :2].copy()
arr2b[:1, :1] = 101 # 101 will not reflect in arr2
arr2
#> array([[ 100., 2., 3., 4.],
#> [ 3., -1., -1., 6.],
#> [ 5., 6., 7., 8.]])
6. flatten() 和 ravel()的区别
有两种方式实现扁平(平铺),flatten()
和ravel()
方法。两者的区别是:
使用ravel()
创建新数组实际上是对父数组的引用。所以,新数组会影响到父数组,跟上面那个例子一样,但是在内存方面效率更高点,因为不创建一个副本。
# Flatten it to a 1d array
arr2.flatten()
#> array([ 100., 2., 3., 4., 3., -1., -1., 6., 5., 6., 7., 8.])
# Changing the flattened array does not change parent
b1 = arr2.flatten()
b1[0] = 99 # changing b1 does not affect arr2
arr2
#> array([[ 100., 2., 3., 4.],
#> [ 3., -1., -1., 6.],
#> [ 5., 6., 7., 8.]])
# Changing the raveled array changes the parent also.
b2 = arr2.ravel()
b2[0] = 99 # changing b2 changes arr2 also
arr2
#> array([[ 99., 2., 3., 4.],
#> [ 3., -1., -1., 6.],
#> [ 5., 6., 7., 8.]])
7. 使用numpy创建序列,重复以及随机数字
np.arange
函数能很方便地创建定制数字序列作为一个多维数组
# Lower limit is 0 be default
print(np.arange(5))
# 0 to 9
print(np.arange(0, 10))
# 0 to 9 with step of 2
print(np.arange(0, 10, 2))
# 10 to 1, decreasing order
print(np.arange(10, 0, -1))
#> [0 1 2 3 4]
#> [0 1 2 3 4 5 6 7 8 9]
#> [0 2 4 6 8]
#> [10 9 8 7 6 5 4 3 2 1]
你可以在np.arange
中设置开始与结束位置。但是如果你专注于数组中的项目数量,则必须手动计算适当的步长(step)。
比如,你想创建一个正好在1到50之间的10个数字的数组,你能计算出什么是步长?然而我会选择使用np.linspace
# Start at 1 and end at 50
np.linspace(start=1, stop=50, num=10, dtype=int)
#> array([ 1, 6, 11, 17, 22, 28, 33, 39, 44, 50])
注意,因为我明确强制dtype为int,所以数字不会等间隔因为舍入了。
与np.linspace
类似,也有np.logspace
以对数形式上升。在np.logspace
中,给定的起始值实际上是base ^ start,以base ^ stop结束,默认基础(base)值为10。
# Limit the number of digits after the decimal to 2
np.set_printoptions(precision=2)
# Start at 10^1 and end at 10^50
np.logspace(start=1, stop=50, num=10, base=10)
#> array([ 1.00e+01, 2.78e+06, 7.74e+11, 2.15e+17, 5.99e+22,
#> 1.67e+28, 4.64e+33, 1.29e+39, 3.59e+44, 1.00e+50])
使用np.zeros
和np.ones
函数可以创建所需形状的数组,其中所有项都是0或1。
np.zeros([2,2])
#> array([[ 0., 0.],
#> [ 0., 0.]])
np.ones([2,2])
#> array([[ 1., 1.],
#> [ 1., 1.]])
7.1 创建重复序列
np.tile
将重复整个列表或数组n次。而np.repeat
重复每一项n次。
a = [1,2,3]
# Repeat whole of 'a' two times
print('Tile: ', np.tile(a, 2))
# Repeat each element of 'a' two times
print('Repeat: ', np.repeat(a, 2))
#> Tile: [1 2 3 1 2 3]
#> Repeat: [1 1 2 2 3 3]
7.2 生成随机数
random
模块提供了很好的函数来生成任意给定形状的随机数(以及统计分布)。
# Random numbers between [0,1) of shape 2,2
print(np.random.rand(2,2))
# Normal distribution with mean=0 and variance=1 of shape 2,2
print(np.random.randn(2,2))
# Random integers between [0, 10) of shape 2,2
print(np.random.randint(0, 10, size=[2,2]))
# One random number between [0,1)
print(np.random.random())
# Random numbers between [0,1) of shape 2,2
print(np.random.random(size=[2,2]))
# Pick 10 items from a given list, with equal probability
print(np.random.choice(['a', 'e', 'i', 'o', 'u'], size=10))
# Pick 10 items from a given list with a predefined probability 'p'
print(np.random.choice(['a', 'e', 'i', 'o', 'u'], size=10, p=[0.3, .1, 0.1, 0.4, 0.1])) # picks more o's
#> [[ 0.84 0.7 ]
#> [ 0.52 0.8 ]]
#> [[-0.06 -1.55]
#> [ 0.47 -0.04]]
#> [[4 0]
#> [8 7]]
#> 0.08737272424956832
#> [[ 0.45 0.78]
#> [ 0.03 0.74]]
#> ['i' 'a' 'e' 'e' 'a' 'u' 'o' 'e' 'i' 'u']
#> ['o' 'a' 'e' 'a' 'a' 'o' 'o' 'o' 'a' 'o']
现在,每次运行上述任何功能时,都会得到一组不同的随机数
如果你想每次重复同一组随机数,你需要设置种子或随机状态。看到的可以是任何值。唯一的要求是你必须在每次要生成相同的一组随机数时将种子设置为相同的值。
一旦创建了np.random.RandomState
,np.random
模块的所有功能就可用于创建的随机状态对象。
# Create the random state
rn = np.random.RandomState(100)
# Create random numbers between [0,1) of shape 2,2
print(rn.rand(2,2))
#> [[ 0.54 0.28]
#> [ 0.42 0.84]]
# Set the random seed
np.random.seed(100)
# Create random numbers between [0,1) of shape 2,2
print(np.random.rand(2,2))
#> [[ 0.54 0.28]
#> [ 0.42 0.84]]
7.3 获得独特的项目和数量
np.unique
方法可用于获取唯一项目。如果您想要每个项目的重复计数,请将return_counts
参数设置为True。
# Create random integers of size 10 between [0,10)
np.random.seed(100)
arr_rand = np.random.randint(0, 10, size=10)
print(arr_rand)
#> [8 8 3 7 7 0 4 2 5 2]
# Get the unique items and their counts
uniqs, counts = np.unique(arr_rand, return_counts=True)
print("Unique items : ", uniqs)
print("Counts : ", counts)
#> Unique items : [0 2 3 4 5 7 8]
#> Counts : [1 2 1 1 1 2 2]
8.0 总结
这完成了numpy系列的第一部分。下一个是用于数据分析的高级numpy,其中我将详细说明作为数据分析必不可少的工具包的功能。