《利用python進行數據分析》讀書筆記--第四章 numpy基礎:數組和矢量計算


第四章 Numpy基礎:數組和矢量計算

第一部分:numpy的ndarray:一種多維數組對象

實話說,用numpy的主要目的在於應用矢量化運算。Numpy並沒有多么高級的數據分析功能,理解Numpy和面向數組的計算能有助於理解后面的pandas.按照課本的說法,作者關心的功能主要集中於:

  • 用於數據整理和清理、子集構造和過濾、轉換等快速的矢量化運算
  • 常用的數組解法,如排序、唯一化、集合運算等
  • 高效的描述統計和數據聚合/摘要運算
  • 用於異構數據集的合並/連接運算的數據對齊和關系型數據運算
  • 將條件邏輯表述為數組表達式(而不是帶有if-elif-else分支的循環)
  • 數據的分組運算(聚合、轉換、函數應用等)。

作者說了,可能還是pandas更好一些,我感覺顯然pandas更高級,其中的函數真是太方便了,數據框才是最好的數據結構。只是,Numpy中的函數之類的是基礎,需要熟悉。

NumPy的ndarray:一種多維數組對象

ndarray對象是NumPy最重要的對象,特點是矢量化。ndarray每個元素的數據類型必須相同,每個數組有兩個屬性:shape和dtype.

#-*- encoding:utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

data = [[1,2,5.6],[21,4,2]]
data = np.array(data)
print data.shape
print data.dtype
print data.ndim
>>>
(2, 3)
float64
2

array函數接受一切序列型的對象(包括其他數組),然后產生新的含有傳入數據的NumPy數組,array會自動推斷出一個合適的數據類型。還有一個方法是ndim:這個翻譯過來叫維度,標明數據的維度。上面的例子是兩維的。zeros和ones可以創建指定長度或形狀全為0或1的數組。empty可以創建一個沒有任何具體值的數組,arange函數是python內置函數range的數組版本。

#-*- encoding:utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

data = [[1,2,5.6],[21,4,2],[2,5,3]]
data1 = [[2,3,4],[5,6,7,3]]
data = np.array(data)
data1 = np.array(data1)

arr1 = np.zeros(10)
arr2 = np.ones((2,3))
arr3 = np.empty((2,3,4))

print arr1
print arr2
print arr3
print arr3.ndim
>>>
[ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
[[ 1.  1.  1.]
[ 1.  1.  1.]]
[[[  3.83889007e-321   0.00000000e+000   0.00000000e+000   0.00000000e+000]
  [  0.00000000e+000   0.00000000e+000   0.00000000e+000   0.00000000e+000]
  [  0.00000000e+000   0.00000000e+000   0.00000000e+000   0.00000000e+000]]
[[  0.00000000e+000   0.00000000e+000   0.00000000e+000   0.00000000e+000]
  [  0.00000000e+000   0.00000000e+000   0.00000000e+000   0.00000000e+000]
  [  0.00000000e+000   0.00000000e+000   0.00000000e+000   0.00000000e+000]]]
3

QQ圖片20151127140512

上面是常用的生成數組的函數。

ndarray的數據類型

dtype(數據類型)是一個特殊的對象。它含有ndarray將一塊內存解釋為指定數據類型所需的信息。他是NumPy如此靈活和強大的原因之一。多數情況下,它們直接映射到相應的機器表示,這使得“讀寫磁盤上的二進制數據流”以及“集成低級語言代碼(C\Fortran)”等工作變得更加簡單。dtype命名方式為,類型名+表示元素位長的數字。標准雙精度浮點型數據需要占用8字節(64位)。記作float64.常見的數據類型為:

image

image

我終於找到了f4,f8的含義了……布爾型數據的代碼倒是很有個性。函數astype可以強制轉換數據類型。

#-*- encoding:utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

arr = np.array([1,2,3,4,5])
print arr.dtype
float_arr = arr.astype(np.float64)
print float_arr.dtype

arr1 = np.array([2.3,4.2,32.3,4.5])
#浮點型會被整型截斷
print arr1.astype(np.int32)
#一個全是數字的字符串也可以轉換為數值類型
arr2 = np.array(['2323.2','23'])
print arr2.astype(float)

#數組的dtype還有一個用法
int_array = np.arange(10)
calibers = np.array([.22,.270,.357,.44,.50],dtype = np.float64)
print int_array.astype(calibers.dtype)
print np.empty(10,'u4')

調用astype總會創建一個新的數組(原始數組的一個拷貝),即使和原來的數據類型相同。警告:浮點數只能表示近似數,比較小數的時候要注意。

數組與標量之間的運算

矢量化(vectorization)是數組最重要的特點了。可以避免(顯示)循環。注意加減乘除的向量化運算。不同大小的數組之間的運算叫廣播(broadcasting)。

索引和切片,不再贅述,注意的是 廣播的存在使得數組即使只賦一個值也會被廣播到所有數組元素上,其實和R語言中自動補齊功能相同。下面的性質有點蛋疼:跟列表最重要的區別在於,數組切片是原始數組的視圖,對視圖的任何修改都會反映到源數據上。即使是下面的情況:

#-*- encoding:utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

arr = np.array([1,2,3,4,5,6,7,8,9])
arr1 = arr[1:2]
arr1[0] = 10
print arr
#如果想得到拷貝,需要顯示地復制一份
arr2 = arr[3:4].copy()
arr2[0] = 10
print arr

arr2d = np.array([[1,2,3],[4,5,6],[7,8,9]])
#下面兩種索引方式等價
print arr2d[0][2]
print arr2d[0,2]
print arr2d[:,1] #注意這里的方式和下面的方式
print arr2d[:,:1]

arr3d = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[[10,11,12]]]])
print arr3d[(1,0)]
>>>
[ 1 10  3  4  5  6  7  8  9]
[ 1 10  3  4  5  6  7  8  9]
3
3
[2 5 8] #注意這里的方式和下面的方式
[[1]
[4]
[7]]
[7, 8, 9]

布爾型索引

這里的布爾型索引就是TRUE or FALSE索引。==、!=、-(表示否定)、&(並且)、|(或者)。注意布爾型索引選取數組中的數據,將創建數據的副本。python關鍵字and、or無效。

花式索引(Fancy indexing)

花式索引指的是利用整數數組進行索引。

#-*- encoding:utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

arr = np.arange(32).reshape(8,4)

print  arr
#注意這里的向量式方式
print arr[[1,5,7,2],[0,3,1,2]]
print arr[[1,5,7,2]][:,[0,3,1,2]]
#也可以使用np.ix_函數,將兩個一維整數數組組成選取方形區域的索引器
print arr[np.ix_([1,5,7,2],[0,3,1,2])]
>>>
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]
 [20 21 22 23]
 [24 25 26 27]
 [28 29 30 31]]
[ 4 23 29 10]
[[ 4  7  5  6]
 [20 23 21 22]
 [28 31 29 30]
 [ 8 11  9 10]]
[[ 4  7  5  6]
 [20 23 21 22]
 [28 31 29 30]
 [ 8 11  9 10]]

花式索引總是將數據復制到新數組中,跟切片不同,一定要注意下面的區別:

#-*- encoding:utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

arr = np.arange(32).reshape(8,4)
arr1 = np.arange(32).reshape(8,4)
#注意下面得到的結果是一樣的
arr3 = arr[[1,2,3]][:,[0,1,2,3]]
arr3_1 = arr1[1:4][:]

#注意下面是區別了
arr3[0,1] = 100  #花式索引得到的是復制品,重新賦值以后arr不變化
arr3_1[0,1] = 100 #切片方式得到的是一個視圖,重新賦值后arr1會變化

print arr3
print arr3_1
print arr
print arr1
>>>
[[  4 100   6   7]
 [  8   9  10  11]
 [ 12  13  14  15]]
[[  4 100   6   7]
 [  8   9  10  11]
 [ 12  13  14  15]]
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]
 [20 21 22 23]
 [24 25 26 27]
 [28 29 30 31]]
[[  0   1   2   3]
 [  4 100   6   7]
 [  8   9  10  11]
 [ 12  13  14  15]
 [ 16  17  18  19]
 [ 20  21  22  23]
 [ 24  25  26  27]
 [ 28  29  30  31]]

數組轉置和軸轉換

轉置transpose,是一種對源數據的視圖,不會進行復制。調用T就可以。np中的矩陣乘積函數為np.dot。

比較復雜的是高維數組:

#-*- encoding:utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

arr = np.arange(24).reshape((2,3,4))
#下面解釋一下transpose:
#(1,0,2) 是將reshape中的參數 (2,3,4) 進行變化 ,變為(3,2,4)
#但是由於是轉置,所以是將所有元素的下標都進行了上述變化,比如 12這個元素,原來索引為 (1,0,0) ,現在為 (0,1,0)
arr1 = arr.transpose((1,0,2))
arr2 = arr.T #直接用T是變為了(4,3,2)的形式

#arr3 = np.arange(120).reshape((2,3,4,5))
#arr4 = arr3.T #直接用T就是將形式變為 (5,4,3,2)
#ndarray還有swapaxes方法,接受一對軸編號
arr5 = arr.swapaxes(1,2)

#print arr
#print arr1
#print arr2
#print arr3
#print arr4
print arr5

>>>
[[[ 0  4  8]
  [ 1  5  9]
  [ 2  6 10]
  [ 3  7 11]]

 [[12 16 20]
  [13 17 21]
  [14 18 22]
  [15 19 23]]]

第二部分是關於一些元素級函數:即作用於數組每個元素上的函數,用過R語言之后就覺得其實沒什么了。

下面是一些常見的矢量化函數(姑且這么叫吧)。

image

image

下面是幾個例子:

#-*- encoding:utf-8 -*-

import numpy as np
import numpy.random as npr
import pandas as pd

#接收兩個數組的函數,對應值取最大值
x = npr.randn(8)
y = npr.randn(8)
#注意不是max函數
z = np.maximum(x,y)
print x,y,z

#雖然並不常見,但是一些ufunc函數的確可以返回多個數組。modf函數就是一例,用來分隔小數的整數部分和小數部分,是python中divmod的矢量化版本
arr = npr.randn(8)
print np.modf(arr)
#ceil函數取天花板,不小於這個數的最小整數
print np.ceil(arr)
#concatenate函數是將兩個numpy數組連接,注意要組成元組方式再連接
#arr = np.concatenate((arr,np.array([0,0])))
#logical_not函數, 非 函數
#print np.logical_not(arr)
print np.greater(x,y)
print np.multiply(x,y)

第三部分:利用數組進行數據處理

作者說矢量化數組運算比純pyhton方式快1-2個數量級(or more),又一次強調了broadcasting作用很強大。

#-*- encoding:utf-8 -*-

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt


#假設想在一個二維網格上計算一個 sqrt(x^2 + y^2)
#生成-5到5的網格,間隔0.01
points = np.arange(-5,5,0.01)
#meshgrid返回兩個二維矩陣,描述出所有(-5,5)* (-5,5)的點對
xs,ys = np.meshgrid(points,points)

z = np.sqrt(xs ** 2 + ys ** 2)
#print xs
#print ys
#不做個圖都對不起觀眾
#imshow函數,展示z是一個矩陣,cmap就是colormap,用的時候值得研究
plt.imshow(z,cmap=plt.cm.gray)
plt.colorbar()
plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values")
plt.show()

image

上面的畫圖語句在用的時候還需要好好研究一下。

下面的一個例子是np.where函數,簡潔版本的if-else。

'''
#np.where函數通常用於利用已有的數組生產新的數組
arr = npr.randn(4,4)
#正值賦成2,負值為-2
print np.where(arr > 0,2,-2)
#注意這里的用法
print np.where(arr > 0,2,arr)
#可以用where表示更為復雜的邏輯表達
#兩個布爾型數組cond1和cond2,4種不同的組合賦值不同
#注意:按照課本上的說法,下面的語句是從左向右運算的,不是從做內層括號計算起的;這貌似與python的語法不符
np.where(cond1 & cond2,0,np.where(cond1,1,np.where(cond2,2,3)))
#不過感覺沒有更好的寫法了。
#書上“投機取巧”的式子,前提是True = 1,False = 0
result = 1 * (cond1 - cond2) + 2 * (cond2 & -cond1) + 3 * -(cond1 | cond2)
'''
#-*- encoding:utf-8 -*-

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import numpy.random as npr
#值得注意的是,mean、sum這樣的函數,會有一個參數axis表示對哪個維度求值
arr = np.array([[0,1,2],[3,4,5],[6,7,8]])
#cumsum不是聚合函數,維度不會減少
print arr.cumsum(0)

下面是常用的數學函數:

image

image

用於布爾型數組的方法

sum經常用於True的加和;any和all分別判斷是否存在和是否全部為True。

排序及唯一化

#-*- encoding:utf-8 -*-

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import numpy.random as npr

#sort函數是就地排序
arr = npr.randn(10)
print arr
arr.sort()
print arr
#多維數組可以按照維度排序,把軸編號傳遞給sort即可
arr = npr.randn(5,3)
print arr
#sort傳入1,就是把第1軸排好序,即按列
arr.sort(1)
print arr
#np.sort返回的是排序副本,不是就地排序
#輸出5%分位數
arr_npr = npr.randn(1000)
arr_npr.sort()
print arr_npr[int(0.05 * len(arr_npr))]
#pandas中有更多排序、分位數之類的函數,直接可以取分位數的,第二章的例子中就有
#numpy中有unique函數,唯一化函數,R語言中也有
names = np.array(['Bob','Joe','Will','Bob','Will'])
print sorted(set(names))
print np.unique(names)
values = np.array([6,0,0,3,2,5,6])
#in1d函數用來查看一個數組中的元素是否在另一個數組中,名字挺好玩,注意返回的長度與第一個數組相同
print np.in1d(values,[6,2,3])

下面是常用集合運算

image

用於數組的文件輸入輸出

NumPy能夠讀寫磁盤上的文本數據或二進制數據。后面的章節將會給出一些pandas中用於將表格型數據讀取到內存的工具。

np.save 和 np.load是讀寫磁盤數據的兩個主要函數。默認情況下,數組是以未壓縮的原始二進制文件格式保存在擴展名為.npy的文件中。

#-*- encoding:utf-8 -*-

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import numpy.random as npr
'''
arr = np.arange(10)
np.save('some_array',arr)
np.savez('array_archive.npz',a = arr,b = arr)
arr1 = np.load('some_array.npy')
arch = np.load('array_archive.npz')
print arr1
print arch['a']
'''
#下面是存取文本文件,pandas中的read_csv和read_table是最好的了
#有時需要用np.loadtxt或者np.genfromtxt將數據加載到普通的NumPy數組中
#這些函數有許多選項使用:指定各種分隔符,針對特定列的轉換器函數,需要跳過的行數等
#np.savetxt執行的是相反的操作:將數組寫到以某種分隔符隔開的文本文件中
#genfromtxt跟loadtxt差不多,只不過它面向的是結構化數組和缺失數據處理

線性代數

關於線性代數的一些函數,NumPy的linalg中有很多關於矩陣的函數,與MATLAB、R使用的是相同的行業標准級Fortran庫。

image

隨機數生成

NumPy.random模塊對Python內置的random進行了補充,增加了一些用於高效生成多種概率分布的樣本值的函數。

#-*- encoding:utf-8 -*-
import numpy as np
import numpy.random as npr
from random import normalvariate
#生成標准正態4*4樣本數組
samples = npr.normal(size = (4,4))
print samples
#從下面的例子中看出,如果產生大量樣本值,numpy.random快了不止一個數量級
N = 1000000
#xrange()雖然也是內置函數,但是它被定義成了Python里一種類型(type),這種類型就叫做xrange.
#下面的循環中,for _ in xrange(N) 非常good啊,查了一下和range的關系,兩者都用於循環,但是在大型循環時,xrange好得多
%timeit samples = [normalvariate(0,1) for _ in xrange(N)]
%timeit npr.normal(size = N)

image

image

范例:隨機漫步

#-*- encoding:utf-8 -*-
import numpy as np
import random #這里的random是python內置的模塊
import matplotlib.pyplot as plt

position = 0
walk = [position]
steps = 1000
for i in xrange(steps):
    step = 1 if random.randint(0,1) else -1
    position += step
    walk.append(position)
plt.plot(walk)
plt.show()

#下面看看簡單的寫法
nsteps = 1000
draws = np.random.randint(0,2,size = nsteps)
steps = np.where(draws > 0,1,-1)
walk = steps.cumsum()
plt.plot(walk)
plt.show()
#argmax函數返回數組第一個最大值的索引,但是在這argmax不高效,因為它會掃描整個數組
print (np.abs(walk) >= 10).argmax()

nwalks = 5000
nsteps = 1000
draws = np.random.randint(0,2,size = (nwalks,nsteps))
steps = np.where(draws > 0,1,-1)
walks = steps.cumsum(1)
print walks.max()
print walks.min()
#這里的any后面的參數1表示每行(軸為1)是否存在true
hist30 = (np.abs(walks) >= 30).any(1)
print hist30
print hist30.sum()  #這就是有多少行超過了30
#這里argmax的參數1就是
crossing_time = (np.abs(walks[hist30]) >= 30).argmax(1)
print crossing_time.mean()
X = range(1000)
plt.plot(X,walks.T)
plt.show()

image

NumPy寫完了,接下來寫pandas.NumPy寫的還好,比較順利。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM