十個編碼過程中的“坑”,一篇文章幫你填平了!
數據科學家是“比任何軟件工程師都更擅長統計學、又比任何統計學家都更擅長軟件工程”的人。許多數據科學家都有統計學背景,但在軟件工程方面經驗很少。本文列出了常見的10個編碼錯誤,希望你能認真閱讀并避免它們。
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 drive等或保存到數據庫,以便收件人可以檢索文件(但不要將它們添加到git,見下文)。
2. 硬編碼無法訪問的路徑
與***個錯誤類似,如果你對其他人無權訪問的路徑進行硬編碼,他們就無法運行代碼并且必須查看許多地方以手動更改路徑。
- 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 讓你的數據易于訪問。
3. 混淆數據與代碼
很多人會這么想:由于數據科學代碼需要數據,為什么不將它轉儲到同一目錄中?當你這么做的時候,很有可能也會把圖像,報告和其他垃圾保存到一個目錄下。這樣就一團亂麻了。
- ├── data.csv
- ├── ingest.py
- ├── other-data.csv
- ├── output.png
- ├── report.html
- └── run.py
解決方案:將文件夾歸類,如數據、報告、代碼等。請參閱#5,并使用#1中提到的工具來存儲和共享數據。
4. 和源代碼一起用Gitcommit命令處理數據
大多數人會在版本控制他們的代碼(如果你不這樣做,那這也是你犯的錯誤之一!)。在嘗試共享數據時,你可能很想把數據文件添加到版本控制中。這對于非常小的文件是可以的;但是git無法針對數據進行優化,尤其是對大文件來說。
- git add data.csv
解決方案:使用#1中提到的工具來存儲和共享數據。如果你真的想版本控制數據,請參閱d6tpipe, DVC(https://dvc.org/) 和Git Large File Storage(https://git-lfs.github.com/)。
5. 編寫函數而不是使用DAGs
說了這么多數據,讓我們談談實際的代碼。
學習編碼時學到的***件事就是函數,因此數據科學代碼主要被處理為一系列線性運行的函數。這會導致一些問題。
- defprocess_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(https://github.com/d6t/d6tflow) 或airflow(https://airflow.apache.org/)。
6. 寫for循環
與函數一樣,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 indf['g'].unique():
- dfdfg = df[df[g']==i]
- groupavg.append(dfg['g'].mean())
- # should be:df.groupby('g').mean()
解決方案:
Numpy(http://www.numpy.org/), scipy(https://www.scipy.org/)和pandas(https://pandas.pydata.org/)為大多數你認為可能需要循環的情況提供了矢量化函數。
7. 不寫單元測試
隨著數據,參數或用戶輸入的變化,代碼可能會中斷,有時你甚至注意不到。這可能導致輸出錯誤,如果有人根據輸出做決策,那么糟糕的數據將導致錯誤的決策!
解決方案:使用assert語句檢查數據質量。pandas有同等性測試,d6tstack
(https://github.com/d6t/d6tstack) 檢查數據攝取,d6tjoin
(https://github.com/d6t/d6tjoin/blob/master/examples-prejoin.ipynb)檢查數據連接。以下是數據檢查示例的代碼:
- assertdf['id'].unique().shape[0] == len(ids) # have data for all ids?
- assertdf.isna().sum()<0.9 # catch missing values
- assertdf.groupby(['g','date']).size().max() ==1 # no duplicate values/date?
- assertd6tjoin.utils.PreJoin([df1,df2],['id','date']).is_all_matched() # all idsmatched?
8. 不記錄代碼
為了急著做分析,你可能囫圇吞棗地弄出結果,然后把結果交給客戶或老板;一個星期后,他們找到你說“能改一下這里嗎”或“能更新一下這個嗎”。這時你看看代碼,完全不記得當初為什么這么寫了?,F在想象一下,其他人還必須運行你的代碼……
- defsome_complicated_function(data):
- datadata = data[data['column']!='wrong']
- datadata = data.groupby('date').apply(lambdax: complicated_stuff(x))
- datadata = data[data['value']<0.9]
- return data
解決方案:在提供分析之后,也要花費額外的時間來記錄編碼時做了什么。你會慶幸自己這么做了的,其他人更會感謝你!這樣你會看起來更專業。
9. 將數據保存為csv或pickle格式
回到數據,畢竟我們在談數據科學。就像函數和for循環一樣,CSV和pickle文件很常用,但它們實際上并不是很好。CSV不包含架構,因此每個人都必須再次解析數字和日期。Pickles解決了這個問題但只能在python中使用并且不會被壓縮。兩者都不是存儲大型數據集的好格式。
- defprocess_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(https://github.com/dask/fastparquet)或其他二進制數據格式,這兩者是壓縮數據的理想格式。d6tflow自動將任務的數據輸出保存為parquet,這樣就不用再操心格式問題了。
10. 使用jupyternotebooks筆記本
這一點也許頗具爭議:jupyternotebooks和CSV一樣普遍。很多人都使用它們。但這并不意味它們就是很好的工具。jupyternotebooks助長了上面提到的軟件工程中的壞習慣,特別是:
- 你很想將所有文件轉儲到一個目錄中
- 編寫自上而下運行的代碼,而不是DAGs
- 沒有將代碼模塊化
- 難以糾錯
- 代碼和輸出混在一個文件中
- 沒有很好地進行版本控制
- 上手很容易,但擴展很難。
解決方案:
使用pycharm (https://www.jetbrains.com/pycharm/)和/或spyder(https://www.spyder-ide.org/)。