感度=陽性的中率の特性

概要

機械学習のモデルの性能や感染症検査の確からしさを検証する際、陽性的中率(適合度)や陰性的中率を確認すべきだが、これらの値が、そもそものデータの特性やモデル/検査の性能によってどのように変化するかを確認する。

具体的には、注目事象の率と真陽性率(感度)・真陰性率(特異度)を変化させたときの、陽性的中率・陰性的中率の変化を見る。

これらの値の意味や計算方法については、Confusing matrixを参照。

その結果から、以下のようなことがわかった。

  • 予測モデルや検査において、単に感度のみを向上させても適合度(陽性的中率)は大きく変化しない
  • 特異度を向上させることで適合度は大きく向上する
  • ターゲット比率がとても小さい場合、感度・特異度をかなり大きくしても、適合度は小さな値になる

2020年現在、世界的に大きな影響を及ぼしているCOVID-19(新型コロナウィルス)感染症のPCR検査では、一般に感度が70%程度、特異度が90%以上、陽性的中率が数%程度という値が多い。感度が7割程度というのは少し低く、陽性的中率がそもそも小さすぎるという気がしていたが、上記のことと符合することがわかった。

指標

以下の指標を、目的として計算する指標とする。

  • PPV(Positive Predicted Value):陽性的中率、適合度、Precision
  • NPV(Negative Predicted Value):陰性的中率

これらの指標を計算するために用いる指標は以下の通り。

  • TR(Target Rate):注目事象の全体比率(ターゲット比率)
  • TPR(True Positive Rate):真陽性率、感度(Sencitivity)
  • TNR(True Negative Rate):真陰性率、特異度(Specificity)

例えば感染症の例で言うと、有病率(TR)、検査の感度(TPR)、特異度(TNR)がわかっているときに、陽性的中率(PPV)、陰性的中率(NPV)を求めることに相当する。

PPV・NPVの計算式の導出

元データの構成

まず、confusing matrixを以下のように表現する。これは、データ数で表現されたテーブルの各要素を全データ数で割った率で表すことに相当する。

     \begin{align*} \begin{array}{cc|cc|c} & & \mathrm{Prediction}\\ & & \mathrm{Positive} & \mathrm{Negative} & \mathrm{Sum} \\ \hline \mathrm{Fact} & \mathrm{Positive} & tp & fn & r_1 \\ & \mathrm{Negative} & fp & tn & r_2 \\ \hline & \mathrm{Sum}& c_1 & c_2 & 1 \end{array} \end{align}

PPV・NPVの計算式

まず、事実(Fact)がpositiveである率がr1に相当し、これはTR (target rate)に等しい。このTRと率TPRを使って、Positiveの行のtp(true positive)とfn (false negative)の率を計算。

(1)    \begin{align*} r_1 &= TR \\ tp &= r_1 \cdot TPR = TR \cdot TPR \\ fn &= r_1 \cdot (1 - TPR) = TR (1 - TPR) \end{align*}

2行目の合計r2については、行和の合計が1になることから以下のように計算される。

(2)    \begin{align*} r_2 &= 1 - r_1 = 1 - TR \end{align*}

このr2と率TNRからNegativeの行のtn(true negative)とfp (false positive)を計算。

(3)    \begin{align*} tn &= r_2 \cdot TNR = (1 - TR) TNR \\ fp &= r_2 (1 - TNR) = (1 - TR) (1 - TNR) \end{align*}

tpとfpからc1を、tnとfnからc2を計算。

(4)    \begin{align*} c_1 &= tp + fp = TR \cdot TPR + (1 - TR) (1 - TNR) \\ c_2 &= tn + fn = (1 - TR) TNR + TR (1 - TPR) \end{align*}

PPV(陽性的中率、感度)はc1に対するtpの率で計算される。以下の式は分数の分数で若干ややこしいが、3つの指標が1回ずつ現れ、整った形になる。

(5)    \begin{align*} PPV &= \frac{tp}{c_1} = \frac{TR \cdot TPR}{TR \cdot TPR + (1 - TR) (1 - TNR) } \\ &= \frac{1}{1 + \left(\dfrac{1}{TR} - 1 \right) \dfrac{1 - TNR}{TPR}} \end{align*}

NPV(陰性的中率、特異度)はc2に対するtnの率で計算される。以下の式とPPVの式を比べると、はTRの分数項ついて逆数であり、TPRTNRが入れ替わっていて、PPVNPVで対称性がある。

(6)    \begin{align*} NPV &= \frac{tn}{c_2} = \frac{(1 - TR) TNR}{(1 - TR) TNR + TR (1 - TPR)}\\ &= \frac{1}{1 + \dfrac{TR}{1 - TR} \dfrac{1 - TPR}{TNR}} \end{align*}

パラメーターに応じたPPV・NPVの変化

PPV

