返回介绍

数学基础

统计学习

深度学习

工具

Scala

一、 ndarray

发布于 2023-07-17 23:38:23 字数 25409 浏览 0 评论 0 收藏 0

1. ndarray 对象的内存结构

  1. ndarray对象在内存中的结构如下: array

    • ndarray.dtype:存储了数组保存的元素的类型。float32
    • ndarray.ndim:它是一个整数,保存了数组的维度,即多少个轴
    • ndarray.shape:它是一个整数的元组,每个元素一一对应地保存了数组某个维度的大小(即某个轴的长度)。
    • ndarray.strides:它是一个整数的元组,每个元素保存着每个轴上相邻两个元素的地址差。即当某个轴的下标增加1 时,数据存储区中的指针增加的字节数
    • ndarray.data:它指向数组的数据的存储区

    array_code 可以看到:该数组中元素类型为float32;该数组有2 个轴。每个轴的长度都是 3 个元素。第 0 轴增加1时,下标增加 12字节(也就是 3个元素,即一行的距离); 第 1 轴增加 1时,下标增加 4字节(也就是一个元素的距离)。

  2. 元素在数据存储区中的排列格式有两种:C语言格式和Fortran语言格式。

    • C语言中,多维数组的第 0 轴是最外层的。即 0 轴的下标增加 1时,元素的地址增加的字节数最多
    • Fortran语言中,多维数组的第 0 轴是最内层的。即 0 轴的下标增加 1时,元素的地址增加的字节数最少

    numpy中默认是以 C语言格式存储数据。如果希望改为Fortran格式,则只需要在创建数组时,设置order参数为"F" order

  3. 数组的flags属性描述了数据存储区域的一些属性。你可以直接查看flags属性,也可以单独获取其中某个标志值。

    • C_CONTIGUOUS:数据存储区域是否是C语言格式的连续区域
    • F_CONTIGUOUS:数据存储区域是否是F语言格式的连续区域
    • OWNDATA:数组是否拥有此数据存储区域。当一个数组是其他数组的视图时,它并不拥有数据存储区域,通过视图数组的base属性可以获取保存数据存储区域的那个原始数组。 flags
  4. 数组的转置可以通过其T属性获取。转置数组可以简单的将其数据存储区域看作是Fortran语言格式的连续区域,并且它不拥有数据存储区域。 ndarray_T

  5. 修改数组的内容时,会直接修改数据存储区域。所有使用该数据存储区域的数组都将被同时修改! ndarray_share

1.1 dtype

  1. numpy 有自己的浮点数类型: float16/float32/float64/float128等等。

    • 在需要指定dtype参数时,你可以使用numpy.float16,也可以传递一个表示数值类型的字符串。numpy中的每个数值类型都有几种字符串表示。字符串和类型之间的对应关系都存储在numpy.typeDict字典中。 typeDict
    • dtype是一种对象,它不同于数值类型。只有dtype.type才能获取对应的数值类型 dtype_type
    • 你可以通过np的数值类型np.float32来创建数值对象。但要注意:numpy的数值对象的运算速度比python内置类型的运算速度要慢很多。所以应当尽量避免使用numpy的数值对象 np_float32
  2. 使用ndarray.astype()方法可以对数组元素类型进行转换。 astype

1.2 shape

  1. 你可以使用ndarray.reshape()方法调整数组的维度。

    • 你可以在某个维度设置其长度为 -1,此时该维度的长度会被自动计算 reshape
  2. 你可以直接修改ndarryshape属性,此时直接修改原始数组。

    • 你可以在某个维度设置其长度为 -1,此时该维度的长度会被自动计算 shape

1.3 view

  1. 我们可以通过ndarray.view()方法,从同一块数据区创建不同的dtype数组。即使用不同的数值类型查看同一段内存中的二进制数据。它们使用的是同一块内存。 view
  2. 如果我们直接修改原始数组的dtype,则同样达到这样的效果,此时直接修改原始数组。 dtype_modified

