本案例是Pandas数据分析课程【第一章】NumPy基础配套案例。案例使用葡萄酒品质数据,我们将结合数据使用NumPy进行简单分析。

1 数据集

本次案例使用葡萄酒品质数据,该数据集包含1599种红葡萄酒的各种信息,如酒的固定酸度、挥发性酸度和pH值等测量值,也包括一个酒的品质得分,该得分是至少三类口味测试者给该款酒打分的平均值。该数据来源于公开数据库UCI,更多详细信息可以查看https://archive.ics.uci.edu/ml/datasets/Wine+Quality

数据中变量有:

变量名称 含义说明
fixed acidity 固定酸度
volatile acidity 挥发性酸度
pH 酸碱值
alcohol 酒精度数
quality 品质得分

2 数据分析

2.1 导入数据

In [1]:
import numpy as np

我们使用NumPy中的genfromtxt导入数据,由于数据为csv格式即逗号分隔符文件,故设定参数delimiter为","。

In [2]:
wines1=np.genfromtxt("./input/winequality-red.csv",delimiter=",")

查看数据

In [3]:
wines1
Out[3]:
array([[   nan,    nan,    nan,    nan,    nan],
       [ 7.4  ,  0.7  ,  3.51 ,  9.4  ,  5.   ],
       [ 7.8  ,  0.88 ,  3.2  ,  9.8  ,  5.   ],
       ...,
       [ 6.3  ,  0.51 ,  3.42 , 11.   ,  6.   ],
       [ 5.9  ,  0.645,  3.57 , 10.2  ,  5.   ],
       [ 6.   ,  0.31 ,  3.39 , 11.   ,  6.   ]])

数据中第一行为缺失值,这是因为数据第一行为各变量名,是字符串格式,而genfromtxt默认导入的数据格式为浮点型,故会被读成缺失值。为了防止缺失值的产生,我们可以调整参数skip_header为1,在读取时跳过第一行。

In [4]:
wines1=np.genfromtxt("./input/winequality-red.csv",delimiter=",",skip_header=1)
In [5]:
wines1
Out[5]:
array([[ 7.4  ,  0.7  ,  3.51 ,  9.4  ,  5.   ],
       [ 7.8  ,  0.88 ,  3.2  ,  9.8  ,  5.   ],
       [ 7.8  ,  0.76 ,  3.26 ,  9.8  ,  5.   ],
       ...,
       [ 6.3  ,  0.51 ,  3.42 , 11.   ,  6.   ],
       [ 5.9  ,  0.645,  3.57 , 10.2  ,  5.   ],
       [ 6.   ,  0.31 ,  3.39 , 11.   ,  6.   ]])

但这样我们就失去了各变量名的数据,我们也可以通过设定namesTrue来处理这个问题。

In [6]:
wines2=np.genfromtxt("./input/winequality-red.csv",delimiter=",",names=True)
In [7]:
wines2
Out[7]:
array([(7.4, 0.7  , 3.51,  9.4, 5.), (7.8, 0.88 , 3.2 ,  9.8, 5.),
       (7.8, 0.76 , 3.26,  9.8, 5.), ..., (6.3, 0.51 , 3.42, 11. , 6.),
       (5.9, 0.645, 3.57, 10.2, 5.), (6. , 0.31 , 3.39, 11. , 6.)],
      dtype=[('fixed_acidity', '<f8'), ('volatile_acidity', '<f8'), ('pH', '<f8'), ('alcohol', '<f8'), ('quality', '<f8')])

这样,就把首行读取为了每一列的列名,同时默认了各列的数据类型都是浮点型'f8'。但这两种方法读入的数据有所不同。我们可以通过shape查看两份数据的维度。

In [8]:
wines1.shape
Out[8]:
(1599, 5)
In [9]:
wines2.shape
Out[9]:
(1599,)

可以看到,通过第一种方法读入的数据是一个二维数组,而通过第二种方法读入的数据是一个一维数组。我们可以通过dtype查看各这两个数组的数据类型。

In [10]:
wines1.dtype
Out[10]:
dtype('float64')
In [11]:
wines2.dtype
Out[11]:
dtype([('fixed_acidity', '<f8'), ('volatile_acidity', '<f8'), ('pH', '<f8'), ('alcohol', '<f8'), ('quality', '<f8')])

第一种方法获得的数组数据类型都是浮点型,第二种方法获得的数组则可以针对不同的列指定不同的数据类型。

2.2 数据选取

使用索引选取数据

和Python的列表一样,NumPy也是零索引的,即第一行的索引为0,第一列的索引为0。例如,我们想获取第三种红葡萄酒的品质得分,即第三行第五列的数据,对应的行索引为2,列索引为4。

