検索
連載

Pythonで線形代数!〜行列編(基礎・前編)数学×Pythonプログラミング入門(4/5 ページ)

AI・機械学習で使われるデータを表現するためにはベクトルや行列などの線形代数を理解することが必要不可欠。今回は行列の各種計算や行、列の抽出、形状の変更方法などについて、プログラミングの方法を初歩から見ていく。

Share
Tweet
LINE
Hatena

目標4: 一部の行や列を取り出す、行や列の合計/平均を求める

 続いて、行や列を操作する方法を見ていきましょう。以下の表2の例は、成績データを表にしたものです。まず、これを配列として表してみてください。配列名はAとします。

名前 国語 数学 理科 社会 英語
H 95 84 81 90 91
Y 85 90 87 71 83
K 74 98 93 74 87
S 88 87 90 94 91
表2 架空の成績データ
ここでは、数値だけを配列にすればいいので、教科の見出しや名前は配列に含めなくても構わない。

 次に、以下のような操作を行ってみましょう。

  • 1行目だけを取り出す(先頭を0行目とするのでYさんの成績)
  • 1列目と2列目だけを取り出す(理数系科目の成績一覧)
  • 0列目、3列目、4列目を取り出す(文科系科目の成績一覧)

 さらに、以下のような計算を行いましょう。

  • 全体の合計を求める
  • 各行の平均を求める(個人ごとの平均)
  • 各列の標準偏差を求める(科目ごとの標準偏差)

 実際のところ、見出しなども含めた二次元の表形式のデータを取り扱うには、pandasのデータフレームが便利なのですが、ここでは、NumPyの配列の機能を知るために、あえてこのような例を使うことにします。

4. 一部の行や列を取り出す(スライス機能)

 まず、配列を作りましょう。おさらいになりますが、考え方としては、

  A = [Hさんの成績, Yさんの成績, Kさんの成績, Sさんの成績] …… (1)

と考え、

  Hさんの成績 = [95, 94, 81, 90, 91]

(1)に埋め込めばいいですね。Yさん、Kさん、Sさんについても同様です。

 配列ができたら、行や列を取り出してみましょう(リスト10)。Pythonのリストと同様のスライス機能が使えます。

import numpy as np

A = np.array([[95, 94, 81, 90, 91],
              [85, 90, 87, 71, 83],
              [74, 98, 93, 74, 87],
              [88, 87, 90, 94, 91]])

print(A[1, :])
# 出力例:
# [85 90 87 71 83]  # 1行目のデータ

print(A[:, 1:3])  # 1列目から3列目の手前まで(1列目と2列目)
# [[94 81]
#  [90 87]
#  [98 93]
#  [87 90]]  # 1列目と2列目のデータ(理数系科目)

print(A[:, (0, 3, 4)])
# [[95 90 91]
#  [85 71 83]
#  [74 74 87]
#  [88 94 91]]  # 0列目、3列目、4列目のデータ(文科系科目)

リスト10 スライス機能を使って行や列を取り出す
スライス機能を使って行や列を指定する場合、1:3は「1から3まで」という意味ではなく、「1以上3未満」という意味になることに注意。列位置に1:3を指定した場合には、結局1列目と2列目を表すことになる。その場合、(1, 2)と指定してもよい。ただし、(0, 3 ,4)(0, 3:5)と書くことはできない。

 スライス機能では、:だけを指定すると「全て」(の行または列)という意味になり、a:bと指定すると「aからbの手前」までという意味になります。さらに、(a,b,d)のようなタプルやリストによって「abd」を指定することもできます。

コラム 配列を結合するには

 リスト10の成績データに、何人かのデータを付け加えたいということもあるでしょう。その場合にはvstack関数に、結合したい配列をタプルまたはリストとして指定します(リスト11)。この場合、2つの配列の列数を同じにする必要があります。

import numpy as np

A = np.array([[95, 94, 81, 90, 91],
              [85, 90, 87, 71, 83],
              [74, 98, 93, 74, 87],
              [88, 87, 90, 94, 91]])
B = np.array([[91, 90, 87, 70, 77],
              [78, 74, 72, 84, 98]])

print(np.vstack((A, B)))
# 出力例:
# [[95 94 81 90 91]
#  [85 90 87 71 83]
#  [74 98 93 74 87]
#  [88 87 90 94 91]
#  [91 90 87 70 77]  # Bが結合された
#  [78 74 72 84 98]]

リスト11 vstack関数で配列を上下に結合する
vstackのvは垂直(vertical)の略。stackは積み重ねるという意味なので、結果がイメージしやすい。引数には結合したい配列をタプルまたはリストで指定する。ただし、元の配列が変更されるのではなく、結合された配列が返されることに注意。

 また、別の科目のデータを付け加えたいこともありますね。その場合にはhstack関数に、結合したい配列をタプルまたはリストとして指定します。例えば、「情報」という科目の成績が、Hさん85点、Yさん92点、Kさん78点、Sさん85点だとすると、リスト12のようにします。この場合、2つの配列の行数を同じにする必要があります。

