Cięcie tablicy NumPy 2d lub jak wyodrębnić podmacierz mxm z tablicy nxn (n> m)?

174

Chcę wyciąć tablicę NumPy nxn. Chcę wyodrębnić plik dowolny wybór m wierszy i kolumn tej tablicy (tj. Bez żadnego wzorca w liczbie wierszy / kolumn), tworząc nową tablicę mxm. W tym przykładzie powiedzmy, że tablica to 4x4 i chcę wyodrębnić z niej tablicę 2x2.

Oto nasza tablica:

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]]

Linia i kolumny do usunięcia są takie same. Najłatwiej jest wtedy, gdy chcę wyodrębnić podmacierz 2x2, która jest na początku lub na końcu, czyli:

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]])

Ale co, jeśli muszę usunąć inną kombinację wierszy / kolumn? Co się stanie, jeśli muszę usunąć pierwszą i trzecią linię / wiersz, wyodrębniając w ten sposób podmacierz [[5,7],[13,15]]? Może istnieć dowolna kompozycja wierszy / linii. Czytałem gdzieś, że muszę po prostu zindeksować moją tablicę za pomocą tablic / list indeksów zarówno dla wierszy, jak i kolumn, ale to nie działa:

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

Znalazłem jeden sposób, czyli:

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

Po pierwsze, jest to trudne do odczytania, chociaż mogę z tym żyć. Jeśli ktoś ma lepsze rozwiązanie, z pewnością bym to usłyszał.

Inną rzeczą, którą przeczytałem na forum, jest to, że indeksowanie tablic za pomocą tablic zmusza NumPy do wykonania kopii żądanej tablicy, więc podczas traktowania z dużymi tablicami może to stać się problemem. Dlaczego tak jest / jak działa ten mechanizm?

Levesque
źródło

Odpowiedzi:

62

Jak wspomniał Sven, x[[[0],[2]],[1,3]]zwróci wiersze 0 i 2 pasujące do kolumn 1 i 3, podczas gdy x[[0,2],[1,3]]zwróci wartości x [0,1] i x [2,3] w tablicy.

Jest pomocny funkcja robi pierwszy przykład dałem, numpy.ix_. Możesz zrobić to samo, co mój pierwszy przykład x[numpy.ix_([0,2],[1,3])]. Może to uchronić Cię przed koniecznością wpisywania wszystkich tych dodatkowych nawiasów.

Justin Peel
źródło
111

Aby odpowiedzieć na to pytanie, musimy przyjrzeć się, jak działa indeksowanie tablicy wielowymiarowej w Numpy. Najpierw powiedzmy, że masz tablicę xz pytania. Bufor przypisany do xbędzie zawierał 16 rosnących liczb całkowitych od 0 do 15. Jeśli masz dostęp do jednego elementu, powiedzmy x[i,j], NumPy musi określić położenie pamięci tego elementu względem początku bufora. Odbywa się to poprzez obliczenie w efekcie i*x.shape[1]+j(i pomnożenie przez rozmiar int w celu uzyskania rzeczywistego przesunięcia pamięci).

Jeśli wyodrębnisz podtablicę przez podstawowe wycinanie y = x[0:2,0:2], wynikowy obiekt będzie współdzielił bazowy bufor z x. Ale co się stanie, jeśli uzyskasz dostęp y[i,j]? NumPy nie może użyć i*y.shape[1]+jdo obliczenia przesunięcia w tablicy, ponieważ dane należą doy nie są kolejne w pamięci.

NumPy rozwiązuje ten problem, wprowadzając kroki . Podczas obliczania przesunięcia pamięci przy dostępie x[i,j], to, co jest faktycznie obliczane, to i*x.strides[0]+j*x.strides[1](i to już zawiera współczynnik rozmiaru int):

x.strides
(16, 4)

Gdy yekstrahowano jak wyżej, NumPy nie tworzy nowy bufor, ale nie tworzyć nową tablicę Odwoływanie się do tego samego buforu (inaczej ypo prostu być równe x). Nowy obiekt tablica będzie mieć inny kształt, a następnie xi może być inny wyjścia przesunąć do bufora, ale będzie dzielić kroki z x(przynajmniej w tym przypadku):

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

W ten sposób obliczenie przesunięcia pamięci dla y[i,j]da poprawny wynik.

Ale co NumPy powinien zrobić dla czegoś takiego z=x[[1,3]]? Mechanizm strides nie pozwoli na poprawne indeksowanie, jeśli używany jest oryginalny bufor z. NumPy teoretycznie mógłby dodać bardziej wyrafinowany mechanizm niż kroki, ale spowodowałoby to, że dostęp do elementów byłby stosunkowo drogi, w jakiś sposób sprzeczny z całą ideą tablicy. Ponadto widok nie byłby już naprawdę lekkim obiektem.

Jest to szczegółowo omówione w dokumentacji NumPy dotyczącej indeksowania .

Aha, i prawie zapomniałem o swoim rzeczywistym pytaniu: Oto, jak sprawić, by indeksowanie z wieloma listami działało zgodnie z oczekiwaniami:

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

Dzieje się tak, ponieważ tablice indeksów są transmitowane do wspólnego kształtu. Oczywiście w tym konkretnym przykładzie możesz również wykonać podstawowe krojenie:

x[1::2, 1::2]
Sven Marnach
źródło
Powinna istnieć możliwość tworzenia podklas tablic, tak aby można było mieć obiekt „slcie-view”, który mógłby ponownie odwzorować indeksy na oryginalną tablicę. To prawdopodobnie mogłoby zaspokoić potrzeby PO
jsbueno
@jsbueno: to będzie działać dla kodu Pythona, ale nie dla procedur C / Fortran, które Scipy / Numpy zawija. W tych opakowanych procedurach tkwi siła Numpy.
Dat Chu
Więc .. jaka jest różnica między x [[1], [3]], [1,3]] i x [[1,3],:] [:, [1,3]]? Chodzi mi o to, czy istnieje wariant, który jest lepszy w użyciu niż drugi?
levesque
1
@JC: x[[[1],[3]],[1,3]]tworzy tylko jedną nową tablicę, podczas gdy x[[1,3],:][:,[1,3]]kopiuje dwukrotnie, więc użyj pierwszej.
Sven Marnach
@JC: Albo użyj metody z odpowiedzi Justina.
Sven Marnach
13

Nie sądzę, żeby x[[1,3]][:,[1,3]]było to trudne do odczytania. Jeśli chcesz mieć większą jasność co do swoich zamiarów, możesz:

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

Nie jestem ekspertem w krojeniu, ale zazwyczaj, jeśli spróbujesz wyciąć tablicę, a wartości są ciągłe, otrzymasz widok, w którym wartość kroku zostanie zmieniona.

np. w twoich wejściach 33 i 34, mimo że otrzymujesz tablicę 2x2, krok wynosi 4. Tak więc, kiedy indeksujesz następny wiersz, wskaźnik przesuwa się na właściwą pozycję w pamięci.

Oczywiście ten mechanizm nie działa dobrze w przypadku tablicy indeksów. Dlatego numpy będzie musiał wykonać kopię. W końcu wiele innych funkcji matematycznych opiera się na rozmiarze, kroku i ciągłej alokacji pamięci.

Dat Chu
źródło
10

Jeśli chcesz pominąć co drugi wiersz i co drugą kolumnę, możesz to zrobić za pomocą podstawowego krojenia:

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]])

Zwraca widok, a nie kopię twojej tablicy.

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]])

while z=x[(1,3),:][:,(1,3)]używa zaawansowanego indeksowania i dlatego zwraca kopię:

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

Zauważ, że xto się nie zmieniło:

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

Jeśli chcesz wybrać dowolne wiersze i kolumny, nie możesz użyć podstawowego cięcia. Będziesz musiał użyć zaawansowanego indeksowania, używając czegoś takiego jak sekwencje x[rows,:][:,columns], gdzie rowsi columnssą. To oczywiście da ci kopię, a nie widok oryginalnej tablicy. Jest to zgodne z oczekiwaniami, ponieważ tablica numpy używa ciągłej pamięci (ze stałymi krokami) i nie byłoby sposobu na wygenerowanie widoku z dowolnymi wierszami i kolumnami (ponieważ wymagałoby to niestałych kroków).

unutbu
źródło
5

Za pomocą numpy możesz przekazać wycinek dla każdego składnika indeksu - więc twój x[0:2,0:2]przykład powyżej działa.

Jeśli chcesz po prostu równomiernie pomijać kolumny lub wiersze, możesz przekazać plasterki z trzema komponentami (tj. Start, stop, step).

Ponownie, dla twojego przykładu powyżej:

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

To jest w zasadzie: wycinek w pierwszym wymiarze, zaczynając od indeksu 1, zatrzymuj się, gdy indeks jest równy lub większy niż 4 i dodawaj 2 do indeksu w każdym przejściu. To samo dotyczy drugiego wymiaru. Ponownie: działa to tylko dla stałych kroków.

Składnia, którą musisz zrobić wewnętrznie coś zupełnie innego - co x[[1,3]][:,[1,3]] faktycznie robi, to utworzenie nowej tablicy zawierającej tylko wiersze 1 i 3 z oryginalnej tablicy (zrobione z x[[1,3]]częścią), a następnie ponowne podzielenie jej - tworząc trzecią tablicę - zawierającą tylko kolumny 1 i 3 poprzedniej tablicy.

jsbueno
źródło
1
To rozwiązanie nie działa, ponieważ jest specyficzne dla wierszy / kolumn, które próbowałem wyodrębnić. Wyobraź sobie to samo w macierzy 50x50, kiedy chcę wyodrębnić wiersze / kolumny 5,11,12,32,39,45, nie da się tego zrobić za pomocą prostych plasterków. Przepraszam, jeśli nie było jasne w moim pytaniu.
Levesque
3

Mam tutaj podobne pytanie: Pisanie w sub-ndarray z ndarray w najbardziej pytoński sposób. Python 2 .

Zgodnie z rozwiązaniem z poprzedniego postu dla Twojej sprawy rozwiązanie wygląda następująco:

columns_to_keep = [1,3] 
rows_to_keep = [1,3]

Korzystanie ix_:

x[np.ix_(rows_to_keep, columns_to_keep)] 

Który jest:

array([[ 5,  7],
       [13, 15]])
Rafael Valero
źródło
0

Nie jestem pewien, jak wydajne jest to, ale możesz użyć range (), aby wyciąć w obu osiach

 x=np.arange(16).reshape((4,4))
 x[range(1,3), :][:,range(1,3)] 
Valery Marcel
źródło