Jak zmienić kształt tablic NumPy w Pythonie?

W tym poradniku zgłębisz tajniki funkcji `reshape()` biblioteki NumPy, która umożliwia modyfikację struktury tablic bez wpływu na ich pierwotną zawartość.

Podczas operacji na tablicach NumPy, często pojawia się potrzeba przekształcenia istniejącej tablicy w strukturę o innych wymiarach. Jest to szczególnie użyteczne w procesach przetwarzania danych, gdzie modyfikacja wymiarów jest etapem pośrednim.

Funkcja `reshape()` biblioteki NumPy przychodzi z pomocą, umożliwiając łatwe przekształcanie. W dalszej części tego materiału poznasz składnię tej funkcji, nauczysz się jej wykorzystywać oraz zobaczysz, jak zmieniać tablice na inne wymiary.

Na czym polega zmiana kształtu w tablicach NumPy?

W trakcie pracy z tablicami NumPy często zaczynamy od utworzenia jednowymiarowej tablicy liczb. Następnie może zajść potrzeba przekształcenia jej w tablicę o określonych wymiarach.

Jest to szczególnie przydatne, gdy docelowe wymiary nowej tablicy nie są z góry znane lub są wyznaczane w trakcie działania programu. Może też się zdarzyć, że dany etap obróbki danych wymaga, aby dane wejściowe miały określoną formę.

W takich przypadkach zmiana kształtu jest nieoceniona.

Przykładem może być sytuacja, gdzie mamy wektor – tablicę jednowymiarową zawierającą 6 elementów. Możemy ją przekształcić w tablice o wymiarach 2×3, 3×2, 6×1 i tak dalej.

▶️ Aby móc korzystać z przykładów, musisz mieć zainstalowane środowisko Python oraz bibliotekę NumPy. Jeśli NumPy nie jest jeszcze zainstalowane, sprawdź nasz poradnik instalacyjny.

Po instalacji możesz zaimportować bibliotekę NumPy, używając aliasu `np`: `import numpy as np`.

W kolejnej sekcji przejdziemy do omówienia składni funkcji `reshape()`.

Składnia funkcji `reshape()` NumPy

Oto jak wygląda składnia funkcji `reshape()` w NumPy:

np.reshape(arr, newshape, order="C"|'F'|'A')
  • `arr` to dowolna poprawna tablica NumPy. Jest to tablica, którą chcemy przekształcić.
  • `newshape` określa kształt, jaki ma przyjąć nowa tablica. Może to być liczba całkowita lub krotka.
  • Gdy `newshape` jest liczbą całkowitą, zwracana tablica będzie jednowymiarowa.
  • `order` odnosi się do sposobu odczytywania elementów tablicy.
  • Domyślna wartość to „C”, co oznacza, że elementy z tablicy źródłowej będą odczytywane w kolejności indeksowania zgodnej z językiem C (od 0).
  • „F” oznacza indeksowanie zgodne z językiem Fortran (zaczynając od 1). Natomiast „A” odczytuje elementy w kolejności zależnej od sposobu ułożenia danych w pamięci (C lub Fortran).

Co zwraca `np.reshape()`?

Zwraca tablicę o zmienionym kształcie, bazując na oryginalnej tablicy, o ile jest to możliwe. W przeciwnym wypadku zwraca kopię.

Wspomnieliśmy, że funkcja NumPy `reshape()` stara się zwrócić widok tablicy, gdy tylko jest to możliwe. Omówmy różnice między widokiem a kopią.

Widok a kopia tablic NumPy

Kopia to, jak sama nazwa wskazuje, samodzielna kopia oryginalnej tablicy. Zmiany w kopii nie wpłyną na pierwowzór.

Widok natomiast to tylko „okno” na oryginalną tablicę. To oznacza, że modyfikacja widoku spowoduje zmianę w oryginalnej tablicy i odwrotnie.

Zastosowanie `reshape()` do zmiany tablicy 1D w 2D

#1. Zaczniemy od stworzenia przykładowej tablicy za pomocą `np.arange()`.

Potrzebujemy tablicy 12 liczb, od 1 do 12, którą nazwiemy `arr1`. Funkcja `np.arange()` nie uwzględnia domyślnie punktu końcowego, dlatego ustawimy wartość stop na 13.

Teraz użyjemy poznanej składni i przekształcimy `arr1` (12 elementów) w tablicę 2D o wymiarach (4,3). Nazwijmy ją `arr2` (4 wiersze i 3 kolumny).

import numpy as np

arr1 = np.arange(1,13)
print("Oryginalna tablica, przed zmianą kształtu:\n")
print(arr1)

# Zmiana kształtu tablicy
arr2 = np.reshape(arr1,(4,3))
print("\nTablica po zmianie kształtu:")
print(arr2)

