NumPy之:NumPy簡介教程

flydean 發表於 2021-04-21

簡介

NumPy是一個開源的Python庫,主要用在資料分析和科學計算,基本上可以把NumPy看做是Python資料計算的基礎,因為很多非常優秀的資料分析和機器學習框架底層使用的都是NumPy。比如:Pandas, SciPy, Matplotlib, scikit-learn, scikit-image 等。

NumPy庫主要包含多維陣列和矩陣資料結構。 它為ndarray(一個n維陣列物件)提供了對其進行有效操作的方法。 NumPy可以用於對陣列執行各種數學運算。 並且提供了可在這些陣列和矩陣上執行的龐大的高階數學函式庫。

安裝NumPy

有很多方式可以按照NumPy:

pip install numpy

如果你使用的是conda,那麼可以:

conda install numpy

或者直接使用Anaconda. 它是一系列資料分析包的集合。

Array和List

Python中有一個資料型別叫做List,list中可以儲存不同種類的物件。在應用程式中這樣做沒有什麼問題,但是如果是在科學計算中,我們希望一個陣列中的元素型別必須是一致的,所以有了NumPy中的Array。

NumPy可以快速的建立Array,並且對其中的資料進行操作。

NumPy中的Array要比Python中的List要快得多,並且佔用更少的記憶體空間。

看下兩者之間的效能差異:

In [1]: import numpy as np
   ...: my_arr = np.arange(1000000)
   ...: my_list = list(range(1000000))
   ...: %time for _ in range(10): my_arr2 = my_arr * 2
   ...: %time for _ in range(10): my_list2 = [x * 2 for x in my_list]
   ...:
CPU times: user 12.3 ms, sys: 7.88 ms, total: 20.2 ms
Wall time: 21.4 ms
CPU times: user 580 ms, sys: 172 ms, total: 752 ms
Wall time: 780 ms

上面的例子對一個包含一百萬的資料進行乘2操作,可以看到,使用NumPy的效率是Python的幾十倍,如果在大型資料專案中這個效率會造成非常大的效能影響。

建立Array

上面的例子中,我們已經建立了一個array,使用的是np.arange方法。

我們還可以通過List來建立Array,List可以是一維列表,也可以是多維列表:

>>> a = np.array([1, 2, 3, 4, 5, 6])

>>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

和List一樣,Array也可以通過index來訪問:

>>> print(a[0])
[1 2 3 4]

接下來我們介紹幾個常用的名詞:

  • vector — 表示的是一維陣列
  • matrix — 表示的是二維陣列
  • tensor — 表示的是三維或者更高維度的陣列

在NumPy中維度也被稱之為 axes

下面我們來看下其他幾種建立Array的方法:

最簡單的就是np.array,之前的例子中我們已經提到過了。

如果要快速的建立都是0 的陣列,我們可以使用zeros:

>>> np.zeros(2)
array([0., 0.])

或者都填充為1:

>>> np.ones(2)
array([1., 1.])

還可以建立空的陣列:

In [2]: np.empty(2)
Out[2]: array([0.        , 2.00389455])

注意,empty方法中的內容並不一定是空的,而是隨機填充資料,所以我們在使用empty建立陣列之後,一定要記得覆蓋其中的內容。使用empty的好處就是建立的速度比較快。

還可以在range範圍內填充陣列:

In [3]: np.arange(10)
Out[3]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

可以指定間隔:

In [4]: np.arange(1,10,2)
Out[4]: array([1, 3, 5, 7, 9])

使用linspace可以建立等分的陣列:

In [5]: np.linspace(0, 10, num=5)
Out[5]: array([ 0. ,  2.5,  5. ,  7.5, 10. ])

預設情況下建立的陣列內容型別是np.float64,我們還可以將其切換成整數:np.int64

In [6]: x = np.ones(2, dtype=np.int64)

In [7]: x
Out[7]: array([1, 1])

