天天看點

第79天:資料分析之 Numpy 初步

by 軒轅禦龍

NumPy 是 Python 中一個基本的科學計算庫,包含以下特性:

強大的 N 維數組對象;

精巧的廣播(broadcasting)功能;

C/C++ 和 Fortran 代碼內建工具;

實用的線性代數、傅裡葉變換、随機數生成等功能。

其中,N 維數組是 NumPy 最為核心的特性。

除了顯而易見的科學計算用途,NumPy 還可以用作一般資料類型的多元容器,并且是任何資料類型均可;但有一點:一個數組中必須是同種資料類型。這一特性使得 NumPy 可以高效、無縫地與各種資料庫進行內建。

NumPy 基于開源許可協定 BSD license 釋出,對于二次分發使用幾乎沒有限制。

由于 NumPy 并非 Python 的内置子產品,是以我們直接從 Python 官網下載下傳安裝的發行版是不包含 NumPy 這個子產品的。這個時候你要是想<code>import numpy</code>,顯然是會無功而返的。是以我們需要額外安裝 NumPy 子產品。

安裝 NumPy 有好幾種方式,我們這裡推薦的是:1)使用<code>pip</code>進行安裝;2)安裝Anaconda。

這種方式推薦給已經從 Python 官網下載下傳了某個 Python 發行版的讀者,或是已經通過其它方式獲得了 Python 環境,但卻沒有 NumPy 這個子產品的讀者。

安裝指令:

或:

均可。

當然,實際上 NumPy 子產品本身也有很多依賴,也需要其他一些子產品才能夠真正發揮出它強大的功能,是以我們推薦一次安裝多個子產品:

這種方式适合還沒有安裝 Python 的讀者,或是已經安裝了 Python 但是想一勞永逸擁有大多數科學計算庫的讀者。

通路 Anaconda 官網找到下載下傳連結進行安裝即可。

或者如果你覺得 Anaconda 過于臃腫,也可以安裝其簡化版本 Miniconda 。

N 維數組,也稱“多元數組”,這裡的“N”代表的就是數組的維數。

我們在之前已經學過了 Python 中的内置資料類型,其中有一種在表現形式上跟多元數組很像——就是序列(sequence)型的清單(list)資料:二者都是由中括号括起來的資料類型。

但實際上,多元數組和清單是完全不同的兩種資料類型,我們在使用的時候一定要嚴格地将二者區分開來。具體地講,二者有以下不同點:

NumPy 的多元數組在建立之初形狀大小就已經固定;而清單的大小則是動态變化的。改變一個多元數組的大小,實際上會導緻一個新的數值被建立,舊的數組的删除。

NumPy 多元數組中的元素要求是同種資料類型,是以在記憶體中實際的大小也是相同的。隻有一個例外:數組由 Python 的各種對象組成,這樣數組中元素的大小才會不同。

NumPy 的多元數組使得對大量資料進行進階的數學運算或其他操作變得更加友善快捷。對同樣一個運算來說,使用多元數組會比使用 Python 内置的序列類型更加高效,代碼量也更少。

許許多多的基于 Python 的科學計算庫、數學應用庫都使用 NumPy 的多元數組進行實作,并且這些庫的數量還在日漸增多。就算有些庫也同樣支援 Python 的序列作為輸入,它們也會優先将序列轉換為多元數組,再進行處理;此外,這些庫的輸出一般也是多元數組。換而言之,要無障礙地使用當今大多數基于 Python 的科學/數學計算軟體,僅僅掌握 Python 内置的序列資料類型是不夠的,還應當學會如何使用 NumPy 的多元數組。

本小節代碼示例來自《何為 NumPy》

讓我們來看一個例子(暫時忽略對變量的定義,假定存在 a、b、result 這三個數組)。

假設我們現在有 a、b 兩個一維數組(類似于一維清單),要對它們進行“對應元素相乘”的運算,如果使用 Python 内置的清單,應該寫成什麼樣呢?

顯然結果是正确無誤的。但是我們考慮一個情形:如果 a、b 兩個一維數組中每個都包含數百萬甚至更多的元素,會發生什麼呢?衆所周知,Python 作為一門解釋型語言,其代碼的執行速度本就遠低于衆多的編譯型語言,并且之前我們也講到過,Python 中的<code>for</code>循環實際上是一個疊代計算的過程,這樣就會導緻性能上成本很高;大多數時間都被浪費在了對代碼的解釋和對對象的操作上。

