Космический корабль «Титаник» — межзвездный пассажирский лайнер, запущенный месяц назад. С почти 13 000 пассажиров на борту судно отправилось в свой первый рейс, доставив эмигрантов из нашей Солнечной системы к трем новым обитаемым экзопланетам, вращающимся вокруг ближайших звезд.

Обогнув Альфу Центавра по пути к своему первому пункту назначения — жаркому 55 Cancri E — неосторожный космический корабль «Титаник» столкнулся с пространственно-временной аномалией, скрытой в пылевом облаке. К сожалению, его постигла та же участь, что и его тезку 1000 лет назад. Хотя корабль остался цел, почти половина пассажиров была перенесена в другое измерение!

Цель

Чтобы помочь спасательным экипажам и найти потерянных пассажиров, мы должны предсказать, какие пассажиры были перевезены аномалией, используя записи, восстановленные из поврежденной компьютерной системы космического корабля.

Загрузка данных

Импорт библиотек

import numpy as np 
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
import os
import math
import plotly
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import  train_test_split, RandomizedSearchCV
!pip install -q lazypredict
from lazypredict.Supervised import LazyClassifier
!pip install -q kaggle

Используйте Kaggle API для получения данных

from google.colab import files

# Upload kaggle API file
files.upload()

# Choose the kaggle.json file obtained from kaggle API
! mkdir ~/.kaggle
! cp kaggle.json ~/.kaggle/

# Make directory named kaggle and copy kaggle.json file there
! chmod 600 ~/.kaggle/kaggle.json
Saving kaggle.json to kaggle.json
mkdir: cannot create directory ‘/root/.kaggle’: File exists

Загрузить данные

# Get the data from the Kaggle Spaceship Titanic competition
!kaggle competitions download -c spaceship-titanic
Downloading spaceship-titanic.zip to /content
  0% 0.00/299k [00:00<?, ?B/s]
100% 299k/299k [00:00<00:00, 86.8MB/s]
# Unzip the data
! unzip spaceship-titanic.zip -d spaceship-titanic
Archive:  spaceship-titanic.zip
  inflating: spaceship-titanic/sample_submission.csv  
  inflating: spaceship-titanic/test.csv  
  inflating: spaceship-titanic/train.csv
# Load the train and test data from csv files into dataframes
train = pd.read_csv('/content/spaceship-titanic/train.csv')
train2 = train
test = pd.read_csv('/content/spaceship-titanic/test.csv')

# Make a dataframe for the end result
submission = test[['PassengerId']]

train.head()

Проблеск данных о поезде.

# Look into the train dataframe's shape
print('The shape of train data:', train.shape)
print('The shape of test data:', test.shape)
The shape of train data: (8693, 14)
The shape of test data: (4277, 13)

Данные поезда содержат 14 функций и 8693 записи.

# Get more info about the data
train.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 8693 entries, 0 to 8692
Data columns (total 14 columns):
 #   Column        Non-Null Count  Dtype  
---  ------        --------------  -----  
 0   PassengerId   8693 non-null   object 
 1   HomePlanet    8492 non-null   object 
 2   CryoSleep     8476 non-null   object 
 3   Cabin         8494 non-null   object 
 4   Destination   8511 non-null   object 
 5   Age           8514 non-null   float64
 6   VIP           8490 non-null   object 
 7   RoomService   8512 non-null   float64
 8   FoodCourt     8510 non-null   float64
 9   ShoppingMall  8485 non-null   float64
 10  Spa           8510 non-null   float64
 11  VRDeck        8505 non-null   float64
 12  Name          8493 non-null   object 
 13  Transported   8693 non-null   bool   
dtypes: bool(1), float64(6), object(7)
memory usage: 891.5+ KB

Данные поезда имеют 14 функций (столбцов), 1 типа boolean, 6 типа float и 7 типа object. Только столбцы «PassangerId» и «Transported» содержат ненулевые записи. В других столбцах не все записи не равны нулю.

Разделение функций

# Make function for feature splitting
def split(df):
  df['PassengerId_Group'] = df['PassengerId'].str.split("_", expand = True)[0].astype(np.int)
  df['PassengerId_Number'] = df['PassengerId'].str.split("_", expand=True)[1].astype(np.int)
  
  df['Cabin_Deck'] = df['Cabin'].str.split("/", expand = True)[0].astype("string")
  df['Cabin_Num'] = df['Cabin'].str.split("/", expand = True)[1].astype("string")
  df['Cabin_Side'] = df['Cabin'].str.split("/", expand = True)[2].astype("string")
  
  return df

