kakakakakku blog

Weekly Tech Blog : Keep on Learning 👍

scikit-learn でカテゴリ変数を変換する : OneHotEncoder と LabelEncoder

前回の記事では Pandas の get_dummies() 関数を使って「カテゴリ変数」の変換(One-Hot エンコーディング)を試した.

kakakakakku.hatenablog.com

Pandas 以外の選択肢として scikit-learn の sklearn.preprocessing モジュールを使うこともできる.今回は sklearn.preprocessing モジュールに含まれている OneHotEncoder クラスを試す.さらに関連するラベルエンコーディングとして LabelEncoder クラスも試す.

データセット 🔬

今回の検証も前回と同じく GitHub リポジトリ chendaniely/pandas_for_everyone に含まれているデータセット gapminder.tsv を使う.今回は結果をわかりやすくするために DataFrame を continent に限定しておく.また確認する値を赤と黄色で強調しておいた🖍

import sys
import pandas as pd
from sklearn.preprocessing import LabelEncoder, OneHotEncoder

gapminder = pd.read_csv('./gapminder.tsv', delimiter='\t').loc[:, ['continent']]
gapminder

f:id:kakku22:20210501001144p:plain

OneHotEncoder クラス 🔬

ドキュメントを参考に OneHotEncoder クラスを試す.まず fit() 関数で DataFrame を適用すると categories_ 属性でカテゴリを確認できる.次に transform() 関数で変換すると行列になる.categories_ 属性で確認したカテゴリと一致しているため,今回は Africa と Asia が 1 になっていて「One-Hot エンコーディング」を実現できている.なお,get_feature_names() 関数を使うと Pandas の get_dummies() 関数のような属性名を取得できる.

oh_encoder = OneHotEncoder()
oh_encoder.fit(gapminder)
oh_encoder.categories_
# [array(['Africa', 'Americas', 'Asia', 'Europe', 'Oceania'], dtype=object)]

oh_encoder.transform(gapminder).toarray()
# array([[0., 0., 1., 0., 0.],
#        [0., 0., 1., 0., 0.],
#        [0., 0., 1., 0., 0.],
#        ...,
#        [1., 0., 0., 0., 0.],
#        [1., 0., 0., 0., 0.],
#        [1., 0., 0., 0., 0.]])

oh_encoder.get_feature_names(['continent'])
# array(['continent_Africa', 'continent_Americas', 'continent_Asia', 'continent_Europe', 'continent_Oceania'], dtype=object)

f:id:kakku22:20210501001911p:plain

LabelEncoder クラス 🔬

次は「ラベルエンコーディング」を実現する LabelEncoder クラスを試す.「ラベルエンコーディング」ではエンコードした数値でそのまま変換するため,「One-Hot エンコーディング」とは違って,値ごとにカラムが増えることはなく,値も 0 と 1 以外にも入り得る.

同じように fit() 関数で適用すると classes_ 属性でカテゴリを確認できる.配列の添字から Africa = 0 や Americas = 1 や Asia = 2 であると確認できる.そして transform() 関数で変換すると [2, 2, 2, ..., 0, 0, 0] のような配列が返ってくる.これは Asia と Africa がエンコードされている.

l_encoder = LabelEncoder()
l_encoder.fit(gapminder.continent)
l_encoder.classes_
# array(['Africa', 'Americas', 'Asia', 'Europe', 'Oceania'], dtype=object)

l_encoder.transform(gapminder.continent)
# array([2, 2, 2, ..., 0, 0, 0])

f:id:kakku22:20210501002316p:plain

まとめ 🔬

Pandas の get_dummies() 関数に関連して,今回は scikit-learn の sklearn.preprocessing モジュールを使って OneHotEncoder クラス(One-Hot エンコーディング)と LabelEncoder クラス(ラベルエンコーディング)を試した.

scikit-learn も機能が多くあるため,1歩1歩学んでいくぞー💡

Pandas の get_dummies() 関数でカテゴリ変数をダミー変数に変換する

Pandas で get_dummies() 関数を使うと「カテゴリ変数」を「ダミー変数」に変換できる.「カテゴリ変数」とは,例えば「血液型」や「職業」など,限られた選択肢の中から選んだ値で,ENUM 型のようなイメージをするとわかりやすい💡分析業務を前提に考えると A や B や AB など,文字列のままだと計算しにくく,機械学習などの前処理として数値にエンコーディングする必要がある.そこで get_dummies() 関数を使う.今回は get_dummies() 関数の基本的な操作を試す.

pandas.pydata.org

データセット 🏷

今回の検証では,GitHub リポジトリ chendaniely/pandas_for_everyone に含まれているデータセット gapminder.tsv を使う.これは「国名 / 大陸名 / 平均余命などをまとめたデータセット」となり,1700 行程度含まれている.あくまでサンプルとして使う.GitHub リポジトリ jennybc/gapminder には Gapminder の公式データも載っている.

gapminder = pd.read_csv('./gapminder.tsv', delimiter='\t')
gapminder

f:id:kakku22:20210430210345p:plain

get_dummies() 関数を使う 🏷

get_dummies() 関数の引数に DataFrame を指定する.デフォルトでは「文字列」など dtype が object の全てのカラムを「ダミー変数」に変換する.スクリーンショットには一部しか表示されていないけど country と continent が変換対象となる.具体的には country の値によって,新しいカラム country_Afghanistan や country_Albania が追加されていて,それぞれの値は 0 と 1 になっている.なお,このように「N 個の値」に対して「N 個のカラム」を追加する方法を「One-Hot エンコーディング」と言う.

  • country_Afghanistan
  • country_Albania
  • country_Algeria
  • country_Angola
  • country_Argentina
  • etc
pd.get_dummies(gapminder).head(20)

f:id:kakku22:20210430210633p:plain

columns パラメータを使う 🏷

get_dummies() 関数に columns パラメータを指定すると「ダミー変数」に変換するカラムを指定できる.今回は continent を指定した.すると country はそのままとなり,新しいカラム continent_Africa や continent_Americas が追加された.

  • continent_Africa
  • continent_Americas
  • continent_Asia
  • continent_Europe
  • continent_Oceania
pd.get_dummies(gapminder, columns=['continent']).head(20)

f:id:kakku22:20210430211856p:plain

prefix_sep パラメータを使う 🏷

country_Afghanistan や continent_Africa など,デフォルトでは _ をセパレータとして新しいカラムが追加される.そこで prefix_sep パラメータを指定すると任意のセパレータに変更できる.今回はサンプルとして prefix_sep='/' とした.すると continent/Africa や continent/Americas など,期待通りにセパレータを変更できた.

pd.get_dummies(gapminder, columns=['continent'], prefix_sep='/').head(20)

f:id:kakku22:20210430212324p:plain

dummy_na パラメータを使う 🏷

get_dummies() 関数はデフォルトでは NaN を無視する.そこで dummy_na パラメータに True を指定すると continent/nan のように NaN も値として変換される.

pd.get_dummies(gapminder, columns=['continent'], prefix_sep='/', dummy_na=True).head(20)

f:id:kakku22:20210430212551p:plain

まとめ 🏷

Pandas で「カテゴリ変数」を「ダミー変数」に変換する get_dummies() 関数を試した.値によって新しいカラムを追加する「One-Hot エンコーディング」を簡単に実現できた.試せば試すほど Pandas の機能の豊富さに驚かされる.引き続き学んでいくぞ👍

Pandas で相関件数を計算して Seaborn で可視化する

Pandas で corr() 関数を使うと DataFrame と Series で「相関係数 (correlation coefficient)」を計算できる.今回は DataFrame の corr() 関数と Seaborn を使った可視化を試す.

相関係数 (correlation coefficient) 📊

まず「相関係数」とは「2種類のデータ (x, y) の線形的な関係性を表現する指標」で,値は -1 ~ 1 の範囲となる.大きく3種類あり「正の相関(x が増えると y が増える)」と「負の相関(x が増えると y が減る)」と「無相関」がある.相関係数が 1 に近いほど「正の相関」で,-1 に近いほど「負の相関」になる.例えば「勉強時間 x が長いと学力 y が高い」など💡

データセット 📊

今回の検証では,GitHub リポジトリ chendaniely/pandas_for_everyone に含まれているデータセット housing.csv を使う.これは「住宅情報をまとめたデータセット(コンドミニアム🏢)」となり,今回は以下の「3カラム」に限定して関係性を見ていく.

  • Total.Units : 部屋数
  • Year.Built : 建設年
  • Full.Market.Value : 市場価値
housing = pd.read_csv('./housing.csv').loc[:, ['Total.Units', 'Year.Built', 'Full.Market.Value']]
housing

f:id:kakku22:20210423235309p:plain

よって,今回は以下の組み合わせで「相関係数」を計算する.

  • Total.Units と Year.Built(部屋数と建設年には相関はある?)
  • Total.Units と Full.Market.Value(部屋数と市場価値には相関はある?)
  • Year.Built と Full.Market.Value(建設年と市場価値には相関はある?)

ペアプロット図(散布図行列)📊

まず DataFrame の関係性を「ペアプロット図(散布図行列)」で可視化する.Seaborn の pairplot() 関数を使えば簡単に描画できる.散布図をザッと見ると Total.Units と Full.Market.Value には緩やかな「正の相関」があるように見える.そして Total.Units と Year.Built や Year.Built と Full.Market.Value は「無相関」のように見える.

figure = sns.pairplot(housing, plot_kws = {'alpha': 0.5})
figure.fig.set_figheight(10)
figure.fig.set_figwidth(10)

f:id:kakku22:20210423222703p:plain

なお「散布図」は Pandas の DataFrame.plot.scatter() 関数や plotting.scatter_matrix() 関数を使って描画することもできるけど,今回は「Pandas ライブラリ活用入門」にも載っていた Seaborn を試したくて使った.

相関係数 : corr() 関数 📊

次に Pandas の corr() 関数を使って相関係数を計算する.散布図を見て判断した通り,Total.Units と Full.Market.Value の相関係数は 0.661639 となり,ある程度「正の相関」があると確認できる.

corr = housing.corr()
corr

f:id:kakku22:20210423224136p:plain

相関係数とヒートマップ 📊

最後は corr() 関数を使って計算した相関係数をヒートマップで可視化する.Seaborn の heatmap() 関数を使えば簡単に描画できる.色は設定で変えられるけど,今回の設定だと「正の相関」だと赤くなり「無相関」だと黒くなる.もっとカラム(特徴量)が多くても関係性を見極めることができそう.

sns.heatmap(corr, vmax=1, vmin=-1, center=0)

f:id:kakku22:20210423224233p:plain

まとめ 📊

Pandas の corr() 関数を使って「相関係数 (correlation coefficient)」を計算して,Seaborn の heatmap() 関数を使って可視化した.「相関係数」に関しては,最近並行して読んでいる「Python によるあたらしいデータ分析の教科書」にも載っていた.正確には「ピアソンの積率相関係数」と「スピアマンの順位相関係数」と「ケンドールの順位相関係数」という指標があり,corr() 関数は method パラメータでそれぞれの指標をサポートしていることも確認できた.

df.corr(method='pearson')
df.corr(method='kendall')
df.corr(method='spearman')

Pandas で NaN を操作する : fillna() と interpolate()

先週紹介した学習コンテンツ「Kaggle Courses : Pandas」で「欠損値 (Missing data) : NaN」の取り扱いを学んだけど,その後「Pandas ライブラリ活用入門」を読んでいたら「置換 : fillna() 関数」に多くのパラメータがあり,他にも「補間 : interoperete() 関数」という選択肢も載っていた.本書やドキュメントを読みながら,気になった機能を試していく💪

データセット 📈

今回の検証では,本書と同じく GitHub リポジトリ chendaniely/pandas_for_everyone に含まれているデータセット country_timeseries.csv を使う.「エボラ出血熱の発生数と死者数を国別にまとめたデータセット」となり,以下の通り,NaN を多く含んでいる(赤く強調しておいた🖍).なお,今回は結果を単純化するため以下の「5カラム」に限定して載せる.

  • Date : 日付
  • Cases_Guinea : ギニア(発生数)
  • Cases_Liberia : リベリア共和国(発生数)
  • Deaths_Guinea : ギニア(死者数)
  • Deaths_Liberia : リベリア共和国(死者数)
ebola = pd.read_csv('./country_timeseries.csv')
ebola.loc[:, ['Date','Cases_Guinea', 'Cases_Liberia', 'Deaths_Guinea', 'Deaths_Liberia']].head(10)

f:id:kakku22:20210415152636p:plain

1. 置換 : fillna() 関数 📈

pandas.pydata.org

1-1. fillna(0)

まず,fillna() 関数を使うと簡単に NaN を置換できる.例えば,以下のように fillna(0) と実装すると全て 0.0 になる(NaN 以外は黄色く強調しておいた🖍).

ebola.fillna(0).loc[:, ['Date','Cases_Guinea', 'Cases_Liberia', 'Deaths_Guinea', 'Deaths_Liberia']].head(10)

f:id:kakku22:20210415153231p:plain

1-2. fillna(ebola.mean()) / fillna(ebola.median())

とは言え,実践的に使う場合は 0.0 よりも具体的な値で置換することが多いと思う.例えば「平均値 : mean() 関数」や「中央値 : median() 関数」と組み合わせると,DataFrame から算出した統計値で置換することもできる.

ebola.fillna(ebola.mean()).loc[:, ['Date','Cases_Guinea', 'Cases_Liberia', 'Deaths_Guinea', 'Deaths_Liberia']].head(10)
ebola.fillna(ebola.median()).loc[:, ['Date','Cases_Guinea', 'Cases_Liberia', 'Deaths_Guinea', 'Deaths_Liberia']].head(10)

f:id:kakku22:20210415153336p:plain

1-3. fillna(method='pad') / fillna(method='backfill')

また fillna() 関数に method パラメータを指定すると「前の値で置換 : method='pad' もしくは method='ffill'」したり「後の値で置換 : method='backfill' もしくは method='bfill'」できる.「前の値で置換」をする場合,最初から NaN になっている部分は置換の対象外となり,実際に以下の例でも NaN が残っている.

ebola.fillna(method='pad').loc[:, ['Date','Cases_Guinea', 'Cases_Liberia', 'Deaths_Guinea', 'Deaths_Liberia']].head(10)
ebola.fillna(method='backfill').loc[:, ['Date','Cases_Guinea', 'Cases_Liberia', 'Deaths_Guinea', 'Deaths_Liberia']].head(10)

f:id:kakku22:20210415152803p:plain

2. 補間 : interoperete() 関数 📈

Pandas では,特定の値で「置換」するだけではなく,DataFrame から近似した値で「補間」する方法もあり interoperete() 関数を使う.

pandas.pydata.org

2-1. interpolate()

まず,デフォルト設定のまま interoperete() 関数を使うと以下のようになる.カラムの値ごとに「線形的に」補間する.NaN 前後の値で補間をするため,最初から NaN になっている部分は補間の対象外となり,実際に以下の例でも NaN が残っている.

ebola.interpolate().loc[:, ['Date','Cases_Guinea', 'Cases_Liberia', 'Deaths_Guinea', 'Deaths_Liberia']].head(10)

f:id:kakku22:20210415154042p:plain

2-2. interpolate(limit_direction='both')

次に interpolate() 関数で limit_direction パラメータを指定すると,補間する方向として forward と backward と both を指定できる.例えば limit_direction='both' のように指定すると,どちらの方向もサポートすることになり,全ての NaN を補間できるようになる.

ebola.interpolate(limit_direction='both').loc[:, ['Date','Cases_Guinea', 'Cases_Liberia', 'Deaths_Guinea', 'Deaths_Liberia']].head(10)

f:id:kakku22:20210415153108p:plain

2-3. interpolate(limit_direction='both')

最後は method パラメータで,デフォルトでは「線形(カラム値を等間隔に補間): linear」となる.選択肢は他にもあり,例えば「インデックス(インデックス値をベースに補間): index」など.もし index を指定する場合はインデックス値が数値型である必要がある.

ebola.interpolate(method='index').loc[:, ['Date','Cases_Guinea', 'Cases_Liberia', 'Deaths_Guinea', 'Deaths_Liberia']].head(10)

f:id:kakku22:20210415154322p:plain

まとめ 📈

引き続き Pandas を学んでいる.今回は「Pandas ライブラリ活用入門」を読みながら fillna() 関数と interoperete() 関数の気になった機能を試した💪パラメータは他にも多くあるため,ドキュメントも読みながらより理解を深めていく💡

Pandas 関連記事 📈

kakakakakku.hatenablog.com

Jupyter Notebook と Pandas で DataFrame を全て表示するオプション設定

Jupyter Notebook で Pandas のコードを実装しているときに同じような表示関連設定を繰り返し使うため,メモも兼ねてまとめておく.オプションは他にも多くあり,詳細はドキュメントに載っている.今回は Python 3.9 と Pandas 1.2.4 を前提とする.

pandas.pydata.org

オプション一覧を取得する 🎯

まず,Pandas では options でオプション一覧(名前空間)を取得できる.例えば display など.また options.display でオプション一覧(display 名前空間)を取得できる.例えば chop_threshold や colheader_justify など多くある.

dir(pd.options)
# ['compute', 'display', 'io', 'mode', 'plotting']

dir(pd.options.display)
# ['chop_threshold',
#  'colheader_justify',
#  'column_space',
#  'date_dayfirst',
#  'date_yearfirst',
#  'encoding',
#  'expand_frame_repr',
#  'float_format',
#  'html',
#  'large_repr',
#  'latex',
#  'max_categories',
#  'max_columns',
#  'max_colwidth',
#  'max_info_columns',
#  'max_info_rows',
#  'max_rows',
#  'max_seq_items',
#  'memory_usage',
#  'min_rows',
#  'multi_sparse',
#  'notebook_repr_html',
#  'pprint_nest_depth',
#  'precision',
#  'show_dimensions',
#  'unicode',
#  'width']

次に get_option() 関数を使うと,オプション値を取得できる.以下では「表示する最低行数 display.min_rows」と「表示する最大行数 display.max_rows」と「表示する最大カラム数 display.max_columns」のオプション値をサンプルとして取得している.なお,今回はデフォルト値として取得している.

pd.get_option('display.min_rows')
# 10
pd.get_option('display.max_rows')
# 60
pd.get_option('display.max_columns')
# 20

また describe_option() 関数を使うと,オプションごとに詳細な説明文やデフォルト値を確認できる.便利!

pd.describe_option('display.max_rows')
# display.max_rows : int
#     If max_rows is exceeded, switch to truncate view. Depending on
#     `large_repr`, objects are either centrally truncated or printed as
#     a summary view. 'None' value means unlimited.
#
#     In case python/IPython is running in a terminal and `large_repr`
#     equals 'truncate' this can be set to 0 and pandas will auto-detect
#     the height of the terminal and print a truncated object which fits
#     the screen height. The IPython notebook, IPython qtconsole, or
#     IDLE do not run in a terminal and hence it is not possible to do
#     correct auto-detection.
#     [default: 60] [currently: 60]

DataFrame を全て表示する 🎯

Pandas で DataFrame を表示すると,以下のようにデフォルトでは省略される.表示する行数に関連するオプションは display.min_rows と display.max_rows で,データセットの行数が display.max_rows を超える場合は display.min_rows を表示するため,今回は「10行」表示されている.なお,今回は GitHub リポジトリ chendaniely/pandas_for_everyone に含まれているデータセット wine.csv を使う.

wine = pd.read_csv('./wine.csv')
wine

f:id:kakku22:20210414225022p:plain

そこで set_option() 関数を使って,オプション値を上書きする.display.max_rows と display.max_columns に None を設定すると「上限なし」となり,DataFrame を省略せずに全て表示できるようになる(以下は 20 行目まで載せている).

pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
wine

f:id:kakku22:20210414225948p:plain

まとめ 🎯

Jupyter Notebook で Pandas のコードを実装しているときに DataFrame を全て表示するオプションを繰り返し使っていたため,メモも兼ねてまとめておくことにした.引き続き Pandas を試すぞー👌

pandas.pydata.org