Array操作

sort

我們可以使用sort對陣列進行排序:

In [8]: arr = np.array([2, 1, 5, 3, 7, 4, 6, 8])

In [10]: np.sort(arr)
Out[10]: array([1, 2, 3, 4, 5, 6, 7, 8])

sort是對Array中的元素進行排序, 除了sort之外還有其他的一些排序的方法。

還可以使用argsort,argsort是一種間接排序的方法,他返回的是排序好的原陣列的index:

In [11]: x = np.array([10, 5, 6])

In [12]: np.argsort(x)
Out[12]: array([1, 2, 0])

上面我們對array進行argsort,排序之後應該返回,5,6,10。 5的index是1,6 的index是2,10的index是0,所以返回1,2,0。

lexsort和argsort一樣都是間接排序法,返回的都是排序過後的index,不同是lexsort 可以進行多key的排序。

surnames =    ('Hertz',    'Galilei', 'Hertz')
first_names = ('Heinrich', 'Galileo', 'Gustav')
ind = np.lexsort((first_names, surnames))
ind
array([1, 2, 0])

上面的lexsort是先按照surnames排序,然後再按照first_names進行排序。

lexsort 的排序順序是從後到前。也就是最後一個傳入的key最先排序。

searchsorted用來查詢要插入元素的index值,舉個例子:

np.searchsorted([1,2,3,4,5], 3)
2
np.searchsorted([1,2,3,4,5], 3, side='right')
3
np.searchsorted([1,2,3,4,5], [-10, 10, 2, 3])
array([0, 5, 1, 2])

partition是對要排序的資料進行分割,舉個例子:

a = np.array([3, 4, 2, 1])
np.partition(a, 3)
array([2, 1, 3, 4])

第一個引數是一個Array,第二個引數是要分隔的基準元素,這個基準元素的位置和排序過後的位置是一樣的,其他的元素比基準元素小的放在前面,比基準元素大的放在後面。

還可以按照多個元素進行分割:

np.partition(a, (1, 3))
array([1, 2, 3, 4])

concatenate

concatenate用來連線多個陣列。

>>> a = np.array([1, 2, 3, 4])
>>> b = np.array([5, 6, 7, 8])

>>> np.concatenate((a, b))
array([1, 2, 3, 4, 5, 6, 7, 8])

還可以連線多維陣列:

>>> x = np.array([[1, 2], [3, 4]])
>>> y = np.array([[5, 6]])
>>> np.concatenate((x, y), axis=0)
array([[1, 2],
       [3, 4],
       [5, 6]])

統計資訊

ndarray.ndim 用來統計陣列的維數:

>>> array_example = np.array([[[0, 1, 2, 3],
...                            [4, 5, 6, 7]],
...
...                           [[0, 1, 2, 3],
...                            [4, 5, 6, 7]],
...
...                           [[0 ,1 ,2, 3],
...                            [4, 5, 6, 7]]])
>>> array_example.ndim
3

ndarray.size 用來統計陣列中的元素個數:

>>> array_example.size
24

ndarray.shape 輸出陣列的形狀:

>>> array_example.shape
(3, 2, 4)

說明上面的陣列是一個3 * 2 * 4 的陣列。

reshape

使用reshape可以重新構造一個陣列。

>>> a = np.arange(6)
>>> print(a)
[0 1 2 3 4 5]

>>> b = a.reshape(3, 2)
>>> print(b)
[[0 1]
 [2 3]
 [4 5]]

上面我們將一個一維的陣列轉成了一個3* 2 的陣列。

reshape還可以接受多個引數:

>>> numpy.reshape(a, newshape=(1, 6), order='C')
array([[0, 1, 2, 3, 4, 5]])

第一個引數是要重構的陣列,第二個引數新的shape,order可以取三個值,C,F或者A。

C表示按照C的index方式進行排序,F表示按照Fortran的index方式進行排序。A表示自動選擇。

