Python – 配列要素の重複数を制限する

概要

文字で表現するとわかり難いが、要するに次のようなことを想定している。たとえば次のような1次元の配列があるとする。

この配列には20個の要素があり、0~4の数値がそれぞれ5個、5個、2個、2個、4個、4個、順不同で含まれている。

この配列において、各数値の数を最大でも3個以内となるように切り落としたい、というのが目標。

たとえば、機械学習の教師データの数がターゲットごとにばらついている場合、各ターゲットのデータ数をある程度の数以下に抑えたいときが想定される。

上の例で仮に早く出現した準から3つまでを残して後は捨てるとすれば、以下のような配列になる。

内容:4 0 3 3 3 1 3 2 4 0 0 4 2 1 0 1 1 0 1 4
個数:1 1 1 2 3 1 4 1 2 2 3 3 2 2 4 3 4 5 5 4

なお、単に1つの配列の要素を切り落とすだけでなく、これと対応する配列が別にあって、その要素についても同時に切り落とすことも想定する。これは、機械学習のターゲット配列でデータを制限するのに、これに紐づけられた画像データなどを格納した配列も同時に操作するイメージ。

手順

ターゲットごとのインデックスの取得

targetsの20個のデータのうちid=0について考える。targetsの要素のうち値が0のものは5個あり、それらのインデックスは(1, 9, 10, 14, 17)。同様にid=1についても5個あり、インデックスは(5, 13, 15, 16, 18)。

このようにしてid=0~4についてインデックスを書き出すと以下の通りになる。

0:1, 9, 10, 14, 17
1:5, 13, 15, 16, 18
2:7, 12
3:2, 3, 4, 6
4:0, 8, 11, 19

各idに対応する配列はnumpy.where()関数を用いて以下のように得られる。

上の例では、ループのidを0~4と変化させていくのにrange(5)を使っている。ところが一般的には、番号が連続して存在しているとは限らず、またその上限もわからない。

そこで、targetsに出てくる要素を重なりなく、かつ全て使うためにnumpy.unique()関数を使っている。unique()関数は引数の配列の要素の重複を除き、昇順・辞書順に並べてくれる。この引数にtargetsを渡して、要素の重なりを除けば、targets中の要素を重なりなく1つずつ参照できる。

取り出す要素の制限

次に、すべてのターゲットのデータ数が3個以下になるようにすることを考える。

これらのデータで各idの個数を3個以下にするのに、出現順位の早いものから3個を選び出すことを考える。

0:1, 9, 10, 14, 17
1:5, 13, 15, 16, 18
2:7, 12
3:2, 3, 4, 6
4:0, 8, 11, 19

各配列の最初の3個を取り出すには、各idに対応する配列の先頭から3個目までをスライスで取り出せばよい。

これで値の最大3個までとするのに取り出すべきtargets中のインデックスが得られた。

要素の抽出

targets配列の要素の個数を制限するには、上で絞り込まれたインデックスに対応する要素を残し、それ以外の要素を切り捨てる。そのためには、残すべきインデックス位置の値がTrue、その他のインデックス位置の値がFalseであるbool配列をつくり、これをtargetsの引数とすればよい。

この配列を例えばmaskという名前とすると、targetsと同じサイズですべての要素がFalseである配列としてmaskを準備し、先ほどの切り落とすべきインデックスの位置のみTrueにするとよい。

以下では、まず全要素がFalsetargetsと同じサイズのbool配列を準備し、各idに対して3つ目までの要素の位置をTrue(1)としている。

ループの1回目で1、9、10番目がTrueになり、2回目で5、13、15番目がTrueに代わっていき、ループを重ねるごとに、取り出すべき要素の位置がTrueになっていることが確認できる。

なおbool配列の初期化では、Falseが数値の0と等価なため、numpy.zeros()関数を使っている。同じ理由で、numpy.where()Trueをセットするときに、数値の1をセットしている。

最後に、このbool配列をtargetsに適用して、取り出すべき要素の配列を得る。

他の配列の同時操作

mask配列は、targetsと同じサイズを持つ次元の配列に繰り返し適用できるので、たとえば機械学習でtargetsの各要素に紐づけられた画像データなどを格納した配列などについても、targetsと整合させながら必要な分だけ切出すことができる。

numpy – bincount

概要

numpy.bincount()関数の仕様

  • 整数型の配列を引数にとる
  • 配列中、同じ値の要素の個数をカウントする
  • 0~要素の最大値を要素とし、各要素番号に対応する値の個数を要素とする配列を返す
  • 元のデータの要素ごとの重みを指定することができる

使い方

基本形

引数で与えた整数型配列中の同じ値をカウントして、各値ごとの個数を要素とする配列を返す。

上の結果の意味は、0が1個、1が2個、2が3個、3が2個。

