title: NumPy-快速处理数据 create: 2016.12.7 modified: 2016.12.7 tags: python ndarray ufunc

  7

[TOC]

标准安装的Python中用列表(list)保存一组值,可以用来当作数组使用,不过由于列表的元素可以是任何对象,因此列表中所保存的是对象的指针。这样为了保存一个简单的[1,2,3],需要有3个指针和三个整数对象。对于数值运算来说这种结构显然比较浪费内存和CPU计算时间。
此外Python还提供了一个array模块,array对象和列表不同,它直接保存数值,和C语言的一维数组比较类似。但是由于它不支持多维,也没有各种运算函数,因此也不适合做数值运算。
NumPy的诞生弥补了这些不足,NumPy提供了两种基本的对象:ndarray(N-dimensional array object)和 ufunc(universal function object)。ndarray(下文统一称之为数组)是存储单一数据类型的多维数组,而ufunc则是能够对数组进行处理的函数。

1 ndarray对象

1.1 创建

首先需要创建数组才能对其进行其它操作。
我们可以通过给array函数传递Python的序列对象创建数组,如果传递的是多层嵌套的序列,将创建多维数组(下例中的变量c):


In [2]:
import numpy as np
a = np.array([1, 2, 3, 4])
c = np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]])
c


Out[2]:
array([[ 1,  2,  3,  4],
       [ 4,  5,  6,  7],
       [ 7,  8,  9, 10]])

数组的大小可以通过其shape属性获得:


In [6]:
c.shape


Out[6]:
(3L, 4L)

使用数组的reshape方法,可以创建一个改变了尺寸的新数组,原数组的shape保持不变:


In [7]:
d = a.reshape((2,2))
d


Out[7]:
array([[1, 2],
       [3, 4]])

In [8]:
a


Out[8]:
array([1, 2, 3, 4])

数组a和d其实共享数据存储内存区域,因此修改其中任意一个数组的元素都会同时修改另外一个数组的内容:


In [9]:
a[1] = 100
d


Out[9]:
array([[  1, 100],
       [  3,   4]])

数组的元素类型可以通过dtype属性获得。上面例子中的参数序列的元素都是整数,因此所创建的数组的元素类型也是整数,并且是32bit的长整型。可以通过dtype参数在创建时指定元素类型:


In [12]:
a.dtype


Out[12]:
dtype('int32')

In [11]:
b=np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]], dtype=np.float)
b


Out[11]:
array([[  1.,   2.,   3.,   4.],
       [  4.,   5.,   6.,   7.],
       [  7.,   8.,   9.,  10.]])

上面的例子都是先创建一个Python序列,然后通过array函数将其转换为数组,这样做显然效率不高。因此NumPy提供了很多专门用来创建数组的函数。下面的每个函数都有一些关键字参数,具体用法请查看函数说明。

  • arange函数类似于python的range函数,通过指定开始值、终值和步长来创建一维数组,注意数组不包括终值:

In [14]:
np.arange(0, 1, 0.1)


Out[14]:
array([ 0. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9])
  • linspace函数通过指定开始值、终值和元素个数来创建一维数组,可以通过endpoint关键字指定是否包括终值,缺省设置是包括终值:

In [15]:
np.linspace(0, 1, 12)


Out[15]:
array([ 0.        ,  0.09090909,  0.18181818,  0.27272727,  0.36363636,
        0.45454545,  0.54545455,  0.63636364,  0.72727273,  0.81818182,
        0.90909091,  1.        ])
  • logspace函数和linspace类似,不过它创建等比数列,下面的例子产生1(10^0)到100(10^2)、有20个元素的等比数列:

In [16]:
np.logspace(0, 2, 20)


Out[16]:
array([   1.        ,    1.27427499,    1.62377674,    2.06913808,
          2.6366509 ,    3.35981829,    4.2813324 ,    5.45559478,
          6.95192796,    8.8586679 ,   11.28837892,   14.38449888,
         18.32980711,   23.35721469,   29.76351442,   37.92690191,
         48.32930239,   61.58482111,   78.47599704,  100.        ])

1.2 存取元素

数组元素的存取方法和Python的标准方法相同:


In [17]:
a = np.arange(10)
a[5]


Out[17]:
5

In [18]:
a[:5]   # 省略开始下标,表示从a[0]开始


Out[18]:
array([0, 1, 2, 3, 4])

和Python的列表序列不同,通过下标范围获取的新的数组是原始数组的一个视图。它与原始数组共享同一块数据空间:


In [20]:
b = a[3:7]
b


Out[20]:
array([3, 4, 5, 6])

In [22]:
b[2] = -10 # 将b的第2个元素修改为-10
a   # a的第5个元素也被修改为-10


Out[22]:
array([  0,   1,   2,   3,   4, -10,   6,   7,   8,   9])

1.3 多维数组

多维数组的存取和一维数组类似,因为多维数组有多个轴,因此它的下标需要用多个值来表示,NumPy采用元组(tuple)作为数组的下标。


In [33]:
a= np.arange(0, 60, 10).reshape(-1, 1)+ np.arange(0, 6)
a


Out[33]:
array([[ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [30, 31, 32, 33, 34, 35],
       [40, 41, 42, 43, 44, 45],
       [50, 51, 52, 53, 54, 55]])

用于存取数组的下标和仍然是一个有两个元素的元组,元组中的每个元素都是整数序列,分别对应数组的第0轴和第1轴。从两个序列的对应位置取出两个整数组成下标: a[0,1], a[1,2], ..., a[4,5]。


In [34]:
a[(0,1,2,3,4),(1,2,3,4,5)]


Out[34]:
array([ 1, 12, 23, 34, 45])

2 ufunc运算

ufunc是universal function的缩写,它是一种能对数组的每个元素进行操作的函数。NumPy内置的许多ufunc函数都是在C语言级别实现的,因此它们的计算速度非常快。让我们来看一个例子:


In [4]:
x = np.linspace(0, 2*np.pi, 10)
x


Out[4]:
array([ 0.        ,  0.6981317 ,  1.3962634 ,  2.0943951 ,  2.7925268 ,
        3.4906585 ,  4.1887902 ,  4.88692191,  5.58505361,  6.28318531])

In [5]:
y = np.sin(x)   # 对数组x中的每个元素进行正弦计算,返回一个同样大小的新数组
y


Out[5]:
array([  0.00000000e+00,   6.42787610e-01,   9.84807753e-01,
         8.66025404e-01,   3.42020143e-01,  -3.42020143e-01,
        -8.66025404e-01,  -9.84807753e-01,  -6.42787610e-01,
        -2.44929360e-16])

我用下面这个小程序,比较了一下numpy.math和Python标准库的math.sin的计算速度::


In [6]:
import time
import math
import numpy as np

x = [i * 0.001 for i in xrange(1000000)]
start = time.clock()
for i, t in enumerate(x):
    x[i] = math.sin(t)
print "math.sin:", time.clock() - start

x = [i * 0.001 for i in xrange(1000000)]
x = np.array(x)
start = time.clock()
np.sin(x,x)
print "numpy.sin:", time.clock() - start

# 输出
# math.sin: 1.15426932753
# numpy.sin: 0.0882399858083


math.sin: 0.387826021987
numpy.sin: 0.0187235443572

通过上面的例子我们了解了如何最有效率地使用math库和numpy库中的数学函数。因为它们各有长短,因此在导入时不建议使用*号全部载入,而是应该使用import numpy as np的方式载入,这样我们可以根据需要选择合适的函数调用。

3 文件存取

NumPy提供了多种文件操作函数方便我们存取数组内容。文件存取的格式分为两类:二进制和文本。而二进制格式的文件又分为NumPy专用的格式化二进制类型和无格式类型。
使用numpy.savetxt和numpy.loadtxt可以读写1维和2维的数组:


In [7]:
a = np.arange(0,12,0.5).reshape(4,-1)
np.savetxt("a.txt", a) # 缺省按照'%.18e'格式保存数据,以空格分隔
np.loadtxt("a.txt")


Out[7]:
array([[  0. ,   0.5,   1. ,   1.5,   2. ,   2.5],
       [  3. ,   3.5,   4. ,   4.5,   5. ,   5.5],
       [  6. ,   6.5,   7. ,   7.5,   8. ,   8.5],
       [  9. ,   9.5,  10. ,  10.5,  11. ,  11.5]])

In [9]:
np.savetxt("a.txt", a, fmt="%d", delimiter=",") #改为保存为整数,以逗号分隔
np.loadtxt("a.txt",delimiter=",") # 读入的时候也需要指定逗号分隔


Out[9]:
array([[  0.,   0.,   1.,   1.,   2.,   2.],
       [  3.,   3.,   4.,   4.,   5.,   5.],
       [  6.,   6.,   7.,   7.,   8.,   8.],
       [  9.,   9.,  10.,  10.,  11.,  11.]])

In [ ]: