我教你如何將記憶體佔用降低90%!
用Pandas!
當使用 pandas 操作小規模資料(低於 100 MB)時,效能一般不是問題。而當面對更大規模的資料(100 MB 到數 GB)時,效能問題會讓執行時間變得更漫長,而且會因為記憶體不足導致執行完全失敗。 儘管 Spark 這樣的工具可以處理大型資料集(100 GB 到數 TB),但要完全利用它們的能力,往往需要更加昂貴的硬體。而且和 pandas 不同,它們缺少豐富的用於高質量資料清理、探索和分析的功能集。對於中等規模的資料,我們最好能更充分地利用 pandas,而不是換成另一種工具。 在這篇文章中,我們將瞭解 pandas 的記憶體使用,以及如何只需透過為列選擇合適的資料型別就能將 dataframe 的記憶體佔用減少近 90%。
我們將處理 130 年之久的美國職業棒球大聯盟(MLB)比賽資料,這些資料來自 Retrosheet:http://www.retrosheet.org/gamelogs/index.html 這些資料原來分成了 127 個不同的 CSV 檔案,但我們已經使用 csvkit 合併了這些資料,並在第一行增加了列名稱。如果你想下載本文所用的這個資料版本,請訪問:https://data.world/dataquest/mlb-game-logs 讓我們首先匯入資料,並看看其中的前五行:
下面我們總結了一些重要的列,但如果你想了解所有的列,我們也為整個資料集建立了一個數據詞典:https://data.world/dataquest/mlb-game-logs/workspace/data-dictionary
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 - 比賽舉辦的球場名
attendance- 比賽觀眾
我們可以使用 DataFrame.info() 方法為我們提供關於 dataframe 的高層面資訊,包括它的大小、資料型別的資訊和記憶體使用情況。預設情況下,pandas 會近似 dataframe 的記憶體用量以節省時間。因為我們也關心準確度,所以我們將 memory_usage 引數設定為 "deep",以便得到準確的數字。
我們可以看到,我們有 171,907 行和 161 列。pandas 會自動為我們檢測資料型別,發現其中有 83 列資料是數值,78 列是 object。object 是指有字串或包含混合資料型別的情況。 為了更好地理解如何減少記憶體用量,讓我們看看 pandas 是如何將資料儲存在記憶體中的。
在 pandas 內部,同樣資料型別的列會組織成同一個值塊(blocks of values)。這裡給出了一個示例,說明了 pandas 對我們的 dataframe 的前 12 列的儲存方式。
你可以看到這些塊並沒有保留原有的列名稱。這是因為這些塊為儲存 dataframe 中的實際值進行了最佳化。pandas 的 BlockManager 類則負責保留行列索引與實際塊之間的對映關係。它可以作為一個 API 使用,提供了對底層資料的訪問。不管我們何時選擇、編輯或刪除這些值,dataframe 類和 BlockManager 類的介面都會將我們的請求翻譯成函式和方法的呼叫。
在pandas.core.internals 模組中,每一種型別都有一個專門的類。pandas 使用 ObjectBlock 類來表示包含字串列的塊,用 FloatBlock 類表示包含浮點數列的塊。對於表示整型數和浮點數這些數值的塊,pandas 會將這些列組合起來,儲存成 NumPy ndarray。NumPy ndarray 是圍繞 C 語言的陣列構建的,其中的值儲存在記憶體的連續塊中。這種儲存方案使得對值的訪問速度非常快。 因為每種資料型別都是分開儲存的,所以我們將檢查不同資料型別的記憶體使用情況。首先,我們先來看看各個資料型別的平均記憶體用量。
可以看出,78 個 object 列所使用的記憶體量最大。我們後面再具體談這個問題。首先我們看看能否改進數值列的記憶體用量。
正如我們前面簡單提到的那樣,pandas 內部將數值表示為 NumPy ndarrays,並將它們儲存在記憶體的連續塊中。這種儲存模式佔用的空間更少,而且也讓我們可以快速訪問這些值。因為 pandas 表示同一型別的每個值時都使用同樣的位元組數,而NumPy ndarray 可以儲存值的數量,所以 pandas 可以快速準確地返回一個數值列所消耗的位元組數。 pandas 中的許多型別都有多個子型別,這些子型別可以使用更少的位元組來表示每個值。比如說 float 型別就包含 float16、float32 和 float64 子型別。型別名稱中的數字就代表該型別表示值的位(bit)數。比如說,我們剛剛列出的子型別就分別使用了 2、4、8、16 個位元組。下面的表格給出了 pandas 中最常用型別的子型別:
一個 int8 型別的值使用 1 個位元組的儲存空間,可以表示 256(2^8)個二進位制數。這意味著我們可以使用這個子型別來表示從 -128 到 127(包括 0)的所有整數值。 我們可以使用 numpy.iinfo 類來驗證每個整型數子型別的最大值和最小值。舉個例子:
這裡我們可以看到 uint(無符號整型)和 int(有符號整型)之間的差異。這兩種型別都有一樣的儲存能力,但其中一個只儲存 0 和正數。無符號整型讓我們可以更有效地處理只有正數值的列。
我們可以使用函式 pd.to_numeric() 來對我們的數值型別進行 downcast(向下轉型)操作。我們會使用DataFrame.select_dtypes來選擇整型列,然後我們會對其資料型別進行最佳化,並比較記憶體用量。
我們可以看到記憶體用量從 7.9 MB 下降到了 1.5 MB,降低了 80% 以上。但這對我們原有 dataframe 的影響並不大,因為其中的整型列非常少。 讓我們對其中的浮點型列進行一樣的操作。
我們可以看到浮點型列的資料型別從 float64 變成了 float32,讓記憶體用量降低了 50%。 讓我們為原始 dataframe 建立一個副本,並用這些最佳化後的列替換原來的列,然後看看我們現在的整體記憶體用量。
儘管我們極大地減少了數值列的記憶體用量,但整體的記憶體用量僅減少了 7%。我們的大部分收穫都將來自對 object 型別的最佳化。 在我們開始行動之前,先看看 pandas 中字串的儲存方式與數值型別的儲存方式的比較。
object 型別表示使用 Python 字串物件的值,部分原因是 NumPy 不支援缺失(missing)字串型別。因為 Python 是一種高階的解釋性語言,它對記憶體中儲存的值沒有細粒度的控制能力。 這一限制導致字串的儲存方式很碎片化,從而會消耗更多記憶體,而且訪問速度也更慢。object 列中的每個元素實際上都是一個指標,包含了實際值在記憶體中的位置的“地址”。 下面這幅圖給出了以 NumPy 資料型別儲存數值資料和使用 Python 內建型別儲存字串資料的方式。
本圖來自這篇出色的文章:https://jakevdp.github.io/blog/2014/05/09/why-python-is-slow/ 在前面的表格中,你可能已經注意到 object 型別的記憶體使用是可變的。儘管每個指標僅佔用 1 位元組的記憶體,但如果每個字串在 Python 中都是單獨儲存的,那就會佔用實際字串那麼大的空間。我們可以使用 sys.getsizeof() 函式來證明這一點,首先檢視單個的字串,然後檢視 pandas series 中的項。
你可以看到,當儲存在 pandas series 時,字串的大小與用 Python 單獨儲存的字串的大小是一樣的。
pandas 在 0.15 版引入了 Categorials。category 型別在底層使用了整型值來表示一個列中的值,而不是使用原始值。pandas 使用一個單獨的對映詞典將這些整型值對映到原始值。只要當一個列包含有限的值的集合時,這種方法就很有用。當我們將一列轉換成 category dtype 時,pandas 就使用最節省空間的 int 子型別來表示該列中的所有不同值。
為了瞭解為什麼我們可以使用這種型別來減少記憶體用量,讓我們看看我們的 object 型別中每種型別的不同值的數量。
大概看看就能發現,對於我們整個資料集的 172,000 場比賽,其中不同(unique)值的數量可以說非常少。 為了瞭解當我們將其轉換成 categorical 型別時究竟發生了什麼,我們拿出一個 object 列來看看。我們將使用資料集的第二列 day_of_week. 看看上表,可以看到其僅包含 7 個不同的值。我們將使用 .astype() 方法將其轉換成 categorical 型別。
如你所見,除了這一列的型別發生了改變之外,資料看起來還是完全一樣。讓我們看看這背後發生了什麼。 在下面的程式碼中,我們使用了 Series.cat.codes 屬性來返回 category 型別用來表示每個值的整型值。
你可以看到每個不同值都被分配了一個整型值,而該列現在的基本資料型別是 int8。這一列沒有任何缺失值,但就算有,category 子型別也能處理,只需將其設定為 -1 即可。 最後,讓我們看看在將這一列轉換為 category 型別前後的記憶體用量對比。
9.8 MB 的記憶體用量減少到了 0.16 MB,減少了 98%!注意,這個特定列可能代表了我們最好的情況之一——即大約 172,000 項卻只有 7 個不同的值。 儘管將所有列都轉換成這種型別聽起來很吸引人,但瞭解其中的取捨也很重要。最大的壞處是無法執行數值計算。如果沒有首先將其轉換成數值 dtype,那麼我們就無法對 category 列進行算術運算,也就是說無法使用 Series.min() 和 Series.max() 等方法。 我們應該堅持主要將 category 型別用於不同值的數量少於值的總數量的 50% 的 object 列。如果一列中的所有值都是不同的,那麼 category 型別所使用的記憶體將會更多。因為這一列不僅要儲存所有的原始字串值,還要額外儲存它們的整型值程式碼。你可以在 pandas 文件中瞭解 category 型別的侷限性:http://pandas.pydata.org/pandas-docs/stable/categorical.html 我們將編寫一個迴圈函式來迭代式地檢查每一 object 列中不同值的數量是否少於 50%;如果是,就將其轉換成 category 型別。
和之前一樣進行比較:
在這個案例中,所有的 object 列都被轉換成了 category 型別,但並非所有資料集都是如此,所以你應該使用上面的流程進行檢查。 object 列的記憶體用量從 752MB減少到了 52MB,減少了 93%。讓我們將其與我們 dataframe 的其它部分結合起來,看看從最初 861MB 的基礎上實現了多少進步。
你可能記得這一列開始是一個整型,現在已經最佳化成了 unint32 型別。因此,將其轉換成 datetime 型別實際上會讓記憶體用量翻倍,因為 datetime 型別是 64 位的。將其轉換成 datetime 型別是有價值的,因為這讓我們可以更好地進行時間序列分析。
pandas.to_datetime() 函式可以幫我們完成這種轉換,使用其 format 引數將我們的日期資料儲存成 YYYY-MM-DD 形式。
在讀入資料的同時選擇型別 現在,我們已經探索了減少現有 dataframe 的記憶體佔用的方法。透過首先讀入 dataframe ,然後在這個過程中迭代以減少記憶體佔用,我們瞭解了每種最佳化方法可以帶來的記憶體減省量。但是正如我們前面提到的一樣,我們往往沒有足夠的記憶體來表示資料集中的所有值。如果我們一開始甚至無法建立 dataframe ,我們又可以怎樣應用節省記憶體的技術呢? 幸運的是,我們可以在讀入資料的同時指定最優的列型別。pandas.read_csv() 函式有幾個不同的引數讓我們可以做到這一點。dtype 引數接受具有(字串)列名稱作為鍵值(key)以及 NumPy 型別 object 作為值的詞典。 首先,我們可將每一列的最終型別儲存在一個詞典中,其中鍵值表示列名稱,首先移除日期列,因為日期列需要不同的處理方式。
現在我們可以使用這個詞典了,另外還有幾個引數可用於按正確的型別讀入日期,而且僅需幾行程式碼:
透過最佳化這些列,我們成功將 pandas 的記憶體佔用從 861.6MB 減少到了 104.28MB——減少了驚人的 88%!
現在我們已經最佳化好了我們的資料,我們可以執行一些分析了。讓我們先從瞭解這些比賽的日期分佈開始。
我們可以看到在 1920 年代以前,星期日的棒球比賽很少,但在上個世紀後半葉就變得越來越多了。 我們也可以清楚地看到過去 50 年來,比賽的日期分佈基本上沒什麼大變化了。讓我們再看看比賽時長的變化情況:
從 1940 年代以來,棒球比賽的持續時間越來越長。
總結和下一步 我們已經瞭解了 pandas 使用不同資料型別的方法,然後我們使用這種知識將一個 pandas dataframe 的記憶體用量減少了近 90%,而且也僅使用了一些簡單的技術: 將數值列向下轉換成更高效的型別將字串列轉換成 categorical 型別 如果你還想使用 pandas 處理更大規模的資料,可以參與這個互動式課程:https://www.dataquest.io/m/163/optimizing-dataframe-memory-footprint/16/next-steps
我教你如何將記憶體佔用降低90%!
用Pandas!
當使用 pandas 操作小規模資料(低於 100 MB)時,效能一般不是問題。而當面對更大規模的資料(100 MB 到數 GB)時,效能問題會讓執行時間變得更漫長,而且會因為記憶體不足導致執行完全失敗。 儘管 Spark 這樣的工具可以處理大型資料集(100 GB 到數 TB),但要完全利用它們的能力,往往需要更加昂貴的硬體。而且和 pandas 不同,它們缺少豐富的用於高質量資料清理、探索和分析的功能集。對於中等規模的資料,我們最好能更充分地利用 pandas,而不是換成另一種工具。 在這篇文章中,我們將瞭解 pandas 的記憶體使用,以及如何只需透過為列選擇合適的資料型別就能將 dataframe 的記憶體佔用減少近 90%。
處理棒球比賽日誌我們將處理 130 年之久的美國職業棒球大聯盟(MLB)比賽資料,這些資料來自 Retrosheet:http://www.retrosheet.org/gamelogs/index.html 這些資料原來分成了 127 個不同的 CSV 檔案,但我們已經使用 csvkit 合併了這些資料,並在第一行增加了列名稱。如果你想下載本文所用的這個資料版本,請訪問:https://data.world/dataquest/mlb-game-logs 讓我們首先匯入資料,並看看其中的前五行:
下面我們總結了一些重要的列,但如果你想了解所有的列,我們也為整個資料集建立了一個數據詞典:https://data.world/dataquest/mlb-game-logs/workspace/data-dictionary
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 - 比賽舉辦的球場名
attendance- 比賽觀眾
我們可以使用 DataFrame.info() 方法為我們提供關於 dataframe 的高層面資訊,包括它的大小、資料型別的資訊和記憶體使用情況。預設情況下,pandas 會近似 dataframe 的記憶體用量以節省時間。因為我們也關心準確度,所以我們將 memory_usage 引數設定為 "deep",以便得到準確的數字。
我們可以看到,我們有 171,907 行和 161 列。pandas 會自動為我們檢測資料型別,發現其中有 83 列資料是數值,78 列是 object。object 是指有字串或包含混合資料型別的情況。 為了更好地理解如何減少記憶體用量,讓我們看看 pandas 是如何將資料儲存在記憶體中的。
dataframe 的內部表示在 pandas 內部,同樣資料型別的列會組織成同一個值塊(blocks of values)。這裡給出了一個示例,說明了 pandas 對我們的 dataframe 的前 12 列的儲存方式。
你可以看到這些塊並沒有保留原有的列名稱。這是因為這些塊為儲存 dataframe 中的實際值進行了最佳化。pandas 的 BlockManager 類則負責保留行列索引與實際塊之間的對映關係。它可以作為一個 API 使用,提供了對底層資料的訪問。不管我們何時選擇、編輯或刪除這些值,dataframe 類和 BlockManager 類的介面都會將我們的請求翻譯成函式和方法的呼叫。
在pandas.core.internals 模組中,每一種型別都有一個專門的類。pandas 使用 ObjectBlock 類來表示包含字串列的塊,用 FloatBlock 類表示包含浮點數列的塊。對於表示整型數和浮點數這些數值的塊,pandas 會將這些列組合起來,儲存成 NumPy ndarray。NumPy ndarray 是圍繞 C 語言的陣列構建的,其中的值儲存在記憶體的連續塊中。這種儲存方案使得對值的訪問速度非常快。 因為每種資料型別都是分開儲存的,所以我們將檢查不同資料型別的記憶體使用情況。首先,我們先來看看各個資料型別的平均記憶體用量。
可以看出,78 個 object 列所使用的記憶體量最大。我們後面再具體談這個問題。首先我們看看能否改進數值列的記憶體用量。
理解子型別(subtype)正如我們前面簡單提到的那樣,pandas 內部將數值表示為 NumPy ndarrays,並將它們儲存在記憶體的連續塊中。這種儲存模式佔用的空間更少,而且也讓我們可以快速訪問這些值。因為 pandas 表示同一型別的每個值時都使用同樣的位元組數,而NumPy ndarray 可以儲存值的數量,所以 pandas 可以快速準確地返回一個數值列所消耗的位元組數。 pandas 中的許多型別都有多個子型別,這些子型別可以使用更少的位元組來表示每個值。比如說 float 型別就包含 float16、float32 和 float64 子型別。型別名稱中的數字就代表該型別表示值的位(bit)數。比如說,我們剛剛列出的子型別就分別使用了 2、4、8、16 個位元組。下面的表格給出了 pandas 中最常用型別的子型別:
一個 int8 型別的值使用 1 個位元組的儲存空間,可以表示 256(2^8)個二進位制數。這意味著我們可以使用這個子型別來表示從 -128 到 127(包括 0)的所有整數值。 我們可以使用 numpy.iinfo 類來驗證每個整型數子型別的最大值和最小值。舉個例子:
這裡我們可以看到 uint(無符號整型)和 int(有符號整型)之間的差異。這兩種型別都有一樣的儲存能力,但其中一個只儲存 0 和正數。無符號整型讓我們可以更有效地處理只有正數值的列。
使用子型別最佳化數值列我們可以使用函式 pd.to_numeric() 來對我們的數值型別進行 downcast(向下轉型)操作。我們會使用DataFrame.select_dtypes來選擇整型列,然後我們會對其資料型別進行最佳化,並比較記憶體用量。
我們可以看到記憶體用量從 7.9 MB 下降到了 1.5 MB,降低了 80% 以上。但這對我們原有 dataframe 的影響並不大,因為其中的整型列非常少。 讓我們對其中的浮點型列進行一樣的操作。
我們可以看到浮點型列的資料型別從 float64 變成了 float32,讓記憶體用量降低了 50%。 讓我們為原始 dataframe 建立一個副本,並用這些最佳化後的列替換原來的列,然後看看我們現在的整體記憶體用量。
儘管我們極大地減少了數值列的記憶體用量,但整體的記憶體用量僅減少了 7%。我們的大部分收穫都將來自對 object 型別的最佳化。 在我們開始行動之前,先看看 pandas 中字串的儲存方式與數值型別的儲存方式的比較。
數值儲存與字串儲存的比較object 型別表示使用 Python 字串物件的值,部分原因是 NumPy 不支援缺失(missing)字串型別。因為 Python 是一種高階的解釋性語言,它對記憶體中儲存的值沒有細粒度的控制能力。 這一限制導致字串的儲存方式很碎片化,從而會消耗更多記憶體,而且訪問速度也更慢。object 列中的每個元素實際上都是一個指標,包含了實際值在記憶體中的位置的“地址”。 下面這幅圖給出了以 NumPy 資料型別儲存數值資料和使用 Python 內建型別儲存字串資料的方式。
本圖來自這篇出色的文章:https://jakevdp.github.io/blog/2014/05/09/why-python-is-slow/ 在前面的表格中,你可能已經注意到 object 型別的記憶體使用是可變的。儘管每個指標僅佔用 1 位元組的記憶體,但如果每個字串在 Python 中都是單獨儲存的,那就會佔用實際字串那麼大的空間。我們可以使用 sys.getsizeof() 函式來證明這一點,首先檢視單個的字串,然後檢視 pandas series 中的項。
你可以看到,當儲存在 pandas series 時,字串的大小與用 Python 單獨儲存的字串的大小是一樣的。
使用 Categoricals 最佳化 object 型別pandas 在 0.15 版引入了 Categorials。category 型別在底層使用了整型值來表示一個列中的值,而不是使用原始值。pandas 使用一個單獨的對映詞典將這些整型值對映到原始值。只要當一個列包含有限的值的集合時,這種方法就很有用。當我們將一列轉換成 category dtype 時,pandas 就使用最節省空間的 int 子型別來表示該列中的所有不同值。
為了瞭解為什麼我們可以使用這種型別來減少記憶體用量,讓我們看看我們的 object 型別中每種型別的不同值的數量。
大概看看就能發現,對於我們整個資料集的 172,000 場比賽,其中不同(unique)值的數量可以說非常少。 為了瞭解當我們將其轉換成 categorical 型別時究竟發生了什麼,我們拿出一個 object 列來看看。我們將使用資料集的第二列 day_of_week. 看看上表,可以看到其僅包含 7 個不同的值。我們將使用 .astype() 方法將其轉換成 categorical 型別。
如你所見,除了這一列的型別發生了改變之外,資料看起來還是完全一樣。讓我們看看這背後發生了什麼。 在下面的程式碼中,我們使用了 Series.cat.codes 屬性來返回 category 型別用來表示每個值的整型值。
你可以看到每個不同值都被分配了一個整型值,而該列現在的基本資料型別是 int8。這一列沒有任何缺失值,但就算有,category 子型別也能處理,只需將其設定為 -1 即可。 最後,讓我們看看在將這一列轉換為 category 型別前後的記憶體用量對比。
9.84 MB0.16 MB9.8 MB 的記憶體用量減少到了 0.16 MB,減少了 98%!注意,這個特定列可能代表了我們最好的情況之一——即大約 172,000 項卻只有 7 個不同的值。 儘管將所有列都轉換成這種型別聽起來很吸引人,但瞭解其中的取捨也很重要。最大的壞處是無法執行數值計算。如果沒有首先將其轉換成數值 dtype,那麼我們就無法對 category 列進行算術運算,也就是說無法使用 Series.min() 和 Series.max() 等方法。 我們應該堅持主要將 category 型別用於不同值的數量少於值的總數量的 50% 的 object 列。如果一列中的所有值都是不同的,那麼 category 型別所使用的記憶體將會更多。因為這一列不僅要儲存所有的原始字串值,還要額外儲存它們的整型值程式碼。你可以在 pandas 文件中瞭解 category 型別的侷限性:http://pandas.pydata.org/pandas-docs/stable/categorical.html 我們將編寫一個迴圈函式來迭代式地檢查每一 object 列中不同值的數量是否少於 50%;如果是,就將其轉換成 category 型別。
和之前一樣進行比較:
在這個案例中,所有的 object 列都被轉換成了 category 型別,但並非所有資料集都是如此,所以你應該使用上面的流程進行檢查。 object 列的記憶體用量從 752MB減少到了 52MB,減少了 93%。讓我們將其與我們 dataframe 的其它部分結合起來,看看從最初 861MB 的基礎上實現了多少進步。
optimized_gl[converted_obj.columns] = converted_objmem_usage(optimized_gl)"103.64 MB"你可能記得這一列開始是一個整型,現在已經最佳化成了 unint32 型別。因此,將其轉換成 datetime 型別實際上會讓記憶體用量翻倍,因為 datetime 型別是 64 位的。將其轉換成 datetime 型別是有價值的,因為這讓我們可以更好地進行時間序列分析。
pandas.to_datetime() 函式可以幫我們完成這種轉換,使用其 format 引數將我們的日期資料儲存成 YYYY-MM-DD 形式。
104.29 MB在讀入資料的同時選擇型別 現在,我們已經探索了減少現有 dataframe 的記憶體佔用的方法。透過首先讀入 dataframe ,然後在這個過程中迭代以減少記憶體佔用,我們瞭解了每種最佳化方法可以帶來的記憶體減省量。但是正如我們前面提到的一樣,我們往往沒有足夠的記憶體來表示資料集中的所有值。如果我們一開始甚至無法建立 dataframe ,我們又可以怎樣應用節省記憶體的技術呢? 幸運的是,我們可以在讀入資料的同時指定最優的列型別。pandas.read_csv() 函式有幾個不同的引數讓我們可以做到這一點。dtype 引數接受具有(字串)列名稱作為鍵值(key)以及 NumPy 型別 object 作為值的詞典。 首先,我們可將每一列的最終型別儲存在一個詞典中,其中鍵值表示列名稱,首先移除日期列,因為日期列需要不同的處理方式。
現在我們可以使用這個詞典了,另外還有幾個引數可用於按正確的型別讀入日期,而且僅需幾行程式碼:
104.28 MB透過最佳化這些列,我們成功將 pandas 的記憶體佔用從 861.6MB 減少到了 104.28MB——減少了驚人的 88%!
分析棒球比賽現在我們已經最佳化好了我們的資料,我們可以執行一些分析了。讓我們先從瞭解這些比賽的日期分佈開始。
我們可以看到在 1920 年代以前,星期日的棒球比賽很少,但在上個世紀後半葉就變得越來越多了。 我們也可以清楚地看到過去 50 年來,比賽的日期分佈基本上沒什麼大變化了。讓我們再看看比賽時長的變化情況:
從 1940 年代以來,棒球比賽的持續時間越來越長。
總結和下一步 我們已經瞭解了 pandas 使用不同資料型別的方法,然後我們使用這種知識將一個 pandas dataframe 的記憶體用量減少了近 90%,而且也僅使用了一些簡單的技術: 將數值列向下轉換成更高效的型別將字串列轉換成 categorical 型別 如果你還想使用 pandas 處理更大規模的資料,可以參與這個互動式課程:https://www.dataquest.io/m/163/optimizing-dataframe-memory-footprint/16/next-steps