【NumPy中級編1 第4回】データ永続化と計算の基礎!ファイル入出力&線形代数の初歩 (save/load, dot, @, T)
「NumPyで苦労して作った配列、プログラムを終了したら消えちゃうのは困る…」
「計算結果をファイルに保存して、後でまた使いたいんだけど、どうすればいい?」
「行列の掛け算とか、転置とか、数学っぽい計算もNumPyでできるの?」
こんにちは! NumPy探検隊、隊長のPythonistaです! 前回の第3回では、NumPy配列の形状変更、結合・分割、そして条件による要素抽出といった、配列を自由自在に操るための応用テクニックを学びましたね。これで、かなり複雑なデータ構造も扱えるようになったのではないでしょうか。
シリーズ第4回となる今回は、いよいよ中級編の第一歩です! これまでメモリ上で操作してきたNumPy配列を、ファイルとして永続化(保存・読み込み)する方法と、データサイエンスや機械学習の分野で必須の知識となる線形代数の基本的な演算(行列積や転置など)をNumPyでどのように行うかについて、分かりやすく解説していきます。これらの知識を身につければ、NumPyをさらに実用的なツールとして活用できるようになりますよ!
1. NumPy配列のデータをファイルに保存・読み込み:データ永続化の第一歩
プログラムの実行が終了すると、メモリ上にあった変数や配列のデータは通常消えてしまいます。計算に時間がかかった結果や、他のプログラムでも利用したい配列データを保存しておき、後で再利用できるようにするのが「データの永続化」です。NumPyでは、配列データを効率的にファイルに保存したり、ファイルから読み込んだりするための便利な関数が用意されています。
1.1. バイナリ形式での保存・読み込み:.npy
ファイル(単一配列)
NumPy独自のバイナリ形式(.npy
)で配列を保存すると、高速かつ効率的にデータの読み書きができます。1つのファイルに1つのNumPy配列を保存する場合に使います。
np.save(filename, arr)
: 配列arr
を、指定したfilename
(通常は拡張子.npy
)にバイナリ形式で保存します。np.load(filename)
:.npy
ファイルからNumPy配列を読み込みます。
import numpy as np
# 保存する配列を作成
original_array = np.arange(10).reshape((2, 5))
print(f"元の配列:\n{original_array}")
file_npy = 'my_array.npy'
# .npyファイルに保存
np.save(file_npy, original_array)
print(f"\n'{file_npy}' に配列を保存しました。")
# .npyファイルから読み込み
loaded_array_npy = np.load(file_npy)
print(f"'{file_npy}' から読み込んだ配列:\n{loaded_array_npy}")
# 元の配列と同じか確認
print(f"読み込んだ配列は元の配列と同じか: {np.array_equal(original_array, loaded_array_npy)}")
1.2. バイナリ形式での保存・読み込み:.npz
ファイル(複数配列)
複数のNumPy配列を1つのファイルにまとめて保存したい場合は、.npz
形式が便利です。これは、複数の.npy
ファイルをZIP形式で圧縮せずに(または圧縮して)まとめたものです。
np.savez(filename, name1=arr1, name2=arr2, ...)
: 複数の配列(arr1
,arr2
など)を、それぞれに対応する名前(name1
,name2
など)を付けて、指定したfilename
(通常は拡張子.npz
)に保存します。np.savez_compressed(filename, name1=arr1, ...)
:np.savez()
と似ていますが、圧縮して保存するためファイルサイズを小さくできます。np.load(filename)
:.npz
ファイルを読み込みます。返されるオブジェクトは、保存時に指定した名前をキーとする辞書のような形で各配列にアクセスできます。
import numpy as np
arr_x = np.array([1, 2, 3])
arr_y = np.array([[4, 5, 6], [7, 8, 9]])
file_npz = 'my_arrays.npz'
# .npzファイルに複数の配列を保存
np.savez(file_npz, x_data=arr_x, y_data=arr_y)
print(f"\n'{file_npz}' に複数の配列を保存しました。")
# .npzファイルから読み込み
loaded_data_npz = np.load(file_npz)
print(f"読み込んだデータのキー: {loaded_data_npz.files}") # ['x_data', 'y_data']
loaded_arr_x = loaded_data_npz['x_data']
loaded_arr_y = loaded_data_npz['y_data']
print(f"\n読み込んだx_data:\n{loaded_arr_x}")
print(f"読み込んだy_data:\n{loaded_arr_y}")
# np.savez_compressed() を使うと圧縮されます
# np.savez_compressed('my_compressed_arrays.npz', x_data=arr_x, y_data=arr_y)
1.3. テキスト形式での保存・読み込み:.txt
, .csv
など
人間が直接読めるテキスト形式(CSVファイルなど)で配列を保存・読み込みしたい場合は、np.savetxt()
とnp.loadtxt()
を使います。ただし、これらは主に1次元または2次元の数値データ向けです。
np.savetxt(filename, arr, delimiter=',', fmt='%.18e', header='', footer='', comments='# ')
: 配列arr
をテキストファイルfilename
に保存します。delimiter
: 区切り文字(デフォルトはスペース。','
でCSV形式)。fmt
: 各数値の出力フォーマット(デフォルトは科学技術表記)。'%.2f'
(小数点以下2桁)など。header
: ファイルの先頭に書き込むヘッダー文字列。footer
: ファイルの末尾に書き込むフッター文字列。comments
: ヘッダーやフッターの行頭に付加されるコメント文字(デフォルトは#
)。
np.loadtxt(filename, delimiter=',', skiprows=0, usecols=None, dtype=float, comments='#')
: テキストファイルからデータを読み込み、NumPy配列として返します。delimiter
: 区切り文字。skiprows
: ファイルの先頭から読み飛ばす行数(ヘッダー行など)。usecols
: 読み込む列をタプルで指定(例:(0, 2)
で0列目と2列目)。dtype
: 読み込むデータの型(デフォルトはfloat
)。comments
: コメントとして扱う文字(その文字で始まる行は無視される)。
import numpy as np
arr_to_text = np.array([[1.1, 2.25, 3.333], [4.0, 5.55, 6.789]])
file_csv = 'my_data.csv'
file_txt_custom = 'my_data_custom.txt'
# CSV形式で保存 (小数点以下2桁)
np.savetxt(file_csv, arr_to_text, delimiter=',', fmt='%.2f', header='col1,col2,col3', comments='')
print(f"\n'{file_csv}' にCSV形式で保存しました。")
# my_data.csv の中身:
# col1,col2,col3
# 1.10,2.25,3.33
# 4.00,5.55,6.79 (四捨五入される場合あり)
# CSVファイルから読み込み
loaded_from_csv = np.loadtxt(file_csv, delimiter=',', skiprows=1) # ヘッダーを1行スキップ
print(f"'{file_csv}' から読み込んだ配列:\n{loaded_from_csv}")
# カスタムフォーマットでテキストファイルに保存
np.savetxt(file_txt_custom, arr_to_text, fmt='%d', delimiter='\t') # 整数としてタブ区切り
print(f"\n'{file_txt_custom}' にカスタム形式で保存しました。")
# my_data_custom.txt の中身 (整数に丸められる):
# 1 2 3
# 4 5 6
注意: np.loadtxt()
は、データが全て同じ型(数値)であるシンプルなテキストファイルに向いています。もしCSVファイルに文字列や欠損値が混在している場合は、Pandasライブラリのpd.read_csv()
を使う方がはるかに柔軟で強力です。
2. NumPyと線形代数の初歩:データ計算の言語を操る
線形代数は、ベクトルや行列といった概念を扱い、データサイエンス、機械学習、画像処理、物理シミュレーションなど、多くの科学技術分野で基礎となる数学です。NumPyは、これらの線形代数演算を効率的に行うための機能を豊富に備えています。
2.1. NumPyにおけるベクトルと行列 (おさらい)
前回の記事でも触れましたが、NumPyでは以下のように考えます。
- ベクトル: 1次元配列 (
ndarray
) - 行列: 2次元配列 (
ndarray
)
2.2. 行列の転置 (Transpose):行と列を入れ替える
行列の転置とは、行列の行と列を入れ替える操作です。NumPyでは、ndarray
オブジェクトの.T
属性を使うか、np.transpose()
関数を使うことで簡単に行えます。
import numpy as np
matrix_a = np.array([[1, 2, 3],
[4, 5, 6]]) # (2, 3) 行列
print(f"\n元の行列 matrix_a (2x3):\n{matrix_a}")
# .T 属性を使って転置
transposed_a_T = matrix_a.T
print(f"転置行列 (matrix_a.T) (3x2):\n{transposed_a_T}")
# np.transpose() 関数を使っても同じ
transposed_a_func = np.transpose(matrix_a)
print(f"転置行列 (np.transpose(matrix_a)) (3x2):\n{transposed_a_func}")
2.3. 行列積(ドット積):行列同士の「正しい」掛け算
NumPy配列で*
演算子を使うと、要素ごとの積が計算されましたね。しかし、線形代数における行列の掛け算(行列積またはドット積)は、それとは異なるルールで計算されます。
行列A (m x n) と 行列B (n x p) の行列積は、Aの列数とBの行数が一致している場合にのみ定義され、結果として (m x p) の行列が得られます。
NumPyでは、行列積は以下の方法で計算できます。
np.dot(arr1, arr2)
関数arr1.dot(arr2)
メソッド@
演算子 (Python 3.5以降で導入。こちらが推奨されることが多いです)
import numpy as np
matrix_m1 = np.array([[1, 2], [3, 4]]) # (2, 2)
matrix_m2 = np.array([[5, 6], [7, 8]]) # (2, 2)
vector_v1 = np.array([1, 0]) # (2,) ベクトル
print(f"\nmatrix_m1:\n{matrix_m1}")
print(f"matrix_m2:\n{matrix_m2}")
print(f"vector_v1: {vector_v1}")
# 要素ごとの積 (おさらい)
element_wise_product = matrix_m1 * matrix_m2
print(f"\n要素ごとの積 (matrix_m1 * matrix_m2):\n{element_wise_product}")
# 行列積 (np.dot)
dot_product1 = np.dot(matrix_m1, matrix_m2)
print(f"\n行列積 (np.dot(matrix_m1, matrix_m2)):\n{dot_product1}")
# [[1*5+2*7, 1*6+2*8], [3*5+4*7, 3*6+4*8]] = [[19, 22], [43, 50]]
# 行列積 (@演算子) - こちらが推奨
dot_product2 = matrix_m1 @ matrix_m2
print(f"\n行列積 (matrix_m1 @ matrix_m2):\n{dot_product2}")
# 行列とベクトルの積
matrix_vector_product = matrix_m1 @ vector_v1 # vector_v1は列ベクトルとして扱われることが多い
print(f"\n行列とベクトルの積 (matrix_m1 @ vector_v1):\n{matrix_vector_product}") # [1 3]
# 形状が合わない場合の行列積はエラーになる
matrix_m3 = np.array([[1, 2, 3], [4, 5, 6]]) # (2, 3)
# dot_product_error = matrix_m1 @ matrix_m3 # (2,2) @ (2,3) はOK, 結果は(2,3)
# print(dot_product_error)
# dot_product_error2 = matrix_m3 @ matrix_m1 # (2,3) @ (2,2) はエラー (ValueError)
2.4. その他の基本的な線形代数演算 (簡単に紹介)
NumPyのlinalg
サブモジュールには、さらに多くの線形代数演算関数が含まれています。ここではいくつか名前だけ紹介します。
- 単位行列の作成:
np.eye(N)
またはnp.identity(N)
(N x Nの単位行列) - 逆行列:
np.linalg.inv(matrix)
(正方行列の逆行列を計算) - 行列式:
np.linalg.det(matrix)
(正方行列の行列式を計算) - 固有値・固有ベクトル、特異値分解など、高度な演算も可能です。
これらの詳細については、線形代数の知識と共に、また別の機会に触れることになるでしょう。
3. ちょっと実践:計算結果を保存して読み込んでみる
簡単な例として、2つの行列を作成し、その行列積を計算し、元の行列と結果の行列を.npz
ファイルに保存してみましょう。そして、それを読み込んで確認します。
import numpy as np
# 行列の作成
A = np.array([[1, 0], [2, 3]])
B = np.array([[4, 1], [0, 2]])
# 行列積
C = A @ B
print(f"行列A:\n{A}")
print(f"行列B:\n{B}")
print(f"行列積 C = A @ B:\n{C}")
output_filename = 'matrix_operations.npz'
# ファイルに保存
np.savez(output_filename, matrix_a=A, matrix_b=B, result_c=C)
print(f"\n'{output_filename}' に行列を保存しました。")
# ファイルから読み込み
loaded_matrices = np.load(output_filename)
A_loaded = loaded_matrices['matrix_a']
B_loaded = loaded_matrices['matrix_b']
C_loaded = loaded_matrices['result_c']
print(f"\n読み込んだ行列A:\n{A_loaded}")
print(f"読み込んだ行列B:\n{B_loaded}")
print(f"読み込んだ行列C:\n{C_loaded}")
# 元の計算結果と一致するか確認
print(f"C_loadedは A_loaded @ B_loaded と同じか: {np.array_equal(C_loaded, A_loaded @ B_loaded)}")
まとめ:NumPyでデータと計算を次のレベルへ!
今回は、NumPyシリーズの中級編第一歩として、以下の内容を学びました。
- NumPy配列を効率的にファイルに保存・読み込みする方法:
- 単一配列向けのバイナリ形式
.npy
(np.save()
,np.load()
) - 複数配列向けのバイナリ形式
.npz
(np.savez()
,np.load()
) - テキスト形式 (
np.savetxt()
,np.loadtxt()
)
- 単一配列向けのバイナリ形式
- 線形代数の基本的な演算:
- 行列の転置 (
.T
属性,np.transpose()
) - 行列積 (
np.dot()
関数,@
演算子)
- 行列の転置 (
ファイル入出力の機能を身につけることで、NumPyで処理した大切なデータを永続化したり、他のプログラムや人と共有したりすることが容易になります。また、線形代数の基本操作は、NumPyがデータサイエンスや機械学習といった分野で広く使われる理由の一つであり、これらの分野に進む上での重要な基礎となります。
NumPyの世界はまだまだ奥深いです。次回は、NumPy配列のより高度な操作や、統計処理、乱数生成といった、さらに実践的な機能について探求していく予定です。お楽しみに!
コメント
コメントを投稿