In [12]:
wines1[2,4]
Out[12]:
5.0

使用切片选取数据

如果我们想要从第四列中选择前三项,我们可以使用冒号(:)来完成。冒号表示我们要从起始索引中选择所有元素,但不包括结束索引。

In [13]:
wines1[0:3,3]
Out[13]:
array([9.4, 9.8, 9.8])

就像列表切片一样,可以省略0。

In [14]:
wines1[:3,3]
Out[14]:
array([9.4, 9.8, 9.8])

当不指定开始或结束索引,只用冒号制定时,将选择所有数据。例如,我们选取第四列的所有数据。

In [15]:
wines1[:,3]
Out[15]:
array([ 9.4,  9.8,  9.8, ..., 11. , 10.2, 11. ])

同样的,我们也可以提取整行,例如我们提取第一种红葡萄酒的所有数据。

In [16]:
wines1[0,:]
Out[16]:
array([7.4 , 0.7 , 3.51, 9.4 , 5.  ])

2.3 改变数组阵列

对数组进行转置

将二维数组进行翻转可以使用transposeT。区别在于transpose是NumPy中的一个函数,而T是一个方法。

In [17]:
np.transpose(wines1)
Out[17]:
array([[ 7.4  ,  7.8  ,  7.8  , ...,  6.3  ,  5.9  ,  6.   ],
       [ 0.7  ,  0.88 ,  0.76 , ...,  0.51 ,  0.645,  0.31 ],
       [ 3.51 ,  3.2  ,  3.26 , ...,  3.42 ,  3.57 ,  3.39 ],
       [ 9.4  ,  9.8  ,  9.8  , ..., 11.   , 10.2  , 11.   ],
       [ 5.   ,  5.   ,  5.   , ...,  6.   ,  5.   ,  6.   ]])
In [18]:
wines1.T
Out[18]:
array([[ 7.4  ,  7.8  ,  7.8  , ...,  6.3  ,  5.9  ,  6.   ],
       [ 0.7  ,  0.88 ,  0.76 , ...,  0.51 ,  0.645,  0.31 ],
       [ 3.51 ,  3.2  ,  3.26 , ...,  3.42 ,  3.57 ,  3.39 ],
       [ 9.4  ,  9.8  ,  9.8  , ..., 11.   , 10.2  , 11.   ],
       [ 5.   ,  5.   ,  5.   , ...,  6.   ,  5.   ,  6.   ]])

将多维数组降为一维

我们可以使用ravelflatten将多维数据降为一维。

In [19]:
wines1.ravel()
Out[19]:
array([ 7.4 ,  0.7 ,  3.51, ...,  3.39, 11.  ,  6.  ])
In [20]:
wines1.flatten()
Out[20]:
array([ 7.4 ,  0.7 ,  3.51, ...,  3.39, 11.  ,  6.  ])

默认的降维方式会将每一行接在前一行后,也可以修改参数order为'F'按列进行降维,将每一列接在前一列后。两个函数都有这一参数,我们以ravel为例。

In [21]:
wines1.ravel(order='F')
Out[21]:
array([7.4, 7.8, 7.8, ..., 6. , 5. , 6. ])

两个函数的差别在于,ravel返回的结果是一个视图,对此进行修改会改变原始数据集,而flatten返回的是一个拷贝,对其进行修改不会影响原始数据集。

将数组修改为特定行列数

我们可以使用reshaperesize将数据重新排列为指定的行列形状。例如,我们将降成一维的数据再变回原来的样子。

In [22]:
test=wines1.flatten()
test
Out[22]:
array([ 7.4 ,  0.7 ,  3.51, ...,  3.39, 11.  ,  6.  ])
In [23]:
test.reshape(1599,5)
Out[23]:
array([[ 7.4  ,  0.7  ,  3.51 ,  9.4  ,  5.   ],
       [ 7.8  ,  0.88 ,  3.2  ,  9.8  ,  5.   ],
       [ 7.8  ,  0.76 ,  3.26 ,  9.8  ,  5.   ],
       ...,
       [ 6.3  ,  0.51 ,  3.42 , 11.   ,  6.   ],
       [ 5.9  ,  0.645,  3.57 , 10.2  ,  5.   ],
       [ 6.   ,  0.31 ,  3.39 , 11.   ,  6.   ]])

这里同样可以通过调整order参数修改数据排列方法,默认的排序方法为'C',将按行排,排满一行后排下一行。可以调整为'F',这将按列排,排满一列后排下一列。