Przyjrzyjmy się oryginalnej i zmodyfikowanej tablicy.

Oryginalna tablica, przed zmianą kształtu:

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

Tablica po zmianie kształtu:
[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]]

Zamiast przekazywać tablicę jako argument funkcji `np.reshape()`, możemy wywołać metodę `.reshape()` na oryginalnej tablicy.

Uruchomienie `dir(arr1)` pokaże listę wszystkich dostępnych metod i atrybutów, które możemy użyć w obiekcie `arr1`.

dir(arr1)

# Wynik 
[
...
...
'reshape'
...
..
]

Powyższy kod pokazuje, że `.reshape()` jest prawidłową metodą dla obiektu tablicy NumPy `arr1`.

▶️ Możemy zatem użyć poniższej, uproszczonej składni, aby zmienić kształt tablic NumPy.

arr.reshape(d0,d1,...,dn)

# gdzie:

# d0, d1,..,dn to wymiary nowej tablicy

# d0 * d1 * ...* dn = N, czyli liczba elementów w tablicy arr

W dalszych przykładach będziemy stosować tę uproszczoną składnię.

#2. Spróbujmy teraz przekształcić nasz 12-elementowy wektor w tablicę 12 x 1.

import numpy as np

arr1 = np.arange(1,13)
print("Oryginalna tablica, przed zmianą kształtu:\n")
print(arr1)

# Zmiana kształtu tablicy
arr3 = arr1.reshape(12,1)
print("\nTablica po zmianie kształtu:")
print(arr3)

Poniżej widzimy, że tablica została zmieniona zgodnie z naszymi oczekiwaniami.

Oryginalna tablica, przed zmianą kształtu:

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

Tablica po zmianie kształtu:
[[ 1]
 [ 2]
 [ 3]
 [ 4]
 [ 5]
 [ 6]
 [ 7]
 [ 8]
 [ 9]
 [10]
 [11]
 [12]]

❔ Jak więc sprawdzić, czy otrzymaliśmy kopię czy widok?

W tym celu można użyć atrybutu `base` na zwróconej tablicy.

  • Jeśli tablica jest kopią, atrybut `base` zwróci `None`.
  • Jeśli tablica jest widokiem, atrybut `base` zwróci oryginalną tablicę.

Sprawdźmy to.

arr3.base
# Wynik
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12])

Jak widzimy, atrybut `base` tablicy `arr3` zwraca oryginalną tablicę. To oznacza, że otrzymaliśmy widok oryginalnej tablicy.

#3. Teraz przekształćmy nasz wektor w inną, poprawną tablicę 2 x 6.

import numpy as np

arr1 = np.arange(1,13)
print("Oryginalna tablica, przed zmianą kształtu:\n")
print(arr1)

# Zmiana kształtu tablicy
arr4 = arr1.reshape(2,6)
print("\nTablica po zmianie kształtu:")
print(arr4)

A oto wynik:

Oryginalna tablica, przed zmianą kształtu:

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

Tablica po zmianie kształtu:
[[ 1  2  3  4  5  6]
 [ 7  8  9 10 11 12]]

W kolejnym punkcie zmienimy `arr1` w tablicę 3D.

Zastosowanie `reshape()` do zmiany tablicy 1D w 3D

Aby zmienić kształt `arr1` na tablicę 3D, ustawmy pożądane wymiary na (1, 4, 3).

import numpy as np

arr1 = np.arange(1,13)
print("Oryginalna tablica, przed zmianą kształtu:\n")
print(arr1)

# Zmiana kształtu tablicy
arr3D = arr1.reshape(1,4,3)
print("\nTablica po zmianie kształtu:")
print(arr3D)

Utworzyliśmy teraz tablicę 3D z tymi samymi 12 elementami, co oryginalna tablica `arr1`.

Oryginalna tablica, przed zmianą kształtu:

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

Tablica po zmianie kształtu:
[[[ 1  2  3]
  [ 4  5  6]
  [ 7  8  9]
  [10 11 12]]]

Jak debugować błędy wartości podczas zmiany kształtu?

Pamiętając składnię, zmiana kształtu tablicy jest możliwa tylko wtedy, gdy iloczyn wymiarów jest równy liczbie elementów w tablicy.

import numpy as np

arr1 = np.arange(1,13)
print("Oryginalna tablica, przed zmianą kształtu:\n")
print(arr1)

# Zmiana kształtu tablicy
arr2D = arr1.reshape(4,4)
print("\nTablica po zmianie kształtu:")
print(arr2D)

Próbujemy przekształcić 12-elementową tablicę w tablicę 4×4 z 16 elementami. Interpreter zgłosi błąd wartości, co widzimy poniżej.

