Разница между формой numpy.array (R, 1) и (R,)

321

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

clwen
источник
3
Это может помочь. Не с поиском практического решения, хотя.
keyser
1
Правильное решение: numpy.ravel (M [:, 0]) - преобразует фигуру из (R, 1) в (R,)
Andi R

Ответы:

546

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)
Гарет Рис
источник
34
Это было чрезвычайно полезно для размышления о том, как хранятся массивы. Спасибо! Доступ к столбцу (или строке) (2-d) матрицы для дальнейшего вычисления матрицы неудобен, поскольку мне всегда приходится соответствующим образом изменять форму столбца. Каждый раз мне приходится менять форму с (n,) на (n, 1).
OfLettersAndNumbers
4
@SammyLee: Используйте , newaxisесли вам нужна другая ось, например , a[:, j, np.newaxis]является jй столбец a, и a[np.newaxis, i]это iя строка.
Гарет Рис
Я пытаюсь построить индексы, чтобы получить лучшее понимание на бумаге с помощью этой модели, и я, кажется, не понимаю, если бы у меня была форма 2 x 2 x 4, я понимаю, что первые 2 можно понять как 0000000011111111, а последние 4 можно понимается как 0123012301230123 что происходит со средним?
PirateApp
3
Простой способ думать об этом заключается в том, что numpy работает именно так, как и ожидалось, но печать кортежей в Python может вводить в заблуждение. В этом (R, )случае форма ndarrayпредставляет собой кортеж с единичными элементами, поэтому печатается Python с запятой. Без лишней запятой это было бы неоднозначно с выражением в скобках . А 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. Все матрицы превращаются в двумерные массивы, а оператор *выполняет умножение матриц вместо поэлементного (так что вам не нужна точка). По моему опыту, это больше проблем, чем оно того стоит, но может быть неплохо, если вы привыкли к Matlab.

Evan
источник
Да. Я ожидал более похожего на Matlab поведения. Я посмотрю на matrixкласс. В чем проблема для matrixкласса BTW?
14:00
2
Проблема в matrixтом, что это только 2D, а также в том, что, поскольку он перегружает оператор '*', функции, написанные для, ndarrayмогут завершиться ошибкой, если используются в 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 начинал с двухмерных массивов. Более новые версии допускают больше измерений, но сохраняют нижнюю границу 2. Но вы все равно должны обратить внимание на разницу между матрицей строк и столбцом один, один с формой (1,3)v (3,1). Как часто ты писал [1,2,3].'? Я собирался написать row vectorи column vector, но с этим 2d ограничением, в MATLAB нет векторов - по крайней мере, не в математическом смысле вектора как 1d.

Вы смотрели np.atleast_2d(также версии _1d и _3d)?

hpaulj
источник
1

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

2) Для вашего примера вы вычисляете внешний продукт, так что вы можете сделать это без reshapeвызова, используя np.outer:

np.outer(M[:,0], numpy.ones((1, R)))
bogatron
источник
Спасибо за ответ. 1) M[:,0]по существу получает все строки с первым элементом, поэтому имеет больше смысла иметь, (R, 1)чем (1, R). 2) Это не всегда можно заменить np.outer, например, точкой для матрицы в форме (1, R), затем (R, 1).
14:00
1) Да, это может быть соглашение, но это делает его менее удобным в других обстоятельствах. Соглашение также может заключаться в том, что M [1, 1] возвращает массив shape (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)

это работает правильно!

Mikhail_Sam
источник