1.1 数据分析简介
当今世界对信息技术的依赖程度日渐加深,每天都会产生和存储海量的数据。数据的来源多种多样一自 动检测系统、传感器和科学仪器等。不知你有没有意识到,你每次从银行取钱、买东西、写博客、发微博也会产生新的数据。
什么是数据呢?数据实际上不同于信息,至少在形式上不一样。对于没有任何形式可言的字节流,除了其数量、用词和发送的时间外,其他一无所知, 一眼看,上去,很难理解其本质。信息实际上是对数据集进行处理,从中提炼出可用于其他场合的结论,也就是说,它是对数据进行处理后得到的结果。从原始数据中抽取信息的这个过程叫作数据分析。
数据分析的目的正是抽取不易推断的信息,而一旦理解了这些信息,就能够对产生数据的系统的运行机制进行研究,从而对系统可能的响应和演变作出预测。
数据分析最初用作数据保护,现已发展成为数据建模的方法论,从而完成了到一门真正学科的蜕变。模型实际上是指将所研究的系统转化为数学形式。一.旦建立数学或逻辑模型,对系统的响应能作出不同精度的预测,我们]就可以预测在给定输人的情况下,系统会给出怎样的输出。这样看来,数据分析的目标不止于建模,更重要的是其预测能力。
模型的预测能力不仅取决于建模技术的质量,还取决于选择供分析用的优质数据集的能力。因此数据搜寻、数据提取和数据准备等预处理工作也属于数据分析的范畴,它们对最终结果有重要影响。
到现在为止,我们一直在讲数据、数据的准备及数据处理。在数据分析的各个阶段,还有各种各样的数据可视化方法。无论是孤立地看数据,还是将其放到整个数据集来看,理解数据的最好方法莫过于将其做成可视化图形,从而传达出数字中蕴含(有时是隐藏着)的信息。到目前为止,已经有很多可视化模式:类型多样的图表。
数据分析的产出为模型和图形化展示,据此可预测所研究系统的响应;随后进人测试阶段,用已知输出结果的一个数据集对模型进行测试。这些数据不是用来生成模型的,而是用来检验系统能否重现实际观察到的输出,从而掌握模型的误差,了解其有效性和局限。
拿新模型的测试结果与既有模型进行对比便可知优劣。如新模型胜出,即可进行数据分析的最后一步:部署。部署阶段需要根据模型给出的预测结果,实现相应的决策,同时还要防范模型预测到的潜在风险。
很多工作都离不开数据分析。了解数据分析及实际操作方法,对工作中做出可靠决策大有裨益。有了它,人们可以检验假说,加深对系统的理解。
1.2NumPy简介
NumPy(Numerical Python的简称)是Python数值计算最重要的基础包。大多数提供科学计算的包都是用NumPy的数组作为构建基础。
NumPy的部分功能如下:
- ndarray,一个具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组。
- 用于对整组数据进行快速运算的标准数学函数(无需编写循环)。
- 用于读写磁盘数据的工具以及用于操作内存映射文件的工具。
- 线性代数、随机数生成以及傅里叶变换功能。
- 用于集成由C、C++、Fortran等语言编写的代码的A C API。
由于NumPy提供了一个简单易用的C API,因此很容易将数据传递给由低级语言编写的外部库,外部库也能以NumPy数组的形式将数据返回给Python。这个功能使Python成为一种包装C/C++/Fortran历史代码库的选择,并使被包装库拥有一个动态的、易用的接口。
NumPy本身并没有提供多么高级的数据分析功能,理解NumPy数组以及面向数组的计算将有助于你更加高效地使用诸如pandas之类的工具。
对于大部分数据分析应用而言,最关注的功能主要集中在:
- 用于数据整理和清理、子集构造和过滤、转换等快速的矢量化数组运算。
- 常用的数组算法,如排序、唯一化、集合运算等。
- 高效的描述统计和数据聚合/摘要运算。
- 用于异构数据集的合并/连接运算的数据对齐和关系型数据运算。
- 将条件逻辑表述为数组表达式(而不是带有if-elif-else分支的循环)。
- 数据的分组运算(聚合、转换、函数应用等)。。
虽然NumPy提供了通用的数值数据处理的计算基础,但大多数人可能还是想将pandas作为统计和分析工作的基础,尤其是处理表格数据时。pandas还提供了一些NumPy所没有的领域特定的功能,如时间序列处理等。
笔记:Python的面向数组计算可以追溯到1995年,Jim Hugunin创建了Numeric库。接下来的10年,许多科学编程社区纷纷开始使用Python的数组编程,但是进入21世纪,库的生态系统变得碎片化了。2005年,Travis Oliphant从Numeric和Numarray项目整合出了NumPy项目,进而所有社区都集合到了这个框架下。
1.3 高效处理大数组的数据
NumPy之于数值计算特别重要的原因之一,是因为它可以高效处理大数组的数据。这是因为:
- NumPy是在一个连续的内存块中存储数据,独立于其他Python内置对象。NumPy的C语言编写的算法库可以操作内存,而不必进行类型检查或其它前期工作。比起Python的内置序列,NumPy数组使用的内存更少。
- NumPy可以在整个数组上执行复杂的计算,而不需要Python的for循环。
要搞明白具体的性能差距,考察一个包含一百万整数的数组,和一个等价的Python列表:
import numpy as np
my_arr = np.arange(1000000)
my_list = list(range(1000000))
%time来测试语句运行的时间
各个序列分别乘以2:
%time for _ in range(10): my_arr2 = my_arr * 2
输出结果:
CPU times: user 10.9 ms, sys: 8.45 ms, total: 19.3 ms
Wall time: 19.4 ms
%time for _ in range(10): my_list2 = [x * 2 for x in my_list]
输出结果:
CPU times: user 556 ms, sys: 156 ms, total: 712 ms
Wall time: 712 ms
基于NumPy的算法要比纯Python快10到100倍(甚至更快),并且使用的内存更少。
1.4引入惯例
Python社区已经广泛采取了一些常用模块的命名惯例:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
也就是说,当你看到np.arange时,就应该想到它引用的是NumPy中的arange函数。这样做的原因是:在Python软件开发过程中,不建议直接引入类似NumPy这种大型库的全部内容(from numpy import *)。
1.5阶阵列
ndarray:一种多维数组对象。
NumPy最重要的一个特点就是其N维数组对象(即ndarray),该对象是一个快速而灵活的大数据集容器。你可以利用这种数组对整块数据执行一些数学运算,其语法跟标量元素之间的运算一样。
要明白Python是如何利用与标量值类似的语法进行批次计算,我先引入NumPy,然后生成一个包含随机数据的小数组:
import numpy as np
# Generate some random data
data = np.random.randn(2, 3)
data
输出结果:
array([[-0.2047, 0.4789, -0.5194],
[-0.5557, 1.9658, 1.3934]])
然后进行数学运算:
data * 10
` `
输出结果:
array([[ -2.0471, 4.7894, -5.1944],
[ -5.5573, 19.6578, 13.9341]])
data + data
` `
输出结果:
array([[-0.4094, 0.9579, -1.0389],
[-1.1115, 3.9316, 2.7868]])
第一个例子中,所有的元素都乘以10。第二个例子中,每个元素都与自身相加。
笔记:在整个过程中,我会使用标准的NumPy惯用法。你当然也可以在代码中使用,但不建议这么做。的命名空间很大,包含许多函数,其中一些的名字与Python的内置函数重名(比如min和max)。import numpy as npfrom numpy import *numpy
ndarray是一个通用的同构数据多维容器,也就是说,其中的所有元素必须是相同类型的。每个数组都有一个shape(一个表示各维度大小的元组)和一个dtype(一个用于说明数组数据类型的对象):
data.shape
输出结果:
(2, 3)
data.dtype
输出结果:
dtype('float64')
笔记:当你在平台中看到"数组"、"NumPy数组"、"ndarray"时,基本上都指的是同一样东西,即ndarray对象。
1.5.1 创建参数
创建数组最简单的办法就是使用array函数。它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的NumPy数组。以一个列表的转换为例:
data1 = [6, 7.5, 8, 0, 1]
arr1 = np.array(data1)
arr1
输出结果:
array([ 6. , 7.5, 8. , 0. , 1. ])
嵌套序列(比如由一组等长列表组成的列表)将会被转换为一个多维数组:
data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
arr2 = np.array(data2)
arr2
` `
输出结果:
array([[1, 2, 3, 4],
[5, 6, 7, 8]])
因为data2是列表的列表,NumPy数组arr2的两个维度的shape是从data2引入的。可以用属性ndim和shape验证:
arr2.ndim
输出结果:
2
arr2.shape
输出结果:
(2, 4)
除非特别说明(稍后将会详细介绍),np.array会尝试为新建的这个数组推断出一个较为合适的数据类型。数据类型保存在一个特殊的dtype对象中。比如说,在上面的两个例子中,我们有:
arr1.dtype
输出结果:
dtype('float64')
arr2.dtype
输出结果:
dtype('int64')
除np.array之外,还有一些函数也可以新建数组。比如,zeros和ones分别可以创建指定长度或形状的全0或全1数组。empty可以创建一个没有任何具体值的数组。要用这些方法创建多维数组,只需传入一个表示形状的元组即可:
np.zeros(10)
输出结果:
array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
np.zeros((3, 6))
` `
输出结果:
array([[ 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0.]])
np.empty((2, 3, 2))
输出结果:
array([[[1.09385326e-303, 1.09402988e-303],
[9.12411861e-063, 7.78003736e-299],
[2.03712459e-019, 2.53873282e-207]],
[[6.83429278e-217, 9.39129707e-275],
[5.87391048e-294, 4.30940463e-159],
[3.23457677e-236, 8.01262336e-246]]])
注意:np.empty返回的是一些未初始化的垃圾值。
arange是Python内置函数range的数组版:
np.arange(15)
输出结果:
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
表1-1列出了一些数组创建函数。由于NumPy关注的是数值计算,因此,如果没有特别指定,数据类型基本都是float64(浮点数)。

