Мы могли бы использовать ранжированный массив, умноженный на маску нижнего треугольника -
In [44]: n = 5
In [45]: np.arange(1,n+1)[:,None]*np.tri(n,dtype=bool)
Out[45]:
array([[1, 0, 0, 0, 0],
[2, 2, 0, 0, 0],
[3, 3, 3, 0, 0],
[4, 4, 4, 4, 0],
[5, 5, 5, 5, 5]])
Он легко переносим на numexpr
, чтобы использовать многоядерные системы для больших данных, учитывая арифметически-ориентированный характер -
import numexpr as ne
ne.evaluate('A*B',{'A':np.arange(1,n+1)[:,None],'B':np.tri(n,dtype=bool)})
Сравнительный анализ
Включая все опубликованные решения здесь.
Сценарий сравнительного анализа -
import numpy as np
import perfplot
import numexpr as ne
def numexpr_range_broadcast(n):
return ne.evaluate('A*B',{'A':np.arange(1,n+1)[:,None],'B':np.tri(n,dtype=bool)})
def where_method(n):
x = np.arange(1,n+1)
return np.where(x[:,None]>=x,x[:,None],0)
perfplot.show(
setup=lambda n: n,
kernels=[
lambda n: where_method(n),
lambda n: np.tril(np.broadcast_to(np.arange(1, n+1)[:, None], (n, n))),
lambda n: np.arange(1,n+1)[:,None]*np.tri(n,dtype=bool),
lambda n: numexpr_range_broadcast(n),
],
labels=['where','tril_broadacast','range_broadcast','numexpr_range_broadcast'],
n_range=[10, 20, 50, 80, 100, 200, 500, 800, 1000, 2000, 5000],
xlabel='n',
logx=True,
logy=True,
)
Вывод -
Следовательно, на более низком n's
чуть раньше 100
, np.where
на основе выигрывает его, а на большем n's
, numexpr
светит.