как перенести массив в диагональ массива питона - PullRequest
0 голосов
/ 16 октября 2018

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

import numpy as np
X = np.array([[0.7513,  0.6991,    0.5472,    0.2575],
          [0.2551,  0.8909,    0.1386,    0.8407],
          [0.5060,  0.9593,    0.1493,    0.2543],
          [0.5060,  0.9593,    0.1493,    0.2543]])
y = np.array([[1,2,3,4]])

Как заменить диагональ X на y.Мы можем написать цикл, но более быстрым способом?

Ответы [ 3 ]

0 голосов
/ 16 октября 2018

Используйте diag_indices для векторизованного решения:

X[np.diag_indices(X.shape[0])] = y

array([[1.    , 0.6991, 0.5472, 0.2575],
       [0.2551, 2.    , 0.1386, 0.8407],
       [0.506 , 0.9593, 3.    , 0.2543],
       [0.506 , 0.9593, 0.1493, 4.    ]])
0 голосов
/ 16 октября 2018

Быстрый и надежный метод: np.einsum:

>>> diag_view = np.einsum('ii->i', X)

Это создает представление по диагонали:

>>> diag_view
array([0.7513, 0.8909, 0.1493, 0.2543])

Это представление доступно для записи:

>>> diag_view[None] = y
>>> X                                                                                                               
array([[1.    , 0.6991, 0.5472, 0.2575],                                                                            
       [0.2551, 2.    , 0.1386, 0.8407],                                                                            
       [0.506 , 0.9593, 3.    , 0.2543],                                                                            
       [0.506 , 0.9593, 0.1493, 4.    ]])                                                                           

Это работает для смежных и несмежных массивов и очень быстро:

contiguous:
loop          21.146424998732982
diag_indices  2.595232878000388
einsum        1.0271988900003635
flatten       1.5372659160002513

non contiguous:
loop          20.133818001340842
diag_indices  2.618005960001028
einsum        1.0305795049989683
Traceback (most recent call last): <- flatten does not work here
...

Как это работает?Под капотом einsum делает продвинутую версию трюка @ Julien: он добавляет шаги arr:

>>> arr.strides
(3200, 16)
>>> np.einsum('ii->i', arr).strides
(3216,)

Можно убедить себя, что это всегда будет работать, пока arr организован по шагам, что имеет место для numy массивов.

Хотя это использование einsum довольно аккуратно, также почти невозможно найти, если кто-то не знает.Так что распространите слово!

Код для воссоздания времени и сбоя:

import numpy as np

n = 100
arr = np.zeros((n, n))
replace = np.ones(n)

def loop():
    for i in range(len(arr)):
        arr[i,i] = replace[i]

def other():
    l = len(arr)
    arr.shape = -1
    arr[::l+1] = replace
    arr.shape = l,l

def di():
    arr[np.diag_indices(arr.shape[0])] = replace

def es():
    np.einsum('ii->i', arr)[...] = replace

from timeit import timeit
print('\ncontiguous:')
print('loop         ', timeit(loop, number=1000)*1000)
print('diag_indices ', timeit(di))
print('einsum       ', timeit(es))
print('flatten      ', timeit(other))

arr = np.zeros((2*n, 2*n))[::2, ::2]
print('\nnon contiguous:')
print('loop         ', timeit(loop, number=1000)*1000)
print('diag_indices ', timeit(di))
print('einsum       ', timeit(es))
print('flatten      ', timeit(other))
0 голосов
/ 16 октября 2018

Это должно быть довольно быстро (особенно для больших массивов, для вашего примера это примерно вдвое медленнее):

arr = np.zeros((4,4))
replace = [1,2,3,4]

l = len(arr)
arr.shape = -1
arr[::l+1] = replace
arr.shape = l,l

Тест на большем массиве:

n = 100
arr = np.zeros((n,n))
replace = np.ones(n)

def loop():
    for i in range(len(arr)):
        arr[i,i] = replace[i]

def other():
    l = len(arr)
    arr.shape = -1
    arr[::l+1] = replace
    arr.shape = l,l

%timeit(loop())
%timeit(other())

14.7 µs ± 112 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
1.55 µs ± 24.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...