
編譯 | AI科技大本營(rgznai100)數組
參與 | 周翔 數據結構
注:Pandas(Python Data Analysis Library) 是基於 NumPy 的一種工具,該工具是爲了解決數據分析任務而建立的。此外,Pandas 歸入了大量庫和一些標準的數據模型,提供了高效地操做大型數據集所需的工具。
相比較於 Numpy,Pandas 使用一個二維的數據結構 DataFrame 來表示表格式的數據, 能夠存儲混合的數據結構,同時使用 NaN 來表示缺失的數據,而不用像 Numpy 同樣要手工處理缺失的數據,而且 Pandas 使用軸標籤來表示行和列。
Pandas 一般用於處理小數據(小於 100Mb),並且對計算機的性能要求不高,可是當咱們須要處理更大的數據時(100Mb到幾千Gb),計算機性能就成了問題,若是配置太低就會致使更長的運行時間,甚至由於內存不足致使運行失敗。
在處理大型數據集時(100Gb到幾TB),咱們一般會使用像 Spark 這樣的工具,可是想要充分發揮 Spark 的功能,一般須要很高的硬件配置,致使成本太高。並且與 Pandas 不一樣,這些工具缺乏可用於高質量數據清洗、勘測和分析的特徵集。
所以對於中等規模的數據,咱們最好挖掘 Pandas 的潛能,而不是轉而使用其餘工具。那麼在不升級計算機配置的前提下,咱們要怎麼解決內存不足的問題呢?
在這篇文章中,咱們將介紹 Pandas 的內存使用狀況,以及如何經過爲數據框(dataframe)中的列(column)選擇適當的數據類型,將數據框的內存佔用量減小近 90%。
咱們將要處理的是 130 年來的大型棒球聯盟比賽數據,原始數據來源於 retrosheet。
最原始的數據是 127 個獨立的 CSV 文件,不過咱們已經使用 csvkit 合併了這些文件,而且在第一行中爲每一列添加了名字。
若是讀者想親自動手操做,可下載網站上的數據實踐下:https://data.world/dataquest/mlb-game-logs
首先讓咱們導入數據,看看前五行:
import pandas as pdgl = pd.read_csv('game_logs.csv')gl.head()
框架

咱們總結了一些重要的列,可是若是你想查看全部的列的指南,咱們也爲整個數據集建立了一個數據字典:
函數
咱們可使用 DataFrame.info() 的方法爲咱們提供數據框架的更多高層次的信息,包括數據大小、類型、內存使用狀況的信息。默認狀況下,Pandas 會佔用和數據框大小差很少的內存來節省時間。由於咱們對準確度感興趣,因此咱們將 memory_usage 的參數設置爲 ‘deep’,以此來獲取更準確的數字。
咱們能夠看到,這個數據集共有 171,907 行、161 列。Pandas 已經自動檢測了數據的類型:83 列數字(numeric),78 列對象(object)。對象列(object columns)主要用於存儲字符串,包含混合數據類型。爲了更好地瞭解怎樣減小內存的使用量,讓咱們看看 Pandas 是如何將數據存儲在內存中的。
在底層,Pandas 按照數據類型將列分紅不一樣的塊(blocks)。這是 Pandas 如何存儲數據框前十二列的預覽。工具
你會注意到這些數據塊不會保留對列名的引用。這是由於數據塊對存儲數據框中的實際值進行了優化,BlockManager class 負責維護行、列索引與實際數據塊之間的映射。它像一個 API 來提供訪問底層數據的接口。每當咱們選擇、編輯、或刪除某個值時,dataframe class 會和 BlockManager class 進行交互,將咱們的請求轉換爲函數和方法調用。
每一個類型在 pandas.core.internals 模塊中都有一個專門的類, Pandas 使用 ObjectBlock class 來表明包含字符串列的塊,FloatBlock class 表示包含浮點型數據(float)列的塊。對於表示數值(如整數和浮點數)的塊,Pandas 將這些列組合在一塊兒,並存儲爲 NumPy ndarry 數組。NumPy ndarry 是圍繞 C array 構建的,並且它們的值被存儲在連續的內存塊中。因爲採用這種存儲方案,訪問這些值的地址片斷(slice)是很是快的。
由於不一樣的數據都是單獨存儲的,因此咱們將檢查不一樣類型的數據的內存使用狀況。咱們先來看看全部數據類型的平均內存使用狀況。

