Cắt một mảng NumPy 2d, hoặc làm cách nào để trích xuất một chuỗi con mxm từ một mảng nxn (n> m)?


174

Tôi muốn cắt một mảng nxn NumPy. Tôi muốn trích xuất một lựa chọn tùy ý gồm m hàng và cột của mảng đó (nghĩa là không có bất kỳ mẫu nào trong số hàng / cột), biến nó thành một mảng mxm mới. Trong ví dụ này, chúng ta hãy nói rằng mảng là 4 x 4 và tôi muốn trích xuất một mảng 2x2 từ nó.

Đây là mảng của chúng tôi:

from numpy import *
x = range(16)
x = reshape(x,(4,4))

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

Các dòng và cột để loại bỏ là như nhau. Trường hợp dễ nhất là khi tôi muốn trích xuất một hàm con 2x2 ở đầu hoặc cuối, tức là:

In [33]: x[0:2,0:2]
Out[33]: 
array([[0, 1],
       [4, 5]])

In [34]: x[2:,2:]
Out[34]: 
array([[10, 11],
       [14, 15]])

Nhưng nếu tôi cần loại bỏ một hỗn hợp hàng / cột khác thì sao? Điều gì sẽ xảy ra nếu tôi cần loại bỏ các dòng / hàng đầu tiên và thứ ba, do đó trích xuất hàm con [[5,7],[13,15]]? Có thể có bất kỳ thành phần của hàng / dòng. Tôi đã đọc ở đâu đó rằng tôi chỉ cần lập chỉ mục cho mảng của mình bằng cách sử dụng mảng / danh sách các chỉ mục cho cả hàng và cột, nhưng điều đó dường như không hoạt động:

In [35]: x[[1,3],[1,3]]
Out[35]: array([ 5, 15])

Tôi tìm thấy một cách, đó là:

    In [61]: x[[1,3]][:,[1,3]]
Out[61]: 
array([[ 5,  7],
       [13, 15]])

Vấn đề đầu tiên với điều này là nó khó đọc, mặc dù tôi có thể sống với điều đó. Nếu ai đó có giải pháp tốt hơn, tôi chắc chắn muốn nghe.

Một điều khác là tôi đọc trên một diễn đàn rằng lập chỉ mục các mảng với các mảng buộc NumPy tạo một bản sao của mảng mong muốn, do đó khi xử lý các mảng lớn, điều này có thể trở thành một vấn đề. Tại sao lại như vậy / cơ chế này hoạt động như thế nào?

Câu trả lời:


62

Như Sven đã đề cập, x[[[0],[2]],[1,3]]sẽ trả lại 0 và 2 hàng khớp với cột 1 và 3 trong khi x[[0,2],[1,3]]sẽ trả về các giá trị x [0,1] và x [2,3] trong một mảng.

Có một chức năng hữu ích để làm ví dụ đầu tiên tôi đã đưa ra numpy.ix_. Bạn có thể làm điều tương tự như ví dụ đầu tiên của tôi với x[numpy.ix_([0,2],[1,3])]. Điều này có thể giúp bạn không phải nhập vào tất cả các dấu ngoặc phụ đó.


111

Để trả lời câu hỏi này, chúng ta phải xem xét cách lập chỉ mục một mảng đa chiều hoạt động trong Numpy. Trước tiên hãy nói rằng bạn có mảng xtừ câu hỏi của bạn. Bộ đệm được gán cho xsẽ chứa 16 số nguyên tăng dần từ 0 đến 15. Nếu bạn truy cập một phần tử, giả sử x[i,j], NumPy phải tìm ra vị trí bộ nhớ của phần tử này so với phần đầu của bộ đệm. Điều này được thực hiện bằng cách tính toán có hiệu lực i*x.shape[1]+j(và nhân với kích thước của một int để có được một bộ nhớ thực tế).

Nếu bạn trích xuất một phân đoạn bằng cách cắt cơ bản như y = x[0:2,0:2], đối tượng kết quả sẽ chia sẻ bộ đệm bên dưới với x. Nhưng điều gì xảy ra nếu bạn gia nhập y[i,j]? NumPy không thể sử dụng i*y.shape[1]+jđể tính toán bù vào mảng, vì dữ liệu thuộc về ykhông liên tiếp trong bộ nhớ.

