怎样将三维ndarray格式的数据存储为文件_数据分析小书2-Numpy深度解析

刚开始学数据分析,其实我并没注意Numpy有什么牛逼,觉得不就是个多维数组和各种计算嘛,有什么大不了的,到后面玩的数据量大了,这才发现Numpy在所有Python数据分析与挖掘工具都能看到他的影子,玩的溜不溜理解的深不深,直接影响后期模型效率优化。哪还有半点马虎,跑回来屁屁颠颠研究它呗。

天下武功,蹲马步为先。 那Numpy就是数据分析和挖掘里最最基本的蹲马步!

说了那么多,到底Numpy神奇在什么地方呢?

1。一个小例子引发的思考

我们从一个1000万个整数求和的小例子开始,代码如下所示

import numpy as np
import array

#list sum
li=[1 for i in range(10000000)]
%timeit sum(li)
%timeit np.sum(li)

#array.array sum
a_array =array.array('i',li)
%timeit sum(a_array)
%timeit np.sum(a_array)

#numpy.array sum
np_array=np.array(li)
%timeit sum(np_array)
%timeit np.sum(np_array)

上面的代码,对1000万个数据进行两种sum求和操作,分别用到了Python里三种线性存储array的形式:Python的List,标准Array库的array以及Numpy里array,先预存储数据,然后用jupyter的行魔法命令%timeit,分别测试两种求和方式sum和np.sum的代码执行时间。

猜猜,上面的测试结果会怎样??哪个最快,哪个最慢?

揭晓答案,显示结果如下:

124 ms ± 7.31 ms per loop
1e+03 ms ± 59.8 ms per loop
159 ms ± 5.18 ms per loop
7.76 ms ± 367 µs per loop
1.37 s ± 58.1 ms per loop
7.62 ms ± 538 µs per loop 

简单整理画了一张运算时间的对比图,看看,是不是跟你预想,相差太大了:

170fcb7f6a84a661186a461e09c04f99.png

那问题,就来了:

为什么array.array和np.array通过np.sum求和,运算速度会如此之快?

答案一会揭晓。带着这个问题,我们再对比测试一下C语言的运行速度,同样测试1000万个整数求和。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define MAX 10000000

int main(int argc, char *argv[])
{
  int *list=(int *)malloc(MAX*sizeof(int));
  int i,start,end;
  int sum=0;
  
  //初始化数组 
  for(i=0;i<MAX;i++){
      list[i]=1; 
  }
  //求和运算 
  start=clock();
  for(i=0;i<MAX;i++){
      sum+=list[i]; 
  }
  end = clock();
  //输出时间
  printf("%d %dms",sum,end-start);
  
  system("PAUSE");
  return 0;
}

a34aa4e4e48cfb87d30c60b6554410f3.png

C代码,编译不优化执行时间为20-30ms,编译优化后计算时间为5-10ms。

刚刚我们测试用np.sum配合array.array和np.array的时间在7ms左右,也就是说,已经完全媲美C语言的原生代码速度了。

下面开始我们的解析之路,分两个维度:

list,array.array,np.array三者结构有什么不同?
sum和np.sum两者运行方式有什么不同?

先看下面list在Python源码里的结构定义,里面是一个二维指针PyObject **ob_item指向的内存块保存所有的数据,这里保存一个指针数组,数组里的每个地址指向最终的对象元素,这样设计不好的地方是二级索引元素,好的地方是而Py_ssize_t allocated是已经分配的指针数组大小。

#ifndef Py_LIMITED_API
typedef struct {
    PyObject_VAR_HEAD
    /* Vector of pointers to list elements.  list[0] is ob_item[0], etc. */
    PyObject **ob_item;

    /* ob_item contains space for 'allocated' elements.  The number
     * currently in use is ob_size.
     * Invariants:
     *     0 <= ob_size <= allocated
     *     len(list) == ob_size
     *     ob_item == NULL implies ob_size == allocated == 0
     * list.sort() temporarily sets allocated to -1 to detect mutations.
     *
     * Items must normally not be NULL, except during construction when
     * the list is not yet visible outside the function that builds it.
     */
    Py_ssize_t allocated;
} PyListObject;
#endif

我们再看array的源代码,char*ob_item一维数组直接保存数据,struct arraydescr *ob_descr用来描述每个数据的类型和操作,与list不同的是,array直接保存数据数组,索引数据非常快,但是只能存储同一数据类型(二进制格式存储,非Python的int对象)。numpy的源代码和结构图,跟array.array差不多。

