多快好省地使用pandas分析大型數(shù)據(jù)集
1. 簡(jiǎn)介
pandas雖然是個(gè)非常流行的數(shù)據(jù)分析利器,但很多朋友在使用pandas處理較大規(guī)模的數(shù)據(jù)集的時(shí)候經(jīng)常會(huì)反映pandas運(yùn)算“慢”,且內(nèi)存開銷“大”。
特別是很多學(xué)生黨在使用自己性能一般的筆記本嘗試處理大型數(shù)據(jù)集時(shí),往往會(huì)被捉襟見肘的算力所勸退。但其實(shí)只要掌握一定的pandas使用技巧,配置一般的機(jī)器也有能力hold住大型數(shù)據(jù)集的分析。
圖1
本文就將以真實(shí)數(shù)據(jù)集和運(yùn)存16G的普通筆記本電腦為例,演示如何運(yùn)用一系列策略實(shí)現(xiàn)多快好省地用pandas分析大型數(shù)據(jù)集。
2. pandas多快好省策略
我們使用到的數(shù)據(jù)集來自kaggle上的「TalkingData AdTracking Fraud Detection Challenge」競(jìng)賽( https://www.kaggle.com/c/talkingdata-adtracking-fraud-detection ),使用到其對(duì)應(yīng)的訓(xùn)練集,這是一個(gè)大小有7.01G的csv文件。
下面我們將循序漸進(jìn)地探索在內(nèi)存開銷和計(jì)算時(shí)間成本之間尋求平衡,首先我們不做任何優(yōu)化,直接使用pandas的read_csv()來讀取train.csv文件:
- import pandas as pd
- raw = pd.read_csv('train.csv')
- # 查看數(shù)據(jù)框內(nèi)存使用情況
- raw.memory_usage(deep=True)
圖2
可以看到首先我們讀入整個(gè)數(shù)據(jù)集所花費(fèi)的時(shí)間達(dá)到了將近三分鐘,且整個(gè)過程中因?yàn)橹虚g各種臨時(shí)變量的創(chuàng)建,一度快要撐爆我們16G的運(yùn)行內(nèi)存空間。
這樣一來我們后續(xù)想要開展進(jìn)一步的分析可是說是不可能的,因?yàn)殡S便一個(gè)小操作就有可能會(huì)因?yàn)橹虚g過程大量的臨時(shí)變量而撐爆內(nèi)存,導(dǎo)致死機(jī)藍(lán)屏,所以我們第一步要做的是降低數(shù)據(jù)框所占的內(nèi)存:
(1) 指定數(shù)據(jù)類型以節(jié)省內(nèi)存
因?yàn)閜andas默認(rèn)情況下讀取數(shù)據(jù)集時(shí)各個(gè)字段確定數(shù)據(jù)類型時(shí)不會(huì)替你優(yōu)化內(nèi)存開銷,比如我們下面利用參數(shù)nrows先讀入數(shù)據(jù)集的前1000行試探著看看每個(gè)字段都是什么類型:
- raw = pd.read_csv('train.csv', nrows=1000)
- raw.info()
圖3
怪不得我們的數(shù)據(jù)集讀進(jìn)來會(huì)那么的大,原來所有的整數(shù)列都轉(zhuǎn)換為了int64來存儲(chǔ),事實(shí)上我們?cè)瓟?shù)據(jù)集中各個(gè)整數(shù)字段的取值范圍根本不需要這么高的精度來存儲(chǔ),因此我們利用dtype參數(shù)來降低一些字段的數(shù)值精度:
- raw = pd.read_csv('train.csv', nrows=1000,
- dtype={
- 'ip': 'int32',
- 'app': 'int16',
- 'device': 'int16',
- 'os': 'int16',
- 'channel': 'int16',
- 'is_attributed': 'int8'
- })
- raw.info()
圖4
可以看到,在修改數(shù)據(jù)精度之后,前1000行數(shù)據(jù)集的內(nèi)存大小被壓縮了將近54.6%,這是個(gè)很大的進(jìn)步,按照這個(gè)方法我們嘗試著讀入全量數(shù)據(jù)并查看其info()信息:
圖5
可以看到隨著我們對(duì)數(shù)據(jù)精度的優(yōu)化,數(shù)據(jù)集所占內(nèi)存有了非常可觀的降低,使得我們開展進(jìn)一步的數(shù)據(jù)分析更加順暢,比如分組計(jì)數(shù):
- (
- raw
- # 按照app和os分組計(jì)數(shù)
- .groupby(['app', 'os'])
- .agg({'ip': 'count'})
- )
圖6
那如果數(shù)據(jù)集的數(shù)據(jù)類型沒辦法優(yōu)化,那還有什么辦法在不撐爆內(nèi)存的情況下完成計(jì)算分析任務(wù)呢?
(2) 只讀取需要的列
如果我們的分析過程并不需要用到原數(shù)據(jù)集中的所有列,那么就沒必要全讀進(jìn)來,利用usecols參數(shù)來指定需要讀入的字段名稱:
- raw = pd.read_csv('train.csv', usecols=['ip', 'app', 'os'])
- raw.info()
圖7
可以看到,即使我們沒有對(duì)數(shù)據(jù)精度進(jìn)行優(yōu)化,讀進(jìn)來的數(shù)據(jù)框大小也只有4.1個(gè)G,如果配合上數(shù)據(jù)精度優(yōu)化效果會(huì)更好:
圖8
如果有的情況下我們即使優(yōu)化了數(shù)據(jù)精度又篩選了要讀入的列,數(shù)據(jù)量依然很大的話,我們還可以以分塊讀入的方式來處理數(shù)據(jù):
(3) 分塊讀取分析數(shù)據(jù)
利用chunksize參數(shù),我們可以為指定的數(shù)據(jù)集創(chuàng)建分塊讀取IO流,每次最多讀取設(shè)定的chunksize行數(shù)據(jù),這樣我們就可以把針對(duì)整個(gè)數(shù)據(jù)集的任務(wù)拆分為一個(gè)一個(gè)小任務(wù)最后再匯總結(jié)果:
- from tqdm.notebook import tqdm
- # 在降低數(shù)據(jù)精度及篩選指定列的情況下,以1千萬行為塊大小
- raw = pd.read_csv('train.csv',
- dtype={
- 'ip': 'int32',
- 'app': 'int16',
- 'os': 'int16'
- },
- usecols=['ip', 'app', 'os'],
- chunksize=10000000)
- # 從raw中循環(huán)提取每個(gè)塊并進(jìn)行分組聚合,最后再匯總結(jié)果
- result = \
- (
- pd
- .concat([chunk
- .groupby(['app', 'os'], as_index=False)
- .agg({'ip': 'count'}) for chunk in tqdm(raw)])
- .groupby(['app', 'os'])
- .agg({'ip': 'sum'})
- )
- result
圖9
可以看到,利用分塊讀取處理的策略,從始至終我們都可以保持較低的內(nèi)存負(fù)載壓力,并且一樣完成了所需的分析任務(wù),同樣的思想,如果你覺得上面分塊處理的方式有些費(fèi)事,那下面我們就來上大招:
(4) 利用dask替代pandas進(jìn)行數(shù)據(jù)分析
dask相信很多朋友都有聽說過,它的思想與上述的分塊處理其實(shí)很接近,只不過更加簡(jiǎn)潔,且對(duì)系統(tǒng)資源的調(diào)度更加智能,從單機(jī)到集群,都可以輕松擴(kuò)展伸縮。
圖10
推薦使用conda install dask來安裝dask相關(guān)組件,安裝完成后,我們僅僅需要需要將import pandas as pd替換為import dask.dataframe as dd,其他的pandas主流API使用方式則完全兼容,幫助我們無縫地轉(zhuǎn)換代碼:
圖11
可以看到整個(gè)讀取過程只花費(fèi)了313毫秒,這當(dāng)然不是真的讀進(jìn)了內(nèi)存,而是dask的延時(shí)加載技術(shù),這樣才有能力處理「超過內(nèi)存范圍的數(shù)據(jù)集」。
接下來我們只需要像操縱pandas的數(shù)據(jù)對(duì)象一樣正常書寫代碼,最后加上.compute(),dask便會(huì)基于前面搭建好的計(jì)算圖進(jìn)行正式的結(jié)果運(yùn)算:
- (
- raw
- # 按照app和os分組計(jì)數(shù)
- .groupby(['app', 'os'])
- .agg({'ip': 'count'})
- .compute() # 激活計(jì)算圖
- )
并且dask會(huì)非常智能地調(diào)度系統(tǒng)資源,使得我們可以輕松跑滿所有CPU:
圖12
關(guān)于dask的更多知識(shí)可以移步官網(wǎng)自行學(xué)習(xí)( https://docs.dask.org/en/latest/ )。
圖13