如何優化基於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代碼並不是不可能,只是看是否具備相關的習慣,增長可讀性對本身以及團隊的工做和開源社區的聲望都會有利,但願上面的建議對你們有所幫助。

相關文章
相關標籤/搜索