import numpy as np

A = np.array([[95, 94, 81, 90, 91],
              [85, 90, 87, 71, 83],
              [74, 98, 93, 74, 87],
              [88, 87, 90, 94, 91]])
C = np.array([[85],
              [92],
              [78],
              [85]])

print(np.hstack((A, C)))
# 出力例:
# [[95 94 81 90 91 85]
#  [85 90 87 71 83 92]
#  [74 98 93 74 87 78]
#  [88 87 90 94 91 85]]  # 最後の列が配列Cのデータ

リスト12 hstack関数で配列を左右に結合する
hstackのhは水平(horizontal)の略。配列Cは4行1列になっていることに注意。配列Cは、C = np.array([85, 92, 78, 85]).reshape(4, 1)のように書いて、1行4列の配列を4行1列に変形して作成してもよい。


5. 行や列の合計・平均を求める

 続いて、目標4の後半に進みます。目標は配列Aの以下の値を求めるということでした。

  • 全体の合計を求める
  • 各行の平均を求める(個人ごとの平均)
  • 各列の標準偏差を求める(科目ごとの標準偏差)

 合計を求めるにはNumPyのsum関数が使えます。また、平均はmean関数、標準偏差はstd関数で求められます(リスト13)。スライス機能を使って行や列を取り出し、それぞれの平均や標準偏差を求めることもできますが、axisという引数を指定すれば、各行あるいは、各列の集計値が簡単に求められます。axisとは「軸」という意味です。

import numpy as np

A = np.array([[95, 94, 81, 90, 91],
              [85, 90, 87, 71, 83],
              [74, 98, 93, 74, 87],
              [88, 87, 90, 94, 91]])

print(np.sum(A))  # 全体の合計
print(np.mean(A, axis=1))  # 各行の平均
print(np.std(A, axis=0))  # 各列の標準偏差
# 出力例:
# 1743  # 全体の合計
# [90.2 83.2 85.2 90. ]  # 個人ごとの平均(Hさんの平均=90.2など)
# [7.56637298 4.14578099 4.43705984 9.90896059 3.31662479]  # 科目ごとの標準偏差(国語の標準偏差=7.56……など)

リスト13 合計、平均、標準偏差を求める
axisには、何番目の軸かを指定する。つまり、どのインデックスが変わる方向に計算するかという指定になる。axis=0であれば、0番目の軸、つまり行番号が変わる方向に計算を行う。axis=1であれば、1番目の軸、つまり列番号が変わる方向に計算を行う。

 axisという引数が新しく出てきましたが、これはどの軸に沿って計算するかという指定です。例えば、axis=0を指定して標準偏差を求める場合であれば、0番目の軸に沿って、つまり、行番号が変わる方向に計算するので、まず、A[1, 0]A[2, 0]A[3, 0]A[4, 0]の標準偏差が求められ、次に、A[1, 1]A[2, 1]A[3, 1]A[4, 1]の標準偏差が求められ……というように計算されます。axisを指定しない場合は、合計の場合と同様、全体の平均や標準偏差が求められます。

 np.sum(A)A.sum()と書いても構いません。同様に、それ以降の例もA.mean(axis=1)A.std(axis=0)と書くことができます。

 一つ補足です。NumPyのsum関数と標準ライブラリのsum関数では結果が異なることにも注意してください。print(sum(A))の結果は[342 369 351 329 352]となります(行を軸とした合計になっていますね)。また、標準ライブラリのsum関数ではaxisという引数は指定できません。

コラム 行方向/列方向と軸について

 配列の計算では、行方向や列方向という表現が使われることがあります。例えば、axis=0を指定すると「行方向に集計する」と表現されることがあります。では、「行方向」は以下の図のどちらを意味するのでしょうか。どちらとも取れるので、紛らわしいですね(図4)。

行方向とは?
図4 行方向/列方向という表現は紛らわしい
二次元の配列だと「行方向」「列方向」ではなく「縦方向」「横方向」といえば分かる。ただし、三次元以上の配列になるとそれでは対処しきれない。

 このような場合には、「行」や「列」といった言葉を使わず、単に「何番目の軸なのか」と捉えた方がスッキリします。axis=0なら0番目の軸、つまり行番号が変わる方向となり(図3の左側)、axis=1なら1番目の軸、つまり列番号が変わる方向となります(図3の右側)。

 というわけで、「軸の方向」とか「軸に沿って」といった表現が登場したら、「行方向」とか「列方向」と考えずに、どのインデックスが変わる方向なのか、と考えれば間違うことはありません。


 さて、今回は、配列の要素ごとの演算、ブロードキャスト機能、行や列の操作といったテーマを取り上げました。ぜひ、次の練習問題に取り組んで、理解を確実にするとともに、どのような応用ができるか思い描いてみてください。

Copyright© Digital Advantage Corp. All Rights Reserved.

[an error occurred while processing this directive]
ページトップに戻る