NumPy配列(ndarray)の生成方法まとめ
NumPyはPythonで数値計算を行うための強力なライブラリです。NumPyは、高性能な多次元配列オブジェクト(ndarray)を提供し、これにより大規模なデータセットや数値計算において効率的な演算を行うことができます。
Pythonの標準的なlistでも数値データは扱えますが、要素数が増えるほど計算処理が遅くなったり、行列演算を自前でループ実装する必要があったりと、実務では扱いにくい場面が出てきます。NumPyのndarrayを使えば、こうした処理をシンプルなコードで高速に実行できるため、データ分析や機械学習、画像処理など幅広い分野で標準的に利用されています。
本記事では、「ndarrayをどうやって作ればいいのか分からない」「生成方法がいくつもあってどれを使えばいいか迷う」といった悩みを解決できるよう、NumPy配列(ndarray)の基本的な生成方法について、用途別に整理して説明します。
インストール
NumPyはPython標準のライブラリではないため、利用する前にインストールが必要です。Pythonのパッケージ管理コマンドであるpipを使って、以下のコマンドを実行します。
pip install numpy
インストールが完了すると、import numpy as npという記述でNumPyの機能をスクリプト内で読み込めるようになります。以降のサンプルコードでも、このimport文が前提になります。
生成メソッド
NumPyにはndarrayを生成するための方法が複数用意されており、それぞれ得意な場面が異なります。
array- 既存のリストやタプルからそのままNumPy配列に変換したい場合arange- 一定の間隔で並んだ連番の配列を作りたい場合where- 条件に応じて要素を書き換えたり、条件に合う位置を調べたりしたい場合zeros- 決まった大きさの配列を0で初期化しておきたい場合nonzero- 配列の中から0以外の値が入っている位置を調べたい場合
以下でそれぞれの使い方を具体的に見ていきます。
array
リストやタプルなどのシーケンスからNumPy配列(ndarray)を生成します。既存のデータをNumPyの世界に持ち込みたいときに、最もよく使う方法です。
import numpy as np
arr = np.array([1, 2, 3, 4, 5, 6])
print(arr)
[1 2 3 4 5 6]
リストを二重にすると、2次元配列(行列)を生成できます。
import numpy as np
arr = np.array([[1, 2], [3, 4], [5, 6]])
print(arr)
print(len(arr), len(arr[0]))
[[1 2]
[3 4]
[5 6]]
3 2
ここで注意したいのが、len(arr)は配列の「行数」を返すという点です。「全体の要素数」ではないため、1行あたりの要素数を知りたい場合はlen(arr[0])のように、行の中身を取り出してからlen()を使う必要があります。またNumPy配列はPythonのlistと違い、各行の要素数がそろっている(長方形の形をしている)ことが前提になっているため、行によって長さがバラバラなデータを渡すと意図した形の配列にならない点にも注意してください。
タプルから生成
import numpy as np
arr = np.array([(1, 2), (3, 4), (5, 6)])
print(arr)
print(len(arr), len(arr[0]))
[[1 2]
[3 4]
[5 6]]
3 2
リストの代わりにタプルを渡しても、生成されるndarrayの中身は同じです。np.array()は渡された値を要素として順番に取り出すだけなので、元の入れ物がリストかタプルかは結果に影響しません。既存のコードでどちらの形式のデータを受け取っても、同じ感覚でNumPy配列に変換できると覚えておくと安心です。
型も指定できます。
import numpy as np
arr = np.array([(1, 2), (3, 4), (5, 6)], dtype=np.float32)
print(arr)
[[1. 2.]
[3. 4.]
[5. 6.]]
dtypeは配列の要素が持つデータ型を指定する引数です。np.float32は32ビットの浮動小数点数を意味し、指定した値は自動的に小数として扱われるため、出力結果でも1.のように末尾に.が付きます。dtypeを明示的に指定することで、計算精度をそろえたり、扱うデータ量に応じてメモリ使用量を抑えたりできます。指定を省略した場合は、渡した値の内容からNumPyが自動で適切な型を判断します。
空のNumPy配列を生成
import numpy as np
arr = np.array([[],[],[],[]])
print(arr)
print(len(arr), len(arr[0]))
[]
4 0
あらかじめ「枠だけ」を用意しておき、後から値を追加していきたいケースで使う書き方です。この例では、中身が空のリストを4つ並べているため、len(arr)は4(行数)、len(arr[0])は0(1行あたりの要素数)になります。
1次元の長さは取得できるので、空かどうかはarr[0]のサイズを確認する必要があります。len(arr)だけを見ると要素が入っているように見えてしまうため、「配列そのものが空かどうか」を判定したいときは、必ずarr[0](各行の中身)のサイズもあわせて確認するようにしてください。
arange
任意の値を設定したNumPy配列を生成します。Python標準のrange()と似ていますが、range()が「必要になったときに値を返す」仕組みであるのに対して、np.arange()は実際にNumPy配列(ndarray)を生成して返す点、そして小数のステップ幅も指定できる点が異なります。
np.arange(start, stop, step)
start- 開始値stop- 終了値step- 増加量
import numpy as np
arr = np.arange(0, 100, 10)
print(arr) # [ 0 10 20 30 40 50 60 70 80 90]
引数が1個の場合はNumPy配列の要素数になり、0から1ずつ埋めた値が設定されます。
import numpy as np
arr = np.arange(3)
print(arr) # [0 1 2]
where
条件に応じて、要素を変更したNumPy配列を返します。「配列の中身を条件によって別の値に置き換えたい」「forループを書かずに条件分岐を配列全体に適用したい」といった場面で活躍するメソッドです。
np.where(condition, x, y)
condition- 条件x- 条件が真の場合に、設定する要素y- 条件が偽の場合に、設定する要素
import numpy as np
arr = np.arange(1, 10, 1)
arr2 = np.where(arr % 2, 'A', 'B')
print(arr) # [1 2 3 4 5 6 7 8 9]
print(arr2) # ['A' 'B' 'A' 'B' 'A' 'B' 'A' 'B' 'A']
引数が1個の場合は条件を満たすインデックスのNumPy配列を格納した、タプルを返します。「値そのもの」ではなく「条件に合う位置(インデックス)」を知りたいときに使う書き方です。
import numpy as np
arr = np.arange(1, 10, 1)
arr2 = np.where(arr % 2)
print(arr)
print(type(arr2), arr2)
for i in arr2:
print(type(i), i)
[1 2 3 4 5 6 7 8 9]
<class 'tuple'> (array([0, 2, 4, 6, 8]),)
<class 'numpy.ndarray'> [0 2 4 6 8]
戻り値がタプルになっているのは、2次元以上の配列にも対応できるようにするためです。1次元配列の場合はタプルの中身が1つだけなので、arr2[0]のように取り出せば、条件に合致したインデックスの配列を直接扱えます。
zeros
0で埋めたNumPy配列を生成します。ループの中で値を1つずつ足し込んでいく「集計用の入れ物」や、後から特定の位置だけ値を書き換える「マスク」を用意するときによく使われる方法です。
np.zeros(shape)
shape- NumPy配列の長さ(intまたはタプルで指定)
import numpy as np
arr = np.zeros(10)
print(arr) # [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
データタイプも指定できます。
import numpy as np
arr = np.zeros(10, dtype=np.uint8)
print(arr) # [0 0 0 0 0 0 0 0 0 0]
np.uint8は0〜255までの整数だけを扱う型で、画像データのように値の範囲があらかじめ決まっている場合に指定すると、メモリ使用量を抑えられます。用途に合わせて型を指定できる点は、前述のdtypeと同じ考え方です。
タプルで指定もできます。
import numpy as np
arr = np.zeros((2,3), dtype=np.uint8)
print(arr)
[[0 0 0]
[0 0 0]]
shapeにタプル(2,3)を渡すと、2行3列の2次元配列を0で初期化できます。1次元だけでなく、行列のような多次元データの器を用意したいときにも同じ書き方が使えます。
nonzero
配列の中から、値が0ではない要素の位置(インデックス)を取得するメソッドです。以下の例は、音声や信号データを分析する際に「値がゼロを横切った(ゼロ交差した)位置」を検出するようなケースを想定した使い方です。
zero_cross_amplitudes = np.zeros(10)
# zero_cross_amplitudes 操作
zero_cross_amplitudes[3] = 1
zero_cross_amplitudes[5] = 1
# 値が0以外のインデックスを取得
nonzero_indexes = np.nonzero(zero_cross_amplitudes)[0]
print(np.nonzero(zero_cross_amplitudes), nonzero_indexes)
(array([3, 5]),) [3 5]
np.nonzero()の戻り値も、whereと同様に「次元ごとのインデックス配列を格納したタプル」です。1次元配列であれば[0]を付けて取り出すことで、条件に合う位置だけを1本の配列として扱えます。前述の「引数が1個のnp.where()」と実質的に同じ結果が得られるため、条件式で書きたいときはwhere、単純に非ゼロの位置を探したいときはnonzero、と使い分けると分かりやすいです。
2つのリストから2次元配列を作成する
[x1,x2]のリストと[y1,y2]のリストから[x1,y1],[x2,y2]となる新たなリストを作成する方法。.Tを使用して、行と列を入れ替えることで、[x1, y1]、[x2, y2]、...という形式のNumPy配列を作成しています。座標データ(x軸とy軸)や、対になった2つの数値データをひとまとめにして扱いたいときに使える方法です。
list1 = [1, 2, 3, 4, 5, 6, 7]
list2 = [100, 200, 300, 400, 500, 600, 700]
# NumPy配列の作成
np_array = np.array([list1, list2])
print(np_array)
np_array = np.array([list1, list2]).T
print(np_array)
[[ 1 2 3 4 5 6 7]
[100 200 300 400 500 600 700]]
[[ 1 100]
[ 2 200]
[ 3 300]
[ 4 400]
[ 5 500]
[ 6 600]
[ 7 700]]
はじめにnp.array([list1, list2])とすると、list1が1行目、list2が2行目に並んだ配列になります。ここに.T(転置)を適用すると行と列が入れ替わり、list1とlist2の同じ位置にある値同士がペアになった配列が得られます。
片方のリストが文字列の場合、NumPy配列を作成した時にもう片方も文字列になるので注意が必要です。
list1 = ['1', '2', '3', '4', '5', '6', '7']
list2 = [100, 200, 300, 400, 500, 600, 700]
# NumPy配列の作成
np_array = np.array([list1, list2])
print(np_array)
print(type(np_array[0,0]), type(np_array[1,0]))
[['1' '2' '3' '4' '5' '6' '7']
['100' '200' '300' '400' '500' '600' '700']]
<class 'numpy.str_'> <class 'numpy.str_'>
NumPy配列は1つの配列の中で複数のデータ型を混在させることができず、型が異なる値をまとめると自動的にどちらか一方の型に統一されます。この例では文字列と整数が混在しているため、配列全体が文字列(numpy.str_)に変換されています。数値のつもりで扱っていた列が、実は文字列になっていて計算時にエラーになる、というのはNumPyでよくあるつまずきポイントなので、型変換されている可能性を意識しておくと原因調査がスムーズになります。
抽出結果を数値として扱う場合は、astype()関数でキャストします。
np_line = np_array[1, :].astype(int)
print(np_line)
print(type(np_line[0]))
[100 200 300 400 500 600 700]
<class 'numpy.int64'>
指定した条件に一致するインデックスを取得します。
import numpy as np
final_cls_inds = np.array([1,2,1,3,5])
r = final_cls_inds==1
r = r | (final_cls_inds==5) # ()が必要
print(r) # [False False False False True]
# こちらも ()が必要
print((final_cls_inds==1) | (final_cls_inds==2)) # [ True True True False False]
ここで使われている|は「配列の要素ごとの論理和(OR)」を計算する演算子です。Python標準のorはNumPy配列全体をまとめて1つの真偽値として扱おうとするため、要素ごとの比較には使えず、|を使う必要があります。また比較演算子(==)より|の方が優先順位が高いため、括弧を付けずに書くと意図しない範囲で演算されてしまいます。コメントにもある通り、|を使うときは必ず比較部分を()で囲むようにしてください。同様の理由で、AND条件を作る場合は&を使い、こちらも同じように括弧が必要です。
'float' object has no attribute 'shape'エラーへの対処法
floatのndarrayで下記のエラーが出る場合があります。
AttributeError: 'float' object has no attribute 'shape’
このエラーは、本来NumPy配列(ndarray)として扱われるはずの値が、実際には通常のPythonのfloat型になってしまっている場合に発生します。たとえば、行ごとに要素数が異なる(そろっていない)データから配列を作成すると、NumPyはdtype=objectの配列として生成し、中身がNumPy独自の数値型ではなく標準のPython floatになることがあります。そのような値に対して、配列であることを前提にした.shapeのような属性へアクセスしようとすると、このエラーが発生します。
解決方法
対処法としては、astype()を使って配列全体の型を明示的にNumPyの数値型(np.float32など)へそろえ直すことが有効です。型をそろえることで、各要素が正しくNumPyの数値型として扱われるようになり、.shapeをはじめとする配列特有の属性やメソッドを問題なく利用できるようになります。原因を特定しづらいエラーではありますが、「要素数がそろっていないデータを配列化していないか」「意図せずdtype=objectの配列になっていないか」を確認する切り口として覚えておくと役立ちます。
arr[: 100].astype(np.float32)
このarr[: 100]は、配列の先頭(0番目)から100番目までの要素、つまり最初の100個分を取り出すという意味です(Pythonのスライスは開始位置:終了位置で指定し、終了位置にあたる要素自体は含まれません)。それを.astype(np.float32)によってfloat32型に変換しています。
