1. 程式人生 > >資料科學 IPython 筆記本 9.4 NumPy 陣列的基礎

資料科學 IPython 筆記本 9.4 NumPy 陣列的基礎

9.4 NumPy 陣列的基礎

本節是《Python 資料科學手冊》(Python Data Science Handbook)的摘錄。

譯者:飛龍

協議:CC BY-NC-SA 4.0

Python 中的資料操作幾乎與 NumPy 陣列操作同義:即使是像 Pandas 這樣的新工具也是圍繞 NumPy 陣列構建的。本節將介紹幾個示例,使用 NumPy 陣列操作來訪問資料和子陣列,以及拆分,重塑和連線陣列。

雖然這裡顯示的操作型別可能看起來有點枯燥和怪異,但它們構成了本書中使用的許多其他示例的積木。儘快瞭解它們!

我們將在這裡介紹幾類基本陣列操作:

  • 陣列的屬性:確定陣列的大小,形狀,記憶體消耗和資料型別
  • 陣列的索引:獲取和設定各個陣列元素的值
  • 陣列切片:在較大的陣列中獲取和設定較小的子陣列
  • 陣列的重塑:更改給定陣列的形狀
  • 陣列的連線和分割:將多個數組合併為一個數組,並將一個數組拆分為多個數組

NumPy 陣列屬性

首先讓我們討論一些有用的陣列屬性。

我們首先定義三個隨機陣列,一維,二維和三維陣列。我們將使用 NumPy 的隨機數生成器,並使用設定值設定種子,來確保每次執行此程式碼時,生成相同的隨機陣列:

import numpy as np
np.random.seed(0)  # 用於可復現的種子
x1 = np.random.randint(10, size=6) # 一維陣列 x2 = np.random.randint(10, size=(3, 4)) # 二維陣列 x3 = np.random.randint(10, size=(3, 4, 5)) # 三維陣列

每個陣列都有屬性ndim(維數),shape(每個維度的大小)和size(陣列的總大小):

print("x3 ndim: ", x3.ndim)
print("x3 shape:", x3.shape)
print("x3 size: ", x3.size)

'''
x3 ndim:  3
x3 shape: (3, 4, 5)
x3 size:  60
'''

另一個有用的屬性是dtype,陣列的資料型別(我們之前在“瞭解 Python 中的資料型別”中討論過):

print("dtype:", x3.dtype)

# dtype: int64

其他屬性包括itemsize,它列出每個陣列元素的大小(以位元組為單位)和nbytes,它列出了陣列的總大小(以位元組為單位):

print("itemsize:", x3.itemsize, "bytes")
print("nbytes:", x3.nbytes, "bytes")

'''
itemsize: 8 bytes
nbytes: 480 bytes
'''

一般來說,我們希望nbytes等於itemsize乘以size

陣列索引:訪問單個元素

如果你熟悉 Python 的標準列表索引,NumPy 中的索引將會非常眼熟。

在一維陣列中,可以通過在方括號中指定所需的索引(從零開始計算),來訪問第i值,就像使用 Python 列表一樣:

x1

# array([5, 0, 3, 3, 7, 9])

x1[0]

# 5

x1[4]

# 7

要從陣列的末尾開始索引,可以使用負索引:

x1[-1]

# 9


x1[-2]

# 7

在多維陣列中,可以使用以逗號分隔的索引元組來訪問專案:

x2

'''
array([[3, 5, 2, 4],
       [7, 6, 8, 8],
       [1, 6, 7, 7]])
'''

x2[0, 0]

# 3

x2[2, 0]

# 1

x2[2, -1]

# 7

也可以使用以上任何索引表示法修改值:

x2[0, 0] = 12
x2

'''
array([[12,  5,  2,  4],
       [ 7,  6,  8,  8],
       [ 1,  6,  7,  7]])
'''

請記住,與 Python 列表不同,NumPy 陣列具有固定型別。

這意味著,例如,如果你嘗試將浮點值插入整數陣列,則該值將被靜默截斷。 不要意識不到這種行為!

x1[0] = 3.14159  # 會截斷!
x1

# array([3, 0, 3, 3, 7, 9])

陣列切片:訪問子陣列

就像我們可以使用方括號來訪問單個數組元素一樣,我們也可以使用它們以及由冒號(:)標記的切片表示法,來訪問子陣列。

NumPy 切片語法遵循標準 Python 列表的語法;要訪問陣列x的切片,請使用:

x[start:stop:step]

如果其中任何一個未指定,它們預設為start = 0stop = 維度大小step = 1

我們看一下如何在一維和多維中訪問子陣列。

一維子陣列

x = np.arange(10)
x

# array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

x[:5]  # 前五個元素

# array([0, 1, 2, 3, 4])

x[5:]  # 下標 5 後面的元素

# array([5, 6, 7, 8, 9])

x[4:7]  # 中間的子陣列

# array([4, 5, 6])

x[::2]  # 每個其它元素

# array([0, 2, 4, 6, 8])

x[1::2]  # 每個其它元素,起始於下標 1

# array([1, 3, 5, 7, 9])

一個可能令人困惑的情況是step值為負。在這種情況下,交換startstop的預設值。這成為反轉陣列的便捷方法:

x[::-1]  # 所有元素反過來

# array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])

x[5::-2]  # 反向的每個其它元素,起始於下標 5

# array([5, 3, 1])

多維子陣列

多維切片以相同的方式工作,多個切片用逗號分隔。例如:

x2

'''
array([[12,  5,  2,  4],
       [ 7,  6,  8,  8],
       [ 1,  6,  7,  7]])
'''

x2[:2, :3]  # 兩行三列

'''
array([[12,  5,  2],
       [ 7,  6,  8]])
'''

x2[:3, ::2]  # 所有行,每個其它列

'''
array([[12,  2],
       [ 7,  8],
       [ 1,  7]])
'''

最後,子陣列的維度甚至可以一起反轉:

x2[::-1, ::-1]

'''
array([[ 7,  7,  6,  1],
       [ 8,  8,  6,  7],
       [ 4,  2,  5, 12]])
'''

訪問陣列的行和列

一個常用的例程是訪問陣列的單個行或列。

這可以通過組合索引和切片來完成,使用由單個冒號(:)標記的空切片:

print(x2[:, 0])  # x2 的第一列 

# [12  7  1]

print(x2[0, :])  # x2 的第一行

# [12  5  2  4]

在訪問行的情況下,可以省略空切片來獲得更緊湊的語法:

print(x2[0])  # 等價於 x2[0, :]

# [12  5  2  4]

作為無副本檢視的子陣列

陣列切片的一個重要且非常有用的事情,是它們返回檢視而不是陣列資料的副本。這是 NumPy 陣列切片與 Python 列表切片的不同之處:在列表中,切片是副本。

考慮我們之前的二維陣列:

print(x2)

'''
[[12  5  2  4]
 [ 7  6  8  8]
 [ 1  6  7  7]]
'''

讓我們從中提取2x2子陣列:

x2_sub = x2[:2, :2]
print(x2_sub)

'''
[[12  5]
 [ 7  6]]
'''

現在,如果我們修改這個子陣列,我們會看到原始陣列已經改變了!注意:

x2_sub[0, 0] = 99
print(x2_sub)

'''
[[99  5]
 [ 7  6]]
'''

print(x2)

'''
[[99  5  2  4]
 [ 7  6  8  8]
 [ 1  6  7  7]]
'''

這種預設行為實際上非常有用:這意味著當我們處理大型資料集時,我們可以訪問和處理這些資料集的各個部分,而無需複製底層資料緩衝區。

建立陣列的副本

儘管陣列檢視具有很好的特性,但有時顯式複製陣列或子陣列中的資料也很有用。 使用copy()方法可以很容易地做到:

x2_sub_copy = x2[:2, :2].copy()
print(x2_sub_copy)

'''
[[99  5]
 [ 7  6]]
'''

如果我們現在修改此子陣列,則不會觸及原始陣列:

x2_sub_copy[0, 0] = 42
print(x2_sub_copy)

'''
[[42  5]
 [ 7  6]]
'''

print(x2)

'''
[[99  5  2  4]
 [ 7  6  8  8]
 [ 1  6  7  7]]
'''

陣列的形狀調整

