47598


ある整数Nを与えたら、以下のようなN×N-1の行列Zを出力するようなプログラムを書きたいです。

[code]

    Z = [[1 2 3 ... N]
         [0 2 3 ... N]
         [0 1 3 ... N]
          ...
         [0 1 2 ... N]
         [0 1 2 ... N-1]]
    
[/code]

つまり、i番目の行は、iを除くすべての値を持っているような行列です。単純なコードを書くと

[code]

    Z = [[j for j in range(N) if j != i] for i in range(N)]
    
[/code]

となるのですが、Nが大きくなると時間がかかってしまいます。

このようなことを、numpyで効率的に書くにはどうすれば良いでしょうか？


CC BY-SA 4.0
2018-08-17T23:39:46.480
47592
2018-08-19T03:01:44.450
2018-08-18T04:54:36.307
12874
12874
question
python numpy
numpyで効率的に行列を作りたい


こんな感じでどうでしょうか。for文は使わないようにしてみました。

[code]

    import numpy as np
    
    N = 4
    Z = np.zeros((N, N), dtype=int)
    Z += np.arange(N)
    Z = Z[np.eye(N, N) == 0].reshape(N, N - 1)
    
[/code]


CC BY-SA 4.0
2018-08-18T07:14:49.543
47598
2018-08-18T07:25:09.563
2018-08-18T07:25:09.563
7754
7754
47592
answer


スライスを使う例です。  
`for` ループを少しでも速くするために、ループ内で使う変数のスコープを出来るだけ小さくしています（つまり小さな関数の中に入れています）。

[code]

    def by_np_slice():
        nums = np.arange(0, N, dtype=np.int)
        result = np.zeros([N, N-1], dtype=np.int)
    
        for i, a in enumerate(result):
            a[:i] = nums[:i]
            a[i:] = nums[i+1:]
    
        return result
    
[/code]

* * *

質問の出力例、  
`Z = [[1 2 3 ... N] 中略 [0 1 2 ... N-1]]` は、  
`Z = [[1 2 3 ... N-1] 中略 [0 1 2 ... N-2]]`  
が正しいのかなと思い、上はそのつもりで書いてあります。


CC BY-SA 4.0
2018-08-18T08:26:11.633
47599
2018-08-18T08:26:11.633
3054
47592
answer


こっちのほうがちょっとだけ速そう

[code]

    mask = np.tile([False] + [True] * N, N)
    x = np.tile(np.arange(N), N)
    x = x[mask[:N*N]].reshape(N, N-1)
    
[/code]


CC BY-SA 4.0
2018-08-18T09:35:16.203
47601
2018-08-18T09:35:16.203
2913
47592
answer


timeit (N=10000)
で計測すると、mjyさんの回答を、以下のように`np.zeros`から`np.empty`に変更するのが一番早くなりました。260msぐらいで、他の回答より2倍以上早いです。今回のケースでは結果が全部置き換えられるので`np.empty`を使って初期化しなくても問題ありません。

[code]

    def by_np_slice2(N):
        nums = np.arange(0, N, dtype=np.int)
        result = np.empty((N, N-1), dtype=np.int)
    
        for i, a in enumerate(result):
            a[:i] = nums[:i]
            a[i:] = nums[i+1:]
    
        return result
    
[/code]

また、コメントにある`np.arange`を使って`for`で繰り返し処理をする方法も360msぐらいで悪くはありません。`for`の繰り返しで`result[i]`とnumpyの`array`をインデックスで扱う処理が遅いので、`numba`を入れてJITコンパイルすると速くなります。230msぐらいで処理できるようになり上の処理よりも少し早くなりました。

[code]

    @numba.jit
    def by_np_arange(N):
      result = np.empty((N, N-1),dtype=np.int)
      for i in range(N):
        result[i] = np.concatenate((np.arange(0,i), np.arange(i+1,N)))
      return result
    
[/code]

for文を使わない方法だと以下の単位3角行列を使うのが単純で速いのですが390msぐらいです。for文を使うなというのが常識ですが、場合によるようです。

[code]

    Z = -np.tri(N, N-1, -1, dtype=np.int)
    Z += np.arange(1,N)
    
[/code]


CC BY-SA 4.0
2018-08-18T11:22:07.313
47605
2018-08-19T03:01:44.450
2018-08-19T03:01:44.450
15171
15171
47592
answer