1. 程式人生 > >《Python資料科學手冊》第二章 Numpy入門2.1—2.3

《Python資料科學手冊》第二章 Numpy入門2.1—2.3

 2.1 理解Python中的資料型別

        Python的使用者被其易用性所吸引,其中一個易用之處就在於動態輸入,即在Python中,型別是動態推斷的。這意味著可以將任何型別的資料指定給任何變數。但是這種型別靈活性也指出了一個事實: Python 變數不僅是它們的值,還包括了關於值的型別的一些額外資訊 。

       C語言整型本質上是對應某個記憶體位置的標籤,裡面儲存的位元組會編碼成整型。而Python的整型其實是一個指標,只向包含這個Python物件所有資訊的某個記憶體位置,其中包括可以轉換成整型的位元組。Python 型別中的這些額外資訊也

會成為負擔,在多個物件組合的結構體中尤其明顯。

      Python中的標準可變多元素容器是列表。 可用如下方式建立一個整型值列表 

1 L=list(range(10))
2 print(L)

輸出結果為

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

或者建立一個字串列表

1 L=list(range(10))
2 L2=[str(c) for c in L]
3 print(L2)

其輸出結果為

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

 因為Python的動態型別特性,甚至可以建立一個異構的列表.

L3=[True,"2",3.0,4]

 Python的array陣列模組提供了陣列型資料的有效儲存,而Numpy包中的ndarry物件為該資料加上了高效的操作。

建立Numpy陣列的方法,Numpy要求陣列必須包含同一型別資料。如果型別不匹配,Numpy將會向上轉換(如果可行)。

import numpy as np
np.array([1,4,2,5,3])

如果要設定陣列的資料型別,可以用dtype關鍵字:

np.array([1,2,3,4],dtype='
float32')

Numpy陣列可以被指定為多維的。以下是用列表的列表初始化多維陣列的一種方法

np.array([range(i,i+3) for i in [2,4,6]])
# range(start, stop[, step])  range(起,終,步長)

其輸出結果如下,內層列表被當做二維陣列的行

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

在面對大型陣列的時候,用Numpy內建的方法從頭建立陣列是一種更高效的方法。

# 建立一個長度為10的陣列,陣列的值均為0
np.zeros(10,dtype=int)
#建立一個3×5的浮點陣列,陣列的值均為3.14,3行5列
np.full((3,5),3.14)
#建立一個3×3的、[0,10)區間的隨機整型陣列
np.array.randint(0,10,(3,3))
#建立一個3×3的單位矩陣
np.eye(3)

當用Numpy構建一個數組時,可以用一個字串引數來指定資料型別

2.2Numpy陣列基礎

Python中的資料操作幾乎等同於Numpy陣列操作,甚至Pandas也是構建在Numpy陣列基礎之上的。

介紹以下幾類基本的陣列操作:

屬性:確定陣列的大小、形狀、儲存大小、資料型別    索引:獲取和設定陣列各個元素的值   切分:在大陣列中獲取或設定更小的陣列

變形:改變給定陣列的形狀    拼接和分裂:將多個數組合併為一個,以及將一個數組分裂成多個

 2.2.1 Numpy陣列的屬性

我們將用Numpy的隨機數生成器設定一組種子值,以確保每次程式執行時都可以生成同樣的隨機陣列

import numpy as np
np.random.seed(0)# 設定隨機數種子
x3=np.random.randint(10,size=(3,4,5))#三維陣列

#每個陣列有nidm(陣列維度)、shape(陣列每個維度的大小)、
#size(陣列的總大小)、dtype(陣列的資料型別)

print("x3 nidm",x3.ndim)
print("x3 shape",x3.shape)
print("x3 size",x3.size)
print("x3 dtype",x3.dtype)

#x3 nidm: 3    x3陣列有3個維度
#x3 shape: (3,4,5) 第一個維度大小為3
#x3 size: 60    3×4×5=60
#x3 dtype: int64

2.2.2 陣列索引 獲取單個元素

在一維陣列中,你也可以通過中括號指定索引獲取第i個值(從0開始計數)。為了獲取陣列末尾的索引,可以用負值

import numpy as np 
L=np.array([1,2,3,4])
print(L)
print(L[-1])
print(L[-4])

#輸出結果為
#[1 2 3 4]
#4
#1

 在多維陣列中,可以用逗號分隔的索引元組獲取元素:

import numpy as np
np.random.seed(0)
x2=np.random.randint(10,size=(3,4))
print(x2)

#陣列為[[5 0 3 3]
#      [7 9 3 5]
#      [2 4 7 6]]

print(x2[0,0])  #行、列均從0開始計數
prnit(x2[0,3])  #行、列均從0開始計數

        用以上方式也可以修改元素值,請注意,和 Python 列表不同, NumPy 陣列是固定型別的。這意味著當你試圖將一個浮點值插入一個整型陣列時,浮點值會被截短成整型。並且這種截短是自動完成的,不會給你提示或警告,所以需要特別注意這一點! 

2.2.3陣列切片:獲取子陣列

用切片(slice)獲取子陣列  x[start:stop:step],如果3個引數均未指定,其預設值start=0、stop=維度大小、step=1

import numpy as np
x=np.arrange(10)
# 陣列為[0 1 2 3 4 5 6 7 8 9]
x[:5]#前五個元素
x[5:]#索引五之後的元素
x[4:7]#從索引4開始 到索引7之前
x[::2]#每隔一個元素
x[::-1]#所有元素 逆序的

對於多維子陣列,也用冒號分隔進行處理

x2[:2,:3]#前兩行 前三列
#子陣列維度也可以同時被逆序
x2[::-1,::-1]

一種常見的需求是獲取陣列的單行和單列。你可以將索引與切片組合起來實現這個功能,用一個冒號(:)表示空切片

#獲取x2的第一列
x2[:,0]
#獲取x2的第一行
x2[0,:]
#在獲取行時 出於語法簡介考慮 可以省略空切片
x2[0]

 關於陣列切片有一點很重要也非常有用,那就是陣列切片返回的是陣列資料的檢視,而不是數值資料的副本

這一點也是NumPy陣列切片和Python列表切片的不同之處Python 列表中,切片是值的副本,改變切片裡的值,原始陣列也會修改。
這種預設的處理方式實際上非常有用:它意味著在處理非常大的資料集時,可以獲取或處
理這些資料集的片段,而不用複製底層的資料快取 。

建立陣列的副本,用copy()方法實現,修改子陣列,原始陣列不會被改變。

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

2.2.4陣列的變形

陣列變形最靈活的方式是通過reshape()函式來實現,但要求原始陣列的大小必須和變形後的陣列的大小一致

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

另一個常見的變形模式是將一個一維陣列轉變為二維的行或列矩陣,可以用reshape,也可以在切片中用newaxis

import numpy as np
x=np.array([1,2,3])
x.reshape((1,3))

#[[1 2 3]]

#通過newaxis獲得行向量
x[np.newaxis,:]
#通過newaxis獲得列向量
x[:,np.newaxis]

2.2.5陣列的拼接和分裂

拼接或連線NumPy中的兩個陣列主要由np.concatenatenp.vstacknp.hstack例程實現 。

np.concatenate將陣列元組或陣列列表作為第一引數

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

 np.concatenate也可以用於二維陣列的拼接

import numpy as np
grid=np.array([[1,2,3],
                      [4,5,6]])
#沿著第一個軸拼接
np.concatenate([grid,grid])
#[[1, 2, 3],
# [4, 5, 6],
# [1, 2, 3],
# [4, 5, 6]]

#沿著第二個軸拼接(軸是從0開始索引的)
np.concatenate([grid,grid],axis=1)

# [[1, 2, 3, 1, 2, 3],
#  [4, 5, 6, 4, 5, 6]]

沿著固定維度處理陣列時,使用 np.vstack(垂直棧)和 np.hstack(水平棧)函式會更簡潔,np.dstack將沿著第三個維度拼接。

與拼接相反的過程是分裂。分裂可以通過 np.splitnp.hsplit(水平分裂) np.vsplit(垂直分裂) 函式來實現。 索引列表作為引數,記錄的是分裂點的位置。

import numpy as numpy
x=[1,2,34,6,99,56,26,11,22,33,444,50]
x1,x2,x3=np.split(x,[3,6])
print(x1,x2,x3)

#[ 1  2 34] [ 6 99 56] [ 26  11  22  33 444  50]
#N個分裂點會得到N+1個數組

2.3Numpy陣列的計算:通用函式

Numpy提供了一個簡單靈活的介面來優化資料陣列的計算。使Numpy變快的關鍵是利用向量化操作,通常用Numpy的通用函式。

通用函式有兩種存在形式: 一元通用函式unary ufunc)對單個輸入操作, 二元通用函式binary ufunc)對兩個輸入操作。 

