Różnica między kształtem tablicy numpy. (R, 1) i (R,)

319

W numpyniektóre operacje wracają do formy, (R, 1)ale niektóre powracają (R,). To sprawi, że mnożenie macierzy będzie bardziej nużące, ponieważ reshapewymagane jest jawne . Na przykład, biorąc pod uwagę macierz M, jeśli chcemy zrobić, numpy.dot(M[:,0], numpy.ones((1, R)))gdzie Rjest liczba wierszy (oczywiście ten sam problem występuje również w kolumnach). Otrzymamy matrices are not alignedbłąd, ponieważ M[:,0]jest w formie, (R,)ale numpy.ones((1, R))jest w formie (1, R).

Więc moje pytania to:

  1. Jaka jest różnica między kształtem (R, 1)a (R,). Wiem dosłownie, że to lista liczb i lista list, gdzie cała lista zawiera tylko liczbę. Zastanawiam się, dlaczego nie zaprojektować numpytak, aby sprzyjał kształtowi, (R, 1)zamiast (R,)ułatwiać mnożenie macierzy.

  2. Czy istnieją lepsze sposoby na powyższy przykład? Bez wyraźnego przekształcania w ten sposób:numpy.dot(M[:,0].reshape(R, 1), numpy.ones((1, R)))

clwen
źródło
3
To może pomóc. Jednak nie ze znalezieniem praktycznego rozwiązania.
keyser
1
Właściwe rozwiązanie: numpy.ravel (M [:, 0]) - konwertuje kształt z (R, 1) na (R,)
Andi R

Odpowiedzi:

544

1. Znaczenie kształtów w NumPy

Piszesz: „Wiem dosłownie jest to lista liczb i lista list, gdzie cała lista zawiera tylko liczbę”, ale to trochę niepomocny sposób, aby o tym myśleć.

Najlepszym sposobem myślenia o tablicach NumPy jest to, że składają się one z dwóch części, bufora danych, który jest tylko blokiem surowych elementów oraz widoku opisującego sposób interpretacji bufora danych.

Na przykład, jeśli utworzymy tablicę 12 liczb całkowitych:

>>> a = numpy.arange(12)
>>> a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

Następnie askłada się z bufora danych, ułożonego mniej więcej tak:

┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
  0   1   2   3   4   5   6   7   8   9  10  11 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

oraz widok opisujący sposób interpretacji danych:

>>> a.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : True
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False
>>> a.dtype
dtype('int64')
>>> a.itemsize
8
>>> a.strides
(8,)
>>> a.shape
(12,)

Tutaj kształt (12,) oznacza, że ​​tablica jest indeksowana przez pojedynczy indeks, który biegnie od 0 do 11. Koncepcyjnie, jeśli oznaczymy ten pojedynczy indeks i, tablica awygląda następująco:

i= 0    1    2    3    4    5    6    7    8    9   10   11
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
  0   1   2   3   4   5   6   7   8   9  10  11 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

Jeśli przekształcimy tablicę, nie zmieni to bufora danych. Zamiast tego tworzy nowy widok, który opisuje inny sposób interpretacji danych. Więc później:

>>> b = a.reshape((3, 4))

tablica bma taki sam bufor danych jak a, ale teraz jest indeksowana przez dwa indeksy, które działają odpowiednio od 0 do 2 i od 0 do 3. Jeśli oznaczymy dwa indeksy ii j, tablica bwygląda następująco:

i= 0    0    0    0    1    1    1    1    2    2    2    2
j= 0    1    2    3    0    1    2    3    0    1    2    3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
  0   1   2   3   4   5   6   7   8   9  10  11 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

co oznacza że:

>>> b[2,1]
9

Widać, że drugi indeks zmienia się szybko, a pierwszy indeks zmienia się powoli. Jeśli wolisz, aby było odwrotnie, możesz określić orderparametr:

>>> c = a.reshape((3, 4), order='F')

co powoduje tablicę indeksowaną w ten sposób:

i= 0    1    2    0    1    2    0    1    2    0    1    2
j= 0    0    0    1    1    1    2    2    2    3    3    3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
  0   1   2   3   4   5   6   7   8   9  10  11 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

co oznacza że:

>>> c[2,1]
5

Teraz powinno być jasne, co to znaczy, że tablica ma kształt o co najmniej jednym wymiarze wielkości 1. Po:

>>> d = a.reshape((12, 1))

tablica djest indeksowana przez dwa indeksy, z których pierwszy biegnie od 0 do 11, a drugi indeks zawsze wynosi 0:

i= 0    1    2    3    4    5    6    7    8    9   10   11
j= 0    0    0    0    0    0    0    0    0    0    0    0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
  0   1   2   3   4   5   6   7   8   9  10  11 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

a więc:

>>> d[10,0]
10

Wymiar długości 1 jest „wolny” (w pewnym sensie), więc nic nie powstrzymuje cię przed pójściem do miasta:

>>> e = a.reshape((1, 2, 1, 6, 1))

