Набор данных Titanic — один из лучших наборов данных для практики очистки данных и разработки функций. Это простой набор данных с очень богатой историей. Процесс очистки данных очень важен и является одним из самых трудоемких в анализе данных. Этот набор данных изначально был получен с конкурса Kaggle (Титаник — машинное обучение от катастрофы). Здесь я продемонстрирую некоторые методы очистки набора данных Титаника и выполнения проектирования признаков с целью применить его к машинному обучению с помощью древовидных моделей. Поскольку этапы обработки данных длинные, я расскажу о применении машинного обучения к этой проблеме в другой статье (ссылку добавлю сюда). Легче начать изучение машинного обучения с чистым набором данных, готовым к входным правилам большинства моделей. В реальном мире нам приходится выполнять большую обработку данных, прежде чем применять алгоритмы машинного обучения, и обычно обработка данных занимает больше времени, чем часть моделирования машинного обучения. Разработка признаков может улучшить набор данных, выявив новые переменные, которые могут помочь в исследовательском анализе, понять отношения между переменными и повлиять на окончательные прогнозы с помощью любого алгоритма 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».
- Сгруппируйте повторяющиеся значения Ticket и подсчитайте количество людей, которые путешествовали вместе, включая друзей, горничных и нянь, и создайте переменную Companies.
- Создайте переменную «FamilySize», объединив переменные «SibSp» и «Parch».
- Извлеките переменную «Название» из переменной «Имя» и классифицируйте.
- Сопоставьте «Fare» с «Fare_level»: {1,2,3,4,5,6,7,8,9,10}
- Самая грязная переменная «Cabin» -> разбить ее на [‘Cabin_label’, ‘Cabin_n’, ‘n_of_Cabins’].
- Сопоставьте переменную «Возраст» с «Возрастной_группой».
Секс
Закодируйте переменную Пол в Is_male, 1 для мужчины, 0 для женщины. Этот шаг необходим, потому что мы планируем использовать алгоритмы машинного обучения на основе дерева решений 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(),:]
Мы видим, что оба пассажира имеют общие значения переменных, Пол = женский, Pclass = 1, они были в одной и той же кабине = B28, и номер билета один и тот же Ticket = 113572. Как вы думаете, это совпадение? Думаю, нет. Миссис Стоун села на Титаник в Саутгемптоне 10 апреля 1912 года и путешествовала первым классом со своей горничной Амели Икард. Она занимала каюту Б-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)
Билет
Разделите «Билет» на переменные «Префикс_Билета» и «Номер_Билета».
# 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» содержит некоторые опечатки и, похоже, связан с информацией «Отправлено». Большинство значений пустые, и, возможно, не стоит анализировать вероятность выживания с помощью модели ML.
Компаньоны
Более того, мы уже заметили, что в переменной Ticket_number есть дублирующиеся записи. Некоторые пассажиры делят номер билета, потому что они вместе. У них могут быть отношения с сотрудником или службой, такие как горничная или няня, или они могут быть близкими друзьями. Не все эти отношения были зафиксированы в переменных SibSp и Parch. См., например, случай с миссис Стоун, которая путешествовала со своей служанкой Амели Икард, и обе выжили. (Энциклопедия Титаника).
Мы можем извлечь неявную информацию из повторяющихся значений в переменной «Билет» и создать новую переменную для ее описания. Здесь мы создаем новую переменную Companies, описывающую количество спутников или попутчиков для каждого человека. Возможно, это число оказывает некоторое влияние на вероятность выживания, когда мы используем алгоритм 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» для описания структуры семьи на борту для каждого пассажира. Мы создаем коррелированную переменную, почему? Мы можем исследовать частоту «выживших» в зависимости от общего числа членов семьи. Более того, древовидные модели имеют внутренний алгоритм выбора признаков, который выбирает наиболее релевантные переменные для предсказания целевой переменной. Возможно, переменная «Численность семьи» может быть важнее, чем переменные «СибСп» и «Парч».
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
Новая переменная «Заголовок»
Теперь мы извлечем информацию о заголовке пассажира из переменной Имя. Мы можем создать функцию Заголовок, извлекая из столбца Имя заголовки: Мистер Мисс, Миссис Доктор и другие. (см. также Сина — лучший рабочий классификатор Тинатика)
# 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
Плата за проезд
Мы можем заменить отсутствующее значение «Тариф» (есть только одно) медианным значением. Справедливости ради мы заменим отсутствующее значение тарифа медианой значений тарифа для пола = мужчины и 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
Мы можем классифицировать Тарифы по группам с одинаковыми диапазонами или уровнями Тарифов. Если мы классифицируем его с помощью интерквартиля, распределение бинов возвращает странное представление, потому что переменная «Плата за проезд» не является непрерывной.
# 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».
- Можем поискать дублирующиеся значения переменной «Билет», при которых наблюдается «Каюта» у одного из попутчиков.
- Мы можем использовать контролируемый алгоритм стратегии вменения на основе машинного обучения, такой как 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 пропущенных значения). Можно использовать вменение правила регрессии с использованием аналогичных стратегий, перечисленных в предыдущем разделе. Здесь мы создаем новую переменную «Возрастная_группа», разделив интервал значений на почти равные интервалы. Эта новая переменная может быть гораздо более информативной, чем точные значения «Возраст».
Женщины и дети в первую очередь!
Переменная Возрастная_группа категоризирована со значимыми возрастными диапазонами для каждой группы до 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 — Machine Learning from Disaster). В столбцах Возраст и Возрастная_группа много пропущенных значений, и мы не можем заполнить их, используя простую условную медиану или правило сопоставления. Затем я сохраню набор данных до этого момента как titanic_eng.csv. Вы можете скачать этот набор данных из репозитория GitHub, который я создал для этой статьи.
# 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)
Исследовательский анализ данных
Мы можем провести некоторый исследовательский анализ с помощью недавно очищенного и модифицированного набора данных Titanic.
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 с отсутствующими значениями Cabin. Отсутствующая информация о салоне не связана с данным P-классом, полом или возрастной группой, она распределяется случайным образом. Вот почему очень сложно осмысленно использовать эту функцию с такой структурой.
# 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.
Вопросы? Комментарии? Вы можете оставить свой отзыв в разделе комментариев или связаться со мной напрямую по электронной почте: [email protected].
Если вам понравилась эта статья, нажмите 👏 🙂.
Если вы хотите поддержать меня, купите мне кофе:
Контакты:[email protected],LinkedIn, CV-Lattes, ORCID.