Numpy notes

Posted by Hux Blog on May 23, 2016

introduce

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

ufunc

  • ufunc是universal function的缩写,它是一种能对数组的每个元素进行操作的函数。NumPy内置的许多ufunc函数都是在C语言级别实现的,因此它们的计算速度非常快。
  • ndarray.dtype
    • 可以通过dtype参数在创建时指定元素类型,若运算自动当做相应type
    • np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]], dtype=np.float)
  • ndarray.shape
    • 通过修改数组的shape属性,在保持数组元素个数不变的情况下,改变数组每个轴的长度。eg:c.shape = (3,4), 令c.shape = 4,3 将c变为4*3的矩阵.
    • 注意从(3,4)改为(4,3)并不是对数组 进行转置,而只是改变每个轴的大小,数组元素在 内存中的位置并没有改变
    • 当某个轴的元素为-1时,将根据数组元素的个数自动计算此轴的长度
  • ndarray.reshape
    • 使用数组的reshape方法,可以创建一个改变了尺寸的新数组,原数组的shape保持不变
    • d = a.reshape((2,2))
    • 数组a和d其实共享数据存储内存区域,因此修改其中任意一个数组的元素都会同时修改另外一个数组的内容
  • NumPy提供了很多专门用来创建数组的函数
    • arange函数类似于python的range函数,通过指定开始值、终值和步长来创建一维数组,注意数组不包括终值。
      • np.arange(0,1,0.1),0~1,0.1
    • linspace函数通过指定开始值、终值和元素个数来创建一维数组,可以通过endpoint关键字指定是否包括终值,缺省设置是包括终值
    • logspace函数和linspace类似,不过它创建等比数列,下面的例子产生1(10^0)到100(10^2)、有20个元素的等比数列
      • np.logspace(0, 2, 20)
  • 如果我们希望将sin函数所计算的结果直接覆盖到数组x上去的话,可以将要被覆盖的数组作为第二个参数传递给ufunc函数.t = np.sin(x,x),此时t和x指向同一块内存。即id(t) == id(x)

    存取元素

  • a = np.arange(10)
  • 下标从0开始
  • 支持clip
  • 支持负下标,不止是-1,其他均可,表示从后数
  • a[1:- 1:2] 第三个表示步长,每两个取出一个
  • a[::-1] 省略范围的开始下标和结束下标,步长为-1,整个数组 头尾颠倒
  • 和Python的列表序列不同,通过 下标范围获取的新的数组是原始数组的一个视图。它与原始数组 共享同一块数据空间.即修改一个另一个也会被修改

  • 两种存取元素的高级方法(这两种方法不和原始数组共享数据空间
    • 使用整数序列 b = x[np.array([3,3,-3,8])]
    • 使用布尔数组 x[np.array([True, False, True, False, False])] 取第0、2元素
      • 布尔数组一般不是手工产生,而是使用布尔运算的ufunc函数产生 x = np.random.rand(10) x[x>0.5] 通过上述代码,利用布尔数组(x>0.5产生的结果)取出了x中大于0.5的数组
    • 以上两种方法为了统一,均把序列 转为np.array

多维数组

  • NumPy采用组元(tuple)作为数组的下标。
    • 组元不需要圆括号.虽然我们经常在Python中用圆括号将组元括起来,但是其实组元的语法定义只需要用逗号隔开即可,例如 x,y=y,x 就是用组元交换变量值的一个例子。

二维可以这样取

  • 使用整数序列和布尔数组访问多维数组中的元素
    • a[(0,1,2,3,4),(1,2,3,4,5)] : 用于存取数组的下标和仍然是一个有两个元素的组元,组元中的每个元素都是整数序列,分别对应数组的第0轴和第1轴。从两个序列的对应位置取出两个整数组成下标: a[0,1], a[1,2], …, a[4,5]。
    • a[3:, [0, 2, 5]] : 下标中的第0轴是一个范围,它选取第3行之后的所有行;第1轴是整数序列,它选取第0, 2, 5三列。

ndarray 内存结构

图中对应 a = np.array([[0,1,2],[3,4,5],[6,7,8]], dtype=np.float32)

  • 如果strides中的数值正好和对应轴所占据的字节数相同的话,那么数据在内存中是连续存储的。然而数据并不一定都是连续储存的,前面介绍过通过下标范围得到新的数组是原始数组的视图,即它和原始视图共享数据存储区域:
b = a[::2,::2]  
b.strides  
(24, 8)
  • 由于数组b和数组a共享数据存储区,而b中的第0轴和第1轴都是数组a中隔一个元素取一个,因此数组b的strides变成了24,8,正好都是数组a的两倍。 对照前面的图很容易看出数据0和2的地址相差8个字节,而0和6的地址相差24个字节。
  • 元素在数据存储区中的排列格式有两种:C语言格式和Fortan语言格式。在C语言中,多维数组的第0轴是最上位的,即第0轴的下标增加1时,元素的地址增加的字节数最多;而Fortan语言的多维数组的第0轴是最下位的,即第0轴的下标增加1时,地址只增加一个元素的字节数。在NumPy中,元素在内存中的排列缺省是以C语言格式存储的