1.5.2 ndarray的数据类型
dtype(数据类型)是一个特殊的对象,它含有ndarray将一块内存解释为特定数据类型所需的信息:
arr1 = np.array([1, 2, 3], dtype=np.float64)
arr2 = np.array([1, 2, 3], dtype=np.int32)
arr1.dtype
输出结果:
dtype('float64')
arr2.dtype
输出结果:
dtype('int32')
dtype是NumPy灵活交互其它系统的源泉之一。多数情况下,它们直接映射到相应的机器表示,这使得"读写磁盘上的二进制数据流"以及"集成低级语言代码(如C、Fortran)"等工作变得更加简单。数值型dtype的命名方式相同:一个类型名(如float或int),后面跟一个用于表示各元素位长的数字。标准的双精度浮点值(即Python中的float对象)需要占用8字节(即64位)。因此,该类型在NumPy中就记作float64。表1-2列出了NumPy所支持的全部数据类型。
笔记:记不住这些NumPy的dtype也没关系,新手更是如此。通常只需要知道你所处理的数据的大致类型是浮点数、复数、整数、布尔值、字符串,还是普通的Python对象即可。当你需要控制数据在内存和磁盘中的存储方式时(尤其是对大数据集),那就得了解如何控制存储类型。

你可以通过ndarray的astype方法明确地将一个数组从一个dtype转换成另一个dtype:
arr = np.array([1, 2, 3, 4, 5])
arr.dtype
输出结果:
dtype('int64')
float_arr = arr.astype(np.float64)
float_arr.dtype
输出结果:
dtype('float64')
在本例中,整数被转换成了浮点数。如果将浮点数转换成整数,则小数部分将会被截取删除:
arr = np.array([3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
arr
输出结果:
array([ 3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
arr.astype(np.int32)
输出结果:
array([ 3, -1, -2, 0, 12, 10], dtype=int32)
如果某字符串数组表示的全是数字,也可以用astype将其转换为数值形式:
numeric_strings = np.array(['1.25', '-9.6', '42'], dtype=np.string_)
numeric_strings.astype(float)
输出结果:
array([ 1.25, -9.6 , 42. ])
注意:使用numpy.string_类型时,一定要小心,因为NumPy的字符串数据是大小固定的,发生截取时,不会发出警告。pandas提供了更多非数值数据的便利的处理方法。
如果转换过程因为某种原因而失败了(比如某个不能被转换为float64的字符串),就会引发一个ValueError。这里,偷了一点懒,写的是float而不是np.float64;NumPy很聪明,它会将Python类型映射到等价的dtype上。
数组的dtype还有另一个属性:
int_array = np.arange(10)
calibers = np.array([.22, .270, .357, .380, .44, .50], dtype=np.float64)
int_array.astype(calibers.dtype)
输出结果:
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
你还可以用简洁的类型代码来表示dtype:
empty_uint32 = np.empty(8, dtype='u4')
empty_uint32
输出结果:
array([ 0, 1075314688, 0, 1075707904, 0,
1075838976, 0, 1072693248], dtype=uint32)
笔记:调用astype总会创建一个新的数组(一个数据的备份),即使新的dtype与旧的dtype相同。
1.5.3 NumPy数组的运算
数组很重要,因为它使你不用编写循环即可对数据执行批量运算。NumPy用户称其为矢量化(vectorization)。大小相等的数组之间的任何算术运算都会将运算应用到元素级:
arr = np.array([[1., 2., 3.], [4., 5., 6.]])
arr
` `
输出结果:
array([[ 1., 2., 3.],
[ 4., 5., 6.]])
arr * arr
输出结果:
array([[ 1., 4., 9.],
[ 16., 25., 36.]])
arr - arr
输出结果:
array([[ 0., 0., 0.],
[ 0., 0., 0.]])
数组与标量的算术运算会将标量值传播到各个元素:
1 / arr
输出结果:
array([[ 1. , 0.5 , 0.3333],
[ 0.25 , 0.2 , 0.1667]])
arr ** 0.5
` `
输出结果:
array([[ 1. , 1.4142, 1.7321],
[ 2. , 2.2361, 2.4495]])
大小相同的数组之间的比较会生成布尔值数组:
arr2 = np.array([[0., 4., 1.], [7., 2., 12.]])
arr2
输出结果:
array([[ 0., 4., 1.],
[ 7., 2., 12.]])
arr2 > arr
输出结果:
array([[False, True, False],
[ True, False, True]])
2.1一维数组
NumPy数组的索引是一个内容丰富的主题,因为选取数据子集或单个元素的方式有很多。一维数组很简单。从表面上看,它们跟Python列表的功能差不多:
import numpy as np
arr = np.arange(10)
arr
输出结果:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr[5]
输出结果:
5
arr[5:8]
输出结果:
array([5, 6, 7])
arr[5:8] = 12
arr
输出结果:
array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])
如上所示,当你将一个标量值赋值给一个切片时(如arr[5:8]=12),该值会自动传播到整个选区。跟列表最重要的区别在于,数组切片是原始数组的视图。这意味着数据不会被复制,视图上的任何修改都会直接反映到源数组上。
作为例子,先创建一个arr的切片:
arr_slice = arr[5:8]
arr_slice
输出结果:
array([12, 12, 12])
现在,当我修稿arr_slice中的值,变动也会体现在原始数组arr中:
arr_slice[1] = 12345
arr
输出结果:
array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8,
9])
切片[ : ]会给数组中的所有值赋值:
arr_slice[:] = 64
arr
输出结果:
array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
如果你刚开始接触NumPy,可能会对此感到惊讶(尤其是当你曾经用过其他热衷于复制数组数据的编程语言)。由于NumPy的设计目的是处理大数据,所以你可以想象一下,假如NumPy坚持要将数据复制来复制去的话会产生何等的性能和内存问题。
注意:如果你想要得到的是ndarray切片的一份副本而非视图,就需要明确地进行复制操作,例如。arr[5:8].copy()
2.2二维数组
对于高维度数组,能做的事情更多。在一个二维数组中,各索引位置上的元素不再是标量而是一维数组:
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr2d[2]
输出结果:
array([7, 8, 9])
因此,可以对各个元素进行递归访问,但这样需要做的事情有点多。你可以传入一个以逗号隔开的索引列表来选取单个元素。也就是说,下面两种方式是等价的:
arr2d[0][2]
输出结果:
3
arr2d[0, 2]
输出结果:
3
图1-1说明了二维数组的索引方式。轴0作为行,轴1作为列。

在多维数组中,如果省略了后面的索引,则返回对象会是一个维度低一点的ndarray(它含有高一级维度上的所有数据)。因此,在2×2×3数组arr3d中:
arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
arr3d
` `
输出结果:
array([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]]])
arr3d[0]是一个2×3数组:
arr3d[0]
` `
输出结果:
array([[1, 2, 3],
[4, 5, 6]])
标量值和数组都可以被赋值给arr3d[0]:
old_values = arr3d[0].copy()
arr3d[0] = 42
arr3d
` `
输出结果:
array([[[42, 42, 42],
[42, 42, 42]],
[[ 7, 8, 9],
[10, 11, 12]]])
arr3d[0] = old_values
arr3d
` `
输出结果:
array([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]]])
相似的,arr3d[1,0]可以访问索引以(1,0)开头的那些值(以一维数组的形式返回):
arr3d[1, 0]
输出结果:
array([7, 8, 9])
虽然是用两步进行索引的,表达式是相同的:
x = arr3d[1]
x
输出结果:
array([[ 7, 8, 9],
[10, 11, 12]])
x[0]
输出结果:
array([7, 8, 9])
注意,在上面所有这些选取数组子集的例子中,返回的数组都是视图。
2.3切片索引
ndarray的切片语法跟Python列表这样的一维对象差不多:
arr
输出结果:
array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
arr[1:6]
输出结果:
array([ 1, 2, 3, 4, 64])
对于之前的二维数组arr2d,其切片方式稍显不同:
arr2d
输出结果:
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
arr2d[:2]
输出结果:
array([[1, 2, 3],
[4, 5, 6]])
可以看出,它是沿着第0轴(即第一个轴)切片的。也就是说,切片是沿着一个轴向选取元素的。表达式arr2d[:2]可以被认为是"选取arr2d的前两行"。
你可以一次传入多个切片,就像传入多个索引那样:
arr2d[:2, 1:]
输出结果:
array([[2, 3],
[5, 6]])
像这样进行切片时,只能得到相同维数的数组视图。通过将整数索引和切片混合,可以得到低维度的切片。
例如,我可以选取第二行的前两列:
arr2d[1, :2]
输出结果:
array([4, 5])
相似的,还可以选择第三列的前两行:
arr2d[:2, 2]
输出结果:
array([3, 6])
图1-2对此进行了说明。注意,"只有冒号"表示选取整个轴,因此你可以像下面这样只对高维轴进行切片:
arr2d[:, :1]
输出结果:
array([[1],
[4],
[7]])

