TitanicでのAgeの欠損値の補填の為の自作変換器について
1.解決したいこと
TitanicでのAgeの欠損値の補填の為の変換器を作ってみました。
とにかくエラーが出ないように試行錯誤を重ねて、現在に至っております。
動作はしているようですが、selfの使い方、変数の使い方等、よく理解はできていません。
正しいコードのあり方、および正しい理解をサジェスチョンしていただけるとありがたいです。
def _init_(self): --- 形式的に書きました。しかし、うまく動作しているようです。
def fit(self, X)での return self ---- self だけでよい理由が理解できていません。
def fit(self, X):、def transform(self, X):の中だけで使う変数(i、tempList、等)、外へ放り出す変数という観点でのとらえ方。
クラス変数(クラス直下で定義)、インスタンス変数(def _init_(self)で定義)、この観点で考えると、下で書いているLookupTable1、LookupTable3 は、インスタンス変数として定義しなければいけないはず・・・・
でも、現状、インスタンス変数として定義しなくても、単に、頭にselfを付ければ、transformでは使えています。
2.大まかなコードの構成
1.Titanicのデータに'Title'の列を新設する(Ageは、Title、その他により決まると考える)
2.ルックアップテーブル(Title,その他-->Age)を用意する。3種類
LookupTable1=データ.groupby(['Embarked','Pclass','Title','SibSp','Parch'])['Age'].mean()
LookupTable2=データ.groupby(['Embarked','Pclass','Title'])['Age'].mean()
LookupTable3=データ.groupby(['Title'])['Age'].mean()
Embarked--乗船地、Pclass--客室クラス、Title、SibSp--家族構成情報(兄弟、配偶者の数)、Parch--家族構成情報(親、子供の数)
Titleについては、https://chart-studio.plotly.com/~maks-sh/262.embed を参考としています
LookupTable1は、細かくグルーピングをしているので、NaNが存在しうる。そのNaNを、LookupTable2を使って埋める(fitのコード内で)。
fit、fit_transformのレベルでは、自分自身(training data)ですのでこれで足りる。しかし、Test dataに対しては足りないので、
LookupTable3を用意する(fitのコード内で用意して、transformのコード内で使う)。
3.fitのコード内で作成したルックアップテーブルをtransformコード内で使えるようにするために selfを頭につける
self.LookupTable1=LookupTable1
self.LookupTable3=LookupTable3
(LookupTable2は、fitのコード内のみで使い、外には出さないのでselfは不要)
注:Titanic train.csv,test.csvはkaggleからダウンロードしてきております。データの中身は、下記のdumpを参照ください。
3.データのインポート
import pandas as pd
import re
train = pd.read_csv("train.csv")
display(train.head(3).style)
myTrain=train.copy()
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.000000 1 0 A/5 21171 7.250000 nan S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Thayer) female 38.000000 1 0 PC 17599 71.283300 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.000000 0 0 STON/O2. 3101282 7.925000 nan S
test = pd.read_csv("test.csv")
display(test.head(3).style)
myTest=test.copy()
PassengerId Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 892 3 Kelly, Mr. James male 34.500000 0 0 330911 7.829200 nan Q
1 893 3 Wilkes, Mrs. James (Ellen Needs) female 47.000000 1 0 363272 7.000000 nan S
2 894 2 Myles, Mr. Thomas Francis male 62.000000 0 0 240276 9.687500 nan Q
myTrain.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 891 non-null int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null object
5 Age 714 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
myTest.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 418 non-null int64
1 Pclass 418 non-null int64
2 Name 418 non-null object
3 Sex 418 non-null object
4 Age 332 non-null float64
5 SibSp 418 non-null int64
6 Parch 418 non-null int64
7 Ticket 418 non-null object
8 Fare 417 non-null float64
9 Cabin 91 non-null object
10 Embarked 418 non-null object
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB
4.欠損値の確認(train)
missinglistTr=myTrain[myTrain['Age'].isnull()].index
myTrain.loc[missinglistTr].head(3).style
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
5 6 0 3 Moran, Mr. James male nan 0 0 330877 8.458300 nan Q
17 18 1 2 Williams, Mr. Charles Eugene male nan 0 0 244373 13.000000 nan S
19 20 1 3 Masselmani, Mrs. Fatima female nan 0 0 2649 7.225000 nan C
5.欠損値の確認(test)
missinglistTe=myTest[myTest['Age'].isnull()].index
myTest.loc[missinglistTe].head(3).style
PassengerId Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
10 902 3 Ilieff, Mr. Ylio male nan 0 0 349220 7.895800 nan S
22 914 1 Flegenheim, Mrs. Alfred (Antoinette) female nan 0 0 PC 17598 31.683300 nan S
29 921 3 Samaan, Mr. Elias male nan 2 0 2662 21.679200 nan C
6.自作変換器、クラスの定義
from sklearn.base import BaseEstimator, TransformerMixin
class CompensationOfAge(BaseEstimator, TransformerMixin):
def __init__(self):
pass
def fit(self, X):
X2=X.copy()
X2['Title']=X2['Name'] # Nameの列をそのままコピーして 列Titleとし、正規表現を使って Titleを代入
p = re.compile(r'\w+\,\s*\w*\s([CDJLMRS].+?)\.\s.+') # 最短一致 r'\w+\,\s*\w*\s([CDJLMRS].+?)\.\s.+'
# CDJLMRS ---- Capt. Col. Countess. Don. Dr. Jonkheer. Lady. Major. Master. Miss. Mlle. Mme. Mr. Mrs. Ms. Rev. Sir.
# https://chart-studio.plotly.com/~maks-sh/262.embed を参考としています
for i in list(X2.index):
myNameData=X2.loc[i,'Name'] # ex.Braund, Mr. Owen Harris
myTitle0=re.search(p, myNameData)
myTitle=myTitle0.group(1) # ex.Mr
X2.loc[i,'Title']=myTitle
LookupTable1=X2.groupby(['Embarked','Pclass','Title','SibSp','Parch'])['Age'].mean()
LookupTable2=X2.groupby(['Embarked','Pclass','Title'])['Age'].mean()
tempList=LookupTable1[LookupTable1.isna()].index # LookupTable1の欠損値を知る
for i in list(tempList):
LookupTable1[i]=LookupTable2.loc[i[0],i[1],i[2]] # LookupTable1の欠損値をLookupTable2の値で埋める
self.LookupTable1=LookupTable1 # selfをつけることによりfitの外へ吐き出す
self.LookupTable3=X2.groupby(['Title'])['Age'].mean() # selfをつけることによりfitの外へ吐き出す
return self # selfは、多分、BaseEstimator、TransformerMixinからの要請
def transform(self, X):
X2=X.copy()
X2['Title']=X2['Name'] # Nameの列をそのままコピーして 列Titleとし、正規表現を使って Titleを代入
p = re.compile(r'\w+\,\s*\w*\s([CDJLMRS].+?)\.\s.+') # 最短一致 r'\w+\,\s*\w*\s([CDJLMRS].+?)\.\s.+'
# CDJLMRS ---- Capt. Col. Countess. Don. Dr. Jonkheer. Lady. Major. Master. Miss. Mlle. Mme. Mr. Mrs. Ms. Rev. Sir.
for i in list(X2.index):
myNameData=X2.loc[i,'Name']
myTitle0=re.search(p, myNameData)
myTitle=myTitle0.group(1)
X2.loc[i,'Title']=myTitle
tempList=X2[X2["Age"].isnull()].index
for i in list(tempList):
myEmbarked=X2.loc[i,'Embarked']
myPclass=X2.loc[i,'Pclass'] # X2(例えばtest data)での欠損値を知って
myTicket=X2.loc[i,'Ticket'] # 大半は LookupTable1で補完できる
mySibSp=X2.loc[i,'SibSp'] # しかし、LookupTable1にないケースもある
myParch=X2.loc[i,'Parch'] # この時、pythonはエラーをだす。
myTitle=X2.loc[i,'Title'] # try,exceptを使ってLookupTable3を使って補完する
try:
tempvalue=self.LookupTable1.loc[(myEmbarked,myPclass,myTitle,mySibSp,myParch)]
except:
tempvalue=self.LookupTable3.loc[myTitle]
X2.loc[i,'Age']=tempvalue # Ageの欠損値はすべて埋まる
X2['Age']=X2['Age'].astype('int64') # NaNがなくなっているので、型をintに変更する
return X2
7.自作変換器の実行
a=CompensationOfAge()
myTrain2=a.fit_transform(myTrain)
myTest2=a.transform(myTest)
8.欠損値が埋まったことの確認(train) --- 4での実行結果と比較
display(myTrain2.loc[missinglistTr].head(3).style)
print(f"Ageの型は {myTrain2['Age'].dtype}")
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked Title
5 6 0 3 Moran, Mr. James male 37 0 0 330877 8.458300 nan Q Mr
17 18 1 2 Williams, Mr. Charles Eugene male 33 0 0 244373 13.000000 nan S Mr
19 20 1 3 Masselmani, Mrs. Fatima female 28 0 0 2649 7.225000 nan C Mrs
Ageの型は int64
9.欠損値が埋まったことの確認(test) --- 5での実行結果と比較
display(myTest2.loc[missinglistTe].head(3).style)
print(f"Ageの型は {myTest2['Age'].dtype}")
PassengerId Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked Title
10 902 3 Ilieff, Mr. Ylio male 28 0 0 349220 7.895800 nan S Mr
22 914 1 Flegenheim, Mrs. Alfred (Antoinette) female 48 0 0 PC 17598 31.683300 nan S Mrs
29 921 3 Samaan, Mr. Elias male 26 2 0 2662 21.679200 nan C Mr
Ageの型は int64
10.環境の情報
PC--i7-11700K
OS--Windows 10 Home 21H2 (64bit)
anaconda3(64bit)上のnotebookで作業をしています。
import sys
print(sys.version)
print(pd.__version__)
print(re.__version__)
3.9.12 (main, Apr 4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)]
1.4.2
2.2.1
11.その他、色々なトライの検証の方法
fit、transform内のコードを色々変えて、試行錯誤を繰り返し、毎回、それ以前との比較をしています。
使ったコマンドは、下記です。
myTrain1.equals(myTrain3)
myTest1.equals(myTest3)
12.依頼事項
動作はしているようですが、selfの使い方、変数の使い方等、よく理解はできていません。
正しいコードのあり方、および正しい理解をサジェスチョンしていただけるとありがたいです。
def _init_(self): --- 形式的に書きました。しかし、うまく動作しているようです。
def fit(self, X)での return self ---- self だけでよい理由が理解できていません。
def fit(self, X):、def transform(self, X):の中だけで使う変数(i、tempList、等)、外へ放り出す変数という観点でのとらえ方。
クラス変数(クラス直下で定義)、インスタンス変数(def _init_(self)内での定義)、この観点で考えると、LookupTable1、LookupTable3 は、インスタンス変数として定義しなければいけないはず・・・・
でも、現状、インスタンス変数として定義しなくても、単に、頭にselfを付ければ、transformでは使えています。
動作はしているかもしれませんが、理屈が理解できていません。
正しいコードのあり方、および正しい理解をサジェスチョンしていただけるとありがたいです。
よろしくお願いいたします。
尚、ここにたどり着くまでに、下記のサイトを参考にさせていただいております。
【scikit-learn】自作変換器にTransformerMixinを継承してfit_transformメソッドを使えるようにする
https://zerofromlight.com/blogs/detail/73/
scikit-learnのAPIとPipelineの基本的な仕組みと使い方について
https://dodotechno.com/scikit-learn-pipline/