能夠看到,大部分的內存都被 78 個對象列佔用了。咱們稍後再來分析,首先看看咱們是否能夠提升數字列(numeric columns)的內存使用率。
正如前面介紹的那樣,在底層,Pandas 將數值表示爲 NumPy ndarrays,並將它存儲在連續的內存塊中。該存儲模型消耗的空間較小,並容許咱們快速訪問這些值。由於 Pandas 中,相同類型的值會分配到相同的字節數,而 NumPy ndarray 裏存儲了值的數量,因此 Pandas 能夠快速並準確地返回一個數值列佔用的字節數。
Pandas 中的許多類型包含了多個子類型,所以可使用較少的字節數來表示每一個值。例如,float 類型就包含 float1六、float3二、float64 等子類型。類型名稱的數字部分表明
了用於表示值類型的位數。例如,咱們剛剛列出的子類型就分別使用了 二、四、八、16 個字節。下表顯示了最多見的 Pandas 的子類型:
int8 使用 1 個字節(或者 8 位)來存儲一個值,而且能夠以二進制表示 256 個值。這意味着,咱們可使用這種子類型來表示從 -128 到 127 (包括0)的值。咱們可使用 numpy.iinfo class 來驗證每一個整數子類型的最小值和最大值,咱們來看一個例子:

咱們能夠在這裏看到 uint(無符號整數)和 int(有符號整數)之間的區別。這兩種類型具備相同的存儲容量,但若是隻存儲正數,無符號整數顯然可以讓咱們更高效地存儲只包含正值的列。
咱們可使用函數 pd.to_numeric() 來 downcast(向下轉型)咱們的數值類型。咱們將使用 DataFrame.select_dtypes 來選擇整數列,而後優化這些列包含的類型,並比較優化先後內存的使用狀況。

咱們能夠看到,內存的使用量從 7.9Mb 降到了 1.5 Mb,減小了 80% 以上。但這對原始數據框的影響並不大,由於自己整數列就很是少。
優化
如今,讓咱們來對浮點型數列作一樣的事情。
能夠看到,咱們全部的浮點型數列都從 float64 轉換成 float32,使得內存的使用量減小了 50%。
網站
讓咱們建立一個原始數據框的副本,而後分配這些優化後的數字列代替原始數據,並查看如今的內存使用狀況。
雖然咱們大大減小了數字列的內存使用量,可是從總體來看,咱們只是將數據框的內存使用量下降了 7%。內存使用量下降的主要緣由是咱們對對象類型(object types)進行了優化。
在動手以前,讓咱們仔細看一下,與數字類型相比,字符串是怎樣存在 Pandas 中的。
對象類型表明了 Python 字符串對象的值,部分緣由是 NumPy 缺乏對字符串值的支持。由於 Python 是一種高級的解釋語言,它不能對數值的存儲方式進行細粒度控制。
這種限制使得字符串以分散的方式存儲在內存裏,不只佔用了更多的內存,並且訪問速度較慢。對象列表中的每個元素都是一個指針(pointer),它包含了實際值在內存中位置的「地址」。
下面的圖標展現了數字值是如何存儲在 NumPy 數據類型中,以及字符串如何使用 Python 內置的類型存儲。
你可能已經注意到,咱們的圖表以前將對象類型描述成使用可變內存量。當每一個指針佔用一字節的內存時,每一個字符的字符串值佔用的內存量與 Python 中單獨存儲時相同。讓咱們使用 sys.getsizeof() 來自證實這一點:先查看單個字符串,而後查看 Pandas 系列中的項目(items)。


