自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

用Pandas處理大數(shù)據(jù)——節(jié)省90%內(nèi)存消耗的小貼士

大數(shù)據(jù)
一般來說,用pandas處理小于100兆的數(shù)據(jù),性能不是問題。當用pandas來處理100兆至幾個G的數(shù)據(jù)時,將會比較耗時,同時會導致程序因內(nèi)存不足而運行失敗。本文我們討論pandas的內(nèi)存使用,展示怎樣簡單地為數(shù)據(jù)列選擇合適的數(shù)據(jù)類型,就能夠減少dataframe近90%的內(nèi)存占用。

一般來說,用pandas處理小于100兆的數(shù)據(jù),性能不是問題。當用pandas來處理100兆至幾個G的數(shù)據(jù)時,將會比較耗時,同時會導致程序因內(nèi)存不足而運行失敗。

當然,像Spark這類的工具能夠勝任處理100G至幾個T的大數(shù)據(jù)集,但要想充分發(fā)揮這些工具的優(yōu)勢,通常需要比較貴的硬件設備。而且,這些工具不像pandas那樣具有豐富的進行高質量數(shù)據(jù)清洗、探索和分析的特性。對于中等規(guī)模的數(shù)據(jù),我們的愿望是盡量讓pandas繼續(xù)發(fā)揮其優(yōu)勢,而不是換用其他工具。

本文我們討論pandas的內(nèi)存使用,展示怎樣簡單地為數(shù)據(jù)列選擇合適的數(shù)據(jù)類型,就能夠減少dataframe近90%的內(nèi)存占用。

處理棒球比賽記錄數(shù)據(jù)

我們將處理130年的棒球***聯(lián)賽的數(shù)據(jù),數(shù)據(jù)源于

 