講到性能問題,我們一下就想到了日常被拉出來和 Python 比較的 C 語言。那麼我們用 C 語言來實作上面這個運算又如何呢?

較之之前的 Python 代碼,顯然這段 C 代碼節省了解釋代碼和操作對象的性能開銷,但凡事有利有弊,提升了性能的同時也犧牲了 Python 代碼的簡潔明了。如果是二維數組呢?

好了,不用再往上加了。我們完全可以想見,随着數組維數越來越多,這段代碼嵌套的循環也将越來越多,代碼越來越龐雜——總之直覺性越來越差。

如果用上 NumPy 呢?同樣是計算兩個數組的逐元素乘積,NumPy 對于包含多元數組的運算預設模式就是“逐元素運算”,同時又通過預編譯好的 C 代碼來實作高性能的計算。使用示例如下:

啊哈!是不是有點眼熟?這不就是一個很自然的數學表達式嗎?當然跟矩陣的點乘還是不一樣的,但是隻要了解 NumPy 的這個運算規則的人,看到這行代碼就有一種一目了然之感。管你三七二十,兩維、十維還是八十、八百維,我都用這一個表達式全部搞定。既保留了 Python 代碼簡潔明了的風格,也獲得了較高的執行速度。

在上面的例子中,支撐起 NumPy 強大功能的主要有兩個很重要的特性:矢量化(vectorization)和廣播(broadcasting)。

其中,矢量化描述的是這麼個情形:凡是在遇到需要顯式循環、索引的地方,都可以省略掉這些細枝末節,由預編譯好的、優化過的 C 代碼在背景默默耕耘。這麼一來就帶來了幾個好處:

矢量化的代碼更加簡潔易讀;

代碼行的減少也意味着更少的 bug;

代碼中表達式更加接近數學公式的本來面目;

矢量化也使得代碼更加 Pythonic。沒有“矢量化”,我們的代碼将會充斥着低效、難懂的<code>for</code>循環。

“廣播”這個特性對于掌握 NumPy 非常重要,文章後面會單獨講解一下,希望讀者能夠用心了解

“廣播”是一個 NumPy 的術語,描述的則是這麼一個情況:NumPy 中的運算都是預設逐元素進行的。這些運算不僅限于常見的算術運算,也包括不那麼常見的邏輯運算、位運算,以及函數,等等,凡是用到這些運算,或者說操作,NumPy 都預設是逐元素進行的;這就叫“廣播”。

從前面的例子來看,進行逐元素乘法的 a、b 兩個數組,既可以是大小相同的多元數組,也可以其中一個是标量、另一個是數組,甚至可以是兩個大小不同的數組,為了使得“廣播”的語意明确、結果清晰,其中較小的那個數組就可以被擴充為較大數組的大小。

本小節代碼來自《NumPy 快速入門》

建立數組(array)的方式有不少,其中最自然的一種方式就是通過清單來生成數組。

要使用 NumPy 子產品,首先我們要在目前的 Python 環境中導入 NumPy,同時為了便于之後的引用,我們将其重命名為<code>np</code>:

文章前面我們提到過,NumPy 的多元數組和 Python 内置的清單長得很像,估計是表親還是啥的。并且嵌套的清單在一定程度上也能夠實作多元數組的功能,是以 NumPy 也很人性化的提供了接口,可以将現成的清單轉換為我們要的多元數組。

其中,多元數組的<code>dtype</code>屬性指明了多元數組中元素的類型。當然也可以将清單變量作為參數:

要注意的是,通過這種方式建立數組,經常犯的一個錯誤是缺少了清單的方括号,這樣參數就不再是一個清單,而是好幾個獨立的參數了:

實際上不僅是清單,同為 Python 中的序列類型,清單的親兄弟元組也可以起到相同的作用:

同時,使用<code>array</code>建立數組時,如果提供的序列對象是嵌套的,NumPy 還可以直接據此生成二維、三維甚至更高維的多元數組:

還能在建立數組的同時顯式指定資料類型:

一般來說,很多時候我們都是知道多元數組的大小,但不知道其元素具體的值,是以 NumPy 提供了一些函數,可以建立以占位符初始化的固定大小的多元數組。其中,<code>zeros</code>建立的是全為 0 的多元數組,<code>ones</code>建立的時候全為 1 的多元數組,而<code>empty</code>建立的則是随機初始值的多元數組,數組大小由一個序列(即清單或元組,建議使用元組)參數給定。并且這幾種方式的預設類型都是<code>flloat64</code>。

