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