Unterschied zwischen numpy.array Form (R, 1) und (R,)

319

In numpykehren einige der Operationen in Form zurück (R, 1), andere (R,). Dies macht die Matrixmultiplikation mühsamer, da explizit reshapeerforderlich ist. MWenn wir beispielsweise bei einer Matrix angeben möchten, numpy.dot(M[:,0], numpy.ones((1, R)))wo Rsich die Anzahl der Zeilen befindet (das gleiche Problem tritt natürlich auch spaltenweise auf). Wir werden matrices are not alignedFehler bekommen , da M[:,0]es in Form ist, (R,)aber numpy.ones((1, R))in Form ist (1, R).

Meine Fragen sind also:

  1. Was ist der Unterschied zwischen Form (R, 1)und (R,). Ich weiß buchstäblich, dass es eine Liste von Zahlen und eine Liste von Listen ist, in denen alle Listen nur eine Zahl enthalten. Ich frage mich nur, warum nicht numpyso gestalten , dass die Form bevorzugt wird, (R, 1)anstatt die (R,)Matrixmultiplikation zu vereinfachen.

  2. Gibt es bessere Möglichkeiten für das obige Beispiel? Ohne explizite Umformung wie folgt:numpy.dot(M[:,0].reshape(R, 1), numpy.ones((1, R)))

Clwen
quelle
3
Dies könnte helfen. Nicht mit der Suche nach einer praktischen Lösung.
Keyser
1
Richtige Lösung: numpy.ravel (M [:, 0]) - konvertiert die Form von (R, 1) nach (R,)
Andi R

Antworten:

543

1. Die Bedeutung von Formen in NumPy

Sie schreiben: "Ich weiß buchstäblich, dass es eine Liste von Zahlen und eine Liste von Listen ist, in denen alle Listen nur eine Zahl enthalten", aber das ist ein wenig wenig hilfreich, um darüber nachzudenken.

Der beste Weg, um über NumPy-Arrays nachzudenken, besteht darin, dass sie aus zwei Teilen bestehen, einem Datenpuffer, der nur ein Block von Rohelementen ist, und einer Ansicht die beschreibt, wie der Datenpuffer interpretiert wird.

Wenn wir beispielsweise ein Array mit 12 Ganzzahlen erstellen:

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

aBesteht dann aus einem Datenpuffer, der ungefähr so ​​angeordnet ist:

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

und eine Ansicht, die beschreibt, wie die Daten zu interpretieren sind:

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

Hier bedeutet die Form, (12,) dass das Array durch einen einzelnen Index indiziert wird, der von 0 bis 11 läuft. Wenn wir diesen einzelnen Index ikennzeichnen, asieht das Array konzeptionell folgendermaßen aus:

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 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

Wenn wir ein Array umformen , ändert dies nichts am Datenpuffer. Stattdessen wird eine neue Ansicht erstellt, die eine andere Art der Interpretation der Daten beschreibt. So danach:

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

Das Array bhat den gleichen Datenpuffer wie a, wird jedoch jetzt durch zwei Indizes indiziert , die von 0 bis 2 bzw. 0 bis 3 laufen. Wenn wir die beiden Indizes iund beschriften j, bsieht das Array folgendermaßen aus:

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 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

was bedeutet, dass:

>>> b[2,1]
9

Sie können sehen, dass sich der zweite Index schnell und der erste Index langsam ändert. Wenn Sie dies umgekehrt bevorzugen, können Sie den folgenden orderParameter angeben :

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

was zu einem Array führt, das wie folgt indiziert ist:

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 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

was bedeutet, dass:

>>> c[2,1]
5

Es sollte nun klar sein, was es für ein Array bedeutet, eine Form mit einer oder mehreren Dimensionen der Größe 1 zu haben. Nachher:

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

Das Array dwird durch zwei Indizes indiziert, von denen der erste von 0 bis 11 läuft und der zweite Index immer 0 ist:

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 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

und so:

>>> d[10,0]
10

Eine Dimension der Länge 1 ist (in gewissem Sinne) "frei", daher hindert Sie nichts daran, in die Stadt zu gehen:

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

