您好,登錄后才能下訂單哦!
小編給大家分享一下python如何使用pandas處理大數據節省內存,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
一般來說,用pandas處理小于100兆的數據,性能不是問題。當用pandas來處理100兆至幾個G的數據時,將會比較耗時,同時會導致程序因內存不足而運行失敗。
當然,像Spark這類的工具能夠勝任處理100G至幾個T的大數據集,但要想充分發揮這些工具的優勢,通常需要比較貴的硬件設備。而且,這些工具不像pandas那樣具有豐富的進行高質量數據清洗、探索和分析的特性。對于中等規模的數據,我們的愿望是盡量讓pandas繼續發揮其優勢,而不是換用其他工具。
本文我們討論pandas的內存使用,展示怎樣簡單地為數據列選擇合適的數據類型,就能夠減少dataframe近90%的內存占用。
處理棒球比賽記錄數據
我們將處理130年的棒球甲級聯賽的數據,數據源于
Retrosheet(http://www.retrosheet.org/gamelogs/index.html)
原始數據放在127個csv文件中,我們已經用csvkit
(https://csvkit.readthedocs.io/en/1.0.2/)
(https://data.world/dataquest/mlb-game-logs)
我們從導入數據,并輸出前5行開始:
我們將一些重要的字段列在下面:
date- 比賽日期
v_name- 客隊名
v_league- 客隊聯賽
h_name- 主隊名
h_league- 主隊聯賽
v_score- 客隊得分
h_score- 主隊得分
v_line_score- 客隊線得分, 如010000(10)00.
h_line_score- 主隊線得分, 如010000(10)0X.
park_id- 主辦場地的ID
attendance- 比賽出席人數
我們可以用Dataframe.info()方法來獲得我們dataframe的一些高level信息,譬如數據量、數據類型和內存使用量。
這個方法默認情況下返回一個近似的內存使用量,現在我們設置參數memory_usage為‘deep'來獲得準確的內存使用量:
我們可以看到它有171907行和161列。pandas已經為我們自動檢測了數據類型,其中包括83列數值型數據和78列對象型數據。對象型數據列用于字符串或包含混合數據類型的列。
由此我們可以進一步了解我們應該如何減少內存占用,下面我們來看一看pandas如何在內存中存儲數據。
Dataframe對象的內部表示
在底層,pandas會按照數據類型將列分組形成數據塊(blocks)。下圖所示為pandas如何存儲我們數據表的前十二列:
可以注意到,這些數據塊沒有保持對列名的引用,這是由于為了存儲dataframe中的真實數據,這些數據塊都經過了優化。有個BlockManager類
會用于保持行列索引與真實數據塊的映射關系。他扮演一個API,提供對底層數據的訪問。每當我們查詢、編輯或刪除數據時,dataframe類會利用BlockManager類接口將我們的請求轉換為函數和方法的調用。
每種數據類型在pandas.core.internals模塊中都有一個特定的類。pandas使用ObjectBlock類來表示包含字符串列的數據塊,用FloatBlock類來表示包含浮點型列的數據塊。對于包含數值型數據(比如整型和浮點型)的數據塊,pandas會合并這些列,并把它們存儲為一個Numpy數組(ndarray)。Numpy數組是在C數組的基礎上創建的,其值在內存中是連續存儲的。基于這種存儲機制,對其切片的訪問是相當快的。
由于不同類型的數據是分開存放的,我們將檢查不同數據類型的內存使用情況,我們先看看各數據類型的平均內存使用量:
由于不同類型的數據是分開存放的,我們將檢查不同數據類型的內存使用情況,我們先看看各數據類型的平均內存使用量:
我們可以看到內存使用最多的是78個object列,我們待會再來看它們,我們先來看看我們能否提高數值型列的內存使用效率。
選理解子類(Subtypes)
剛才我們提到,pandas在底層將數值型數據表示成Numpy數組,并在內存中連續存儲。這種存儲方式消耗較少的空間,并允許我們較快速地訪問數據。由于pandas使用相同數量的字節來表示同一類型的每一個值,并且numpy數組存儲了這些值的數量,所以pandas能夠快速準確地返回數值型列所消耗的字節量。
pandas中的許多數據類型具有多個子類型,它們可以使用較少的字節去表示不同數據,比如,float型就有float16、float32和float64這些子類型。這些類型名稱的數字部分表明了這種類型使用了多少比特來表示數據,比如剛才列出的子類型分別使用了2、4、8個字節。下面這張表列出了pandas中常用類型的子類型:
一個int8類型的數據使用1個字節(8位比特)存儲一個值,可以表示256(2^8)個二進制數值。這意味著我們可以用這種子類型去表示從-128到127(包括0)的數值。
我們可以用numpy.iinfo類來確認每一個整型子類型的最小和最大值,如下:
這里我們還可以看到uint(無符號整型)和int(有符號整型)的區別。兩者都占用相同的內存存儲量,但無符號整型由于只存正數,所以可以更高效的存儲只含正數的列。
用子類型優化數值型列
我們可以用函數pd.to_numeric()來對數值型進行向下類型轉換。我們用DataFrame.select_dtypes來只選擇整型列,然后我們優化這種類型,并比較內存使用量。
我們看到內存用量從7.9兆下降到1.5兆,降幅達80%。這對我們原始dataframe的影響有限,這是由于它只包含很少的整型列。
同理,我們再對浮點型列進行相應處理:
我們可以看到所有的浮點型列都從float64轉換為float32,內存用量減少50%。
我們再創建一個原始dataframe的副本,將其數值列賦值為優化后的類型,再看看內存用量的整體優化效果。
可以看到通過我們顯著縮減數值型列的內存用量,我們的dataframe的整體內存用量減少了7%。余下的大部分優化將針對object類型進行。
在這之前,我們先來研究下與數值型相比,pandas如何存儲字符串。
選對比數值與字符的儲存
object類型用來表示用到了Python字符串對象的值,有一部分原因是Numpy缺少對缺失字符串值的支持。因為Python是一種高層、解析型語言,它沒有提供很好的對內存中數據如何存儲的細粒度控制。
這一限制導致了字符串以一種碎片化方式進行存儲,消耗更多的內存,并且訪問速度低下。在object列中的每一個元素實際上都是存放內存中真實數據位置的指針。
下圖對比展示了數值型數據怎樣以Numpy數據類型存儲,和字符串怎樣以Python內置類型進行存儲的。
圖示來源并改編自Why Python Is Slow
你可能注意到上文表中提到object類型數據使用可變(variable)大小的內存。由于一個指針占用1字節,因此每一個字符串占用的內存量與它在Python中單獨存儲所占用的內存量相等。我們用sys.getsizeof()來證明這一點,先來看看在Python單獨存儲字符串,再來看看使用pandas的series的情況。
你可以看到這些字符串的大小在pandas的series中與在Python的單獨字符串中是一樣的。
選用類別(categoricalas)類型優化object類型
Pandas在0.15版本中引入類別類型。category類型在底層使用整型數值來表示該列的值,而不是用原值。Pandas用一個字典來構建這些整型數據到原數據的映射關系。當一列只包含有限種值時,這種設計是很不錯的。當我們把一列轉換成category類型時,pandas會用一種最省空間的int子類型去表示這一列中所有的唯一值。
為了介紹我們何處會用到這種類型去減少內存消耗,讓我們來看看我們數據中每一個object類型列中的唯一值個數。
可以看到在我們包含了近172000場比賽的數據集中,很多列只包含了少數幾個唯一值。
我們先選擇其中一個object列,開看看將其轉換成類別類型會發生什么。這里我們選用第二列:day_of_week。
我們從上表中可以看到,它只包含了7個唯一值。我們用.astype()方法將其轉換為類別類型。
可以看到,雖然列的類型改變了,但數據看上去好像沒什么變化。我們來看看底層發生了什么。
下面的代碼中,我們用Series.cat.codes屬性來返回category類型用以表示每個值的整型數字。
可以看到,每一個值都被賦值為一個整數,而且這一列在底層是int8類型。這一列沒有任何缺失數據,但是如果有,category子類型會將缺失數據設為-1。
最后,我們來看看這一列在轉換為category類型前后的內存使用量。
存用量從9.8兆降到0.16兆,近乎98%的降幅!注意這一特殊列可能代表了我們一個極好的例子——一個包含近172000個數據的列只有7個唯一值。
這樣的話,我們把所有這種類型的列都轉換成類別類型應該會很不錯,但這里面也要權衡利弊。首要問題是轉變為類別類型會喪失數值計算能力,在將類別類型轉換成真實的數值類型前,我們不能對category列做算術運算,也不能使用諸如Series.min()和Series.max()等方法。
對于唯一值數量少于50%的object列,我們應該堅持首先使用category類型。如果某一列全都是唯一值,category類型將會占用更多內存。這是因為這樣做不僅要存儲全部的原始字符串數據,還要存儲整型類別標識。有關category類型的更多限制,參看pandas文檔。
下面我們寫一個循環,對每一個object列進行迭代,檢查其唯一值是否少于50%,如果是,則轉換成類別類型。
更之前一樣進行比較:
這本例中,所有的object列都被轉換成了category類型,但其他數據集就不一定了,所以你最好還是得使用剛才的檢查過程。
本例的亮點是內存用量從752.72兆降為51.667兆,降幅達93%。我們將其與我們dataframe的剩下部分合并,看看初始的861兆數據降到了多少。
耶,看來我們的進展還不錯!我們還有一招可以做優化,如果你記得我們剛才那張類型表,會發現我們數據集第一列還可以用datetime類型來表示。
你可能還記得這一列之前是作為整型讀入的,并優化成了uint32。因此,將其轉換成datetime會占用原來兩倍的內存,因為datetime類型是64位比特的。將其轉換為datetime的意義在于它可以便于我們進行時間序列分析。
轉換使用pandas.to_datetime()函數,并使用format參數告之日期數據存儲為YYYY-MM-DD格式。
在數據讀入的時候設定數據類型
目前為止,我們探索了一些方法,用來減少現有dataframe的內存占用。通過首先讀入dataframe,再對其一步步進行內存優化,我們可以更好地了解這些優化方法能節省多少內存。然而,正如我們之前談到,我們通常沒有足夠的內存去表達數據集中的所有數據。如果不能在一開始就創建dataframe,我們怎樣才能應用內存節省技術呢?
幸運的是,我們可以在讀入數據集的時候指定列的最優數據類型。pandas.read_csv()函數有一些參數可以做到這一點。dtype參數接受一個以列名(string型)為鍵字典、以Numpy類型對象為值的字典。
首先,我們將每一列的目標類型存儲在以列名為鍵的字典中,開始前先刪除日期列,因為它需要分開單獨處理。
現在我們使用這個字典,同時傳入一些處理日期的參數,讓日期以正確的格式讀入。
通過對列的優化,我們是pandas的內存用量從861.6兆降到104.28兆,有效降低88%。
分析棒球比賽
現在我們有了優化后的數據,可以進行一些分析。我們先看看比賽日的分布情況。
我們可以看到,1920年代之前,周日棒球賽很少是在周日的,隨后半個世紀才逐漸增多。
我們也看到最后50年的比賽日分布變化相對比較平穩。
我們來看看比賽時長的逐年變化。
看來棒球比賽時長從1940年代之后逐漸變長。
以上是“python如何使用pandas處理大數據節省內存”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。