//Array array
typedef struct arrayobject {
    PyObject_VAR_HEAD
    char *ob_item;
    Py_ssize_t allocated;
    const struct arraydescr *ob_descr;
    PyObject *weakreflist; /* List of weak references */
    int ob_exports;  /* Number of exported buffers */
} arrayobject;

//Numpy array
typedef struct PyArrayObject {
        PyObject_HEAD

        /* Block of memory */
        char *data;

        /* Data type descriptor */
        PyArray_Descr *descr;

        /* Indexing scheme */
        int nd;
        npy_intp *dimensions;
        npy_intp *strides;

        /* Other stuff */
        PyObject *base;
        int flags;
        PyObject *weakreflist;
} PyArrayObject;

fed6a62b01e982c3c6ef1dd72ac4f833.png

到现在,我们就可以知道

为什么array.array和np.array通过np.sum求和,运算速度会如此之快?

np.sum是C语言连续存储数组求和,sum是Python list的object求和,两者速度上的区别具体可以看这篇文章的解释。

Why Python is Slow: Looking Under the Hood​jakevdp.github.io

array.array和np.array内部数据存储就是C语言的数组,而np.sum底层是针对数组的C代码求和,所以效率上接近C就很自然了。

针对长整型,存储空间占用:

  • np.array存储数据:96 + n * 8 Bytes
  • list存储数据空间:64 + 8 * len(lst) + + len(lst) * 28

现在你知道,数据分析要用numpy的原因了吧,很简单,就是占用空间小,而且还速度快!

结论:

1。numpy的array是同类型的数组结构,Python的list是对象指针的数组,所以索引速度numpy更快。 2。大多数的numpy的操作,底层都是用C实现,因此不会产生Python里面的循环损耗,指针重定向和动态类型检测。

2。深入Numpy的内部机制

前一小节,我们简单对比了一下list,array.array和numpy.array,这一小节我们详细介绍一下numpy的ndarray的内部机制。

2.1内存存储

x = np.array([1, 2, 3], dtype=np.int32)
bytes(x.data)

输出显示data的字节数据

b'x01x00x00x00x02x00x00x00x03x00x00x00'

flags可以查看array的状态

x = np.array([[1, 2, 3],[3, 4, 5]], dtype=np.int32)
y = x[:-1]
y[0,0]=9
x
#array([[9, 2, 3],
#       [3, 4, 5]])

输出显示,意味着y和x共用data字节内存块,这也就意味着修改y的同时也在修改x,这一点一定要注意。

下面我们详细介绍一下ndarray的内部存储相关的属性

ndarray.flags array内存存储的详细信息 ndarray.shape 各个维数元组 ndarray.stridesarray每一维度的字节长度的元组 ndarray.ndim 空间维数 ndarray.data 数据data存储地址 ndarray.size 元素总个数 ndarray.itemsize每个元素的字节大小 ndarray.nbytes 所有元素的总字节大小 ndarray.base data数据存储内存块复用的父对象

我们对上面的array变量x分别测试输出,就明白意思了。

#flags
  C_CONTIGUOUS : True   #C语言连续存储模式
  F_CONTIGUOUS : False  #Fortan连续存储模式
  OWNDATA : True        #拥有data数据区
  WRITEABLE : True      #可写
  ALIGNED : True        #对齐
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False
#shape 2行3列
(2, 3)
#strides 1行3个元素,每个4字节,所以12字节为一行,列的话就是元素字节数4
(12, 4)
#ndim  两维空间
2
#data   数据的存储地址
<memory at 0x000001C3F0EFF480>
#size   总共元素个数
6
#itemsize  每个元素字节数
4
#nbytes    所有元素字节数
24
#base      data数据存储内存块复用的父对象
None

2.2数据类型

想学习的小伙伴直接看我的教学视频吧

《科学计算应用基础》,授课目录如下:

  1. 科学计算数学与编程基础
  2. 快速处理数据Numpy
  3. 科学计算Scipy
  4. 绘图与可视化Matplotlib
  5. 数据分析库-Pandas
  6. 图像处理和计算机视觉-OpenCV

B站地址:

哔哩哔哩 ( ゜- ゜)つロ 乾杯~ Bilibili​space.bilibili.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值