数据分析工具篇——for循环运算优化(一)

2021-01-25 12:13:32 浏览数 (1)

这一系列《数据分析工具篇》的开篇,也是数据分析流程中开始和结束的动作,数据导入之后,紧接着需要做的就是对数据的处理,我们会花费几篇的时间,来和大家聊一下常用的处理逻辑和常见的几个包,在数据处理过程中,常用的处理逻辑主要有:for循环优化广播应用方案以及整体(集合)运算方法,特别是for循环,可以说百分之九十九的函数会出现for循环;常见的包主要有:pandaspysparknumpy,这三个包可谓是人尽皆知,特别是前两个,一个是小数据使用的包,一个是大数据使用的包,随着python的不断丰富,这两个包越来越完善,今天我们先了解一下for循环的优化方法:

for循环

衡量一个程序员python水平的一个比较重要的方面就是看他写的循环嵌套了多少层,怎么解决嵌套的问题,写算法都会知道一个概念,叫:复杂度,分为时间复杂度和空间复杂度。随着服务器等硬件设备价钱的降低,空间复杂度不再是难点,关注更多的反而成了时间复杂度。

而这其中最需要解决的就是for循环的问题。

我们先看一个普通的for循环案例,如下:

代码语言:javascript复制
import time
start_time = time.time()
datas = 0
for t in range(0, 1000):
    for i in range(0, 1000):
        for j in range(0, 1000):
            datas = datas   i   j   t
end_time = time.time()
print(datas)
print(end_time-start_time)

刚开始学python的小伙伴写的循环是不是这样的呢?

小有成就感的同时我们计算了一下运行时间:187s。

在代码运行过程中出现这样的时间简直会疯,因为这段代码不仅会消耗很长时间,而且会吃掉你的内存,让你无法做其他的事情。

写过一段时间python的小伙伴有时会痴迷于它独有的写法,于是有了如下代码:

代码语言:javascript复制
import time
import numpy as np

start_time1 = time.time()
ds = np.sum([i j t for i in range(0, 1000) for j in range(0, 1000) for t in range(0, 1000)])
end_time1 = time.time()
print(ds)
print(end_time1-start_time1)

你猜运行结果会怎样?

代码语言:javascript复制
Traceback (most recent call last):
  File "C:/Users/livan/PycharmProjects/Test/test.py", line 17, in <module>
  ds = np.sum([i j t for i in range(0, 1000) for j in range(0, 1000) for t in range(0, 1000)])
  File "C:/Users/livan/PycharmProjects/Test/test.py", line 17, in <listcomp>
  ds = np.sum([i j t for i in range(0, 1000) for j in range(0, 1000) for t in range(0, 1000)])
MemoryError

有没有很吃惊?有没有很意外?

我们暂且忽略最新函数在上面这段代码中的应用以及报错,单纯看循环的结构,你感觉这段代码怎么样呢?

1)上面的两种运算模式会在整个运算过程占据大量内存,一个不小心就会内存爆炸,而且影响其他应用(上面的两段代码中第二个更消耗内存)。

2)一个for循环就是一次嵌套,几个for循环就是几层笛卡尔积,复杂度为O(Mn),n即为for循环的层数,M为每个for循环的循环次数,数据指数型爆炸。

看到这里是否找到了程序低效的一个原因:嵌套太多,循环次数太多

如何解决呢?

最直观优化方法

For循环的修改可以有比较多的方法,一个常用的思路是:空间换时间。

对于上面的循环,我们如何进行修改呢?

我们直接上代码:

代码语言:javascript复制
import time

data = 0
start_times = time.time()
for j in range(0, 1000):
    data = data   j
data1 = 0
for i in range(0, 1000):
    data1 = (1000 * i   data)   data1
data2 = 0
for t in range(0, 1000):
    data2 = (1000000 * t   data1)   data2
end_times = time.time()
print(data2)
print(end_times - start_times)

你猜这段代码运行多久?

0.0

你没有看错,就是0.0,由于运行时间太快,时间忽略不计了。

我们仔细分析上面的代码就会发现其中的奥秘:

1)代码将for循环做了拆解,三层铺成了一层,在复杂度计算上降低了三个量级,原来是O(n3)=10003,而修改后成为了O(n)=1000,时间上也减少了至少三个量级。

2)运算过程中多出了两个变量:data1,data2,这两个变量的目的就是暂时缓存阶段性的运算结果,便于拆解循环,空间上增加了两个变量。

“空间换时间”由此而得名。

“扁平结构比嵌套结构更好” – 《Python之禅》

但是,细心的小伙伴发现了一个问题:数据的处理逻辑变了~

没错的~

这样的拆解自然是提高效率的,但是难度却非常大,需要对数据流程非常熟悉,这对程序员小伙伴的要求也变的很高,变相增加了优化门槛。

那这一问题如何解决呢?

世间总有大佬,如果没有,就再等等~

Python中提供了一些较为高级的函数和nb的数据结构,这些函数和数据结构已经被各个大佬调优并封装,例如:numpy向量结构、pandas中的groupby、apply、sort_values函数等操作,这一块的内容我们会在接下来的文章中逐一梳理,本文做个引子,有兴趣的大佬可以一起思考提高for循环效率的方法。

需要提醒大家的是:for循环的优化需要转变固有的思想,引入新的数据结构和思路。

我们用一个简单的比喻来结束这篇文章:

简单的for循环就像是毛坯房,基本拥有了住的能力,但想生活质量高一点,就需要好的装修,for循环的优化过程就是房子的装修过程,需要因人而异的精细化设计。

好了,欢迎大家沟通~

0 人点赞