Geben eines wie folgt indizierten Arrays:

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 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

und so:

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

Weitere Informationen zur Implementierung von Arrays finden Sie in der Dokumentation zu NumPy-Interna .

2. Was ist zu tun?

Schon seit numpy.reshape nur eine neue Ansicht erstellt wird, sollten Sie keine Angst haben, sie bei Bedarf zu verwenden. Es ist das richtige Werkzeug, wenn Sie ein Array auf andere Weise indizieren möchten.

Bei einer langen Berechnung ist es jedoch normalerweise möglich, Arrays mit der "richtigen" Form zu konstruieren und so die Anzahl der Umformen und Transponierungen zu minimieren. Aber ohne den tatsächlichen Kontext zu sehen, der zur Notwendigkeit einer Umformung geführt hat, ist es schwer zu sagen, was geändert werden sollte.

Das Beispiel in Ihrer Frage lautet:

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

das ist aber nicht realistisch. Erstens dieser Ausdruck:

M[:,0].sum()

berechnet das Ergebnis einfacher. Zweitens, hat Spalte 0 wirklich etwas Besonderes? Vielleicht brauchen Sie tatsächlich:

M.sum(axis=0)
Gareth Rees
quelle
33
Dies war äußerst hilfreich, um darüber nachzudenken, wie Arrays gespeichert werden. Vielen Dank! Der Zugriff auf eine Spalte (oder Zeile) einer (2-d) -Matrix zur weiteren Matrixberechnung ist jedoch unpraktisch, da ich die Spalte immer entsprechend umformen muss. Jedes Mal muss ich die Form von (n,) nach (n, 1) ändern.
OfLettersAndNumbers
3
@SammyLee: Verwenden newaxisSie diese Option, wenn Sie eine andere Achse benötigen, z. B. a[:, j, np.newaxis]die jdritte Spalte von aund a[np.newaxis, i]die idritte Zeile.
Gareth Rees
Ich versuche, Indizes zu zeichnen, um ein besseres Verständnis auf Papier durch dieses Modell zu erhalten, und ich scheine es nicht zu verstehen. Wenn ich eine Form 2 x 2 x 4 hätte, verstehe ich, dass die ersten 2 als 0000000011111111 und die letzten 4 verstanden werden können verstanden als 0123012301230123 was passiert mit dem mittleren?
PirateApp
3
Eine einfache Möglichkeit, sich das vorzustellen, ist, dass Numpy hier genau wie erwartet funktioniert, aber Pythons Drucken von Tupeln kann irreführend sein. In diesem (R, )Fall ist die Form von ndarrayein Tupel mit einzelnen Elementen, das von Python mit einem nachgestellten Komma gedruckt wird. Ohne das zusätzliche Komma wäre ein Ausdruck in Klammern nicht eindeutig . A ndarraymit einer einzelnen Dimension kann jedoch als Spaltenvektor der Länge betrachtet werden R. In dem (R, 1)Fall hat das Tupel zwei Elemente, kann also als Zeilenvektor (oder als Matrix mit 1 Zeile Länge) betrachtet werden R.
Michael Yang
1
@ Alex-droidAD: Siehe diese Frage und ihre Antworten.
Gareth Rees
16

Der Unterschied zwischen (R,)und (1,R)ist buchstäblich die Anzahl der Indizes, die Sie verwenden müssen. ones((1,R))ist ein 2-D-Array, das zufällig nur eine Zeile enthält. ones(R)ist ein Vektor. Wenn es für die Variable nicht sinnvoll ist, mehr als eine Zeile / Spalte zu haben, sollten Sie im Allgemeinen einen Vektor verwenden, keine Matrix mit einer Singleton-Dimension.

Für Ihren speziellen Fall gibt es mehrere Optionen:

1) Machen Sie einfach das zweite Argument zu einem Vektor. Folgendes funktioniert gut:

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