上記の結果を用いて、ターゲット比率、真陽性率(感度)、真陰性率(特異度)の様々な値に対するPPV(陽性的中率)、NPV(陰性的中率の変化を観察する。

まず、ターゲット比率が1に近い(ほとんどがターゲットとなるような)状態から、ターゲットが0に近いような(ターゲットとなるデータがほとんどないような)状態の間で、PPVがどのように変化するか確認してみる。

TPR(感度)の値によって曲線の形に若干の変化はあるがあまり大きくは変わらず、むしろTNR(特異度)の値による曲線の形状の変化が大きい。ここでTRが0.1~0と小さい範囲のところを見てみる。

やはり感度の影響はあまり大きくないようである。TNRを大きくするにしたがって曲線の形状は大きく変化し、ターゲット比率が小さいところでの適合度が向上するが、ターゲット比率が0に近いところではPPVがかなり小さくなる。

次に、TPRを変化させたときの曲線の違いが分かるように、表示させる変数を入れ替えてみる。まずTRが1~0の全域。

やはり感度による曲線の変化は小さく、特異度の影響が大きい。以下のようにTRが0.1~0の範囲を拡大しても同様の傾向。

以上の結果から、以下のことが言える。

  • ターゲット比率が低くなるほどNPVは小さくなる(適合度が低くなり、予測/検査の信頼性が下がる)
  • 予測モデルや検査のTPR(感度)を上げることによるPPVの向上効果はあまり大きくない(いたずらに感度を上げても顕著な効果はない)
  • TNR(特異度)の向上によって、適合度は大きく向上する
  • ターゲット比率がとても小さい場合、その率の現象に従って適合度は急激に低下する

さらにこれを一般的な表現でまとめると、

  • 予測モデルや検査において、単に感度のみを向上させても適合度(陽性的中率)は大きく変化しない
  • 特異度を向上させることで適合度は大きく向上する
  • ターゲット比率がとても小さい場合、感度・特異度をかなり大きくしても、適合度は小さな値になる

NPV

PPVと同様にNPVについても計算してみた。

まずいくつかのTPRに対して、TNRを変化させて曲線を描いたもの。PPVの場合と比べて形状が左右逆で、TNRを固定してTPRを変化させたときの図と同じ傾向。

次に、いくつかのTNRを固定してTPRを変化させたもの。これもPPVと形状、TPRTNRの関係が逆になっている。

PPVとNPVの関係

PPVNPVが同じTPRTNRに対してどのように変化するか重ねてみる。

TPRとTNRを同程度とすることでターゲット比率0.5付近で双方が等しくなり、その値を高くすることで、より広い範囲でPPVが向上する。

シミュレーションによる挙動確認

これまでの結果は、confusion matrixの各要素にTR、TPRなどの比率を適用してPPV、NPVを計算した。この方法は、ある予測/判定が理論通りに再現された場合だが、実際にはターゲットとなる事象の割合も、予測がpositive/negativeになる割合も確率事象である。

そこで念のため、多数の二値(True/False)正解データをランダムに生成し、これに対してTPR、TNRの設定に従った答えを出す疑似的なモデルで「予測」する。その結果を整理したconfusion_matrixからPPVを計算したのが以下の図である。

その結果は計算式による場合と同じで、理論上の挙動と実世界で起こるであろう挙動が一致している。

処理内容は以下の通り。

  • 与えられたTrue/Falseに対して、あらかじめ設定したTPR/TNRと一様乱数に従ってTrue/Falseを「予測」する疑似予測モデルを準備
  • TR=1~0の間で100個のデータについてPPVを計算する
    • 1つのTRについて10万個の2値正解データを生成
    • 正解データセットを疑似予測モデルに適用して予測データセットを得る
    • 予測データセットからconfusion matrixを構成し、その要素からPPVを計算し、配列に格納
  • 以上の結果をプロット

 

Confusion Matrix~混同行列

概要

精度が高いのに性能が悪い?

クラス分類の機械学習の結果、全体の精度のほか、注目しているクラスの分類性能などについて確認しておく必要がある。

たとえば製造部品の良/不良を判別するケースで不良品の確率が1/1000などとても小さい場合や、疾病の判定をするケースで罹患する率が1万人に1人と非常に低い場合を考えてみる。

求めているのは、僅かに発生する不良品を選りだすことや、稀に罹患している人を特定することだ。このとき、ターゲットでない(正常品や罹患していない)多数のクラスを正確に分類できれば全体の正解率は上がる。ところがその一方で、求めている事象(不良品や罹患者の)ターゲットの分類精度が低いと、正解率には影響しないが本来求めているターゲットの分類機能としては低くなる。

誤判定の度合い

このほか、疾病に罹患していると判定したのに実際には罹患していない場合や、罹患していないと判定したのに実は罹患している場合など、分類器の誤判定の度合いも重要だ。

間違ってターゲットを特定してもいいから漏れがないようにしたいのか、誤って特定するリスクを避けたいのか、それらをもちいるケースに応じて分類器の性能がどうあるべきかを検討する必要がある。

confusion matrixの活用

このような場合にもちいられるのがconfusion matrixである。それは機械学習において用いられるテーブルで、クラス分類のターゲットクラスと予測されたクラスを行と列にとり、各々がどのように一致しているか/異なっているかを示したものである。

その要素と行/列の合計から、予測モデルの性能を示す様々な指標を計算することができる。

Confusion matrixの構成

3クラス分類の例

Confusion matrixは以下のように構成される。

  • 行(または列)に正解のクラス列を、(または行)に予測されたクラスの列を、同じ順番で並べる
  • 各正解クラスに対して、予測されたクラスの数を入れていく

例えば画像認識で果物を判別する予測モデルを考え、りんご、梨、洋梨の3つのクラスを分類するものとする。このとき、ある予測を行った結果として得られたconfusion matrixの一例を示す。

行の側に正解(事実)、列の側に予測(判定)を置いているが、この定義は場合によって入れ替わる(Scikit-learnのライブラリーではこれと同じ配置だが、WikipediaのConfusion matrixの解説では逆になっている)。

予測
りんご 洋梨
正解 りんご   80   15   5   100
  27   70   3   100
洋梨   2   3   95   100
  109   88   103   300

2クラス分類での一般化

左記の果物のconfusion matrixは3クラス分類の例だが、以下は2クラス分類で考えていく。様々な2クラス分類におけるconfusion matrixの共通した構成を一般化したのが以下の図である。2つのクラスのうち”Positive”と表現しているのが「特に捕捉したい/注目している事象」に分類されるもので、たとえば不良品や疾病の発見、成長企業の特定など。他方はそれ以外で、製造品が正常、被検者が罹患していないといった捕捉の対象としない事象に対応する。

Positiveな事象(注目している事象)について、英語では”relevant instances”などの表現が使われているが、relevantの意味には、直訳の「関連~」だけではなく「重要な」というニュアンスもあるようなので、ここでは「注目」という用語を使う。

まず表の左上と右下について。予測した事象と実際の事象が一致している場合で色が同じになっている。この場合は予測が正しいという意味でTrueとする。左上はPositive(注目事象)と予測してそれが正しいのでTrue Positive (TP)と呼ぶ。また右下はNegative(注目していない事象)と予測してそれが正しいのでTrue Negative (TN)と呼ぶ。

次に左下と右上について。今度は予測結果の色と事実の色が異なっている。この場合は予測が誤っているという意味でFalseとする。左下はPositiveと予測したが誤りなのでFalse Positive (FP)、一方右上は予測がNegativeだがそれが誤りなのでFalse Negative (FN)と呼ぶ。

True/FalseとPositive/Negativeの順番とテーブル上の位置がややこしいが、常に予測・判定結果から見てそれが事実に対して正しいか誤りかと考えて「正しい/誤った、Positive判定/Negative判定」と定義されている。

Positive/Negativeを疾病検査の陽性(potitive)/陰性(negative)にあてはめると、TP:真陽性、TN:真陰性、FP:偽陽性、FN:偽陰性とも呼ばれる。

2クラス分類の例

疾患検査

次に2クラス分類の実世界での例を見てみる。まず、よくある例として、ある疾患にかかっているかどうかを検査する例。この場合はまさしく予測が陽性(Positive)か陰性(Negative)かに相当する。FPならば罹患していない人が不要な治療・対応を受けることになり、FNならば罹患している人を見逃すことになる。

犯人特定

次に、カメラ画像や様々な証拠などから犯人を見つけ出すような問題。対象者が犯人であるという事象に注目して、これをpositiveな判定としている。FPの場合は無実の人の誤認逮捕に結びつき、FNならば犯人を取り逃がすことになる。

ヒット商品予測

これまでの2つは、どちらかと言えば注目事象がよくない影響を及ぼすものだったが、これがよい効果をもたらす例を考えてみる。以下の例は、開発しようとしている商品がヒットするかどうか、いろいろな情報に基づいて予測しようとするものである。FPならヒットしない商品に無駄な投資をすることになり、FNならばヒット商品の開発の機会を逃すことになる。

4つの象限の意味・結果

これらの例も見ながら、confusion matrixの4つの象限がどのような意味を持つか、以下のように整理してみる。

TP
注目対象を正しく分類する。対処すべき事象が特定できる。
FP
注目すべきでないものを誤って注目対象に分類してしまう。注目対象が好ましくない事象の場合はその対策に余計なコストがかかったり、場合によっては謂れのない差別などの対象となったりする。好ましい事象の場合は、無駄なコストをかけることになる。統計学で言う第2種の過誤にあたる。
TN
注目対象以外のものを正しく分類する。注目対象を誤って見逃すことがなく、被害の拡大や機会損失を避けられる。
FN
注目対象を誤って注目対象以外に分類してしまう。捕捉すべき望ましくないものを見逃して影響が拡大したり、望ましいものを見逃して利得を得る機会を逃したりする。統計学で言う第1種の過誤にあたる。

指標

Confusion matrixの4つの象限の値から、複数の指標が導かれる。それぞれの和名には、時々異なるものを指している例があるので、英語表現を基本にする。

全体に対する率

まず、4象限全体(すなわち全データ数)に対する率を考える。これらは注目事象か非注目事象かに関わらない、モデル全体の正確さを表す。

Accuracy(正解率・正確度)

予測結果が正しく注目対象と非注目対象を言い当てた率。4象限の対角要素の合計の、総計に対する率を計算する。様々な機械学習モデルのスコアとして計算される値に相当する。Accuracyは「(ばらつきはともかく)予測が真値をどれだけ(平均的に)言い当てているか」という意味。このAccuracyを「精度」と呼んでいる場合があるが、科学的な表現としては少しずれている。

(1)    \begin{equation*} \frac{TP + TN}{TP + FP + FN + TN} \end{equation*}

Error Rate(不正解率)

Accuracyと逆で、予測結果が実際の注目・非注目対象から外れた率。4象限の非対角要素の合計の、総計に対する率として計算する。

(2)    \begin{equation*} \frac{FP + FN}{TP + FP + FN + TN} \end{equation*}

正解・事実に対する率

表の横方向の、各行それぞれの合計に対する率。正解に対するモデルの正確さを表す。

Sensitivity/Recall/TPR
(感度・再現率・検出率・真陽性率)

正解が注目事象の場合に、モデルも注目事象と分類する率。疾病検査を例にすると、その検査が疾病をとらえる「感度・検出率」となる。TNR(True Positive Rate):真陽性率については、この後も真~率、偽~率が出てくるが、これらはすべて行方向に対する(正解・事実に関する)予測・分類の正確さと定義される。”recall”のニュアンス(呼び戻す、想起するなど)は、この指標の意味に繋がりにくい。むしろ無理やり日本語にしたような「再現率」の方がまだ本来の意味に近いと感じられる。

(3)    \begin{equation*} TPR = \frac{TP}{TP + FN} \end{equation*}

Specificity/TNR(特異度・真陰性率)

正解が注目していない事象の場合に、モデルがそれを間違いなく分類した率。疾病検査なら、罹患していない人の結果が陰性となる率。「特異度」という訳は”specific”~「特別な」というあたりから名付けたのかもしれないがセンスが悪い。むしろこれは問題ないものを問題ないと分類する率だから、「特異」ではないはずだ。せめてspecify~特定するで「特定率」くらいならまだしもか。TNRはTrue Negative Rate。

(4)    \begin{equation*} TNR = \frac{TN}{FP + TN} \end{equation*}

FNR(偽陰性率)

FNR(False Negative Rate)はTPRの裏で、正解が注目事象なのにそうでないと分類してしまった率。罹患しているのに検査で陰性となる率に相当する。

(5)    \begin{equation*} FNR = 1 - TPR = \frac{FN}{TP + FN} \end{equation*}

FPR(偽陽性率)

FPR(False Positive Rate)はTNRの裏で、正解が注目していない事象なのに注目事象だと判定してしまった率。罹患していないのに検査で陽性だと判定されてしまう率に相当する。

(6)    \begin{equation*} FPR = 1 - TNR = \frac{FP}{FP + TN} \end{equation*}

予測・判定結果に対する率

表の縦方向、各列の合計に対する率。分類結果がどの程度信頼できるかを表す。日本のサイトではPPV、高々NPVまでしか紹介されていないが、英語版のWikipediaではすべて図入りで説明されている。

Precision/PPV(適合度・精度・陽性的中率)

Precision(適合度)はモデルが注目事象と予測した場合に、実際にそれが注目事象である率。疾病検査で陽性判定の場合に実際に罹患している率に相当する。なお、科学上の表現でのprecision(精度)は、本来ばらつきの小ささを意味する。PPVはPositive Predictive Value。

(7)    \begin{equation*} PPV =  \frac{TP}{TP + FP} \end{equation*}

NPV(陰性的中率)

NPV(Negative Predictive Value)はモデルが注目事象ではないと分類して、それがあたっている率。疾病検査で陰性の場合に罹患していない率に相当する。敢えて日本語で言うなら「適正排除率」くらいか。

(8)    \begin{equation*} NPV =  \frac{TN}{FN + TN} \end{equation*}

FDR(陽性誤り率?)

FDR(False Discovery Rate)はモデルが注目事象であると分類したのに、実際には非注目事象である率。英語表現の直訳なら「間違って発見する率」。検査で陽性判定だが罹患していない率に相当する。日本語の訳はないが、敢えて言うなら「過剰陽性判定率」とか「陽性失中率」くらいか。

(9)    \begin{equation*} FDR = 1 - PPV =  \frac{FP}{TP + FP} \end{equation*}

FOR(陰性誤り率?)

FOR(False Omission Rate)はモデルが注目事象でないと分類したのに注目事象である率。疾病検査で陰性判定だが、実は罹患している率に相当する。英語表現の直訳なら「間違って無視してしまう率」だが、日本語なら「逸失率」くらいか。

(10)    \begin{equation*} FOR = 1 - NPV =  \frac{FN}{FN + TN} \end{equation*}

指標間のトレードオフに対する疑問

一般に、Sensitivity(感度・検出率)とPrecision(適合度・陽性的中率)はトレードオフの関係にある、と述べられることが多い。これは単純な仕組みで感度を上げようとするときに、注目対象以外でも多めに陽性と判定すれば率は上がるが、その場合は陽性判定でも注目対象以外のものが多くなって適合率が下がる、ということから来ている。

ここが少しおかしい。感度を上げるにはTPを大きくしFNを小さくしなければならない。このとき適合度の側から見れば、TPが大きくなるなら適合度も上がるし、FNを小さくしたときにFPが大きくなるという相関関係がなければならない。

実際には、見落としを少なくしようとすれば、無関係なケースを陽性と判定する「濡れ衣(FP)」は増えるだろう。しかしこの「濡れ衣(FP)」は、いくら増えても感度には寄与しない。これは感度が上がっていないのに(不安なので)陽性が多めに出るようにしているに過ぎないと思われる。だとすると、このような方針は単に適合度を下げているだけで感度は向上せず、「トレードオフ」とは言えない。

その他の指標

F値

感度と適合度のトレードオフにには疑問があるが、そのバランスを保って双方向上させるというのは重要だ。このような指標がF値(F value)と呼ばれるもので、感度と適合度の調和平均として定義されている。

(11)    \begin{equation*} F = \left( \frac{1}{2} \left( \frac{1}{TPR} + \frac{1}{PPV} \right) \right) ^{-1} = \frac{2 TPR \cdot PPV}{TPR + PPV} \end{equation*}

これを4象限のパラメーターを使って書き直してみる。

(12)    \begin{align*} F = \left( \frac{1}{2} \left( \frac{TP + FN}{TP} + \frac{TP + FP}{TP} \right) \right) ^{-1} &= \frac{2 TP^2}{2TP + FN + FP} \\ &= \frac{TP}{1 + \dfrac{FN + FP}{TP}} \end{align*}

一般にF値は感度と適合率のトレードオフを想定して、双方を加味した指標とされているが、双方のバランスがとれた状態がF値を最大化するというわけでもなさそうだ。

用語について

“confusion”は、LONGMAN、Cambridgeなどの英英辞典を見ると、(不明瞭な状況、人や物事などの誤認による)混乱・混迷、(不快な状況下での)困惑というニュアンスで、confusion matrixを的確に表現できるものがない。英語サイトで「confusion matrixの語源は何か?」という問いかけがいくつか見られた。どうも心理学にその元があるようだが、その中で言及されている”classification matrix”の方が明快に思われる。実際、TP、FNなどのタームや指標の名称がかなりconfusingなことからみると、アメリカ流のジョークとも思えてしまう。

和名は「混同行列」とされているが、これも何と何を混同するのか不明瞭だ。”confusion”の的確な訳ではないので、何となくそれに近い言葉を一生懸命にあてたのかもしれない。それならいっそのこと、より的確な用語(判定行列とか)をあてればよかったのにと感じる。

 

Python – 行・列単位の合計・率の計算

概要

2次元のndarrayDataFrameで、行単位や列単位での合計を計算したり、それを使って行単位/列単位の率を計算する方法。

2次元のndarrayの場合は、

  • 合計はsum()メソッドの引数にaxisを指定
    • 列和ならaxis=0、行和ならaxis=1
    • 結果は1次元配列で得られる
  • 率の計算はこれらの合計の配列を使うが、列和に対する各列要素の率なら1次元配列の行ベクトルのまま、行和に対する各行要素の率なら2次元の列ベクトルに変換して除算

DataFrameの場合、まず合計を求めるには、

  • 合計はsum()メソッドの引数にaxisを指定
    • 列和ならaxis=0、行和ならaxis=1
    • 結果はSeriesオブジェクトで得られる

その上で率の計算には2通りある。1つ目はSeriesオブジェクトの内容をndarrayとして取り出して計算する方法で、

  • Series.valuesで列和/行和の配列を取り出し、ndarrayの場合と同じ方法で計算する

もう1つの方法はSeriesオブジェクトのままでdiv()メソッドにaxisを指定する方法で、

  • 列和に対する各列要素の率を計算するには、div(列和Series, axis=1)
  • 行和に対する各行要素の率を計算するには、div(列和Series, axis=0)

ndarrayの場合

確認

まず確認のために、以下の配列を準備する。

行ベクトルを2次元配列に加えると、配列の各行に対して行ベクトルが加えらえる。

列ベクトルを2次元配列に加えると、配列の各列に対して列ベクトルが加えられる。

つまり、ndarrayの2次元配列に行または列のベクトルを加えると、加える方のベクトルの形状に合わせて各行/列に演算が実行される。これは他のオペレーターについても同じ。

この演算は直感的にも分かりやすく、列ごと/行ごとの小計に対する比率の計算も思い浮かべることができる。

合計

ndarrayの各列/行に沿った合計を計算する。合計計算はndarraysum()メソッドを使うが、引数を省略すると全要素の和となる。引数にaxis=0を指定すると列方向に処理がされ(つまり配列の各列の要素が列方向に足され)、axis=1を指定すると行方向に処理がされる(つまり配列の各行の要素が行方向に足される)。

以下の例は、最初に使った2次元配列の列方向の和(の行ベクトル)と行方向の和(の列ベクトル)を計算している。

率の計算

2次元配列を行合計ベクトルで割ると、各行の要素が合計ベクトルの各要素で割られる(各列の行要素の合計は1になる)。

また、2次元配列を列合計ベクトルで割ると、各列の要素が合計ベクトルの各要素で割られる。繰り返しになるが、この場合の合計ベクトルは2次元の列ベクトルになっている。

以下の図のように、この

DataFrameの場合

確認

まず確認のために、以下のDataFrameSeriesを準備する。2次元配列はndarrayの例と同じものを流用。

合計ベクトルがndarrayの場合

演算をほどこすベクトルがndarrayの場合、2次元配列の時と同じように、ベクトルが行/列によって自動的に加えられる方向が決められる。

合計ベクトルがSeriesの場合

DataFramesum()メソッドで行や列の合計を計算するとSeriesオブジェクトで結果が得られるため、その挙動を確認しておく。

Seriesを単純にDataFrameと演算子で結ぶと、行ベクトルとして扱われる。

Seriesは行・列の概念を持たないが、演算の方向を明示するのに以下の方法をとる。

演算子に対応するメソッドは、addsubmuldivmodpowが準備されている。

合計

列/行ごとの合計は、ndarrayと同じくDataFramesum()メソッドで引数axisを指定して計算する。

 

率の計算

Seriesndarrayで取り出して計算する方法

Seriesvaluesプロパティーでその内容をndarrayとして取り出せる。

列和に対しては、それを行ベクトルのまま除算すれば、各列の要素を行ベクトルの対応する要素で割った値となる。

行和に対しては、ndarrayを1列の列ベクトルに変換して除算すれば、各行の要素を列ベクトルの対応する要素で割った値となる。

Seriesのままで計算する方法

合計Seriesをそのまま使って除算する場合はDataFramediv()メソッドを使いaxisを指定するが、axisの指定の仕方に注意が必要。

列和で除算する場合は、その各要素が各行の各要素に対応するため、列和を行ベクトルとみて各行に除算を適用する(axis=1)。

分かりにくいので図示すると以下のようになる。まず合計を求めるのにsum()メソッドでaxis=0として列和を求める。この合計で各要素を割るのに1列目の要素は合計の1つ目の要素、2列目の要素は合計の2つ目の要素・・・で割る必要があるので、div()メソッドでaxis=1とする。こうすると合計のSeriesは行ベクトルとみなされて、それが各行の要素に適用される。

行和で除算する場合は、その各要素が各列の各要素に対応するため、行和を列ベクトルとみて各列に除算を適用する(axis=0)

これも分かりにくいので以下のように図示する。合計を求めるのにsum()メソッドでaxis=1として行和を求める。この合計で各要素を割るのに1行目の要素は合計の1つ目の要素、2行目の要素は合計の2つ目の要素・・・で割る必要があるので、div()メソッドでaxis=1とする。こうすると合計のSeriesは列ベクトルとみなされて、それが各列の要素に適用される。

このように、div()のような演算子メソッドでaxisを使う方法はややこしい(少なくとも私には)。

実行速度

各計算方法の実行速度には、あまり大きな差は出なかった。

実行時間は以下の通りで、各計算手法の間に差はない。敢えて言えば、DataFrameを使った場合に僅かに時間がかかっている。

なお、この計算はpandasのバージョン1.1.4で実行したが、upgrade前のバージョン0では、3つ目と5つ目、DataFrameで行単位の演算を行うときに20秒台と2桁長い時間がかかっていた。

 

axisの方向

概要

配列などのメソッドの引数で指定するaxis=0/1について確認。

  • axis=0は配列やDataFrameを列単位で捉えて、その列の中で処理を行いながら、すべての列に対して処理が行われる
    • SerieseオブジェクトがDataFrameの処理の対象となる場合は列として扱われ、DataFrameの各列を処理しながらすべての列に適用される
  • axis=1は配列やDataFrameを行単位で捉えて、その行の中で処理を行いながら、すべての行に対して処理が行われる。
    • SerieseオブジェクトがDataFrameの処理の対象となる場合は行として扱われ、DataFrameの各行を処理しながらすべての行に適用される

ndarrayの場合

まずndarrayの2次元配列で確認する。

max()メソッド

  • axis=0は列単位で各列の最大値を探し、それらを要素とする配列(要素数=列数の1次元配列)
  • axis=1は行単位で各行の最大値を探し、それらを要素とする配列(要素数=行数の1次元配列)

sum()メソッド

  • axis=0は列単位で各列の合計を要素とする配列(要素数=列数の1次元配列)
  • axis=1は行単位で各行の合計を要素とする配列(要素数=行数の1次元配列)

repeat()メソッド

  • axis=0は列単位で各列の要素が指定回数繰り返される
  • axis=1は行単位で各行の要素が指定回数繰り返される

図による理解

sum()メソッドを例に、axis=0/1に対する挙動を図にすると、以下のようになる。

DataFrameの場合

以下のDataFrameSeriesオブジェクトで確認する。Seriesオブジェクトは行として扱われ、array_like、1次元の配列でも同じ結果になる。

min()メソッドなど

minmaxsumなどのメソッドの考え方はndarrayと同じ挙動。

add()などの演算メソッド

DataFrameには演算子による演算の代替となるメソッドがある(addsubmuldivmodpow)。addメソッドを例にとると、以下のように引数を指定。

add(array_like, axis=0/1)

  • axis=0array_likeを列とみなして、DataFrameオブジェクトの各列の要素との和を計算する
  • axis=1array_likeを行とみなして、DataFrameオブジェクトの各行の要素との和を計算する

apply()メソッド

applyメソッドは、行または列を指定した関数に渡す。

  • axis=0DataFrameオブジェクトの各列を指定した関数に渡す
  • axis=1DataFrameオブジェクトの各行を指定した関数に渡す

演算メソッドの図による理解

演算メソッドは少し挙動が違うので図で整理しておく。1次元のarray_likeオブジェクトがaxisの指定によって列/行としてみなされる点に注意。

補足

1次元配列の場合

1次元配列に対してaxis引数を使う場合、行ベクトルとしてaxis=1に反応しそうだが、実際にはaxis=0で各要素に対する処理が行われる。axis=1を指定すると、たとえば以下のようなエラーになる。

元々多次元配列を意図した引数なので、1次元配列に使うのはナンセンスだろう(axis=0を行単位の処理にしておけば自然ではあったかもしれないが)。

1行の2次元配列の場合

1行の配列(1つの1次元配列を要素に持つ2次元配列:行ベクトル)に対するaxisの効果を、sumメソッドで見てみる。

axis=0の場合は各要素が1要素の列ベクトルとみなされ、3つの列(要素)ごとに処理される。その結果は3つの要素を持つ1次元配列(行ベクトル)となる。

axis=1の場合は行ベクトル全体が1つの行とみなされ、それらの要素に対して処理がされる。その結果は1つの数値となるが、1つの要素を持つ1次元配列で返される。

1列の2次元配列の場合

1列の配列(列ベクトル)に対するaxisの効果を、sumメソッドで見てみる。

axis=0の場合は3つの要素を持つ1つの列に対して処理される。その結果は1つの数値となるが、1つの要素を持つ1次元配列で返される。

axis=1の場合は列の各要素が1要素の行とみなされ、3つの行(要素)ごとに処理される。その結果は3つの要素を持つ列ベクトルだが、3つの要素を持つ1次元配列(行ベクトル)で返される。

DataFrame – 列の操作

概要

DataFrameの列の操作をまとめる。

以下、次のDataFrameを使う。

列の参照

DataFrameで列名を直接指定

1つの列を指定

DataFrameで直接列名を指定するとSeriesオブジェクトが得られ、リストやndarrayにも変換できる。

ファンシー・インデックスによる複数列の指定

DataFrameで直接列名を指定する際に列名のリストを渡すと、その要素の列が並んだDataFrameが返される。

locによる列の指定

1つの列の指定

locで全行のスライス':'とすることで、列名を指定して列を取り出すことができる。結果はSeriesオブジェクト。

スライスによる連続した列の指定

列名にもスライスを使って連続した列を参照することができ、複数列の場合はDataFrameが返される。

ファンシーインデックスによる複数列の指定

locでもファンシーインデックスを使うことができて、個別の列を組み合わせたDataFrameを得ることができる。

列の追加

DataFrameで直接列名を指定して追加

DataFrameに新たな列名を指定して末尾に列を追加。列にarray_likeではなく数値を指定すると、列の全ての要素が同じ数値で埋められる。

locでスライス指定して追加

全行をスライス指定、新たな行名を指定して末尾に列を追加。

assignメソッドによる追加

assignメソッドで末尾に追加する場合、列名は文字列("列名")ではなく、直接「列名=列」で指定する。assignメソッドは元のオブジェクトは変更せず、新たなDataFrameを生成して返す。

insertメソッドによる途中への追加

insertメソッドは(挿入する列位置、"列名"、挿入する列)で挿入する。

列の更新

DataFrameで列名を直接指定

1つの列を指定

DataFrameで列名を直接指定して、その列に新たな列を代入する。代入する列は縦ベクトルでなくてもよく、1次元のリストや配列でよい。

ファンシーインデックスによる複数列の指定

ファンシーインデックスで複数列をリストで指定し、その列数と同じ列数のデータを与えて更新する。

locによる列名・スライスの指定

1つの列の指定

locで全行のスライスと1つの列名を指定して列を更新。

スライスによる連続した列の指定

スライスで連続した列を指定して一括して更新することができる。その場合、指定されたDataFrameの形状に合った次元・次数のデータを与える必要がある。

ファンシーインデックスによる複数列の指定

ファンシーインデックスも使うことができて、この場合も形状に合った次元・次数のデータを与える必要がある。

列の削除

例題のデータ

以下の2つのデータを使って、列番号指定の場合と列名指定の場合を確認する。

1列の削除

drop()メソッドの引数に削除する列の列番号とaxis=1を指定して削除(axis指定の方向に注意)。

第1引数の列指定はlabels=1labels="one"のように指定してもよい。

複数列の削除

複数列を削除する場合はリストで指定。

連続した列の削除

連続した列を削除する場合はリストの内包表記で。

文字列の列名の場合は、泥臭いが以下の方法か。

 

DataFrame – 要素の内容の参照・変更

要素の操作

以下の2つのDataFrameを使っていく。

at/iat~単独要素の参照・変更

at~インデックス指定

atは行・列のラベルを指定して単独の要素を参照・変更

インデックスがデフォルトの数値の場合は数値指定。

iat~行・列番号指定

iatは行・列の番号を数値で指定して単独の要素を参照・変更。行・列のインデックスはカウントされない。

インデックスがデフォルトの数値の場合は数値指定。

loc/iloc~単独要素・スライスの参照・変更

loc~インデックス指定/終端含む

locの単独要素指定もatと同様。

locは行・列のラベルにスライスを指定可能。ただしlocのスライス指定ではリストや配列と異なり、(デフォルトの数値ラベルであっても)終端の行・列を含む点に注意

スライス指定した範囲を変更可能。行単位・列単位のデータの更新の際に使う。

iloc~行・列番号指定/終端含まず

ilocは行・列の番号にスライスを指定可能。ilocのスライス指定では、リストや配列と同じで、終端の行・列は含まれない

ilocでもスライス指定で内容変更が可能。

DataFrame – 生成時の数値と文字列の混在について

概要

DataFrameは列同士の型が異なってもよいが、配列でデータを組み立てる場合に数値と文字を混在させると、数値が全て文字列となってしまうので注意が必要。

配列は不適

以下のようにndarrayで文字列と数値を混在させたデータを基にしてDataFrameを生成すると、その内容が全て文字列になってしまう。

このデータの数値演算を行おうとすると以下のように文字列演算になってしまう。

これはndarrayが型の混在を許さないためで、いわばケアレスミスだが注意。

リストはOK

元のデータをリストにすれば問題なく数値と文字列に分けられる。

列の辞書もOK

列ごとのリストを辞書で組み立てても数値と文字は分けられる。

 

DataFrame – 生成

リスト・配列からの生成

リストからの生成

2次元のリストをそのまま引数にしてDataFrameを生成。列名・行名には自動的に番号が振られる。

ndarrayからの生成

2次元のndarrayからもDataFrameを生成可能。

列名・行インデックスの設定

DataFrame生成時にcolumnsで列名を、indexで行インデックスを設定可能。

列リストと辞書による

列ごとのリストやndarrayが与えられていれば、それぞれの列名をキー、リストや配列を値とした辞書を引数にしてDataFrameを生成できる。

 

DataFrame – 情報・内容の取得

概要

pandas.DataFrameの行数・列数などの数や、列名・行名・データ配列を取り出す方法。以下のデータを使う。

行数・列数・サイズ

df.shapeプロパティーで(行数, 列数)のタプルが帰る。2つの変数にアンパッキングして使える。列名やインデックス列は行数・列数にカウントされない。

行数だけを得るにはlen(df)、列数だけを得るにはlen(df.columns)

df.sizeで全要素数を得られる。

行名・インデックス・データの内容

列名~columns

列名はcolumnsプロパティーで得られる。Indexオブジェクトで格納されていて、リストやndarrayにも変換可能。

行名~index

列名はcolumnsプロパティーで得られる。こちらもIndexオブジェクトで格納されていて、リストやndarrayにも変換可能。

データの内容~values

列名・行名を除いたデータの内容のみを取り出したいときはvaluesプロパティーにアクセスする。結果はndarrayで返される。

なお、valuesの内容を変更すると元のデータが変更される点に注意。以下は元データをndarrayで与えているが、リストで与えても結果は同じ。

 

Figure全体のタイトル

複数グラフを含むFigure全体に1つのタイトルを付けたい場合、Figure.suptitle()を使う。