In [24]:
test.reshape(1599,5,order='F')
Out[24]:
array([[ 7.4 ,  6.  , 10.3 ,  3.36,  0.3 ],
       [ 0.7 ,  9.8 ,  6.  , 10.  ,  3.14],
       [ 3.51,  0.66,  9.9 ,  5.  , 11.5 ],
       ...,
       [ 0.77,  8.9 ,  7.  ,  9.5 ,  3.39],
       [ 3.3 ,  0.29,  8.  ,  6.  , 11.  ],
       [10.4 ,  3.18,  0.59,  9.8 ,  6.  ]])

reshape相比,resize在参数上一致,但没有返回值,即会直接对原始数据进行修改。

In [25]:
test.resize(1599,5)

此时原始数据直接被修改了。

In [26]:
test
Out[26]:
array([[ 7.4  ,  0.7  ,  3.51 ,  9.4  ,  5.   ],
       [ 7.8  ,  0.88 ,  3.2  ,  9.8  ,  5.   ],
       [ 7.8  ,  0.76 ,  3.26 ,  9.8  ,  5.   ],
       ...,
       [ 6.3  ,  0.51 ,  3.42 , 11.   ,  6.   ],
       [ 5.9  ,  0.645,  3.57 , 10.2  ,  5.   ],
       [ 6.   ,  0.31 ,  3.39 , 11.   ,  6.   ]])

2.4 数组拼接方法

使用hstack拼接新列

假设我们计划引入一种新的葡萄酒品质打分方法,同时保留原来的品质得分,则我们要添加一列新值在原始数组后。

假设初始的新得分都为零值,我们使用zeros生成一列长度为1599,值全为0的数组。

In [27]:
np.zeros((1599,1))
Out[27]:
array([[0.],
       [0.],
       [0.],
       ...,
       [0.],
       [0.],
       [0.]])

再使用hstack将这一列拼接在原来的数组后。

In [28]:
np.hstack((wines1,np.zeros((1599,1))))
Out[28]:
array([[ 7.4  ,  0.7  ,  3.51 ,  9.4  ,  5.   ,  0.   ],
       [ 7.8  ,  0.88 ,  3.2  ,  9.8  ,  5.   ,  0.   ],
       [ 7.8  ,  0.76 ,  3.26 ,  9.8  ,  5.   ,  0.   ],
       ...,
       [ 6.3  ,  0.51 ,  3.42 , 11.   ,  6.   ,  0.   ],
       [ 5.9  ,  0.645,  3.57 , 10.2  ,  5.   ,  0.   ],
       [ 6.   ,  0.31 ,  3.39 , 11.   ,  6.   ,  0.   ]])

使用vstack拼接新行

另一种情况,假设有一种新的红葡萄酒的各指标数据,我们要将其拼接在原始数组后成为新的一行。

同样假设新的数据为零值,我们使用zeros生成一行长度为5,值全为零的数组。

In [29]:
np.zeros((1,5))
Out[29]:
array([[0., 0., 0., 0., 0.]])

再使用vstack将这一行拼接在原来的数据后

In [30]:
np.vstack((wines1,np.zeros((1,5))))
Out[30]:
array([[ 7.4  ,  0.7  ,  3.51 ,  9.4  ,  5.   ],
       [ 7.8  ,  0.88 ,  3.2  ,  9.8  ,  5.   ],
       [ 7.8  ,  0.76 ,  3.26 ,  9.8  ,  5.   ],
       ...,
       [ 5.9  ,  0.645,  3.57 , 10.2  ,  5.   ],
       [ 6.   ,  0.31 ,  3.39 , 11.   ,  6.   ],
       [ 0.   ,  0.   ,  0.   ,  0.   ,  0.   ]])

使用concatenate进行拼接

使用concatenate可以实现hstackvstack的功能,只需要通过调整参数axis即可。设定axis为1,将拼接新列,等同于hstack

In [31]:
np.concatenate((wines1,np.zeros((1599,1))),axis=1)
Out[31]:
array([[ 7.4  ,  0.7  ,  3.51 ,  9.4  ,  5.   ,  0.   ],
       [ 7.8  ,  0.88 ,  3.2  ,  9.8  ,  5.   ,  0.   ],
       [ 7.8  ,  0.76 ,  3.26 ,  9.8  ,  5.   ,  0.   ],
       ...,
       [ 6.3  ,  0.51 ,  3.42 , 11.   ,  6.   ,  0.   ],
       [ 5.9  ,  0.645,  3.57 , 10.2  ,  5.   ,  0.   ],
       [ 6.   ,  0.31 ,  3.39 , 11.   ,  6.   ,  0.   ]])

设定axis为0,按拼接新行,等同于vstack

