Наборът от данни на Титаник е един от най-добрите набори от данни за практикуване на почистване на данни и инженеринг на функции. Това е прост набор от данни с много богата история. Процесът на почистване на данни е много важен и един от най-трудоемките в анализа на данни. Този набор от данни първоначално е получен от състезанието Kaggle („Титаник — Машинно обучение от бедствие“). Тук ще демонстрирам някои техники за почистване на набора от данни на Титаник и ще извърша инженеринг на функции с цел да го приложа към машинно обучение с дървовидни модели. Тъй като стъпките за обработка на данни са дълги, ще разгледам приложението на машинното обучение към този проблем в друга статия (ще добавя връзката тук). По-лесно е да започнете да изучавате ML с чист набор от данни, готов за повечето правила за въвеждане на модели. В реалния свят трябва да извършим много обработка на данните, преди да приложим ML алгоритмите и обикновено обработката на данни отнема повече време от частта за моделиране на ML. Инженерингът на функции може да подобри набора от данни чрез разкриване на нови променливи, които могат да помогнат в проучвателния анализ, да разберат връзките между променливите и да повлияят на крайните прогнози с всеки алгоритъм за машинно обучение. Колкото повече се гмуркаме в набора от данни на Титаник, за да го изчистим, толкова повече откриваме интересна информация, скрита в мръсните неструктурирани колони на таблицата. кодовете и наборите от данни, използвани в тази статия, се споделят в моето репо GitHub.

Нека започнем с импортиране на необходимите библиотеки.

# Basic imports
import numpy as np
import pandas as pd
import re
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
import plotly.graph_objs as go
from ipywidgets import interact

Заредете данни

train = pd.read_csv("titanic_datasets/train.csv")
test = pd.read_csv("titanic_datasets/test.csv")

# Contatenate dataset
df = pd.concat([train, test], ignore_index = True, sort = False)
df.head()

По-долу е представено кратко описание на променливите в този набор от данни. За по-подробна информация се консултирайте с източника в Kaggle: Titanic-Machine Learning from Disaster. Тези променливи предоставят информация за различни аспекти на пътниците на Титаник, като техните демографски данни, семейни отношения, подробности за билета и резултат от оцеляването.

Проверете липсващите стойности и типове данни

nan_counts = df.isna().sum().sort_values(ascending = False)
summary = pd.concat([df.info(), nan_counts], axis=0)
print(summary)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1309 entries, 0 to 1308
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  1309 non-null   int64  
 1   Survived     891 non-null    float64
 2   Pclass       1309 non-null   int64  
 3   Name         1309 non-null   object 
 4   Sex          1309 non-null   object 
 5   Age          1046 non-null   float64
 6   SibSp        1309 non-null   int64  
 7   Parch        1309 non-null   int64  
 8   Ticket       1309 non-null   object 
 9   Fare         1308 non-null   float64
 10  Cabin        295 non-null    object 
 11  Embarked     1307 non-null   object 
dtypes: float64(3), int64(4), object(5)
memory usage: 122.8+ KB
Cabin          1014
Survived        418
Age             263
Embarked          2
Fare              1
PassengerId       0
Pclass            0
Name              0
Sex               0
SibSp             0
Parch             0
Ticket            0
dtype: int64

Стъпки за обработка на данни

  1. Съпоставете „Sex“ с „is_male“, за да използвате тази променлива в корелационни диаграми и в ML модели.
  2. Съпоставете „Embarqued“ с Embarqued_code{1,2,3}. запазете нулата за NaN.
  3. Разделете променливата „Ticket“ на „Ticket_preffix“ и „Ticket_n“
  4. Групирайте дублираните стойности на билети и пребройте броя на хората, които са пътували заедно, включително приятели, прислужници и детегледачки, и създайте променливата „Придружители“.
  5. Създайте променливата „FamilySize“, като комбинирате променливите „SibSp“ и „Parch“.
  6. Извлечете променливата „Заглавие“ от променливата „Име“ и категоризирайте.
  7. Съпоставяне на „тарифа“ с „ниво на такса“: {1,2,3,4,5,6,7,8,9,10}
  8. Най-мръсната променлива ‘Cabin’ -› я разделя на [‘Cabin_label’, ‘Cabin_n’, ‘n_of_Cabins’].
  9. Съпоставете променливата „Възраст“ в „Възрастова_група“

секс

Кодирайте променливата „Sex“ на „Is_male“, 1 за мъж, 0 за жена. Тази стъпка е необходима, защото планираме да използваме ML алгоритми, базирани на дърво на решенията на scikit-learn, и изисква кодиране на категориални променливи (вижте scikit-learn 1.3.0: Дърво на решенията).

# Mapping Sex bolean
df['Is_male'] = df['Sex'].map( {'female': 0, 'male': 1} ).astype(int)

Качих се

Почистете променливата за пристанището на качване, „Embarked“. Първо попълваме само две NaN стойности с истинските стойности, които намерихме в Google, след което кодираме променливата в числови стойности. Стойностите C = Шербург, Q = Куинстаун и S = ​​Саутхемптън ще бъдат съпоставени с 1 = C, 2 = Q и 3 = S.

# Check the entries with NaN vales.
df.loc[df['Embarked'].isna(),:]

Виждаме, че и двамата пътници споделят стойностите на променливите, Пол = женски, Pклас = 1, те са били в една и съща кабина = B28 и номерът на билета е един и същ Билет = 113572. Смятате ли, че е съвпадение? Аз не мисля. Г-жа Стоун се качва на борда на Титаник в Саутхемптън на 10 април 1912 г. и пътува в първа класа с прислужницата си Амели Икард. Тя заемаше кабина B-28 (вижте енциклопедия Титаника).

# Fill the NaN and map Embarked to numerical codes
df.loc[df['Embarked'].isna(), 'Embarked'] = 'S'  # (Encyclopedia titanica)
df['Embarked_code'] = df['Embarked'].map( {'C': 1, 'Q': 2, 'S': 3} ).astype(int)

Билет

Разделете „Билета“ на променливите „Ticket_prefix“ и „Ticket_number“

# split the column 'Ticket'
Ticket_split = []
Ticket_len = []
for i in df['Ticket'].index:
    splitted = df.loc[i,'Ticket'].split(' ')
    Ticket_split.append(splitted)
    length = len(splitted)
    Ticket_len.append(length)
    
df['Ticket_split'] = Ticket_split
df['Ticket_len'] = Ticket_len

# df[['Ticket_len']].value_counts()
# df.loc[df['Ticket_len'] > 2, :] We can see tome typo errors.

# Create columns Ticket_preffix and Ticket_number
ticket_preffix = []
ticket_number = []
for i in df['Ticket'].index:
    ticket_parts = df.loc[i, 'Ticket_split']
    number = ticket_parts[-1]
    ticket_number.append(number)
    length = df.loc[i, 'Ticket_len'].item()
    if length > 1:
        preffix = ''.join(ticket_parts[0:-1])
        ticket_preffix.append(preffix)
    else:
        ticket_preffix.append('blanck')
        
df['Ticket_preffix'] = ticket_preffix
df['Ticket_number'] = ticket_number
# drop unnecessary columns created
df.drop(columns=['Ticket_split', 'Ticket_len'], inplace=True)

df.Ticket_preffix.unique()
array(['A/5', 'PC', 'STON/O2.', 'blanck', 'PP', 'A/5.', 'C.A.', 'A./5.',
       'SC/Paris', 'S.C./A.4.', 'A/4.', 'CA', 'S.P.', 'S.O.C.', 'SO/C',
       'W./C.', 'SOTON/OQ', 'W.E.P.', 'A4.', 'C', 'SOTON/O.Q.',
       'SC/PARIS', 'S.O.P.', 'A.5.', 'Fa', 'CA.', 'F.C.C.', 'W/C',
       'SW/PP', 'SCO/W', 'P/PP', 'SC', 'SC/AH', 'A/S', 'SC/AHBasle',
       'A/4', 'WE/P', 'S.W./PP', 'S.O./P.P.', 'F.C.', 'SOTON/O2',
       'S.C./PARIS', 'C.A./SOTON', 'SC/A.3', 'STON/OQ.', 'SC/A4', 'AQ/4',
       'A.2.', 'LP', 'AQ/3.'], dtype=object)

Можем да визуализираме разнообразието от стойности на префикса на билета и да се опитаме да намерим връзка с пристанището на качване.

df_grouped = df.groupby(['Ticket_preffix', 'Embarked']).agg({'Survived': ['sum', lambda x: x.count() - x.sum()]})
df_grouped.columns = ['Survived', 'Non-Survived']
df_grouped['Count'] = df.groupby(['Ticket_preffix', 'Embarked']).size()
print(df_grouped)
                            Survived  Non-Survived  Count
Ticket_preffix Embarked                               
A./5.          S              0.0           2.0      3
A.2.           S              0.0           0.0      1
A.5.           S              0.0           2.0      3
A/4            S              0.0           3.0      6
A/4.           S              0.0           3.0      3
A/5            S              1.0           9.0     12
A/5.           Q              0.0           1.0      1
               S              1.0           5.0      9
A/S            S              0.0           1.0      1
A4.            S              0.0           1.0      1
AQ/3.          Q              0.0           0.0      1
AQ/4           Q              0.0           0.0      1
C              S              2.0           3.0      8
C.A.           C              0.0           0.0      1
               S             13.0          14.0     45
C.A./SOTON     S              0.0           1.0      1
CA             S              0.0           6.0     10
CA.            S              1.0           7.0     12
F.C.           C              0.0           0.0      1
               S              0.0           1.0      2
F.C.C.         S              4.0           1.0      9
Fa             S              0.0           1.0      1
LP             S              0.0           0.0      1
P/PP           C              1.0           1.0      2
PC             C             29.0          17.0     72
               S             10.0           4.0     20
PP             S              2.0           1.0      4
S.C./A.4.      S              0.0           1.0      1
S.C./PARIS     C              1.0           1.0      3
S.O./P.P.      S              0.0           3.0      7
S.O.C.         S              0.0           5.0      7
S.O.P.         S              0.0           1.0      1
S.P.           S              0.0           1.0      1
S.W./PP        S              1.0           0.0      1
SC             C              1.0           0.0      1
               S              0.0           0.0      1
SC/A.3         C              0.0           0.0      1
SC/A4          S              0.0           0.0      1
SC/AH          S              1.0           1.0      4
SC/AHBasle     C              1.0           0.0      1
SC/PARIS       C              2.0           3.0     10
               S              0.0           0.0      1
SC/Paris       C              2.0           2.0      5
SCO/W          S              0.0           1.0      1
SO/C           S              1.0           0.0      1
SOTON/O.Q.     S              1.0           7.0     16
SOTON/O2       S              0.0           2.0      3
SOTON/OQ       S              1.0           6.0      8
STON/O2.       S              8.0          10.0     21
STON/OQ.       S              0.0           0.0      1
SW/PP          S              1.0           0.0      1
W./C.          S              1.0           8.0     14
W.E.P.         S              0.0           1.0      2
W/C            S              0.0           1.0      1
WE/P           S              1.0           1.0      2
blanck         C             56.0          51.0    173
               Q             30.0          46.0    120
               S            169.0         313.0    668

Колоната „Tiket_prefix“ има някои печатни грешки и изглежда е свързана с информацията „Embarked“. Повечето от стойностите са празни и може би не си струва да анализирате вероятността за оцеляване с ML модел.

Придружители

Освен това вече забелязахме, че променливата „Ticket_number“ има някои дублирани записи. Някои пътници споделят номера на билета, защото са заедно. Те могат да имат връзка със служител или служба като прислужница или бавачка, или могат да бъдат близки приятели. Тези връзки не бяха всички уловени в променливите „SibSp“ и „Parch“. Вижте например случая с г-жа Стоун, качена на борда, която пътуваше с прислужницата си Амели Икар и двете оцеляха. („енциклопедия Титаника“).

Можем да извлечем имплицитна информация от дублираните стойности в променливата „Билет“ и да създадем нова променлива, която да я опише. Тук създаваме новата променлива „Спътници“, описваща броя на спътниците или спътниците за всеки индивид. Може би това число оказва известно влияние върху вероятността за оцеляване, когато използваме ML алгоритъм за прогнозиране на стойностите „Оцелели“ в тестовия набор от данни.

n_alone , n_duplicates = df['Ticket'].duplicated(keep=False).astype(int).value_counts()
print("Number of duplicated ticket values:", n_duplicates)
print("Number of non-duplicated ticket values:", n_alone)
Number of duplicated ticket values: 596
Number of non-duplicated ticket values: 713
df['Companions'] = df['Ticket'].duplicated(keep=False).astype(int) * df.groupby('Ticket')['Ticket'].transform('count') -1 
df.loc[df['Companions'] == -1, 'Companions'] = 0

Можем да проверим разпределението на броя на придружителите.

df['Companions'].value_counts()
0     713
1     264
2     147
3      64
4      35
6      35
5      24
7      16
10     11
Name: Companions, dtype: int64df['Ticket_number'].duplicated(keep=False).astype(int).value_counts()
# (df.groupby('Ticket')['Ticket'].transform('count') - 1)