NumPy 還提供了一個類似于 Python 内置的<code>range</code>的函數<code>arange</code>,用以建立一個由等差序列組成的數組:

可以看到,<code>arange</code>的參數還可以是浮點數,與<code>range</code>略有不同。其他的特性都差不多,不再贅述。

由于浮點精度的原因,使用<code>arange</code>建立浮點數組時,我們不能保證得到我們預期大小的數組,是以這個時候就建議使用<code>linspace</code>這個函數。<code>linspace</code>與<code>arange</code>的差別就在于它們的第三個參數:前者指定的是最終得到的元素個數,而後者指定的則是元素間的步長。

多元數組是 NumPy 中最主要的對象。其實就是一個由同種元素組成的元素表,可以由元組進行索引。在 NumPy 中,次元又被稱作“軸(axe)”。

注意,在繼續介紹數組的各種屬性之前,我們要差別開“數組的次元”和“數組某個軸上的次元”。

“數組的次元”指的是數組的“軸”數,用次元空間的概念來了解,也就是數組能夠在多少個方向上具有坐标。比如一維的線性空間中,數組就隻能在 x 方向上具有坐标;對于二維的平面空間,數組就在 x 和 y 兩個方向上具有坐标。

而“數組軸的次元”則是指的在某個特定的方向上,數組可以有幾個刻度,或者說“層次”。比如一維數組<code>[0,1,2]</code>就是在 x 方向上具有 3 個層次;二維數組<code>[[0,1,2],[2,3,4]]</code>則是在 x 方向上具有 2 個層次,每個層次都是一個在 y 方向上的三個層次的一維數組,在 y 方向上具有 3 個層次,每個層次都是一個在 x 方向上具有兩個層次的一維數組。

下面介紹 NumPy 多元數組的基本屬性:

ndim

<code>ndim</code>即“n dimension”的簡寫。該屬性訓示的是多元數組的維數,或者說是“軸數”。

shape

字面意思。這個屬性訓示的是多元數組整體的次元,或者說是多元數組的“形狀”。是一個整型元組,每一個元素都對應與相應軸上的維數。對 n 行 m 列的矩陣而言,它的<code>shape</code>就是<code>(n,m)</code>。<code>shape</code>的元素個數等于多元數組的軸數。

size

多元數組中元素的總個數。等于<code>shape</code>中各元素之積。

dtype

<code>dtype</code>實際上是“data type”的簡寫,意味着它訓示的是多元數組中元素的資料類型。

itemsize

多元數組中,每個元素的位元組大小。等效于<code>dtype.itemsize</code>。

data

訓示的是包含多元數組中元素實際記憶體的緩沖區。通常用不到。

數組在列印的時候長得跟嵌套清單差不多,但其排布都要遵循以下規律:

最後一個軸從左往右列印;

次後軸從上往下列印;

剩下的軸都從上往下列印,每部分由一個空行隔開。

也就是說,一維數組按行列印,二維數組按矩陣形式列印,三維及更高維數組會列印成矩陣清單。

另外,為了更靈活地使用多元數組,NumPy 還提供了<code>reshape</code>方法,可以将多元數組重整為某個大小。

比如在圖像識别領域,就需要圖像作為機器學習輸入資料,而實用的機器學習應用圖像來源又是不确定的,是以圖像的像素陣列大小不一定一緻。

使用<code>reshape</code>要求參數乘積與被重整的數組元素個數相同:

并且<code>reshape</code>還允許預設 1 個參數(用<code>-1</code>占位),它會根據數組元素的總數和提供的其他參數自動求出一個合适的值,進而得到新的大小的數組:

本節參考自《NumPy 索引》。示例代碼多來自《NumPy 索引》。

所謂索引,在 NumPy 中指的是任何用中括号來擷取數組元素值的行為。

NumPy 中,索引的方式有很多,這既使得 NumPy 更加強大靈活,也帶來了難于辨析的問題。

最簡單的一種索引方式就是單個索引。對于一維數組,我們可以像對 Python 中的序列一樣進行索引:

對于二維和更高維的數組,我們可以在同一個中括号内直接索引:

而不必像對嵌套序列一樣,用多個中括号分别索引:

當給出的索引少于數組維數(軸數)時,得到的會是一個數組對象:

而對于傳回的這個數組,我們又可以繼續索引,是以對于多元數組而言,也可以使用嵌套序列的索引方式,即使用多個中括号(但這種方式比一次索引要更低效,是以不推薦):

