[NumPy超入門]多次元配列から最大値/最小値を求めてみようPythonデータ処理入門

NumPyには最大値や最小値を求める関数/メソッドがとてもたくさん用意されています。それらの幾つかと最大値や最小値を求める際に注意が必要なNaN値の扱いについて見ていきます。

» 2023年08月18日 05時00分 公開
[かわさきしんじDeep Insider編集部]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「Pythonデータ処理入門」のインデックス

連載目次

連載概要

 本連載はPythonについての知識を既にある程度は身に付けている方を対象として、Pythonでデータ処理を行う上で必須ともいえるNumPyやpandas、Matplotlibなどの各種ライブラリの基本的な使い方を学んでいくものです。そして、それらの使い方をある程度覚えた上で、それらを活用してデータ処理を行うための第一歩を踏み出すことを目的としています。


最大値の取得

 NumPyの多次元配列(numpy.ndarrayオブジェクト)に格納されている要素から最大値を抽出するのに使える関数やメソッドには幾つかの種類があります。

関数/メソッド 説明 NaNがあった場合
numpy.amax関数 引数に指定した配列の要素から最大値を返す NaNを返す
numpy.nanmax関数 引数に指定した配列の要素から最大値を返す NaNを無視する
numpy.ndarray.maxメソッド 呼び出しに使用した配列の要素から最大値を返す NaNを返す
numpy.maximum関数 引数に指定した2つの配列を対応する要素ごとに比較して大きい方の値を取り出し、それらを要素とする新しい配列を作成して戻り値とする NaNを返す
numpy.fmax関数 引数に指定した2つの配列を対応する要素ごとに比較して大きい方の値を取り出し、それらを要素とする新しい配列を作成して戻り値とする NaNを無視する
numpy.argmax関数 最大値を含んでいる要素のインデックスを返す NaNが含まれているインデックス位置を返す
numpy.ndarray.argmaxメソッド 最大値を含んでいる要素のインデックスを返す NaNが含まれているインデックス位置を返す
最大値を取得する関数/メソッド

 以下ではこれらの関数について簡単に見ていきましょう。

numpy.amax関数

 numpy.amax関数の構文を以下に示します。

numpy.amax(a, axis=None, out=None, keepdims=<no value>)

numpy.amax関数の構文(抜粋)

 パラメーターの意味は以下の通りです。

  • a:最大値を調べたいndarrayオブジェクトを指定
  • axis:最大値を求めたい軸(0始まり)
  • out:結果を格納する配列。numpy.amax関数が算出する結果と同じ形状でなければならない
  • keepdims:Trueを指定すると戻り値の次元数が元のndarrayオブジェクトと同じになる

 numpy.amax関数に配列だけを渡したときには、その配列の全要素の中で最大の値が返されます。以下はその例です。

a = np.arange(12).reshape((3, 4))
print(a)
# 出力結果:
#[[ 0  1  2  3]
# [ 4  5  6  7]
# [ 8  9 10 11]]

m = np.amax(a)
print(m)  # 11

最大値の取得

 この例では配列のみを渡しているので、 その中の最大値が返されます(このときには、元の配列を1次元化したものが渡されます)。

 axisに最大値を求める際にどの軸に沿って進むかを指定することも可能です。以下は行方向に最大値を検索する例です。