Размер на семейството

Новата променлива „Размер на семейството“ е създадена със сумата от променливите „SibSp“ и „Parch“, за да опише семейната структура на борда за всеки пътник. Създаваме корелирана променлива защо? Можем да изследваме честотата на „оцелял“ като функция на общия брой членове на семейството. Освен това, дървовидните модели имат вътрешен алгоритъм за избор на характеристики, който избира най-подходящите променливи за прогнозирането на целевата променлива. Може би променливата „Размер на семейството“ може да бъде по-важна от променливите „SibSp“ и „Parch“.

df['FamilySize'] = df['SibSp'] + df['Parch']
print(df[['SibSp','Parch','FamilySize', 'Survived']].groupby(['FamilySize'], as_index=False).mean().sort_values(by = 'Survived'))
FamilySize     SibSp     Parch  Survived
7           7  4.000000  3.000000  0.000000
8          10  6.727273  3.272727  0.000000
5           5  2.800000  2.200000  0.136364
4           4  2.000000  2.000000  0.200000
0           0  0.000000  0.000000  0.303538
6           6  3.250000  2.750000  0.333333
1           1  0.778723  0.221277  0.552795
2           2  0.893082  1.106918  0.578431
3           3  1.302326  1.697674  0.7241383.5

Новата променлива „Заглавие“

Сега ще извлечем информацията за титлата на пътника от променливата „Име“. Можем да създадем функцията „Заглавие“, като извлечем от колоната „Име“ заглавията: г-н г-жа г-жа д-р и др. (вижте също Sina — Tinatic най-добрият работещ класификатор)

# get the 'Title'
def get_title(name):
 title_search = re.search(' ([A-Za-z]+)\.', name)
 # If the title exists, extract and return it.
 if title_search:
  return title_search.group(1)
 return ""

df['Title'] = df['Name'].apply(get_title)

df_grouped = df.groupby(['Title', 'Sex']).agg({'Survived': ['sum', lambda x: x.count() - x.sum()]})
df_grouped.columns = ['Survived', 'Non-Survived']
df_grouped['Count'] = df.groupby(['Title', 'Sex']).size()
print(df_grouped)
Survived  Non-Survived  Count
Title    Sex                                  
Capt     male         0.0           1.0      1
Col      male         1.0           1.0      4
Countess female       1.0           0.0      1
Don      male         0.0           1.0      1
Dona     female       0.0           0.0      1
Dr       female       1.0           0.0      1
         male         2.0           4.0      7
Jonkheer male         0.0           1.0      1
Lady     female       1.0           0.0      1
Major    male         1.0           1.0      2
Master   male        23.0          17.0     61
Miss     female     127.0          55.0    260
Mlle     female       2.0           0.0      2
Mme      female       1.0           0.0      1
Mr       male        81.0         436.0    757
Mrs      female      99.0          26.0    197
Ms       female       1.0           0.0      2
Rev      male         0.0           6.0      8
Sir      male         1.0           0.0      1

почистете и категоризирайте променливата „Заглавие“, след което проверете броя на оцелелите и неоцелелите за всяко категоризирано заглавие.

