首页 > 代码库 > 揭开Python科学计算的面纱

揭开Python科学计算的面纱

技术分享

春牛春杖。无限春风来海上。便与春工。染得桃红似肉红。
春幡春胜。一阵春风吹酒醒。不似天涯。卷起杨花似雪花。

  标准的Python中用列表保存一组值,可以当做数组使用,但是由于其值类型任意,所以列表中保存的是指针,这样的话保存一个简单的列表,例如:[1,2,3]需要三个指针和三个对象。对于数值运算来说这样十分耗费内存和CPU。

  Python提供了array 模块,他所提供的array对象和列表不同,能直接保存数值和C语言的一维数组相似,但是他不支持多维数组,也没有各种运算函数,因此也不适合做数值运算。

  NumPy的诞生弥补了这些不足之处,NumPy提供了两种基本的对象:

  1. ndarray:英文全称n-dimensional array object ,他是储存单一数据类型的多维数组,后来统称为数组。
  2. ufunc:英文全称为universal function object , 他是一种能够对数组进行特殊处理的函数。

本文采用1.12版本

>>> import numpy as np
>>> np.__version__
1.12.0

ndarray对象


 

  Numpy中使用ndarray对象来表示数组,他是整个库的核心对象,NumPy中所有的函数都是围绕这ndarray对象展开处理的。ndarray的结构不复杂,但是功能十分强大,不但可以用它大量高效的储存数值元素,从而提高数组计算的运算速度,还可以用它和各种扩展库进行数据交换。

创建

  首先需要创建数组才能对其进行运算和操作。可以通过给array() , 函数传递Python的序列对象创建数组,如果传递的是多层嵌套的序列,将创建个多维数组:

>>> a = np.array([1,2,3,4])
>>> b = np.array([5,6,7,8])
>>> c = np.array([[1,2,3,4],[4,5,6,7],[7,8,9,0]])
>>> a
array([1, 2, 3, 4])
>>> b
array([5, 6, 7, 8])
>>> c
array([[1, 2, 3, 4],
       [4, 5, 6, 7],
       [7, 8, 9, 0]])
>>> a.shape                    # 查看数组的形状可以通过其 shape属性
(4,)
>>> b.shape
(4,)
>>> c.shape
(3, 4)

  数组a的shape属性只有一个元素是因为他是一维数组 . 而数组C的shape属性有两个元素, 是因为他是二维数组 . 其元素的排序是从高维到低维 . 我们还可以通过修改数组的shape属性,在保持数组元素个数不变的情况下,改变数组每个轴的长度.下面的例子讲数组c的shape属性改为(4,3), 注意从(3,4)到(4,3)不是对数组进行转置, 而是改变每个轴的大小,数组元素在内存中的位置没有变化

>>> c.shape = 4,3
>>> c
array([[1, 2, 3],
       [4, 4, 5],
       [6, 7, 7],
       [8, 9, 0]])

  你应该可以发现(4,3)和(3,4)总结起来不还是12个基本元素么 ? 我们改变总容量行不行 ?

>>> c.shape = 4,2
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    c.shape = 4,2
ValueError: cannot reshape array of size 12 into shape (4,2)
>>> c.shape = 4,5
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    c.shape = 4,5
ValueError: cannot reshape array of size 12 into shape (4,5)

  当我们设置某个轴的元素个数为 - 1 的时候, 讲自动计算此轴的长度 . 由于数组c有12个元素因此下面的程序讲c的shape属性改为(2,6):

>>> c.shape = 2,-1
>>> c
array([[1, 2, 3, 4, 4, 5],
       [6, 7, 7, 8, 9, 0]])
>>> c.shape 
(2, 6)

  如果是三维数组我们设置两个 -1 呢 ?( 这个问题好像有点弱智 哈 )  发生错误:只能指定一个位置的维度

>>> c = np.array([[[1,2,3,4],[4,5,6,7],[7,8,9,0]],[[1,2,3,4],[4,5,6,7],[7,8,9,0]
]])
>>> c.shape
(2, 3, 4)
>>> c.shape = 3,-1,-1
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    c.shape = 3,-1,-1
ValueError: can only specify(指定) one unknown dimension(维度)

  使用数组的reshape方法, 可以创建指定形状的新数组, 而原数组的形状保持不变:

>>> a
array([1, 2, 3, 4])
>>> d = a.reshape((2,2))
>>> d
array([[1, 2],
       [3, 4]])

  汇编/C语言 中毒比较深的同学可能问了, 那么储存方式呢 ? 其实数组a和d是共享储存空间的,因此如果修改其中任意一个数组的元素都会同事修改另一个数组的内容, 注意在下面的例子中 , 数组d中的2也被改变成了100:(很好,很强大.)

>>> a
array([1, 2, 3, 4])
>>> d = a.reshape((2,2))
>>> d
array([[1, 2],
       [3, 4]])
>>> a[1]
2
>>> a[1] = 100
>>> a
array([  1, 100,   3,   4])
>>> d
array([[  1, 100],
       [  3,   4]])

元素类型

  数组的元素类型可以通过dtype来获得, 在前面的例子中, 创建数组所用到的序列的元素都是证书, 因此创建的数组的元素类型也都是整数, 我用的是Ubuntu64位, 所以我显示的类型是int类型64位, 如果你的是32 位可能你用的是32位的系统.