Функция «PassangerId» состоит из 2 частей, а именно «Группы» и «Номера», разделенных подчеркиванием. Функция «Кабина» состоит из 3 частей, а именно «Палуба», «Номер» и «Сторона», разделенных косой чертой. Эта функция разбивает эти части на отдельные столбцы.

# Apply split function
train=split(train)

Разделить некоторые функции на данные о поездах

Отсутствующие значения

# Count the number of missing values on each features
train.isnull().sum()
PassengerId             0
HomePlanet            201
CryoSleep             217
Cabin                 199
Destination           182
Age                   179
VIP                   203
RoomService           181
FoodCourt             183
ShoppingMall          208
Spa                   183
VRDeck                188
Name                  200
Transported             0
PassengerId_Group       0
PassengerId_Number      0
Cabin_Deck            199
Cabin_Num             199
Cabin_Side            199
dtype: int64

Наибольшее количество нулевых значений в признаке — 217 из 8693 записей (2,49%), это функция CryoSleep. Это совсем небольшая сумма, так что это не будет проблемой.

# Handle missing values
def imputeNull(df): # Imputes null values
    # Categorical columns
    df['HomePlanet'].fillna(df['HomePlanet'].mode(), inplace = True)
    df['CryoSleep'].fillna(df['CryoSleep'].mode(), inplace = True)
    df['Destination'].fillna(df['Destination'].mode(), inplace = True)
    df['VIP'].fillna(df['VIP'].mode(), inplace=True)
    df['Cabin_Deck'].fillna(df['Cabin_Deck'].mode(), inplace = True)
    df['Cabin_Side'].fillna(df['Cabin_Side'].mode(), inplace = True)
    
    # Numerical columns
    df[df['CryoSleep'] == True][['RoomService', 'FoodCourt', 'ShoppingMall', 
                                 'Spa', 'VRDeck']].fillna(0, inplace = True)
                                  # If passanger asleep, any expenses will be 0
    df['Age'].fillna(df['Age'].mean(), inplace = True)
    df['RoomService'].fillna(df['RoomService'].median(), inplace = True)
    df['FoodCourt'].fillna(df['FoodCourt'].median(), inplace = True)
    df['ShoppingMall'].fillna(df['ShoppingMall'].median(), inplace = True)
    df['Spa'].fillna(df['Spa'].median(), inplace = True)
    df['VRDeck'].fillna(df['VRDeck'].median(), inplace = True)
    #df['Cabin_Num'].fillna(df['Cabin_Num'].median(), inplace=True)
    
    return df
# Some columns will be deleted before this function is used so no null values handling is made on those columns

Нулевые значения заменены из категориальных столбцов режимом записей и медианой записей для числовых столбцов, за исключением столбцов «RoomService», «FoodCourt», «ShoppingMall», «Spa», «VRDeck», для которых нулевые записи заменяются на 0, если статус «CyroSleep» имеет значение «Истина», потому что сумма не взимается, если кто-то находится в режиме cyro sleep (не пользуется какими-либо удобствами).

Исследовательский анализ данных

Уникальные значения

# Number of unique values for numerical features
print('Unique values for numerical features\n',train.select_dtypes(include = "number").nunique().sort_values(),"\n")

# Number of unique values for categorical features
print('Unique values for categorical features\n',train.select_dtypes(exclude = "number").nunique().sort_values())
Unique values for numerical features
 PassengerId_Number       8
Age                     80
ShoppingMall          1115
RoomService           1273
VRDeck                1306
Spa                   1327
FoodCourt             1507
PassengerId_Group     6217
dtype: int64 

Unique values for categorical features
 CryoSleep         2
VIP               2
Transported       2
Cabin_Side        2
HomePlanet        3
Destination       3
Cabin_Deck        8
Cabin_Num      1817
Cabin          6560
Name           8473
PassengerId    8693
dtype: int64

Группа пассажиров 6217 человек. Столбцы «RoomService», «FoodCourt», «ShoppingMall», «Spa», «VRDeck» имеют много уникальных значений, поскольку они представляют сумму денег, выставленную в счете.

Имеется 1817 номеров кают. «Кабина», «Имя», «PassengerId», «Cabin_Num» представляют собой идентификатор, поэтому они имеют много уникальных значений.