NumPy giải quyết vấn đề này bằng cách giới thiệu những bước tiến . Khi tính toán bù bộ nhớ để truy cập x[i,j], những gì thực sự được tính là i*x.strides[0]+j*x.strides[1](và điều này đã bao gồm yếu tố cho kích thước của một int):

x.strides
(16, 4)

Khi yđược chiết xuất như trên, NumPy không tạo ra một bộ đệm mới, nhưng nó không tạo ra một đối tượng mảng mới tham khảo cùng một bộ đệm (nếu không ysẽ chỉ được bằng x.) Các đối tượng mảng mới sẽ có một hình dạng khác nhau sau đó xvà có thể là một khởi đầu khác nhau bù vào bộ đệm, nhưng sẽ chia sẻ các bước với x(ít nhất là trong trường hợp này):

y.shape
(2,2)
y.strides
(16, 4)

Bằng cách này, tính toán bù bộ nhớ cho y[i,j]sẽ mang lại kết quả chính xác.

Nhưng NumPy nên làm gì cho một cái gì đó như thế z=x[[1,3]]nào? Cơ chế sải chân sẽ không cho phép lập chỉ mục chính xác nếu bộ đệm ban đầu được sử dụng cho z. Về mặt lý thuyết, NumPy có thể thêm một số cơ chế phức tạp hơn các bước tiến, nhưng điều này sẽ khiến việc truy cập phần tử tương đối tốn kém, bằng cách nào đó bất chấp toàn bộ ý tưởng của một mảng. Ngoài ra, một khung nhìn sẽ không còn là một vật thể nhẹ nữa.

Điều này được đề cập sâu trong tài liệu NumPy về lập chỉ mục .

Ồ, và gần như quên mất câu hỏi thực tế của bạn: Đây là cách làm cho việc lập chỉ mục với nhiều danh sách hoạt động như mong đợi:

x[[[1],[3]],[1,3]]

Điều này là do các mảng chỉ số được phát sóng thành một hình dạng phổ biến. Tất nhiên, đối với ví dụ cụ thể này, bạn cũng có thể thực hiện với việc cắt lát cơ bản:

x[1::2, 1::2]

Có thể phân vùng các mảng con để người ta có thể có một đối tượng "slcie-view" taht sẽ ánh xạ lại các chỉ mục cho mảng ban đầu. Điều đó có thể đáp ứng nhu cầu của OP
jsbueno

@jsbueno: sẽ hoạt động với mã Python nhưng không hoạt động cho các thói quen C / Fortran mà Scipy / Numpy bao bọc. Những thói quen được bao bọc đó là nơi sức mạnh của Numpy nằm.
Đạt Chu

Soo .. sự khác biệt giữa x [[[1], [3]], [1,3]] và x [[1,3] ,:] [:, [1,3]]? Ý tôi là có một biến thể nào tốt hơn để sử dụng hơn các biến thể khác?
levesque

1
@JC: x[[[1],[3]],[1,3]]chỉ tạo một mảng mới, trong khi x[[1,3],:][:,[1,3]]sao chép hai lần, vì vậy hãy sử dụng mảng đầu tiên.
Sven Marnach

@JC: Hoặc sử dụng phương pháp từ câu trả lời của Justin.
Sven Marnach

13

Tôi không nghĩ rằng x[[1,3]][:,[1,3]]nó khó đọc. Nếu bạn muốn rõ ràng hơn về ý định của mình, bạn có thể làm:

a[[1,3],:][:,[1,3]]

Tôi không phải là một chuyên gia trong việc cắt lát nhưng thông thường, nếu bạn cố gắng cắt thành một mảng và các giá trị là liên tục, bạn sẽ quay lại chế độ xem giá trị sải chân được thay đổi.

ví dụ: Trong các đầu vào 33 và 34 của bạn, mặc dù bạn nhận được một mảng 2x2, sải chân là 4. Do đó, khi bạn lập chỉ mục cho hàng tiếp theo, con trỏ sẽ di chuyển đến vị trí chính xác trong bộ nhớ.