>>> c
array([[1, 2, 3, 4, 4, 5],
       [6, 7, 7, 8, 9, 0]])
>>> c.dtype
dtype(int64)

  如果有编译器有代码提示的同学可能就会发现, 那个dtype了, 在没有介绍前面的东西的时候你可能还以为这是个什么鬼 ? 现在应该知道他是用于指定元素类型用的了吧?

>>> ai32 = np.array(
┌──────────────────────────────────────────────────────────────┐
│ np.array: (object, dtype=None, copy=True, order=K,         │
│ subok=False, ndmin=0)                                        │
│ array           array2string    array_equal                  │
│ array_equiv     array_repr      array_split                  │
│ array_str                                                    │
│ array(object, dtype=None, copy=True, order=K, subok=False, │
│  ndmin=0)                                                    │
│                                                              │
│ Create an array.                                             │
│                                                              │
│ Parameters                                                   │
│ ----------                                                   │
│ object : array_like                                          │
│     An array, any object exposing the array interface, an ob │
│ ject whose                                                   │
│     __array__ method returns an array, or any (nested) seque │
│ nce.                                                         │
│ dtype : data-type, optional                                  │
└──────────────────────────────────────────────────────────────┘

 

>>> ai32 = np.array([1,2,3,4],dtype = np.int32)
>>> ai32
array([1, 2, 3, 4], dtype=int32)
>>> ai32.shape
(4,)
>>> af = np.array([1,2,3,4],dtype = np.float)
>>> af
array([ 1.,  2.,  3.,  4.])
>>> ac = np.array([1,2,3,4],dtype = np.complex)
>>> ac
array([ 1.+0.j,  2.+0.j,  3.+0.j,  4.+0.j])
>>> ac.shape
(4,)
>>> ai32.dtype
dtype(‘int32‘)
>>> af.dtype
dtype(‘float64‘)
>>> ac.dtype
dtype(‘complex128‘)

 

  在上面的例子中传递给dtype参数的都是类型对象, 其中类型对象都是NumPy定义的数据类型. 当然内置的也有并且其效果和内置的相同.

  在需要指定dtype参数的时候, 也可以传递一个字符串来表示元素的数值类型. NumPy中每个数值类型都有集中字符串的表达方式, 字符串和类型之间的对用关系都储存在typeDict字典中,下面的程序获得与float64类型对应的所有键值.

>>> [key for key, value in np.typeDict.items() if value is np.float64]
[12, d, float_, float, double, Float64, f8, float64]

  好吧翻译一下.

>>> for key,value in np.typeDict.items():
...     if value is np.float64:
...         value,key
...         
...     
... 
(<class numpy.float64>, 12)
(<class numpy.float64>, d)
(<class numpy.float64>, float_)
(<class numpy.float64>, float)
(<class numpy.float64>, double)
(<class numpy.float64>, Float64)
(<class numpy.float64>, f8)
(<class numpy.float64>, float64)

  完整的类型列表可以通过下面的语句获得, 他讲typeDict字典中的所有值转化为一个集合,从而去除重复项. ( 重复项 值只有这么多, 但是一个值可以有很多个键. )

>>> set(np.typeDict.values())
{<class numpy.float128>, <class numpy.uint64>, <class numpy.int64>, <class
 numpy.str_>, <class numpy.complex128>, <class numpy.float64>, <class num
py.uint32>, <class numpy.int32>, <class numpy.bytes_>, <class numpy.comple
x64>, <class numpy.float32>, <class numpy.uint16>, <class numpy.int16>, <
class numpy.bool_>, <class numpy.timedelta64>, <class numpy.float16>, <cla
ss numpy.uint8>, <class numpy.int8>, <class numpy.object_>, <class numpy.
datetime64>, <class numpy.uint64>, <class numpy.int64>, <class numpy.void>, <class numpy.complex256>}

  上面显示的数值类型与数组的dtype属性是不同的对象. 通过dtype 对象的type属性可以获得与之对应的数值类型.

>>> c.dtype   # 我的理解是这里的int64 指的是数组中的元素是 int64储存的
dtype(int64)
>>> c.dtype.type  # 这里应该是数组本身是按照 numpy.int64的方式储存的 , 不知道对不对, 如果不对我会回来修改的.
<class numpy.int64>

  通过NumPy的数值类型也可以创建数值对象, 下面创建一个16位的符号整数对象, 它与Python的整数对象不同的是,他的取值范围有限, 因此计算200*200 会溢出,得到一个负数.

>>> a = np.int16(200)
>>> a*a
__console__:1: RuntimeWarning: overflow encountered in short_scalars
-25536
>>> a+a
400

  另外值得指出的是, NumPy的数值对象的运算速度比Python的内置类型的运算速度慢的很多, 如果程序中需要大量的对单个数值进行计算, 应当避免使用NumPy的数值对象. 使用astype()方法可以对数组的元素类型进行转换.

>>> t1 = np.array([1,2,3,4.0],dtype = np.float64)
>>> t2 = np.array([1,2,3,4],dtype = np.complex)
>>> t3 = t1.astype(np.int64)
>>> t4 = t2.astype(np.complex256)
>>> t1.dtype
dtype(float64)
>>> t2.dtype
dtype(complex128)
>>> t3.dtype
dtype(int64)
>>> t4.dtype
dtype(complex256)
>>> t4.dtype.type
<class numpy.complex256>

 

揭开Python科学计算的面纱