# Clean the variable Title.
df['Title'] = df['Title'].replace(['Lady', 'Countess','Capt', 'Col',
  'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'noble')
df['Title'] = df['Title'].replace('Mlle', 'Miss')
df['Title'] = df['Title'].replace('Ms', 'Mrs')
df['Title'] = df['Title'].replace('Mme', 'Mrs')
# Check the survival rate for the title cathegories obtained
df_grouped = df.groupby(['Title', 'Sex']).agg({'Survived': ['sum', lambda x: x.count() - x.sum()]})
df_grouped.columns = ['Survived', 'Non-Survived']
df_grouped['Count'] = df.groupby(['Title', 'Sex']).size()
# Calculate the survived/non-survived rate
df_grouped['Survival Rate'] = df_grouped['Survived'] / (df_grouped['Survived'] + df_grouped['Non-Survived'])
# Order the DataFrame by the survival rate in descending order
df_grouped = df_grouped.sort_values('Survival Rate', ascending=False)

print(df_grouped)
Title   Sex     Survived   Non-Survived  Count  Survival Rate
                                                 
noble  female       3.0           0.0      4       1.000000
Mrs    female     101.0          26.0    200       0.795276
Miss   female     129.0          55.0    262       0.701087
Master male        23.0          17.0     61       0.575000
noble  male         5.0          15.0     25       0.250000
Mr     male        81.0         436.0    757       0.156673

Можем да видим, че да бъдеш благородник има голяма разлика във вероятността за оцеляване на Титаник. Другите заглавия отразяват пола и възрастта на пътника. Можем да забележим, че процентът на оцеляване за мъж с титлата майстор е по-добър от този с титлата Господин, въпреки че може да се тълкува според възрастта, докато по-младият мъж има по-добър шанс да оцелее. Ако сравним степента на оцеляване на жените, пътниците с титлата Мис имат по-нисък шанс за оцеляване в сравнение с жените с титлата Мисис, като има предвид, че статусът на жената като омъжена или не може да има значение. Но всички сме съгласни, че информацията за семейството и възрастта има свои собствени характеристики за описание. Изключителната информация, която можем да получим от функцията за заглавие, е дали пътникът притежава благородническа титла или не.

df['noble'] = df['Title'].apply(lambda x: 1  if x =='noble'  else 0)
df['noble'].value_counts()
0    1280
1      29
Name: noble, dtype: int64

тарифа

Можем да заменим липсващата стойност „Fare“ (има само една) със средната стойност. За да бъдем честни, ще заместим липсващата стойност на тарифата с медианата на стойностите на тарифата за пол = мъжки и Pклас = 3.

# fill with the median Fare for the Pclass = 3 and Sex= male.
Fare_Pclass3_male = df.loc[(df['Pclass'] == 3) & (df['Sex'] == 'male'), 'Fare'].median()
df.loc[df['PassengerId'] == 1044, 'Fare'] = df.loc[(df['Pclass'] == 3) & (df['Sex'] == 'male'), 'Fare'].median()
print("medium Fare for Sex = male and Pclass=3: ", Fare_Pclass3_male)
medium Fare for Sex = male and Pclass=3:  7.8958

Можем да категоризираме таксата в групи от равни диапазони или нива на стойността на таксата. Ако го категоризираме с помощта на интерквартил, разпределението на контейнерите връща странно представяне, тъй като променливата „Fare“ не е непрекъсната.

# Divide Fare in 10 levels
n = 10
df['Fare_level'] = pd.cut(df['Fare'], n, labels=np.arange(1,n+1))
df['Fare_range'] = pd.cut(df['Fare'], n)

Можем да визуализираме как се променя вероятността за оцеляване с „Fare_level“.

# Calculate survival probability for each fare level
fare_survival_grouped = df.groupby('Fare_level').agg({'Fare_range': ['first', 'count'], 'Survived': 'mean'}).reset_index()
fare_survival_grouped.columns = ['Fare_level', 'Fare_range', 'Count', 'Survival_proba']
fare_survival_grouped = fare_survival_grouped.reset_index(drop=True)
fare_survival_grouped

В горната таблица четирима пътници с вероятност за оцеляване от 100% имат стойности на билета от £512,33. Това е случаят с г-н Томас Дрейк Мартинез Кардеса, богат банкер от Джърмантаун, Пенсилвания. Той се качи на борда на Титаник с майка си Шарлот Кардеса, слугата си Гюстав Лесюр и г-ца Ани Мур Уорд, прислужницата на семейство Кардеса. (вижте Енциклопедия Титаника и Титаник Фендом). Те заеха един от двата най-луксозни апартамента на борда (B51, B53 и B55) и платиха друг костюм за прислужника и вероятно още един за тяхната прислужница Ани.

Нека начертаем зависимостта на вероятността за оцеляване с новата функция „Fare_level“. В графиката по-долу можем да видим как вероятността за оцеляване нараства бързо с „Fare_level“ и осцилира за стойности над 4-то ниво.

# Calculate the counts
variable = 'Fare_level'
counts = df[variable].value_counts()
# Calculate the probability of survival
survived = df[df['Survived'] == 1][variable].value_counts().sort_index()
not_survived = df[df['Survived'] == 0][variable].value_counts().sort_index()
survival_prob = survived / (survived + not_survived)
# Create subplots with shared x-axis
fig, ax1 = plt.subplots()
# Plot the survival probability using scatter plot markers and lines on the left y-axis
ax1.plot(survival_prob.index, survival_prob.values, marker='o', linestyle='-', color='red')
ax1.set_ylabel('Survival Probability', color='black')
ax1.tick_params(axis='y', labelcolor='red')
# Create a twin y-axis for the left side
ax2 = ax1.twinx()
#Plot the count bars on the right y-axis
ax2.bar(counts.index, counts.values, alpha=0.4, width=0.2, color='royalblue')
ax2.set_ylabel('Counts', color='black')
ax2.tick_params(axis='y', labelcolor='blue')
# Set the x-axis label and title
ax1.set_xlabel(variable)
plt.title('Count and Survival Probability')
# Adjust the layout of subplots
plt.tight_layout()

Кабина

Това е най-мръсната колона, повечето стойности липсват и някои редове съдържат повече от една стойност на колона (повече от една кабина за някои пътници). Бяха наблюдавани само 22% от стойностите на „Кабината“ (295/1309). Информацията в кабината може да бъде много информативна като заместител на разстоянието от палубата на лодката, където са разположени спасителните лодки, оказвайки голямо влияние върху шанса за оцеляване. Но тази колона не е структурирана и има предимно липсващи стойности. За да помогнем за разрешаването на това, започваме с разделянето на тази колона на три колони: [“Cabin_label”, “Cabin_number”, “N_of_Cabins reserved”]. Някои подходи за приписване на данни към променливата „Кабина“ са:

  • Анализирайте тези три колони заедно със стойностите „Fare“ и броя на членовете на семейството на борда, „FamilySize“, по такъв начин, че да можем да се опитаме да предвидим липсващите стойности поне за липсващия Cabin_label.
  • Можем да приложим просто правило и да изчислим липсващия Cabin_label чрез картографиране на променливата „Fare“.
  • Можем да търсим дублирани стойности на променливата „Билет“, за която се наблюдава „Кабина“ за един от спътниците.
  • Можем да използваме контролиран алгоритъм за стратегия за импутиране, базиран на ML, като MISS Forest.
  • Базираните на дърво модели имат някои техники за правене на прогнози дори в сценарии, при които имаме липсващи стойности. Самостоятелните версии на XGBoost и LightGBM имат вградена поддръжка за липсващи стойности. Сурогатното разделяне е техника, при която дървото намира друга променлива, за да извърши разделянето, ако основната променлива за разделяне липсва. сурогатната променлива има най-висока корелация с първичната променлива и има тенденция да извършва разделянето по същия начин, по който го прави първичната. Въпреки това не намерих внедряване на дърво на решения в Python, което да поддържа използването на сурогатни разделяния.
  • Можем да създадем нова категорична променлива за липсващата стойност.

Съществуват различни подходи за импутиране на данни и стратегии за използване на колоните „Кабина“, за да се помогне при прогнозирането на вероятността за оцеляване. Някои споделени бележници в Kaggle се опитват да направят импутация на етикета на кабината, като използват прости правила, базирани на променливата „Fare“ или „Pclass“, но променливата „Fare“ не отговаря непременно на пропорционална стойност за „Pclass“ в случаи, когато кодът на билета е един и същ за група пътници. Някои пътници се качиха със семейството и служителите и платиха за няколко кабини. В тези случаи кодът на билета е един и същ и тарифата е същата, съответстваща на сумата от каютите и придружителите. Това е идеалното определение на неструктуриран набор от данни, където променливите съдържат повече от една информация. Липсващите стойности в променливата „Cabin_number“ не могат да бъдат изчислени. Тук избирам последния метод и в новата променлива „Cabin_label“ липсващите стойности се приписват чрез създаване на нова категорична променлива „N“.

# get the last cabin if exists
df['Last_cabin'] = df['Cabin'].apply(lambda x: str(x).split()[-1] if pd.notnull(x) else 'N')
# get the Cabin_label of the last cabin
pattern = r'([A-Za-z])'
df['Cabin_label'] = df['Last_cabin'].apply(lambda x: re.search(pattern, str(x)).group(1) if x != 'N' else 'N')
# Get the number of the last cabin if exists
pattern = r'(\d+)'
df['Cabin_number'] = df['Last_cabin'].apply(lambda x: re.search(pattern, str(x)) if x != 'N' else -1)
# Get the number of distinct cabins reserved by the passenger.
df['Cabin_count'] = df['Cabin'].apply(lambda x: len(str(x).split()) if pd.notnull(x) else 0)
# drop not used columns and columns with NaN values.
df.drop(columns = ['Cabin','Last_cabin', 'Cabin_number'], inplace = True)

Възраст на картографиране

Променливата „Възраст“ има 1046 наблюдения от общо 1309 записа, съответстващи на 80% от набора от данни (20% неизвестни, 263 липсващи стойности). Може да се използва импутиране на регресионно правило, като се използват подобни стратегии, изброени в горния раздел. Тук създаваме нова променлива „Age_group“, като разделяме интервала от стойности на почти равни интервали. Тази нова променлива може да бъде много по-информативна от точните стойности на „Възрастта“.

Първо жените и децата!

Променливата „Age_group“ е категоризирана със значими възрастови диапазони за всяка група до 60 години. Очакваната продължителност на живота през 1900 г. е била около 50 години според u.demog. Бъркли. Тук избрах диапазоните 0–13 за деца, 13–30 за млади, диапазон 30–45 за зряла група, 45–60 за възрастни. От 60 години можем да класифицираме като много стари. Тази произволна категоризация беше избрана, за да се получи добро разпределение на стойностите за всяка категория (балансирано). Ако изберем по-малка възрастова граница (с диапазон от 10 години, например), броят на представителите в клас намалява и е дисбалансиран. В следващата статия ще проуча някои базирани на ML алгоритми за импутиране на данни и ще направя импутация за променливите „Възраст“ и „Възрастова_група“.

# Mapping Age
df.loc[df['Age'] <= 13, 'Age_group'] = 0 # kids
df.loc[(df['Age'] > 13) & (df['Age'] <= 30), 'Age_group'] = 1 # young
df.loc[(df['Age'] > 30) & (df['Age'] <= 45), 'Age_group'] = 2 # mature1
df.loc[(df['Age'] > 45) & (df['Age'] <= 60), 'Age_group'] = 3 # old
df.loc[(df['Age'] > 60) & (df['Age'] <= 100), 'Age_group'] = 4 # very old

# Calculate survival probability for each fare level
age_survival_grouped = df.groupby('Age_group').agg({'Age': ['min', 'max'], 'Survived': ['mean', 'count']}).reset_index()
age_survival_grouped.columns = ['Age_group', 'Age_min' , 'Age_max', 'Survival_proba', 'Count']
age_survival_grouped

# Calculate the counts
variable = 'Age_group'
counts = df[variable].value_counts()

# Calculate the probability of survival
survived = df[df['Survived'] == 1][variable].value_counts().sort_index()
not_survived = df[df['Survived'] == 0][variable].value_counts().sort_index()
survival_prob = survived / (survived + not_survived)

# Create subplots with shared x-axis
fig, ax1 = plt.subplots()   

# Plot the survival probability using scatter plot markers and lines on the left y-axis
ax1.plot(survival_prob.index, survival_prob.values, marker='o', linestyle='-', color='red')
ax1.set_ylabel('Survival Probability', color='black')
ax1.tick_params(axis='y', labelcolor='red')    

# Create a twin y-axis for the left side
ax2 = ax1.twinx()

#Plot the count bars on the right y-axis
ax2.bar(counts.index, counts.values, alpha=0.4, width=0.2, color='royalblue')
ax2.set_ylabel('Counts', color='black')
ax2.tick_params(axis='y', labelcolor='blue')

# Set the x-axis label and title
ax1.set_xlabel(variable)
plt.title('Count and Survival Probability')

# Adjust the layout of subplots
plt.tight_layout()

До този момент извършвахме само почистване на данни и инженеринг на функции. Някои липсващи стойности бяха попълнени със стойности, получени от външни източници, или ние приложихме условна операция на медианата, за да запълним малък брой NaN стойности. Тази операция променя малко набора от данни, но ще считам набора от данни току-що изчистен и проектиран с функции. В колоната „Оцелял“ липсващите стойности съответстват на тестовата част (вижте Kaggle Титаник — Машинно обучение от бедствие). Колоните „Възраст“ и „Възрастова_група“ имат много липсващи стойности и не можем да ги попълним с помощта на проста условна медиана или правило за картографиране. След това ще запазя набора от данни до този момент като „titanic_eng.csv“. Можете да изтеглите този набор от данни от GitHub repo, който създадох за тази статия.

# Plot the missing values matrix
import missingno as msno

dataframe = df

plt.figure(figsize=(10, 6))
msno.matrix(dataframe, color=(0.29803922, 0.44705882, 0.69019608), fontsize=20)
plt.xlabel('Variables')
plt.ylabel('Rows', fontsize=20)
plt.title('Missing Values in Titanic_eng dataset', fontsize=20)
plt.show()

# export the dataset_eng
df.to_csv('titanic_datasets/titanic_eng.csv', index = False)

Проучвателен анализ на данни

Можем да направим проучвателен анализ с наскоро изчистения и проектиран набор от данни за Титаник.

print(df.columns)
Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'SibSp', 'Parch',
       'Ticket', 'Fare', 'Embarked', 'Sex_bool', 'Embarked_code',
       'Ticket_preffix', 'Ticket_number', 'Companions', 'FamilySize', 'Title',
       'noble', 'Fare_level', 'Fare_range', 'Cabin_label', 'Cabin_count',
       'Age_group', 'Title_Master', 'Title_Miss', 'Title_Mr', 'Title_Mrs',
       'Title_noble'],
      dtype='object')


