多快好省地使用pandas分析大型數據集

1 簡介

  pandas雖然是個很是流行的數據分析利器,但不少朋友在使用pandas處理較大規模的數據集的時候常常會反映pandas運算「慢」,且內存開銷「大」。app

  特別是不少學生黨在使用本身性能通常的筆記本嘗試處理大型數據集時,每每會被捉襟見肘的算力所勸退。但其實只要掌握必定的pandas使用技巧,配置通常的機器也有能力hold住大型數據集的分析。性能

圖1

  本文就將以真實數據集和運存16G的普通筆記本電腦爲例,演示如何運用一系列策略實現多快好省地用pandas分析大型數據集。學習

2 pandas多快好省策略

  咱們使用到的數據集來自kaggle上的TalkingData AdTracking Fraud Detection Challenge競賽( https://www.kaggle.com/c/talkingdata-adtracking-fraud-detection ),使用到其對應的訓練集,這是一個大小有7.01G的csv文件。優化

  下面咱們將按部就班地探索在內存開銷和計算時間成本之間尋求平衡,首先咱們不作任何優化,直接使用pandasread_csv()來讀取train.csv文件:3d

import pandas as pd

raw = pd.read_csv('train.csv')

# 查看數據框內存使用狀況
raw.memory_usage(deep=True)
圖2

  能夠看到首先咱們讀入整個數據集所花費的時間達到了將近三分鐘,且整個過程當中由於中間各類臨時變量的建立,一度快要撐爆咱們16G的運行內存空間。code

  這樣一來咱們後續想要開展進一步的分析但是說是不可能的,由於隨便一個小操做就有可能會由於中間過程大量的臨時變量而撐爆內存,致使死機藍屏,因此咱們第一步要作的是下降數據框所佔的內存:對象

  • 指定數據類型以節省內存

  由於pandas默認狀況下讀取數據集時各個字段肯定數據類型時不會替你優化內存開銷,好比咱們下面利用參數nrows先讀入數據集的前1000行試探着看看每一個字段都是什麼類型:blog

raw = pd.read_csv('train.csv', nrows=1000)
raw.info()
圖3

  怪不得咱們的數據集讀進來會那麼的大,原來全部的整數列都轉換爲了int64來存儲,事實上咱們原數據集中各個整數字段的取值範圍根本不須要這麼高的精度來存儲,所以咱們利用dtype參數來下降一些字段的數值精度:ip

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

  能夠看到,在修改數據精度以後,前1000行數據集的內存大小被壓縮了將近54.6%,這是個很大的進步,按照這個方法咱們嘗試着讀入全量數據並查看其info()信息:內存

圖5

  能夠看到隨着咱們對數據精度的優化,數據集所佔內存有了很是可觀的下降,使得咱們開展進一步的數據分析更加順暢,好比分組計數:

(
    raw
    # 按照app和os分組計數
    .groupby(['app', 'os'])
    .agg({'ip': 'count'})
)
圖6

  那若是數據集的數據類型沒辦法優化,那還有什麼辦法在不撐爆內存的狀況下完成計算分析任務呢?

  • 只讀取須要的列

  若是咱們的分析過程並不須要用到原數據集中的全部列,那麼就不必全讀進來,利用usecols參數來指定須要讀入的字段名稱:

raw = pd.read_csv('train.csv', usecols=['ip', 'app', 'os'])
raw.info()
圖7

  能夠看到,即便咱們沒有對數據精度進行優化,讀進來的數據框大小也只有4.1個G,若是配合上數據精度優化效果會更好:

圖8

  若是有的狀況下咱們即便優化了數據精度又篩選了要讀入的列,數據量依然很大的話,咱們還能夠以分塊讀入的方式來處理數據:

  • 分塊讀取分析數據

  利用chunksize參數,咱們能夠爲指定的數據集建立分塊讀取IO流,每次最多讀取設定的chunksize行數據,這樣咱們就能夠把針對整個數據集的任務拆分爲一個一個小任務最後再彙總結果:

from tqdm.notebook import tqdm

# 在下降數據精度及篩選指定列的狀況下,以1千萬行爲塊大小
raw = pd.read_csv('train.csv', 
                  dtype={
                      'ip': 'int32',
                      'app': 'int16',
                      'os': 'int16'
                  },
                  usecols=['ip', 'app', 'os'],
                  chunksize=10000000)

# 從raw中循環提取每一個塊並進行分組聚合,最後再彙總結果
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

  能夠看到,利用分塊讀取處理的策略,從始至終咱們均可以保持較低的內存負載壓力,而且同樣完成了所需的分析任務,一樣的思想,若是你以爲上面分塊處理的方式有些費事,那下面咱們就來上大招:

  • 利用dask替代pandas進行數據分析

  dask相信不少朋友都有據說過,它的思想與上述的分塊處理其實很接近,只不過更加簡潔,且對系統資源的調度更加智能,從單機到集羣,均可以輕鬆擴展伸縮。

圖10

  推薦使用conda install dask來安裝dask相關組件,安裝完成後,咱們僅僅須要須要將import pandas as pd替換爲import dask.dataframe as dd,其餘的pandas主流API使用方式則徹底兼容,幫助咱們無縫地轉換代碼:

圖11

  能夠看到整個讀取過程只花費了313毫秒,這固然不是真的讀進了內存,而是dask的延時加載技術,這樣纔有能力處理超過內存範圍的數據集

  接下來咱們只須要像操縱pandas的數據對象同樣正常書寫代碼,最後加上.compute()dask便會基於前面搭建好的計算圖進行正式的結果運算:

(
    raw
    # 按照app和os分組計數
    .groupby(['app', 'os'])
    .agg({'ip': 'count'})
    .compute() # 激活計算圖
)

  而且dask會很是智能地調度系統資源,使得咱們能夠輕鬆跑滿全部CPU:

圖12

  關於dask的更多知識能夠移步官網自行學習( https://docs.dask.org/en/latest/ )。

圖13

  以上就是本文的所有內容,歡迎在評論區與我進行討論~

相關文章
相關標籤/搜索