資料科學家是“比軟體工程師更擅長統計學,比統計學家更擅長軟體工程的人”。許多資料科學家都具有統計學背景,但是在軟體工程方面的經驗甚少。我是一名資深資料科學家,在Stackoverflow的python程式設計方面排名前1%,並與許多(初級)資料科學家共事。以下是我經常看到的10大常見錯誤,本文將為你相關解決方案:
- 不共享程式碼中引用的資料
- 對無法訪問的路徑進行硬編碼
- 將程式碼與資料混合
- 在Git中和原始碼一起提交資料
- 編寫函式而不是DAG
- 寫for迴圈
- 不編寫單元測試
- 不寫程式碼說明文件
- 將資料儲存為csv或pickle檔案
- 使用jupyter notebook
1. 不共享程式碼中引用的資料
資料科學需要程式碼和資料。因此,為了讓別人可以復現你的結果,他們需要能夠訪問到資料。道理很簡單,但是很多人忘記分享他們程式碼中的資料。
import pandas as pd
df1 = pd.read_csv('file-i-dont-have.csv') # fails
do_stuff(df)
解決方案:使用d6tpipe(https://github.com/d6t/ d6tpipe)來共享你的程式碼中的資料檔案、將其上傳到S3/web/google驅動等,或者儲存到資料庫,以便於別人可以檢索到檔案(但是不要將其新增到git,原因見下文)。
2. 對無法訪問的路徑進行硬編碼
與錯誤1相似,如果你對別人無法訪問的路徑進行硬編碼,他們將無法執行你的程式碼,並且必須仔細檢視程式碼來手動更改路徑。令人崩潰!
import pandas as pd
df = pd.read_csv('/path/i-dont/have/data.csv') # fails
do_stuff(df)
# or
import os
os.chdir('c:\\Users\\yourname\\desktop\\python') # fails
解決方案:使用相對路徑、全域性路徑配置變數或d6tpipe,使你的資料易於訪問。
d6tpipe:
https://github.com/d6t/d6tpip
3. 將程式碼與資料混合
既然資料科學的程式碼中包含資料,為什麼不把它們放到同一目錄中?那樣你還可以在其中儲存影像、報告和其他垃圾。哎呀,真是一團糟!
├── data.csv
├── ingest.py
├── other-data.csv
├── output.png
├── report.html
└── run.py
解決方案:將你的目錄進行分類,比如資料、報告、程式碼等。請參閱Cookiecutter Data Science或d6tflow專案模板[見#5],並使用#1中提到的工具來儲存和共享資料。
Cookiecutter Data Science:
https://drivendata.github.io/cookiecutter-data-science/
d6tflow專案模板:
https://github.com/d6t/d6tflow-templat
4. 在Git中和原始碼一起提交資料
現在,大多數人對他們的程式碼使用版本控制(如果你不使用,那就是另外一個錯誤,請參閱git:https://git-scm.com/)。在嘗試共享資料時,很容易將資料檔案新增到版本控制中。當檔案很小時是可以的,但是git並沒有針對資料進行最佳化,尤其是大檔案。
git add data.csv
解決方案:使用第1點中提到的工具來儲存和共享資料。如果你真的希望對資料進行版本控制,請參閱 d6tpipe,DVC和Git大檔案儲存。
d6tpipe:
https://github.com/d6t/d6tpipe
DVC:
https://dvc.org/
Git大檔案儲存:
https://git-lfs.github.com
5. 編寫函式而不是DAG
關於資料部分已經夠多了,現在來談一談實際的程式碼!在學習程式設計時最先學習的內容之一就是函式,資料科學程式碼通常由一系列線性執行的函式組成。
這會導致一些問題,請參閱“為什麼你的機器學習程式碼可能不好的4個原因”:
https://github.com/d6t/d6t-python/blob/master/blogs/reasons-why-bad-ml-code.rst
def process_data(data, parameter):
data = do_stuff(data)
data.to_pickle('data.pkl')
data = pd.read_csv('data.csv')
process_data(data)
df_train = pd.read_pickle(df_train)
model = sklearn.svm.SVC()
model.fit(df_train.iloc[:, :-1], df_train['y'])
解決方案:資料科學程式碼不是一系列線性連線的函式,而是一組具有依賴關係的任務集合。請使用d6tflow或airflow。
d6tflow:
https://github.com/d6t/d6tflow-template
airflow:
https://airflow.apache.org
6. 寫for迴圈
與函式類似,for迴圈也是你學習程式設計時最初學習的內容。它們易於理解,但是執行緩慢且過於冗長,通常意味著你不瞭解向量化的替代方案。
x = range(10)
avg = sum(x)/len(x); std = math.sqrt(sum((i-avg)**2 for i in x)/len(x));
zscore = [(i-avg)/std for x]
# should be: scipy.stats.zscore(x)
# or
groupavg = []
for i in df['g'].unique():
dfg = df[df[g']==i]
groupavg.append(dfg['g'].mean())
# should be: df.groupby('g').mean()
解決方案:Numpy,scipy和pandas為你需要for迴圈的情況提供了向量化函式。
Numpy:
http://www.numpy.org/
scipy:
https://www.scipy.org/
pandas:
https://pandas.pydata.org
7. 不編寫單元測試
隨著資料、引數或使用者輸入的改變,你的程式碼可能會出現問題,有時你並沒有注意到。這可能會導致糟糕的輸出結果,而如果有人基於你的輸出做出決策,那麼糟糕的資料將會導致糟糕的決策。
解決方案:使用assert語句來檢查資料質量。pandas有相等測試,d6tstack有資料提取檢查以及用於資料連線的d6tjoin。
pandas相等測試:
https://pandas.pydata.org/pandas-docs/stable/reference/general_utility_functions.html
d6tstack:
https://github.com/d6t/d6tstack
d6tjoin:
https://github.com/d6t/d6tjoin/blob/master/examples-prejoin.ipyn
以下是資料檢查的示例程式碼:
assert df['id'].unique().shape[0] == len(ids) # have data for all ids?
assert df.isna().sum()<0.9 # catch missing values
assert df.groupby(['g','date']).size().max() ==1 # no duplicate values/date?
assert d6tjoin.utils.PreJoin([df1,df2],['id','date']).is_all_matched() # all ids matched?
8. 不寫程式碼說明文件
我明白,你急著做出一些分析結果。你把事情彙總到一起分析,將結果交給你的客戶或老闆。一個星期之後,他們回來說,“可以把XXX改一下嗎”或者“可以更新一下這裡嗎”。你看著你的程式碼,但是並不記得你當初為什麼這麼寫。現在就像是在執行別人的程式碼。
def some_complicated_function(data):
data = data[data['column']!='wrong']
data = data.groupby('date').apply(lambda x: complicated_stuff(x))
data = data[data['value']<0.9]
return data
解決方案:即使在你已經提交分析報告後,也要花費額外的時間,來對你做的事情編寫說明文件。以後你會感謝自己,別人更會感謝你。那樣顯得你很專業!
9. 將資料儲存為csv或pickle檔案
回到資料,畢竟是在講資料科學。就像函式和for迴圈一樣,CSV和pickle檔案很常用,但是並不好用。CSV檔案不包含綱要(schema),因此每個人都必須再次解析數字和日期。Pickle檔案解決了這個問題,但是它只能在python中使用,並且不能壓縮。兩者都不是儲存大型資料集的最優格式。
def process_data(data, parameter):
data = do_stuff(data)
data.to_pickle('data.pkl')
data = pd.read_csv('data.csv')
process_data(data)
df_train = pd.read_pickle(df_train)
解決方案:使用parquet或其他帶有資料綱要的二進位制資料格式,在理想情況下可以壓縮資料。d6tflow將任務的資料輸出儲存為parquet,無需額外處理。
parquet:
https://github.com/dask/fastparquet
d6tflow:
https://github.com/d6t/d6tflow-template
10. 使用jupyter notebook
最後一個是頗有爭議的錯誤:jupyter notebook和csv檔案一樣普遍。許多人使用它們,但是這並不意味著它們很好。jupyter notebook助長了上述提到的許多不良程式設計習慣,尤其是:
把所有檔案儲存在一個目錄中
編寫從上至下執行的程式碼,而不是DAG
沒有對程式碼進行模組化
很難除錯
程式碼和輸出混在一個檔案中
沒有很好的版本控制
它容易上手,但是擴充套件性很差。
解決方案:使用pycharm和/或spyder。
pycharm:
https://www.jetbrains.com/pycharm/
spyder:
https://www.spyder-ide.org
作者簡介:Norman Niemer是一家大規模資產管理公司的首席資料科學家,他在其中釋出資料驅動的投資見解。他有哥倫比亞大學的金融工程專業理學碩士學位,和卡斯商學院(倫敦)的銀行與金融專業理學學士學位。
原文標題:
Top 10 Coding Mistakes Made by Data Scientists
原文連結:
https://github.com/d6t/d6t-python/blob/master/blogs/top10-mistakes-coding.md