在Fortran中,當移動儲存在記憶體中的二維陣列的元素時,第一個索引是變化最快的索引。 當第一個索引更改時移動到下一行時,矩陣一次儲存一列。另一方面,在C中,最後一個索引變化最快。

增加維度

np.newaxis可以給現有的陣列增加一個維度:

>>> a = np.array([1, 2, 3, 4, 5, 6])
>>> a.shape
(6,)

>>> a2 = a[np.newaxis, :]
>>> a2.shape
(1, 6)

>>> col_vector = a[:, np.newaxis]
>>> col_vector.shape
(6, 1)

還可以使用expand_dims來指定axis的位置:

>>> b = np.expand_dims(a, axis=1)
>>> b.shape
(6, 1)

>>> c = np.expand_dims(a, axis=0)
>>> c.shape
(1, 6)

index和切片

陣列的index和切片跟Python中的list是類似的:

>>> data = np.array([1, 2, 3])

>>> data[1]
2
>>> data[0:2]
array([1, 2])
>>> data[1:]
array([2, 3])
>>> data[-2:]
array([2, 3])

除此之外,陣列還支援更多更強大的index操作:

>>> a = np.array([[1 , 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

>>> print(a[a < 5])
[1 2 3 4]

上面我們找出了a中所有元素小於5的值。

In [20]: a<5
Out[20]:
array([[ True,  True,  True,  True],
       [False, False, False, False],
       [False, False, False, False]])

可以看到a< 5 其實返回的也是一個陣列,這個陣列的元素shape和原陣列是一樣的,只不過裡面的值是true和false,表示是否應該被選擇出來。

同樣的,我們可以挑出所有大於5的元素:

>>> five_up = (a >= 5)
>>> print(a[five_up])
[ 5  6  7  8  9 10 11 12]

選出所有可以被2整除的數:

>>> divisible_by_2 = a[a%2==0]
>>> print(divisible_by_2)
[ 2  4  6  8 10 12]

還可以使用 & 和 | 運算子:

>>> c = a[(a > 2) & (a < 11)]
>>> print(c)
[ 3  4  5  6  7  8  9 10]

還可以使用nonzero來列印出滿足條件的index資訊:

In [23]: a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

In [24]: b = np.nonzero(a < 5)

In [25]: b
Out[25]: (array([0, 0, 0, 0]), array([0, 1, 2, 3]))
  
>>> print(a[b])
[1 2 3 4]

上面返回的元組中,第一個值表示的是行號,第二個值表示的是列。

從現有資料中建立Array

我們可以使用 slicing , indexing,np.vstack(),np.hstack(),np.hsplit(),.view(),copy() 來從現有資料中建立Array。

前面的例子中,我們看到可以使用List和切片來建立新的陣列:

>>> a = np.array([1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
>>> arr1 = a[3:8]
>>> arr1
array([4, 5, 6, 7, 8])

兩個現有的陣列可以進行垂直或者水平堆疊:

>>> a1 = np.array([[1, 1],
...                [2, 2]])

>>> a2 = np.array([[3, 3],
...                [4, 4]])

>>> np.vstack((a1, a2))
array([[1, 1],
       [2, 2],
       [3, 3],
       [4, 4]])

>>> np.hstack((a1, a2))
array([[1, 1, 3, 3],
       [2, 2, 4, 4]])

使用hsplit 可以將大的陣列分割成為幾個小的陣列:

>>> x = np.arange(1, 25).reshape(2, 12)
>>> x
array([[ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]])

>>> np.hsplit(x, 3)
[array([[1,  2,  3,  4],
        [13, 14, 15, 16]]), array([[ 5,  6,  7,  8],
        [17, 18, 19, 20]]), array([[ 9, 10, 11, 12],
        [21, 22, 23, 24]])]

算數運算

array的加法:

>>> data = np.array([1, 2])
>>> ones = np.ones(2, dtype=int)
>>> data + ones
array([2, 3])

其他的運算:

>>> data - ones
array([0, 1])
>>> data * data
array([1, 4])
>>> data / data
array([1., 1.])

array求和:

>>> a = np.array([1, 2, 3, 4])

>>> a.sum()
10

如果是求和多維陣列的話,需要指定維度:

>>> b = np.array([[1, 1], [2, 2]])
>>> b.sum(axis=0)
array([3, 3])

>>> b.sum(axis=1)
array([2, 4])

其他有用操作

這裡列出了其他的有用操作:

>>> data.max()
2.0
>>> data.min()
1.0
>>> data.sum()
3.0

對於二維陣列來說,sum預設會求和所有的元素,min也會從所有元素中查詢最小的:

>>> a = np.array([[0.45053314, 0.17296777, 0.34376245, 0.5510652],
...               [0.54627315, 0.05093587, 0.40067661, 0.55645993],
...               [0.12697628, 0.82485143, 0.26590556, 0.56917101]])

>>> a.sum()
4.8595784

>>> a.min()
0.05093587

我們還可以指定維度:

>>> a.min(axis=0)
array([0.12697628, 0.05093587, 0.26590556, 0.5510652 ])

矩陣

矩陣就是 2 * 2 的陣列:

>>> data = np.array([[1, 2], [3, 4]])
>>> data
array([[1, 2],
       [3, 4]])

矩陣同樣可以進行統計操作:

>>> data.max()
4
>>> data.min()
1
>>> data.sum()
10

預設情況是累加所有的元素,我們也可以指定特定的累加維度:

>>> data.max(axis=0)
array([3, 4])
>>> data.max(axis=1)
array([2, 4])

矩陣的運算:

>>> data = np.array([[1, 2], [3, 4]])
>>> ones = np.array([[1, 1], [1, 1]])
>>> data + ones
array([[2, 3],
       [4, 5]])

如果是多維的和低維的進行運算,那麼將會使用內建的broadcast機制,將低維的進行廣播:

>>> data = np.array([[1, 2], [3, 4], [5, 6]])
>>> ones_row = np.array([[1, 1]])
>>> data + ones_row
array([[2, 3],
       [4, 5],
       [6, 7]])

生成隨機數

在機器學習中,生成隨機數是一個非常重要的功能。我們看下如何在Numpy中生成隨機數。

>>> rng = np.random.default_rng(0)
>>> rng.random(3)
array([0.63696169, 0.26978671, 0.04097352])

>>> rng.random((3, 2))
array([[0.01652764, 0.81327024],
       [0.91275558, 0.60663578],
       [0.72949656, 0.54362499]])  # may vary
       
>>> rng.integers(5, size=(2, 4))
array([[2, 1, 1, 0],
       [0, 0, 0, 4]])  # may vary

unique

np.unique可以統計陣列的唯一值:

>>> a = np.array([11, 11, 12, 13, 14, 15, 16, 17, 12, 13, 11, 14, 18, 19, 20])

>>> unique_values = np.unique(a)
>>> print(unique_values)
[11 12 13 14 15 16 17 18 19 20]

還可以返回index或者count:

>>> unique_values, indices_list = np.unique(a, return_index=True)
>>> print(indices_list)
[ 0  2  3  4  5  6  7 12 13 14]
>>> unique_values, occurrence_count = np.unique(a, return_counts=True)
>>> print(occurrence_count)
[3 2 2 2 1 1 1 1 1 1]

對矩陣也適用:

>>> a_2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [1, 2, 3, 4]])

>>> unique_values = np.unique(a_2d)
>>> print(unique_values)
[ 1  2  3  4  5  6  7  8  9 10 11 12]

如果想得到唯一的行或者列,可以傳入axis引數:

>>> unique_rows = np.unique(a_2d, axis=0)
>>> print(unique_rows)
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]

矩陣變換

我們可以使用transpose來把矩陣的行和列進行調換:

>>> arr = np.arange(6).reshape((2, 3))
>>> arr
array([[0, 1, 2],
       [3, 4, 5]])

>>> arr.transpose()
array([[0, 3],
       [1, 4],
       [2, 5]])

反轉陣列

使用flip可以反轉陣列:

>>> arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])
>>> reversed_arr = np.flip(arr)
>>> print('Reversed Array: ', reversed_arr)
Reversed Array:  [8 7 6 5 4 3 2 1]