値が飛んでいる場合

引数の配列中、0~最大値までの整数値に対する数をカウントする。値が存在しない場合の個数は0。

上の例では、0~5までの個数がカウントされ、0, 2, 4は配列中に存在しないので0となっている。

順不同

引数の配列中の要素は昇順である必要はない。

weightsの意味

引数にweightsを指定する場合。

  • データの配列と同じ要素数のweightsの配列を与える。
  • 要素をカウントの場合に1ずつ足すのではなく、各要素の位置に対応した重みが加算されていく

上の例では以下のように動作している。

  • 0は存在しないので0
  • 1は0番目に1つだけ存在し、その位置のweightsの値は0.1
  • 2は1番目と3番目に存在するので、weightsの第1要素0.2と第3要素0.4を加えて0.6
  • 3は存在しないので0
  • 4は2番目、4番目、5番目にあるので、weightsの第2要素0.3、第4要素0.5、第5要素0.6を加えて1.4

 

Python – 平均・分散・共分散

1次元配列の場合

平均

平均はmean()メソッドで計算。

分散・標準偏差

標本分散・標本標準偏差

分散はvar()メソッド、標準偏差はstd()メソッドで計算。デフォルトでは標本分散と標本標準偏差。

不偏推定量

分散の算出では、引数ddof (delta degrees of freedom)で指定した引数をデータ数nから引いた数で偏差の二乗和を割る。デフォルトではddof=0なので標本分散、標本標準偏差になるが、ddof=1を指定すると、不偏分散、標準偏差の不偏推定量が計算される。

2次元配列の場合

平均

平均はmean()メソッドで計算。引数axis=0/1で列方向/行方向の計算方向を指定。計算結果は1次元配列で、要素数は列方向の平均なら行数、行方向の平均なら列数と同じになる。

分散・標準偏差

標本分散・標本標準偏差

分散はvar()メソッド、標準偏差はstd()メソッドで計算。デフォルトでは標本分散、標本標準偏差を計算。デフォルトでは標本分散、標本標準偏差。

不偏推定量

引数ddof=1を指定すると、データ数nからddof=1を引いた数で偏差の二乗和が割られ、不偏分散、標準偏差の不偏推定量が計算される。

共分散

cov()メソッドで二つのデータの共分散行列を計算する。結果は2次元配列で得られ、対角要素は各データの分散、対角要素以外の要素は行数・列数に対応する共分散。

デフォルトでは引数bias=Falseとなっていて、偏差の積和をn−1で割った不偏推定量が計算される。

bias=Trueを指定すると、偏差の積和を割る数がnとなり、標本に対する分散・共分散が計算される。

 

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次元配列(行ベクトル)で返される。

ndarrayの書式設定 – printoptions

概要

配列をprintで表示させようとして、書式設定でよく間違える。たとえば以下のように。

配列の各要素の書式を指定して表示させたい場合、formatメソッドではなく、Numpyのset_printoptionsを使う必要がある。

get_printoptions()

配列の書式オプションの一覧は、numpy.get_printoptions()で得られる。各オプションは辞書形式で保存されている。

set_printoptions()

これらのオプションを個別に設定するにはnumpy.set_printoptions()メソッドでキーと値を指定する。

numpy.set_printoptions([キー]=[値])

よく使いそうないくつかのオプションについてまとめる。

省略表示

thresholdedgeitems

要素数(列数・行数)がthresholdに指定した値を越えた場合に省略表示する。

edgeitemsは省略時に表示する要素数(列数・行数)を指定する。

threshold=0を指定すると、edgeitemsの値を超えると常に省略表示する(デフォルトの場合、edgeitems=3を越えると省略表示)。

2次元配列の行も同じ条件で省略表示される。

数値の書式

supress

デフォルトでは要素にオーダーが小さい数値が含まれていると浮動小数点表示となり、1つの要素でも浮動小数点表示になるとすべての要素が浮動小数点表示になる。

オプションで'supress=True'を指定すると、強制的に固定小数点で表示される。

precision

precisionで精度の桁数を指定する。固定小数点数の場合は小数点以下の桁数、浮動小数点数の場合は仮数部の桁数。

floatmode

floatmodeでキーワードを指定し、あらかじめ定められた書式を設定する。

次のような配列でキーワードごとの挙動を確認する。配列aは最大でもprecision設定より低い精度、配列bprecisionを超える精度の要素を持ち、デフォルトのprecision=8で表示が丸められている。

maxprec

デフォルトの設定。各要素がそれぞれ最大の精度で表示される。いずれの配列も、最大精度となる最後尾の要素の桁幅に統一されていて、0埋めはされない。デフォルトはこの設定なので、結果は上と同じ。

maxprec_equal

