第一章 NumPy快速入门
首先,我们将介绍如何在不同的操作系统中安装NumPy和相关软件,并给出使用NumPy的简单示例代码。
然后,我们将简单介绍IPython(一种交互式shell工具)。
如前言所述,SciPy和NumPy有着密切的联系,因此你将多次看到SciPy的身影。
在本章的末尾,我们将告诉你如何利用在线资源,以便你在受困于某个问题或不确定最佳的解题方法时,可以在线获取帮助。
本章涵盖以下内容:
1.在Windows、Linux和Macintosh操作系统上安装Python,SciPy,Matplotlib,IPython和NumPy;
2.编写简单的NumPy代码;
3.了解IPython;
4.浏览在线文档和相关资源。
1.1 Python
NumPy是基于Python的,因此在安装NumPy之前,我们需要先安装Python。某些操作系统已经默认安装有Python环境,但你仍需检查Python的版本是否与你将要安装的NumPy版本兼容。
Python有很多种实现,包括一些商业化的实现和发行版。
在本书中,我们将使用CPython(CPython是用C语言实现的Python解释器)实现,从而保证与NumPy兼容。
1.2 动手实践:在不同的操作系统上安装Python
NumPy在Windows、各种Linux发行版以及Mac OS X上均有二进制安装包。
如果你愿意,也可以安装包含源代码的版本。
(1) Debian和Ubuntu Debian和Ubuntu可能已经默认安装了Python,但开发者包(development headers) ①一般不会默认安装。在Debian和Ubuntu中安装python和python-dev的命令如下:
sudo apt-get install python
sudo apt-get install python-dev
(2) Windows Python的Windows安装程序可以在www.python.org/download下载。在这个站点中,我们也可以找到Mac OS X的安装程序,以及Linux、 Unix和Mac OS X下的源代码包。
(3) Mac Mac OS X中预装了Python,而我们也可以通过MacPorts、 Fink或者类似的包管理工具来获取Python。举例来说,可以使用如下命令安装Python 2.7:
sudo port install python27
LAPACK并不是必需的,但如果需要,NumPy在安装过程中将检测并使用之。我们推荐大家安装LAPACK以便应对海量数据的计算,因为它拥有高效的线性代数计算模块。
1.3 Windows
在Windows上安装NumPy是很简单的。你只需要下载安装程序,运行后在安装向导的指导下完成安装。
1.4 动手实践:在Windows上安装NumPy、Matplotlib、SciPy和IPython
在Windows上安装NumPy是必需的,但幸运的是,安装过程并不复杂,我们将在下面详细阐述。
虽然这一操作对于使用本书不是必需的。
我们将按照如下步骤安装这些软件。
1.5 Linux
在Linux上安装NumPy和相关软件的方法取决于具体使用的Linux发行版。我们将用命令行的方式安装NumPy,不过你也可以使用图形界面安装程序,这取决于具体的Linux发行版。除了软件包的名字不一样,安装Matplotlib、SciPy和IPython的命令与安装NumPy时是完全一致的。
这几个软件包不是必需安装的,但这里建议你也一并安装。
1.6 动手实践:在Linux上安装NumPy,Matplotlib,SciPy和IPython
大部分Linux发行版都有NumPy的软件包。我们将针对一些流行的Linux发行版给出安装步骤。
(1)要在Red Hat上安装NumPy,请在命令行中执行如下命令:
yum install python-numpy
(2)要在Mandriva上安装NumPy,请在命令行中执行如下命令:
urpmi python-numpy
(3)要在Gentoo上安装NumPy,请在命令行中执行如下命令:
sudo emerge numpy
(4)要在Debian或Ubuntu上安装NumPy,请在命令行中执行如下命令:
sudo apt-get install python-numpy
1.7 Mac OS X
在Mac上,你可以通过图形用户界面或者命令行来安装NumPy,Matplotlib和SciPy,根据自己的喜好选择包管理工具,如MacPorts或Fink等。
1.8 动手实践:在Mac OS X上安装NumPy,Matplotlib和SciPy
1.9 动手实践:使用MacPorts或Fink安装NumPy,SciPy,Matplotlib和IPython
我们也可以选择另外一种安装方式,即使用MacPorts或Fink来安装NumPy,SciPy,Matplotlib以及IPython。
下面给出的安装步骤将安装所有这些软件包。
(1)输入以下命令,从MacPorts安装这些软件包:
sudo port install py-numpy py-scipy py-matplotlib py-ipython
(2)Fink也包含了相关软件包,NumPy的有scipy-core-py24,scipy-core-py25和scipy-core-py26;SciPy的有scipy-py24,scipy-py25和scipy-py26。
执行如下命令,我们来安装基于Python2.6的NumPy以及其他推荐安装的软件包。
fink install scipy-core-py26 scipy-py26 matplotlib-py26
??书中是这么写的,但我怀疑numpy的安装是出现的错误。
1.10 编译源代码
NumPy的源代码可以使用git获取,如下所示:
git clone git://github.com/numpy/numpy.git numpy
使用如下命令将NumPy安装至/usr/local;
python setup.py build
sudo python setup.py install --prefix=/usr/local
我们需要有C编译器(如GCC)和Python开发者包(python-dev或python-devel),然后才可对源代码进行编译。
1.11 数组对象
在介绍完NumPy的安装步骤后,我们来看看NumPy中的数组对象。NumPy数组在数值运算方面的效率优于Python提供的list容器。
使用NumPy可以在代码中省去很多循环语句,因此其代码比等价的Python代码更为简洁。
1.12 动手实践:向量加法
假设我们需要对两个向量a和b做加法。
这里的向量即数学意义上的一维数组,随后我们将在第5章中学习如何用NumPy数组表示矩阵。
向量a的取值为0-n的整数的平方,例如n取3时,向量a为0,1,或4.
向量b的取值为0-n的整数的立方,例如n取3时,向量b为0,1,或8.
用纯Python代码应该怎么写呢?我们先想一想这个问题,随后再与等价的NumPy代码进行比较。
(1)以下的纯Python代码可以解决上述问题。
def pythonsum(n):
a=range(n)
b=range(n)
c=[]
for i in range(Len(a)):
a[i]=i**2
b[i]=i**3
c.append(a[i]+b[i])
return c
(2)以下是使用NumPy的代码,它同样能够解决问题:
def numpysum(n):
a=numpy.arange(n)**2
b=numpy.arange(n)**3
c=a+b
return c
注意,numpysum()函数中没有使用for循环。同时,我们使用NumPy中的arange函数来创建包含0-n的整数的NumPy数组。
代码中的arange函数前有一个前缀numpy,表明该函数是从NumPy模块导入的。
下面我们做一个有趣的实验。
在前言部分我们曾提到,NumPy在数组操作上的效率优于纯Python代码。
那么究竟快多少呢?
接下来的程序将告诉我们答案,它以微秒(106 s)的精度分别记录下numpysum()和pythonsum()函数的耗时。
这个程序还将输出加和候的向量最末的两个元素。
让我们来看看纯Python代码和NumPy代码是否得到相同的结果。
#!/usr/bin/env/python
import sys
from datetime import datetime
import numpy as np
'''
本书第1章
该段代码演示Python中的向量加法
使用如下命令运行程序:
python vectorsum.py n
n为指定向量大小的整数
加法中的第一个向量包含0到n的整数的平方
第二个向量包含0到n的整数的立方
程序将打印出向量加和后的最后两个元素以及运行消耗的时间
'''
def numpysum():
a=np.arange(n)**2
b=np.arange(n)**3
c=a+b
return c
def pythonsum():
a=range(n)
b=range(n)
c=[]
for i in range(len(a)):
a[i]=i**2
b[i]=i**3
c.append(a[i]+b[i])
return c
size=int(sys.argv[1])
start=datetime.now()
c=pythonsum(size)
delta=datetime.now()-start
print('The last 2 elements of the sum',c[-2:])
print('PythonSun elapsed time in microseconds',delta.microseconds)
start=datetime.now()
c=numpysum(size)
delta=datetime.now()-start
print("The last 2 elements of the sum",c[-2:])
print('NumPySum elapsed time in microseconds',delta.microseconds)
显然,NumPy代码比等价的纯Python代码运行速度快得多。
有一点可以肯定,即不论我们使用NumPy还是Python,得到的结果是一致的。
不过,两者的输出结果在形式上有些差异。
注意,numpysum()函数的输出不包含逗号。这是为什么呢?显然,我们使用的是NumPy数组,而非Python资深的list容器。
正如前言所述,NumPy数组对象以专用数据结构来存储数值。我们将在下一章中详细介绍NumPy数组对象。
1.13 IPython:一个交互式shell工具
科学家和工程师都喜欢做实验,而IPython正是诞生于爱做实验的科学家之手。IPython提供的交互式实验环境被很多人认为是Matlab,Mathematica和Maple的开源替代品。
你可以在线获取包括安装指南在内的更多信息,地址为http://ipython.org/。
IPython是开源免费的软件,可以在Linux、Unix、Mac OS以及Windows上使用。
IPython的作者们希望那些用导IPython的科研工作成果在发表时能够提到IPython,这是他们对IPython使用者唯一的要求。
下面是IPython的基本功能:
tab键自动补全;
历史记录存档;
行内编辑;
使用%run可以调用外部Python脚本;
支持系统命令;
支持pylab模式;
Python代码调试和性能分析。
在pylab模式下,IPython将自动导入SciPy,NumPy和Matplotlib模块。如果没有这个功能,我们只能手动导入每一个所需模块。
而现在,我们只需在命令行中输入如下命令:
ipython--pylab
使用quit()函数或快捷键Ctrl+D均可以退出IPython shell。
有时我们想要回到之前做过的实验,IPython可以便捷地保存会话以便稍后使用。
%logstart
举例来说,我们之前的向量加法程序放在当前目录下,可以按照如下方式运行脚本:
ls
README vectorsum.py
%run -i vectorsum.py 1000
你可能还记得,这里的1000是指向量中元素的数量。%run的-d参数将开启ipdb调试器,键入c后,脚本就开始逐行执行了(如果脚本有n行,就一共执行n步直到代码结束)。
在ipdb提示符后面键入quit可以关闭调试器。
我们还可以使用%run的-p参数对脚本进行性能分析。
根据性能分析的结果,可以更多地了解程序的工作机制,并能够据此找到程序的性能瓶颈。
使用%hist命令可以查看命令行历史记录。
1.14 在线资源和帮助
在IPython的pylab模式下,我们可以使用help命令打开NumPy函数的手册页面。
你并不需要知道所偶函数的名字,因为可以在键入少量字符后按下Tab键进行自动补全。
例如,我们来查看一下arange函数的相关信息。
广受欢迎的开发技术问题网站Stack Overflow上有成百上千的提问被标记为numpy相关问题。你可以到这里查看这些问题:
1.15 本章小结
在本章中,我们安装了NumPy以及其他推荐软件。我们成功运行了向量加法程序,并以此证明NumPy优异的性能。随后,我们介绍了交互式shell工具IPython。此外,我们还列出了供你参考的NumPy文档和在线资源。
在下一章中,我们将深入了解NumPy中的一些基本概念,包括数组和数据类型。
第2章 NumPy基础
本章涵盖以下内容:
数据类型
数组类型
类型转换
创建数组
数组索引
数组切片
改变维度
本章代码段中的输入和输出均来自IPython会话。
(IPython是用于科学计算的交互式shell工具)IPython的pylab模式可以自动导入包括NumPy在内的很多Python科学计算库,并且在IPython中没有必要显式调用print语句输出变量的值。
2.1 NumPy数组对象
NumPy中的ndarray是一个多维数组对象,该对象由两部分组成:
实际的数据;
描述这些数据的元数据。
大部分的数组操作仅仅修改元数据部分,而不改变底层的实际数据。
在第1章中,我们已经知道如何使用arange函数创建数组。实际上,当时创建的数只是包含一组数字的一维数组,而ndarray支持更高的维度。
NumPy数组一般是同质的(但有一种特殊的数组类型例外,它是异质的),即数组中的所有元素类型必须是一致的。
这样有一个好处:如果我们知道数组中的元素均为同一类型,该数组所需的存储空间就很容易确定下来。
与Python中一样,NumPy数组的下标也是从0开始的。数组元素的数据类型用专门的对象表示,而这些对象我们将在本章详细探讨。
我们再次用arange函数创建数组,并获取其数据类型:
a=np.arange(5)
a.dtype
Out[17]: dtype('int32')
数组a的数据类型为int32。——如果你使用64位的Python,得到的结果可能是int64.不论哪种情形,该数组的数据类型都是整数(64位或32位)。除了数据类型,数组的维度也是重要的属性。
第1章中的例子演示了怎样创建一个向量(即一维的NumPy数组)。向量在数学中很常用,但大部分情况下,我们需要更高维的对象。先来确定一下刚刚所创建向量的维度。
a
Out[18]: array([0,1,2,3,4])
a.shape
Out[19]:(5,)
正如你所看到的,这是一个包含5个元素的向量,取值分别为0-4的整数。数组的shape属性返回一个元组(tuple),元组中的元素即为NumPy数组每一个维度上的大小。上面例子中的数组是一维的,因此元组中只有一个元素。
2.2 动手实践:创建多维数组
既然我们已经知道如何创建向量,现在可以试着创建多维的NumPy数组,并查看其维度了。
(1)创建一个多维数组。
(2)显示该数组的维度。
m=np.array([np.arange(2),np.arange(2)])
m
Out[23]:
array([[0,1],
[0,1]])
m.shape
Out[24]:(2,2)
我们将arange函数创建的数组作为列表元素,把这个列表作为参数传给array函数,从而创建了一个2*2的数组,而且没有出现任何报错信息。
array函数可以根据给定的对象生成数组。给定的对象应是类数组,如Python中的列表。
在上面的例子中,我们传给array函数的对象是一个NumPy数组的列表。像这样的类型数组对象是array函数的唯一必要参数,其余的诸多参数均有默认值的可选参数。
2.2.1 选取数组元素
有时候,我们需要选取数组中的某个特定元素。首先还是创建一个2*2的多维数组。
a=np.array([[1,2],[3,4]])
a
Out[34]:
array([[1,2],
[3,4]])
在创建这个多维数组时,我们给array函数传递的对象是一个嵌套的列表。现在来依次选取该数组中的元素。记住,数组的下表是从0开始的。
a
Out[34]:
array([[1,2],
[3,4]])
a[0]
Out[35]: array([1,2])
a[1]
Out[36]: array([3,4])
a[0,0]
Out[37]:1
a[0,1]
Out[38]:2
从数组中选取元素就是这么简单,对于数组a,只需要用a[m,n]选取各数组元素,其中m和n为元素下表,对应的位置如下表所示。
2.2.2 NumPy数据类型
Python支持的数据类型有:整型,浮点型以及复数型。
但这些类型不足以满足科学计算的需求,因此NumPy添加了很多其他的数据类型。在实际应用中,我们需要不同精度的数据类型,它们占用的内容空间也是不同的。在NumPy中,大部分数据类型名是以数字结尾的,这个数字表示其在内存中所占用的位数。
下面的表格列出了NumPy中支持的数据类型。
每一种数据类型均有对应的类型转换函数:
float(42)
Out[39]:42.0
int(42.0)
Out[40]:42
bool(42)
Out[41]:True
bool(0)
Out[42]:False
float(True)
Out[43]:1.0
float(False)
Out[44]:0.0
在NumPy中,许多函数的参数中可以指定数据类型,通常这个参数是可选的:
np.arange(7)
Out[55]: array([0,1,2,3,4,5,6])
- _.dtype
Out[57]: dtype('int32')
np.arange(9,dtype='int64')
Out[58]: array([0,1,2,3,4,5,6,7,8], dtype=int64)
需要注意的是,复数是不能和转换为整数的,这将触发TypeError错误。
int(42.0+1.j)
Traceback(most recent call last):
File"<ipython-input-60-9e29587d355a>", line 1,in<module>
int(42.0+1.j)
TypeError: can't convert complex to int
同样,复数也不能转换为浮点数。
不过,浮点数却可以转换为复数,例如complex(1.0)。注意,有j的部分为复数的虚部。
2.2.3 数据类型对象
数据类型对象是np.dtype类的实例。
如前所述,NumPy数组是有数据类型的,更确切地说,NumPy数组中的每一个元素均为相同的数据类型。
数据类型对象可以给出单个数组元素在内存中占用的字节数,即dtype类的itemsize属性。
a.dtype.itemsize
Out[62]:4
2.2.4 字符编码
NumPy可以使用字符编码来表示数据类型,这是为了兼容NumPy的前身Numeric。
我不推荐使用字符编码,但有时会用到,因此下面还是列出了字符编码的对应表。
读者应该优先使用dtype对象来表示数据类型,而不是这些字符编码。
下面的代码创建了一个单精度浮点数数组:
np.arange(7,dtype='f')
Out[64]: array([0.,1.,2.,3.,4.,5.,6.], dtype=float32)
与此类似,还可以创建一个复数数组:
np.arange(7,dtype='D')
Out[65]: array([0.+0.j,1.+0.j,2.+0.j,3.+0.j,4.+0.j,5.+0.j,6.+0.j])
2.2.5 自定义数据类型
我们有很多种自定义数据类型的方法,以浮点型为例。
可以使用Python中的浮点数类型。
dype='float32'
可以使用字符编码来指定单精度浮点数类型;
可以使用字符编码来指定双精度浮点数类型。
dtype='f'
dtype='d'
还可以将两个字符作为参数传给数据类型的构造函数。
此时,第一个字符表示数据类型,第二个字符表示该类型在内容中占用的字节数(2,,8分别代表精度为16,32,64位的浮点数)
np.arange(1,dtype='f8')
Out[67]: array([0.])
_.dtype
Out[68]: dtype('float64')
完整的NumPy数据类型列表可以在sctypeDict.keys()中找到:
np.sctypeDict.keys()
Out[70]: dict_keys(['?',0,'byte','b',1,'ubyte','B',2,'short','h',3,'ushort','H',4,'i',5,'uint','I',6,'intp','p',9,'uintp','P',10,'long','l',7,'L',8,'longlong','q','ulonglong','Q','half','e',23,'f',11,'double','d',12,'longdouble','g',13,'cfloat','F',14,'cdouble','D',15,'clongdouble','G',16,'O',17,'S',18,'unicode','U',19,'void','V',20,'M',21,'m',22,'bool8','Bool','b1','float16','Float16','f2','float32','Float32','f4','float64','Float64','f8','complex64','Complex32','c8','complex128','Complex64','c16','object0','Object0','bytes0','Bytes0','str0','Str0','void0','Void0','datetime64','Datetime64','M8','timedelta64','Timedelta64','m8','int32','uint32','Int32','UInt32','i4','u4','int64','uint64','Int64','UInt64','i8','u8','int16','uint16','Int16','UInt16','i2','u2','int8','uint8','Int8','UInt8','i1','u1','complex_','int0','uint0','single','csingle','singlecomplex','float_','intc','uintc','int_','longfloat','clongfloat','longcomplex','bool_','unicode_','object_','bytes_','str_','string_','int','float','complex','bool','object','str','bytes','a'])
2.2.6 dtype类的属性
dtype类有很多有用的属性。
例如,我们可以获取数据类型的字符编码:
t=np.dtype('float64')
t.char
Out[77]:'d'
type属性对应于数组元素的数据类型:
t.type
Out[78]: numpy.float64
str属性可以给出数据类型的字符串表示,该字符串的首个字符表示字节序(endianness),后面如果还有字符的话,将是一个字符编码,接着一个数字表示每个数组元素存储所需的字节数。
这里,字节序是指位长尾32或64的字(word)存储的顺序,包括大端序(big-endian)和小端序(little-endian)。
大端序是将最高位字节存储在最低的内存地址处,用>表示;
小端序是将最低位字节存储在最低的内存地址处,用<表示:
t.str
Out[79]:'<f8'
2.3 动手实践:创建自定义数据类型
自定义数据类型是一种异构数据类型,可以当作用来记录电子表格或数据库中一行数据的结构。
作为示例,我们将创建一个存储商店库存信息的数据类型。其中,我们用一个长度为40个字符的字符串来记录商品名称,用一个32位的整数来记录商品的库存数量,最后用一个32位的单精度浮点数来记录商品价格。
下面是具体的步骤。
(1)创建数据类型:
(2)查看数据类型(也可以查看某一字段的数据类型):