刚开始学数据分析,其实我并没注意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](https://i-blog.csdnimg.cn/blog_migrate/9fe1c8121ad485ce2c8a4dffb0879b54.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](https://i-blog.csdnimg.cn/blog_migrate/707a6831d4d72163e41a7cc46b3d4a0b.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](https://i-blog.csdnimg.cn/blog_migrate/117559f09137e936d7f7c48652e9e2b2.jpeg)
到现在,我们就可以知道
为什么array.array和np.array通过np.sum求和,运算速度会如此之快?
np.sum是C语言连续存储数组求和,sum是Python list的object求和,两者速度上的区别具体可以看这篇文章的解释。
Why Python is Slow: Looking Under the Hoodjakevdp.github.ioarray.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.strides
array每一维度的字节长度的元组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数据类型
想学习的小伙伴直接看我的教学视频吧
《科学计算应用基础》,授课目录如下:
- 科学计算数学与编程基础
- 快速处理数据Numpy
- 科学计算Scipy
- 绘图与可视化Matplotlib
- 数据分析库-Pandas
- 图像处理和计算机视觉-OpenCV
B站地址:
哔哩哔哩 ( ゜- ゜)つロ 乾杯~ Bilibilispace.bilibili.com