podając tablicę indeksowaną w ten sposób:

i= 0    0    0    0    0    0    0    0    0    0    0    0
j= 0    0    0    0    0    0    1    1    1    1    1    1
k= 0    0    0    0    0    0    0    0    0    0    0    0
l= 0    1    2    3    4    5    0    1    2    3    4    5
m= 0    0    0    0    0    0    0    0    0    0    0    0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
  0   1   2   3   4   5   6   7   8   9  10  11 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

a więc:

>>> e[0,1,0,0,0]
6

Zobacz dokumentację takie wewnętrzne NumPy aby uzyskać więcej informacji o tym, jak tablice zostały zaimplementowane.

2. Co robić?

Ponieważ numpy.reshapepo prostu tworzy nowy widok, nie powinieneś się bać korzystania z niego w razie potrzeby. To właściwe narzędzie do użycia, gdy chcesz zindeksować tablicę w inny sposób.

Jednak w długim obliczeniu zwykle możliwe jest ustawienie konstruowania tablic o „właściwym” kształcie, a tym samym zminimalizowanie liczby przekształceń i transpozycji. Ale nie widząc faktycznego kontekstu, który doprowadził do konieczności przekształcenia, trudno powiedzieć, co należy zmienić.

Przykład w twoim pytaniu to:

numpy.dot(M[:,0], numpy.ones((1, R)))

ale to nie jest realistyczne. Po pierwsze, to wyrażenie:

M[:,0].sum()

oblicza wynik prościej. Po drugie, czy naprawdę jest coś specjalnego w kolumnie 0? Być może tak naprawdę potrzebujesz:

M.sum(axis=0)
Gareth Rees
źródło
33
Było to niezwykle pomocne w myśleniu o tym, jak przechowywane są tablice. Dziękuję Ci! Dostęp do kolumny (lub wiersza) macierzy (2-d) w celu dalszego obliczenia macierzy jest jednak niewygodny, ponieważ zawsze muszę odpowiednio przekształcać kolumnę. Za każdym razem muszę zmienić kształt z (n,) na (n, 1).
OfLettersAndNumbers
3
@SammyLee: Użyj, newaxisjeśli potrzebujesz innej osi, na przykład a[:, j, np.newaxis]jest to jkolumna th ai a[np.newaxis, i]jest to iwiersz.
Gareth Rees
próbuję wykreślić wskaźniki, aby uzyskać lepsze zrozumienie na papierze za pomocą tego modelu i nie wydaje mi się, żebym je zrozumiał, gdybym miał kształt 2 x 2 x 4, rozumiem, że pierwsze 2 można rozumieć jako 0000000011111111, a ostatnie 4 mogą być rozumiany jako 0123012301230123 co dzieje się ze środkowym?
PirateApp,
3
Łatwym sposobem na pomyślenie o tym jest to, że numpy działa tutaj dokładnie zgodnie z oczekiwaniami, ale drukowanie krotek w Pythonie może być mylące. W tym (R, )przypadku kształt ndarrayjest krotką z pojedynczymi elementami, więc jest drukowany przez Python za pomocą przecinka. Bez dodatkowego przecinka byłby niejednoznaczny z wyrażeniem w nawiasach . A ndarrayz jednym wymiarem może być traktowane jako wektor długości kolumny R. W tym (R, 1)przypadku krotka ma dwa elementy, więc można ją traktować jako wektor wiersza (lub macierz o 1 rzędzie długości R.
Michael Yang,
1
@ Alex-droidAD: Zobacz to pytanie i odpowiedzi.
Gareth Rees,
16

Różnica między (R,)i (1,R)to dosłownie liczba wskaźników, których należy użyć. ones((1,R))to tablica 2-D, która ma tylko jeden wiersz. ones(R)jest wektorem. Zasadniczo, jeśli nie ma sensu, aby zmienna miała więcej niż jeden wiersz / kolumnę, powinieneś użyć wektora, a nie macierzy o wymiarze singletonu.

W konkretnym przypadku istnieje kilka opcji:

1) Po prostu przekształć drugi argument w wektor. Następujące działa dobrze:

    np.dot(M[:,0], np.ones(R))

2) Jeśli chcesz operacji typu Matlab, takich jak macierz, użyj klasy matrixzamiast ndarray. Wszystkie macierze są zmuszone do tworzenia tablic 2-D, a operator *dokonuje mnożenia macierzy zamiast elementów (więc nie potrzebujesz kropki). Z mojego doświadczenia wynika, że ​​jest to więcej kłopotów niż warte, ale może być fajnie, jeśli jesteś przyzwyczajony do Matlaba.

Evan
źródło
Tak. Spodziewałem się bardziej matlabskiego zachowania. Rzucę okiem na matrixzajęcia. Jaki problem ma matrixklasa BTW?
clwen
2
Problem matrixpolega na tym, że jest on tylko 2D, a także dlatego, że przeciąża operatora „*”, funkcje napisane dla ndarraymogą się nie powieść, jeśli zostaną użyte na matrix.
Evan
11