# Function that plots count of the entries of every unique values in categorical column that has relatively few unique values
def view_categorical(df, categorical_threshold):
  # List every non-numerical column that has number of unique values below certain threshold
  categorical_features = df.select_dtypes(exclude = "number").nunique()[df.select_dtypes(
      exclude = "number").nunique() <= categorical_threshold].index.tolist()
  
  # Create subplots
  rows = math.ceil(len(categorical_features) / 4)
  plt.subplots(rows,4,figsize = (13,5))

  # Make bar plot 
  for i in range(len(categorical_features)):
      plt.subplot(rows,4,i + 1)
      df[categorical_features[i]].value_counts().sort_values().plot.bar()

      value_counts = df[categorical_features[i]].value_counts(normalize=True).sort_values()
      ax = value_counts.plot.bar()
      # print()
      # for j, v in enumerate(value_counts):
      # ax.containers: print(j)
      #   height = j.get_height()
        # ax.bar_label(j, label=f"{v*100/value_counts.sum():.2f}%", font_size=8)  
        
      plt.xticks(rotation = 45)
      plt.title(categorical_features[i])
  
  plt.tight_layout()
  plt.show()
# Plot categorical columns in train data
categorical_threshold = 16
view_categorical(train, categorical_threshold)

Графики выше показывают количество всех уникальных значений, исключая нулевые значения, в категориальном столбце с относительно небольшим количеством уникальных значений, ограниченным пороговым значением. На родной планете доминирует «Земля», за ней следует «Европа», затем «Марс». Что касается пункта назначения, наиболее популярным является «TRAPPIST-1e», за ним следует «55 Cancri 3», затем «PSO J318.5–22». Самая популярная каютная палуба — «F», а наименее — «T». В то время как борта кабины почти поровну распределены между «P» и «S». Большинство пассажиров не находятся в крио-сне. Лишь немногие из них являются VIP. Статус пассажира в транспортировке показывает почти одинаковые значения между транспортируемым и нетранспортированным.

# Plot histogram for all numerical features
train.hist(bins = 30, figsize = (13,4), layout = (-1,4), edgecolor = 'k')
plt.tight_layout()

Возраст пассажиров распределяется неравномерно, выглядит смещенным влево и имеет тенденцию накапливаться между 18–40 годами. Младенцев также больше, чем других молодых пассажиров и пожилых пассажиров. Для столбцов «RoomService», «FoodCourt», «ShoppingMall», «Spa», «VRDeck» записи накапливаются с низким значением, с подавляющим числом со значением 0, что означает, что большинство пассажиров не тратят деньги на удобства космического корабля. Пассажиры по группам распределены практически поровну. Однако, поскольку он показывает только идентификацию, распределение не имеет существенного значения, равно как и «Номер пассажира».

Построить модель

Обработка выбросов

# Function that checks and removes outliers
def handle_outliers(df):
  df2 = df
  df = df[(df.select_dtypes(include = 'number').columns).to_list()]
  
  # Calculate the thresholds using quartile
  upper_threshold = df.quantile(0.75) + 1.5 * (df.quantile(0.75) - df.quantile(0.25))
  lower_threshold = df.quantile(0.25) - 1.5 * (df.quantile(0.75) - df.quantile(0.25))

  for i in df.select_dtypes(include = 'number'):
      # Check outliers
      x = df[(df[i] < lower_threshold[i]) | (df[i] > upper_threshold[i])][i].to_list()
      print('No of outliers (lower than',round(lower_threshold[i],2),'or greater than',round(upper_threshold[i],2),') presesnt in column', i, ":",len(x), '\n')

      # Keep entries that are not outliers
      df2 = df2[((df[i] >= lower_threshold[i]) & (df[i] <= upper_threshold[i]))]
  return df2
# The original shape of the train data
print('Original data shape:',train.shape)

# Check and remove outliers from train data
train = handle_outliers(train)

# The shape of train data after outliers removed
print('Data shape after outliers removal:',train.shape)
Original data shape: (8693, 19)
No of outliers (lower than -9.5 or greater than 66.5 ) presesnt in column Age : 77 

No of outliers (lower than -70.5 or greater than 117.5 ) presesnt in column RoomService : 1861 

No of outliers (lower than -114.0 or greater than 190.0 ) presesnt in column FoodCourt : 1823 

No of outliers (lower than -40.5 or greater than 67.5 ) presesnt in column ShoppingMall : 1829 

No of outliers (lower than -88.5 or greater than 147.5 ) presesnt in column Spa : 1788 

No of outliers (lower than -69.0 or greater than 115.0 ) presesnt in column VRDeck : 1809 

No of outliers (lower than -4527.0 or greater than 13729.0 ) presesnt in column PassengerId_Group : 0 

No of outliers (lower than -0.5 or greater than 3.5 ) presesnt in column PassengerId_Number : 493 

Data shape after outliers removal: (2888, 19)

Разработка функций