自然,对切片表达式的赋值操作也会被扩散到整个选区:
arr2d[:2, 1:] = 0
arr2d
` `
输出结果:
array([[1, 0, 0],
[4, 0, 0],
[7, 8, 9]])
2.4布尔型索引
来看这样一个例子,假设我们有一个用于存储数据的数组以及一个存储姓名的数组(含有重复项)。在这里,我将使用numpy.random中的randn函数生成一些正态分布的随机数据:
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4)
names
输出结果:
array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'],
dtype='<U4')
data
输出结果:
array([[ 0.0929, 0.2817, 0.769 , 1.2464],
[ 1.0072, -1.2962, 0.275 , 0.2289],
[ 1.3529, 0.8864, -2.0016, -0.3718],
[ 1.669 , -0.4386, -0.5397, 0.477 ],
[ 3.2489, -1.0212, -0.5771, 0.1241],
[ 0.3026, 0.5238, 0.0009, 1.3438],
[-0.7135, -0.8312, -2.3702, -1.8608]])
假设每个名字都对应data数组中的一行,而我们想要选出对应于名字"Bob"的所有行。跟算术运算一样,数组的比较运算(如==)也是矢量化的。因此,对names和字符串"Bob"的比较运算将会产生一个布尔型数组:
names == 'Bob'
输出结果:
array([ True, False, False, True, False, False, False], dtype=bool)
这个布尔型数组可用于数组索引:
data[names == 'Bob']
输出结果:
array([[ 0.0929, 0.2817, 0.769 , 1.2464],
[ 1.669 , -0.4386, -0.5397, 0.477 ]])
布尔型数组的长度必须跟被索引的轴长度一致。此外,还可以将布尔型数组跟切片、整数(或整数序列,稍后将对此进行详细讲解)混合使用:
注意:如果布尔型数组的长度不对,布尔型选择就会出错,因此一定要小心。
下面的例子,我选取了的行,并索引了列:names == 'Bob'
data[names == 'Bob', 2:]
输出结果:
array([[ 0.769 , 1.2464],
[-0.5397, 0.477 ]])
data[names == 'Bob', 3]
输出结果:
array([ 1.2464, 0.477 ])
要选择除"Bob"以外的其他值,既可以使用不等于符号(!=),也可以通过~对条件进行否定:
names != 'Bob'
输出结果:
array([False, True, True, False, True, True, True], dtype=bool)
data[~(names == 'Bob')]
输出结果:
array([[ 1.0072, -1.2962, 0.275 , 0.2289],
[ 1.3529, 0.8864, -2.0016, -0.3718],
[ 3.2489, -1.0212, -0.5771, 0.1241],
[ 0.3026, 0.5238, 0.0009, 1.3438],
[-0.7135, -0.8312, -2.3702, -1.8608]])
~操作符用来反转条件很好用:
cond = names == 'Bob'
data[~cond]
` `
输出结果:
array([[ 1.0072, -1.2962, 0.275 , 0.2289],
[ 1.3529, 0.8864, -2.0016, -0.3718],
[ 3.2489, -1.0212, -0.5771, 0.1241],
[ 0.3026, 0.5238, 0.0009, 1.3438],
[-0.7135, -0.8312, -2.3702, -1.8608]])
选取这三个名字中的两个需要组合应用多个布尔条件,使用&(和)、|(或)之类的布尔算术运算符即可:
mask = (names == 'Bob') | (names == 'Will')
mask
输出结果:
array([ True, False, True, True, True, False, False], dtype=bool)
data[mask]
` `
array([[ 0.0929, 0.2817, 0.769 , 1.2464],
[ 1.3529, 0.8864, -2.0016, -0.3718],
[ 1.669 , -0.4386, -0.5397, 0.477 ],
[ 3.2489, -1.0212, -0.5771, 0.1241]])
通过布尔型索引选取数组中的数据,将总是创建数据的副本,即使返回一模一样的数组也是如此。
注意:Python关键字andand和or在布尔型数组中无效。要使用&与|。
通过布尔型数组设置值是一种经常用到的手段。为了将data中的所有负值都设置为0,我们只需:
data[data < 0] = 0
data
输出结果:
array([[ 0.0929, 0.2817, 0.769 , 1.2464],
[ 1.0072, 0. , 0.275 , 0.2289],
[ 1.3529, 0.8864, 0. , 0. ],
[ 1.669 , 0. , 0. , 0.477 ],
[ 3.2489, 0. , 0. , 0.1241],
[ 0.3026, 0.5238, 0.0009, 1.3438],
[ 0. , 0. , 0. , 0. ]])
通过一维布尔数组设置整行或列的值也很简单:
data[names != 'Joe'] = 7
data
输出结果:
array([[ 7. , 7. , 7. , 7. ],
[ 1.0072, 0. , 0.275 , 0.2289],
[ 7. , 7. , 7. , 7. ],
[ 7. , 7. , 7. , 7. ],
[ 7. , 7. , 7. , 7. ],
[ 0.3026, 0.5238, 0.0009, 1.3438],
[ 0. , 0. , 0. , 0. ]])
后面会看到,这类二维数据的操作也可以用pandas方便的来做。
2.5花式索引
花式索引(Fancy indexing)是一个NumPy术语,它指的是利用整数数组进行索引。假设我们有一个8×4数组:
arr = np.empty((8, 4))
for i in range(8):
arr[i] = i
arr
输出结果:
array([[ 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即可:
arr[[4, 3, 0, 6]]
输出结果:
array([[ 4., 4., 4., 4.],
[ 3., 3., 3., 3.],
[ 0., 0., 0., 0.],
[ 6., 6., 6., 6.]])
这段代码确实达到我们的要求了!使用负数索引将会从末尾开始选取行:
arr[[-3, -5, -7]]
输出结果:
array([[ 5., 5., 5., 5.],
[ 3., 3., 3., 3.],
[ 1., 1., 1., 1.]])
一次传入多个索引数组会有一点特别。它返回的是一个一维数组,其中的元素对应各个索引元组:
arr = np.arange(32).reshape((8, 4))
arr
输出结果:
array([[ 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]])
arr[[1, 5, 7, 2], [0, 3, 1, 2]]
输出结果:
array([ 4, 23, 29, 10])
最终选出的是元素(1,0)、(5,3)、(7,1)和(2,2)。无论数组是多少维的,花式索引总是一维的。
这个花式索引的行为可能会跟某些用户的预期不一样(包括我在内),选取矩阵的行列子集应该是矩形区域的形式才对。下面是得到该结果的一个办法:
arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]]
` `
array([[ 4, 7, 5, 6],
[20, 23, 21, 22],
[28, 31, 29, 30],
[ 8, 11, 9, 10]])
记住,花式索引跟切片不一样,它总是将数据到新数组中。复制
3.1转置
转置是重塑的一种特殊形式,它返回的是源数据的视图(不会进行任何复制操作)。数组不仅有transpose方法,还有一个特殊的T属性:
import numpy as np
arr = np.arange(15).reshape((3, 5))
arr
输出结果:
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
arr.T
输出结果:
array([[ 0, 5, 10],
[ 1, 6, 11],
[ 2, 7, 12],
[ 3, 8, 13],
[ 4, 9, 14]])
在进行矩阵计算时,经常需要用到该操作,比如利用np.dot计算矩阵内积:
arr = np.random.randn(6, 3)
arr
输出结果:
array([[-0.8608, 0.5601, -1.2659],
[ 0.1198, -1.0635, 0.3329],
[-2.3594, -0.1995, -1.542 ],
[-0.9707, -1.307 , 0.2863],
[ 0.378 , -0.7539, 0.3313],
[ 1.3497, 0.0699, 0.2467]])
np.dot(arr.T, arr)
输出结果:
array([[ 9.2291, 0.9394, 4.948 ],
[ 0.9394, 3.7662, -1.3622],
[ 4.948 , -1.3622, 4.3437]])
3.2轴对换
对于高维数组,转置需要得到一个由轴编号组成的元组才能对这些轴进行转置(比较费脑子):
arr = np.arange(16).reshape((2, 2, 4))
arr
输出结果:
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
arr.transpose((1, 0, 2))
输出结果:
array([[[ 0, 1, 2, 3],
[ 8, 9, 10, 11]],
[[ 4, 5, 6, 7],
[12, 13, 14, 15]]])
这里,第一个轴被换成了第二个,第二个轴被换成了第一个,最后一个轴不变。
简单的转置可以使用.T,它其实就是进行轴对换而已。ndarray还有一个swapaxes方法,它需要接受一对轴编号:
arr
输出结果:
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
arr.swapaxes(1, 2)
输出结果:
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]],
[[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])
swapaxes也是返回源数据的视图(不会进行任何复制操作)。
3.3快速的元素级数组函数
通用函数(即ufunc)是一种对ndarray中的数据执行元素级运算的函数。你可以将其看做简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化包装器。
许多ufunc都是简单的元素级变体,如sqrt和exp:
arr = np.arange(10)
arr
输出结果:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.sqrt(arr)
` `
输出结果:
array([ 0. , 1. , 1.4142, 1.7321, 2. , 2.2361, 2.4495,
2.6458, 2.8284, 3. ])
np.exp(arr)
输出结果:
array([ 1. , 2.7183, 7.3891, 20.0855, 54.5982,
148.4132, 403.4288, 1096.6332, 2980.958 , 8103.0839])
这些都是一元(一元)ufunc。另外一些(如add或maximum)接受2个数组(因此也叫二元(binary)ufunc),并返回一个结果数组:
x = np.random.randn(8)
y = np.random.randn(8)
x
输出结果:
array([-0.0119, 1.0048, 1.3272, -0.9193, -1.5491, 0.0222, 0.7584,
-0.6605])
y
输出结果:
array([ 0.8626, -0.01 , 0.05 , 0.6702, 0.853 , -0.9559, -0.0235,
-2.3042])
np.maximum(x, y)
` `
输出结果:
array([ 0.8626, 1.0048, 1.3272, 0.6702, 0.853 , 0.0222, 0.7584,
-0.6605])
这里,numpy.maximum计算了x和y中元素级别最大的元素。
虽然并不常见,但有些ufunc的确可以返回多个数组。modf就是一个例子,它是Python内置函数divmod的矢量化版本,它会返回浮点数数组的小数和整数部分:
arr = np.random.randn(7) * 5
arr
输出结果:
array([-3.2623, -6.0915, -6.663 , 5.3731, 3.6182, 3.45 , 5.0077])
remainder, whole_part = np.modf(arr)
remainder
输出结果:
array([-0.2623, -0.0915, -0.663 , 0.3731,
0.6182, 0.45 , 0.0077])
whole_part
输出结果:
array([-3., -6., -6., 5., 3., 3., 5.])
Ufuncs可以接受一个out可选参数,这样就能在数组原地进行操作:
arr
输出结果:
array([-3.2623, -6.0915, -6.663 , 5.3731, 3.6182, 3.45 , 5.0077])
np.sqrt(arr)
输出结果:
array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378])
np.sqrt(arr, arr)
输出结果:
array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378])
arr
输出结果:
array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378])
3.3快速的元素级数组函数
通用函数(即ufunc)是一种对ndarray中的数据执行元素级运算的函数。你可以将其看做简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化包装器。
许多ufunc都是简单的元素级变体,如sqrt和exp:
arr = np.arange(10)
arr
输出结果:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.sqrt(arr)
` `
输出结果:
array([ 0. , 1. , 1.4142, 1.7321, 2. , 2.2361, 2.4495,
2.6458, 2.8284, 3. ])
np.exp(arr)
输出结果:
array([ 1. , 2.7183, 7.3891, 20.0855, 54.5982,
148.4132, 403.4288, 1096.6332, 2980.958 , 8103.0839])
这些都是一元(一元)ufunc。另外一些(如add或maximum)接受2个数组(因此也叫二元(binary)ufunc),并返回一个结果数组:
x = np.random.randn(8)
y = np.random.randn(8)
x
输出结果:
array([-0.0119, 1.0048, 1.3272, -0.9193, -1.5491, 0.0222, 0.7584,
-0.6605])
y
输出结果:
array([ 0.8626, -0.01 , 0.05 , 0.6702, 0.853 , -0.9559, -0.0235,
-2.3042])
np.maximum(x, y)
` `
输出结果:
array([ 0.8626, 1.0048, 1.3272, 0.6702, 0.853 , 0.0222, 0.7584,
-0.6605])
这里,numpy.maximum计算了x和y中元素级别最大的元素。
虽然并不常见,但有些ufunc的确可以返回多个数组。modf就是一个例子,它是Python内置函数divmod的矢量化版本,它会返回浮点数数组的小数和整数部分:
arr = np.random.randn(7) * 5
arr
输出结果:
array([-3.2623, -6.0915, -6.663 , 5.3731, 3.6182, 3.45 , 5.0077])
remainder, whole_part = np.modf(arr)
remainder
输出结果:
array([-0.2623, -0.0915, -0.663 , 0.3731,
0.6182, 0.45 , 0.0077])
whole_part
输出结果:
array([-3., -6., -6., 5., 3., 3., 5.])
Ufuncs可以接受一个out可选参数,这样就能在数组原地进行操作:
arr
输出结果:
array([-3.2623, -6.0915, -6.663 , 5.3731, 3.6182, 3.45 , 5.0077])
np.sqrt(arr)
输出结果:
array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378])
np.sqrt(arr, arr)
输出结果:
array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378])
arr
输出结果:
array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378])
3.5用于数组的文件输入输出
NumPy能够读写磁盘上的文本数据或二进制数据。这一小节只讨论NumPy的内置二进制格式,因为更多的用户会使用pandas或其它工具加载文本或表格数据。
np.save和np.load是读写磁盘数组数据的两个主要函数。默认情况下,数组是以未压缩的原始二进制格式保存在扩展名为.npy的文件中的:
arr = np.arange(10)
np.save('./data/some_array', arr)
如果文件路径末尾没有扩展名.npy,则该扩展名会被自动加上。然后就可以通过np.load读取磁盘上的数组:
np.load('./data/some_array.npy')
输出结果:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
通过np.savez可以将多个数组保存到一个未压缩文件中,将数组以关键字参数的形式传入即可:
np.savez('./data/array_archive.npz', a=arr, b=arr)
加载.npz文件时,你会得到一个类似字典的对象,该对象会对各个数组进行延迟加载:
arch = np.load('./data/array_archive.npz')
arch['b']
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
如果要将数据压缩,可以使用numpy.savez_compressed:
np.savez_compressed('./data/arrays_compressed.npz', a=arr, b=arr)
4.1利用数组进行数据处理
NumPy数组使你可以将许多种数据处理任务表述为简洁的数组表达式(否则需要编写循环)。用数组表达式代替循环的做法,通常被称为矢量化。一般来说,矢量化数组运算要比等价的纯Python方式快上一两个数量级(甚至更多),尤其是各种数值计算。
作为简单的例子,假设我们想要在一组值(网格型)上计算函数。np.meshgrid函数接受两个一维数组,并产生两个二维矩阵(对应于两个数组中所有的(x,y)对):sqrt(x2+y2)
points = np.arange(-5, 5, 0.01) # 1000 equally spaced points
xs, ys = np.meshgrid(points, points)
ys
输出结果:
array([[-5. , -5. , -5. , ..., -5. , -5. , -5. ],
[-4.99, -4.99, -4.99, ..., -4.99, -4.99, -4.99],
[-4.98, -4.98, -4.98, ..., -4.98, -4.98, -4.98],
...,
[ 4.97, 4.97, 4.97, ..., 4.97, 4.97, 4.97],
[ 4.98, 4.98, 4.98, ..., 4.98, 4.98, 4.98],
[ 4.99, 4.99, 4.99, ..., 4.99, 4.99, 4.99]])
现在,对该函数的求值运算就好办了,把这两个数组当做两个浮点数那样编写表达式即可:
z = np.sqrt(xs ** 2 + ys ** 2)
z
输出结果:
array([[ 7.0711, 7.064 , 7.0569, ..., 7.0499, 7.0569, 7.064 ],
[ 7.064 , 7.0569, 7.0499, ..., 7.0428, 7.0499, 7.0569],
[ 7.0569, 7.0499, 7.0428, ..., 7.0357, 7.0428, 7.0499],
...,
[ 7.0499, 7.0428, 7.0357, ..., 7.0286, 7.0357, 7.0428],
[ 7.0569, 7.0499, 7.0428, ..., 7.0357, 7.0428, 7.0499],
[ 7.064 , 7.0569, 7.0499, ..., 7.0428, 7.0499, 7.0569]])
4.2将条件逻辑表述为数组运算
numpy.where函数是三元表达式x if condition else y的矢量化版本。假设我们有一个布尔数组和两个值数组:
xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
cond = np.array([True, False, True, True, False])
假设我们想要根据cond中的值选取xarr和yarr的值:当cond中的值为True时,选取xarr的值,否则从yarr中选取。列表推导式的写法应该如下所示:
result = [(x if c else y) for x, y, c in zip(xarr, yarr, cond)]
result
输出结果:
[1.1, 2.2, 1.3, 1.4, 2.5]
这有几个问题。第一,它对大数组的处理速度不是很快(因为所有工作都是由纯Python完成的)。第二,无法用于多维数组。若使用np.where,则可以将该功能写得非常简洁:
result = np.where(cond, xarr, yarr)
result
输出结果:
array([ 1.1, 2.2, 1.3, 1.4, 2.5])
np.where的第二个和第三个参数不必是数组,它们都可以是标量值。在数据分析工作中,其中通常用于根据另一个数组而产生一个新的数组。假设有一个由随机数据组成的矩阵,你希望将所有正值替换为2,将所有负值替换为-2。若利用np.where,则会非常简单:
arr = np.random.randn(4, 4)
arr
输出结果:
array([[-0.5031, -0.6223, -0.9212, -0.7262],
[ 0.2229, 0.0513, -1.1577, 0.8167],
[ 0.4336, 1.0107, 1.8249, -0.9975],
[ 0.8506, -0.1316, 0.9124, 0.1882]])
arr > 0
` `
输出结果:` `
array([[False, False, False, False],
[ True, True, False, True],
[ True, True, True, False],
[ True, False, True, True]])
np.where(arr > 0, 2, -2)
输出结果:
array([[-2, -2, -2, -2],
[ 2, 2, -2, 2],
[ 2, 2, 2, -2],
[ 2, -2, 2, 2]])
使用np.where,可以将标量和数组结合起来。例如,我可用常数2替换arr中所有正的值:
np.where(arr > 0, 2, arr) # set only positive values to 2
输出结果:
array([[-0.5031, -0.6223, -0.9212, -0.7262],
[ 2. , 2. , -1.1577, 2. ],
[ 2. , 2. , 2. , -0.9975],
[ 2. , -0.1316, 2. , 2. ]])
传递给where的数组大小可以不相等,甚至可以是标量值。
4.3数学和统计方法
可以通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算。sum、mean以及标准差std等聚合计算(aggregation,通常叫做约简(reduction))既可以当做数组的实例方法调用,也可以当做顶级NumPy函数使用。
这里,我生成了一些正态分布随机数据,然后做了聚类统计:
arr = np.random.randn(5, 4)
arr
输出结果:
array([[ 2.1695, -0.1149, 2.0037, 0.0296],
[ 0.7953, 0.1181, -0.7485, 0.585 ],
[ 0.1527, -1.5657, -0.5625, -0.0327],
[-0.929 , -0.4826, -0.0363, 1.0954],
[ 0.9809, -0.5895, 1.5817, -0.5287]])
arr.mean()
输出结果:
0.19607051119998253
np.mean(arr)
输出结果:
0.19607051119998253
arr.sum()
输出结果:
3.9214102239996507
mean和sum这类的函数可以接受一个axis选项参数,用于计算该轴向上的统计值,最终结果是一个少一维的数组:
arr.mean(axis=1)
输出结果:
array([ 1.022 , 0.1875, -0.502 , -0.0881, 0.3611])
arr.sum(axis=0)
输出结果:
array([ 3.1693, -2.6345, 2.2381, 1.1486])
这里,arr.mean(1)是"计算行的平均值",arr.sum(0)是"计算每列的和"。
其他如cumsum和cumprod之类的方法则不聚合,而是产生一个由中间结果组成的数组:
arr = np.array([0, 1, 2, 3, 4, 5, 6, 7])
arr.cumsum()
输出结果:
array([ 0, 1, 3, 6, 10, 15, 21, 28])
在多维数组中,累加函数(如cumsum)返回的是同样大小的数组,但是会根据每个低维的切片沿着标记轴计算部分聚类:
arr = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
arr
输出结果:
array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
arr.cumsum(axis=0)
输出结果:` `
array([[ 0, 1, 2],
[ 3, 5, 7],
[ 9, 12, 15]])
arr.cumprod(axis=1)
` `
输出结果:
array([[ 0, 0, 0],
[ 3, 12, 60],
[ 6, 42, 336]])
表1-5列出了全部的基本数组统计方法。后续章节中有很多例子都会用到这些方法。

