數據科學家的排序技巧

原題 | Surprising Sorting Tips for Data Scientistshtml

做者 | Jeff Halepython

原文 | towardsdatascience.com/surprising-…git

譯者 | kbsc13("算法猿的成長"公衆號做者)github

聲明 | 翻譯是出於交流學習的目的,歡迎轉載,但請保留本文出於,請勿用做商業或者非法用途算法

導讀

這篇文章介紹了 Python 中幾個經常使用庫的排序技巧,包括原生 Python的、Numpy、Pandas、PyTorch、TensorFlow 以及 SQL。sql

前言

如今其實有很大基礎的排序算法,其中有的算法速度很快並且只須要不多的內存,有的算法更適合用於數據量很大的數據,有的算法適合特定排序的數據,下面的表格給出了大部分經常使用的排序算法的時間複雜度和空間複雜度:shell

對於大部分數據科學問題,並不須要精通全部排序算法的基礎實現。事實上,過早進行優化有時候會被認爲是全部錯誤的根源。不過,瞭解哪一個庫以及須要使用哪些參數進行排序是很是有幫助的,下面是我作的一份小抄:數據庫

接下來將分別介紹上述這幾個庫的排序方法,不過首先是介紹本文用到的這幾個庫的版本,由於不一樣版本的排序方法可能會有些不一樣:編程

python 3.6.8
numpy 1.16.4
pandas 0.24.2
tensorflow==2.0.0-beta1  #tensorflow-gpu==2.0.0-beta1 slows sorting
pytorch 1.1
複製代碼

Python

Python 包含兩個內置的排序方法:api

  • my_list.sort() 會修改列表自己的排序順序,應該它返回值是 None
  • sorted(my_list) 是複製一份列表並進行排序,它不會修改原始列表的數值,返回排序好的列表。

sort 方法是二者中速度更快的,由於是修改列表自己的關係。但這種操做是很是危險的,由於會修改原始數據。

兩種排序方法的默認排序方式都是升序--由小到大。大部分排序方法均可以接受一個參數來改變排序方式爲降序,不過,不幸的是,每一個庫的這個參數名字都不相同。

在 python 中,這個參數名字是 reverse,若是設置 reverse=True 表示排序方式是降序--從大到小。

key 也是一個參數名字,能夠用於建立本身的排序標準,好比sort(key=len) 表示根據元素的長度進行排序。

在 python 中的惟一排序算法是TimsortTimsort是源自歸併排序和插入排序,它會根據須要排序的數據的特徵選擇排序方法。好比,須要排序的是一個短列表,就選擇插入排序方法。更詳細的Timsort實現能夠查看 Brandon Skerritt 的文章:

skerritt.blog/timsort-the…

Timsort是一個穩定的排序算法,這表示對於相同數值的元素,排序先後會保持原始的順序。

對於 sort()sorted() 兩個方法的記憶,這裏提供一個小技巧,由於sorted() 是一個更長的詞語,因此它的運行速度更長,由於須要作一個複製的操做。

Numpy

Numpy 是 Python 用於科學計算的基礎庫,它一樣也有兩個排序方法,一個改變數組自己,另外一個進行復制操做:

  • my_array.sort() 修改數組自己,但會返回排序好的數組;
  • np.sort(my_array) 複製數組並返回排序好的數組,不會改變原始數組

下面是兩個方法可選的參數:

  • axis 整數類型,表示選擇哪一個維度進行排序,默認是 -1,表示對最後一個維度進行排序;
  • kind 排序算法的類型,可選爲 {quicksort’, ‘mergesort’, ‘heapsort’, ‘stable’},排序算法,默認是快速排序--quicksort
  • order 當數組 a 是定義了字段的,這個參數能夠決定根據哪一個字段進行比較。

不過須要注意的是這個排序算法的使用和對這些參數名字的期待會有所不一樣,好比傳遞kind=quicksort實際上採用的是一個 introsort 算法,這裏給出 numpy 的文檔解釋:

當沒有足夠的進展的時候,會轉成堆排序算法,它可讓快速排序在最糟糕的狀況的時間複雜度是 O(n*log(n))

stable會根據待排序數據類型自動選擇最佳的穩定排序算法。而若是選擇 mergesort 參數,則會根據數據類型採用 timsort 或者 radix sort 。由於 API 的匹配性限制了選擇實現方法而且也固定了對不一樣數據類型的排序方法。

Timsort是用於排序好的或者接近排序好的數據,對於隨機排列的數據,它的效果幾乎和 mergesort 同樣。目前它是做爲排序算法,而若是沒有設置 kind 參數,默認選擇仍是快速排序quicksort ,而對於整數數據類型,'mergesort' 和 'stable' 被映射爲採用 radix sort 方法

上述來自 numpy 的文檔解釋,以及做者的部分修改:

github.com/numpy/numpy…

在上述介紹的幾個庫中,只有 numpy 是沒有能夠控制排序方式的參數,不過它能夠經過切片的方式快速反轉一個數組--my_arr[::-1]

numpy 的算法參數在更加友好的 pandas 中能夠繼續使用,而且我發現函數能夠很容易就保持。

Pandas

Pandas 中對 DataFrame 的排序方法是 df.sort_values(by=my_column) ,參數有:

  • bystr 或者是 list of str ,必須指定。根據哪一個或者哪些列進行排序。若是參數axis 是 0 或者 index ,那麼包含的就是索引級別或者是列標籤。若是 axis 是 1 或者 columns ,那麼包含的就是列級別或者索引標籤。
  • axis{0 or index, 1 or columns},默認是 0。排序的軸
  • ascending: bool 或者list of bool 。默認是 True 。排序方式,升序或者降序,能夠指定多個值,但數量必須匹配 by 參數的數量。
  • inplacebool ,默認是 False 。若是是真,那就是修改自己數值,不然就是複製一份;
  • kind{quicksort, mergesort, heapsort, stable},默認是 quicksort。排序算法的選擇。詳情能夠看看numpyndarray.np.sort 。在 pandas 中這個參數只會在對單個標籤或者列中使用
  • na_position{'first', 'last'} 。默認是 'last' 。這是指定 NaN 放置的位置,first 是將其放在開頭,last 就是放在末尾。

對於 Series 相似也是一樣的排序方法。但Series 並不須要指定 by 參數,由於不會有多列。

因爲底層實現是採用 numpy ,因此一樣能夠獲得很好的優化排序選項,但 pandas 由於其便利性會額外耗時一點。

默認對單列的排序算法是採用 Numpy 的 quicksort ,固然實際上調用的排序算法是 introsort ,由於堆排序會比較慢。而對於多列的排序算法,Pandas 確保採用的是 Numpy 的 mergesort ,但實際上會採用 Timsort 或者 Radix sort 算法。這兩個都是穩定的排序算法,而且對多列進行排序的時候也是必須採用穩定的排序算法。

對於 Pandas,必須記住的是這些關鍵知識點是:

  • 排序方面的名字:sort_values()
  • 須要指定參數 by=column_name 或者是一個列名字的列表
  • 倒序的關鍵參數是 ascending
  • 穩定排序是採用 mergesort 參數值

在作數據探索分析的時候,通常在對 DataFrame 作求和和排序數值的時候都採用方法 Series.value_counts()。這裏介紹一個代碼片斷用於對每列出現次數最多的數值進行求和和排序:

for c in df.columns:
  print(f"---- {c} ----")
  print(df[c].value_counts().head())
複製代碼

Dask ,是一個基於 Pandas 的用於處理大數據的庫,儘管已經開始進行討論,直到2019年秋天的時候,尚未實現並行排序的功能。關於這個庫,其 github 地址:

github.com/dask/dask