Numpy的通用函式可以對陣列進行向量化操作,可以提高陣列元素的重複計算的效率。通用函式的主要目標是對Numpy陣列中的值執行更快的操作

import numpy as np
np.arange(5)/np.arange(1,6)

# array([0.        , 0.5       , 0.66666667, 0.75      , 0.8       ])

x=np.arange(9).reshape((3,3))
2**x

#array([[  1,   2,   4],
#       [  8,  16,  32],
#       [ 64, 128, 256]], dtype=int32)

 通過通用函式用向量的方式進行計算幾乎總比用 Python 迴圈實現的計算更加有效,尤其是當陣列很大時。

只要你看到 Python 指令碼中有這樣的迴圈,就應該考慮能否用向量方式替換這個迴圈。 

(1)加減乘除,邏輯非,**表示的指數運算子和%表示的模運算子  都是一元通用函式

(2)絕對值函式。直接abs(),括號內為一個Numpy陣列

(3)三角函式。np.sin() np.cos() np.tan() np.arcsin()  np.arccos() np.arctan() 括號裡直接是一個Numpy陣列

(4)指數和對數。np.exp(x)表示 e^x   np.exp2(x)表示2^x  np.power(3,x)表示3^x。

          最基本的 np.log 給出的是以自然數為底數的對數。如果你希望計算以2為底數或者以10為底數的對數 np.log2(),np.log10()

2.3.4高階通用函式的特性

指定輸出:所有的通用函式都可以通過out引數來指定計算結果的存放位置

import numpy as np
x=np.arange(5)
y=np.empty(5)
np.multiply(x,10,out=y)
print(y)

#[ 0. 10. 20. 30. 40.]

聚合:reduce方法會對給定的元素和操作重複執行,直至得到單個的結果 。

import numpy as np
x=np.arange(10)
np.add.reduce(x)
# 45

np.add.reduce()#返回陣列中所有元素的和

np.multiply.reduce()#返回陣列中所有元素的積

如果需要儲存中間結果,可以使用accumulate

外積:任何通用函式都可以用 outer 方法獲得兩個不同輸入陣列所有元素對的函式運算結果。這意味著你可以用一行程式碼實現一個乘法表: 

import numpy as np
x=np.arange(1,6)
np.multiply.outer(x,x)


Out[17]: 
array([[ 1,  2,  3,  4,  5],
       [ 2,  4,  6,  8, 10],
       [ 3,  6,  9, 12, 15],
       [ 4,  8, 12, 16, 20],
       [ 5, 10, 15, 20, 25]])

有關通用函式的更多資訊(包括可用的通用函式的完整列表)可以在 NumPyhttp://www.numpy.org)和 SciPyhttp://www.scipy.org)文件的網站找到。

前面的章節介紹過,可直接在 IPython 中通過匯入相應的包,然後利用 IPython Tab 鍵補全和幫助(?)功能獲取資訊 。