Ответ 1
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 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
Если мы reshape массив, это не изменит буфер данных. Вместо этого он создает новое представление, которое описывает другой способ интерпретации данных. Итак, после:
>>> 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)