若是是小數據集,採用 Pandas 進行排序是一個不錯的選擇,可是數據量很大的時候,想要在 GPU 上並行搜索,就須要採用 TensorFlow 或者 PyTorch 了。

TensorFlow

TensorFlow 是目前最流行的深度學習框架,這裏能夠看下我寫的這篇對比不一樣深度學習框架的流行性和使用方法的文章:

towardsdatascience.com/which-deep-…

下面的介紹都是 TensorFlow 2.0 版本的 GPU 版本。

在 TensorFlow 中,排序方法是 tf.sort(my_tensor) ,返回的是一個排序好的 tensor 的拷貝。可選的參數有:

  • axis{int, optional},選擇在哪一個維度進行排序操做。默認是 -1,表示最後一個維度。
  • direction{ascending or discending}。升序仍是降序。
  • name{str, optional}。給這個操做的命名。

tf.sort 採用的是 top_k 方法,而 top_k 是採用 CUB 庫來使得 CUDA GPUs 更容易實現並行化操做。正如官方文檔說的:

CUB 提供給 CUDA 編程模型的每一層提供了最好的可複用的軟件組件。

TensorFlow 的排序算法經過 CUB 庫採用在 GPU 上的 radix sort ,詳細介紹能夠查看:

github.com/tensorflow/…

TensorFlow 的 GPU 信息能夠查看:

www.tensorflow.org/install/gpu

若是須要讓 GPU 兼容 2.0 版本,須要採用下列安裝命令:

!pip3 install tensorflow-gpu==2.0.0-beta1
複製代碼

下面這個代碼能夠查看是否每行代碼都在 GPU 或者 CPU 上運行:

tf.debugging.set_log_device_placement(True)
複製代碼

若是須要指定使用一個 GPU, 代碼以下所示:

with tf.device('/GPU:0'):
  %time tf.sort(my_tf_tensor)
複製代碼

若是是想用CPU,只須要將上述代碼第一行修爲: with tf.device('/CPU:0'),也就是替換 GPU 爲 CPU 便可。

tf.sort() 是很是容易記住的方法,另外就是記住須要改變排序順序,是修改參數 direction

PyTorch

PyTorch 的排序方法是:torch.sort(my_tensor),返回的也是排序好的 tensor 的拷貝,可選參數有:

  • dim{dim, optional}。排序的維度。
  • descending{bool, optional}。控制排序的順序(升序仍是降序)
  • out{tuple, optional}Tensor, LongTensor 的輸出元祖,可用於做爲輸出的緩存。

經過下列代碼來指定採用 GPU:

gpu_tensor=my_pytorch_tensor.cuda()
%time torch.sort(gpu_tensor)
複製代碼

PyTorch 在面對一個數據量大於一百萬行乘10萬列的數據集的時候,是經過 Thrust 實現分割的並行排序。

但不幸的是,我嘗試在谷歌的 Cola 上經過 Numpy 構建一個 1.1M * 100 K 的隨機數據集的時候出現內存不足的錯誤,而後嘗試用 GCP 的 416 MB,出現一樣的內存不足的錯誤。

Thrust 是一個並行算法庫,可使得性能在 GPUs 和多核 GPUs 之間移植。它能夠自動選擇最有效率的排序算法實現。而剛剛介紹的 TensorFlow 使用的 CUB 庫是對 Thrust 的封裝。因此 PyTorch 和 TensorFlow 都採用類似的排序算法實現方式。

和 TensorFlow 同樣,PyTorch 的排序方法也是很是直接,很容易記住:torch.sort()。二者稍微不一樣的就是排序順序的參數名字:TensorFlow 是 direction,而 PyTorch 是 descending 。另外,不要忘記經過 .cuda() 方法指定採用 GPU 來提升對大數據集的計算速度。

在大數據集經過 GPU 進行排序是很好的選擇,但直接在 SQL 上排序也是有意義的。