你能夠看到,存儲在 Pandas 中的字符串的大小與做爲 Python 中單獨字符串的大小相同。
ui
Pandas 在 0.15版引入了 Categoricals (分類)。category 類型在底層使用整數類型來表示該列的值,而不是原始值。Pandas 用一個單獨的字典來映射整數值和相應的原始值之間的關係。當某一列包含的數值集有限時,這種設計是頗有用的。當咱們將列轉換爲 category dtype 時,Pandas 使用了最省空間的 int 子類型,來表示一列中全部的惟一值。
想要知道咱們能夠怎樣使用這種類型來減小內存使用量。首先 ,讓咱們看看每一種對象類型的惟一值的數量。
能夠看到,咱們的數據集中一共有 17.2 萬場比賽, 而惟一值的數量是很是少的。
spa
在咱們深刻分析以前,咱們首先選擇一個對象列,當咱們將其轉換爲 categorical type時,觀察下會發生什麼。咱們選擇了數據集中的第二列 day_of_week 來進行試驗。
在上面的表格中,咱們能夠看到它只包含了七個惟一的值。咱們將使用 .astype() 的方法將其轉換爲 categorical。
如你所見,除了列的類型已經改變,這些數據看起來徹底同樣。咱們來看看發生了什麼。
在下面的代碼中,咱們使用 Series.cat.codes 屬性來返回 category 類型用來表示每一個值的整數值。
你能夠看到,每一個惟一值都被分配了一個整數,而且該列的底層數據類型如今是 int8。該列沒有任何缺失值,若是有的話,這個 category 子類型會將缺省值設置爲 -1。最後,咱們來看看這個列在轉換到 category 類型以前和以後的內存使用狀況。
能夠看到,內存使用量從原來的 9.8MB 降到了 0.16MB,至關於減小了 98%!請注意,這一列可能表明咱們最好的狀況之一:一個具備 172,000 個項目的列,只有 7 個惟一的值。
將全部的列都進行一樣的操做,這聽起來很吸引人,但使咱們要注意權衡。可能出現的最大問題是沒法進行數值計算。咱們不能在將其轉換成真正的數字類型的前提下,對這些 category 列進行計算,或者使用相似 Series.min() 和 Series.max() 的方法。
當對象列中少於 50% 的值時惟一對象時,咱們應該堅持使用 category 類型。可是若是這一列中全部的值都是惟一的,那麼 category 類型最終將佔用更多的內存。這是由於列不只要存儲整數 category 代碼,還要存儲全部的原始字符串的值。你能夠閱讀 Pandas 文檔,瞭解 category 類型的更多限制。
咱們將編寫一個循環程序,遍歷每一個對象列,檢查其惟一值的數量是否小於 50%。若是是,那麼咱們就將這一列轉換爲 category 類型。

和以前的相比

在這種狀況下,咱們將全部對象列都轉換爲 category 類型,可是這種狀況並不符合全部的數據集,所以務必確保事先進行過檢查。
此外,對象列的內存使用量已經從 752MB 將至 52MB,減小了 93%。如今,咱們將其與數據框的其他部分結合起來,再與咱們最開始的 861MB 的內存使用量進行對比。
能夠看到,咱們已經取得了一些進展,可是咱們還有一個地方能夠優化。回到咱們的類型表,裏面有一個日期(datetime)類型能夠用來表示數據集的第一列。

你可能記得這一列以前是做爲整數型讀取的,並且已經被優化爲 uint32。所以,將其轉換爲 datetime 時,內存的佔用量會增長一倍,由於 datetime 的類型是 64 位。不管如何,將其轉換成 datetime 是有價值的,由於它將讓時間序列分析更加容易。
咱們將使用 pandas.to_datetime() 函數進行轉換,並使用 format 參數讓日期數據按照 YYYY-MM-DD 的格式存儲。

到目前爲止,咱們已
經
探索了減小現有數
據框內存佔用的方法。首先,讀入閱讀數據框,而後再反覆迭代節省內存的方法,這讓咱們能夠更好地瞭解每次優化能夠節省的內存空間。然而,正如咱們前面提到那樣,咱們常常沒有足夠的內存來表示數據集中全部的值。若是一開始就不能建立數據框,那麼咱們該怎樣使用內存節省技術呢?
幸運的是,當咱們讀取數據集時,咱們能夠制定列的最優類型。pandas.read_csv() 函數有幾個不一樣的參數可讓咱們作到這一點。dtype 參數能夠是一個以(字符串)列名稱做爲 keys、以 NumPy 類型對象做爲值的字典。
首先,咱們將每列的最終類型、以及列的名字的 keys 存在一個字典中。由於日期列須要單獨對待,所以咱們先要刪除這一列。

如今,咱們可使用字典、以及幾個日期的參數,經過幾行代碼,以正確的類型讀取日期數據。

經過優化這些列,咱們設法將 pandas 中的內存使用量,從 861.6MB 降到了 104.28MB,減小了 88%。
咱們已經優化了數據,如今咱們能夠開始對數據進行分析了。咱們來看看比賽的時間分佈。


能夠看到,在二十世紀二十年代以前,棒球比賽不多在週日舉行,一直到下半世紀才逐漸流行起來。此外,咱們也能夠清楚地看到,在過去的五十年裏,比賽時間的分是相對靜態的。咱們來看看比賽時長多年來的變化。


看起來,棒球比賽的時長自 1940 年以來就一直處於增加狀態。
總結和後續步驟
咱們已經瞭解到 Pandas 是如何存儲不一樣類型的數據的,而後咱們使用這些知識將 Pandas 裏的數據框的內存使用量下降了近 90%,而這一切只須要幾個簡單的技巧:
你,學會了嗎?
原文地址
https://www.dataquest.io/blog/pandas-big-data/