另一種有用的操作型別是陣列的形狀調整。最靈活的方法是使用reshape方法。例如,如果要將數字 1 到 9 放在3x3網格中,則可以執行以下操作:

grid = np.arange(1, 10).reshape((3, 3))
print(grid)

'''
[[1 2 3]
 [4 5 6]
 [7 8 9]]
'''

請注意,為此,初始陣列的大小必須匹配形狀調整的陣列的大小。在可能的情況下,reshape方法將使用初始陣列的非副本檢視,但對於非連續的記憶體緩衝區,情況並非總是如此。

另一種常見的形狀調整是將一維陣列轉換為二維行或列矩陣。這可以使用reshape方法完成,或者通過在切片操作中使用newaxis關鍵字更容易地完成:

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

# 通過 reshape 來建立行向量
x.reshape((1, 3))

# array([[1, 2, 3]])

# 通過 newaxis 來建立行向量
x[np.newaxis, :]

# array([[1, 2, 3]])

# 通過 reshape 來建立列向量 
x.reshape((3, 1))

'''
array([[1],
       [2],
       [3]])
'''

# 通過 newaxis 來建立列向量 
x[:, np.newaxis]

'''
array([[1],
       [2],
       [3]])
'''

我們將在本書的其餘部分經常看到這種型別的轉換。

陣列的連線和分割

所有上述例程都適用於單個數組。也可以將多個數組合併為一個,並與之相反,將單個數組拆分為多個數組。我們將在這裡看看這些操作。

陣列的連線

在 NumPy 中連線兩個陣列,主要是使用例程np.concatenatenp.vstacknp.hstack完成的。

np.concatenate將陣列元組或列表作為它的第一個引數,我們可以在這裡看到:

x = np.array([1, 2, 3])
y = np.array([3, 2, 1])
np.concatenate([x, y])

# array([1, 2, 3, 3, 2, 1])

你還可以同時連線兩個以上的陣列:

z = [99, 99, 99]
print(np.concatenate([x, y, z]))

# [ 1  2  3  3  2  1 99 99 99]

它也可以用於二維陣列:

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

# 沿第一個軸連線
np.concatenate([grid, grid])

'''
array([[1, 2, 3],
       [4, 5, 6],
       [1, 2, 3],
       [4, 5, 6]])
'''

# 沿第二個軸連線(下標從零開始)
np.concatenate([grid, grid], axis=1)

'''
array([[1, 2, 3, 1, 2, 3],
       [4, 5, 6, 4, 5, 6]])
'''

對於處理混合維度的陣列,使用np.vstack(垂直堆疊)和np.hstack(水平堆疊)函式更清楚:

x = np.array([1, 2, 3])
grid = np.array([[9, 8, 7],
                 [6, 5, 4]])

# 垂直堆疊陣列
np.vstack([x, grid])

'''
array([[1, 2, 3],
       [9, 8, 7],
       [6, 5, 4]])
'''

# 水平堆疊陣列
y = np.array([[99],
              [99]])
np.hstack([grid, y])

'''
array([[ 9,  8,  7, 99],
       [ 6,  5,  4, 99]])
'''

類似地,np.dstack將沿第三個軸堆疊陣列。

陣列的分割

連線的反面是分割,它由函式np.splitnp.hsplitnp.vsplit實現。 對於其中的每一個,我們可以傳遞索引列表來提供分割點:

x = [1, 2, 3, 99, 99, 3, 2, 1]
x1, x2, x3 = np.split(x, [3, 5])
print(x1, x2, x3)

# [1 2 3] [99 99] [3 2 1]

請注意,N個分割點會導致N+1個子陣列。相關函式np.hsplitnp.vsplit是相似的:

grid = np.arange(16).reshape((4, 4))
grid

'''
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])
'''

upper, lower = np.vsplit(grid, [2])
print(upper)
print(lower)

'''
[[0 1 2 3]
 [4 5 6 7]]
[[ 8  9 10 11]
 [12 13 14 15]]
'''

left, right = np.hsplit(grid, [2])
print(left)
print(right)

'''
[[ 0  1]
 [ 4  5]
 [ 8  9]
 [12 13]]
[[ 2  3]
 [ 6  7]
 [10 11]
 [14 15]]
'''

類似地,np.dsplit將沿第三個軸分割陣列。