前のページで説明したように,IDの配列と得点の行列をそれぞれ別の配列に格納して様々な処理を行うことはできますが,ソートなどは処理が複雑になりました.このページでは構造化配列を用いて効率的に複雑なデータを扱う方法を示します.
ここでも前のページとほぼ同様に,3名の学生が4回のテストを受験したデータを管理することを考えます.さらに,学籍番号,各回の得点,合計点だけでなく,名前もまとめて管理します.このために,まずは必要となるデータ配列を準備しておきます.
ids = np.array([101, 102, 103])
names = ['Tanaka', 'Sato', 'Abe']
scores = np.array([
[30, 60, 55, 40],
[25, 70, 20, 60],
[45, 55, 60, 50]
])
複合データ型指定によって,構造化配列を作成します.
# 複合データ型を定義
tp = np.dtype([('id', 'u4'), ('name', 'U10'), ('score', 'i8', 4), ('total', 'i8')])
# 定義した複合データ型を利用して,3つの要素からなる構造化配列をゼロで初期化
X = np.zeros(3, dtype=tp)
X
array([(0, '', [0, 0, 0, 0], 0), (0, '', [0, 0, 0, 0], 0), (0, '', [0, 0, 0, 0], 0)], dtype=[('id', '<u4'), ('name', '<U10'), ('score', '<i8', (4,)), ('total', '<i8')])
上の2行目にある複合データ型の定義では,'id'
,'name'
,'score'
,'total'
という4つの要素からなる構造体が定義されています.また,'id'
の「'u4'」は「4バイトの符号なし整数」を,'name'
の「'U10'」は「最大10文字の Unicode 文字列」を意味します.さらに,'score'
の「'i8', 4」は「8バイトの符号なし整数で4つの要素からなる配列」を,'total'
の「'i8'」も「8バイトの符号なし整数」を意味します.さらに,4行目では,複合データ型 tp
を使って 3 つの要素からなる構造化配列をゼロで初期化しています.
作成した構造化配列の各要素には名前,またはインデックスでアクセス可能です.例えば,次の方法で 'id' をすべて参照できます.
id の参照
X['id']
array([0, 0, 0], dtype=uint32)
同様に,'name' や 'score','total' で参照します.
name の参照
X['name']
array(['', '', ''], dtype='<U10')
score の参照
X['score']
array([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
total の参照
X['total']
array([0, 0, 0])
また,インデックスを用いる参照は次のようになります.
X[0]
(0, '', [0, 0, 0, 0], 0)
名前やインデックスでの参照方法がわかれば,代入することも可能です.まずは 'id' にデータを代入して結果を確認します.
id にデータを代入
X['id'] = ids
X
array([(101, '', [0, 0, 0, 0], 0), (102, '', [0, 0, 0, 0], 0), (103, '', [0, 0, 0, 0], 0)], dtype=[('id', '<u4'), ('name', '<U10'), ('score', '<i8', (4,)), ('total', '<i8')])
同様に,name と 'score' に代入して順に結果を確認します.
name にデータを代入
X['name'] = names
X
array([(101, 'Tanaka', [0, 0, 0, 0], 0), (102, 'Sato', [0, 0, 0, 0], 0), (103, 'Abe', [0, 0, 0, 0], 0)], dtype=[('id', '<u4'), ('name', '<U10'), ('score', '<i8', (4,)), ('total', '<i8')])
X['score'] = scores
X
array([(101, 'Tanaka', [30, 60, 55, 40], 0), (102, 'Sato', [25, 70, 20, 60], 0), (103, 'Abe', [45, 55, 60, 50], 0)], dtype=[('id', '<u4'), ('name', '<U10'), ('score', '<i8', (4,)), ('total', '<i8')])
さらに,'score' の行列をnp.sum()
関数によって行ごとに合計し,'total' に代入します.
X['total'] = np.sum(X['score'], axis=1)
X
array([(101, 'Tanaka', [30, 60, 55, 40], 185), (102, 'Sato', [25, 70, 20, 60], 175), (103, 'Abe', [45, 55, 60, 50], 210)], dtype=[('id', '<u4'), ('name', '<U10'), ('score', '<i8', (4,)), ('total', '<i8')])
これで必要な構造化配列が完成しました.
構造化配列にデータを設定できたので,様々な参照方法を再度確認してみよう.まず,全体を表示します.
print(X)
[(101, 'Tanaka', [30, 60, 55, 40], 185) (102, 'Sato', [25, 70, 20, 60], 175) (103, 'Abe', [45, 55, 60, 50], 210)]
次に,'name' だけを参照します.
print(X['name'])
['Tanaka' 'Sato' 'Abe']
次は,'name' と 'total' を参照します.
print(X[['name', 'total']])
[('Tanaka', 185) ('Sato', 175) ('Abe', 210)]
インデックスを使って,先頭のデータを参照します.
print(X[0])
(101, 'Tanaka', [30, 60, 55, 40], 185)
さらに,インデックスと名前の両方を使って構造化配列を参照します.
print(X[2][['name', 'total']])
('Abe', 210)
構造化配列を使うとここで説明した方法よりもより簡単にソートができるようになります.まず,'total' でソートします.
print(np.sort(X, order='total'))
[(102, 'Sato', [25, 70, 20, 60], 175) (101, 'Tanaka', [30, 60, 55, 40], 185) (103, 'Abe', [45, 55, 60, 50], 210)]
次に,'name' でソートします.なお,アルファベットでのソートは動作しますが,漢字でのソートは必ずしも読みの順にならないことに注意してください.
print(np.sort(X, order='name'))
[(103, 'Abe', [45, 55, 60, 50], 210) (102, 'Sato', [25, 70, 20, 60], 175) (101, 'Tanaka', [30, 60, 55, 40], 185)]
もちろん 'id' でソートすることも可能です.
print(np.sort(X, order='id'))
[(101, 'Tanaka', [30, 60, 55, 40], 185) (102, 'Sato', [25, 70, 20, 60], 175) (103, 'Abe', [45, 55, 60, 50], 210)]