一行代碼將Pandas加速4倍
導(dǎo)讀
雖然Pandas的功能非常強(qiáng)大,但是對(duì)于大數(shù)據(jù)集來說,確實(shí)是很慢的。
雖然 panda 是 Python 中用于數(shù)據(jù)處理的庫,但它并不是真正為了速度而構(gòu)建的。了解一下新的庫 Modin,Modin 是為了分布式 panda 的計(jì)算來加速你的數(shù)據(jù)準(zhǔn)備而開發(fā)的。
Pandas是處理 Python 數(shù)據(jù)的首選庫。它易于使用,并且在處理不同類型和大小的數(shù)據(jù)時(shí)非常靈活。它有大量的函數(shù),使得操縱數(shù)據(jù)變得輕而易舉。

隨著時(shí)間的推移,各種Python包的流行程度
但是有一個(gè)缺點(diǎn):對(duì)于較大的數(shù)據(jù)集來說,panda“慢”。
默認(rèn)情況下,panda 使用單個(gè) CPU 內(nèi)核作為單個(gè)進(jìn)程執(zhí)行其函數(shù)。這對(duì)于較小的數(shù)據(jù)集工作得很好,因?yàn)槟憧赡懿粫?huì)注意到速度上的差異。但是,隨著數(shù)據(jù)集越來越大,計(jì)算量越來越大,如果只使用單個(gè) cpu 核,速度會(huì)受到很大的影響。它在數(shù)據(jù)集上同一時(shí)間只能計(jì)算一次,但該數(shù)據(jù)集可以有數(shù)百萬甚至數(shù)十億行。
然而,大多數(shù)用于數(shù)據(jù)科學(xué)的現(xiàn)代機(jī)器都有至少 2 個(gè) CPU 核。這意味著,以 2 個(gè) CPU 核為例,在使用 pandas 時(shí),50%或更多的計(jì)算機(jī)處理能力在默認(rèn)情況下不會(huì)執(zhí)行任何操作。當(dāng)你使用 4 核(現(xiàn)代 Intel i5)或 6 核(現(xiàn)代 Intel i7)時(shí),情況會(huì)變得更糟。pandas 的設(shè)計(jì)初衷并不是為了有效利用這種計(jì)算能力。
Modin是一個(gè)新的庫,通過在系統(tǒng)所有可用的 CPU 核上自動(dòng)分配計(jì)算來加速 pandas。有了它,對(duì)于任何尺寸的 pandas 數(shù)據(jù)數(shù)據(jù)集,Modin 聲稱能夠以 CPU 內(nèi)核的數(shù)量得到近乎線性的加速。
讓我們看看它是如何工作的,并通過一些代碼示例進(jìn)行說明。
Modin 如何用 Pandas 并行計(jì)算
給定 pandas 中的 DataFrame ,我們的目標(biāo)是以盡可能快的方式對(duì)其執(zhí)行某種計(jì)算或處理。可以用*.mean()取每一列的平均值,用groupby對(duì)數(shù)據(jù)進(jìn)行分組,用drop_duplicates()*刪除所有重復(fù)項(xiàng),或者使用其他任何內(nèi)置的 pandas 函數(shù)。
在前一節(jié)中,我們提到了 pandas 如何只使用一個(gè) CPU 核進(jìn)行處理。自然,這是一個(gè)很大的瓶頸,特別是對(duì)于較大的 DataFrames,計(jì)算時(shí)就會(huì)表現(xiàn)出資源的缺乏。
理論上,并行計(jì)算就像在每個(gè)可用的 CPU 核上的不同數(shù)據(jù)點(diǎn)上應(yīng)用計(jì)算一樣簡單。對(duì)于一個(gè) pandas 的 DataFrame,一個(gè)基本的想法是將 DataFrame 分成幾個(gè)部分,每個(gè)部分的數(shù)量與你擁有的 CPU 內(nèi)核的數(shù)量一樣多,并讓每個(gè) CPU 核在一部分上運(yùn)行計(jì)算。最后,我們可以聚合結(jié)果,這是一個(gè)計(jì)算上很 cheap 的操作。

