利用Python进行数据分析

Python for Data Analysis, 3E作者原书电子版

pydata-book 作者的github页面(有ipynb文件供学习)

NumPy documentation Numpy官方文档

pandas documentation pandas官方文档

numpy

NumPy之于数值计算特别重要的原因之一,是因为它可以高效处理大数组的数据。这是因为:

  • NumPy是在一个连续的内存块中存储数据,独立于其他Python内置对象。NumPy的C语言编写的算法库可以操作内存,而不必进行类型检查或其它前期工作。比起Python的内置序列,NumPy数组使用的内存更少。
  • NumPy可以在整个数组上执行复杂的计算,而不需要Python的for循环。

ndarray

NumPy最重要的一个特点就是其N维数组对象(即ndarray),该对象是一个快速而灵活的大数据集容器。

创建 ndarray

array()array函数接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的NumPy数组。嵌套序列(比如由一组等长列表组成的列表)将会被转换为一个多维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> a = np.array(1,2,3,4)    # WRONG
>>> a = np.array([1,2,3,4]) # RIGHT

>>> b = np.array([(1.5,2,3), (4,5,6)])
>>> b
array([[ 1.5, 2. , 3. ],
[ 4. , 5. , 6. ]])


>>> c = np.array( [ [1,2], [3,4] ], dtype=complex )
>>> c
array([[ 1.+0.j, 2.+0.j],
[ 3.+0.j, 4.+0.j]])

np.array会尝试为新建的这个数组推断出一个较为合适的数据类型,或者在创建时显式指定数据类型。数据类型保存在一个特殊的dtype对象中。

函数zeros创建一个由0组成的数组,函数 ones创建一个完整的数组,函数empty 创建一个数组,其初始内容是随机的,取决于内存的状态。默认情况下,创建的数组的dtype是 float64 类型的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> np.zeros( (3,4) )
array([[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.]])
>>> np.ones( (2,3,4), dtype=np.int16 ) # dtype can also be specified
array([[[ 1, 1, 1, 1],
[ 1, 1, 1, 1],
[ 1, 1, 1, 1]],
[[ 1, 1, 1, 1],
[ 1, 1, 1, 1],
[ 1, 1, 1, 1]]], dtype=int16)
>>> np.empty( (2,3) ) # uninitialized, output may vary
array([[ 3.73603959e-262, 6.02658058e-154, 6.55490914e-260],
[ 5.30498948e-313, 3.14673309e-307, 1.00000000e+000]])

arange可以创建一个在定义的起始值和结束值之间具有特定序列的数组。

1
2
3
4
>>> np.arange( 10, 30, 5 )
array([10, 15, 20, 25])
>>> np.arange( 0, 2, 0.3 ) # it accepts float arguments
array([ 0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8])

使用arange默认输出一维数组,要创建其他形状的数组,用reshape函数:

1
2
3
4
5
6
7
>>> array = np.arange(20).reshape(4,5)
>>> array

array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])

其他创建数组的函数

full函数创建一个填充给定值的n * n数组。

1
np.full((2,2), 3)

输出:

1
2
array([[3, 3],
[3, 3]])

eye函数可以创建一个n * n矩阵,对角线为1s,其他为0。

1
np.eye(3,3)

输出:

1
2
3
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])

函数linspace在指定的时间间隔内返回均匀间隔的数字。 例如,下面的函数返回0到10之间的四个等间距数字。

1
np.linspace(0, 10, num=4)

输出:

1
array([ 0., 3.33333333, 6.66666667, 10.])

dtype

dtype(数据类型)是一个特殊的对象,它含有ndarray将一块内存解释为特定数据类型所需的信息,描述了如何解释与数组项对应的固定大小的内存块中的字节

它描述了数据的以下几个方面:

  1. 数据类型(整型、浮点型、Python对象等)。
  2. 数据的大小(例如整数中有多少字节)。
  3. 数据的顺序(little-endian big-endian )。
  4. 如果数据类型是结构化数据类型,则是其他数据类型的集合(例如,描述由整数和浮点数组成的数组项)。
    1. 结构的 “字段 ” 的名称是什么,通过这些名称可以访问它们。
    2. 每个 字段 的数据类型是什么,以及
    3. 每个字段占用内存块的哪一部分。
  5. 如果数据类型是子数组,那么它的形状和数据类型是什么。

可以通过ndarray的astype方法明确地将一个数组从一个dtype转换成另一个dtype

1
2
3
4
5
6
7
8
9
In [37]: arr = np.array([1, 2, 3, 4, 5])

In [38]: arr.dtype
Out[38]: dtype('int64')

In [39]: float_arr = arr.astype(np.float64)

In [40]: float_arr.dtype
Out[40]: dtype('float64')

运算

大小相等的数组之间的任何算术运算都会将运算应用到元素级:

index

对于一维数组,和 python 列表差别不大

在多维数组中,如果省略了后面的索引,则返回对象会是一个维度低一点的ndarray(它含有高一级维度上的所有数据)。

在2×2×3数组arr3d中:

1
2
3
4
5
6
7
8
In [76]: arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])

In [77]: arr3d
Out[77]:
array([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]]])

arr3d[0]是一个2×3数组:

1
2
3
4
In [78]: arr3d[0]
Out[78]:
array([[1, 2, 3],
[4, 5, 6]])

相似的,arr3d[1,0]可以访问索引以(1,0)开头的那些值(以一维数组的形式返回):

1
2
In [84]: arr3d[1, 0]
Out[84]: array([7, 8, 9])

slice

  • 对于一维数组,和 python 列表差别不大
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
In [60]: arr = np.arange(10)

In [61]: arr
Out[61]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [62]: arr[5]
Out[62]: 5

In [63]: arr[5:8]
Out[63]: array([5, 6, 7])

In [64]: arr[5:8] = 12

In [65]: arr
Out[65]: array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])

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

以下是示例:

1
2
3
4
5
6
7
8
9
In [66]: arr_slice = arr[5:8]

In [67]: arr_slice
Out[67]: array([12, 12, 12])

In [68]: arr_slice[1] = 12345

In [69]: arr
Out[69]: array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8, 9])

切片[:]会给数组中的所有值赋值

1
2
3
4
In [70]: arr_slice[:] = 64

In [71]: arr
Out[71]: array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])

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