此外,NumPy 多元數組又可以像清單一樣,進行切片(用冒号“:”):

數組也可以用另一個數組來索引:

也可以用多個數組來進行索引:

還可以用布爾數組作為掩碼,篩選數組元素:

對應于布爾數組中為“真”的元素就被篩選出來了。

本小節參考自《NumPy 廣播》和《NumPy 廣播機制詳解》。圖檔來自《NumPy 廣播機制詳解》。

NumPy 中,各種運算預設都是“逐元素”進行的。最簡單的例子就是兩個明顯大小相同的數組運算:

但是逐元素計算有一個問題:對于形狀不太像的數組怎麼辦呢?比如下面這個數組和标量的乘法運算:

按“逐元素”運算的預期,顯然我們是期望把這個标量與數組的每一個元素相乘,來得到一個新的數組。但是 NumPy 懂得這其中的邏輯嗎?诶?好像還真的懂。

既然我們号稱 NumPy 可以用自然的方式來表達數學公式,肯定不能把一個簡單的标量乘向量弄得太複雜,同樣是直接乘就可以了,得到的結果與之前兩個大小相同的數組直接相乘是一樣的:

在這裡邊,标量<code>scalar</code>就好像被擴充為了一個跟<code>multi_array</code>大小相當的數組一樣。

第79天:資料分析之 Numpy 初步

在 NumPy 的廣播機制中,有一個很重要的概念叫做“相容的形狀”。隻有當兩個數組具有“相容的形狀”時,“廣播”才能起作用;否則抛出異常<code>ValueError: operands could not be broadcast together with shapes xx yy</code>。

所謂“相容的形狀”,指的是參與運算的這兩個數組各個次元要麼 1)相等;要麼 2)其中一個數組的對應次元為 1(不存在的次元也是 1)。

而 NumPy 比較各個次元的順序是從後往前,一次比較,就相當于把參與運算的數組形狀右對齊,然後若相等就再往前看,若其中一個為 1 就将其在這個次元上擴充到更高的次元,直到第一個次元。

下面是對于上述規則一個更清晰的表述描述:

但是像這樣的兩個數組就無法通過“廣播”實作逐元素運算了:

對于一個多元數組和一個一維數組的運算,執行個體如下:

廣播圖示如下:

第79天:資料分析之 Numpy 初步

對齊之後,次元不相容是萬萬不行的:

圖示如下:

第79天:資料分析之 Numpy 初步

當然我們也可以通過<code>reshape</code>重整<code>b</code>數組的形狀,以适應廣播的要求:

這樣就沒問題了。

看了那麼多枯燥的原理,我們接下來輕松一下,看看 NumPy 還能幹什麼。

喜歡拍照的同學都知道,圖檔是由“像素(pixel)”構成的。所謂“像素”,英文 pixel 就是“picture element”的簡寫,指的是“構成圖像的元素”。

實際上我們可以把一張圖檔縱橫切分成很多小塊,這些最基本的小方格就是構成多姿多彩的數字圖像世界的一磚一瓦。作為二維的圖像,它們像素的排布是不是跟二維數組很像?诶~ 對了,我們可以用一個很常用的圖形庫<code>matplotlib</code>來讀取圖像,得到的實際上就是一個 NumPy 二維數組:

第79天:資料分析之 Numpy 初步

取下一半圖像:

第79天:資料分析之 Numpy 初步

取右邊一半的圖像:

第79天:資料分析之 Numpy 初步

本文對我們以後經常會用到的 NumPy 子產品進行了簡要的介紹。雖然本文篇幅已經很長,但對于 NumPy 相關知識的講解依然隻是滄海一粟。

本文的目的在于給讀者提供一個粗略的印象,記不住沒關系,希望在以後的使用中讀者能夠熟練掌握 NumPy 的使用。

示例代碼:Python-100-days-day077

NumPy 官網

何為 NumPy

NumPy 快速入門

NumPy 索引

NumPy 廣播

NumPy 廣播機制詳解

關注公衆号:python技術,回複"python"一起學習交流

第79天:資料分析之 Numpy 初步

作者:純潔的微笑

出處:www.ityouknow.com

資源:微信搜【純潔的微笑】關注我,回複 【程式員】【面試】【架構師】有我準備的一線程式必備計算機書籍、大廠面試資料和免費電子書。 一共1024G的資料,希望可以幫助大家提升技術和能力。

本文如對您有幫助,還請多幫 【推薦】 下此文。

點我了解:Tooool-程式員一站式導航網站