4.4用于布尔型数组的方法
在上面这些方法中,布尔值会被强制转换为1(True)和0(False)。因此,sum经常被用来对布尔型数组中的True值计数:
arr = np.random.randn(100)
(arr > 0).sum() # Number of positive values
42
另外还有两个方法any和all,它们对布尔型数组非常有用。any用于测试数组中是否存在一个或多个True,而all则检查数组中所有值是否都是True:
bools = np.array([False, False, True, False])
bools.any()
输出结果:
True
bools.all()
输出结果:
False
这两个方法也能用于非布尔型数组,所有非0元素将会被当做True。
4.5排序
跟Python内置的列表类型一样,NumPy数组也可以通过sort方法就地排序:
arr = np.random.randn(6)
arr
输出结果:
array([ 0.6095, -0.4938, 1.24 , -0.1357, 1.43 , -0.8469])
arr.sort()
arr
输出结果:
array([-0.8469, -0.4938, -0.1357, 0.6095, 1.24 , 1.43 ])
多维数组可以在任何一个轴向上进行排序,只需将轴编号传给sort即可:
arr = np.random.randn(5, 3)
arr
输出结果:
array([[ 0.6033, 1.2636, -0.2555],
[-0.4457, 0.4684, -0.9616],
[-1.8245, 0.6254, 1.0229],
[ 1.1074, 0.0909, -0.3501],
[ 0.218 , -0.8948, -1.7415]])
arr.sort(1)
arr
输出结果:` `
array([[-0.2555, 0.6033, 1.2636],
[-0.9616, -0.4457, 0.4684],
[-1.8245, 0.6254, 1.0229],
[-0.3501, 0.0909, 1.1074],
[-1.7415, -0.8948, 0.218 ]])
顶级方法np.sort返回的是数组的已排序副本,而就地排序则会修改数组本身。计算数组分位数最简单的办法是对其进行排序,然后选取特定位置的值:
large_arr = np.random.randn(1000)
large_arr.sort()
large_arr[int(0.05 * len(large_arr))] # 5% quantile
输出结果:
-1.5311513550102103
4.6唯一化以及其它的集合逻辑
NumPy提供了一些针对一维ndarray的基本集合运算。最常用的可能要数np.unique了,它用于找出数组中的唯一值并返回已排序的结果:
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
np.unique(names)
输出结果:
array(['Bob', 'Joe', 'Will'],
dtype='<U4')
ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4])
np.unique(ints)
输出结果:
array([1, 2, 3, 4])
拿跟np.unique等价的纯Python代码来对比一下:
sorted(set(names))
输出结果:
['Bob', 'Joe', 'Will']
另一个函数np.in1d用于测试一个数组中的值在另一个数组中的成员资格,返回一个布尔型数组:
values = np.array([6, 0, 0, 3, 2, 5, 6])
np.in1d(values, [2, 3, 6])
输出结果:
array([ True, False, False, True, True, False, True], dtype=bool)
NumPy中的集合函数请参见下图。