如果是2維的陣列:

>>> arr_2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

>>> reversed_arr = np.flip(arr_2d)
>>> print(reversed_arr)
[[12 11 10  9]
 [ 8  7  6  5]
 [ 4  3  2  1]]

預設會反轉行和列,我們也可以只反轉行或者列:

>>> reversed_arr_rows = np.flip(arr_2d, axis=0)
>>> print(reversed_arr_rows)
[[ 9 10 11 12]
 [ 5  6  7  8]
 [ 1  2  3  4]]

>>> reversed_arr_columns = np.flip(arr_2d, axis=1)
>>> print(reversed_arr_columns)
[[ 4  3  2  1]
 [ 8  7  6  5]
 [12 11 10  9]]

還可以只反轉一行或者一列:


>>> arr_2d[1] = np.flip(arr_2d[1])
>>> print(arr_2d)
[[ 1  2  3  4]
 [ 8  7  6  5]
 [ 9 10 11 12]]

>>> arr_2d[:,1] = np.flip(arr_2d[:,1])
>>> print(arr_2d)
[[ 1 10  3  4]
 [ 8  7  6  5]
 [ 9  2 11 12]]

flatten 和 ravel

flatten 可以將陣列變成一維的:

>>> x = np.array([[1 , 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

>>> x.flatten()
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12])

flatten之後的陣列和原陣列是無關的,我們修改flatten之後的陣列不會改變之前的陣列內容:

>>> a1 = x.flatten()
>>> a1[0] = 99
>>> print(x)  # Original array
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
>>> print(a1)  # New array
[99  2  3  4  5  6  7  8  9 10 11 12]

但是如果使用ravel,對新陣列的修改同樣也會改變原始陣列:

>>> a2 = x.ravel()
>>> a2[0] = 98
>>> print(x)  # Original array
[[98  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
>>> print(a2)  # New array
[98  2  3  4  5  6  7  8  9 10 11 12]

save 和 load

NumPy 的物件可以通過save和load存放到檔案和從檔案中載入:

>>> a = np.array([1, 2, 3, 4, 5, 6])

>>> np.save('filename', a)

>>> b = np.load('filename.npy')

如果想以文字的方式來儲存,那麼可以使用np.savetxt:

>>> csv_arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])

>>> np.savetxt('new_file.csv', csv_arr)

>>> np.loadtxt('new_file.csv')
array([1., 2., 3., 4., 5., 6., 7., 8.])

CSV

NumPy有專門的方法來對CSV檔案進行操作:

>>> import pandas as pd

>>> # If all of your columns are the same type:
>>> x = pd.read_csv('music.csv', header=0).values
>>> print(x)
[['Billie Holiday' 'Jazz' 1300000 27000000]
 ['Jimmie Hendrix' 'Rock' 2700000 70000000]
 ['Miles Davis' 'Jazz' 1500000 48000000]
 ['SIA' 'Pop' 2000000 74000000]]

>>> # You can also simply select the columns you need:
>>> x = pd.read_csv('music.csv', usecols=['Artist', 'Plays']).values
>>> print(x)
[['Billie Holiday' 27000000]
 ['Jimmie Hendrix' 70000000]
 ['Miles Davis' 48000000]
 ['SIA' 74000000]]

本文已收錄於 http://www.flydean.com/01-python-numpy-basic/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!