利用Python进行数据分析——NumPy(1)

IPython基础

IPython的开发者吸收了标准解释器的基本概念,在此基础上进行了大量的改进,创造出一个令人惊奇的工具。在它的主页上是这么说的:“这是一个增强的交互式Python shell。”具有tab补全,对象自省,强大的历史机制,内嵌的源代码编辑,集成Python调试器,%run机制,宏,创建多个环境以及调用系统shell的能力。

不过我们使用Pycharm,所以跳过学习。

NumPy

简单来看看一个例子:

from numpy.random import randn
import numpy as np
data={i:randn() for i in range(7)}
print(data[])

注:from numpy.random import randn需要写在import numpy as np的前面

注:{}表示字典[]表示数组()表示元组

NumPy(Numerical Python的简称)是高性能科学计算和数据分析的基础包。它是本书所介绍的几乎所有高级工具的构建基础。其部分功能如下:

  1. ndarray,一个具有矢量算术运算和复杂广播能力的快速且节省
    空间的多维数组。
  2. 用于对整组数据进行快速运算的标准数学函数(无需编写循
    环)。
  3. 用于读写磁盘数据的工具以及用于操作内存映射文件的工具。
  4. 线性代数、随机数生成以及傅里叶变换功能。
  5. 用于集成由C、C++、Fortran等语言编写的代码的工具。

最后一点也是从生态系统角度来看最重要的一点。由于NumPy提供了一个简单易用的C API,因此很容易将数据传递给由低级语言编写的外部库,外部库也能以NumPy数组的形式将数据返回给Python。这个功能使Python成为一种包装C/C++/Fortran历史代码库的选择,并使被包装库拥有一个动态的、易用的接口。

NumPy本身并没有提供多么高级的数据分析功能,理解NumPy数组以及面向数组的计算将有助于你更加高效地使用诸如pandas之类的工具。

NumPy的ndarray:一种多维数组对象

NumPy最重要的一个特点就是其N维数组对象(即ndarray),该对象是一个快速而灵活的大数据集容器。你可以利用这种数组对整数据执行一些数学运算,其语法跟标量元素之间的运算一样:

data=np.array([[0.9526, -0.246 , -0.8856],[0.5639, 0.2379, 0.9104]])
print(data)
print(data+data)
print(data*10)

output:

[[ 0.9526 -0.246  -0.8856]
 [ 0.5639  0.2379  0.9104]]
[[ 1.9052 -0.492  -1.7712]
 [ 1.1278  0.4758  1.8208]]
[[ 9.526 -2.46  -8.856]
 [ 5.639  2.379  9.104]]

ndarray是一个通用的同构数据多维容器,也就是说,其中的所有元素必须是相同类型的。每个数组都有一个shape(一个表示各维度大小的元组)和一个dtype(一个用于说明数组数据类型的对象):

print(data.shape)
print(data.dtype)

output:

(2, 3)
float64

创建ndarray

创建数组最简单的办法就是使用array函数。它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的NumPy数组。以一个列表的转换为例:

import numpy as np
data1=[6,7,5,8,0,1]
arr1=np.array(data1)
print(arr1)

嵌套序列(比如由一组等长列表组成的列表)将会被转换为一个多维数组:

import numpy as np
data1=[[1, 2, 3, 4], [5, 6, 7, 8]]
arr1=np.array(data1)
print(arr1)

除非显式说明(稍后将会详细介绍),np.array会尝试为新建的这个数组推断出一个较为合适的数据类型。数据类型保存在一个特殊的dtype对象中。


除np.array之外,还有一些函数也可以新建数组。比如,zeros和ones分别可以创建指定长度或形状的全0或全1数组。empty可以创建一个没有任何具体值的数组。要用这些方法创建多维数组,只需传入一个表示形状的元组即可:

arr2=np.zeros((3,6),float)
print(arr2)

arr3=np.zeros(10)
print(arr3)

arr4=np.ones(1,int)
print(arr4)

arr5=np.empty((2,3,2))  #2个 3x2的数组
print(arr5)

output:

[[0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0.]]
 
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

[1]

[[[6.23042070e-307 3.56043053e-307]
  [1.37961641e-306 1.22382967e-307]
  [8.01097889e-307 1.78020169e-306]]
 [[7.56601165e-307 1.02359984e-306]
  [1.33510679e-306 2.22522597e-306]
  [6.23053614e-307 8.91253127e-313]]]

认为np.empty会返回全0数组的想法是不安全的。很多情况下(如前所示),它返回的都是一些未初始化的垃圾值。