Retrosheet(http://www.retrosheet.org/gamelogs/index.html)

原始數(shù)據(jù)放在127個csv文件中,我們已經(jīng)用csvkit

 

(https://csvkit.readthedocs.io/en/1.0.2/)

將其合并,并添加了表頭。如果你想下載我們版本的數(shù)據(jù)用來運行本文的程序,我們提供了下載地址。

 

(https://data.world/dataquest/mlb-game-logs)

我們從導入數(shù)據(jù),并輸出前5行開始:

 

 

我們將一些重要的字段列在下面:

  • date - 比賽日期
  • v_name - 客隊名
  • v_league - 客隊聯(lián)賽
  • h_name - 主隊名
  • h_league - 主隊聯(lián)賽
  • v_score - 客隊得分
  • h_score - 主隊得分
  • v_line_score - 客隊線得分, 如010000(10)00.
  • h_line_score- 主隊線得分, 如010000(10)0X.
  • park_id - 主辦場地的ID
  • attendance- 比賽出席人數(shù)

我們可以用Dataframe.info()方法來獲得我們dataframe的一些高level信息,譬如數(shù)據(jù)量、數(shù)據(jù)類型和內(nèi)存使用量。

這個方法默認情況下返回一個近似的內(nèi)存使用量,現(xiàn)在我們設置參數(shù)memory_usage為'deep'來獲得準確的內(nèi)存使用量:

 

我們可以看到它有171907行和161列。pandas已經(jīng)為我們自動檢測了數(shù)據(jù)類型,其中包括83列數(shù)值型數(shù)據(jù)和78列對象型數(shù)據(jù)。對象型數(shù)據(jù)列用于字符串或包含混合數(shù)據(jù)類型的列。

由此我們可以進一步了解我們應該如何減少內(nèi)存占用,下面我們來看一看pandas如何在內(nèi)存中存儲數(shù)據(jù)。

Dataframe對象的內(nèi)部表示

在底層,pandas會按照數(shù)據(jù)類型將列分組形成數(shù)據(jù)塊(blocks)。下圖所示為pandas如何存儲我們數(shù)據(jù)表的前十二列:

 

可以注意到,這些數(shù)據(jù)塊沒有保持對列名的引用,這是由于為了存儲dataframe中的真實數(shù)據(jù),這些數(shù)據(jù)塊都經(jīng)過了優(yōu)化。有個BlockManager類

 

會用于保持行列索引與真實數(shù)據(jù)塊的映射關系。他扮演一個API,提供對底層數(shù)據(jù)的訪問。每當我們查詢、編輯或刪除數(shù)據(jù)時,dataframe類會利用BlockManager類接口將我們的請求轉換為函數(shù)和方法的調(diào)用。

每種數(shù)據(jù)類型在pandas.core.internals模塊中都有一個特定的類。pandas使用ObjectBlock類來表示包含字符串列的數(shù)據(jù)塊,用FloatBlock類來表示包含浮點型列的數(shù)據(jù)塊。對于包含數(shù)值型數(shù)據(jù)(比如整型和浮點型)的數(shù)據(jù)塊,pandas會合并這些列,并把它們存儲為一個Numpy數(shù)組(ndarray)。Numpy數(shù)組是在C數(shù)組的基礎上創(chuàng)建的,其值在內(nèi)存中是連續(xù)存儲的?;谶@種存儲機制,對其切片的訪問是相當快的。

由于不同類型的數(shù)據(jù)是分開存放的,我們將檢查不同數(shù)據(jù)類型的內(nèi)存使用情況,我們先看看各數(shù)據(jù)類型的平均內(nèi)存使用量:

由于不同類型的數(shù)據(jù)是分開存放的,我們將檢查不同數(shù)據(jù)類型的內(nèi)存使用情況,我們先看看各數(shù)據(jù)類型的平均內(nèi)存使用量:

 

我們可以看到內(nèi)存使用最多的是78個object列,我們待會再來看它們,我們先來看看我們能否提高數(shù)值型列的內(nèi)存使用效率。

選理解子類(Subtypes)

剛才我們提到,pandas在底層將數(shù)值型數(shù)據(jù)表示成Numpy數(shù)組,并在內(nèi)存中連續(xù)存儲。這種存儲方式消耗較少的空間,并允許我們較快速地訪問數(shù)據(jù)。由于pandas使用相同數(shù)量的字節(jié)來表示同一類型的每一個值,并且numpy數(shù)組存儲了這些值的數(shù)量,所以pandas能夠快速準確地返回數(shù)值型列所消耗的字節(jié)量。

pandas中的許多數(shù)據(jù)類型具有多個子類型,它們可以使用較少的字節(jié)去表示不同數(shù)據(jù),比如,float型就有float16、float32和float64這些子類型。這些類型名稱的數(shù)字部分表明了這種類型使用了多少比特來表示數(shù)據(jù),比如剛才列出的子類型分別使用了2、4、8個字節(jié)。下面這張表列出了pandas中常用類型的子類型:

 

一個int8類型的數(shù)據(jù)使用1個字節(jié)(8位比特)存儲一個值,可以表示256(2^8)個二進制數(shù)值。這意味著我們可以用這種子類型去表示從-128到127(包括0)的數(shù)值。

我們可以用numpy.iinfo類來確認每一個整型子類型的最小和***值,如下:

 

 

這里我們還可以看到uint(無符號整型)和int(有符號整型)的區(qū)別。兩者都占用相同的內(nèi)存存儲量,但無符號整型由于只存正數(shù),所以可以更高效的存儲只含正數(shù)的列。

用子類型優(yōu)化數(shù)值型列

我們可以用函數(shù)pd.to_numeric()來對數(shù)值型進行向下類型轉換。我們用DataFrame.select_dtypes來只選擇整型列,然后我們優(yōu)化這種類型,并比較內(nèi)存使用量。

 

我們看到內(nèi)存用量從7.9兆下降到1.5兆,降幅達80%。這對我們原始dataframe的影響有限,這是由于它只包含很少的整型列。

同理,我們再對浮點型列進行相應處理:

 

我們可以看到所有的浮點型列都從float64轉換為float32,內(nèi)存用量減少50%。

我們再創(chuàng)建一個原始dataframe的副本,將其數(shù)值列賦值為優(yōu)化后的類型,再看看內(nèi)存用量的整體優(yōu)化效果。

 

可以看到通過我們顯著縮減數(shù)值型列的內(nèi)存用量,我們的dataframe的整體內(nèi)存用量減少了7%。余下的大部分優(yōu)化將針對object類型進行。

在這之前,我們先來研究下與數(shù)值型相比,pandas如何存儲字符串。

選對比數(shù)值與字符的儲存

object類型用來表示用到了Python字符串對象的值,有一部分原因是Numpy缺少對缺失字符串值的支持。因為Python是一種高層、解析型語言,它沒有提供很好的對內(nèi)存中數(shù)據(jù)如何存儲的細粒度控制。

這一限制導致了字符串以一種碎片化方式進行存儲,消耗更多的內(nèi)存,并且訪問速度低下。在object列中的每一個元素實際上都是存放內(nèi)存中真實數(shù)據(jù)位置的指針。

下圖對比展示了數(shù)值型數(shù)據(jù)怎樣以Numpy數(shù)據(jù)類型存儲,和字符串怎樣以Python內(nèi)置類型進行存儲的。

 


圖示來源并改編自Why Python Is Slow

你可能注意到上文表中提到object類型數(shù)據(jù)使用可變(variable)大小的內(nèi)存。由于一個指針占用1字節(jié),因此每一個字符串占用的內(nèi)存量與它在Python中單獨存儲所占用的內(nèi)存量相等。我們用sys.getsizeof()來證明這一點,先來看看在Python單獨存儲字符串,再來看看使用pandas的series的情況。

 

 

你可以看到這些字符串的大小在pandas的series中與在Python的單獨字符串中是一樣的。

選用類別(categoricalas)類型優(yōu)化object類型

Pandas在0.15版本中引入類別類型。category類型在底層使用整型數(shù)值來表示該列的值,而不是用原值。Pandas用一個字典來構建這些整型數(shù)據(jù)到原數(shù)據(jù)的映射關系。當一列只包含有限種值時,這種設計是很不錯的。當我們把一列轉換成category類型時,pandas會用一種最省空間的int子類型去表示這一列中所有的唯一值。

 

為了介紹我們何處會用到這種類型去減少內(nèi)存消耗,讓我們來看看我們數(shù)據(jù)中每一個object類型列中的唯一值個數(shù)。

 

 

可以看到在我們包含了近172000場比賽的數(shù)據(jù)集中,很多列只包含了少數(shù)幾個唯一值。

我們先選擇其中一個object列,開看看將其轉換成類別類型會發(fā)生什么。這里我們選用第二列:day_of_week。

我們從上表中可以看到,它只包含了7個唯一值。我們用.astype()方法將其轉換為類別類型。

 

可以看到,雖然列的類型改變了,但數(shù)據(jù)看上去好像沒什么變化。我們來看看底層發(fā)生了什么。

下面的代碼中,我們用Series.cat.codes屬性來返回category類型用以表示每個值的整型數(shù)字。

 

可以看到,每一個值都被賦值為一個整數(shù),而且這一列在底層是int8類型。這一列沒有任何缺失數(shù)據(jù),但是如果有,category子類型會將缺失數(shù)據(jù)設為-1。

***,我們來看看這一列在轉換為category類型前后的內(nèi)存使用量。

 

存用量從9.8兆降到0.16兆,近乎98%的降幅!注意這一特殊列可能代表了我們一個極好的例子——一個包含近172000個數(shù)據(jù)的列只有7個唯一值。

這樣的話,我們把所有這種類型的列都轉換成類別類型應該會很不錯,但這里面也要權衡利弊。首要問題是轉變?yōu)轭悇e類型會喪失數(shù)值計算能力,在將類別類型轉換成真實的數(shù)值類型前,我們不能對category列做算術運算,也不能使用諸如Series.min()和Series.max()等方法。

對于唯一值數(shù)量少于50%的object列,我們應該堅持首先使用category類型。如果某一列全都是唯一值,category類型將會占用更多內(nèi)存。這是因為這樣做不僅要存儲全部的原始字符串數(shù)據(jù),還要存儲整型類別標識。有關category類型的更多限制,參看pandas文檔。

下面我們寫一個循環(huán),對每一個object列進行迭代,檢查其唯一值是否少于50%,如果是,則轉換成類別類型。

 

更之前一樣進行比較:

 

這本例中,所有的object列都被轉換成了category類型,但其他數(shù)據(jù)集就不一定了,所以你***還是得使用剛才的檢查過程。

本例的亮點是內(nèi)存用量從752.72兆降為51.667兆,降幅達93%。我們將其與我們dataframe的剩下部分合并,看看初始的861兆數(shù)據(jù)降到了多少。

 

耶,看來我們的進展還不錯!我們還有一招可以做優(yōu)化,如果你記得我們剛才那張類型表,會發(fā)現(xiàn)我們數(shù)據(jù)集***列還可以用datetime類型來表示。

 

你可能還記得這一列之前是作為整型讀入的,并優(yōu)化成了uint32。因此,將其轉換成datetime會占用原來兩倍的內(nèi)存,因為datetime類型是64位比特的。將其轉換為datetime的意義在于它可以便于我們進行時間序列分析。

轉換使用pandas.to_datetime()函數(shù),并使用format參數(shù)告之日期數(shù)據(jù)存儲為YYYY-MM-DD格式。

 

在數(shù)據(jù)讀入的時候設定數(shù)據(jù)類型

目前為止,我們探索了一些方法,用來減少現(xiàn)有dataframe的內(nèi)存占用。通過首先讀入dataframe,再對其一步步進行內(nèi)存優(yōu)化,我們可以更好地了解這些優(yōu)化方法能節(jié)省多少內(nèi)存。然而,正如我們之前談到,我們通常沒有足夠的內(nèi)存去表達數(shù)據(jù)集中的所有數(shù)據(jù)。如果不能在一開始就創(chuàng)建dataframe,我們怎樣才能應用內(nèi)存節(jié)省技術呢?

幸運的是,我們可以在讀入數(shù)據(jù)集的時候指定列的***數(shù)據(jù)類型。pandas.read_csv()函數(shù)有一些參數(shù)可以做到這一點。dtype參數(shù)接受一個以列名(string型)為鍵字典、以Numpy類型對象為值的字典。

首先,我們將每一列的目標類型存儲在以列名為鍵的字典中,開始前先刪除日期列,因為它需要分開單獨處理。

 

現(xiàn)在我們使用這個字典,同時傳入一些處理日期的參數(shù),讓日期以正確的格式讀入。

 

 

通過對列的優(yōu)化,我們是pandas的內(nèi)存用量從861.6兆降到104.28兆,有效降低88%。

分析棒球比賽

現(xiàn)在我們有了優(yōu)化后的數(shù)據(jù),可以進行一些分析。我們先看看比賽日的分布情況。

 

 

我們可以看到,1920年代之前,周日棒球賽很少是在周日的,隨后半個世紀才逐漸增多。

我們也看到***50年的比賽日分布變化相對比較平穩(wěn)。

我們來看看比賽時長的逐年變化。

 

 

看來棒球比賽時長從1940年代之后逐漸變長。

總結

我們學習了pandas如何存儲不同的數(shù)據(jù)類型,并利用學到的知識將我們的pandas dataframe的內(nèi)存用量降低了近90%,僅僅只用了一點簡單的技巧:

  • 將數(shù)值型列降級到更高效的類型
  • 將字符串列轉換為類別類型

通過對列的優(yōu)化,我們是pandas的內(nèi)存用量從861.6兆降到104.28兆,有效降低88%。

責任編輯:未麗燕 來源: 網(wǎng)絡大數(shù)據(jù)
相關推薦

2022-12-30 15:29:35

數(shù)據(jù)分析工具Pandas

2017-02-28 10:54:40

Pandas

2022-11-17 11:52:35

pandasPySpark大數(shù)據(jù)

2021-08-10 09:04:43

內(nèi)存視圖 NumPy

2021-08-10 13:17:31

NumPy內(nèi)存Python

2024-05-20 09:31:53

PandasPython大數(shù)據(jù)集

2015-05-13 14:20:24

大數(shù)據(jù)大數(shù)據(jù)規(guī)則

2010-04-28 13:40:38

2022-09-26 00:00:02

PandasExcel文件

2020-05-19 17:09:33

Pandas大數(shù)據(jù)數(shù)據(jù)分析

2009-08-18 19:56:34

遠程數(shù)據(jù)備份ProxySG設備Blue Coat

2020-03-12 13:09:02

編程數(shù)據(jù)科學代碼

2017-07-19 09:54:31

數(shù)據(jù)CIO

2025-02-27 13:30:00

2013-04-11 09:40:05

大數(shù)據(jù)醫(yī)療開支

2019-06-18 10:05:55

大數(shù)據(jù)80后90后

2023-12-12 11:06:37

PythonPandas數(shù)據(jù)

2020-10-29 06:02:44

PythonPandasExcel

2019-02-21 15:34:08

數(shù)據(jù)分析師大數(shù)據(jù)可視化

2024-05-13 11:43:39

Python數(shù)據(jù)分析CSV
點贊
收藏

51CTO技術棧公眾號