5.1线性代数
线性代数(如矩阵乘法、矩阵分解、行列式以及其他方阵数学等)是任何数组库的重要组成部分。不像某些语言(如MATLAB),通过*对两个二维数组相乘得到的是一个元素级的积,而不是一个矩阵点积。因此,NumPy提供了一个用于矩阵乘法的dot函数(既是一个数组方法也是numpy命名空间中的一个函数):
import numpy as np
x = np.array([[1., 2., 3.], [4., 5., 6.]])
y = np.array([[6., 23.], [-1, 7], [8, 9]])
x
` `
输出结果:
array([[ 1., 2., 3.],
[ 4., 5., 6.]])
y
输出结果:
array([[ 6., 23.],
[ -1., 7.],
[ 8., 9.]])
x.dot(y)
输出结果:
array([[ 28., 64.],
[ 67., 181.]])
x.dot(y)等价于np.dot(x, y):
np.dot(x, y)
输出结果:
array([[ 28., 64.],
[ 67., 181.]])
一个二维数组跟一个大小合适的一维数组的矩阵点积运算之后将会得到一个一维数组:
np.dot(x, np.ones(3))
输出结果:
array([ 6., 15.])
@符(类似Python 3.5)也可以用作中缀运算符,进行矩阵乘法:
x @ np.ones(3)
输出结果:
array([ 6., 15.])
5.2碱
numpy.linalg中有一组标准的矩阵分解运算以及诸如求逆和行列式之类的东西。它们跟MATLAB和R等语言所使用的是相同的行业标准线性代数库,如BLAS、LAPACK、Intel MKL(Math Kernel Library,可能有,取决于你的NumPy版本)等:
from numpy.linalg import inv, qr
X = np.random.randn(5, 5)
mat = X.T.dot(X)
inv(mat)
输出结果:
array([[ 933.1189, 871.8258, -1417.6902, -1460.4005, 1782.1391],
[ 871.8258, 815.3929, -1325.9965, -1365.9242, 1666.9347],
[-1417.6902, -1325.9965, 2158.4424, 2222.0191, -2711.6822],
[-1460.4005, -1365.9242, 2222.0191, 2289.0575, -2793.422 ],
[ 1782.1391, 1666.9347, -2711.6822, -2793.422 , 3409.5128]])
mat.dot(inv(mat))
输出结果:
array([[ 1., 0., -0., -0., -0.],
[-0., 1., 0., 0., 0.],
[ 0., 0., 1., 0., 0.],
[-0., 0., 0., 1., -0.],
[-0., 0., 0., 0., 1.]])
q, r = qr(mat)
r
输出结果:
array([[-1.6914, 4.38 , 0.1757, 0.4075, -0.7838],
[ 0. , -2.6436, 0.1939, -3.072 , -1.0702],
[ 0. , 0. , -0.8138, 1.5414, 0.6155],
[ 0. , 0. , 0. , -2.6445, -2.1669],
[ 0. , 0. , 0. , 0. , 0.0002]])
表达式X.T.dot(X)计算X和它的转置X.T的点积。
图中列出了一些最常用的线性代数函数。

5.3伪随机数生成
numpy.random模块对Python内置的random进行了补充,增加了一些用于高效生成多种概率分布的样本值的函数。例如,你可以用normal来得到一个标准正态分布的4×4样本数组:
samples = np.random.normal(size=(4, 4))
samples
` `
输出结果:
array([[ 0.5732, 0.1933, 0.4429, 1.2796],
[ 0.575 , 0.4339, -0.7658, -1.237 ],
[-0.5367, 1.8545, -0.92 , -0.1082],
[ 0.1525, 0.9435, -1.0953, -0.144 ]])
而Python内置的random模块则只能一次生成一个样本值。从下面的测试结果中可以看出,如果需要产生大量样本值,numpy.random快了不止一个数量级:
from random import normalvariate
N = 1000000
%timeit samples = [normalvariate(0, 1) for _ in range(N)]
输出结果:
1.77 s +- 126 ms per loop (mean +- std. dev. of 7 runs, 1 loop each)
%timeit np.random.normal(size=N)
输出结果:
61.7 ms +- 1.32 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)
我们说这些都是伪随机数,是因为它们都是通过算法基于随机数生成器种子,在确定性的条件下生成的。你可以用NumPy的np.random.seed更改随机数生成种子:
np.random.seed(1234)
numpy.random的数据生成函数使用了全局的随机种子。要避免全局状态,你可以使用numpy.random.RandomState,创建一个与其它隔离的随机数生成器:
rng = np.random.RandomState(1234)
rng.randn(10)
输出结果:
array([ 0.4714, -1.191 , 1.4327, -0.3127, -0.7206, 0.8872, 0.8596,
-0.6365, 0.0157, -2.2427])
5.4部分numpy.random函数列表
下图列出了numpy.random中的部分函数。在下一节中,我将给出一些利用这些函数一次性生成大量样本值的范例。

