Categories
程式開發

如何用Python在筆記本上分析100GB數據?


許多組織都想盡可能多地收集和利用數據,從而改進業務、增加收入和提升影響力。因此,數據科學家們要面對50GB,甚至500GB數據集的場景變得越來越普遍。

目前,這些數據集處理起來有點麻煩。就大小而言,它們可以放進你筆記本電腦的硬盤裡,但卻無法裝入內存。所以,僅僅打開和查看它們就很困難,更何況進一步探索和分析。

處理這樣的數據集時,一般有3種策略。

第1種是對數據進行子抽樣,但它有一個明顯缺點:可能因忽略部分數據而錯失關鍵信息,甚至誤解數據表達的含義。

第2種是使用分佈式計算。雖然在某些情況下這是一種有效的方法,但是管理和維護集群會帶來巨大開銷。想像一下,要為一個剛超出內存大小、大概30-50GB的數據集就建立一套集群,對我來說,這似乎有點“用力過猛”。

第3種是租用一個內存大小等同於數據集大小的強大雲服務實例,例如,AWS 提供了TB級內存的雲服務實例。但這種情況還需要管理雲數據存儲空間,並且在每次實例啟動時都要等待數據從存儲空間傳輸到實例。另外還需要應對數據上雲的合規性問題,以及忍受在遠程機器上工作帶來的不便。更別提成本,雖然開始會比較低,但隨著時間推移會快速上漲。

本文向你展示一種全新方法,它更快、更安全,可以更方便、全面地對幾乎任意大小的數據集進行數據科學研究,只要這個數據集能裝進你的筆記本電腦、台式機或者服務器的硬盤裡就行。

Vaex

如何用Python在筆記本上分析100GB數據? 1

Vaex是一個開源的DataFrame庫,對於和你硬盤空間一樣大小的表格數據集,它可以有效進行可視化、探索、分析乃至實踐機器學習。

為實現這些功能,Vaex採用內存映射、高效的核外算法和延遲計算等概念。所有這些都封裝為類Pandas的API,因此,任何人都能快速上手。

10億級出租車的數據分析

為闡述這些概念,我們對一個遠超出一般筆記本電腦內存大小的數據集進行簡單地探索分析。

這裡,我們使用New York City(NYC) Taxi數據集,它包含了標誌性的黃色出租車2009年到2015年間超過十億次的出租車行程信息。

數據從網站下載,提供CSV格式。完整分析可以單獨查看這個Jupyter notebook

0.052秒打開100G的數據集

第一步是將數據轉換為內存映射文件格式,如Apache ArrowApache ParquetHDF5。關於如何把CSV數據轉為HDF5的例子請看這裡。一旦數據存為內存映射格式,即便它的磁盤大小超過100GB,用Vaex也可以在瞬間打開它(0.052秒):

如何用Python在筆記本上分析100GB數據? 2

Vaex瞬間打開內存映射文件(0.052秒)

為什麼這麼快?在用Vaex打開內存映射文件時,實際上並沒有讀取數據。 Vaex只讀取了文件元數據,比如數據在磁盤上的位置、數據結構(行數、列數、列名和類型)、文件描述等等。那如果想查看或者操作數據呢?

將數據結果展示在一個標準的DataFrame中進行預覽,速度一樣非常快。

如何用Python在筆記本上分析100GB數據? 3

預覽New York City Yellow Taxi數據

代碼單元執行時間還是非常短。這是因為展示Vaex DataFrame或者某列,只需要從硬盤中讀取前5行和後5行數據。這裡引出另一個要點:Vaex只會在必要的時候遍歷整個數據集,而且它會盡可能遍歷更少的數據。

不管怎樣,我們先從異常值和錯誤的輸入值開始清理這個數據集。一種好的方式是用describe方法獲取數據的高級概覽,它可以展示樣本數量、缺失值的數量和每列的數據類型。

如果某列的數據類型是數值,它還將展示其平均值、標準差、最小值及最大值。而所有的這些數據都是通過一次數據遍歷計算的。

如何用Python在筆記本上分析100GB數據? 4

使用describe方法獲得DataFrame的高級概覽,注意這個DataFrame包含18列數據,不過截圖只展示了前7列。

describe方法很好地體現了Vaex的能力和效率:所有這些數據都是在我的MacBook Pro(15英寸、2018款、2.6GHz Intel Core i7和32G內存)上在3分鐘內計算出來的。其他庫或方法則需要分佈式計算或超過100GB的雲服務實例才能完成相同的計算,而有了Vaex你需要的只是數據以及擁有幾GB內存的筆記本電腦。

