SpaceShip Titanicに挑戦()0.77998

# KaggleのSpaceShip Titanicに挑戦した話
まとめにプログラムの全容あり。
モデルは

# 使用環境
- GoogleColabratory
- Python

# 相関関係の出力
- csvファイルでTransportedのターゲットカラムと他のカラムの相関係数を書き出します
- 数値データでないものはダミーデータを代入し、欠損値には平均値を代入します
- 相関係数の計算の前に正規化します
- 値の自由度の高い"Name"カラムなどは除外します
- 関係のありそうな"Cabin"カラムは先頭の英語文字のみ抽出します
- 'PassengerId'については先頭4字と最後2字に分割し、それぞれターゲットとの関連度を生成します
- 行き先を書いている"Destination"カラムは何種類あるのか。(量が5以下ならダミーで置き換えるのありだな。)

ということで、初めに
"Destination"カラムに何種類の値が含まれているのかを出力する
```test.py
import pandas as pd

df = pd.read_csv('train.csv')
destination_count = df['Destination'].nunique()
print(destination_count)
```
```
3
```

よしダミーで置き換えます。
相関係数をcsvファイルで書き出します。

```test.py
import pandas as pd

df = pd.read_csv('train.csv')

# PassengerIdを最初の4文字と最後の2文字に分割する(数値として取り込む)
passenger_id_first4 = df['PassengerId'].str[:4].astype(int)
passenger_id_last2 = df['PassengerId'].str[-2:].astype(int)

# 特定のカラムを定義
target_column = 'Cabin'

# 特定のカラムの値が文字列の先頭の文字のみに変換する関数
def first_character_only(value):
    if isinstance(value, str) and value:  # 文字列であり、かつ空でない場合のみ変換
        return value[0]
    else:
        return value

# 特定のカラムの値に関数を適用する
df[target_column] = df[target_column].apply(first_character_only)

# 除外するカラムを定義
excluded_columns = ['PassengerId', 'Name']

# 除外するカラムを含まないデータフレームを作成
df_encoded = pd.get_dummies(df.drop(excluded_columns, axis=1))

# PassengerIdの分割結果を再度結合する
df_encoded['PassengerId_first4'] = passenger_id_first4
df_encoded['PassengerId_last2'] = passenger_id_last2

# 欠損値を平均値で補完する。
missing_values = df_encoded.columns[df_encoded.isnull().any()]
for column in missing_values:
    mean_value = df_encoded[column].mean()
    df_encoded[column].fillna(mean_value, inplace=True)

# 相関係数の計算のために正規化を行う
df_normalized = (df_encoded - df_encoded.mean()) / df_encoded.std()

# 相関係数の計算
correlation = df_normalized.corr()

# 何との相関係数を計算したいのかを定義
features = ['Transported']

# 取り出したカラムがfeaturesの中身と一致しなければ代入
other_features = [col for col in correlation.columns if col not in features]

# featuresとother_featuresの間の相関係数を計算
correlation_df = correlation.loc[other_features, features]

# 相関係数をCSVファイルで書き出す。
correlation_df.to_csv('ST_correlation.csv')
```
結果
![スクリーンショット 2024-03-27 23.20.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326449/7ed228ef-cbf3-c92d-5f11-ee5bfe62950d.png)


相関係数の表から
- "Cabin"カラムはB,Cで比較的高いが他が低いため総合しては相関は低そう(もし上位6個のカラムで不十分なら学習に組み込むかも...)
- "Passengerld"カラムは思ったより相関係数は高くなかった
- "CryoSleep"カラムというよく分からないカラムの値は相関が高い
- "Destination"カラムは全然ですね。一つに限っては1万分の1って...

よって関連度の高い"CryoSleep"カラムの中身はTrue, False, Nanがどの程度の割合で含まれているのか棒グラフで出力する。

```test.py
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('train.csv')

# 出現頻度を調べたいカラムを指定(複数できるようにリスト)
target_columns = ['CryoSleep']

# 各指定されたカラムごとに棒グラフを作成
for column in target_columns:
    target_df = df[[column]]

    # カラムごとにTrue、False、NaN（欠損値）の出現頻度を計算
    value_counts = target_df[column].value_counts(dropna=False)
    plt.figure(figsize=(8, 6))
    value_counts.plot(kind='bar', color=['blue', 'orange', 'gray'], alpha=0.7)

    # グラフに具体的な数値を表示
    for i, v in enumerate(value_counts):
        plt.text(i, v, str(v), ha='center', va='bottom')

    plt.title(f'Value Counts of {column}')
    plt.xlabel('Value')
    plt.ylabel('Count')
    plt.xticks(rotation=0)
    plt.show()
```
結果
![スクリーンショット 2024-03-27 21.55.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326449/47c4f57e-9bff-5215-3372-86038217378e.png)

Falseの値が多いのでNanはFalseで置換して使用します。
TrueとFalseでカラムを分けることにします。
# 学習
```SST_train.py
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import MinMaxScaler

df = pd.read_csv('train.csv')

df['CryoSleep'].fillna(False, inplace=True)

columns_to_fill_mean = ['RoomService', 'Spa', 'VRDeck']
for column in columns_to_fill_mean:
    mean_value = df[column].mean()
    df[column].fillna(mean_value, inplace=True)

df['CryoSleep_True'] = df['CryoSleep'].apply(lambda x: 1 if x else 0)
df['CryoSleep_False'] = df['CryoSleep'].apply(lambda x: 0 if x else 1)

scaler_features = ['CryoSleep_False', 'CryoSleep_True', 'RoomService', 'Spa', 'VRDeck']
scaler = MinMaxScaler()
for features_to_normalize in scaler_features:
  df[features_to_normalize] = scaler.fit_transform(df[[features_to_normalize]])

y = df.Transported
features = ['CryoSleep_False', 'CryoSleep_True', 'RoomService', 'Spa', 'VRDeck']
X = df[features]

train_x, test_x, train_y, test_y = train_test_split(X, y, random_state=1)

model = LogisticRegression(C=1, solver='liblinear', max_iter=100, random_state=1)

model.fit(train_x, train_y)

y_pred = model.predict(test_x)

accuracy = accuracy_score(test_y, y_pred)
print("Accuracy:", accuracy)
```
カラムが`'CryoSleep_False', 'CryoSleep_True', 'RoomService', 'Spa', 'VRDeck'`であろうと`'CryoSleep_False', 'CryoSleep_True'`であろうと、ハイパーパラメータを変更しようと正答率は
```
正答率: 0.7161913523459061
```
でした。このプログラムに下記のプログラムを最後に追加することで、特徴量の重要度が出力されます。絶対値が大きいほど、学習時に重視した特徴量になります。


`'CryoSleep_False', 'CryoSleep_True', 'RoomService', 'Spa', 'VRDeck'`を特徴量にした場合
|カラム名 |重要度 |
|--- |--- |
CryoSleep_False| -0.7158806487643995
CryoSleep_True| 1.096169349014709
RoomService| -7.035419986709575
Spa| -7.247748061420495
VRDeck| -6.230924499854833

`'CryoSleep_False', 'CryoSleep_True'`を特徴量にした場合
|カラム名 |重要度 |
|--- |--- |
CryoSleep_False| -0.9556691032284553
CryoSleep_True| 1.2128313157790795

`'RoomService', 'Spa', 'VRDeck'`を特徴量にした場合(正答率: 0.7263109475620975)
|カラム名 |重要度 |
|--- |--- |
RoomService| -11.422903561662215
Spa| -10.762279866044832
VRDeck| -9.627964907026872

ここで正答率の向上が見られました。そのため、今回の学習では、`'CryoSleep_False', 'CryoSleep_True'`カラムの相関関係が高かったことにより過剰にその値にfitしていた可能性があります。そのため相関の低いものを使用したことで、モデルの表現の自由度が増加し、より良い結果が得られた可能性があります。

- `'RoomService', 'Spa', 'VRDeck'`にカラムを追加しより良いモデルが存在するのか確かめます
- ハイパーパラメータのCを変更することで正答率に変化が見られます。最大値を探します。最大値で予測を出し過学習の評価を行います(アルゴリズの変化では向上が見られませんでした。なので`lbfgs`を使用します)

```SST_train.py
import numpy as np
import matplotlib.pyplot as plt

C_values = np.linspace(1, 100, 100)

accuracies = []
max_accuracy = 0
best_C = 0

for C in C_values:
    model = LogisticRegression(C=C, solver='liblinear', max_iter=100, random_state=1)
    model.fit(train_x, train_y)
    y_pred = model.predict(test_x)
    accuracy = accuracy_score(test_y, y_pred)
    accuracies.append(accuracy)
    if accuracy > max_accuracy:
        max_accuracy = accuracy
        best_C = C

plt.plot(C_values, accuracies)
plt.title('Accuracy vs C')
plt.xlabel('C')
plt.ylabel('Accuracy')
plt.grid(True)
plt.show()

print("Maximum Accuracy:", max_accuracy)
print("Best C:", best_C)
```
を`train_x, test_x, train_y, test_y = train_test_split(X, y, random_state=1)`の後に追加することでCの変化を出力できるようになります。
結果
![スクリーンショット 2024-03-28 10.26.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326449/ffdf2266-eb0f-6d4e-3d84-a12894d3cf8f.png)

```
Maximum Accuracy: 0.7667893284268629
Best C:           23.0
```
のようです。
このCを使用して、`max_iter`を変更するようにします。
```SST_train.py
import matplotlib.pyplot as plt

max_iter_values = range(100, 201)

accuracies = []
max_accuracy = 0
best_max_iter = 0

for max_iter in max_iter_values:
    model = LogisticRegression(C=1, solver='lbfgs', max_iter=max_iter, random_state=1)
    model.fit(train_x, train_y)
    y_pred = model.predict(test_x)
    accuracy = accuracy_score(test_y, y_pred)
    accuracies.append(accuracy)
    if accuracy > max_accuracy:
        max_accuracy = accuracy
        best_max_iter = max_iter

plt.plot(max_iter_values, accuracies)
plt.title('Accuracy vs max_iter')
plt.xlabel('max_iter')
plt.ylabel('Accuracy')
plt.grid(True)
plt.show()

print("Maximum Accuracy:", max_accuracy)
print("Best max_iter:", best_max_iter)
```

結果
![スクリーンショット 2024-03-28 10.33.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326449/c09b5dd4-f5c2-fc63-cce4-9065a9851f4e.png)

```
Maximum Accuracy: 0.7263109475620975
Best max_iter:    100
```
変化しませんでした。なのでCのみを23に変更し予測を行います。

# 予測
```SST_expect.py
import pandas as pd
from sklearn.preprocessing import MinMaxScaler

df_train = pd.read_csv('train.csv')
df_test = pd.read_csv('test.csv')

df_train['CryoSleep'].fillna(False, inplace=True)
df_test['CryoSleep'].fillna(False, inplace=True)

columns_to_fill_mean = ['RoomService', 'Spa', 'VRDeck']
for column in columns_to_fill_mean:
    mean_value_train = df_train[column].mean()
    mean_value_test = df_test[column].mean()
    df_train[column].fillna(mean_value_train, inplace=True)
    df_test[column].fillna(mean_value_test, inplace=True)

df_train['CryoSleep_True'] = df_train['CryoSleep'].apply(lambda x: 1 if x else 0)
df_train['CryoSleep_False'] = df_train['CryoSleep'].apply(lambda x: 0 if x else 1)
df_test['CryoSleep_True'] = df_test['CryoSleep'].apply(lambda x: 1 if x else 0)
df_test['CryoSleep_False'] = df_test['CryoSleep'].apply(lambda x: 0 if x else 1)

scaler_features = ['RoomService', 'Spa', 'VRDeck']
scaler = MinMaxScaler()
for features_to_normalize in scaler_features:
    df_train[features_to_normalize] = scaler.fit_transform(df_train[[features_to_normalize]])
    df_test[features_to_normalize] = scaler.transform(df_test[[features_to_normalize]])

y_train = df_train['Transported']
features = ['RoomService', 'Spa', 'VRDeck']
X_train = df_train[features]
X_test = df_test[features]

model = LogisticRegression(C=10, solver='lbfgs', max_iter=100, random_state=1)
model.fit(X_train, y_train)

y_pred_test = model.predict(X_test)

result_df = pd.DataFrame({'PassengerId': df_test['PassengerId'], 'Transported': y_pred_test})

result_df.to_csv('predictions.csv', index=False)
```
```
スコア(C=23): 0.76432
スコア(C=10): 0.76221
```
```
trainでの正答率(C=23): 0.7667893284268629
trainでの正答率(C=10): 0.7612695492180312
```
かなり適当ですが、過学習は起きていないはず...1日に5回までしかファイルを投稿できないので完璧に調べる事はできませんが、trainの予測値の正答率変化とそこまで差がないことなどから科学種の可能性は低いのではという読みです。評価方法が正しいのかは分かりません。

再度、特徴量カラムを変更し最大を見つけます。その結果はまとめに記載します。
`Cabin`カラムの値がどのようになっているかを出力します。(先頭の英文字のみで分類しています。)
![スクリーンショット 2024-03-28 14.12.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3326449/c9a37228-0918-a83c-3735-a7394c64b1da.png)
# まとめ
結果`C`の最適化は行えなかったが、下記の`SST_expect.py`で作成した予想が最も精度が良かった。
ここには、プログラムの全容を載せる。
## テスト用
```SST_train.py
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import MinMaxScaler

df = pd.read_csv('train.csv')

df['HomePlanet'].fillna('Earth', inplace=True)
df = pd.concat([df, pd.get_dummies(df['HomePlanet'], prefix='HomePlanet')], axis=1)

df.drop('HomePlanet', axis=1, inplace=True)

columns_to_fill_mean = ['RoomService', 'Spa', 'VRDeck']
for column in columns_to_fill_mean:
    mean_value = df[column].mean()
    df[column].fillna(mean_value, inplace=True)

scaler_features = ['RoomService', 'Spa', 'VRDeck']
scaler = MinMaxScaler()
for features_to_normalize in scaler_features:
  df[features_to_normalize] = scaler.fit_transform(df[[features_to_normalize]])

y = df.Transported
features = ['RoomService', 'Spa', 'VRDeck', 'HomePlanet_Earth', 'HomePlanet_Europa']
X = df[features]

train_x, test_x, train_y, test_y = train_test_split(X, y, random_state=1)

model = LogisticRegression(C=10, solver='lbfgs', max_iter=100, random_state=1)

model.fit(train_x, train_y)

y_pred = model.predict(test_x)

accuracy = accuracy_score(test_y, y_pred)
print("Accuracy:", accuracy)
```

## 予測用
`HomePlanet_Mars`は重要度&相関係数が他の二つと比べ小さいので外す。追加しても制度に影響は出ないので、無駄なものは削除
```SST_expect.py
import pandas as pd
from sklearn.preprocessing import MinMaxScaler

df_train = pd.read_csv('train.csv')
df_test = pd.read_csv('test.csv')

df_train['HomePlanet'].fillna('Earth', inplace=True)
df_train = pd.concat([df_train, pd.get_dummies(df_train['HomePlanet'], prefix='HomePlanet')], axis=1)

df_test['HomePlanet'].fillna('Earth', inplace=True)
df_test = pd.concat([df_test, pd.get_dummies(df_test['HomePlanet'], prefix='HomePlanet')], axis=1)

df_train.drop('HomePlanet', axis=1, inplace=True)
df_test.drop('HomePlanet', axis=1, inplace=True)

columns_to_fill_mean = ['RoomService', 'Spa', 'VRDeck']
for column in columns_to_fill_mean:
    mean_value_train = df_train[column].mean()
    mean_value_test = df_test[column].mean()
    df_train[column].fillna(mean_value_train, inplace=True)
    df_test[column].fillna(mean_value_test, inplace=True)

scaler_features = ['RoomService', 'Spa', 'VRDeck']
scaler = MinMaxScaler()
for features_to_normalize in scaler_features:
    df_train[features_to_normalize] = scaler.fit_transform(df_train[[features_to_normalize]])
    df_test[features_to_normalize] = scaler.transform(df_test[[features_to_normalize]])

y_train = df_train['Transported']
features = ['RoomService', 'Spa', 'VRDeck', 'HomePlanet_Earth', 'HomePlanet_Europa']
X_train = df_train[features]
X_test = df_test[features]

model = LogisticRegression(C=10, solver='lbfgs', max_iter=100, random_state=1)
model.fit(X_train, y_train)

y_pred_test = model.predict(X_test)

result_df = pd.DataFrame({'PassengerId': df_test['PassengerId'], 'Transported': y_pred_test})

result_df.to_csv('predictions.csv', index=False)
```

## 他に使用したプログラム
### 相関係数の出力
```SST_test.py
# ターゲットとその他のカラムとの相関係数の出力
import pandas as pd

df = pd.read_csv('train.csv')

# PassengerIdを最初の4文字と最後の2文字に分割する(数値として取り込む)
passenger_id_first4 = df['PassengerId'].str[:4].astype(int) # ここも変更
passenger_id_last2 = df['PassengerId'].str[-2:].astype(int) # ここも変更

# 特定のカラムを定義
target_column = 'ここを変更'

# 特定のカラムの値が文字列の先頭の文字のみに変換する関数
def first_character_only(value):
    if isinstance(value, str) and value:  # 文字列であり、かつ空でない場合のみ変換
        return value[0]
    else:
        return value

# 特定のカラムの値に関数を適用する
df[target_column] = df[target_column].apply(first_character_only)

# 除外するカラムを定義(値の自由度が高くダミーでやると莫大な量になるデータなど)
excluded_columns = ['ここを', '変更']

# 除外するカラムを含まないデータフレームを作成
df_encoded = pd.get_dummies(df.drop(excluded_columns, axis=1))

# PassengerIdの分割結果を再度結合する
df_encoded['PassengerId_first4'] = passenger_id_first4 # ここも変更
df_encoded['PassengerId_last2'] = passenger_id_last2 # ここも変更

# 欠損値を平均値で補完する。
missing_values = df_encoded.columns[df_encoded.isnull().any()]
for column in missing_values:
    mean_value = df_encoded[column].mean()
    df_encoded[column].fillna(mean_value, inplace=True)

# 相関係数の計算のために正規化を行う
df_normalized = (df_encoded - df_encoded.mean()) / df_encoded.std()

# 相関係数の計算
correlation = df_normalized.corr()

# 何との相関係数を計算したいのかを定義
features = ['ここを変更']

# 取り出したカラムがfeaturesの中身と一致しなければ代入
other_features = [col for col in correlation.columns if col not in features]

# featuresとother_featuresの間の相関係数を計算
correlation_df = correlation.loc[other_features, features]

# 相関係数をCSVファイルで書き出す。
correlation_df.to_csv('ST_correlation.csv')
```
### カラムに含まれる値の種類
```SST_test.py
import pandas as pd

df = pd.read_csv('train.csv')
destination_count = df['ここを変更'].nunique()
print(destination_count)
```

### カラムに含まれる値の種類を棒グラフで出力
```SST_test.py
# 棒グラフでどんな値がどのくらいあるのかを計算
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('train.csv')

target_column = 'ここを変更'

# 特定のカラムの値が文字列の先頭の文字のみに変換する関数
def first_character_only(value):
    if isinstance(value, str) and value:  # 文字列であり、かつ空でない場合のみ変換
        return value[0]
    else:
        return value

# 特定のカラムの値に関数を適用する
df[target_column] = df[target_column].apply(first_character_only)

# 出現頻度を調べたいカラムを指定(複数できるようにリスト)
target_columns = ['ここを変更']

# 各指定されたカラムごとに棒グラフを作成
for column in target_columns:
    target_df = df[[column]]

    # カラムごとにTrue、False、NaN（欠損値）の出現頻度を計算
    value_counts = target_df[column].value_counts(dropna=False)
    plt.figure(figsize=(8, 6))
    value_counts.plot(kind='bar', color=['blue', 'orange', 'gray'], alpha=0.7)

    # グラフに具体的な数値を表示
    for i, v in enumerate(value_counts):
        plt.text(i, v, str(v), ha='center', va='bottom')

    plt.title(f'Value Counts of {column}')
    plt.xlabel('Value')
    plt.ylabel('Count')
    plt.xticks(rotation=0)
    plt.show()
```

### Cを指定した範囲で変更して遷移をプロット
```SST_test.py
# Cを変化しての正答率の変化
import numpy as np
import matplotlib.pyplot as plt

C_values = np.linspace(1, 100, 100)

accuracies = []
max_accuracy = 0
best_C = 0

for C in C_values:
    model = LogisticRegression(C=C, solver='lbfgs', max_iter=100, random_state=1)
    model.fit(train_x, train_y)
    y_pred = model.predict(test_x)
    accuracy = accuracy_score(test_y, y_pred)
    accuracies.append(accuracy)
    if accuracy > max_accuracy:
        max_accuracy = accuracy
        best_C = C

plt.plot(C_values, accuracies)
plt.title('Accuracy vs C')
plt.xlabel('C')
plt.ylabel('Accuracy')
plt.grid(True)
plt.show()

print("Maximum Accuracy:", max_accuracy)
print("Best C:", best_C)
```

### max_iterを指定した範囲で変更して遷移をプロット
```SST_test.py
# max_iterを変更しての正答率の変化
import matplotlib.pyplot as plt

for max_iter in max_iter_values:
    model = LogisticRegression(C=1, solver='lbfgs', max_iter=max_iter, random_state=1)
    model.fit(train_x, train_y)
    y_pred = model.predict(test_x)
    accuracy = accuracy_score(test_y, y_pred)
    accuracies.append(accuracy)
    if accuracy > max_accuracy:
        max_accuracy = accuracy
        best_max_iter = max_iter

plt.plot(max_iter_values, accuracies)
plt.title('Accuracy vs max_iter')
plt.xlabel('max_iter')
plt.ylabel('Accuracy')
plt.grid(True)
plt.show()

print("Maximum Accuracy:", max_accuracy)
print("Best max_iter:", best_max_iter)
```

### 重要度の出力
モデルを学習後に実行
```SST_test.py
coefficients = model.coef_[0]

# 各特徴量の重要度を表示
print("Feature Importance:")
for feature, coefficient in zip(features, coefficients):
    print(f"{feature}: {coefficient}")
```