Різниця між numpy.array формою (R, 1) і (R,)


319

В numpyдеякі операції повернення в формі , (R, 1)але деякі повернення (R,). Це зробить множення матриць більш втомливим, оскільки reshapeпотрібне явне . Наприклад, маючи матрицю M, якщо ми хочемо зробити, numpy.dot(M[:,0], numpy.ones((1, R)))де Rкількість рядків (звичайно, те саме питання виникає і у стовпцях). Ми отримаємо matrices are not alignedпомилку, оскільки M[:,0]є у формі, (R,)але numpy.ones((1, R))знаходиться у формі (1, R).

Тому мої запитання:

  1. Яка різниця між формою (R, 1)та (R,). Я знаю буквально це список номерів та список списків, де весь список містить лише число. Цікаво, чому б не спроектувати numpyтак, щоб вона надала перевагу формі (R, 1)замість (R,)легшого множення матриці.

  2. Чи є кращі способи для наведеного вище прикладу? Без чітко переробленої форми на зразок цього:numpy.dot(M[:,0].reshape(R, 1), numpy.ones((1, R)))


3
Це може допомогти. Не з пошуку практичного рішення.
клавішник

1
Правильне рішення: numpy.ravel (M [:, 0]) - перетворює форму з (R, 1) в (R,)
Andi R

Відповіді:


544

1. Значення форм у NumPy

Ви пишете: "Я буквально знаю, що це список номерів і список списків, де весь список містить лише число", але це трохи не корисний спосіб подумати про це.

Найкращий спосіб подумати про масиви NumPy - це те, що вони складаються з двох частин, буфера даних, який є лише блоком необроблених елементів, і подання, яке описує, як інтерпретувати буфер даних.

Наприклад, якщо ми створимо масив з 12 цілих чисел:

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

Потім aскладається з буфера даних, розташованого приблизно так:

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

і представлення, яке описує, як інтерпретувати дані:

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

Тут форма (12,) означає, що масив індексується одним індексом, який працює від 0 до 11. Концептуально, якщо ми позначимо цей єдиний індекс i, масив aвиглядає так:

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

Якщо ми переглянемо масив, це не змінить буфер даних. Натомість він створює новий погляд, який описує інший спосіб інтерпретації даних. Отже після:

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

масив bмає той самий буфер даних, що і a, але тепер він індексується двома індексами, які працюють від 0 до 2 і 0 до 3 відповідно. Якщо ми позначимо два індекси iта j, масив bвиглядає так:

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

це означає, що:

>>> b[2,1]
9

Ви можете бачити, що другий індекс змінюється швидко, а перший індекс повільно змінюється. Якщо ви хочете, щоб це було навпаки, ви можете вказати orderпараметр:

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

що призводить до масиву, індексованого так:

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

це означає, що:

>>> c[2,1]
5

Тепер має бути зрозуміло, що означає для масиву форму з одним або кількома розмірами розміром 1. Після:

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

масив dіндексується двома індексами, перший з яких працює від 0 до 11, а другий індекс завжди дорівнює 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 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

і так:

>>> d[10,0]
10

Розмір довжини 1 "вільний" (в деякому сенсі), тому нічого не заважає вам їхати в місто:

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

надання масиву, індексованого так:

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

і так:

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

Докладніше про те, як реалізуються масиви, перегляньте документацію щодо внутрішніх ресурсів NumPy .

2. Що робити?

Оскільки numpy.reshapeтільки створюється новий погляд, вам не слід боятися його використовувати, коли це необхідно. Це правильний інструмент, який потрібно використовувати, коли ви хочете індексувати масив по-іншому.

Однак у довгих обчисленнях, як правило, можливо домогтися спорудження масивів з "правильною" формою в першу чергу і таким чином мінімізувати кількість змін і транспозицій. Але не бачачи фактичного контексту, який призвів до необхідності переформування, важко сказати, що слід змінити.

Приклад вашого запитання:

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

але це не реально. По-перше, це вираз:

M[:,0].sum()

обчислює результат простіше. По-друге, чи дійсно є щось особливе у стовпці 0? Можливо, що вам насправді потрібно:

M.sum(axis=0)

33
Це було надзвичайно корисно для роздумів про те, як зберігаються масиви. Дякую! Доступ до стовпця (або рядка) (2-d) матриці для подальшого обчислення матриці незручно, хоча оскільки мені завжди доводиться перетворювати стовпчик відповідно. Щоразу мені потрібно змінювати форму з (n,) на (n, 1).
OfLettersAndNumbers

3
@SammyLee: Використовуйте, newaxisякщо вам потрібна інша вісь, наприклад, a[:, j, np.newaxis]це jстовпець aі a[np.newaxis, i]є другий iрядок.
Гарет Різ