2) Wenn Sie matlabähnliche Matrixoperationen wünschen, verwenden Sie matrixstattdessen die Klasse ndarray. Alle Matrizen werden zu 2D-Arrays gezwungen, und der Operator *führt die Matrixmultiplikation anstelle der Elemente durch (Sie benötigen also keinen Punkt). Nach meiner Erfahrung ist dies mehr Mühe, als es wert ist, aber es kann schön sein, wenn Sie an Matlab gewöhnt sind.

Evan
quelle
Ja. Ich hatte ein matlabartigeres Verhalten erwartet. Ich werde mir den matrixUnterricht ansehen . Was ist das Problem für die matrixKlasse BTW?
Clwen
2
Das Problem dabei matrixist, dass es sich nur um 2D handelt und dass Funktionen, für die geschrieben wurde, ndarraymöglicherweise fehlschlagen, wenn sie auf a verwendet werden , da sie den Operator '*' überladen matrix.
Evan
11

Die Form ist ein Tupel. Wenn es nur eine Dimension gibt, ist die Form eine Zahl und nach einem Komma nur leer. Bei mehr als 2 Dimensionen wird nach allen Kommas eine Zahl angezeigt.

# 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
quelle
5

Für die Basis-Array-Klasse sind 2d-Arrays nicht spezieller als 1d- oder 3d-Arrays. Es gibt einige Operationen, bei denen die Dimensionen erhalten bleiben, andere, die sie reduzieren, andere kombinieren oder sogar erweitern.

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

Andere Ausdrücke, die dasselbe Array ergeben

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 begann mit nur 2D-Arrays. Neuere Versionen erlauben mehr Dimensionen, behalten jedoch die Untergrenze von 2 bei. Sie müssen jedoch immer noch auf den Unterschied zwischen einer Zeilenmatrix und einer Spalte mit der Form (1,3)v achten (3,1). Wie oft hast du geschrieben [1,2,3].'? Ich wollte schreibenrow vector und column vector, aber mit dieser 2d-Einschränkung gibt es in MATLAB keine Vektoren - zumindest nicht im mathematischen Sinne von Vektor als 1d.

Haben Sie sich angesehen np.atleast_2d(auch _1d und _3d Versionen)?

hpaulj
quelle
1

1) Der Grund , nicht eine Form zu bevorzugen (R, 1)über (R,)ist , dass es Dinge unnötig kompliziert. Außerdem, warum sollte es vorzuziehen sein, (R, 1)standardmäßig eine Form für einen Vektor der Länge R anstelle von zu haben (1, R)? Es ist besser, es einfach zu halten und explizit zu sein, wenn Sie zusätzliche Abmessungen benötigen.

2) In Ihrem Beispiel berechnen Sie ein äußeres Produkt, sodass Sie dies ohne reshapeAufruf tun können , indem Sie Folgendes verwenden np.outer:

np.outer(M[:,0], numpy.ones((1, R)))
Bogatron
quelle
Danke für die Antwort. 1) M[:,0]erhält im Wesentlichen alle Zeilen mit dem ersten Element, daher ist es sinnvoller, (R, 1)als zu haben (1, R). 2) Es ist nicht immer durch einen np.outerPunkt für eine Matrix in Form (1, R) und dann (R, 1) ersetzbar .
Clwen
1) Ja, das könnte die Konvention sein, aber das macht es unter anderen Umständen weniger bequem. Die Konvention könnte auch sein, dass M [1, 1] ein Formarray (1, 1) zurückgibt, aber das ist normalerweise auch weniger bequem als ein Skalar. Wenn Sie wirklich ein matrixartiges Verhalten wünschen, ist es besser, ein matrixObjekt zu verwenden. 2) Eigentlich np.outerfunktioniert unabhängig davon , ob die Formen sind (1, R), (R, 1)oder eine Kombination aus beiden.
Bogatron
0

Hier gibt es bereits viele gute Antworten. Aber für mich war es schwierig, ein Beispiel zu finden, bei dem die Form oder das Array das gesamte Programm beschädigen kann.

Also hier ist der eine:

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)

Dies wird mit Fehler fehlschlagen:

ValueError: Erwartetes 2D-Array, stattdessen 1D-Array

aber wenn wir hinzufügen reshapezu a:

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

das funktioniert richtig!

Mikhail_Sam
quelle