Rõ ràng, cơ chế này không thực hiện tốt trong trường hợp một loạt các chỉ số. Do đó, numpy sẽ phải tạo bản sao. Rốt cuộc, nhiều hàm toán học ma trận khác phụ thuộc vào kích thước, bước tiến và phân bổ bộ nhớ liên tục.


10

Nếu bạn muốn bỏ qua mọi hàng khác và mọi cột khác, thì bạn có thể thực hiện với cách cắt cơ bản:

In [49]: x=np.arange(16).reshape((4,4))
In [50]: x[1:4:2,1:4:2]
Out[50]: 
array([[ 5,  7],
       [13, 15]])

Điều này trả về một khung nhìn, không phải là một bản sao của mảng của bạn.

In [51]: y=x[1:4:2,1:4:2]

In [52]: y[0,0]=100

In [53]: x   # <---- Notice x[1,1] has changed
Out[53]: 
array([[  0,   1,   2,   3],
       [  4, 100,   6,   7],
       [  8,   9,  10,  11],
       [ 12,  13,  14,  15]])

trong khi z=x[(1,3),:][:,(1,3)]sử dụng lập chỉ mục nâng cao và do đó trả về một bản sao:

In [58]: x=np.arange(16).reshape((4,4))
In [59]: z=x[(1,3),:][:,(1,3)]

In [60]: z
Out[60]: 
array([[ 5,  7],
       [13, 15]])

In [61]: z[0,0]=0

Lưu ý rằng xkhông thay đổi:

In [62]: x
Out[62]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])

Nếu bạn muốn chọn các hàng và cột tùy ý, thì bạn không thể sử dụng cắt cơ bản. Bạn sẽ phải sử dụng lập chỉ mục nâng cao, sử dụng thứ gì đó như x[rows,:][:,columns], ở đâu rowscolumnslà chuỗi. Điều này tất nhiên sẽ cung cấp cho bạn một bản sao, không phải là một khung nhìn, của mảng ban đầu của bạn. Điều này đúng như người ta mong đợi, vì một mảng numpy sử dụng bộ nhớ liền kề (với các bước không đổi) và sẽ không có cách nào để tạo chế độ xem với các hàng và cột tùy ý (vì điều đó đòi hỏi các bước không liên tục).


5

Với numpy, bạn có thể truyền một lát cho từng thành phần của chỉ mục - vì vậy, x[0:2,0:2]ví dụ của bạn ở trên hoạt động.

Nếu bạn chỉ muốn bỏ qua các cột hoặc hàng một cách đồng đều, bạn có thể chuyển các lát có ba thành phần (nghĩa là bắt đầu, dừng, bước).

Một lần nữa, ví dụ của bạn ở trên:

>>> x[1:4:2, 1:4:2]
array([[ 5,  7],
       [13, 15]])

Về cơ bản là: lát ở chiều thứ nhất, bắt đầu ở chỉ số 1, dừng khi chỉ số bằng hoặc lớn hơn 4 và thêm 2 vào chỉ mục trong mỗi lần vượt qua. Tương tự cho chiều thứ hai. Một lần nữa: điều này chỉ hoạt động cho các bước liên tục.

Cú pháp bạn phải thực hiện một cái gì đó khá khác biệt trong nội bộ - những gì x[[1,3]][:,[1,3]]thực sự tạo ra một mảng mới chỉ bao gồm các hàng 1 và 3 từ mảng ban đầu (được thực hiện với x[[1,3]]phần đó), sau đó cắt lại - tạo một mảng thứ ba - chỉ bao gồm cột 1 và 3 của mảng trước.


1
Giải pháp này không hoạt động vì nó đặc trưng cho các hàng / cột tôi đang cố trích xuất. Hãy tưởng tượng tương tự trong ma trận 50x50, khi tôi muốn trích xuất các hàng / cột 5,11,12,32,39,45, không có cách nào để làm điều đó với các lát đơn giản. Xin lỗi nếu tôi không rõ ràng trong câu hỏi của tôi.
levesque


0

Tôi không chắc mức độ hiệu quả của nó nhưng bạn có thể sử dụng phạm vi () để cắt theo cả hai trục

 x=np.arange(16).reshape((4,4))
 x[range(1,3), :][:,range(1,3)] 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.