Нека проверим корелациите между променливите.

# print(train_data.corr())
corrMatrix = df[~pd.isna(df["Survived"])].loc[:, ['Pclass', 'Is_male', 'Age_group', 'FamilySize', 'Survived', 'Fare']].corr()
fig, ax = plt.subplots(figsize=(8,7))         # Sample figsize in inches
sns.heatmap(corrMatrix, annot=True, linewidths=.5, ax=ax)

По-долу вграждам приложение Dash с интерактивен график на вероятността и броя на оцелелите.



Пътници с липсваща информация за кабината

Какъв е профилът на пътниците, който няма информация за кабината, или Cabin_label = N. Анализирайки разпределението на характеристиките на пътниците, които са били във всеки етикет на кабината, можем да видим, че профилът на пътниците с липсваща информация за кабината е много близък към профила на всички пътници, независимо в коя кабина се намират. Може да се разбере, че пътниците в групата с Cabin_label = N не са предимно от най-ниския социален статус (Pclass = 3). Има пътници от трите стойности на Pclass с липсващи стойности на кабината. Липсващата информация за кабината не е свързана с даден Pclass, Sex или Age_group, тя се разпределя на случаен принцип. Ето защо е много трудно тази функция, с тази структура, да се използва по смислен начин.

# Filter the DataFrame for passengers with cabin label 'N'
cabin_N_passengers = df[df['Cabin_label'] == 'N']