1.4 strides

  1. 我们可以直接修改ndarray对象的strides属性。此时修改的是原始数组。 strides
  2. 你可以使用np.lib.stride_tricks.as_stride()函数创建一个不同strides的视图。 as_strides 注意:使用as_stride时并不会执行内存越界检查,因此shapestride设置不当可能会发生意想不到的错误。

1.5 拷贝和视图

  1. 当处理ndarray时,它的数据存储区有时被拷贝,但有时并不被拷贝。有三种情况。

    • 完全不拷贝:简单的赋值操作并不拷贝ndarray的任何数据,这种情况下是新的变量引用ndarray对象(类似于列表的简单赋值)

    • 视图和浅拷贝:不同的ndarray可能共享相同的数据存储区。如ndarray.view()方法创建一个新的ndarray但是与旧ndarray共享相同的数据存储区。新创建的那个数组称作视图数组。

      • 对于视图数组,ndarray.base返回的是拥有数据存储区的那个底层ndarray。而非视图数组的ndarray.base返回None
      • ndarray.flags.owndata返回数组是否拥有基础数据
      • 对于数组的分片操作返回的是一个ndarray的视图。对数组的索引返回的不是视图,而是含有基础数据。
    • 深拷贝:ndarray.copy()操作会返回一个完全的拷贝,不仅拷贝ndarray也拷贝数据存储区。 copy_view

2. 数组的创建

  1. 这里有几个共同的参数:

    • a:一个array-like类型的实例,它不一定是数组,可以为listtuplelist of tuplelist of listtuple of listtuple of tuple等等。

    • dtype:数组的值类型,默认为float。你可以指定Python的标准数值类型,也可以使用numpy的数值类型如:numpy.int32或者numpy.float64等等。

    • order:指定存储多维数据的方式。

      • 可以为'C',表示按行优先存储(C风格);
      • 可以为'F',表示按列优先存储(Fortran风格)。
      • 对于**_like()函数,order可以为:'C''F''A'(表示结果的ordera相同),'K'(表示结果的ordera尽可能相似)
    • subokbool值。如果为True则:如果andarray的子类(如matrix类),则结果类型与a类型相同。如果为False则:结果类型始终为ndarray。默认为True

2.1 创建全1或者全0

  1. np.empty(shape[,dtype,order]):返回一个新的ndarray,指定了shapedtype,但是没有初始化元素。因此其内容是随机的。

    • np.empty_like(a[,dtype,order,subok]):返回一个新的ndarrayshapea相同,但是没有初始化元素。因此其内容是随机的。

    empty

  2. np.eye(N[, M, k, dtype]):返回一个二维数组,对角线元素为1,其余元素为0。M默认等于Nk默认为0表示对角线元素为1,如为正数则表示对角线上方一格的元素为1,如为负数表示对角线下方一格的元素为1.

    • np.identity(n[, dtype]) :返回一个单位矩阵

    eye_identity

  3. np.ones(shape[, dtype, order]):返回一个新的ndarray,指定了shapetype,每个元素初始化为1.

    • np.ones_like(a[, dtype, order, subok]) :返回一个新的ndarrayshapea相同,每个元素初始化为1。

    ones

  4. np.zeros(shape[, dtype, order]) :返回一个新的ndarray,指定了shapetype,每个元素初始化为0.

    • np.zeros_like(a[, dtype, order, subok]):返回一个新的ndarrayshapea(另一个数组)相同,每个元素初始化为0。

    zeros

  5. np.full(shape, fill_value[, dtype, order]):返回一个新的ndarray,指定了shapetype,每个元素初始化为fill_value

    • np.full_like(a, fill_value[, dtype, order, subok]):返回一个新的ndarrayshapea相同,每个元素初始化为fill_value

    full