5.5示例:随机漫步
5.5.1 随机漫步
我们通过模拟随机漫步来说明如何运用数组运算。先来看一个简单的随机漫步的例子:从0开始,步长1和-1出现的概率相等。
下面是一个通过内置的random模块以纯Python的方式实现1000步的随机漫步:
import random
position = 0
walk = [position]
steps = 1000
for i in range(steps):
step = 1 if random.randint(0, 1) else - 1
position += step
walk.append(position)
图中是根据前100个随机漫步值生成的折线图:
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(walk[:100])

不难看出,这其实就是随机漫步中各步的累计和,可以用一个数组运算来实现。因此,我用np.random模块一次性随机产生1000个"掷硬币"结果(即两个数中任选一个),将其分别设置为1或-1,然后计算累计和:
nsteps = 1000
draws = np.random.randint(0, 2, size=nsteps)
steps = np.where(draws > 0, 1, -1)
walk = steps.cumsum()
有了这些数据之后,我们就可以沿着漫步路径做一些统计工作了,比如求取最大值和最小值:
walk.min()
输出结果:
-3
walk.max()
输出结果:
31
现在来看一个复杂点的统计任务------首次穿越时间,即随机漫步过程中第一次到达某个特定值的时间。假设我们想要知道本次随机漫步需要多久才能距离初始0点至少10步远(任一方向均可)。np.abs(walk)>=10可以得到一个布尔型数组,它表示的是距离是否达到或超过10,而我们想要知道的是第一个10或-10的索引。可以用argmax来解决这个问题,它返回的是该布尔型数组第一个最大值的索引(True就是最大值):
(np.abs(walk) >= 10).argmax()
输出结果:
37
注意,这里使用argmax并不是很高效,因为它无论如何都会对数组进行完全扫描。在本例中,只要发现了一个True,那我们就知道它是个最大值了。
5.5.2 一次模拟多个随机漫步
如果你希望模拟多个随机漫步过程(比如5000个),只需对上面的代码做一点点修改即可生成所有的随机漫步过程。只要给numpy.random的函数传入一个二元元组就可以产生一个二维数组,然后我们就可以一次性计算5000个随机漫步过程(一行一个)的累计和了:
nwalks = 5000
nsteps = 1000
draws = np.random.randint(0, 2, size=(nwalks, nsteps)) # 0 or 1
steps = np.where(draws > 0, 1, -1)
walks = steps.cumsum(1)
walks
输出结果:
array([[ 1, 0, 1, ..., 8, 7, 8],
[ 1, 0, -1, ..., 34, 33, 32],
[ 1, 0, -1, ..., 4, 5, 4],
...,
[ 1, 2, 1, ..., 24, 25, 26],
[ 1, 2, 3, ..., 14, 13, 14],
[ -1, -2, -3, ..., -24, -23, -22]])
现在,我们来计算所有随机漫步过程的最大值和最小值:
walks.max()
输出结果:
138
walks.min()
输出结果:
-133
得到这些数据之后,我们来计算30或-30的最小穿越时间。这里稍微复杂些,因为不是5000个过程都到达了30。我们可以用any方法来对此进行检查:
hits30 = (np.abs(walks) >= 30).any(1)
hits30
输出结果:
array([False, True, False, ..., False, True, False], dtype=bool)
hits30.sum() # Number that hit 30 or -30
输出结果:
3410
然后我们利用这个布尔型数组选出那些穿越了30(绝对值)的随机漫步(行),并调用argmax在轴1上获取穿越时间:
crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1)
crossing_times.mean()
输出结果:
498.88973607038122
请尝试用其他分布方式得到漫步数据。只需使用不同的随机数生成函数即可,如normal用于生成指定均值和标准差的正态分布数据:
steps = np.random.normal(loc=0, scale=0.25, size=(nwalks, nsteps))
steps
6.1熊猫简介
pandas含有使数据清洗和分析工作变得更快更简单的数据结构和操作工具。pandas经常和其它工具一同使用,如数值计算工具NumPy和SciPy,分析库statsmodels和scikit-learn,和数据可视化库matplotlib。pandas是基于NumPy数组构建的,特别是基于数组的函数和不使用for循环的数据处理。
虽然pandas采用了大量的NumPy编码风格,但二者最大的不同是pandas是专门为处理表格和混杂数据设计的。而NumPy更适合处理统一的数值数组数据。
自从2010年pandas开源以来,pandas逐渐成长为一个非常大的库,应用于许多真实案例。开发者社区已经有了800个独立的贡献者,他们在解决日常数据问题的同时为这个项目提供贡献。
6.1.1 引入约定
在后续部分中,将使用下面这样的pandas和numpy引入约定:
import pandas as pd
import numpy as np
因此,只要你在代码中看到pd.,就得想到这是pandas。因为Series和DataFrame用的次数非常多,所以将其引入本地命名空间中会更方便:
from pandas import Series, DataFrame
6.2熊猫的数据结构系列
Series是一种类似于一维数组的对象,它由一组数据(各种NumPy数据类型)以及一组与之相关的数据标签(即索引)组成。仅由一组数据即可产生最简单的系列:
obj = pd.Series([4, 7, -5, 3])
obj
输出结果:
0 4
1 7
2 -5
3 3
dtype: int64
Series的字符串表现形式为:索引在左边,值在右边。由于我们没有为数据指定索引,于是会自动创建一个0到N-1(N为数据的长度)的整数型索引。你可以通过系列 的values和index属性获取其数组表示形式和索引对象:
obj.values
输出结果:
array([ 4, 7, -5, 3])
obj.index # like range(4)
输出结果:
RangeIndex(start=0, stop=4, step=1)
通常,我们希望所创建的Series带有一个可以对各个数据点进行标记的索引:
obj2 = pd.Series([4, 7, -5, 3], index=['d', 'b', 'a', 'c'])
obj2
输出结果:
d 4
b 7
a -5
c 3
dtype: int64
obj2.index
输出结果:
Index(['d', 'b', 'a', 'c'], dtype='object')
与普通NumPy数组相比,你可以通过索引的方式选取Series中的单个或一组值:
obj2['a']
输出结果:
-5
obj2['d'] = 6
obj2[['c', 'a', 'd']]
输出结果:
c 3
a -5
d 6
dtype: int64
'c', 'a', 'd'\]是索引列表,即使它包含的是字符串而不是整数。 使用NumPy函数或类似NumPy的运算(如根据布尔型数组进行过滤、标量乘法、应用数学函数等)都会保留索引值的链接: ``` obj2[obj2 > 0] ``` ``` ``` ``` 输出结果: ``` ``` d 6 ``` ``` b 7 ``` ``` c 3 ``` ``` dtype: int64 ``` ``` ``` ``` obj2 * 2 ``` ``` ``` ``` 输出结果: ``` ``` d 12 ``` ``` b 14 ``` ``` a -10 ``` ``` c 6 ``` ``` dtype: int64 ``` ``` ``` ``` np.exp(obj2) ``` ``` ``` ``` 输出结果: ``` ``` d 403.428793 ``` ``` b 1096.633158 ``` ``` a 0.006738 ``` ``` c 20.085537 ``` ``` dtype: float64 ``` 还可以将系列看成是一个定长的有序字典,因为它是索引值到数据值的一个映射。它可以用在许多原本需要字典参数的函数中: ``` 'b' in obj2 ``` ``` ``` ``` 输出结果: ``` ``` True ``` ``` ``` ``` 'e' in obj2 ``` ``` ``` ``` 输出结果: ``` ``` False ``` 如果数据被存放在一个Python字典中,也可以直接通过这个字典来创建系列: ``` sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000} ``` ``` ``` ``` obj3 = pd.Series(sdata) ``` ``` ``` ``` obj3 ``` ``` ``` ``` 输出结果: ``` ``` Ohio 35000 ``` ``` Oregon 16000 ``` ``` Texas 71000 ``` ``` Utah 5000 ``` ``` dtype: int64 ``` 如果只传入一个字典,则结果Series中的索引就是原字典的键(有序排列)。你可以传入排好序的字典的键以改变顺序: ``` states = ['California', 'Ohio', 'Oregon', 'Texas'] ``` ``` ``` ``` obj4 = pd.Series(sdata, index=states) ``` ``` ``` ``` obj4 ``` ``` ``` ``` 输出结果: ``` ``` California NaN ``` ``` Ohio 35000.0 ``` ``` Oregon 16000.0 ``` ``` Texas 71000.0 ``` ``` dtype: float64 ``` 在这个例子中,sdata中跟states索引相匹配的那3个值会被找出来并放到相应的位置上,但由于"California"所对应的sdata值找不到,所以其结果就为NaN(即"非数字"(not a number),在pandas中,它用于表示缺失或NA值)。因为'Utah'不在states中,它被从结果中除去。 我将使用缺失(missing)或NA表示缺失数据。pandas的isnull和notnull函数可用于检测缺失数据: ``` pd.isnull(obj4) ``` ``` ``` ``` 输出结果: ``` ``` California True ``` ``` Ohio False ``` ``` Oregon False ``` ``` Texas False ``` ``` dtype: bool ``` ``` ``` ``` pd.notnull(obj4) ``` ``` ``` ``` 输出结果: ``` ``` California False ``` ``` Ohio True ``` ``` Oregon True ``` ``` Texas True ``` ``` dtype: bool ``` Series也有类似的实例方法: ``` obj4.isnull() ``` ``` ``` ``` 输出结果: ``` ``` California True ``` ``` Ohio False ``` ``` Oregon False ``` ``` Texas False ``` ``` dtype: bool ``` 对于许多应用而言,Series最重要的一个功能是,它会根据运算的索引标签自动对齐数据: ``` obj3 ``` ``` ``` ``` 输出结果: ``` ``` Ohio 35000 ``` ``` Oregon 16000 ``` ``` Texas 71000 ``` ``` Utah 5000 ``` ``` dtype: int64 ``` ``` ``` ``` obj4 ``` ``` ``` ``` 输出结果: ``` ``` California NaN ``` ``` Ohio 35000.0 ``` ``` Oregon 16000.0 ``` ``` Texas 71000.0 ``` ``` dtype: float64 ``` ``` ``` ``` obj3 + obj4 ``` ``` ``` ``` 输出结果: ``` ``` California NaN ``` ``` Ohio 70000.0 ``` ``` Oregon 32000.0 ``` ``` Texas 142000.0 ``` ``` Utah NaN ``` ``` dtype: float64 ``` 数据对齐功能将在后面详细讲解。如果你使用过数据库,你可以认为是类似join的操作。 Series对象本身及其索引都有一个name属性,该属性跟pandas其他的关键功能关系非常密切: ``` obj4.name = 'population' ``` ``` ``` ``` obj4.index.name = 'state' ``` ``` ``` ``` obj4 ``` ``` ``` ``` 输出结果: ``` ``` state ``` ``` California NaN ``` ``` Ohio 35000.0 ``` ``` Oregon 16000.0 ``` ``` Texas 71000.0 ``` ``` Name: population, dtype: float64 ``` Series的索引可以通过赋值的方式就地修改: ``` obj ``` ``` ``` ``` 输出结果: ``` ``` 0 4 ``` ``` 1 7 ``` ``` 2 -5 ``` ``` 3 3 ``` ``` dtype: int64 ``` ``` ``` ``` obj.index = ['Bob', 'Steve', 'Jeff', 'Ryan'] ``` ``` ``` ``` obj ``` ``` ``` ``` 输出结果: ``` ``` Bob 4 ``` ``` Steve 7 ``` ``` Jeff -5 ``` ``` Ryan 3 ``` ``` dtype: int64 ``` ### **6.3Pandas的数据结构DataFrame** DataFrame是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame既有行索引也有列索引,它可以被看做由系列组成的字典(共用同一个索引)。DataFrame中的数据是以一个或多个二维块存放的(而不是列表、字典或别的一维数据结构)。有关DataFrame内部的技术细节远远超出了本实验所讨论的范围。 笔记:虽然DataFrame是以二维结构保存数据的,但你仍然可以轻松地将其表示为更高维度的数据(层次化索引的表格型结构,这是pandas中许多高级数据处理功能的关键要素)。 #### **6.3.1 创建数据帧** 建DataFrame的办法有很多,最常用的一种是直接传入一个由等长列表或NumPy数组组成的字典: ``` data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'], ``` ``` 'year': [2000, 2001, 2002, 2001, 2002, 2003], ``` ``` 'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]} ``` ``` frame = pd.DataFrame(data) ``` 结果DataFrame会自动加上索引(跟系列一样),且全部列会被有序排列: ``` frame ``` ``` ``` ``` 输出结果: ``` ``` state year pop ``` ``` 0 Ohio 2000 1.5 ``` ``` 1 Ohio 2001 1.7 ``` ``` 2 Ohio 2002 3.6 ``` ``` 3 Nevada 2001 2.4 ``` ``` 4 Nevada 2002 2.9 ``` ``` 5 Nevada 2003 3.2 ``` #### **6.3.2 对数据帧进行操作** 对于特别大的DataFrame,head方法会选取前五行: ``` frame.head() ``` ``` ``` ``` 输出结果: ``` ``` state year pop ``` ``` 0 Ohio 2000 1.5 ``` ``` 1 Ohio 2001 1.7 ``` ``` 2 Ohio 2002 3.6 ``` ``` 3 Nevada 2001 2.4 ``` ``` 4 Nevada 2002 2.9 ``` 如果指定了列序列,则DataFrame的列就会按照指定顺序进行排列: ``` pd.DataFrame(data, columns=['year', 'state', 'pop']) ``` ``` ` ` ``` ``` 输出结果: ``` ``` year state pop ``` ``` 0 2000 Ohio 1.5 ``` ``` 1 2001 Ohio 1.7 ``` ``` 2 2002 Ohio 3.6 ``` ``` 3 2001 Nevada 2.4 ``` ``` 4 2002 Nevada 2.9 ``` ``` 5 2003 Nevada 3.2 ``` 如果传入的列在数据中找不到,就会在结果中产生缺失值: ``` frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'], index=['one', 'two', 'three', 'four', 'five', 'six']) ``` ``` ``` ``` frame2 ``` ``` ``` ``` 输出结果: ``` ``` year state pop debt ``` ``` one 2000 Ohio 1.5 NaN ``` ``` two 2001 Ohio 1.7 NaN ``` ``` three 2002 Ohio 3.6 NaN ``` ``` four 2001 Nevada 2.4 NaN ``` ``` five 2002 Nevada 2.9 NaN ``` ``` six 2003 Nevada 3.2 NaN ``` ``` ``` ``` frame2.columns ``` ``` ``` ``` 输出结果: ``` ``` Index(['year', 'state', 'pop', 'debt'], dtype='object') ``` 通过类似字典标记的方式或属性的方式,可以将DataFrame的列获取为一个系列: ``` frame2['state'] ``` ``` ``` ``` 输出结果: ``` ``` one Ohio ``` ``` two Ohio ``` ``` three Ohio ``` ``` four Nevada ``` ``` five Nevada ``` ``` six Nevada ``` ``` Name: state, dtype: object ``` ``` ``` ``` frame2.year ``` ``` ``` ``` 输出结果: ``` ``` one 2000 ``` ``` two 2001 ``` ``` three 2002 ``` ``` four 2001 ``` ``` five 2002 ``` ``` six 2003 ``` ``` Name: year, dtype: int64 ``` 笔记:IPython提供了类似属性的访问(即frame2.year)和tab补全。 frame2\[column\]适用于任何列的名,但是frame2.column只有在列名是一个合理的Python变量名时才适用。 注意,返回的Series拥有原DataFrame相同的索引,且其name属性也已经被相应地设置好了。 行也可以通过位置或名称的方式进行获取,比如用loc属性(稍后将对此进行详细讲解): ``` frame2.loc['three'] ``` ``` ``` ``` 输出结果: ``` ``` year 2002 ``` ``` state Ohio ``` ``` pop 3.6 ``` ``` debt NaN ``` ``` Name: three, dtype: object ``` #### **6.3.3 修改列** 列可以通过赋值的方式进行修改。例如,我们可以给那个空的"debt"列赋上一个标量值或一组值: ``` frame2['debt'] = 16.5 ``` ``` ``` ``` frame2 ``` ``` ``` ``` 输出结果: ``` ``` year state pop debt ``` ``` one 2000 Ohio 1.5 16.5 ``` ``` two 2001 Ohio 1.7 16.5 ``` ``` three 2002 Ohio 3.6 16.5 ``` ``` four 2001 Nevada 2.4 16.5 ``` ``` five 2002 Nevada 2.9 16.5 ``` ``` six 2003 Nevada 3.2 16.5 ``` ``` ``` ``` frame2['debt'] = np.arange(6.) ``` ``` ``` ``` frame2 ``` ``` ``` ``` 输出结果: ``` ``` year state pop debt ``` ``` one 2000 Ohio 1.5 0.0 ``` ``` two 2001 Ohio 1.7 1.0 ``` ``` three 2002 Ohio 3.6 2.0 ``` ``` four 2001 Nevada 2.4 3.0 ``` ``` five 2002 Nevada 2.9 4.0 ``` ``` six 2003 Nevada 3.2 5.0 ``` 将列表或数组赋值给某个列时,其长度必须跟DataFrame的长度相匹配。如果赋值的是一个系列,就会精确匹配DataFrame的索引,所有的空位都将被填上缺失值: ``` val = pd.Series([-1.2, -1.5, -1.7], index=['two', 'four', 'five']) ``` ``` ``` ``` frame2['debt'] = val ``` ``` ``` ``` frame2 ``` ``` ``` ``` 输出结果: ``` ``` year state pop debt ``` ``` one 2000 Ohio 1.5 NaN ``` ``` two 2001 Ohio 1.7 -1.2 ``` ``` three 2002 Ohio 3.6 NaN ``` ``` four 2001 Nevada 2.4 -1.5 ``` ``` five 2002 Nevada 2.9 -1.7 ``` ``` six 2003 Nevada 3.2 NaN ``` 为不存在的列赋值会创建出一个新列。关键字del用于删除列。 作为del的例子,我先添加一个新的布尔值的列,state是否为'Ohio': ``` frame2['eastern'] = frame2.state == 'Ohio' ``` ``` ``` ``` frame2 ``` ``` ``` ``` 输出结果: ``` ``` year state pop debt eastern ``` ``` one 2000 Ohio 1.5 NaN True ``` ``` two 2001 Ohio 1.7 -1.2 True ``` ``` three 2002 Ohio 3.6 NaN True ``` ``` four 2001 Nevada 2.4 -1.5 False ``` ``` five 2002 Nevada 2.9 -1.7 False ``` ``` six 2003 Nevada 3.2 NaN False ``` 注意:不能用frame2.eastern创建新的列。 del方法可以用来删除这列: ``` del frame2['eastern'] ``` ``` ``` ``` frame2.columns ``` ``` ``` ``` 输出结果: ``` ``` Index(['year', 'state', 'pop', 'debt'], dtype='object') ``` 注意:通过索引方式返回的列只是相应数据的视图而已,并不是副本。因此,对返回的Series所做的任何就地修改全都会反映到源DataFrame上。通过系列的copy方法即可指定复制列。 另一种常见的数据形式是嵌套字典: ``` pop = {'Nevada': {2001: 2.4, 2002: 2.9},'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}} ``` 如果嵌套字典传给DataFrame,pandas就会被解释为:外层字典的键作为列,内层键则作为行索引: ``` frame3 = pd.DataFrame(pop) ``` ``` ``` ``` frame3 ``` ``` ``` ``` 输出结果: ``` ``` Nevada Ohio ``` ``` 2000 NaN 1.5 ``` ``` 2001 2.4 1.7 ``` ``` 2002 2.9 3.6 ``` 你也可以使用类似NumPy数组的方法,对DataFrame进行转置(交换行和列): ``` frame3.T ``` ``` ``` ``` 输出结果: ``` ``` 2000 2001 2002 ``` ``` Nevada NaN 2.4 2.9 ``` ``` Ohio 1.5 1.7 3.6 ``` 内层字典的键会被合并、排序以形成最终的索引。如果明确指定了索引,则不会这样: ``` pd.DataFrame(pop, index=[2001, 2002, 2003]) ``` ``` ``` ``` 输出结果: ``` ``` Nevada Ohio ``` ``` 2001 2.4 1.7 ``` ``` 2002 2.9 3.6 ``` ``` 2003 NaN NaN ``` 由系列组成的字典差不多也是一样的用法: ``` pdata = {'Ohio': frame3['Ohio'][:-1],'Nevada': frame3['Nevada'][:2]} ``` ``` ``` ``` pd.DataFrame(pdata) ``` ``` ``` ``` 输出结果: ``` ``` Nevada Ohio ``` ``` 2000 NaN 1.5 ``` ``` 2001 2.4 1.7 ``` 图片列出了DataFrame构造函数所能接受的各种数据。  #### **6.3.4 设置数据帧属性** 如果设置了DataFrame的index和columns的name属性,则这些信息也会被显示出来: ``` frame3.index.name = 'year'; frame3.columns.name = 'state' ``` ``` ``` ``` frame3 ``` ``` ``` ``` 输出结果: ``` ``` state Nevada Ohio ``` ``` year ``` ``` 2000 NaN 1.5 ``` ``` 2001 2.4 1.7 ``` ``` 2002 2.9 3.6 ``` 跟系列一样,values属性也会以二维ndarray的形式返回DataFrame中的数据: ``` frame3.values ``` ``` ``` ``` 输出结果: ``` ``` array([[ nan, 1.5], ``` ``` [ 2.4, 1.7], ``` ``` [ 2.9, 3.6]]) ``` 如果DataFrame各列的数据类型不同,则值数组的dtype就会选用能兼容所有列的数据类型: ``` frame2.values ``` ``` ``` ``` 输出结果: ``` ``` array([[2000, 'Ohio', 1.5, nan], ``` ``` [2001, 'Ohio', 1.7, -1.2], ``` ``` [2002, 'Ohio', 3.6, nan], ``` ``` [2001, 'Nevada', 2.4, -1.5], ``` ``` [2002, 'Nevada', 2.9, -1.7], ``` ``` [2003, 'Nevada', 3.2, nan]], dtype=object) ``` ### **6.4索引对象** pandas的索引对象负责管理轴标签和其他元数据(比如轴名称等)。构建序列或DataFrame时,所用到的任何数组或其他序列的标签都会被转换成一个Index: ``` obj = pd.Series(range(3), index=['a', 'b', 'c']) ``` ``` ``` ``` index = obj.index ``` ``` ``` ``` index ``` ``` ``` ``` 输出结果: ``` ``` Index(['a', 'b', 'c'], dtype='object') ``` ``` ``` ``` index[1:] ``` ``` ``` ``` 输出结果: ``` ``` Index(['b', 'c'], dtype='object') ``` Index对象是不可变的,因此用户不能对其进行修改: ``` index[1] = 'd' # TypeError ``` 不可变可以使Index对象在多个数据结构之间安全共享: ``` ``` ``` labels = pd.Index(np.arange(3)) ``` ``` ``` ``` labels ``` ``` ``` ``` 输出结果: ``` ``` Int64Index([0, 1, 2], dtype='int64') ``` ``` ``` ``` obj2 = pd.Series([1.5, -2.5, 0], index=labels) ``` ``` ``` ``` obj2 ``` ``` ``` ``` 输出结果: ``` ``` 0 1.5 ``` ``` 1 -2.5 ``` ``` 2 0.0 ``` ``` dtype: float64 ``` ``` ``` ``` obj2.index is labels ``` ``` ``` ``` 输出结果: ``` ``` True ``` 注意:虽然用户不需要经常使用Index的功能,但是因为一些操作会生成包含被索引化的数据,理解它们的工作原理是很重要的。 除了类似于数组,Index的功能也类似一个固定大小的集合: ``` frame3 ``` ``` ``` ``` 输出结果: ``` ``` state Nevada Ohio ``` ``` year ``` ``` 2000 NaN 1.5 ``` ``` 2001 2.4 1.7 ``` ``` 2002 2.9 3.6 ``` ``` ``` ``` frame3.columns ``` ``` ``` ``` 输出结果: ``` ``` Index(['Nevada', 'Ohio'], dtype='object', name='state') ``` ``` ``` ``` 'Ohio' in frame3.columns ``` ``` ``` ``` 输出结果: ``` ``` True ``` ``` ``` ``` 2003 in frame3.index ``` ``` ``` ``` 输出结果: ``` ``` False ``` 与python的集合不同,pandas的Index可以包含重复的标签: ``` dup_labels = pd.Index(['foo', 'foo', 'bar', 'bar']) ``` ``` ``` ``` dup_labels ``` ``` ``` ``` 输出结果: ``` ``` Index(['foo', 'foo', 'bar', 'bar'], dtype='object') ``` 选择重复的标签,会显示所有的结果。 ### **6.5Index 方法和属性列表** 每个索引都有一些方法和属性,它们可用于设置逻辑并回答有关该索引所包含的数据的常见问题。图片列出了这些函数。  drop()方法: : | **参数说明** | | |------------|-------------------------------------------| | 标签 | 就是要删除的行列的名字,用列表给定 | | axis 默认为 0 | 指删除行,因此删除列 时要指定 axis=1; | | 指数 | 直接指定要删除的行 | | 列 | 直接指定要删除的列 | | 就地 = 假 | 默认该删除操作不改变原数据,而是返回一个执行删除操作后的 新 dataframe; | | 就地 = 真 | 则会直接在原数据上进行删除操作,删除后无法返回。 | ``` series = Series([4.5,7.2,-5.3,3.6], index=['A','B','C','D']) ``` ``` series .drop('C') ``` ``` ``` ``` 输出结果: ``` ``` A 4.5 ``` ``` B 7.2 ``` ``` D 3.6 ``` ``` dtype: float64 ``` ``` df = pd.DataFrame(np.arange(12).reshape(3,4), columns=['A', 'B', 'C', 'D']) ``` ``` df ``` ``` ``` ``` 输出结果: ``` ``` A B C D ``` ``` 0 0 1 2 3 ``` ``` 1 4 5 6 7 ``` ``` 2 8 9 10 11 ``` ``` ``` ``` #Drop columns, 两种方法等价 ``` ``` df.drop(['B', 'C'], axis=1) ``` ``` ``` ``` 输出结果: ``` ``` A D ``` ``` 0 0 3 ``` ``` 1 4 7 ``` ``` 2 8 11 ``` ``` ``` ``` df.drop(columns=['B', 'C']) ``` ``` ``` ``` 输出结果: ``` ``` A D ``` ``` 0 0 3 ``` ``` 1 4 7 ``` ``` 2 8 11 ``` 第一种方法下删除column一定要指定axis=1,否则会报错,如下 ``` ``` ``` df.drop(['B', 'C']) ``` ``` ``` ``` 输出结果: ``` ``` KeyError: "['B' 'C'] not found in axis" ``` 删除行 ``` ``` ``` df.drop([0, 1]) ``` ``` ``` ``` 输出结果: ``` ``` A B C D ``` ``` 2 8 9 10 11 ``` ``` ``` ``` df.drop(index=[0, 1]) ``` ``` ``` ``` 输出结果: ``` ``` A B C D ``` ``` 2 8 9 10 11 ``` ``` ``` ``` df.drop ``` 删除源数据 ``` df.drop(['A', 'D'],axis=1,inplace=True) ``` ``` ``` ``` df ``` ``` ``` ``` 输出结果: ``` ``` ``` ``` B C ``` ``` 0 1 2 ``` ``` 1 5 6 ``` ``` 2 9 10 ```