Я намагаюсь побудувати індекси, щоб краще зрозуміти на папері цю модель, і я, здається, не розумію, якби у мене була форма 2 x 2 x 4, я розумію, що перші 2 можна зрозуміти як 0000000011111111, а останні 4 можна розуміється як 0123012301230123, що відбувається із середнім?
PirateApp

3
Простий спосіб подумати про це - це те, що нумій працює саме так, як очікувалося, але друк кортежів Python може ввести в оману. У (R, )випадку, форма фігури ndarrayє кортежем з одиничними елементами, тому друкується Python із заднім комом. Без зайвої коми це було б неоднозначно з виразом у дужках . A ndarrayз одним виміром може бути як стовпець вектора довжини R. У (R, 1)випадку, кортеж має два елементи, тому його можна розглядати як векторний рядок (або матрицю з 1 рядком довжини R.
Майкл Ян

1
@ Alex-droidAD: Дивіться це питання та його відповіді.
Гарет Різ

16

Різниця між (R,)і (1,R)є буквально кількістю індексів, які потрібно використовувати. ones((1,R))- це двовимірний масив, який, мабуть, має лише один рядок. ones(R)є вектором. Як правило, якщо змінна не має більше, ніж один рядок / стовпець, ви повинні використовувати вектор, а не матрицю з однотонним виміром.

Для вашого конкретного випадку є кілька варіантів:

1) Просто зробіть другий аргумент вектором. Наступне добре працює:

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

2) Якщо ви хочете, як матричні операції, як матриця, використовуйте клас matrixзамість ndarray. Усі матриці змушені бути двовимірними масивами, а оператор *робить матричне множення замість елементарних (так що вам не потрібна крапка). На мій досвід, це більше клопоту, ніж це варто, але це може бути приємно, якщо ви звикли до матлаба.


Так. Я очікував більш схожої на поведінку математики. Я погляну на matrixклас. У чому біда matrixкласу BTW?
clwen

2
Проблема matrixполягає в тому, що це лише 2D, а також через те, що він перевантажує оператор '*', функції, записані для, ndarrayможуть не працювати при використанні на a matrix.
Еван

11

Форма - кортеж. Якщо є лише 1 розмір, форма буде одним числом і просто комою. Для 2+ розмірів буде число після всіх коми.

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


5

Для базового класу масивів 2d масиви не більш особливі, ніж 1d або 3d. Є деякі операції збереження розмірів, деякі, що їх зменшують, інші комбінують або навіть розширюють.

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

Інші вирази, які дають той самий масив

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 почався лише з 2D-масивів. Більш новіші версії дозволяють отримати більше розмірів, але зберігають нижню межу 2. Але все ж слід звернути увагу на різницю між рядковою матрицею та першим стовпцем з формою (1,3)v (3,1). Як часто ви писали [1,2,3].'? Я збирався писати row vectorі column vector, але з цим обмеженням 2d, в MATLAB немає жодних векторів - принаймні, не в математичному сенсі вектора, як 1d.

Ви переглядали np.atleast_2d(також версії _1d та _3d)?


1

1) Причина не віддати перевагу форму (R, 1)більш (R,), що це зайве ускладнює. Крім того, чому було б бажано мати форму (R, 1)за замовчуванням для вектора довжини R замість (1, R)? Краще зберігати його просто та чітко, коли вам потрібні додаткові параметри.

2) Для вашого прикладу ви обчислюєте зовнішній продукт, щоб ви могли це зробити без reshapeдзвінка, використовуючи np.outer:

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

Дякую за відповідь. 1) M[:,0]по суті отримує всі рядки з першим елементом, тому має більше сенсу мати (R, 1)ніж (1, R). 2) Його не завжди можна замінити np.outer, наприклад, крапкою для матриці у формі (1, R), тоді (R, 1).
clwen

1) Так, це може бути конвенція, але це робить її менш зручною в інших обставинах. Умовою також може бути M [1, 1], щоб повернути масив форми (1, 1), але це, як правило, менш зручно, ніж скаляр. Якщо ви дійсно хочете поведінку, схожу на матрицю, тоді вам краще використовувати matrixоб’єкт. 2) На насправді, np.outerпрацює незалежно від того , чи є форма (1, R), (R, 1)або комбінації з двох.
богатрон

0

Тут вже багато хороших відповідей. Але мені важко було знайти якийсь приклад, де форма чи масив можуть зламати всю програму.

Отже ось один:

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)

Це не вдасться з помилкою:

ValueError: Очікуваний 2D масив, натомість отримав 1D масив

але якщо ми додамо reshapeдо a:

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

це працює правильно!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.