# Delete unnecessary features
def delete_irrelenvant_features(df): 
    df = df.drop(columns = ['PassengerId', 'Name', 'Cabin_Num', 'Cabin'], axis = 1, errors = 'ignore') 
    return df

Столбцы «Кабина», «PassengerId» были разделены на другие столбцы, а столбцы «Имя», «Номер кабины» представляют собой идентификацию. Эти столбцы удалены, так как они не нужны для построения модели данных.

# Function to convert categorical columns into numerical columns
def onehot_encoder(df, feature):
    dummies = pd.get_dummies(df[feature], prefix = feature)
    df = pd.concat([df,dummies], axis = 1)
    df = df.drop(columns = [feature], axis = 1)
    return df

# Function to convert boolean valued columns into numerical columns
def onehot_encoder_boolean(df, feature):
    dummies = pd.get_dummies(df[feature], prefix = feature, drop_first = True)
    df = pd.concat([df,dummies], axis = 1)
    df = df.drop(columns = [feature], axis = 1)
    return df

# Function to convert all non-numerical columns
def label_encoder_onehot(df):
    df = onehot_encoder(df, 'HomePlanet')
    df = onehot_encoder_boolean(df, 'CryoSleep')
    df = onehot_encoder(df, 'Destination')
    df = onehot_encoder_boolean(df, 'VIP')
    df = onehot_encoder(df, 'Cabin_Deck')
    df = onehot_encoder(df, 'Cabin_Side')
    return df
# Function to replace spaces with underlines
def replaceSpaces(df):
    df.columns = df.columns.str.replace(' ', '_')
    return df
# Function to do all of the preprocessing functions for train data
def preProcess(df, imputingNulls = True):
    df = split(df)
    df = delete_irrelenvant_features(df)
    df = handle_outliers(df)
    if imputingNulls == True:
        df = imputeNull(df)
    df = label_encoder_onehot(df)
    df = replaceSpaces(df)
    return df
# Function to do all of the preprocessing functions for test result (without dropping any rows)
def preProcess_analytics(df, imputingNulls = True):
    df = split(df)
    df = delete_irrelenvant_features(df)
    if imputingNulls == True:
        df = imputeNull(df)
    df = label_encoder_onehot(df)
    df = replaceSpaces(df)
    return df

Подготовка данных

# Do data prepatation to build the model
y = train['Transported']
X = train.drop(columns = 'Transported', axis = 1)
X_train , X_test , y_train , y_test = train_test_split(X, y, random_state = 5, test_size = 0.40)

Колонка «Перевезен» указывает на то, доставлен ли пассажир в пункт назначения или нет. Этот столбец состоит из логических значений. Это целевой столбец, который мы собираемся предсказать, поэтому мы отделяем этот столбец от данных поезда в другую переменную.

Затем основные данные поезда разделяются на данные поезда и тестовые данные для построения модели.

# Apply the preprocessing function to splitted train data
X_train = preProcess(X_train, imputingNulls = True)
X_test = preProcess(X_test, imputingNulls = True)

# Apply the preprocessing function to the main train data
X_preProcessed = preProcess_analytics(X, imputingNulls = True)

# Apply the preprocessing function to the test data we are about to predict
data_test_preProcessed = preProcess_analytics(test, imputingNulls = True) 

No of outliers (lower than -15.0 or greater than 65.0 ) presesnt in column Age : 0 

No of outliers (lower than 0.0 or greater than 0.0 ) presesnt in column RoomService : 0 

No of outliers (lower than 0.0 or greater than 0.0 ) presesnt in column FoodCourt : 0 

No of outliers (lower than 0.0 or greater than 0.0 ) presesnt in column ShoppingMall : 0 

No of outliers (lower than 0.0 or greater than 0.0 ) presesnt in column Spa : 0 

No of outliers (lower than 0.0 or greater than 0.0 ) presesnt in column VRDeck : 0 

No of outliers (lower than -4308.0 or greater than 13614.0 ) presesnt in column PassengerId_Group : 0 

No of outliers (lower than -0.5 or greater than 3.5 ) presesnt in column PassengerId_Number : 0 

No of outliers (lower than -16.5 or greater than 67.5 ) presesnt in column Age : 0 

No of outliers (lower than 0.0 or greater than 0.0 ) presesnt in column RoomService : 0 

No of outliers (lower than 0.0 or greater than 0.0 ) presesnt in column FoodCourt : 0 

No of outliers (lower than 0.0 or greater than 0.0 ) presesnt in column ShoppingMall : 0 

No of outliers (lower than 0.0 or greater than 0.0 ) presesnt in column Spa : 0 

No of outliers (lower than 0.0 or greater than 0.0 ) presesnt in column VRDeck : 0 

No of outliers (lower than -4586.5 or greater than 13841.5 ) presesnt in column PassengerId_Group : 0 

No of outliers (lower than -0.5 or greater than 3.5 ) presesnt in column PassengerId_Number : 0

Функция предварительной обработки применяется к данным поезда и к тестовым данным, и мы увидим результаты прогнозов в качестве нашей конечной цели. Предварительная обработка наших основных данных поезда «X» и тестовых данных «test» не включает удаление выбросов, потому что нам нужны все записи в этих данных.

# Check the number of columns on each splitted data
print(len(X_train.columns),len(X_test.columns),len(X_preProcessed.columns),len(data_test_preProcessed.columns))

# Check which columns is missing
print(set(data_test_preProcessed.columns)-set(X_test.columns))
print(set(data_test_preProcessed.columns)-set(X_train.columns))
print(set(data_test_preProcessed.columns)-set(X_preProcessed.columns))

# Add the missing columns
for i in (set(data_test_preProcessed.columns)-set(X_test.columns)):
  X_test[i] = False
  X_train[i] = False
  X_preProcessed[i] = False

print(X_test.columns)
25 25 25 26
{'Cabin_Deck_T'}
{'Cabin_Deck_T'}
{'Cabin_Deck_T'}
Index(['Age', 'RoomService', 'FoodCourt', 'ShoppingMall', 'Spa', 'VRDeck',
       'PassengerId_Group', 'PassengerId_Number', 'HomePlanet_Earth',
       'HomePlanet_Europa', 'HomePlanet_Mars', 'CryoSleep_True',
       'Destination_55_Cancri_e', 'Destination_PSO_J318.5-22',
       'Destination_TRAPPIST-1e', 'VIP_True', 'Cabin_Deck_A', 'Cabin_Deck_B',
       'Cabin_Deck_C', 'Cabin_Deck_D', 'Cabin_Deck_E', 'Cabin_Deck_F',
       'Cabin_Deck_G', 'Cabin_Side_P', 'Cabin_Side_S', 'Cabin_Deck_T'],
      dtype='object')

При построении модели машинного обучения данные поезда, тестовые данные и окончательные тестовые данные, которые мы прогнозируем, должны иметь все одинаковые столбцы. Поскольку мы выполнили кодирование столбцов, которое преобразует категориальный столбец в отдельные числовые столбцы, возможно, что этот категориальный столбец преобразуется в различное количество числовых столбцов для разных фреймов данных одного и того же типа (данные обучения и тестирования). В этом случае столбец «Cabin_Deck» имеет значение «T» в наших тестовых данных, но не в наших данных поезда. Следовательно, после кодирования этого столбца наши тестовые данные имеют на один столбец больше, чем наши данные поезда. Чтобы обойти это, к основным данным поезда добавляется новый столбец со значением 0, что означает, что ни один пассажир не находится на палубе T.

Выберите метод

# Use some classifier methods simultaneously
clf = LazyClassifier(verbose = 0,
                     ignore_warnings = True,
                     custom_metric = None,
                     predictions = False,
                     random_state = 12,
                     classifiers = 'all')

models, predictions = clf.fit(X_train, X_test ,y_train , y_test)

models.sort_values(by=['Accuracy'], ascending = False)
100%|██████████| 29/29 [00:03<00:00,  7.60it/s]

px.line(data_frame = models.sort_values(by=['Accuracy'], ascending = False)[:], y = ['Accuracy','Balanced Accuracy','ROC AUC','F1 Score'],  markers = True)

LazyClassifier используется для моделирования данных поезда с использованием 26 методов одновременно. Используемые метрики оценки: точность, сбалансированная точность, ROC AUC и F1 Score. Результат показан в таблице.

Модель, дающая наибольшую точность, — линейный дискриминантный анализ.

Более того, все показатели относительно высоки в SGD Classifier.

Предсказывать

y_pred = lda.predict(data_test_preProcessed)
submission['Transported'] = y_pred.astype("bool")
submission.to_csv("submission.csv",index = False)
files.download('submission.csv')
submission.head()
submission

y_pred = sgd.predict(data_test_preProcessed)
submission['Transported'] = y_pred.astype("bool")
submission.to_csv("submission.csv",index = False)
# files.download('submission.csv')
submission.head()

Здесь мы прогнозируем тестовые данные, используя метод, который мы выбрали ранее. Результат прогноза, показанный в таблице, также автоматически загружается в формате CSV.

Полный код показан ниже и его можно увидеть здесь.