In [32]:
np.concatenate((wines1,np.zeros((1,5))),axis=0)
Out[32]:
array([[ 7.4  ,  0.7  ,  3.51 ,  9.4  ,  5.   ],
       [ 7.8  ,  0.88 ,  3.2  ,  9.8  ,  5.   ],
       [ 7.8  ,  0.76 ,  3.26 ,  9.8  ,  5.   ],
       ...,
       [ 5.9  ,  0.645,  3.57 , 10.2  ,  5.   ],
       [ 6.   ,  0.31 ,  3.39 , 11.   ,  6.   ],
       [ 0.   ,  0.   ,  0.   ,  0.   ,  0.   ]])

无论是使用vstackhstack还是concatenate进行拼接都要注意,传入的数组必须在指定轴上有相同的维度。例如,要拼接新行到葡萄酒数据中,新的数据必须也为5列,行数上则没有要求。同理,若要拼接新列到葡萄酒数据中,则新数据的行数必须为1599,列数上则没有要求。

2.5 数组计算

单列运算

当我们对某个数组和值进行基本数学运算时(如$+$、$-$、$*$、$/$),NumPy会将该运算应用于数组中的每个元素。例如,我们认为葡萄酒数据中的品质得分普遍偏低,我们打算将所有葡萄酒的品质得分加10。

In [33]:
wines1[:,4] + 10
Out[33]:
array([15., 15., 15., ..., 16., 15., 16.])

这不会对原始数据进行修改,如果要直接修改原始数据,可以使用$+=$运算。

In [34]:
wines1[:,4] += 10
wines1[:,4]
Out[34]:
array([15., 15., 15., ..., 16., 15., 16.])

与之对应,我们也可以使用$*=、/=、-=$。例如我们想将红葡萄酒的品质得分再翻倍。

In [35]:
wines1[:,4] *= 2
wines1[:,4]
Out[35]:
array([30., 30., 30., ..., 32., 30., 32.])

多列运算

也可以在数组之间进行数学运算,这会将操作应用于对应元素。例如,还是想将红葡萄酒的品质得分翻倍,也可以使用品质得分列加品质得分列实现。

In [36]:
wines1[:,4] + wines1[:,4]
Out[36]:
array([60., 60., 60., ..., 64., 60., 64.])

假设我们想要选择最大化酒精含量和品质的葡萄酒(我们想喝醉,但我们想喝好酒)。我们可以计算各葡萄酒酒精度数乘以品质得分的值。

In [37]:
wines1[:,3] * wines1[:,4]
Out[37]:
array([282., 294., 294., ..., 352., 306., 352.])

NumPy数组方法

除算术运算之外,NumPy提供了许多方法来解决数组中更复杂的计算。例如sumstdmeanminmax等。例如,我们想计算所有品质得分的平均数。

In [38]:
wines1[:,4].mean()
Out[38]:
31.27204502814259

计算品质得分数据的标准差。

In [39]:
wines1[:,4].std()
Out[39]:
1.6146337539279025

我们也可以计算整个数组的标准差。

In [40]:
wines1.std()
Out[40]:
10.897918530277742

但这样的计算并没有实际意义,我们可能更想了解每一个变量对应数据的标准差,可以通过设定参数axis为0来实现。

In [41]:
wines1.std(axis=0)
Out[41]:
array([1.7405518 , 0.1790037 , 0.15433818, 1.0653343 , 1.61463375])

数组比较

NumPy可以使用比较操作(如$<、>、> =、<=和==$)测试是否匹配某些值。例如,我们想要查看哪些葡萄酒的酒精度数高于10。

In [42]:
wines1[:,3]>10
Out[42]:
array([False, False, False, ...,  True,  True,  True])

我们得到一个布尔数组,告诉我们哪种葡萄酒的酒精度数大于10。我们可以使用sum统计有多少葡萄酒的酒精度数高于10。

In [43]:
a = wines1[:,3]>10
a.sum()
Out[43]:
852

使用布尔索引筛选子集

我们可以使用这个布尔数组作为索引,查看酒精度数高于10的葡萄酒数据。

In [44]:
wines1[wines1[:,3]>10]
Out[44]:
array([[ 7.5  ,  0.5  ,  3.35 , 10.5  , 30.   ],
       [ 7.5  ,  0.5  ,  3.35 , 10.5  , 30.   ],
       [ 8.5  ,  0.28 ,  3.3  , 10.5  , 34.   ],
       ...,
       [ 6.3  ,  0.51 ,  3.42 , 11.   , 32.   ],
       [ 5.9  ,  0.645,  3.57 , 10.2  , 30.   ],
       [ 6.   ,  0.31 ,  3.39 , 11.   , 32.   ]])