fig, axes = plt.subplots(2, 4, figsize=(20, 8))

# Plot the distribution of classes
sns.countplot(x='Pclass', data=cabin_N_passengers, ax=axes[0,0], width=0.5)
axes[0,0].set_title('Passenger Class Distribution for Cabin Label N')
# Plot the distribution of sex
sns.countplot(x='Sex', data=cabin_N_passengers, ax=axes[0,1], width=0.3)
axes[0,1].set_title('Sex Distribution for Cabin Label N')
# Plot the distribution of fare
sns.countplot(x='Fare_range', data=cabin_N_passengers, ax=axes[0,2], width=0.5)
axes[0,2].set_title('Fare Distribution for Cabin Label N')
# Plot the distribution of fare
sns.countplot(x='Age_group', data=cabin_N_passengers, ax=axes[0,3], width=0.5)
axes[0,3].set_title('Age_group Distribution for Cabin Label N')
# Plot the distribution of fare
sns.countplot(x='Embarked_code', data=cabin_N_passengers, ax=axes[1,0], width=0.5)
axes[1,0].set_title('Embarked_code Distribution for Cabin Label N')
# Plot the distribution of fare
sns.countplot(x='FamilySize', data=cabin_N_passengers, ax=axes[1,1], width=0.5)
axes[1,1].set_title('Familysize Distribution for Cabin Label N')
# Plot the distribution of fare
sns.countplot(x='Companions', data=cabin_N_passengers, ax=axes[1,2], width=0.5)
axes[1,2].set_title('Companions Distribution for Cabin Label N')
# Plot the distribution of fare
sns.countplot(x='Title', data=cabin_N_passengers, ax=axes[1,3], width=0.5)
axes[1,3].set_title('Title Distribution for Cabin Label N')

# Adjust the spacing between subplots
plt.tight_layout()
# Display the plots
plt.show()

По-долу вграждам приложение Dash с интерактивен график за проверка на профилите на лентата на всички променливи, филтрирани по стойността Cabin_label.



Всички кодове, използвани в тази статия, са в моето GitHub repo.



въпроси? коментари? Чувствайте се свободни да оставите вашите отзиви в секцията за коментари или да се свържете с мен директно от моя имейл: [email protected].

Ако сте харесали тази статия, щракнете върху 👏 🙂.

Ако искаш да ме подкрепиш, купи ми кафе:

Контакт:[email protected],LinkedIn, CV-Lattes, ORCID.