Умножить два вектора и получить матрицу

У меня есть два массива:

arr1 = np.array([1, 2])
arr2 = np.array([3, 4, 5])

Kак мне их умножить и получить матрицу:

[
  [3, 4, 5],
  [6, 8, 10]
]

Я использую следующее решение:

np.dot(arr1.reshape((2, 1)), arr2.reshape((1, 3)))

Но на мой взгляд это не удобно. Какие существуют более удобные решения?


Ответы (3 шт):

Автор решения: MaxU
res = np.multiply.outer(arr1, arr2)

результат:

In [110]: res
Out[110]: 
array([[ 3,  4,  5],
       [ 6,  8, 10]])
→ Ссылка
Автор решения: strawdog
res = (np.array([arr2,]*2).T*arr1).T
array([[ 3,  4,  5],
       [ 6,  8, 10]])
→ Ссылка
Автор решения: Vitalizzare

Этот случай реализуется через бродкастинг - совмещение и распространение размерностей массивов при арифметических операциях. Для этого нужно развести векторы по разным размерностям путем добавления фиктивной оси к первому вектору. Тогда при умножении второго вектора, его размерность будет соотнесена с новой фиктивной осью, и поскольку размерность первого вектора вдоль этой оси равна 1, то найденное там единственное число будет растянуто/распространено на размерность второго вектора:

import numpy as np

arr1 = np.array([1, 2])
arr2 = np.array([3, 4, 5])

answer = arr1[:, np.newaxis] * arr2

# array([[ 3,  4,  5],
#        [ 6,  8, 10]])

Более наглядно и управляемо это реализуется в тензорной форме через numpy.einsum, где мы прямо указываем, где и какие индексы должны в итоге находиться:

answer = np.einsum('i,j->ij', arr1, arr2)

Удобство второго варианта становится явным в комплексном случае, когда нужно перемножить несколько массивов, правильно совместив их размерности.

Пример для наглядности: допустим, у нас есть количество просмотров медийного продукта по дням и часам, скидка в зависимости от времени и базовая цена просмотра по разным слотам. Найдем выручку по дню, часу и слоту:

import numpy as np

days, hours, slots = 7, 24, 3
# количество просмотров в конкретный день и час
day_hour_views = np.arange(days*hours).reshape(days, hours)
hour_discount = 0.05 * np.arange(hours) # скидка в зависимости от времени
slot_price = np.arange(slots) # стоимость просмотра на рекламном слоте

revenue = np.einsum('dh,h,s->dhs', day_hour_views, hour_discount, slot_price)

# sanity check
assert revenue.shape == (days, hours, slots)
d, h, s = map(np.random.randint, revenue.shape)
assert np.isclose(
    revenue[d, h, s], day_hour_views[d, h]*hour_discount[h]*slot_price[s])

Здесь происходит два варианта бродкастинга:

  • совмещаем ось времени первых двух массивов, где размерность совпадает, что эквивалентно умножению tmp = day_hour_views * hour_discount.
  • к предыдущему результату добавляем новую ось для слотов, после чего умножаем на третий массив revenue = tmp[..., np.newaxis] * slot_price

Эти шаги упакованы во фразе 'dh,h,s->dhs', которая передается первым аргументов в einsum. Выбор букв d, h, s произвольный, но важна структура индексов.

→ Ссылка