.ndim :维度
.shape :各维度的尺度 (2,5)
.size :元素的个数 10
.dtype :元素的类型 dtype(‘int32’)
.itemsize :每个元素的大小,以字节为单位 ,每个元素占4个字节
ndarray数组的创建
np.arange(n) ; 元素从0到n-1的ndarray类型
np.ones(shape): 生成全1
np.zeros((shape), dtype = np.int32) : 生成int32型的全0
np.full(shape, val): 生成全为val
np.eye(n) : 生成单位矩阵

np.ones_like(a) : 按数组a的形状生成全1的数组
np.zeros_like(a): 同理
np.full_like (a, val) : 同理

np.linspace(1,10,4): 根据起止数据等间距地生成数组
np.linspace(1,10,4, endpoint = False):endpoint 表示10是否作为生成的元素
np.concatenate():


ndarray的数据类型

bool 用一位存储的布尔类型(值为TRUE或FALSE)
inti 由所在平台决定其精度的整数(一般为int32或int64)
int8 整数,范围为128至127
int16 整数,范围为32 768至32 767
int32 整数,范围为231至231 1
int64 整数,范围为263至263 1
uint8 无符号整数,范围为0至255
uint16 无符号整数,范围为0至65 535
uint32 无符号整数,范围为0至2321
uint64 无符号整数,范围为0至2641
float16 半精度浮点数(16位):其中用1位表示正负号,5位表示指数,10位表示尾数
float32 单精度浮点数(32位):其中用1位表示正负号,8位表示指数,23位表示尾数
float64或float 双精度浮点数(64位):其中用1位表示正负号,11位表示指数,52位表示尾数
complex64 复数,分别用两个32位浮点数表示实部和虚部
complex128或complex 复数,分别用两个64位浮点数表示实部和虚部

你可以通过ndarray的astype方法显式地转换其dtype:

import numpy as np
arr = np.array([1, 2, 3, 4, 5])
float_arr=arr.astype(np.float64)
print(float_arr.dtype)

在本例中,整数被转换成了浮点数。如果将浮点数转换成整数,则小数部分将会被截断。
如果某字符串数组表示的全是数字,也可以用astype将其转换为数值形式。如果转换过程因为某种原因而失败了(比如某个不能被转换为float64的字符串),就会引发一个TypeError。

numeric_strings = np.array(['1.2','2.4'], dtype=np.string_)
numeric_strings.astype(float)

数组和标量之间的运算
数组很重要,因为它使你不用编写循环即可对数据执行批量运算。这通常就叫做矢量化(vectorization)。大小相等的数组之间的任何算术运算都会将运算应用到元素级:
利用Python进行数据分析——NumPy(1)_第1张图片
利用Python进行数据分析——NumPy(1)_第2张图片
在这里插入图片描述


基本的索引和切片
NumPy数组的索引是一个内容丰富的主题,因为选取数据子集或单个元素的方式有很多。一维数组很简单。从表面上看,它们跟Python列表的功能差不多:

In [51]: arr = np.arange(10)
In [52]: arr
Out[52]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [53]: arr[5]
Out[53]: 5
In [54]: arr[5:8]
Out[54]: array([5, 6, 7])
In [55]: arr[5:8] = 12
In [56]: arr
Out[56]: array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])

如上所示,当你将一个标量值赋值给一个切片时(如arr[5:8]=12),该值会自动传播(也就说后面将会讲到的“广播”)到整个选区。跟列表最重要的区别在于,数组切片是原始数组的视图。这意味着数据不会被复制,视图上的任何修改都会直接反映到源数组上:

In [57]: arr_slice = arr[5:8]
In [58]: arr_slice[1] = 12345
In [59]: arr
Out[59]: array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8, 9])
In [60]: arr_slice[:] = 64
In [61]: arr
Out[61]: array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])

如果你想要得到的是ndarray切片的一份副本而非视图,就需要显式地进行复制操作,例如arr[5:8].copy():

arr=np.array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8, 9])
print(arr)
arr_slice=arr[5:8].copy()
arr_slice[:]=1
print(arr_slice)
print(arr)

output

[    0     1     2     3     4    12 12345    12     8     9]
[1 1 1]
[    0     1     2     3     4    12 12345    12     8     9]

对于高维度数组,能做的事情更多。在一个二维数组中,各索引位置上的元素不再是标量而是一维数组:

In [62]: arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
In [63]: arr2d[2]
Out[63]: array([7, 8, 9])

因此,可以对各个元素进行递归访问,但这样需要做的事情有点多。你可以传入一个以逗号隔开的索引列表来选取单个元素。也就是说,下面两种方式是等价的:

In [64]: arr2d[0][2]
Out[64]: 3
In [65]: arr2d[0, 2]
Out[65]: 3

切片索引
ndarray的切片语法跟Python列表这样的一维对象差不多:

In [75]: arr[1:6]
Out[75]: array([ 1, 2, 3, 4, 64])

高维度对象的花样更多,你可以在一个或多个轴上进行切片,也可以跟整数索引混合使用。对于上面那个二维数组arr2d,其切片方式稍显不同:
利用Python进行数据分析——NumPy(1)_第3张图片
可以看出,它是沿着第0轴(即第一个轴)切片的。也就是说,切片是沿着一个轴向选取元素的。你可以一次传入多个切片,就像传入多个索引那样:

In [78]: arr2d[:2, 1:]
Out[78]:
array([[2, 3],
[5, 6]])

像这样进行切片时,只能得到相同维数的数组视图。通过将整数索引和切片混合,可以得到低维度的切片:
在这里插入图片描述
注意,只有单独一个:的时候表示选取整个轴,因此你可以像下面这样只对高维轴进行切片:

In [81]: arr2d[:, :1]
Out[81]:
array([[1],
[4],
[7]])

布尔型索引

来看这样一个例子,假设我们有一个用于存储数据的数组以及一个存储姓名的数组(含有重复项)。在这里,我将使用numpy.random中的randn函数生成一些正态分布的随机数据。
假设每个名字都对应data数组中的一行,而我们想要选出对应于名字"Bob"的所有行。跟算术运算一样,数组的比较运算(如==)也是矢量化的。因此,对names和字符串"Bob"的比较运算将会产生一个布尔型数组:

import numpy as np
names=np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe','Joe'])
data=np.random.randn(7,4)
print(names)
print(data)
print(names=='Bob')
print(data[names=='Bob'])

output:

['Bob' 'Joe' 'Will' 'Bob' 'Will' 'Joe' 'Joe']

[[ 1.04800916 -2.43798919 -0.80548374 -0.22321222]
 [-2.51647025 -0.85169887 -0.2588378  -0.37398974]
 [ 1.19123641  0.62080487  0.85106681 -0.24398211]
 [ 1.39354191  0.98470156  0.3815374  -0.08218244]
 [-0.58025068 -1.6809215   0.77786144  0.48330294]
 [ 0.276453   -1.33200014 -0.41610978  0.07494557]
 [ 1.45946492 -1.75151171  0.32370049 -0.47419905]]
 
[ True False False  True False False False]

[[ 1.04800916 -2.43798919 -0.80548374 -0.22321222]
 [ 1.39354191  0.98470156  0.3815374  -0.08218244]]

布尔型数组的长度必须跟被索引的轴长度一致。此外,还可以将布尔型数组跟切片、整数(或整数序列,稍后将对此进行详细讲解)混合使用:

In [89]: data[names == 'Bob', 2:]
Out[89]:
array([[-0.2349, 1.2792],
[-0.0523, 0.0672]])
In [90]: data[names == 'Bob', 3]
Out[90]: array([ 1.2792, 0.0672])

要选择除"Bob"以外的其他值,既可以使用不等于符号(!=),也可以通过负号(-)对条件进行否定:

In [91]: names != 'Bob'
Out[91]: array([False, True, True, False, True, True, True], dtype=bool)
In [92]: data[-(names == 'Bob')]
Out[92]:
array([[-0.268 , 0.5465, 0.0939, -2.0445],
[-0.047 , -2.026 , 0.7719, 0.3103],
[-1.0023, -0.1698, 1.1503, 1.7289],
[ 0.1913, 0.4544, 0.4519, 0.5535],
[ 0.5994, 0.8174, -0.9297, -1.2564]])

选取这三个名字中的两个需要组合应用多个布尔条件,使用&(和)、|(或)之类的布尔算术运算符即可:

In [93]: mask = (names == 'Bob') | (names == 'Will')
In [94]: mask
Out[94]: array([True, False, True, True, True, False, False], dtype=bool)
In [95]: data[mask]
Out[95]:
array([[-0.048 , 0.5433, -0.2349, 1.2792],
[-0.047 , -2.026 , 0.7719, 0.3103],
[ 2.1452, 0.8799, -0.0523, 0.0672],
[-1.0023, -0.1698, 1.1503, 1.7289]])

通过布尔型数组设置值是一种经常用到的手段。为了将data中的所有负值都设置为0,我们只需:

In [96]: data[data < 0] = 0
In [97]: data
Out[97]:
array([[ 0. , 0.5433, 0. , 1.2792],
[ 0. , 0.5465, 0.0939, 0. ],
[ 0. , 0. , 0.7719, 0.3103],
[ 2.1452, 0.8799, 0. , 0.0672],
[ 0. , 0. , 1.1503, 1.7289],
[ 0.1913, 0.4544, 0.4519, 0.5535],
[ 0.5994, 0.8174, 0. , 0. ]])

