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.