如何优化基于Jupyter的分析/挖掘测试项目

对于一个有软件工程项目基础的程序员而言,咱们这群来源「可疑」的Data Scientist最被人诟病的就是期代码质量堪忧到让人崩溃的程度。本篇文章将介绍本身在以python/Jupyter Notebook为基础的分析/挖掘项目时是如何优化代码使其具备更大的可读性(执行效率不是本文的主要目的)。python

Python语法级别的优化

合适的style

固然,这个层面的优化是最简单的,你们熟悉的PEP风格和GOOGLE风格都是不错的实践。参加下面两个文档:git

值得一试的命名方案

多年经(踩)验(坑)摸索出一套比较适合的变量命名方案,基本的方法是程序员

[具象词](_[操做])(_[介词短语])_[数据结构]

具象词是描述数据的具体用处的词语,例如一个班级的男生的成绩,能够用boy_score来表述。github

操做则是归纳了对数据作过什么处理,若是是我的团队能够维护一个简单的缩略词词库。好比,咱们要描述一个班级的成绩作过去空值的处理,能够用drop_null或者rm_na来表示。在这种状况下,咱们能够对上面的对象完整描述成boy_score_rm_namarkdown

介词短语其实也是操做的一部分,每每以in/over/top(不严谨地归在此类)/group by等。好比,咱们这里要描述一个班级的学生按性别取前10的成绩并作了去重处理,能够写成score_unique_groupbysex_top10,若是长度过长,维护一个简写的映射表固然也是不错的(牺牲部分可读性)。数据结构

数据结构则是描述数据是存储在什么数据结构中的,常见的好比listpandas.DataFramedict等,在上面的例子里,咱们储存在pandas.DataFrame则能够写成score_unique_groupbysex_top10_dfapp

操做介词短语在不少场合下能够不写。固然在更加抽象地机器学习训练中时,能够以test_dftrain_df这种抽象描述是更适合的方案。机器学习

Jupyter级别的优化

线性执行

这点是容易忽视的一个问题,任何Jupyter里面的cell必定要保证,具体的cell中的代码是自上而下执行的。这在工做中,可能因为反复调试,致使在编辑cell的过程当中不是线性操做。保证notebook能够线性执行的缘由,一部分是方便其余阅读人能够正常执行整个notebook;另外一部分,也是为了增长可读性。ide

<!-- 可接受的例子 -->

​```cell 1
import pandas as pd
​```

​```cell 2
df = pd.read_csv('train.csv')
​```

​```cell 3
def sum_ab(row):
    return row['a'] + row['b']
​```

​```cell 4
df.apply(sum_ab, axis=1)
​```
<!-- 不可接受的例子,不能正常运行 -->

​```cell 1
import pandas as pd
​```

​```cell 2
df = pd.read_csv('train.csv')
​```

​```cell 3
df.apply(sum_ab, axis=1)
​```

​```cell 4
def sum_ab(row):
    return row['a'] + row['b']
​```

载入模块和读入数据放在开头

在具体分析中,载入模块和数据是很是常见的工做,而放置在notebook开始容易帮助阅读者注意,须要的依赖以及数据。若是零散地出如今notebook任何地方,这样就容易形成困难的路径依赖检查、模块重命名方式和模块依赖检查。学习

若是有些模块并不经常使用,但在notebook引用到了,建议在载入模块前的cell中加入一个cell用来安装依赖。jupyter能够用!来实现临时调用系统的命令行,这个能够很好地利用在这个场景中。

此外,全部数据和自写模块使用相对连接的方式导入是不错的选择。由于,后期可能别人会经过git的方法获取你的代码,相对连接能够容许咱们不修改连接也能使用git项目中的模块和数据。

​```cell 1
!pip install scikit-learn
​```

​```cell 2
import panda as pd
from sklearn import metrics
import sys

## 自编模块
sys.path.append('../')
from my_module import my_func
​```

​```cell 3
df = pd.read_csv('./train.csv')
​```

一个Cell一个功能

咱们在具体撰写Jupyter时,没法避免,会反复尝试,而且测试中间输出的结果。所以,不少同行的cell代码每每会呈现如下状态。

​```cell 1
df_1 = df.sum(axis = 1)
​```

​```cell 2
df_2 = df_1.fill_na(0)
​```

​```cell 3
ggplot(df_2, aes(x = 'x', y = 'y')) + geom_point()
​```

这样的作法无可厚非,且并无错误,可是,这样的缺点是,读者可能须要run三个cells才能得出最终的结果,而中间的处理过程,在阅读报告中,每每是可有可无的甚至会影响到可读性,具体查看中间步骤只有复现和纠错时才是必要的。所以,咱们要保持一个原则,就是一个Cell理应要承担一个功能需求的要求。具体优化以下:

​```cell 1
df_1 = df.sum(axis = 1)
df_2 = df_1.fill_na(0)

## 绘图
ggplot(df_2, aes(x = 'x', y = 'y')) + geom_point()
​```

但不少朋友可能会遇到,过程过于复杂,致使一个cell看起来很是的冗余,或者处理过程异常漫长,须要一些中间表的状况,后面的建议中,我会提到如何改善这两个问题。

数据(包括中间结果)与运算分离

回到刚才的问题,不少时候,咱们会遇到一个中间过程的运行时间过长,如何改变这种情况呢,好比上个例子,咱们发现fillna的时间很长,若是放在一个cell中,读者在本身从新运行时或者本身测试时就会很是耗时。一个具体的解决方案就是,写出临时结果,而且在本身编辑过程当中,维持小cell,只在最后呈递时作处理。好比上面的任务,我会如此工做。

​```cell 1
df_1 = df.sum(axis = 1)
​```

​```cell 2
df_2 = df_1.fill_na(0)
df_2.to_pickle('../temp/df_2.pickle')
​```

​```cell 3
ggplot(df_2, aes(x = 'x', y = 'y')) + geom_point()
​```

最后呈递notebook时改为以下样子

​```cell 1
##~~~~ 中间处理 ~~~~##
# df_1 = df.sum(axis = 1)
# df_2 = df_1.fill_na(0)
# df_2.to_pickle('../temp/df_2.pickle')
##~~~~ 中间处理 ~~~~##

df_2 = pd.read_pickle('../temp/df_2.pickle')

## 绘图
ggplot(df_2, aes(x = 'x', y = 'y')) + geom_point()
​```

这样作的另外个好处能够实现,数据在notebook之间的交互,咱们会在以后提到具体场景。

抽象以及可复用分离到Notebook外部

咱们在撰写notebook时遇到的另外一个问题,不少具体地清洗或者特征工程时的方法,过于隆长,而这些方法,可能在别的地方也会复用,此时写在一个cell里就很是很差看,例以下面一个方法。

​```cell 1
def func1(x):
    """add 1
    """
    return x + 1

def func2(x):
    temp = list(map(func1, x))
    temp.sorted()
    return temp[0] + temp[-1]

df.a.apply(func2, axis)
​```

这种状况的解决方案,是独立于notebook以外维护一个文件夹专门存放notebook中会用到的代码。例如,使用以下结构存放模块,具体使用时再载入。

--- my_module
  |__ __init__.py
  |__ a.py
___ notebook
  |__ test.ipynb

注意使用sys模块添加环境来载入模块。

## import cell
import pandas as pd
import sys

sys.path.append('../')
from my_module import *

但这种方案,存在一个问题——咱们会常常改动模块的内容,特别是在测试时,这时候须要能重载模块,这个时候咱们须要反复重载模块,python在这方面的原生支持并不友善,甚至重开内核运行全部都比手写这个重载代码快。这时候咱们须要一个ipythonmagic extension——autoreload,具体的方法参考这个问答

咱们只须要在载入模块的开头写上以下行,这样咱们每当修改咱们本身编写的module时就会重载模块,实现更新。

%load_ext autoreload
%autoreload 2

import pandas as pd
import sys

sys.path.append('../')
from my_module import *

而模块分离最终的好处是,咱们最后能够容易的把这些模块最后移植到生产环境的项目中。

项目级别的优化

一个notebook解决一个问题

为了让项目更加具备可读性,对任务作一个分解是不错的方案,要实现一个notebook只执行一个问题。具体,我在工做中会如下列方案来作一个jupyter的notebook分类。其中0. introduction and contents.ipynb是必须的,里面要介绍该项目中其余notebook的任务,并提供索引。这种方案,就能够提升一个分析/挖掘项目的可读性。

- 0. introduction and contents.ipynb
- eda.1 EDA问题一.ipynb
- eda.2 EDA问题二.ipynb
- eda. ...
- 1.1 方案一+特征工程.ipynb
- 1.2 方案一训练和结果.ipynb
- 2.1 方案二+特征工程.ipynb
- 2.2 方案二训练和结果.ipynb
- 3.1 方案三+特征工程.ipynb
- 3.2 方案三训练和结果.ipynb
- ...
- final.1 结论.ipynb

对文件进行必要的整理

一个分析、挖掘的项目,常常包括但不限于数据源中间文件临时文件最终报告等内容,所以一个好的整理项目文件的习惯是必要的。我在工做中,具体采用下面这个例子来维护整个分析/挖掘项目。固然,初始化这些文件夹是一个很是麻烦的,所以,这里分享一个初始化脚本(支持python版本3.6+),你们能够根据本身整理习惯稍做修改。

-- 项目根目录
  |__ SQL:存储须要用的SQL
  |__ notebook: 存放notebook的地方
     |__ 0. introduction and contents.ipynb
     |__ eda.1 EDA问题一.ipynb
     |__ eda.2 EDA问题二.ipynb
     |__ eda. ...
     |__ 1.1 方案一+特征工程.ipynb
     |__ 1.2 方案一训练和结果.ipynb
     |__ 2.1 方案二+特征工程.ipynb
     |__ 2.2 方案二训练和结果.ipynb
     |__ 3.1 方案三+特征工程.ipynb
     |__ 3.2 方案三训练和结果.ipynb
     |__ ...
     |__ final.1 结论.ipynb
  |__ src: 撰写报告或者文档时须要引用的文件
  |__ data: 存放原始数据
     |__ csv: csv文件
         |__ train.csv
         |__ ...
     |__ ...
  |__ temp: 存放中间数据
  |__ output: 最后报告须要的综合分析结果
     |__ *.pptx
     |__ *.pdf
     |__ src
         |__ example.png
         |__ ...
  |__ temp_module: 本身写的notebook须要引用的模块

结语

优化一个Jupyter代码并不是不可能,只是看是否具备相关的习惯,增长可读性对本身以及团队的工做和开源社区的声望都会有利,但愿上面的建议对你们有所帮助。

相关文章
相关标签/搜索