多核系統(tǒng)如何更快地處理數(shù)據(jù)。對(duì)于單核進(jìn)程(左),所有10個(gè)任務(wù)都放在一個(gè)節(jié)點(diǎn)上。對(duì)于雙核進(jìn)程(右圖),每個(gè)節(jié)點(diǎn)承擔(dān)5個(gè)任務(wù),從而使處理速度加倍。
這正是 Modin 所做的。它將 DataFrame 分割成不同的部分,這樣每個(gè)部分都可以發(fā)送到不同的 CPU 核。Modin 在行和列之間劃分 DataFrame。這使得 Modin 的并行處理可擴(kuò)展到任何形狀的 DataFrame。
想象一下,如果給你一個(gè)列多行少的 DataFrame。有些庫只執(zhí)行跨行分區(qū),在這種情況下效率很低,因?yàn)槲覀兊牧斜刃卸?。但是?duì)于 Modin 來說,由于分區(qū)是跨兩個(gè)維度進(jìn)行的,所以并行處理對(duì)于所有形狀的數(shù)據(jù)流都是有效的,不管它們是更寬的(很多列)、更長的(很多行),還是兩者都有。

panda的DataFrame(左)存儲(chǔ)為一個(gè)塊,只發(fā)送到一個(gè)CPU核。Modin的DataFrame(右)跨行和列進(jìn)行分區(qū),每個(gè)分區(qū)可以發(fā)送到不同的CPU核上,直到用光系統(tǒng)中的所有CPU核。
上面的圖是一個(gè)簡單的例子。Modin 實(shí)際上使用了一個(gè)“分區(qū)管理器”,它可以根據(jù)操作的類型改變分區(qū)的大小和形狀。例如,可能有一個(gè)操作需要整個(gè)行或整個(gè)列。在這種情況下,“分區(qū)管理器”將以它能找到的最優(yōu)方式執(zhí)行分區(qū)和分配到 CPU 核上。它是非常靈活的。
為了在執(zhí)行并行處理時(shí)完成大量繁重的工作,Modin 可以使用 Dask 或 Ray。它們都是使用 Python api 的并行計(jì)算庫,你可以選擇一個(gè)或另一個(gè)在運(yùn)行時(shí)與 Modin 一起使用。Ray 目前是最安全的一個(gè),因?yàn)樗€(wěn)定 —— Dask 后端是實(shí)驗(yàn)性的。
已經(jīng)有足夠的理論了。讓我們來看看代碼和速度基準(zhǔn)測試!
Modin 速度基準(zhǔn)測試
安裝 Modin 的最簡單的方法是通過 pip。下面的命令安裝 Modin、Ray 和所有相關(guān)的依賴項(xiàng):
- pip install modin[ray]
對(duì)于我們下面的例子和 benchmarks,我們使用了 Kaggle 的 CS:GO Competitive Matchmaking Data。CSV 的每一行都包含了 CS:GO 比賽中的一輪數(shù)據(jù)。
現(xiàn)在,我們嘗試使用最大的 CSV 文件(有幾個(gè)),esea_master_dmg_demo .part1.csv,它有 1.2GB。有了這樣的體量,我們應(yīng)該能夠看到 pandas 有多慢,以及 Modin 是如何幫助我們加速的。對(duì)于測試,我使用一個(gè) i7-8700k CPU,它有 6 個(gè)物理內(nèi)核和 12 個(gè)線程。
我們要做的第一個(gè)測試是使用 read_csv()讀取數(shù)據(jù)。Pandas 和 Modin 的代碼是完全一樣的。
- ### Read in the data with Pandasimport pandas as pds = time.time()df
- = pd.read_csv("esea_master_dmg_demos.part1.csv")e =
- time.time()print("Pandas Loading Time = {}".format(e-s))### Read in
- the data with Modinimport modin.pandas as pds = time.time()df =
- pd.read_csv("esea_master_dmg_demos.part1.csv")e =
- time.time()print("Modin Loading Time = {}".format(e-s))
為了測量速度,我導(dǎo)入了time模塊,并在read_csv()之前和之后放置了一個(gè)time()。panda 將數(shù)據(jù)從 CSV 加載到內(nèi)存需要 8.38 秒,而 Modin 需要 3.22 秒。這是 2.6 倍的加速。對(duì)于只修改 import 語句來說,這不算太寒酸!
讓我們?cè)?DataFrame 上做一些更復(fù)雜的處理。連接多個(gè) DataFrames 是 panda 中的一個(gè)常見操作 — 我們可能有幾個(gè)或多個(gè)包含數(shù)據(jù)的 CSV 文件,然后必須一次讀取一個(gè)并連接它們。我們可以使用 panda 和 Modin 中的*pd.concat()*函數(shù)輕松做到這一點(diǎn)。
我們希望 Modin 能夠很好地處理這種操作,因?yàn)樗幚泶罅康臄?shù)據(jù)。代碼如下所示。
- import pandas as pddf =
- pd.read_csv("esea_master_dmg_demos.part1.csv")s = time.time()df =
- pd.concat([df for _ in range(5)])e = time.time()print("Pandas Concat
- Time = {}".format(e-s))import modin.pandas as pddf =
- pd.read_csv("esea_master_dmg_demos.part1.csv")s = time.time()df =
- pd.concat([df for _ in range(5)])e = time.time()print("Modin Concat
- Time = {}".format(e-s))
在上面的代碼中,我們將 DataFrame 與自身連接了 5 次。pandas 在 3.56 秒內(nèi)完成了連接操作,而 Modin 在 0.041 秒內(nèi)完成,速度提高了 86.83 倍!看起來,即使我們只有 6 個(gè) CPU 核心,DataFrame 的分區(qū)也有助于提高速度。
用于 DataFrame 清洗的 panda 函數(shù)是*.fillna()*函數(shù)。此函數(shù)查找 DataFrame 中的所有 NaN 值,并將它們替換為你選擇的值。panda 必須遍歷每一行和每一列來查找 NaN 值并替換它們。這是一個(gè)應(yīng)用 Modin 的絕佳機(jī)會(huì),因?yàn)槲覀円啻沃貜?fù)一個(gè)非常簡單的操作。
- import pandas as pddf =
- pd.read_csv("esea_master_dmg_demos.part1.csv")s = time.time()df =
- df.fillna(value=0)e = time.time()print("Pandas Concat Time =
- {}".format(e-s))import modin.pandas as pddf =
- pd.read_csv("esea_master_dmg_demos.part1.csv")s = time.time()df =
- df.fillna(value=0)e = time.time()print("Modin Concat Time =
- {}".format(e-s))
這次,Pandas 運(yùn)行*.fillna()*用了 1.8 秒,而 Modin 用了 0.21 秒,8.57 倍的加速!
警告!
Modin 總是這么快嗎?
并不是這樣。
在有些情況下,panda 實(shí)際上比 Modin 更快,即使在這個(gè)有 5,992,097(近 600 萬)行的大數(shù)據(jù)集上也是如此。下表顯示了我進(jìn)行的一些實(shí)驗(yàn)中 panda 與 Modin 的運(yùn)行時(shí)間。
正如你所看到的,在某些操作中,Modin 要快得多,通常是讀取數(shù)據(jù)并查找值。其他操作,如執(zhí)行統(tǒng)計(jì)計(jì)算,在 pandas 中要快得多。

使用 Modin 的實(shí)用技巧
Modin 仍然是一個(gè)相當(dāng)新的庫,并在不斷地發(fā)展和擴(kuò)大。因此,并不是所有的 pandas 功能都被完全加速了。如果你在 Modin 中嘗試使用一個(gè)還沒有被加速的函數(shù),它將默認(rèn)為 panda,因此不會(huì)有任何代碼錯(cuò)誤或錯(cuò)誤。
默認(rèn)情況下,Modin 將使用計(jì)算機(jī)上所有可用的 CPU 內(nèi)核。在某些情況下,你可能希望限制 Modin 可以使用的 CPU 內(nèi)核的數(shù)量,特別是如果你希望在其他地方使用這種計(jì)算能力。我們可以通過 Ray 中的初始化設(shè)置來限制 Modin 可以訪問的 CPU 內(nèi)核的數(shù)量,因?yàn)?Modin 在后端使用它。
- import rayray.init(num_cpus=4)import modin.pandas as pd
在處理大數(shù)據(jù)時(shí),數(shù)據(jù)集的大小超過系統(tǒng)上的內(nèi)存(RAM)的情況并不少見。Modin 有一個(gè)特殊的標(biāo)志,我們可以設(shè)置為“true”,這將使其進(jìn)入“out of core”模式。這意味著 Modin 將使用你的磁盤作為你的內(nèi)存溢出存儲(chǔ),允許你處理比你的 RAM 大得多的數(shù)據(jù)集。我們可以設(shè)置以下環(huán)境變量來啟用此功能:
- export MODIN_OUT_OF_CORE=true
總結(jié)
這就是使用 Modin 加速 panda 函數(shù)的指南。只需修改 import 語句就可以很容易地做到這一點(diǎn)。希望你發(fā)現(xiàn) Modin 至少在一些情況下對(duì)加速 panda有用。