切片操作專題之numpy、pandas

AI_ape發表於2020-12-31

1.for迴圈也能實現陣列賦值,為什麼還要切片?

numpy為例,切片除了使程式碼更簡潔,還能帶來其他好處嗎?

for迴圈作為最直接的賦值手段,難道不能cover所有的問題?

帶著疑問,用事實說話:

1.1 中小型陣列的賦值

如下,原始陣列為20000*100 ,將它其中的20列賦值給一個陣列20000*20

透過for迴圈以及numpy陣列切片的執行時間各位多少呢?

import time
import numpy as np
import random
size=20000
a=np.random.randint(0,100,(size,100),dtype=np.int8)
b=np.random.choice(range(100),20)

t0=time.time()

c1=np.zeros((size,len(b)))
for i in range(len(b)):
    c1[:,i]=a[:,b[i]]
t1=time.time()

c2=a[:,b]
t2=time.time()

print(f"time cost1:{t1-t0}s")
print(f"time cost2:{t2-t1}s")

輸出結果如下:

time cost1:0.0020225048065185547s
time cost2:0.000997304916381836s

cost1、cost2分別為for迴圈以及numpy陣列切片的執行時間。

可見對於中小型陣列,執行時間差別不大。

但是如果對於特別大的陣列呢?

1.2 大型陣列的賦值

原始陣列現在變為20000000*100 ,同樣將其20列賦值給另外一個陣列。

import time
import numpy as np
import random
size=20000000
a=np.random.randint(0,100,(size,100),dtype=np.int8)
b=np.random.choice(range(100),20)

t0=time.time()

c1=np.zeros((size,len(b)))
for i in range(len(b)):
    c1[:,i]=a[:,b[i]]
t1=time.time()

c2=a[:,b]
t2=time.time()

print(f"time cost1:{t1-t0}s")
print(f"time cost2:{t2-t1}s")

輸出結果如下:

time cost1:7.487896680831909s
time cost2:2.3806240558624268s

這個時間就有相當大的差別了。

如果需要讀取大量檔案,再從檔案中抽取部分資料,資料讀取、提取這個時間會佔到程式執行的相當一部分。

這時,使用切片的效益就非常可觀了。

1.3 為什麼會這樣?

Book《Python資料科學手冊》給出如下解釋

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

這一點也是 NumPy 陣列切片和 Python 列表切片的不同之處:在 Python 列表中,切片是值的副本。

這種預設的處理方式實際上非常有用:它意味著在處理非常大的資料集時,可以獲取或處理這些資料集的片段,而不用複製底層的資料快取。

所以,我們就愉快地切片吧~

2.numpy之切片

上面例子中已有涉及,這裡再贅述一點。
隨機產生一個10*10numpy陣列:

import numpy as np
a=np.random.randint(0,10,(8,8))
#out:
array([[7, 4, 3, 6, 8, 6, 7, 3],
       [7, 0, 5, 3, 1, 2, 9, 5],
       [1, 7, 5, 4, 1, 9, 4, 4],
       [4, 7, 3, 5, 6, 7, 5, 1],
       [5, 1, 8, 7, 4, 7, 0, 8],
       [4, 4, 7, 8, 2, 0, 3, 1],
       [1, 3, 1, 4, 3, 9, 4, 5],
       [1, 2, 6, 9, 7, 8, 8, 3]])

如果是針對連續單元切片,如擷取2到4行:

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

但是如果擷取指定行或者指定列呢?如擷取2,4,7行:

b=[2,4,7]
a[b,:]
#out:
array([[1, 7, 5, 4, 1, 9, 4, 4],
       [5, 1, 8, 7, 4, 7, 0, 8],
       [1, 2, 6, 9, 7, 8, 8, 3]])

這裡b=[2,4,7]np.array([2,4,7]),還是b=(2,4,7),對於切片操作來說,都是類似於:3這樣的可迭代單元。

3.pandas之切片

首先建立一個pandas DataFrame物件。

import pandas as pd 
a=pd.Series([1,2,3])
b=pd.Series(["a","b","c"])
c=pd.Series(["A","B","C"])
d=pd.Series(["#","¥","%"])
df=pd.DataFrame({"num":a,"lower":b,"upper":c,"other":d})
#out:
   num lower upper other
0    1     a     A     #
1    2     b     B     ¥
2    3     c     C     %

如果要讀取其第0列和第2列,有兩種切片方法:

3.1 採用關鍵字

col=["num","upper"]
df[col].values
#out:
array([[1, 'A'],
       [2, 'B'],
       [3, 'C']], dtype=object)

3.2 轉成numpy之後,採用第2節方法

df.values[:,[0,2]]
#out:
array([[1, 'A'],
       [2, 'B'],
       [3, 'C']], dtype=object)

相關文章