SunRichSan
@SunRichSan

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

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/

0

No Answers yet.

Your answer might help someone💌