通过一维布尔数组设置整行或列的值也很简单:

In [98]: data[names != 'Joe'] = 7
In [99]: data
Out[99]:
array([[ 7. , 7. , 7. , 7. ],
[ 0. , 0.5465, 0.0939, 0. ],
[ 7. , 7. , 7. , 7. ],
[ 7. , 7. , 7. , 7. ],
[ 7. , 7. , 7. , 7. ],
[ 0.1913, 0.4544, 0.4519, 0.5535],
[ 0.5994, 0.8174, 0. , 0. ]])

花式索引
花式索引(Fancy indexing)是一个NumPy术语,它指的是利用整数数组进行索引。假设我们有一个8×4数组:

import numpy as np
arr=np.empty((8,4),np.int32)
for i in range(8):
    arr[i]=i
print(arr)

output

[[0 0 0 0]
 [1 1 1 1]
 [2 2 2 2]
 [3 3 3 3]
 [4 4 4 4]
 [5 5 5 5]
 [6 6 6 6]
 [7 7 7 7]]

为了以特定顺序选取行子集,只需传入一个用于指定顺序的整数列表或ndarray即可:(这里是传入的顺序,并不是值)

In [103]: arr[[4, 3, 0, 6]] #这里是两个[]
Out[103]:
array([[ 4., 4., 4., 4.],
[ 3., 3., 3., 3.],
[ 0., 0., 0., 0.],
[ 6., 6., 6., 6.]])

使用负数索引将会从末尾开始选取行:

In [104]: arr[[-3, -5, -7]]
Out[104]:
array([[ 5., 5., 5., 5.],
[ 3., 3., 3., 3.],
[ 1., 1., 1., 1.]])

一次传入多个索引数组会有一点特别。它返回的是一个一维数组,其中的元素对应各个索引元组:

arr=np.arange(32).reshape((8, 4))
print(arr)
print(arr[[1,5,7,2],[0,3,1,2]])

output:

[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]
 [20 21 22 23]
 [24 25 26 27]
 [28 29 30 31]]
 
[ 4 23 29 10]

我们来看看具体是怎么一回事。最终选出的是元素(1,0)、(5,3)、(7,1)和(2,2)。这个花式索引的行为可能会跟某些用户的预期不一样(包括我在内),选取矩阵的行列子集应该是矩形区域的形式才对。下面是得到该结果的一个办法:

In [108]: arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]]
Out[108]:
array([[ 4, 7, 5, 6],
[20, 23, 21, 22],
[28, 31, 29, 30],
[ 8, 11, 9, 10]])

另外一个办法是使用np.ix_函数,它可以将两个一维整数数组转换为一个用于选取方形区域的索引器:

In [109]: arr[np.ix_([1, 5, 7, 2], [0, 3, 1, 2])]
Out[109]:
array([[ 4, 7, 5, 6],
[20, 23, 21, 22],
[28, 31, 29, 30],
[ 8, 11, 9, 10]])

记住,花式索引跟切片不一样,它总是将数据复制到新数组中。


数组转置和轴对换

转置(transpose)是重塑的一种特殊形式,它返回的是源数据的视图(不会进行任何复制操作)。数组不仅有transpose方法,还有一个特殊的T属性:

In [110]: arr = np.arange(15).reshape((3, 5))
In [111]: arr
Out[111]:
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
In [112]: arr.T
Out[112]:
array([[ 0, 5, 10],
[ 1, 6, 11],
[ 2, 7, 12],
[ 3, 8, 13],
[ 4, 9, 14]])

在进行矩阵计算时,经常需要用到该操作,比如利用np.dot计算矩阵内积XTX:

In [113]: arr = np.random.randn(6, 3)
In [114]: np.dot(arr.T, arr)
Out[114]:
array([[ 2.584 , 1.8753, 0.8888],
[ 1.8753, 6.6636, 0.3884],
[ 0.8888, 0.3884, 3.9781]])

对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置(比较费脑子):

In [115]: arr = np.arange(16).reshape((2, 2, 4))
In [116]: arr
Out[116]:
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
In [117]: arr.transpose((1, 0, 2))
Out[117]:
array([[[ 0, 1, 2, 3],
[ 8, 9, 10, 11]],
[[ 4, 5, 6, 7],
[12, 13, 14, 15]]])

简单的转置可以使用.T,它其实就是进行轴对换而已。ndarray还有一个swapaxes方法,它需要接受一对轴编号:

In [118]: arr
Out[118]:
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
In [119]: arr.swapaxes(1, 2)
Out[119]:
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]],
[[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])

swapaxes也是返回源数据的视图(不会进行任何复制操作)。

你可能感兴趣的:(Python数据分析)