m = np.amax(a, axis=0# 行方向
print(m)  # [ 8  9 10 11]

行に沿って最大値を探していく

 この例では「axis=0」と指定しているので、行インデックスが小さい方から大きい方へと進みながら最大値が検索されて、見つかった最大値(11)が含まれる行が戻り値になっています。

 一方、「axis=1」を指定した例が以下です。

m = np.amax(a, axis=1# 列方向
print(m)  # [ 3  7 11]

列に沿って最大値を探していく

 この例では各行を列インデックスが小さい方から大きい方へと進みながら最大値を検索して、最大値(11)が含まれる列が戻り値になっています。

 実際には以下のように行と列を(さらに次元数が多ければ、それらの軸の方向も)タプル形式で渡すことも可能です。

m = np.amax(a, axis=(0, 1))  # 行方向/列方向
print(m)  # 11

行と列を指定

 この場合には、最大値がそのまま取り出されます。

 出力を新規の配列に保存しておきたいときにはoutに、numpy.amax関数の戻り値と同じ形状となる配列を渡します。

o = np.empty_like(a[0])
m = np.amax(a, axis=0, out=o)
print(m)  # [ 8  9 10 11]
print(o)  # [ 8  9 10 11]

o = np.empty_like(a[:, 0])
m = np.amax(a, axis=1, out=o)
print(m)  # [ 3  7 11]
print(o)  # [ 3  7 11]

出力を新しい配列に保存

 最初の例ではnumpy.amax関数に「axis=0」を指定しています。そのため、戻り値は4要素の1次元配列です。よって、numpy.empty_like関数を使って、 そうした配列を作成している点に注意してください。

 ここで使用しているnumpy.empty_like関数は、引数と指定した配列と同じ形状で、同じデータ型を要素の型とする配列を新規に作成するものです。

 次の例では「axis=1」と指定しているので、戻り値は3要素の1次元配列になります。これに合わせて、列方向の形状を指定するためにempty_like関数に「a[:, 0]」を渡しています。

 最後のkeepdimsは戻り値の次元数を、元の配列と同じようにするかどうかの指定です。以下に例を示します。

m = np.amax(a, keepdims=True)
print(m)  # [[11]]

m = np.amax(a, axis=0, keepdims=True)
print(m)  # [[ 8  9 10 11]]

keepdims=Trueを指定する例

 ところでNumPyで扱うデータには、データがあるはずのところなのにデータがない(欠損している)といった場合もあります。NumPyではこれらはNaN(Not a Number)値として扱うことが一般的です。NaN値を表すのに、NumPyではnp.nanオブジェクトが使われます。

 どうしてそんな話を突然したかというと、numpy.amax関数は配列中にNaN値があった場合に、それらを最大値として取り出すからです。

a = np.array([np.nan, 0, 1])
m = np.amax(a)
print(m)  # nan

numpy.amax関数では配列中に存在するNaN値を最大値として返送する

 このやり方だと問題があるときには、次に紹介するnumpy.nanmax関数を使うとよいでしょう。

numpy.nanmax関数

 numpy.nanmax関数は配列に格納されているNaN値(numpy.nanオブジェクト)を無視する以外はnumpy.amax関数と同様な振る舞いをします。そのため、ここでは簡単な例を示すだけとしましょう。

a = np.array([0, np.nan, 1])
m = np.nanmax(a)
print(m)  # 1.0

numpy.nanmax関数の使用例

 ここでは戻り値が整数値ではなく浮動小数点数値となっていますが、これはnumpy.nan値の型がfloat型だからです。他の要素は整数のように見えますが、NumPyではある配列には決まった型の値しか格納できないので、それらも実際には浮動小数点数値に型が変換されています。

 ndarrayオブジェクトにはこの後で紹介するmaxメソッドがあります。これは上で見たnumpy.amax関数と同等な処理をするメソッドです。つまり、対象のndarrayオブジェクトにNaN値が含まれていた場合、このメソッドは最大値としてNaN値を返します。一方、ndarrayオブジェクトにnanmaxメソッドはありません。この点には注意しましょう。

 余談ですが、配列にNaN値が含まれているかどうかはnumpy.isnan関数で調べられます。

a = np.array([0, np.nan, 1])
result = np.isnan(a)
print(result)  # [False  True False]

print(np.isnan(np.nan))  # True

numpy.isnan関数

 配列に対してこの関数を使用すると、要素ごとにそれがNaN値かどうかを判定して、NaN値であれば対応する要素をTrueに、そうでなければFalseとした新しい配列が作成され、それが戻り値になります。スカラー値を渡したときには、それがNaN値かどうかを判定してTrueかFalseのいずれかが返送されます。

numpy.ndarray.maxメソッド

 ndarrayオブジェクトが持つmaxメソッドは、上で紹介したnumpy.amax関数に対応するメソッドです(numpy.amax関数では第1引数に最大値を求めたい配列を指定するのに対して、numpy.ndarray.maxメソッドでは、最大値を求めたい配列に対してmaxメソッドを呼び出します)。他のパラメーターについてはnumpy.amax関数と同様なので、以下では例を幾つか示すだけとしましょう。

a = np.array([[1, 2], [3, 4]])

m = a.max()
print(m)  # 4

m = a.max(axis=0)
print(m)  # [3 4]

m = a.max(axis=0, keepdims=True)
print(m)  # [[3 4]]

numpy.ndarray.maxメソッドの使用例

numpy.maximum関数とnumpy.fmax関数

 numpy.maximum関数numpy.fmax関数は、共に配列を2つ受け取って、インデックスが同じ要素同士を比較し、大きい方を選んで新しい(戻り値となる)配列の要素とします。違いはNaN値の扱いにあります。

 numpy.maximum関数は要素の比較時にNaN値があれば、そちらを最大値として扱います。一方、numpy.fmax関数は要素の比較時にNaN値があれば、そうでない方を最大値として扱います。両方の要素がNaN値のときには2つの関数は共に1つ目のNaN値を最大値として選択します。

 なお、これらの関数に渡す配列は形状が同じか、比較が可能になるブロードキャストできる必要があります。

 以下に使用例を示します。

a = np.array([[np.nan, 1], [2, 3]])
b = np.array([[3, 2], [1, np.nan]])

m = np.maximum(a, b)
print(m)
# 出力結果:
#[[nan  2.]
# [ 2. nan]]

m = np.fmax(a, b)
print(m)
# 出力結果:
#[[3. 2.]
# [2. 3.]]

numpy.maximum関数とnumpy.fmax関数の使用例

numpy.argmax関数とnumpy.ndarray.argmaxメソッド

 最大値が配列のどこに含まれているのか、その値ではなくインデックスを知りたいこともあるかもしれません。そうしたときにはnumpy.argmax関数かnumpy.ndarray.argmaxメソッドを使用します。

numpy.argmax(a, axis=None, out=None, *, keepdims=<no value>)
numpy.ndarray(axis=None, out=None, *, keepdims=False)

numpy.argmax関数とnumpy.ndarray.argmaxメソッドの構文

 これらのメソッドもまた、関数の形式かメソッドの形式が異なるだけで後は同様と考えてよいでしょう。パラメーターについても、これまでに見てきたものがそのまま当てはまります。

 まずは次のような配列があったとしましょう。

a = np.array([[0, 3, 2, 1], [2, 4, 8, 6], [2, 5, 6, 1]])
print(a)
# 出力結果:
#[[0 3 2 1]
# [2 4 8 6]
# [2 5 6 1]]

例で使用する配列

m = np.argmax(a)
print(m)  # 6

numpy.argmax関数の呼び出し例

 特に軸の方向を指定しなければ、この関数(およびメソッド)には配列が1次元化されたものが渡されて、その最大値のインデックスが戻されます。上の例なら、最大値は8なので、対応するインデックスである6が戻り値になります。

 次に行方向に最大値を探してみます。これには「axis=0」を指定します。すると、最大値を行が並んでいる方向に探します。つまり、4つの列をインデックスの小さい方から大きい方へと進みながら最大値を探して、各列で最大となる要素の行インデックスを含んだ1行4列の配列が作られるということです。

m = np.argmax(a, axis=0)
print(m)  # [1 2 1 1]

行方向に最大値を検索

 第0列なら最大値の2が2つありますが、このときには先に見つかった要素のインデックス(1)が戻り値に含まれるようになります。第1行では最終行にある5が最大値なので行インデックスは2です。同様に第3列と第4列の行インデックスは1になります。この結果、numpy.argmax関数は[1 2 1 1]という配列を返すことになります。

 列方向に最大値を探すのも同様です。各行をインデックスの小さい方から大きい方へと進みながら最大値を探すので、以下の例では[1 2 2]という配列が戻り値になります。

m = np.argmax(a, axis=1)
print(m)  # [1 2 2]

列方向に最大値を検索

 numpy.ndarray.argmaxメソッドは最大値を探したい配列を引数として指定するのではなく、それに対してメソッド呼び出しを行うことを除けばnumpy.argmax関数と同様です。以下に簡単な例を示しましょう。

m = a.argmax(axis=0)
print(m)  # [1 2 1 1]

numpy.ndarray.argmaxメソッドの呼び出し例

最小値の取得

 ここまで最大値を取得する関数やメソッドについて見てきましたが、もちろん最小値を取得する関数やメソッドもあります。それらを以下の表にまとめます。

関数/メソッド 説明 NaNがあった場合
numpy.amin関数 引数に指定した配列の要素から最小値を返す NaNを返す
numpy.nanmin関数 引数に指定した配列の要素から最小値を返す NaNを無視する
numpy.ndarray.minメソッド 呼び出しに使用した配列の要素から最小値を返す NaNを返す
numpy.minimum関数 引数に指定した2つの配列を対応する要素ごとに比較して小さい方の値を取り出し、それらを要素とする新しい配列を作成して戻り値とする NaNを返す
numpy.fmin関数 引数に指定した2つの配列を対応する要素ごとに比較して小さい方の値を取り出し、それらを要素とする新しい配列を作成して戻り値とする NaNを無視する
numpy.argmin関数 最小値を含んでいる要素のインデックスを返す NaNが含まれているインデックス位置を返す
numpy.ndarray.argminメソッド 最小値を含んでいる要素のインデックスを返す NaNが含まれているインデックス位置を返す
最小値を取得する関数/メソッド

 これらについては最大値ではなく最小値を求める以外はこれまでに説明してきた関数/メソッドと同様なので、コード例を示すだけとしておきましょう。

a = np.array([[1, 2], [3, 4]])

m = np.amin(a)
print(m)  # 1

m = np.amin(a, axis=0)
print(m)  # [1 2]

m = np.amin(a, axis=1)
print(m)  # [1 3]

a = np.array([np.nan, 0, 1])
m = np.amin(a)
print(m)  # nan

m = np.nanmin(a)
print(m)  # 0.0

a = np.array([np.nan, 1, 2, 3, 4])
b = np.array([4, 3, 2, np.nan, 0])

m = np.minimum(a, b)
print(m)  # [nan  1.  2. nan  0.]

m = np.fmin(a, b)
print(m)  # [4. 1. 2. 3. 0.]

a = np.array([[3, 2, 1, 0], [8, 1, 6, 2], [1, 2, 5, 6]])
print(a)
# 出力結果:
#[[3 2 1 0]
# [8 1 6 2]
# [1 2 5 6]]

m = np.argmin(a, axis=0)
print(m)  # [2 1 0 0]

m = a.argmin(axis=1)
print(m)  # [3 1 0]

最小値を求めるコードの例


 NumPyでは配列に含まれる最大値や最小値を取り出すだけでも、この記事で紹介したような関数やメソッドがたくさんあります(これ以外にドキュメントを探せばまだまだ出てきます)。NaN値の扱いをどうするかなどで、どの関数/メソッドを使えばよいか、また最大値を探す軸の方向をどう指定すればよいかなどは、NumPyを使っていくうちに自然に身に付いていくと思います。

 次回はベクトルの内積や行列積の求め方について紹介する予定です(ホントは今回の内容をサクッとまとめて、その辺のお話をするつもりだったのですが、調べ始めると書くことが多くなってしまったのでした)。

「Pythonデータ処理入門」のインデックス

Pythonデータ処理入門

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。