查看describe的輸出很容易發現這個數據包含了一些明顯的異常值。

首先從檢查上車點開始,去除異常值最簡單的方法就是繪製出上車點和下車點位置,並且直觀地確定紐約哪些地區是要分析關注的。由於要處理的數據集如此龐大,直方圖是最有效的可視化方法。用Vaex創建和展示直方圖及熱力圖相當快,而且圖表還是可交互的!

df.plot_widget(df.pickup_longitude, 
               df.pickup_latitude, 
               shape=512, 
               limits='minmax',
               f='log1p', 
               colormap='plasma')

一旦通過交互確定紐約哪些區域是要關注的區域後,我們就可以創建一個篩選後的DataFrame:

如何用Python在筆記本上分析100GB數據? 5

上述代碼很酷的一點是,它只需很少內存就可以執行!在篩選Vaex DataFrame時並不會復制數據。而是只創建對原始對象的引用,並在其上應用二進制掩碼。用掩碼來選擇哪些行將被顯示以及將來用於計算。這為我們節省了100GB的內存,而像現在許多標準數據科學工具則必須得複制數據才行。

現在,來看下passenger_count這一列。單次出租車行程乘坐人數的最大值是255,這似乎有點誇張。我們來數數每次行程的乘客人數,這裡用value_counts方法很容易實現:

如何用Python在筆記本上分析100GB數據? 6

在10億行數據上使用 value_counts 方法只需要20秒!

從上圖可以看出,乘客超出6人可能是少見的異常值或者是錯誤的數據輸入。同時還有大量乘客數為0的行程。既然不知道這些行程是否合理,就先把它們過濾掉。

如何用Python在筆記本上分析100GB數據? 7

再用行程距離做一個類似的練習。由於它是一個連續的變量,我們可以繪製出行程距離的分佈情況。行程距離的最小值是負值,而最大值比火星都遠,所以還是限制在一個合理的區間內繪製直方圖。

如何用Python在筆記本上分析100GB數據? 8

紐約出租車數據行程距離直方圖

從上圖中可以看到,行程數量隨著距離的增加而減少。在距離大約為100英里處,分佈有明顯下降。現在,我們用這個作為分界點來消除行程距離的異常值。

如何用Python在筆記本上分析100GB數據? 9

在行程距離這一列中存在異常值,也因此有了動機查看行程耗費時間和平均速度。數據集中並沒有提供這兩個特徵數據,但是很容易計算得出:

如何用Python在筆記本上分析100GB數據? 10

上面的代碼塊不需要內存也不消耗執行時間!是因為代碼只會創建虛擬列(virtual columns),這些虛擬列只包含數學表達式,僅在需要時才進行計算。除此之外,虛擬列和其他常規列是一樣的。注意,其他的標準庫可能需要數十GB的內存才能實現相同操作。

好了,我們來繪製行程耗費時間的分佈:

如何用Python在筆記本上分析100GB數據? 11

紐約超過10億次出租車行程耗費時間的直方圖

從上圖可以看到,95%的出租車行程不到30分鐘就可以到達目的地,但有些行程可能花費超過4-5個小時。你能想像在紐約市被困在出租車裡3個多小時的情景麼?不管怎樣,我們豁達點只考慮少於3小時的行程:

如何用Python在筆記本上分析100GB數據? 12

對於出租車的平均速度,也選擇一個合理的範圍來查看:

如何用Python在筆記本上分析100GB數據? 13

出租車平均速度分佈

根據分佈趨平的位置,可以推斷出租車合理的平均速度在每小時1到60英里之間,由此可以更新篩選後的DataFrame:

如何用Python在筆記本上分析100GB數據? 14

把關注點切換到出租車行程的費用上,從describe方法的輸出可以看到fare_amount、total_amount和tip_amount列都有一些誇張的異常值。首先,這幾列都不應該出現負值。

另一方面,有些數字表示一些幸運的司機只開一次出租車就快要成為百萬富翁了。讓我們在合理的範圍內查看這些數量的分佈:

如何用Python在筆記本上分析100GB數據? 15

紐約超過10億次出租車行程的車費、總額和小費的分佈。在筆記本上繪製這些圖表只用了31秒!