Oryginalna tablica, przed zmianą kształtu:

[ 1  2  3  4  5  6  7  8  9 10 11 12]
-----------------------------------------------------------
ValueError                                
Traceback (most recent call last)
<ipython-input-11-63552bcc8c37> in <module>()
      6 
      7 # Zmiana kształtu tablicy
----> 8 arr2 = arr1.reshape(4,4)
      9 print("\nTablica po zmianie kształtu:")
     10 print(arr2)

ValueError: cannot reshape array of size 12 into shape (4,4)

Aby uniknąć tego typu błędów, można użyć `-1` w miejscu wymiaru, który ma być wywnioskowany automatycznie na podstawie całkowitej liczby elementów.

Na przykład, jeśli znamy `n-1` wymiarów, możemy użyć `-1`, aby wywnioskować `n`-ty wymiar w tablicy.

Mając tablicę 24-elementową, chcemy ją przekształcić w tablicę 3D, z 4 wierszami i 3 kolumnami. Możemy użyć `-1` jako trzeciego wymiaru.

import numpy as np

arr1 = np.arange(1,25)
print("Oryginalna tablica, przed zmianą kształtu:\n")
print(arr1)

# Zmiana kształtu tablicy
arr_res = arr1.reshape(4,3,-1)
print("\nTablica po zmianie kształtu:")
print(arr_res)
print(f"Kształt arr_res: {arr_res.shape}")

Analizując kształt tablicy, zobaczymy, że zmieniona tablica ma wymiar 2 wzdłuż trzeciego wymiaru.

Oryginalna tablica, przed zmianą kształtu:

[ 1  2  3  4  5  6  7  8  9 10 11 12 
13 14 15 16 17 18 19 20 21 22 23 24]

Tablica po zmianie kształtu:
[[[ 1  2]
  [ 3  4]
  [ 5  6]]

 [[ 7  8]
  [ 9 10]
  [11 12]]

 [[13 14]
  [15 16]
  [17 18]]

 [[19 20]
  [21 22]
  [23 24]]]
Kształt arr_res: (4, 3, 2)

Jest to szczególnie przydatne podczas spłaszczania tablicy. O tym dowiesz się w kolejnej sekcji.

Zastosowanie `reshape()` do spłaszczania tablicy

Czasami zachodzi potrzeba powrotu z tablic `N`-wymiarowych do tablicy spłaszczonej. Załóżmy, że chcemy przekształcić obraz w wektor pikseli.

Stwórzmy prosty przykład:

  • Wygenerujemy tablicę obrazu w skali szarości 3×3 (`img_arr`), gdzie piksele mają wartości od 0 do 255.
  • Następnie spłaszczymy `img_arr` i wyświetlimy spłaszczoną tablicę (`flat_arr`).
  • Wyświetlimy też kształty `img_arr` i `flat_arr`, aby zweryfikować wynik.
img_arr = np.random.randint(0, 255, (3,3))
print(img_arr)
print(f"Kształt img_arr: {img_arr.shape}")
flat_arr = img_arr.reshape(-1)
print(flat_arr)
print(f"Kształt flat_arr: {flat_arr.shape}")

Oto rezultat:

[[195 145  77]
 [ 63 193 223]
 [215  43  36]]
Kształt img_arr: (3, 3)

[195 145  77  63 193 223 215  43  36]
Kształt flat_arr: (9,)

Kod pokazuje, że `flat_arr` to wektor 1D z wartościami pikseli, zawierający 9 elementów.

Podsumowanie 👩‍🏫

Podsumujmy to, czego się nauczyliśmy:

  • Użyj `np.reshape(arr, newshape)`, aby zmienić kształt tablicy `arr` na kształt zdefiniowany przez `newshape`. `newshape` to krotka określająca nowe wymiary.
  • Alternatywnie możesz użyć `arr.reshape(d0, d1, …, dn)`, aby zmienić kształt `arr` na `d0 x d1 x … x dn`.
  • Upewnij się, że `d0 * d1 * …* dn = N`, czyli liczbie elementów w oryginalnej tablicy, aby uniknąć błędów.
  • Użyj `-1` w miejscu co najwyżej jednego wymiaru w `newshape`, jeśli chcesz, aby wymiar został automatycznie wywnioskowany.
  • Na koniec, możesz użyć `arr.reshape(-1)` do spłaszczenia tablicy.

Znając już działanie funkcji `reshape()`, możesz poznać zasadę działania funkcji `linspace()` w NumPy.

Jeśli chcesz, możesz wypróbować kody z tego poradnika w notatniku Jupyter. Jeśli potrzebujesz alternatywnego środowiska programistycznego, sprawdź nasz poradnik na temat alternatyw dla Jupytera.