第1章 IPython:超越Python
IPython : interactive python
一个交互式控制面板
Ipython被紧密地连接在Jupyter项目中
IPython notebook只是Jupyter notebook的一个特例
shell 和 notebook
启动shell: 命令行输入ipython
ipython notebook通过你的Web浏览器窗口进行查看和编辑,但是必须与一个正在运行的python进程(kernel 也叫核)连接才能执行代码。
启动进程输入 jupyter notebook
帮助和文档
学习如何有效的找到未知信息很重要
IPython能够算短用户与帮助文档和搜索之间的距离,
IPython能帮你解答的一些问题:
- 我如何调用这个函数,这个函数有哪些参数和选项
- 这个Python对象的源代码是怎么样的
- 我导入的包中有声明?这个对象有哪些属性和方法
利用符号?来获取文档
每一个python对象都有一个字符串的引用,该字符串叫docstring
该字符串包含对象的简要介绍和使用方法。python内置的help()函数可以获取这个信息,比如查看len函数文档
help(len)
IPython引入了?符号获取这个文档和其他信息的缩写
len?
Signature: len(obj, /)
Docstring: Return the number of items in a container.
Type: builtin_function_or_method
?符号也适用于你自己创建的函数或者其他对象
def square(a):
"""return the square of a"""
a**2
通过??符号获取源代码
square??
有时候??符号等同于?后缀,因为那些对象不是用python实现的
用Tab键可以自动补全和探索对象、模块等
每一个python对象都包括各种属性和方法。
python有一个内置的dir函数,可以返回一个属性和方法的列表,但是tab更加便捷
L.
查询匹配L对象的属性或者方法
Python中前面带_的被称为私有属性或方法,与之相反的被称为公共属性
L._add_
Tab键在import的时候也有用
当你知道一个函数或属性中间或者末尾的几个字符时我们要使用通配符*来实现
*Warning?
BytesWarning
DeprecationWarning
FutureWarning
ImportWarning
PendingDeprecationWarning
ResourceWarning
RuntimeWarning
SyntaxWarning
UnicodeWarning
UserWarning
Warning
*匹配任意字符串,包括空字符串
历史命令快捷键
所有命令历史都会存储在一个IPython配置文件路径下的SQLite数据库中
p8
IPython魔法命令
魔法命令分成两类:
- 行魔法(line magic) %前缀
- 单元魔法(cell magic) %%前缀
%lsmagic 列出所有魔法命令
%run 运行外部代码
%run myscript.py
运行了以后py中的函数也能使用
%timeit
计算一行的python语句执行所用时间
自动多次执行简短的命令,以获得更稳定结果
%%timeit
cell magic
魔法函数的帮助
用?符号
%timeit?
%magic
%lsmagic
输入和输出历史
IPython创建了叫做In和Out的Python变量,这些变量自动更新以反应命令历史
In对象是一个list
Out对象是一个dic
In [1]:....
Out[2]: 0.9092974268256817
所有返回值是None的函数都不会有Out
比如import 和 print语句
In [8]: Out[2] ** 2 + Out[3] ** 2
Out[8]: 1.0
下画线可以获得以前的输出
_: 之前一个Out
__:倒数第二个
___:倒数第三个
_2:第二个输出 和Out[2] 一个意思
禁止一个命令的输出
加一个分号就行了
In [14]: math.sin(2) + math.cos(2);
在画图命令中很常见
%history命令 显示所有输入,也可以显示特定数目的输入
In [16]: %history -n 1-4
1: import math
2: math.sin(2)
3: math.cos(2)
4: print(In)
错误与调试
控制Python异常报告,探索调试代码中错误的工具
控制异常:%xmode
python脚本没有通过时会抛出一个异常。
当解释器捕获其中一个异常时可以在traceback中找到引起这个的原因,用%xmode魔法命令可以控制打印信息的数量
用%xmode可以改变打印的信息
%xmode有一个输入参数,即模式
模式有三个可选项:
- Plain
- Context
- Verbose
默认Context
Plain更紧凑
Verbose加入了一些额外信息
%xmode Plain
func2(1)
Traceback (most recent call last):
File "<ipython-input-4-b2e110f6fc8f>", line 1, in <module>
func2(1)
File "<ipython-input-1-d849e34d61fb>", line 7, in func2
return func1(a, b)
File "<ipython-input-1-d849e34d61fb>", line 2, in func1
return a / b
ZeroDivisionError: division by zero
调试:当阅读诡计追溯不足以解决问题时
标准Python交互式调试工具时pdb
IPython是ipdb
IPython最方便的是用%debug命令
若在捕获一场后调用%debug
它会在异常点自动打开一个交互式调试提示符
ipdb提示符让你可以探索栈空间的当前状态
代码查看书p20
如果希望在发生任何异常时自动启动调试器,可以用%pdb魔法命令
%mode Plain
%pdb on
func2(1)
若有一个脚本,希望以交互式模式运行,可以用%run -d来运行
并用next命令但不向下交互地运行代码
调试命令p21
代码的分析和计时
高德纳:过早优化是一切罪恶的根源
但是提高代码效率总是有用的
Ipython提供了很多执行这些代码计时和分析的操作函数
- %time单个语句执行时间计时
- %timeit 对单个语句的重复执行进行计时,以获得更高准确度
- %prun 利用分析器运行代码
- %lprun 利用逐行分析器运行代码
- %memit 测量单个语句的内存使用
- %mprun 通过逐行的内存分析器运行代码
后四条语句要安装line_profiler 和 memory_profiler扩展
分析整个脚本:%prun
书p23 ~ 25
用%memit 和 %mprun进行内存分析
书p25~26
第2章 Numpy入门
第2 第3章介绍通过Python有效导入、存储和操作内存数据的主要技巧
不管数据是什么类型,第一步都是将这些数据转换成数值数组形式的可分析数据
所以有效存储和操作数值数组是数据科学中绝对的基础过程
Numpy(numerical python)数组与python内置list很像
Numpy数组几乎是整个python数据科学工具生态系统的核心
理解Python中的数据类型
了解python中数据数组是如何被处理的,并对比Numpy所做的改进,这个很重要
Python的易用性首先在它的动态输入
数据类型是动态推断的
result = 0
for i in range(10):
result += i
这个特性使得python非常易用
同时指出了一个事实:Python变量不仅是它们的值,还包括了关于值得类型得一些额外信息
Python整型不仅仅是一个整型
标准Python是用C语言编写得。
这意味着每一个Python对象都是一个聪明得伪C语言结构体,该结构体不仅包括其值,还有其他信息。
当用Python定义一个整型得时候
x = 1000 ,x 并不是一个"原生"得整型,而是一个指针,指向了C语言得一个复杂结构体
这表明与C语言比,在Python中存储一个整型会有一些开销
C语言整型本质上是对应某个内存位置得标签,里面存储得字节会编码成整型。
而Python整型其实是一个指针,指向包含这个Python对象所有信息得某个内存位置
python类型中得这些额外信息也会成为复旦,在多个对象组合的结构体中就会非常明显
Python列表 不仅仅是一个列表
list可以存储不同类型
但是列表中所有变量都是同一个类型时,很多信息都会显得多余,那个使用起来就会更高效
Pythonlist存储结构图
python中也有固定类型数组
import array
L = list(range(10))
A = array.array('i',L)
这里的i是一个数据类型码,表示数据是整型
但是Numpy包中的ndarray对象更加好用
从Python列表创建数组
可以用np.array从Python列表创建数组
np.array([1.4,2,8,7])
//类型不匹配会向上转型
array([1.4,2.,8.,7.])
若是昔我往设置数组有明确的数据类型,那么可以 用dtype关键字
np.array([1,2,3,4,5],dtype = 'float32')
ndarray可以指定为多维的
np.array([range(i,i+3) for i in [2,4,6]])
从头创建数组
构建一个数组时可以用字符串来指定数据类型
也可以用Numpy对象来指定
np.zeros(10,dtype = 'int16')
np.zeros(10,dtype = np.int16)
Numpy标准数据类型
Numpy数组基础
Python中的数据操作几乎等同于Numpy数组操作
数组操作分类:
- 数组的属性:确定数组大小、形状、存储大小、数据类型
- 数组的索引: 获取和设置数组各个元素的值
- 数组的切分:在大的数组中获取或设置更小的子数组
- 数组的变形
- 改变给定数组的形状
- 数组的拼接和分类:将多个数组合并为一个,以及将一个数组分裂成多个
Numpy数组的属性
用Numpy的随机数生成器设置一组种子值,以确保每次程序执行都生成同样的随机数组
import numpy as np
np.random.seed(0)
x1 = np.random.randint(10,size = 6)
x2 = np.random.randint(10,size = (3,4))
x3 = np.random.randint(10,size = (3,4,5))
每个数组都有ndim、shape、和size属性
print(x3.ndim)
print(x3.shape)
print(x3.size)
输出:3
(3,4,5)
60
还有dtype属性、itemsize属性(表示每个数组元素字节大小)、nbytes(表示整个数组总字节大小)
数组索引:获取单个元素
x1 = np.array([5,0,3,3,7,9])
x1[-1] #x1倒数第一个元素
x2[-2] #x1倒数第二个元素
x2 = np.array([[3,5,2,4],
[7,6,8,8],
[1,6,7,7]])
x2[2,-1]
同时可以通过索引修改元素的值
x2[0,0] = 12
数组切片:获取子数组
语法:x[start:stop:step]
start默认为0
stop默认是size
x[4:7] 从索引为4的值开始取(7-4)个值
x[::2] 隔一个取一个
x[::n] 隔n-1个取一个
x[::-1] 所有元素,逆序的
x[5::-2] 从索引5开始每隔一个元素逆序
5,3,1
多维子数组
x2[:2,:3] 前两行前三列
x2[:3,::2]
x2[::-1,::-1]
获得单行或单列:
x2[:,0] x2第一列
x2[0,:] x2第一行
x2[0] 等价于 x2[0,:]
注意
数组切片返回的是数组数据的视图,不是副本。
这点和python中不同
所以对于ndarray ,修改切片的数据也会修改原数据
创建副本的方式:
x2_sub_copy = x2[:2,:2].copy()
修改x2_sub_copy 原数组(x2)不会被修改
数组的变形
数组变形最灵活的实现方式是通过reshape()函数
grid = np.arange(1,10).reshape((3,3))
reshape() 返回一个非副本视图
另外一个常见变形模式是利用newaxis关键字
x = np.array([1,2,3])
x[np.newaxis,:] 获得行向量
x[:,np.newaxis] 获得列向量
数组拼接和分裂
数组拼接
主要用np.concatenate
np.vstack
np.hstack
在这里插入图片描述
axis = 0, 第一个维度 高
axis = 1, 第二个维度 宽
数组的分裂
通过np.split() 、np.hsplit() 和 np.vsplit()
可以想里面传递被分裂数组,以及索引列表作为分裂点
x = [1,2,3,99,99,3,2,1]
x1,x2,x3 = np.split(x,[3,5])
3和5指的是第三个元素和第五个元素后面
np.vsplit() 和 np.hsplit() 一样
第三维度用np.dsplit()
Numpy数组的计算:通用函数
Numpy数组计算有时很快,有时很慢,使Numpy变快的关键使利用向量化操作,通常在Numpy的通用函数(ufunc)中实现
通用哈桑农户可以提高数组元素的重复计算的效率
通用函数是一种对ndarry中的数据执行元素级运算的函数,可以看作是简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化包装器。
缓慢的循环
python的相对缓慢通常出现在很多小操作需要不断重复的时候
通用函数介绍
Numpy提供了很多可编译程序的接口
也被称作向量操作
可以简单对数组操作来实现,这里对数组操作将会被用于数组中的每一个元素
Numpy的向量操作时通过通用函数实现的
通用函数的主要目的就是对Numpy数组中的值执行更快的重复操作
探索Numpy的通用函数
通用函数的两种存在形式
- 一元通用函数(unary ufunc)对单个输入操作
- 二元通用函数(binary ufunc) 对两个输入操作
数组的运算
x = np.arange(4)
x+5
x-1
x*2
x/2
x//2
-x
x%2
这些算术运算符都是NumPy内置函数的简单封装器,例如+运算符就是一个add通用函数的封装器:
下列是运算符对应的通用函数基本都是二元通用函数
绝对值
NumPy可以理解Python内置的绝对值函数:
对应的NumPy通用函数是np.absolute , 该函数也可以用别名np.abs来访问
x = np.array([-2,-1,0,1,2])
np.abs(x)
这个通用函数也可以处理负数。当处理负数时,绝对值返回的是该负数的模
x = np.array([3-4j,4-3j,2+0j,0+1j])
np.abs(x)
输出:array([5.,5.,2.,1.])
三角函数
sin、cos、tan、arcsin…之前要加np.
指数和对数
np.exp(x)
np.power(3,x)
np.log(x) 以自然对数为底
np.log2(x)
np.log10(x)
上述都是一元通用函数
专用的通用函数
有一个更加转义,也更加晦涩的通用函数来源于子模块scipy.special
form scipy import special
NumPy 和 scipy.special中提供了大量通用函数,这些包的文档在网上就可以查到,搜索"gamma function python即可"
高级的通用函数特性
指定输出
进行大量的运算时,指定一个用于存放运算结果的数组是非常有用的。不同于创建临时数组,你可以用这个特性将计算结果直接写入到你期望的存储位置。
可以通过out参数来指定计算结果的存放位置:
x = np.arange(5)
y = np.empty(5)
np.multiply(x,10,out = y)
y = np.zeros(10)
np.power(2,x,out = y[::2])
print(y)
array([1.,0.,2.,0.,4.,0.,8.,0.,16.,0.])
如果写的是y[::2] = 2**x 将会创建一个临时数组存放
2** 的值,会占用内存
聚合
例如我们希望用一个特定的运算reduce一个数组
那么可以用任何通用函数的reduce方法。
一个reduce方法会对给定的元素和操作重复执行。直至得到单个的结果。
x = np.arange(1,6)
np.add.reduce(x)
答案是15:就是1加到5
np.multiply.reduce(x)
答案是120:1乘到5
如果需要存储每次计算的中间结果,可以使用accumulate
np.add.accumulate(x)
array([1,3,5,10,15]) 就是每步计算的结果
在一些特殊情况下提供了专用函数
np.sum()
np.prod() 等等
外积
任何通用函数都可以使用outer方法获得两个不同输入数组所有元素对的函数运算结果。
通用函数另外一个非常有用的特性就是它能操作不同大小和形状的数组,一组这样的操作被称为广播(broadcasting)
聚合:最小值、最大值和其他值
当你面对大量数据的时候,第一步骤通常就是计算相关数据的概括统计值。最常用的概括统计值可能是均值和标准差,但是其他一些形式的聚合也是非常有用的(比如求和、乘积、中位数、最大最小值、分位数等等)
数组值求和
Python本身有内置sum函数
L = np.random.random(100)
sum(L)
NumPy中的sum函数
np.sum(L)
np.sum()更加快一点
两个函数并不等同
Python也有内置的min函数和max函数,分别被用于获取给定数组的最小值和最大值
np.min(big_array) np.max(big_array)
对于min、max、sum和其他NumPy聚合,一种更加间接的语法形式是数组对象直接调用这些方法
print(big_array.min(),big_array.max(),big_array.sum())
多维度聚合
一种常用聚合操作时沿着一行或者一列聚合
M = np.random.random((3,4))
M.sum()
M.min(axis = 0)
M.max(axis = 1)
其他聚合函数
数组的计算:广播
另外一种向量化的操作的方法就是利用NumPy的广播功能。广播功能可以简单理解为用于不同大小数组的二元通用函数(加、减、乘等)的一组规则
广播的介绍
广播允许二元运算符可以用于不通过大小的数组
比如标量和数组相加
a = np.array([0,1,2])
a+5
输出:array([5,6,7])
不同 维度的数组相加
M = np.ones((3,3))
M + a
输出:array([[1,2,3],
[1,2,3],
[1,2,3]])
//这个一维数组被广播了
更复杂的情况会涉及堆两个数组的同时广播
a = np.arange(3)
b = np.arange(3)[:,np.newaxis]
打印a和b:
[0,1,2]
[[0],
[1],
[2]]
a + b
输出:
array([[0,1,2],
[1,2,3],
[2,3,4]])
需要注意的是,这个额外的内存并没有在实际操作中进行分配
广播的规则
规则1:如果两个数组的维度数不相同,那么小唯独数组的形状将会在最左边补上1
规则2:如果两个数组的形状在任何一个维度上都不匹配,那么数组的形状会沿着维度为1的维度扩展以匹配另外一个数组的形状
规则3:如果两个数组的形状在任何一个维度上都不匹配并且没有任何一个维度等于1,那么会引发异常
有一个维度为1,所以延那个方向扩展
具体看书p57~59
比较、掩码和布尔逻辑
用布尔掩码来查看和操作NumPy数组中的值。当你想基于某些准则来抽取、修改、计数或堆一个数组中的值进行其他操作时,掩码就可以派上用场了。
和通用函数类似的比较操作
> < >= <= ==
x = np.array([1,2,3,4,5])
x<3返回一个元素会布尔值的数组
array([True , True , False , False , False])
复合表达式也可行
(2*x) == (x**2)
运算符和对应通用函数
== np.equal
!= np.not_equal
< np.less
<= np.less_equal
> np.greater
>= np.greater_equal
操作布尔数组
给定一个布尔数组可以实现很多有用的功能。
x = np.array([5,0,3,3],
[7,9,3,5],
[2,4,7,6])
统计记录的个数
np.count_nonzero(x<6) //统计x中有多少值小于6
#也可以使用sum
np.sum(x<6)
#sum()也可以沿着特定轴
np.sum(x<6,axis = 1)
#打印出来 array([4,2,2])
np.any() np.all() 这两个函数返回布尔值
np.any(x > 8) #判断有没有值大于8
np.all(x > 8) #判断是不是所有值都大于8
#也可以沿着特定坐标轴
np.any(x > 8,axis = 1)
np.all(x < 8,axis = 1)
布尔运算符
上述操作已经可以统计所有降水量下雨四英寸的天数了
但是如果我们要统计降水量在2英寸到4英寸之间的天数该怎么办
我们要用到逐位逻辑运算符, & | ^ ~
NumPy用通用函数重载了这些逻辑运算符,这样可以实现数组的逐位运算
np.sum((inches > 0.5) & (inches < 1))
括号不能去,因为逻辑运算符优先级高于条件运算符
布尔运算符及其通用函数
& np.bitwise_and
| np.bitwise_or
^ np.bitwise_xor
~ np.bitwise_not
将布尔数组作为掩码
前面是对布尔数组进行聚合运算
另外一种用法是直接的布尔数组作为掩码,通过该掩码选择数据的子数据集
x = np.array([5,0,3,3],
[7,9,3,5],
[2,4,7,6])
#将 小于5的数据抽取出来,可以用掩码操作
x[x<5]
返回一个一维数组
and or 与 & | 的区别
& | 是逐位进行比较
and or是整体
花哨的索引
fancy indexing
fancy indexing传递的是索引数组(ndarray)或者列表(list)不是单个标量
探索花哨索引
x = np.array([0,10,20,30,40,50,60,70,80,90])
ind = [3,7,8,5]
x[ind] #输出array([30,70,80,50])
利用fancy indexing,结果形状与索引数组形状一致
ind = np.array([[3,7],
[4,5]])
x[ind]
#输出:
#array([[30,70],
# [40,50]])
fancy indexing应用于多维度数组
X = np.arange(12).reshape(3,4)
array([[0,1,2,3],
[4,5,6,7],
[8,9,10,11]])
row = np.array([0,1,2])
col = np.array([2,1,3])
X[row,col]
#输出
#array([2,5,11])
结果的第一个值是X[0,2] 第二个值是X[1,1] 第三个值是X[2,3]
其次,索引值的配对也遵循广播的规则
一个列向量与一个行向量作为索引,那么会返回一个二维的矩阵
X[row[:,np.newaxis],col]
输出:
array([[2,1,3],
[6,5,7],
[10,9,11]])
返回结果是索引数组的形状
组合索引
fancy indexing一个常见用途是从一个矩阵中选择行的子集。
X.shape #结果是(100,2)
indices = np.random.choice(X.shape[0],20,replace = flase) #从行中抽取20行的索引
selection = X[indices] #用到了fancy indexing
selection.shape #(20,2) 因为取得是行所以结果是这样的
用fancy indexing索引修改值
fancy indexing可以被用于获取部分数组,也可以被用于修改部分数组
x = np.arange(10)
i = np.array([2,1,8,4])
x[i] = 99
需要注意,操作中重复的索引会出错
x = np.zeros(10)
x[[0,0]] = [4,6]
#这是x[0] = 4 x[0] = 6
print(x)
[6,0,0,0,0,0,0,0,0,0]
前一个代码块起不到累加的效果
要用后面那个代码块的方法
画直方图
p73~75
数组的排序
排序算法有很多
插入排序、选择排序、归并排序、快速排序、冒泡排序等
NumPy中的快速排序:np.sort()和np.argsort()
np.sort的排序算法是快速排序,时间复杂度是O(n logn)
x = np.array([2,1,4,3,5])
np.sort(x) #原数组没有变
#希望原数组变得化用数组得sort方法
x.sort()
另外一个相关函数是argsort,返回原始数组排好序的索引值
x = np.array([2,1,4,3,5])
i = np.argsort(x)
print(x[i]) #显示排好序的数组
沿着行或者列排序
np.sort(X,axis = 0) 沿着列
np.sort(X,axis = 1) 沿着行
但是这么做的话行和列的关系将会丢失
部分排序:分隔
np.partition(X,2,axis = 1) 每行前2小的放在前面两个
np.argpartition
结构化数据:NumPy的结构化数组
可以用Pandas实现,主要作为了解
NumPy结构化数组为复合的、异构的数据提供了非常有效的存储
尽管这里列举的模式对于简单的操作非常有用,但是这些场景通常也可以用Pandas的DataFrame来实现
如果有关于一些人的分类数据(如姓名、年龄和体重)
我们需要存储这些数据用于Python项目,可以用三个数组分别存放
name = ['Alice','Bob','Cathy','Doug']
age = [25,45,37,19]
weight = [55.0,85.5,68.0,61.5]
这样写的话看不出来这三个数据的关联性,可以用单一结构来存储所有数据,看起来会自然一些。
NumPy可以用结构化数组来实现这样的存储。这些结构化数组是复合数据类型的。
data = np.zeros(4,dtype = {'names':('name','age','weight'),
'formats':('U10','i4','f8')})
print(data.dtype)
显示
[('name','<U10') , ('age','<i4'),('weight','<f8')]
U10表示长度不超过10的Unicode字符串
i4表示4字节整型
f8表示8字节浮点型
接下来将列表数据放入数组中:
data['name'] = name
data['age'] = age
data['weight'] = weight
print(data)
显示:[(‘Alice’,25,55.0) (‘Bob’,45,85.5) (‘Cathy’,37,68.0) (‘Doug’, 19 ,61.5)]
正如我们所希望的,所有的数据被安排在一个内存块中
结构化数组的便捷之处在于,可以通过索引或者名称查看相应的值
data['name']
data[0] #查看第一行 ('Alice',25,55.0)
data[-1]['name']