以上三個分佈都有相當長的尾部。尾部可能有一些正確的值,而其他可能都是錯誤的輸入。不管怎樣,我們保守一點只考慮fare_amount、total_amount和tip_amount低於200美元的行程。另外fare_amount、total_amount的值還要大於0。

如何用Python在筆記本上分析100GB數據? 16

最終,在初步清理之後,看看還剩下多少出租車行程數據可供分析:

如何用Python在筆記本上分析100GB數據? 17

還剩下11億的行程!這些數據足以讓我們從出租車出行中獲得一些有價值的見解。

坐上駕駛座

假設我們是一名出租車司機或者一家出租車公司的經理,有興趣使用這些數據來了解如何最大化利潤、最小化成本或者僅僅是改善我們的工作生活。

首先,我們找出可以帶來平均最高收益的接客地點。簡單講,只需要繪製出接客地點的熱力圖,並用顏色標記平均價格,然後查看熱點地區。但是由於出租車司機需要自行承擔費用,例如燃料費。

因此,雖然將乘客載到較遠的地方可能帶來更高車費,但同時也意味著更多的燃料消耗和時間損失。

另外,在偏遠地區找一個回市中心某地的乘客可不是那麼容易,而在沒有乘客的情況下回程就會很浪費。一種解決方法是用車費和行程距離之比的平均值對熱力圖進行顏色編碼。我們來嘗試一下這兩種方法:

如何用Python在筆記本上分析100GB數據? 18

紐約熱力圖,顏色編碼:平均車費(左),車費與行程距離的平均比值

簡單情況下,只關心所提供服務的最高車費時,紐約機場以及像範懷克高速公路(Van Wyck Expressway)和長島高速公路(Long Island Expressway)這樣的主幹道是最佳的載客區。

當考慮行程距離時,會得到一張不同的圖像。範懷克高速公路、長島高速公路以及機場仍然是搭載乘客的好地方,但它們在地圖上的重要性要低得多。而在哈德遜河(Hudson river)的西側出現了一些新的熱點地區,看起來利潤頗豐。

作為出租車司機,實踐中可以做得很靈活。要想更好地利用這種靈活性,除了知道要在哪裡載客之外,了解什麼時候出車最賺錢也很有用。為回答這個問題,我們繪製一個圖表來展示每天和每小時的平均車費與行程距離之比。

如何用Python在筆記本上分析100GB數據? 19

一周中每天以及一天中每小時的車費與行程的平均比值

上面的圖很符合常識:高峰時段最掙錢,特別是工作日的中午。作為出租車司機,收入的一部分要交給出租車公司,所以我們可能會對哪天、哪個時段顧客給的小費最多感興趣。繪製一個類似的圖展示平均小費比例:

如何用Python在筆記本上分析100GB數據? 20

一周中每天以及一天中每小時的小費比例平均值

上圖很有意思,它告​​訴我們在一周中前幾天的早上7-10點和晚上7-10點乘客給司機的小費最多。如果在凌晨3-4點接乘客,不要指望會有大額小費。

結合最後兩張圖的經驗,早上8點到10點是最好的工作時間,司機可以得到較多的車費(每英里)和小費。

發動引擎

在本文的前半部分,我們簡要地關注了trip_distance列,在給它清理異常值時,只保留了低於100英里的行程。但這個邊界值仍然很大,尤其是考慮到Yellow Taxi公司主要在曼哈頓運營。

trip_distance描述出租車從上車點到下車點的行駛距離,但是在確切的兩個上車點和下車點之間,通常有多條不同距離的路線可以選擇,比如為了避開交通堵塞和道路施工的情況。

所以,相對於trip_distance列,我們計算一項接送位置之間可能的最短距離,命名為arc_distance

如何用Python在筆記本上分析100GB數據? 21

對於用numpy編寫的複雜表達式,vaex可以藉助Numba、Pythran甚至CUDA(需要NVIDIA GPU)通過即時編譯來極大提高運算速度。

arc_distance的計算公式非常複雜,它包含了大量的三角函數和數學運算,在處理大型數據集時計算代價非常高。如果表達式或函數只用到了Python運算符和Numpy庫的方法,Vaex會使用計算機的所有核心來並行計算。

除此之外,Vaex通過Numba(使用LLVM)和Pythran(通過C++加速)支持即時編譯從而提供更好性能。如果你碰巧有NVIDIA顯卡,就可以通過jit_cuda方法來運用CUDA以獲取更快的性能。

我們來繪製trip_distance和arc_distance的分佈:

如何用Python在筆記本上分析100GB數據? 22