Kształt jest krotką. Jeśli jest tylko jeden wymiar, kształt będzie miał jedną cyfrę i będzie pusty po przecinku. W przypadku wymiarów 2+ po wszystkich przecinkach pojawi się liczba.

# 1 dimension with 2 elements, shape = (2,). 
# Note there's nothing after the comma.
z=np.array([  # start dimension
    10,       # not a dimension
    20        # not a dimension
])            # end dimension
print(z.shape)

(2,)

# 2 dimensions, each with 1 element, shape = (2,1)
w=np.array([  # start outer dimension 
    [10],     # element is in an inner dimension
    [20]      # element is in an inner dimension
])            # end outer dimension
print(w.shape)

(2,1)

Katie Jergens
źródło
5

Dla podstawowej klasy macierzy tablice 2d nie są bardziej specjalne niż tablice 1d lub 3d. Niektóre operacje zachowują wymiary, niektóre zmniejszają je, inne łączą, a nawet rozszerzają.

M=np.arange(9).reshape(3,3)
M[:,0].shape # (3,) selects one column, returns a 1d array
M[0,:].shape # same, one row, 1d array
M[:,[0]].shape # (3,1), index with a list (or array), returns 2d
M[:,[0,1]].shape # (3,2)

In [20]: np.dot(M[:,0].reshape(3,1),np.ones((1,3)))

Out[20]: 
array([[ 0.,  0.,  0.],
       [ 3.,  3.,  3.],
       [ 6.,  6.,  6.]])

In [21]: np.dot(M[:,[0]],np.ones((1,3)))
Out[21]: 
array([[ 0.,  0.,  0.],
       [ 3.,  3.,  3.],
       [ 6.,  6.,  6.]])

Inne wyrażenia, które dają tę samą tablicę

np.dot(M[:,0][:,np.newaxis],np.ones((1,3)))
np.dot(np.atleast_2d(M[:,0]).T,np.ones((1,3)))
np.einsum('i,j',M[:,0],np.ones((3)))
M1=M[:,0]; R=np.ones((3)); np.dot(M1[:,None], R[None,:])

MATLAB zaczął od tylko tablic 2D. Nowsze wersje pozwalają na więcej wymiarów, ale zachowują dolną granicę 2. Ale wciąż musisz zwrócić uwagę na różnicę między macierzą wierszy a kolumną pierwszą, taką o kształcie (1,3)v (3,1). Jak często pisałeś [1,2,3].'? Zamierzałem pisać row vectori column vector, ale z tym ograniczeniem 2d, w MATLAB-ie nie ma żadnych wektorów - przynajmniej nie w matematycznym znaczeniu wektora jako 1d.

Czy przeglądałeś np.atleast_2d(także wersje _1d i _3d)?

hpaulj
źródło
1

1) Powodem, dla którego nie preferuję kształtu (R, 1)ponad, (R,)jest to, że niepotrzebnie komplikuje rzeczy. Poza tym, dlaczego lepiej byłoby mieć (R, 1)domyślnie kształt dla wektora długości-R zamiast (1, R)? Lepiej jest zachować prostotę i być wyraźnym, gdy potrzebujesz dodatkowych wymiarów.

2) Na przykład, obliczasz produkt zewnętrzny, więc możesz to zrobić bez reshapepołączenia, używając np.outer:

np.outer(M[:,0], numpy.ones((1, R)))
bogatron
źródło
Dziękuję za odpowiedź. 1) M[:,0]zasadniczo pobiera wszystkie wiersze z pierwszym elementem, więc sensowniej jest mieć (R, 1)niż (1, R). 2) Nie zawsze można go zastąpić np.outernp. Kropką dla matrycy w kształcie (1, R), a następnie (R, 1).
clwen
1) Tak, to może być konwencja, ale to czyni ją mniej wygodną w innych okolicznościach. Konwencja może również polegać na zwracaniu przez M [1, 1] tablicy kształtu (1, 1), ale jest to zwykle mniej wygodne niż skalar. Jeśli naprawdę chcesz zachowywać się jak macierz, lepiej byłoby użyć matrixobiektu. 2) Faktycznie, np.outerdziała niezależnie od tego, czy kształty są (1, R), (R, 1)lub kombinacja tych dwóch.
bogatron
0

Tutaj jest już wiele dobrych odpowiedzi. Ale dla mnie trudno było znaleźć jakiś przykład, w którym kształt lub tablica mogą złamać cały program.

Oto jeden z nich:

import numpy as np
a = np.array([1,2,3,4])
b = np.array([10,20,30,40])


from sklearn.linear_model import LinearRegression
regr = LinearRegression()
regr.fit(a,b)

Błąd zakończy się niepowodzeniem:

ValueError: Oczekiwana tablica 2D, zamiast tego dostała tablicę 1D

ale jeśli dodamy reshapedo a:

a = np.array([1,2,3,4]).reshape(-1,1)

to działa poprawnie!

Michaił_Sam
źródło