SQL

在 SQL 中進行排序一般都是很是快速,特別是數據加載到內存中的時候。

SQL 只是一個說明書,並無指定排序算法的具體實現方式。好比 Postgres 根據環境選擇採用 disk merge sort ,或者 quick sort 。若是內存足夠,可讓數據加載在內存中,提升排序的速度。經過設置 work_mem 來增長可用的內存,具體查看:

wiki.postgresql.org/wiki/Tuning…

其餘的 SQL 數據庫採用不一樣的排序算法,好比根據下面這個回答,谷歌的 BigQuery 經過一些技巧實現 introsort

stackoverflow.com/a/53026600/…

在 SQL 中進行排序是經過命令 ORDER_BY ,這個用法和 python 的實現仍是有區別的。但它這個命令名字很獨特,因此很容易記住。

若是是實現降序,採用關鍵詞 DESC。因此查詢顧客的名字,並根據字母表的倒序來返回的語句是以下所示:

SELECT Names FROM Customers
ORDER BY Names DESC;
複製代碼

比較

對上述介紹的方法,我都作了一個分析,採用一樣的 100萬數據,單列,數組或者列表的數據格式。使用的是谷歌的 Colab Jupyter Notebook,而後硬件方面是 K80 GPU, Intel(R) 的 Xeon(R) CPU @2.30GHZ。

源碼地址:colab.research.google.com/drive/1NNar…

對比結果以下所示:

根據上圖可知:

  • GPU 版本的 PyTorch 是速度最快的;
  • 對於 numpy 和 pandas,採用 inplace 都比拷貝數據更快;
  • 默認的 pandas 的 quicksort 速度很快
  • 大部分 pandas 的相同排序算法實現都會慢過 numpy
  • TensorFlow 在 CPU 上速度很快,而 TensorFlow-gpu 版本在 CPU 上使用會變慢,在 GPU 上排序更慢,看起來這多是一個 bug;
  • 原生的 Python inplace 的排序速度很是慢,對比最快的 GPU 版的 PyTorch 要慢接近 100 倍。屢次測量這個方法來確保這不是異常狀況。

另外,這就是一個小小的測試,絕對不是權威的結果。

總結

最後,一般咱們都不須要本身實現排序算法,目前各個庫實現的方法以及很強大了。它們也並非只採用一種排序算法,都是經過對不一樣類型的數據進行測試不一樣的排序算法,從而選擇不一樣狀況下最佳的排序算法,甚至有的實現會改進算法自己來提升排序的速度。

本文介紹了在不一樣的 Python 庫和 SQL 進行排序的方法,通常來講只須要記得采用哪一個參數實現哪一個操做,而後下面是個人一些建議:

  • 對比較小的數據集,採用 Pandas 的默認的 sort_values() 進行數據探索分析;
  • 對於大數據集,或者須要優先考慮速度,嘗試 numpy 的inplacemergesort ,或者 PyTorch 、TensorFlow 在 GPU 上的並行實現,或者是 SQL。

關於在 GPU 進行排序的作法,能夠查看這篇文章:

devtalk.nvidia.com/default/top…


參考

  1. docs.python.org/3/library/s…
  2. docs.python.org/3/library/f…
  3. skerritt.blog/timsort-the…
  4. docs.scipy.org/doc/numpy-1…
  5. docs.scipy.org/doc/numpy-1…
  6. en.wikipedia.org/wiki/Intros…
  7. github.com/numpy/numpy…
  8. github.com/dask/dask
  9. www.tensorflow.org/versions/r2…
  10. towardsdatascience.com/which-deep-…
  11. nvlabs.github.io/cub/
  12. github.com/tensorflow/…
  13. thrust.github.io/
  14. madusudanan.com/blog/all-yo…
  15. wiki.postgresql.org/wiki/Tuning…
  16. stackoverflow.com/a/53026600/…
相關文章
相關標籤/搜索