2.2 从现有数据创建

  1. np.array(object[, dtype, copy, order, subok, ndmin]):从object创建。

    • object可以是一个ndarray,也可以是一个array_like的对象,也可以是一个含有返回一个序列或者ndarray__array__方法的对象,或者一个序列。
    • copy:默认为True,表示拷贝对象
    • order可以为'C'、'F'、'A'。默认为'A'
    • subok默认为False
    • ndmin:指定结果ndarray最少有多少个维度。
  2. np.asarray(a[, dtype, order]):将a转换成一个ndarray。其中aarray_like的对象, 可以是listlist of tupletupletuple of listndarray类型。order默认为C

  3. np.asanyarray(a[, dtype, order]):将a转换成ndarray

  4. np.ascontiguousarray(a[, dtype]) :返回C风格的连续ndarray

  5. np.asmatrix(data[, dtype]):返回matrix fromdata

  6. np.copy(a[, order]):返回ndarray的一份深拷贝

  7. np.frombuffer(buffer[, dtype, count, offset]):从输入数据中返回一维ndarraycount指定读取的数量,-1表示全部读取;offset指定从哪里开始读取,默认为0。创建的数组与buffer共享内存。buffer是一个提供了buffer接口的对象(内置的bytes/bytearray/array.array类型提供了该接口)。 frombuffer

  8. np.fromfile(file[, dtype, count, sep]) :从二进制文件或者文本文件中读取数据返回ndarraysep:当从文本文件中读取时,数值之间的分隔字符串,如果sep是空字符串则表示文件应该作为二进制文件读取;如果sep" "表示可以匹配0个或者多个空白字符。

  9. np.fromfunction(function, shape, **kwargs):返回一个ndarray。从函数中获取每一个坐标点的数据。假设shape的维度为N,那么function带有N个参数,fn(x1,x2,...x_N),其返回值就是该坐标点的值。

  10. np.fromiter(iterable, dtype[, count]):从可迭代对象中迭代获取数据创建一维ndarray

  11. np.fromstring(string[, dtype, count, sep]):从字符串或者raw binary中创建一维ndarray。如果sep为空字符串则string将按照二进制数据解释(即每个字符作为ASCII码值对待)。创建的数组有自己的数据存储区。 fromstring

  12. np.loadtxt(fname[, dtype, comments, delimiter, ...]):从文本文件中加载数据创建ndarray,要求文本文件每一行都有相同数量的数值。comments:指示注释行的起始字符,可以为单个字符或者字符列表(默认为#)。delimiter:指定数值之间的分隔字符串,默认为空白符。converters:将指定列号(0,1,2...)的列数据执行转换,是一个map,如{0:func1}表示对第一列数据执行func1(val_0)skiprows:指定跳过开头的多少行。usecols:指定读取那些列(0表示第一列)。

2.3 从数值区间创建

  1. np.arange([start,] stop[, step,][, dtype]):返回均匀间隔的值组成的一维ndarray。区间是半闭半开的[start,stop),其采样行为类似Python的range函数。start为开始点,stop为终止点,step为步长,默认为1。这几个数可以为整数可以为浮点数。注意如果step为浮点数,则结果可能有误差,因为浮点数相等比较不准确。

  2. np.linspace(start, stop[, num, endpoint, ...]) :返回num个均匀采样的数值组成的一维ndarray(默认为50)。区间是闭区间[start,stop]endpoint为布尔值,如果为真则表示stop是最后采样的值(默认为True),否则结果不包含stopretstep如果为True则返回结果包含采样步长step,默认为True

  3. np.logspace(start, stop[, num, endpoint, base, ...]):返回对数级别上均匀采样的数值组成的一维ndarray。采样点开始于base^start,结束于base^stopbase为对数的基,默认为 10。

    • 它逻辑上相当于先执行arange获取数组array,然后再执行base^array[i]获取采样点
    • 它没有retstep 关键字参数

    fromrange

3. 数组的索引

3.1 一维数组的索引

  1. 一维数组的索引和列表相同。假设a1 是一维数组

    • 可以指定一个整数i作为索引下标,如a1[i] index_int

    • 可以指定一个切片作为索引下标,如a1[i:j]。通过切片获得的新的数组是原始数组的一个视图,它与原始数组共享相同的一块数据存储空间。 index_slice

    • 可以指定一个整数列表对数组进行存取,如a1[[i1,i2,i3]]。此时会将列表中的每个整数作为下标(i1/i2/i3),使用列表作为下标得到的数组(为 np.array([a1[i1],a1[i2],a1[i3]]))不和原始数组共享数据。 index_list

    • 可以指定一个整数数组作为数组下标,如a1[a2]此时会得到一个形状和下标数组a2相同的新数组。新数组的每个元素都是下标数组中对应位置的值作为下标从原始数组中获得的值。新数组不和原始数组共享数据。

      • 当下标数组是一维数组时,其结果和用列表作为下标的结果相同 index_array
      • 当下标是多维数组时,结果也是多维数组 index_array_2
    • 可以指定一个布尔数组作为数组下标,如a1[b]。此时将获得数组a1中与数组b中的True对应的元素。新数组不和原始数组共享数据。

      • 布尔数组的形状与数组a1 完全相同,它就是一个mask

xxxxxxxxxx
- 如果是布尔列表,情况也相同 - 如果布尔数组的长度不够,则不够的部分作为`False`(该特性是`deprecating`,建议不要使用) ![index_bool](../imgs/ndarray/index_bool.JPG)
  1. 上述介绍的一维数组的索引,既可以用于数组元素的选取,也可以用于数组元素的赋值

    • 你可以赋一个值,此时该值会填充被选取出来的每一个位置

    • 你可以赋值一个数组或者列表,此时数组或者列表的形状要跟你选取出来的位置的形状完全匹配(否则报出警告)

      • 数组不同于列表。对于列表,你无法对列表切片赋一个值,而是要赋一个形状相同的值

    index_assign

3.2 多维数组的索引

  1. 多维数组使用元组作为数组的下标,如a[1,2],当然你也可以添加圆括号为a[(1,2)]

    • 元组中每个元素和数组的每个轴对应。下标元组的第 0 个元素与数组的第 0 轴对应,如第 1 个元素与数组的第 1 轴对应...
  2. 多维数组的下标必须是一个长度和数组的维度ndim相等的元组。

    • 如果下标元组的长度大于数组的维度ndim,则报错
    • 如果下标元组的长度小于数组的维度ndim,则在元组的后面补 :,使得下标元组的长度等于数组维度ndim
    • 如果下标对象不是元组,则Numpy会首先将其转换为元组。

    下面的讨论都是基于下标元组的长度等于数组维度ndim的条件。

  3. 单独生成切片时,需要使用slice(begin,end,step) 来创建。其参数分别为:开始值,结束值,间隔步长。如果某些参数需要省略,则使用None。因此, a[2:,2]等价于a[slice(2,None,None),2]

    • 使用python内置的slice()创建下标比较麻烦(首先构造切片,再构造下标元组),numpy提供了一个numpy.s_对象来帮助我们创建数组下标。s_对象实际上是IndexExpression类的一个对象 numpy_s_
  4. 多维数组的下标元组的元素可能为下列类型之一:整数、切片、整数数组、布尔数组。如果不是这些类型,如列表或者元组,则将其转换成整数数组。 mlt_index

    • 多维数组的下标全部是整数或者切片:索引得到的是元素数组的一个视图。 mlt_index_slice

    • 多维数组的下标全部是整数数组:假设多维数组为 $ MathJax-Element-49 $ 。假设这些下标整数数组依次为 $ MathJax-Element-42 $ 。这 $ MathJax-Element-35 $ 个数组必须满足广播条件。假设它们进行广播之后的维度为 $ MathJax-Element-46 $ ,形状为 $ MathJax-Element-47 $ 即:广播之后有 $ MathJax-Element-46 $ 个轴:第 0 轴长度为 $ MathJax-Element-39 $ ,...,第 $ MathJax-Element-40 $ 轴长度为 $ MathJax-Element-41 $ 。假设 $ MathJax-Element-42 $ 经过广播之后分别为数组 $ MathJax-Element-43 $

      则:索引的结果也是一个数组 $ MathJax-Element-45 $ ,结果数组 $ MathJax-Element-45 $ 的维度为 $ MathJax-Element-46 $ ,形状为 $ MathJax-Element-47 $ 。其中

      $ R[i_0,i_1,\cdots,i_{M-1}]=\\ X[A^{\prime}_1[i_0,i_1,\cdots,i_{M-1}],A^{\prime}_2[i_0,i_1,\cdots,i_{M-1}],\cdots,A^{\prime}_n[i_0,i_1,\cdots,i_{M-1}]] $

      结果数组的下标并不来源于 $ MathJax-Element-49 $ ,而是来源于下标数组的广播之后的数组。相反,如果多维数组的下标为整数或者切片,则结果数组的下标来源于 $ MathJax-Element-49 $

    mlt_index_array

    • 多维数组的下标包含整数数组、切片:则切片/整数下标与整数数组下标分别处理。 mlt_index_array_slice

    • 多维数组的下标是布尔数组或者下标元组中包含了布尔数组,则相当于将布尔数组通过nonzero 将布尔数组转换成一个整数数组的元组,然后使用整数数组进行下标运行。

      • nonzero(a)返回数组a中,值非零的元素的下标。它返回值是一个长度为a.ndim的元组,元组的每个元素都是一个一维的整数数组,其值为非零元素的下标在对应轴上的值。如:第 0 个元素为a中的非零值元素在0轴的下标、第 1 个元素为a中的非零值元素在1轴的下标,... mlt_index_bool
  5. 当下标使用整数或者切片时,所取得的数据在数据存储区域中是等间隔分布的。因为只需要修改数组的ndim/shape/strides等属性以及指向数据存储区域的data指针就能够实现整数和切片下标的索引。所以新数组和原始数组能够共享数据存储区域。

    当使用整数数组(整数元组,整数列表页转换成整数数组),布尔数组时,不能保证所取得的数据在数据存储区中是等间隔的,因此无法和原始数组共享数据,只能对数据进行复制。

  6. 索引的下标元组中:

    • 如果下标元组都是切片,则索引结果的数组与原始数组的维度相同(轴的数量相等)
    • 每多一个整数下标,则索引结果的数组就少一个维度(少一个轴)
    • 如果所有的下标都是整数,则索引结果的维度为 0
    • 如果下标元组中存在数组,则还需要考虑该下标数组广播后的维度
  7. 通过索引获取的数组元素的类型为数组的dtype类型 。如果你想获取标准python类型,可以使用数组的item()方法。

    index_item

3.3 索引的维度变换

  1. 对于数组,如果我们不考虑下标数组的情况,也就是:其下标仅仅为整数、或者切片,则有:

    • 每次下标中出现一个整数下标,则索引结果的维度降 1。该维度被吸收掉
    • 每次下标中出现一个切片下标,则该维度保持不变 index_dim
  2. 前面提到:多维数组的下标必须是一个长度和数组的维度 ndim 相等的元组。但是如果下标中包含None,则可以突破这一限制。每多一个None,则索引结构维度升 1 。

    • 当数组的下标元组的长度小于等于数组的维度ndim时,元组中出现的None等价于切片:
    • 当数组的下标元组的长度大于数组的维度ndim时,元组中哪里出现None,索引结果就在哪里创建一个新轴,该轴长度为 1。如c=a[0,:,None],索引结果的维度为 (3,1);而d=a[0,None,:]的索引结果维度为(1,3)

    index_none

4. 操作多维数组

  1. numpy.concatenate((a1, a2, ...), axis=0):连接多个数组。其中(a1,a2,...)为数组的序列,给出了待连接的数组,它们沿着axis指定的轴连接。

    • 所有的这些数组的形状,除了axis轴之外都相同 concatenate
    • numpy.vstack(tup):等价于numpy.concatenate((a1, a2, ...), axis=0)。沿着 0 轴拼接数组

      • 沿0轴拼接(垂直拼接),增加行
    • numpy.hstack(tup):等价于numpy.concatenate((a1, a2, ...), axis=1)。沿着 1 轴拼接数组

      • 沿1轴拼接(水平拼接),增加列
    • numpy.column_stack(tup):类似于hstack,但是如果被拼接的数组是一维的,则将其形状修改为二维的(N,1)

      • 沿列方向拼接,增加列
    • numpy.c_对象的[]方法也可以用于按列连接数组。但是如果被拼接的数组是一维的,则将其形状修改为二维的(N,1)

      • 沿列方向拼接,增加列

    hstack_vstack1

    hstack_vstack2

  2. numpy.split(ary, indices_or_sections, axis=0)用于沿着指定的轴拆分数组aryindices_or_sections指定了拆分点:

    • 如果为整数N,则表示平均拆分成N份。如果不能平均拆分,则报错
    • 如果为序列,则该序列指定了划分区间(无需指定最开始的0起点和终点)。如[1,3]指定了区间:[0,1],[1,3],[3:]

    numpy.array_split(ary, indices_or_sections, axis=0)的作用也是类似。唯一的区别在于:当indices_or_sections为整数,且无法平均拆分时,并不报错,而是尽可能的维持平均拆分。 split

  3. numpy.transpose(a, axes=None):重置轴序。如果axes=None,则默认重置为逆序的轴序(如原来的shape=(1,2,3),逆序之后为(3,2,1))。如果axes!=None,则要给出重置后的轴序。它获得的是原数组的视图。

    numpy.swapaxes(a, axis1, axis2):交换指定的两个轴axis1/axis2。它获得是原数组的视图。

    transpose

5.打印数组

  1. 当打印ndarray时,numpy按照Python的嵌套list的格式打印输出,但是按照以下顺序打印:

    • 最底层的axis按照从左到右的顺序输出
    • 次底层的axis按照从上到下的顺序输出
    • 其他层的axis也是按照从上到下的顺序输出,但是每个slice中间间隔一条空行

    如: 一维的ndarray按行打印;二维的ndarray按照矩阵打印;三维的ndarray按照矩阵的list打印

    如果ndarray太大,那么numpy默认跳过中间部分的数据而只是输出四个角落的数据。

  2. 要想任何时候都打印全部数据,可以在print(array)之前设置选项numpy.set_printoptions(threshold='nan')。这样后续的打印ndarray就不会省略中间数据。

6. Nan 和无穷大

  1. numpy中,有几个特殊的数:

    • numpy.nan表示NaNNot a Number),它并不等价于numpy.inf(无穷大)。
    • numpy.inf:正无穷
    • numpy.PINF:正无穷(它就引用的是numpy.inf
    • numpy.NINF:负无穷
  2. 有下列函数用于判断这几个特殊的数:

    • numpy.isnan(x[,out]):返回x是否是个NaN,其中x可以是标量,可以是数组

    • numpy.isfinite(x[, out]):返回x是否是个有限大小的数,其中x可以是标量,可以是数组

      • numpy.isfinite(np.nan)返回False,因为NaN首先就不是一个数
    • numpy.isposinf(x[, out]):返回x是否是个正无穷大的数,其中x可以是标量,可以是数组

      • numpy.isposinf(np.nan)返回False,因为NaN首先就不是一个数
    • numpy.isneginf(x[, out]):返回x是否是个负无穷大的数,其中x可以是标量,可以是数组

      • numpy.isneginf(np.nan)返回False,因为NaN首先就不是一个数
    • numpy.isinf(x[, out]):返回x是否是个无穷大的数,其中x可以是标量,可以是数组

      • numpy.isinf(np.nan)返回False,因为NaN首先就不是一个数
  3. 下列函数用于对这几个特殊的数进行转换:

    • numpy.nan_to_num(x):将数组x中的下列数字替换掉,返回替换掉之后的新数组:

      • NaN:替换为0
      • 正无穷:替换为一个非常大的数字
      • 负无穷:替换为一个非常小的数字

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文