DataFrame – get_dummies – One-hot

概要

DataFrameのget_dummies()メソッドは、属性データ(カテゴリーデータ)をone-hot-encodingの形に変換してくれる。

Scikit-learnにもOneHotEncoderがあるが、get_dumies()はデータの切り貼りをせずにダイレクトに属性変数だけをone-hotの形にしてくれるので便利。

基本

get_dumies()の引数にDataFrameを指定すると、文字列で属性指定されたデータが自動で認識されてon-hotの形に変換される。

分解された列名は、"元の列名_属性名"となり、それぞれに対応する属性の列のみが1、その他の列は0となる。列の並びは、属性名の辞書順。数値データの列は無視される。

属性データが複数列の場合

文字列の属性データが複数列ある場合も、自動的にone-hotに分解してくれる。

属性が数値表現の場合

属性値が文字列ではなく数値表現の場合、get_dummies()の引数に単にDataFrameを渡すだけでは変換してくれない(通常の数量データとして認識される)。

そこで、変換したい列をcolumns引数で指定する。

複数の属性データの列がある場合、columns引数でリスト指定する。

属性名の指定

prefix引数で文字列を指定すると、属性名がその文字列で置き換えられる。ただし複数の属性列が全て同じ文字列になる。

属性列ごとにprefixを変えて指定したい場合はリストで指定。

 

DataFrame – データの概観

概要

DataFrameの規模、格納されているデータの概要や基礎統計量を概観する各種の手順。Scikit-learnのBoston housingデータセットを例にする。

DataFrameの規模・形状

sizeプロパティーで全データ数、shapeプロパティーで行数と列数を確認。

データの先頭部分と末尾部分

head()メソッド/tail()メソッドで先頭/末尾の5行分が得られる。引数で抜き出す行数を指定。

info()~各列の基本情報の表示

info()メソッドは、DataFrameの概要に関する概略情報を出力する。直接標準出力にプリントする点に注意。

たとえば一部にNaNが含まれる場合の出力は以下のようになる。

dscribe()~基本的な統計量

describe()メソッドは、各列のデータについて、個数や平均といった基本的な統計量を計算する。

特定の列の統計量を見たいときは列を指定。

なおstd(標準偏差)については、ddof=1を指定した結果と同じであり、n−1で割った不偏分散。

全ての列を見たいときには、set_option()メソッドの引数でdisplay.max_columnsパラメーターを指定する。初期値に戻すときはreset_option()メソッド。

属性変数のカウント

属性変数の属性値のカウントには、value_counts()メソッドを使う。このメソッドは、ユニークな値の数をカウントして集計する。

2つの属性変数をファンシーインデックスで指定すると、「2つの属性のユニークな組み合わせ」の数が集計される。

 

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で与えているが、リストで与えても結果は同じ。

 

DataFrameのスピード~行の追加

概要

pandas.DataFrameで数千行のデータの組み換えをやろうとしたときにかなり時間がかかったので、簡単な例で実行時間を確認してみた。

結論から言うと、他の様々なサイトで言及されているように、「行単位の追加はかなり時間がかかるが、列単位の追加は圧倒的に早い」ということになる。また、先にリストなどでデータを構成しておいてからDataFrameを生成する方法も高速なことが分かった。

問題設定

次のように、3つの列を持つ行データを1万個、DataFrameに追加していく例を考える。

appendメソッド

appendメソッドは2つのDataFrameを結合するメソッドで、行の追加方法としてもよく紹介されている。実行結果は以下の通りで約7秒(3回繰り返して同程度)。

appendでリストをDataFrameにする際、リストをそのまま渡すと列と解釈されるので、2次元化して行であることを明示している。また列名を指定しないと新たな列として4~6列目に行が加えられていくので、加えるDataFrameでも列名を指定している。

リストをそのまま渡して列として生成し、行インデックスに列名を渡してDataFrameを生成してから'.T'で転置している例なども見られた。

なお、この場合のDataFrameの各要素は整数型となる。

locプロパティーはインデックス指定に注意

DataFramelocプロパティーは、スライスによって複数行・列の要素の参照・代入ができる。これを利用して、空のDataFrameに1行ずつ追加していく。実行時間は7秒台。

この場合のDataFrameの各要素も整数になる。

興味深いのことに、loc[i:, ]ではなくてloc[i]で指定すると実行時間が倍以上、20秒近くになる。

なお、locの代わりにilocを使うと"IndexError: iloc cannot enlarge its target object"とエラーになる。

DataFrameの領域を確保した場合

リストで確保した場合

予めデータのサイズがわかっている場合に、ダミーデータで埋めたリストで領域を確保してみる。領域を一気に確保して値を入れていくだけなので実行速度は速い。実行時間は0.7秒程度で、appendやlocで1行ずつ追加していくのに比べて1/10。

ここでloc[i, :]loc[i]とすると、実行時間は0.5秒程度と少し早くなる。これは1行ずつ追加する場合と逆の傾向だが、この場合はその差は追加の場合に比べて小さい。

なお、この方法では領域が既に確保されているのでilocに変更しても同じ結果となる。

ndarrayで確保した場合

リストではなくndarrayで領域を確保してみると、実行速度はリストの場合と同程度。

ただし、この場合各要素は実数となる。整数が必要ならndarrayのコンストラクターでdtype='int'を指定する。

ここでndarrayのdtypeを整数で指定すると実行時間が以下のような傾向となった。

  • int8, int16→4秒台
  • int32, int64→0.6秒台

ワード境界の中に値を埋め込んでいくのに時間がかかっていると考えられる。

列ごとのリストを加える方法はかなり速い

列ごとの辞書でDataFrameを生成する方法

列ごとのリストを作っておいて、それらから全体のデータを辞書として準備し、DataFrameを生成する方法。

これは更に速く、実行時間は0.015秒前後。loc[i, :]で行ごとに加えていく方法の1/1000の時間で済むことになる。

ただし辞書のキーで列名を指定するところがやや煩雑か。

列単位でリストを加えていく方法

列ごとのリストを、順次DataFrameに加えていく方法。

この場合もかなり速いが、上の方法では実行時間が一定しているのに対して、こちらは0.015~0.03秒と少しばらついて、ほんの僅かだが遅め。

列ごとのndarrayを加える方法

空のndarrayを準備して要素を加えていき、これを列単位でDataFrameに加える方法。

実行時間は0.25秒程度でリストの時の10倍の時間がかかっている。別途ndarrayの要素追加時部分だけの時間を計測すると、この部分だけで0.2秒台で、配列の要素追加のところで時間がかかっている。

2次元リストから生成する方法がベスト

リストなどを列ごとに加えるのではなく、2次元のリストを構成しておいて、それを使ってDataFrameを生成する方法。

実行時間は0.01~0.02秒程度で、最も早い部類に入る。順次行を追加するという発想にコードも近く、速度・可読性ともに最適のようである。

既存のDataFrameに追加する場合

既にデータがあるDataFrameに新たな行を追加する場合を考える。これまでの例で、どうやらリストの形で操作するのが速そうで、DataFrameからリストへの変換がそれなりに速いのなら、その方法が最もよさそうだと予想できる。

以下のコードはこのことを確認したもの。3つのパートに分かれていて、最初がこれまでと同じDataFrameの生成、次がDataFrameからリストへの変換、最後がリストへの追加と追加後のDataFrameの生成となっている。

DataFrameからリストへの変換は、to_numpy()メソッドでndarrayが得られるので(DataFramevaluesでもndarrayは非推奨)、それをtolist()メソッドでリストに変換している。

結果はかなり高速で、DataFrameのままでlocで追加するよりもはるかに速い。

まとめ

今回のケースの場合、1万行の追加でlocを使うと7秒で1行当たり0.0007秒。100行まとめて追加すると0.07秒で、この時点でリストに変換して追加した方が速くなる。

数少ない行を低頻度で追加するのでなければ、DataFrameにまとまった行を追加したり、既にあるDataFrameの構造を変換するには、一旦リストに変換してからデータを追加し、DataFrameに変換し直した方が速いと言える。