maxplecは0埋めされなかったが、maxprec_equalは最大精度の桁数に統一された上で0で埋められる(equalの意味が曖昧、maxprec_zeroとでもしてくれればよかったのに)。

fixed

全ての要素の精度がprecisionに統一され、それより低い精度の場合は0で埋められる。下の例では、2つの配列のすべての要素が小数点以下8桁に統一され、0で埋められている。

unique

precisionは無視され、各要素で必要な分だけの精度が保たれ、桁数は最大精度に統一される。配列bの最後の要素が丸められていないことに注意。

formatter

書式設定文字列とformatを渡して、任意の書式を設定する。渡し方は以下の通り。

formatter={'型名' : "{:書式}".format }

型名としては'int''float'のほか'numpystr'で文字列も指定できる。

 

インデックス配列の置き換え

表題だけではよくわからないが、以下のような場合に使う。

たとえばクラス分類のためのターゲットのデータセットが以下のように与えられているとする。

このとき、クラス0~2に対応する以下のような名前で表現したターゲット配列を得ることができるというもの。

順を追って考えてみるのに、まずnames配列から一つの要素を取り出す。

配列の要素をリストとすると、そのリストの要素をインデックスとみなして、インデックスに対応する元の配列の要素を並べた配列を返す。結果はリストではなくndarray。

配列の要素を配列としても同じように動作する。

これより、クラス分類のターゲット配列などが与えられたときに、これを番号ではなくクラス名などの配列に変換することができる。

なお、インデックス配列が2次元の場合は結果の配列も2次元となる。

 

ndarray.min/max – 配列の最小値と最大値

ndarray.min()/max()は、配列の最小値/最大値を返すメソッド。また、ndarray.argmin()/argmax()は、最小/最大の要素のインデックスを配列で返す。

なお、numpy.amin()/amax()numpy.argmin()/argmax()もほぼ同じ動作をする。

以下、次の配列で動作を確認する。

引数に何も指定しない場合、配列の全要素の中の最小値と最大値を返す。このとき、argmin/argmaxでは、配列をreshape(-1)で1次元化したときのインデックスが返される。

引数にaxis=0を指定すると、各列ベクトルの行方向の中での最小値/最大値を返す。以下の例では、各列ごとの最小値/最大値とそれらに対する行インデックスが配列で返されている。

axis=0の0を2次元配列の引数の位置と考えると0番目の引数で、各列における行の位置を表す。これはargmin/argmaxの意味合いと符合する。

引数にaxis=1を指定すると、各行ベクトルの列方向の中での最小値/最大値を返す。以下の例では、各行ごとの最小値/最大値とそれらに対する列インデックスが配列で返されている。

axis=1の1を2次元配列の引数の位置と考えると1番目の引数で、各行における列の位置を表す。これはargmin/argmaxの意味合いと符合する。

 

numpy – r_とc_

概要

numpy.r_ / numpy.c_は配列を結合するオブジェクト。r_は縦方向に配列を結合し、c_は横方向に配列を結合する。vstack() / hstack()linspace()と似たような使い方ができるが、少し癖がある。

  • 配列と数値を混在させて結合できる
  • スライスでステップ数やか分割数を指定して数列をつくれる
  • vstack()hstack()の代わりに使える

vstack()hstack()と同じように使う。

r_について

numpy.r_で2次元配列に1行だけ追加するとき、1次元配列のままだ”次元が異なる”とエラー。素直にvstack()を使った方がよい。

r_のデフォルトで1次元配列同士を結合すると、単に横方向に結合される。配列と要素が混在していてもok。文字列の配列も結合できるが、文字列要素が混在するとエラーになる。

スライスを使って数列を生成。

3つ目の引数に'j'をつけてnumpy.linspace()と同様の動作。このときは終了値が含まれる。

c_について

numpy.c_で2次元配列にその行数と同じ要素数の1次元配列を結合すると、列ベクトルとみなされて1列追加される。hstack()が1次元配列を列ベクトル化する必要があるのに比べると手軽。

さらに要素数が同じ1次元配列同士を結合すると、それらが列ベクトルとみなされて結合される。

空の配列に対して順次列ベクトルを追加する場合には、empty(n, 0, dtype=type)を準備する。

 

numpy.percentile()~パーセンタイル

numpy.percentile()は、与えた配列から指定したパーセンタイル値を計算する。

percentile(a, q)
a:パーセンタイルを計算する元の配列。
q:パーセンタイル値、または配列。パーセンタイル値は0~100で、百分率表示であることに注意。1次元配列を指定すると、各要素のパーセンタイル値に相当する値が同じサイズの配列で返される。

以下は実行例。パーセンタイル値が要素の間になる場合は内挿される。

元の配列はソートされていなくてもよい。

 

パーセンタイル値を配列で指定した場合。

95%両側信頼区間の場合、以下のように計算できる。