左:trip_distance和arc_distance的比較;右:arc_distance<100米時trip_distance的分佈。

有意思的是,arc_distance從來沒有超過21英里,但出租車實際行駛距離可能是它的5倍。事實上,有數百萬次的行程下客點距離上客點只有100米(0.06英里)!

過去幾年的Yellow Taxis

我們今天使用數據集時間上跨越了7年。隨著時間流逝,人們的興趣如何演變可能是件有趣的事。使用Vaex可以進行快速的核外分組和聚合操作。

我們來看看7年間車費和行程距離都有什麼變化:

如何用Python在筆記本上分析100GB數據? 23

在四核處理器的筆記本電腦上,對擁有超過10億樣本的Vaex DataFrame進行8個聚合的分組操作只需不到2分鐘。

在上面的代碼塊中,我們執行分組操作,然後執行8個聚合,其中有2個位於虛擬列上。上面的代碼塊在我的筆記本電腦上執行耗時不到2分鐘。鑑於我們使用的數據包含超過10億條樣本,這是相當驚人的。

總之,我們來看看結果,以下是多年來乘坐出租車的費用的變化情況:

如何用Python在筆記本上分析100GB數據? 24

每年的平均車費和總金額以及乘客所付的小費比例

可以看出隨著時間流逝,出租車費和小費都在上漲。再來看看出租車每年的平均trip_disrancearc_distance:

如何用Python在筆記本上分析100GB數據? 25

出租車每年的行程和弧距

上圖顯示,trip_distancearc_distance都有一個小的增長,這意味著,平均而言,人們傾向於每年走得更遠一點。

給錢吧

在旅程結束之前,我們再停一站,調查一下乘客是如何支付乘車費用的。數據集中包含了payment_type,來看看它都包含什麼值:

如何用Python在筆記本上分析100GB數據? 26

數據集文檔中,可以看出只有6個有效條目:

  • 1 = 信用卡支付
  • 2 = 現金付款
  • 3 = 免費
  • 4 = 爭議
  • 5 = 未知
  • 6 = 無效行程

由此,可以簡單地把payment_type映射到整數:

如何用Python在筆記本上分析100GB數據? 27

現在可以根據每年的數據進行分組,看看紐約人在支付打車費用方面的習慣是如何變化的:

如何用Python在筆記本上分析100GB數據? 28

每年的支付方式

可以發現隨著時間的推移,信用卡支付逐漸變得比現金支付更加頻繁。我們果然是生活在數字時代!在上面的代碼塊中,一旦完成數據聚合,小型的Vaex DataFrame可以輕易地轉換為Pandas DataFrame,從而傳遞給Seaborn。不用費勁在這重新發明輪子。

最後通過繪製現金支付和信用卡支付之間的比例,來查看付款方法是否取決於當天的時間或者星期幾。為此,先創建一個過濾器,篩出用現金或者信用卡的行程。

下一步是我最喜歡的 Vaex的特性之一:帶選擇的聚合。其他庫要求對每個支付方法篩選出單獨的DataFrame進行聚合,然後再合併為一個。

而使用Vaex,可以在聚合函數中提供多個選擇從而一步到位。這非常方便,只需進行一次數據傳遞,能提供更好性能。之後就可以用標準方式繪製DataFrame:

如何用Python在筆記本上分析100GB數據? 29

在給定的時間和星期中某一天,現金和信用卡支付的比例

從上圖可以發現其模式非常類似於之前的一周中每天以及一天中每小時的小費比例。從這兩個圖推測,信用卡支付的乘客傾向於比現金支付的乘客給更多的小費。

要想知道這是不是真的,我希望你去嘗試把它弄清楚,因為現在你已經擁有了知識、工具和數據!你可以從這個Jupyter notebook獲取更到額外提示。

抵達目的地

我希望這篇文章是對Vaex一個有用的介紹,它會幫你緩解可能面臨的一些“麻煩數據”的問題,至少在涉及表數據集時是這樣的。如果你對探索本文中用到的數據集感興趣,可以直接在S3中配合Vaex使用它,請參閱完整的Jupyter notebook了解如何實現。

有了Vaex,你可以在短短幾秒內遍歷超過10億行數據,計算各種統計、聚合併產出信息圖表,這一切都能在你的筆記本電腦上完成。它免費且開源,你可以嘗試一下!

數據科學快樂!

原文鏈接:

How to analyse 100 GB of data on your laptop with Python