利用Python进行数据分析(原书第2版)

韦斯·麦金尼

使用代码示例

  • GitHub仓库地址:http://github.com/wesm/pydata-book。

1.2 为何利用Python进行数据分析

  • Python在科学计算方面的成功部分是因为它很容易整合C、C++和FORTRAN等语言的代码。
  • 由于Python是一个解释型语言,大多数情况下Python代码的运行效率会低于Java或C++等编译型语言。因为开发者时间通常比CPU时间更有价值,很多人就愉快地选择了使用Python。

1.3 重要的Python库

  • pandas尤其擅长深度时间序列和处理商业进程中产生的时间索引数据。

1.4 安装与设置

  • 当你能够同时使用conda和pip进行包安装时,请不要尝试使用pip更新conda安装的包,否则可能会导致环境问题。当使用Anaconda或者Miniconda时,最好还是使用conda进行更新。

1.6 快速浏览本书

  • 与外部世界交互读写各种格式的文件以及数据存储准备对分析数据进行清洗、处理、联合、正态化、重组、切片、切块和转换转换将数学或统计操作应用到数据集的分组上以产生新的数据集(例如通过分组参数对一张大表进行聚合)建模和计算将数据接入到统计模型、机器学习算法和其他计算工具上演示创建动态或静态的图形可视化或文字概述
  • 每章的示例数据托管在GitHub仓库(http://github.com/wesm/pydata-book)上。
  • import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import statsmodels as sm
  • 处理/处置/规整(munge/munging/wrangling)描述的是将非结构化或者同时又很凌乱的数据整理成结构化、清晰形式的整个过程。

第2章 Python语言基础、IPython及Jupyter notebook

  • 推荐使用IPython命令行和Jupyter notebook来实验代码示例,以及探索各种类型、函数和方法的文档。
  • 和其他键盘控制的命令行环境一样,练就常用命令的肌肉记忆也是学习曲线的一部分。
  • ·《Python Cookbook》(第3版),作者为David Beazley和Brian K. Jones(O'Reilly) ·《Fluent Python》,作者为Luciano Ramalho(O'Reilly) ·《Effective Python》,作者为Brett Slatkin(Pearson)

2.1 Python解释器

  • IPython是一个加强版的Python解释器,Juypyter notebook是一种基于Web的代码笔记本,最初也是源于IPython项目。

2.2 IPython基础

  • 与常见的print打印语句不同,IPython中大多数Python对象被格式化为更可读、更美观的形式。
  • Jupyter项目中的主要组件就是notebook,这是一种交互式的文档类型,可以用于编写代码、文本(可以带标记)、数据可视化以及其他输出
  • tab补全的另一个应用场景是在函数的关键字参数(包含=号)中节约时间
  • 在一个变量名的前后使用问号(?)可以显示一些关于该对象的概要信息
  • 然后使用?来显示文档字符串:
  • 使用双问号??可以显示函数的源代码:
  • ?有一个终极用途,可以像标准Unix或Windows命令行一样搜索IPython命名空间。把一些字符和通配符(星号*)结合在一起,会显示所有匹配通配符表达式的命名。
  • 在Jupyter notebook中,如果你想将脚本导入一个代码单元,可以使用%load魔术函数:
  • %paste会获得剪贴板中的所有文本,并在命令行中作为一个代码块去执行
  • 使用%cpaste,你可以自由地在执行代码前尽可能多地粘贴代码。你也许会想着使用%cpaste在执行前看下粘贴的代码,如果你发现粘贴的代码有误,可以按下Ctrl-C来中断%cpaste提示符。
  • 魔术命令的前缀符号是百分号%。
  • 魔术函数也可以不加百分号%就使用,只要没有变量被定义为与魔术函数相同的名字即可。这种特性被称为自动魔术,通过%automagic进行启用/禁用关。
  • %matplotlib魔术函数可以设置matplotlib与IPython命令行或Jupyter notebook的集成

2.3 Python语言基础

  • Python使用缩进(tab或者空格)来组织代码
  • 一个冒号代表一个缩进代码块的开始,单个代码块中所有的代码必须保持相同的缩进,直到代码块结束。
  • 我强烈推荐使用四个空格作为你的默认缩进,而不是用tab。
  • 几乎所有的Python对象都有内部函数,称为方法,可以访问到对象内部的内容。
  • 在Python中对一个变量(或者变量名)赋值时,你就创建了一个指向等号右边对象的引用。
  • 变量对于对象来说只是特定命名空间中的名称;类型信息是存储在对象自身之中的
  • isinstance接受一个包含类型的元组,你可以检查对象的类型是否在元组中的类型中
  • 属性和方法也可以通过getattr函数获得
  • 检查两个引用是否指向同一个对象,可以使用is关键字。is not在你想检查两个对象不是相同对象时也是有效的。
  • 还有其他一些对象是不可变的,比如字符串、元组:
  • 整数除法会把结果自动转型为浮点数:
  • 对于含有换行的多行字符串,你可以使用三个单引号’'’或三个双引号"""
  • s[:3]这种语法被称为切片
  • 你可以在字符串前面加一个前缀符号r,表明这些字符是原生字符
  • 字符串对象拥有一个format方法,可以用来代替字符串中的格式化参数,并产生一个新的字符串
  • 我们可以使用enocde方法将这个Unicode字符串转换为UTF-8字节:
  • None是Python的null值类型。如果一个函数没有显式地返回值,则它会隐式地返回None
  • None还可以作为一个常用的函数参数默认值:
  • strftime方法将datetime转换为字符串
  • 使用continue关键字可以跳过conitnue后面的代码进入下一次循环。
  • pass就是Python中的“什么都不做”的语句。它用于在代码段中表示不执行任何操作(或者是作为还没有实现的代码占位符);之所以需要它,是因为Python使用了缩进来分隔代码块:
  • range产生的整数包含起始但不包含结尾
  • Python中的三元表达式允许你将一个if-else代码块联合起来,在一行代码或一个语句中生成数据。

第3章 内建数据结构、函数及文件

  • 元组、列表、字典和集合

3.1 数据结构和序列

  • 你可以使用tuple函数将任意序列或迭代器转换为元组:
  • 这个功能使用特殊的语法*rest,用于在函数调用时获取任意长度的位置参数列表:
  • list函数在数据处理中常用于将迭代器或者生成器转化为列表
  • 请注意通过添加内容来连接列表是一种相对高代价的操作,这是因为连接过程中创建了新列表,并且还要复制对象。使用extend将元素添加到已经存在的列表是更好的方式,尤其是在你需要构建一个大型列表时:
  • Python内建了enumerate函数,返回了(i, value)元组的序列,其中value是元素的值,i是元素的索引
  • zip将列表、元组或其他序列的元素配对,新建一个元组构成的列表
  • 通过hash函数可以检查一个对象是否可以哈希化(即是否可以用作字典的键)

3.2 函数

  • 函数声明时使用def关键字,返回时使用return关键字
  • 函数参数的主要限制是关键字参数必须跟在位置参数(如果有的话)后。
  • 函数有两种连接变量的方式:全局、本地。
  • 在函数外部给变量赋值是可以的,但是那些变量必须使用global关键字声明为全局变量:
  • 和def关键字声明的函数不同,匿名函数对象自身并没有一个显式的__name__属性,这是lambda函数被称为匿名函数的一个原因。
  • 内建的functools模块可以使用pratial函数简化这种处理:
  • 如需创建一个生成器,只需要在函数中将返回关键字return替换为yield关键字:
  • groupby可以根据任意的序列和一个函数,通过函数的返回值对序列中连续的元素进行分组

3.3 文件与操作系统

  • 打开文件进行读取或写入,需要使用内建函数open和绝对、相对路径
  • 使用with语句,文件会在with代码块结束后自动关闭。
  • read方法通过读取的字节数来推进文件句柄的位置。tell方法可以给出句柄当前的位置

第4章 NumPy基础:数组与向量化计算

  • NumPy的方法比Python方法要快10到100倍,并且使用的内存也更少

4.1 NumPy ndarray:多维数组对象

  • 一个ndarray是一个通用的多维同类数据容器,也就是说,它包含的每一个元素均为相同类型。每一个数组都有一个shape属性,用来表征数组每一维度的数量;每一个数组都有一个dtype属性,用来描述数组的数据类型:
  • array函数接收任意的序列型对象(当然也包括其他的数组),生成一个新的包含传递数据的NumPy数组。
  • arange是Python内建函数range的数组版
  • dtype是NumPy能够与其他系统数据灵活交互的原因。
  • 你可以使用astype方法显式地转换数组的数据类型:
  • 数组之所以重要是因为它允许你进行批量操作而无须任何for循环。NumPy用户称这种特性为向量化
  • 带有标量计算的算术操作,会把计算参数传递给数组的每一个元素
  • 同尺寸数组之间的比较,会产生一个布尔值数组
  • 不同尺寸的数组间的操作,将会用到广播特性
  • 区别于Python的内建列表,数组的切片是原数组的视图。这意味着数据并不是被复制了,任何对于视图的修改都会反映到原数组上。
  • 在多维数组中,你可以省略后续索引值,返回的对象将是降低一个维度的数组。
  • 需要注意的是,以上的数组子集选择中,返回的数组都是视图
  • 单独一个冒号表示选择整个轴上的数组
  • 布尔值数组的长度必须和数组轴索引长度一致。
  • ~符号可以在你想要对一个通用条件进行取反时使用
  • Python的关键字and和or对布尔值数组并没有用,请使用&(and)和|(or)来代替。
  • 神奇索引是NumPy中的术语,用于描述使用整数数组进行数据索引。
  • 为了选出一个符合特定顺序的子集,你可以简单地通过传递一个包含指明所需顺序的列表或数组来完成
  • 请牢记神奇索引与切片不同,它总是将数据复制到一个新的数组中。
  • 当计算矩阵内积会使用np.dot:
  • swapaxes返回的是数据的视图,而没有对数据进行复制。

4.2 通用函数:快速的逐元素数组函数

  • 通用函数,也可以称为ufunc,是一种在ndarray数据中进行逐元素操作的函数
  • modf,是Python内建函数divmod的向量化版本。它返回了一个浮点值数组的小数部分和整数部分:
  • 通用函数接收一个可选参数out,允许对数组按位置操作
  • 表4-3:一元通用函数

4.3 使用数组进行面向数组编程

  • 这种利用数组表达式来替代显式循环的方法,称为向量化
  • np.meshgrid函数接收两个一维数组,并根据两个数组的所有(x, y)对生成一个二维矩阵:
  • numpy.where函数是三元表达式x if condition else y的向量化版本。
  • 传递给np.where的数组既可以是同等大小的数组,也可以是标量
  • arr.mean(1)表示“计算每一列的平均值”,而arr.sum(0)表示“计算行轴向的累和”。
  • 表4-5:基础数组统计方法
  • 对于布尔值数组,有两个非常有用的方法any和all。any检查数组中是否至少有一个True,而all检查是否每个值都是True:
  • 常用的一个方法是np.unique,返回的是数组中唯一值排序后形成的数组:

4.4 使用数组进行文件输入和输出

  • np.save和np.load是高效存取硬盘数据的两大工具函数。

4.5 线性代数

  • NumPy的线性代数中所不同的是*是矩阵的逐元素乘积,而不是矩阵的点乘积
  • x.dot(y)等价于np.dot(x, y)
  • 一个二维数组和一个长度合适的一维数组之间的矩阵乘积,其结果是一个一维数组:
  • 特殊符号@也作为中缀操作符,用于点乘矩阵操作:
  • 表4-7:常用numpy.linalg函数

4.6 伪随机数生成

  • 表4-8:numpy.random中的部分函数列表

第5章 pandas入门

  • 但最大的不同在于pandas是用来处理表格型或异质型数据的。而NumPy则相反,它更适合处理同质型的数值类数组数据。

5.1 pandas数据结构介绍

  • Series是一种一维的数组型对象,它包含了一个值序列(与NumPy中的类型相似),并且包含了数据标签,称为索引(index)
  • 通过values属性和index属性分别获得Series对象的值和索引
  • 使用NumPy的函数或NumPy风格的操作,比如使用布尔值数组进行过滤,与标量相乘,或是应用数学函数,这些操作将保存索引值连接:
  • 从另一个角度考虑Series,可以认为它是一个长度固定且有序的字典,因为它将索引值和数据值按位置配对。
  • 如果你已经有数据包含在Python字典中,你可以使用字典生成一个Series:
  • 当你把字典传递给Series构造函数时,产生的Series的索引将是排序好的字典键。你可以将字典键按照你所想要的顺序传递给构造函数,从而使生成的Series的索引顺序符合你的预期:
  • pandas中使用isnull和notnull函数来检查缺失数据
  • Series对象自身和其索引都有name属性
  • Series的索引可以通过按位置赋值的方式进行改变
  • DataFrame既有行索引也有列索引,它可以被视为一个共享相同索引的Series的字典。
  • 对于大型DataFrame, head方法将会只选出头部的五行
  • DataFrame中的一列,可以按字典型标记或属性那样检索为Series
  • frame2[colunm]对于任意列名均有效,但是frame2.column只在列名是有效的Python变量名时有效。
  • 如果你将Series赋值给一列时,Series的索引将会按照DataFrame的索引重新排列,并在空缺的地方填充缺失值:
  • frame2.eastern的语法无法创建新的列。
  • 从DataFrame中选取的列是数据的视图,而不是拷贝。因此,对Series的修改会映射到DataFrame中。如果需要复制,则应当显式地使用Series的copy方法。
  • 表5-1:DataFrame构造函数的有效输入
  • 索引对象是不可变的,因此用户是无法修改索引对象的
  • 与Python集合不同,pandas索引对象可以包含重复标签
  • 表5-2:一些索引对象的方法和属性

5.2 基本功能

  • reindex是pandas对象的重要方法,该方法用于创建一个符合新索引的新对象
  • method可选参数允许我们使用诸如ffill等方法在重建索引时插值,ffill方法会将值前向填充
  • drop方法会返回一个含有指示值或轴向上删除值的新对象
  • 你可以通过传递axis=1或axis='columns’来从列中删除值:
  • 普通的Python切片中是不包含尾部的,Series的切片与之不同:
  • 行选择语法data[:2]非常方便。传递单个元素或一个列表到[]符号中可以选择列。
  • 为了更精确地处理,可以使用loc(用于标签)或iloc(用于整数):
  • 当你将对象相加时,如果存在某个索引对不相同,则返回结果的索引将是索引对的并集。
  • 没有交叠的标签位置上,内部数据对齐会产生缺失值
  • 如果你将两个行或列完全不同的DataFrame对象相加,结果将全部为空
  • 在df1上使用add方法,我将df2和一个fill_value作为参数传入
  • 当我们从arr中减去arr[0]时,减法在每一行都进行了操作。这就是所谓的广播机制
  • 默认情况下,DataFrame和Series的数学操作中会将Series的索引和DataFrame的列进行匹配,并广播到各行
  • 如果一个索引值不在DataFrame的列中,也不在Series的索引中,则对象会重建索引并形成联合
  • 你传递的axis值是用于匹配轴的。上面的示例中表示我们需要在DataFrame的行索引上对行匹配(axis='index’或axis=0),并进行广播。
  • NumPy的通用函数(逐元素数组方法)对pandas对象也有效
  • 另一个常用的操作是将函数应用到一行或一列的一维数组上。DataFrame的apply方法可以实现这个功能:
  • 如果要根据Series的值进行排序,使用sort_values方法
  • 默认情况下,rank通过将平均排名分配到每个组来打破平级关系
  • 索引的is_unique属性可以告诉你它的标签是否唯一

5.3 描述性统计的概述与计算

  • 除非整个切片上(在本例中是行或列)都是NA,否则NA值是被自动排除的。可以通过禁用skipna来实现不排除NA值
  • 表5-8:描述性统计和汇总统计
  • Series的corr方法计算的是两个Series中重叠的、非NA的、按索引对齐的值的相关性。相应地,cov计算的是协方差:
  • DataFrame的corr和cov方法会分别以DataFrame的形式返回相关性和协方差矩阵
  • value_counts计算Series包含的值的个数
  • 与isin相关的Index.get_indexer方法,可以提供一个索引数组,这个索引数组可以将可能非唯一值数组转换为另一个唯一值数组:

6.1 文本格式数据的读写

  • read_csv和read_table可能是后期我们使用最多的函数
  • na_values选项可以传入一个列表或一组字符串来处理缺失值
  • 如果你只想读取一小部分行(避免读取整个文件),可以指明nrows
  • 为了分块读入文件,可以指定chunksize作为每一块的行数
  • 使用DataFrame的to_csv方法,我们可以将数据导出为逗号分隔的文件:
  • 将JSON字符串转换为Python形式时,使用json.loads方法

6.2 二进制格式

  • 如需将pandas数据写入到Excel格式中,你必须先生成一个ExcelWriter,然后使用pandas对象的to_excel方法将数据写入:

6.4 与数据库交互

  • SQLAlchemy项目(http://www.sqlalchemy.org/)是一个流行的Python SQL工具包,抽象去除了SQL数据库之间的许多常见差异。pandas有一个read_sql函数允许你从通用的SQLAlchemy连接中轻松地读取数据

7.1 处理缺失值

  • Python内建的None值在对象数组中也被当作NA处理
  • dropna默认情况下会删除包含缺失值的行
  • 传入how='all’时,将删除所有值均为NA的行:
  • 在调用fillna时使用字典,你可以为不同列设定不同的填充值:
  • fillna返回的是一个新的对象,但你也可以修改已经存在的对象:

7.2 数据转换

  • DataFrame的duplicated方法返回的是一个布尔值Series,这个Series反映的是每一行是否存在重复(与之前出现过的行相同)情况
  • drop_duplicates返回的是DataFrame,内容是duplicated返回数组中为False的部分
  • duplicated和drop_duplicates默认都是保留第一个观测到的值。传入参数keep='last’将会返回最后一个:
  • Series的map方法接收一个函数或一个包含映射关系的字典型对象
  • 使用map是一种可以便捷执行按元素转换及其他清洗相关操作的方法。
  • 参数也可以通过字典传递:
  • 如果你想要创建数据集转换后的版本,并且不修改原有的数据集,一个有用的方法是rename:
  • 要选出所有值大于3或小于-3的行,你可以对布尔值DataFrame使用any方法
  • 语句np.sign(data)根据数据中的值的正负分别生成1和-1的数值

7.3 字符串操作

  • split常和strip一起使用,用于清除空格(包括换行)
  • 请注意find和index的区别在于index在字符串没有找到时会抛出一个异常(而find是返回-1)
  • count返回的是某个特定的子字符串在字符串中出现的次数
  • 描述一个或多个空白字符的正则表达式是\s+:
  • 如果你需要将相同的表达式应用到多个字符串上,推荐使用re.compile创建一个正则表达式对象,这样做有利于节约CPU周期。

8.1 分层索引

  • swaplevel接收两个层级序号或层级名称,返回一个进行了层级变更的新对象(但是数据是不变的)
  • DataFrame的set_index函数会生成一个新的DataFrame,新的DataFrame使用一个或多个列作为索引:

8.2 联合与合并数据集

  • pandas中的merge函数主要用于将各种join操作算法运用在你的数据上:
  • 在DataFrame中,combine_first逐列做相同的操作,因此你可以认为它是根据你传入的对象来”修补“调用对象的缺失值:

8.3 重塑和透视

  • statck(堆叠)该操作会“旋转”或将列中的数据透视到行。unstack(拆堆)该操作会将行中的数据透视到列。
  • 默认情况下,堆叠会过滤出缺失值,因此堆叠拆堆的操作是可逆的:
  • pivot方法等价于使用set_index创建分层索引,然后调用unstack:

9.1 简明matplotlib API入门

  • 使用Jupyter notebook时有个细节需要注意,在每个单元格运行后,图表被重置,因此对于更复杂的图表,你必须将所有的绘图命令放在单个的notebook单元格中。

10.1 GroupBy机制

  • GroupBy对象支持迭代,会生成一个包含组名和数据块的2维元组序列。

10.2 数据聚合

  • 要使用你自己的聚合函数,需要将函数传递给aggregate或agg方法

11.2 时间序列基础

  • 假设你想要聚合含有非唯一时间戳的数据。一种方式就是使用groupby并传递level=0:

11.3 日期范围、频率和移位

  • pandas.date_range是用于根据特定频率生成指定长度的DatetimeIndex:

13.1 pandas与建模代码的结合

  • 特征工程是指从原生数据集中提取可用于模型上下文的有效信息的数据转换过程或分析。