Table of Contents
What I Want to Do
numpy.array([1, 2, 3, 4])
が与えられているとき, これと同じ配列が$N$行ある 2-D numpy.array
を生成したい.
例として以下,
1
2
3
4
np.array([[1, 2, 3, 4],
[1, 2, 3, 4],
[1, 2, 3, 4],
[1, 2, 3, 4]])
配列の繰り返しなのでrepeatが考えられますが, 単純に配列の繰り返しだけを指定すると
1
2
3
4
import numpy as np
np.repeat([1, 2, 3, 4], 2)
>>> array([1, 1, 2, 2, 3, 3, 4, 4])
となり欲しい結果が返ってきません. 配列の次元を増やし繰り返すためには以下の手法が考えられます
numpy.tile
を用いて配列を複製しタイル状に並べるnumpy.broadcast_to
を用いて新しいshapeへブロードキャストする(ただし, 原則編集はできない)numpy.reshape
を用いて一度2D配列へ変更し,numpy.repeat
で配列を複製するnumpy.resize
を用いる
numpy.tile
: 元配列に影響なく編集可能
1
2
## 指定された回数分繰り返した配列を返す
numpy.tile(array, shape)
array
: 複製の元となる配列shape
引数で繰り返し方法を指定,tuple
orint
この手法が推奨手法となります.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import numpy as np
## 1D配列をそのまま繰り返す
np.tile([1, 2, 3, 4], 2)
>>> array([1, 2, 3, 4, 1, 2, 3, 4])
## 1D配列を2D配列にし行を増やす方向に繰り返す
np.tile([1, 2, 3, 4], (2, 1))
>>> array([[1, 2, 3, 4],
[1, 2, 3, 4]])
## 1D配列を2D配列にし列を増やす方向に繰り返す
np.tile([1, 2, 3, 4], (1, 2))
>>> array([[1, 2, 3, 4, 1, 2, 3, 4]])
## 2D配列を行を増やす方向に繰り返す
np.tile([[1, 2, 3, 4], [5, 6, 7, 8]], (2, 1))
>>> array([[1, 2, 3, 4],
[5, 6, 7, 8],
[1, 2, 3, 4],
[5, 6, 7, 8]])
## 2D配列を列を増やす方向に繰り返す
np.tile([[1, 2, 3, 4], [5, 6, 7, 8]], (1, 2))
>>> array([[1, 2, 3, 4, 1, 2, 3, 4],
[5, 6, 7, 8, 5, 6, 7, 8]])
numpy.broadcast_to
: ただし原則編集はできない
複製する形でブロードキャストした配列を参照するのみの場合はnumpy.broadcast_to
のほうが実行時間が早くなります
1
2
## 指定された回数分繰り返した配列を返す
numpy.broadcast_to(array, shape)
array
: 複製の元となる配列shape
: 引数で繰り返し方法を指定,tuple
orint
- 注意点としては繰り返しの回数というよりは感覚的には次元を増やして行数/列数を指定するイメージ
- ブロードキャスト専用の関数と認識したほうが良い
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
## D配列をそのまま繰り返すことはできない
np.broadcast_to([1, 2, 3, 4], (1, 4))
>>> array([[1, 2, 3, 4]])
np.broadcast_to([1, 2, 3, 4], (1, 8))
>>> ValueError: operands could not be broadcast together with remapped
shapes [original->remapped]: (4,) and requested shape (1,2)
## 1D配列を2D配列にし行を増やす方向に繰り返す
np.broadcast_to([1, 2, 3, 4], (2, 4))
>>> array([[1, 2, 3, 4],
[1, 2, 3, 4]])
np.broadcast_to([1, 2, 3, 4], (3, 4))
>>> array([[1, 2, 3, 4],
[1, 2, 3, 4],
[1, 2, 3, 4]])
ブロードキャストした配列を編集することはできない
numpy.broadcast_to
を用いてブロードキャストキャストした配列はイミュータブルであることに
留意が必要です.
1
2
3
4
import numpy as np
A = np.broadcast_to([1, 2, 3, 4], (2, 4))
A[0, 0] = 1
>>> ValueError: assignment destination is read-only
flags.writeable = True
を用いて書き込み禁止を強引に外すことも可能ですが, もとの
オブジェクトを参照しているだけなので一つの要素への変更が他にも波及してしまいます
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
## np.tileだと問題はない
a = [1, 2, 3, 4]
A = np.tile(a, (2, 1))
A[1][1]=3
print(A)
>>> array([[1, 2, 3, 4],
[1, 3, 3, 4]])
print(a)
>>> [1, 2, 3, 4]
## np.broadcastだと参照渡しなので影響が広がる
a = [1, 2, 3, 4]
A = np.broadcast_to(a, (2, 4))
A.flags.writeable = True
A[1][1] = 3
print(A)
>>> array([[1, 3, 3, 4],
[1, 3, 3, 4]])
print(a)
>>> [1, 2, 3, 4]
## np.broadcastだと参照渡しなので影響が広がる
a = np.array([1, 2, 3, 4])
A = np.broadcast_to(a, (2, 4))
A.flags.writeable = True
A[1][1] = 3
print(a)
>>> [1, 3, 3, 4]
Column: イミュータブル
オブジェクトに入っているデータの値を変更できる場合, そのオブジェクトのことをミュータブルと呼び, 変更できない場合はイミュータブルと呼ぶ.
よく聞く例えとして, 中身は見れるけど書き換えられないことか「イミュータブルなオブジェクトは密閉されているが透明な窓がついている箱のようなもの」と言われる.
numpy.reshape
で次元を増やしてから, numpy.repeat
の実行
1
numpy.reshape(array, newshape)
array
: 変換元の配列newshape
: 変換後の配列の形状を指定,tuple
orint
- あくまで要素数が変化しない範囲での形状変換しかできない
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np
## 4要素の1D配列を2D配列にし, 行を増やす方向でrepeat: 元配列には影響なし
a = np.array([1, 2, 3, 4])
A = np.repeat(np.reshape(a, (1, 4)), 2, axis=0)
A[1][1] = 3
print(A)
>>> array([[1, 2, 3, 4],
[1, 3, 3, 4]])
print(a)
>>> array([1, 2, 3, 4])
## 4要素の1D配列を2D配列へ変換する際に要素数をオーバーする形状を指定した場合
a = [1, 2, 3, 4]
np.reshape(a, (2, 4))
>>> ValueError: cannot reshape array of size 4 into shape (2,4)
reshape後の配列に対する値変化の影響
reshape前のndarray
の要素は, reshape後のndarray
と共有されているので,
変換後のある値を変化させると変換前のndarray
の要素も影響をうける
これは, 変換前がndarray
の場合野天に注意が必要です, 例は以下,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
## 変換前がListの場合は影響はない(型変換を噛ませているので)
a = [1, 2, 3, 4]
A = np.reshape(a, (2, 2))
A[1][1] = 3
print(a)
>>> [1, 2, 3, 4]
## 変換前がndarrayの場合は影響がある
a = np.array([1, 2, 3, 4])
A = np.reshape(a, (2, 2))
A[1][1] = 3
print(a)
>>> array([1, 2, 3, 3])
元の配列までに影響を与えてしまうので注意が必要です.
numpu.resize
: 元配列に影響なく編集可能
numpy.reshape
は要素数が揃わないと変換ができなかったですが,
numpy.resize
は要素数をオーバーするような形状変換のときは繰り返しで対応してくれます
1
numpy.resize(array, newshape)
array
: 変換元の配列newshape
: 変換後の配列の形状を指定,tuple
orint
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np
## 1Dを要素数をオーバーする形で2Dへ変換
a = np.array([1, 2, 3, 4])
A = np.resize(a, (2, 4))
print(A)
>>> array([[1, 2, 3, 4],
[1, 2, 3, 4]])
## 1Dを要素数をオーバーする形で2Dへ変換
a = np.array([1, 2, 3, 4])
A = np.resize(a, (2, 6))
print(A)
>>> array([[1, 2, 3, 4, 1, 2],
[3, 4, 1, 2, 3, 4]])
## 変更
a = np.array([1, 2, 3, 4])
A = np.resize(a, (1, 4))
A[0][1] = 3
print(a)
>>> array([1, 2, 3, 4])
どの変換が一番早いのか?
以下の条件でどの変換が一番早いか簡易的に検証してみます.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
import timeit
## 元配列の定義
a = np.array([1, 2, 3, 4])
## 実行時間計測
%timeit A = np.tile(a, (2, 1))
%timeit B = np.broadcast_to(a, (2, 4))
%timeit C = np.repeat(np.reshape(a, (1, 4)), 2, axis=0)
%timeit D = np.resize(a, (2, 4))
## 要素一致検証
print(np.all(A == B), np.all(A == C), np.all(A == D))
結果は以下となります
1
2
3
4
5
1.82 µs ± 15.1 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
1.68 µs ± 2.6 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
1.16 µs ± 4.21 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
1.73 µs ± 2.52 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
True True True
numpy.broadcast_to
が流石に一番早いかな?と期待していたところ, numpy.repeat
& reshape
が圧倒的に速いという結果になった. これは実行順番を変えてみても同じ結論だった.
念の為, 変数のメモリ使用量も確認してみる
1
2
3
4
5
6
7
8
9
10
11
12
13
import sys
print(sys.getsizeof(A))
>> 128
print(sys.getsizeof(B))
>> 128
print(sys.getsizeof(C))
>> 192
print(sys.getsizeof(D))
>> 128
一番実行時間が短いオブジェクトだけがメモリを多く使用している. これはflags
をしらべると
OWNDATA
がTrue
になっていることが理由.
References
(注意:GitHub Accountが必要となります)