SunRichSan
@SunRichSan

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

pythonにおけるsklearnでのOneHotencoding(train_test_splitによるX_train,X_valid分割データ)

pythonにおけるsklearnでのOneHotencodingで立ち止まっております。
問題は、X_train,X_validに分割(train_test_split)した後でのデータ変換です。indexがリセットされているようなのです。
それ故、元のデータとの結合(concat)をしても、まともに結合できません。
いろいろなサイトを調べたのですが、分割前のデータ(indexは0から始まっている)を使ってのものばかりでした。
確かに、0から始まるデータに対しては、sklearnのOneHotencodingは、問題なく変換をしてくれ、結合(concat)もできます。

今回、質問させていただくにあたり、
(1)変換後のデータのindexがリセットされているコード・・・小生の現在の問題点です。
(2)indexが0から始まるデータの場合(うまく行く)
(3)category_encordersでのOneHotencodingを使ってうまく行ったコード
を示します。

(1)のsklearnのOneHotencodingでのコードのどこが、間違っているのか、
どこをどう直せば、(3)のように良い結果が得られるのか、よろしくお願いします。
(Windows10 32bit,python 3.8.5 scikit-learn=1.02 (3)category_encorders=2.3.0 )

(1)変換後のデータのindexがリセットされているコード・・・小生の現在の問題点です------
'''
import pandas as pd
import numpy as np
from sklearn import preprocessing
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import train_test_split
import seaborn as sns

データセットを読み込む

df = sns.load_dataset('titanic')
df=df[['survived','pclass','sex','embarked','sibsp','parch']] # データを限定する

X=df.drop('survived',axis=1)
y=df['survived']

データをtrain,validに分割する

X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.2, random_state=0)
print(f"X({len(X)}) = X_train({len(X_train)}) + X_valid({len(X_valid)})")
print(f'\n-----------X_train{X_train.shape}-------------')
display(X_train.head(3))

print(f'\n-----------X_valid{X_valid.shape}-------------')
display(X_valid.head(3))

変換する特徴量の定義

category_selected=['embarked', 'sex']

ohe = OneHotEncoder(sparse=False)
encoded_T = ohe.fit_transform(X_train[category_selected].values)
encoded_V = ohe.transform(X_valid[category_selected].values)

列名を取得

label = ohe.get_feature_names(category_selected)

データフレーム化

encoded_T_df = pd.DataFrame(encoded_T, columns=label, dtype=np.int8)
encoded_V_df = pd.DataFrame(encoded_V, columns=label, dtype=np.int8)

データフレームを結合

print(f'\n-----------after encoding for X_train and convined with original X_train{encoded_T_df.shape}-------------')
display(pd.concat([X_train, encoded_T_df], axis=1).head(3))
print(f'\n-----------after encoding for X_valid and convined with original X_valid{encoded_V_df.shape}-------------')
display(pd.concat([X_valid, encoded_V_df], axis=1).head(3))

out***********************************************************

X(891) = X_train(712) + X_valid(179)

-----------X_train(712, 5)-------------
pclass sex embarked sibsp parch
140 3 female C 0 2
439 2 male S 0 0
817 2 male C 1 1

indexは140から始まっています

-----------X_valid(179, 5)-------------
pclass sex embarked sibsp parch
495 3 male C 0 0
648 3 male S 0 0
278 3 male Q 4 1

indexは495から始まっています

-----------after encoding for X_train and convined with original X_train(712, 6)-------------
pclass sex embarked sibsp parch embarked_C embarked_Q embarked_S embarked_nan sex_female sex_male
0 3.0 male S 1.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0
1 NaN NaN NaN NaN NaN 0.0 0.0 1.0 0.0 0.0 1.0
2 3.0 female S 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0

indexが0から始まっていることが問題だと思います。

-----------after encoding for X_valid and convined with original X_valid(179, 6)-------------
pclass sex embarked sibsp parch embarked_C embarked_Q embarked_S embarked_nan sex_female sex_male
0 NaN NaN NaN NaN NaN 1.0 0.0 0.0 0.0 0.0 1.0
1 1.0 female C 1.0 0.0 0.0 0.0 1.0 0.0 0.0 1.0
2 NaN NaN NaN NaN NaN 0.0 1.0 0.0 0.0 0.0 1.0

indexが0から始まっていることが問題だと思います。

out***********************************************************

'''
(2)indexが0から始まるデータの場合(うまく行く)
'''

分割されたX_trainではなく、元々の X を使う(変数名はX_trainを使う)

X_train=X

print(f'\n-----------X_train{X_train.shape}-------------')
display(X_train.head(3))

変換する特徴量の定義

category_selected=['embarked', 'sex']

ohe = OneHotEncoder(sparse=False)
encoded_T = ohe.fit_transform(X_train[category_selected].values)

列名を取得

label = ohe.get_feature_names(category_selected)

データフレーム化

encoded_T_df = pd.DataFrame(encoded_T, columns=label, dtype=np.int8)

データフレームを結合

print(f'\n-----------after encoding for X_train and convined with original X_train{encoded_T_df.shape}-------------')
display(pd.concat([X_train, encoded_T_df], axis=1).head(3))
'''

out***********************************************************

-----------X_train(891, 5)-------------
pclass sex embarked sibsp parch
0 3 male S 1 0
1 1 female C 1 0
2 3 female S 0 0

-----------after encoding for X_train and convined with original X_train(891, 6)-------------
pclass sex embarked sibsp parch embarked_C embarked_Q embarked_S embarked_nan sex_female sex_male
0 3 male S 1 0 0 0 1 0 0 1
1 1 female C 1 0 1 0 0 0 1 0
2 3 female S 0 0 0 0 1 0 1 0

out***********************************************************

(3)category_encordersでのOneHotencodingを使ってうまく行ったコード
'''
import category_encoders as ce
X=df.drop('survived',axis=1)
y=df['survived']
X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.2, random_state=0)
print(f'\n-----------X_train{X_train.shape}-------------')
display(X_train.head(3))
print(f'\n-----------X_valid{X_valid.shape}-------------')
display(X_valid.head(3))

category_selected=['sex','embarked']
ce_ohe = ce.OneHotEncoder(cols=category_selected, handle_unknown='ignore')
ce_encoded_T = ce_ohe.fit_transform(X_train)
ce_encoded_V = ce_ohe.transform(X_valid)
print(f'\n-----------after ce_OneHotencoding for X_train{ce_encoded_T.shape}-------------')
display(ce_encoded_T.head(3))
print(f'\n-----------after ce_OneHotencoding for X_valid{ce_encoded_V.shape}-------------')
display(ce_encoded_V.head(3))

nan_list=list(X_train[X_train['embarked'].isna()].index)

trainに含まれていた 'embarked'=nan がどのように変換されたのかチェックする

print("\n original data X_train esp. 'embarked'=nan -----------------------")
print(nan_list)
display(X_train.loc[nan_list])
print("\n 'nan' is classified as embarked_4 in encoded data -----------------------")
display(ce_encoded_T.loc[nan_list])
'''

out***********************************************************

-----------X_train(712, 5)-------------
pclass sex embarked sibsp parch
140 3 female C 0 2
439 2 male S 0 0
817 2 male C 1 1

-----------X_valid(179, 5)-------------
pclass sex embarked sibsp parch
495 3 male C 0 0
648 3 male S 0 0
278 3 male Q 4 1

-----------after ce_OneHotencoding for X_train(712, 9)-------------
pclass sex_1 sex_2 embarked_1 embarked_2 embarked_3 embarked_4 sibsp parch
140 3 1 0 1 0 0 0 0 2
439 2 0 1 0 1 0 0 0 0
817 2 0 1 1 0 0 0 1 1

-----------after ce_OneHotencoding for X_valid(179, 9)-------------
pclass sex_1 sex_2 embarked_1 embarked_2 embarked_3 embarked_4 sibsp parch
495 3 0 1 1 0 0 0 0 0
648 3 0 1 0 1 0 0 0 0
278 3 0 1 0 0 1 0 4 1

original data X_train esp. 'embarked'=nan -----------------------
[61, 829]
pclass sex embarked sibsp parch
61 1 female NaN 0 0
829 1 female NaN 0 0

'nan' is classified as embarked_4 in encoded data -----------------------
pclass sex_1 sex_2 embarked_1 embarked_2 embarked_3 embarked_4 sibsp parch
61 1 1 0 0 0 0 1 0 0
829 1 1 0 0 0 0 1 0 0

out***********************************************************

0

2Answer

まず,

元のデータとの結合(concat)をしても、まともに結合できません。

という点で理解に苦しみました.concatは正常に動作しており,ちゃんと結合がなされております.
「まともに結合できる」という状態は,同じデータ列のものが同じデータ列の箇所に結合されることを望んでいるのでしょうか. プログラムは書いた通りに動きますので,まともに結合してほしいと願ってもそうはいきません. まともに結合された状態というものを察することに時間を要しました...

解決案

concatの動作は,同じインデックスラベル同士で結合がなされます.
上記の動作で新しく作った

encoded_T_df = pd.DataFrame(encoded_T, columns=label, dtype=np.int8)
encoded_V_df = pd.DataFrame(encoded_V, columns=label, dtype=np.int8)

のインデックスラベルはどちらも0から順にインデックスラベルが割り当てられますが,

X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.2, random_state=0)

で切り分けたデータのインデックスラベルは元のラベルのままです.
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]と元々は並んでいてもtrain_test_splitでランダムシャッフルされた上でインデックスを保持したままX_trainX_validそれぞぞれに[1, 6, 3, 2, 8, 4, 7, 0][9, 5]に分けられるといった感じです.

したがって,新しく作られて0からインデックスラベルが割り振られたencoded_T_dfencoded_V_dfに対して,元からある分割されたデータX_train, X_validのインデックスラベルが噛み合わずにNaNが挿入されているといった状態です.

対処法としては,分割したX_train, X_validに対して,

X_train = X_train.reset_index(drop=True)
X_valid = X_valid.reset_index(drop=True)

としてからconcatすると,X_trainencoed_T_dfのインデックスラベルが0スタートで一致し解決になるのではないでしょうか.

もしくは,

encoded_T_df = pd.DataFrame(encoded_T, columns=label, index=X_train.index, dtype=np.int8)
encoded_V_df = pd.DataFrame(encoded_V, columns=label, index=X_valid.index, dtype=np.int8)

のようにして最初からインデックスを元々のデータと一緒にしてDataFrameを構成させるのも手です.

余談

コードブロックにコードを書くときは,'(クォート)ではなく`(バッククォート)を3つ並べて書いてください.

(1行の空白)
```言語名:タイトル
コード本文
```

です.また,コードブロックでない場所で#を使うとHTMLで言うh1タグと同じ扱いになります.今回,バッククォートではなくクォートで記述されてしまったので,コードブロックが発動せず,pythonのコメントではなくh1タグになってしまったと見受けられます.

質問される際には,投稿の前後に自分の意図した通りに表示されているかの確認をお願いします.

1Like

Comments

  1. 解決されたとのこと,よかったです.
    質問を「クローズ」にしていただければ終了になります.
    お疲れ様でした.機械学習への道,応援しております.
  2. @SunRichSan

    Questioner

    ありがとうございます。

@PondVillegeさん、ありがとうございます。
理解できました。
transfrom()やfit_transform()メソッドの戻り値はndarrayであるが故に
元々のndex情報は消えている。従って、考え方としては、データフレーム化した後、indexを取り戻さなければならない。

# データフレーム化
encoded_T_df = pd.DataFrame(encoded_T, columns=label, dtype=np.int8)
encoded_V_df = pd.DataFrame(encoded_V, columns=label, dtype=np.int8)

# indexを取り戻す
encoded_T_df.index = X_train.index
encoded_V_df.index = X_valid.index

# データフレームを結合 & 不要なカラムの削除
encoded_T_df=pd.concat([X_train, encoded_T_df], axis=1).drop(category_selected, axis=1)
encoded_V_df=pd.concat([X_valid, encoded_V_df], axis=1).drop(category_selected, axis=1)

とはいうものの、貴兄お勧めの

# データフレーム化(with columns名,index名)
encoded_T_df = pd.DataFrame(encoded_T, columns=label, index=X_train.index, dtype=np.int8)
encoded_V_df = pd.DataFrame(encoded_V, columns=label, index=X_valid.index, dtype=np.int8)

が、ベストであると思います。

sklearnのOneHotEncoderでの活用のパターンと言うことで記憶します。
バッククォート(shift@)も理解しました。ありがとうございます。

1Like

Your answer might help someone💌