Очистка и подготовка данных

# путь к данным из Титаника
#
url = "https://raw.githubusercontent.com/dm-fedorov/pandas_basic/master/%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5%20%D0%B2%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B2%20pandas/data/titanic.csv"
import pandas as pd
#
# перевод данных из .csv в DataFrame
#
df = pd.read_csv(url)
df.head(3)
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
# Удаляем лишние столбцы:
#
df.drop(['PassengerId', 'Name', 'Ticket'],
        axis=1,
        inplace=True)
df.head(3)
Survived Pclass Sex Age SibSp Parch Fare Cabin Embarked
0 0 3 male 22.0 1 0 7.2500 NaN S
1 1 1 female 38.0 1 0 71.2833 C85 C
2 1 3 female 26.0 0 0 7.9250 NaN S
# Округляем стоимость билета до двух знаков после запятой (так красиво)
#
df['Fare'] = round(df['Fare'], 2)
df.head(3)
Survived Pclass Sex Age SibSp Parch Fare Cabin Embarked
0 0 3 male 22.0 1 0 7.25 NaN S
1 1 1 female 38.0 1 0 71.28 C85 C
2 1 3 female 26.0 0 0 7.92 NaN S
# Определяем проблемные столбцы (обратите внимание на большое число пропусков в столбце Age)
#
df.isna().sum()
Survived      0
Pclass        0
Sex           0
Age         177
SibSp         0
Parch         0
Fare          0
Cabin       687
Embarked      2
dtype: int64
# можно настраивать и изменять способ удаления данных, например с помощью параметра thresh=2,
# который оставит строки с более, чем с 2 непустыми значениями
# df.dropna()
#
# https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.dropna.html
# Что делать с пропусками?

# Способ 1: Заменить пропущенные значения на константу
# (в данном случае нам он не подходит)
#
#df['Age'].fillna(25)
# Способ 2: Заменить пропущенные значения на cреднее арифметическее по столбцу
#
df['Age'].fillna(df['Age'].mean())
0      22.000000
1      38.000000
2      26.000000
3      35.000000
4      35.000000
         ...    
886    27.000000
887    19.000000
888    29.699118
889    26.000000
890    32.000000
Name: Age, Length: 891, dtype: float64
# Способ 3: Заменить пропущенные значения на среднее арифметические в зависимости от класса каюты
#
# Вычисляем среднее арифметические в зависимости от класса каюты
#
df.query("Pclass == 1").Age.mean()
38.233440860215055
df.sample(15)
Survived Pclass Sex Age SibSp Parch Fare Cabin Embarked
56 1 2 female 21.0 0 0 10.50 NaN S
30 0 1 male 40.0 0 0 27.72 NaN C
696 0 3 male 44.0 0 0 8.05 NaN S
173 0 3 male 21.0 0 0 7.92 NaN S
144 0 2 male 18.0 0 0 11.50 NaN S
789 0 1 male 46.0 0 0 79.20 B82 B84 C
347 1 3 female NaN 1 0 16.10 NaN S
365 0 3 male 30.0 0 0 7.25 NaN S
384 0 3 male NaN 0 0 7.90 NaN S
659 0 1 male 58.0 0 2 113.28 D48 C
520 1 1 female 30.0 0 0 93.50 B73 S
315 1 3 female 26.0 0 0 7.85 NaN S
795 0 2 male 39.0 0 0 13.00 NaN S
668 0 3 male 43.0 0 0 8.05 NaN S
55 1 1 male NaN 0 0 35.50 C52 S
# Пишем функцию, которая принимает на входе строку и просматривает  необходимые столбцы
#
def fill_age(row):
    # проверка, что значение пропущенное
    if pd.isnull(row['Age']):
    # тогда смотрим на класс каюты
        if row['Pclass'] == 1:
    # возвращаем среднее значение для первого класса
            return df.query("Pclass == 1").Age.mean()
        elif row['Pclass'] == 2:
            return df.query("Pclass == 2").Age.mean()
        elif row['Pclass'] == 3:
            return df.query("Pclass == 3").Age.mean()
    # возвращаем значение возраста, если оно было задано
    return row['Age']
# Самый важный момент - применение функции apply, которая
# заполняет пропущенные значения указанными в функции fill_age:
#
df.apply(fill_age,
         axis="columns")
0      22.00000
1      38.00000
2      26.00000
3      35.00000
4      35.00000
         ...   
886    27.00000
887    19.00000
888    25.14062
889    26.00000
890    32.00000
Length: 891, dtype: float64
# https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.interpolate.html
#
# https://stackoverflow.com/questions/19966018/pandas-filling-missing-values-by-mean-in-each-group
# Способ 4. Эквивалентен способу 3, но менее очевиден и более короткий
#
df \
    .groupby('Pclass')['Age'] \
    .apply(lambda x:x.fillna(x.mean()))
Pclass     
1       1      38.00000
        3      35.00000
        6      54.00000
        11     58.00000
        23     28.00000
                 ...   
3       882    22.00000
        884    25.00000
        885    39.00000
        888    25.14062
        890    32.00000
Name: Age, Length: 891, dtype: float64
# Проверяем эквивалентность способовов 3 и 4
#
(df.apply(fill_age, axis=1)) \
            .equals(df.groupby('Pclass')['Age'] \
            .apply(lambda x:x.fillna(x.mean())))
False

Создаем новый столбец с информацией о том, был ли пассажир на борту один или с родственниками

Столбец должен содержать значение “alone”, если он был на борту один (без супруга/супруги, братьев, сестер, детей и родителей) и значение “not alone”, если пассажир путешествовал с кем-то из родственников.

  • SibSp - Количество братьев и сестер / супругов на борту
  • Parch - число родителей / детей на борту
# Способ 1: с помощью функции и apply
#
def alone_check(row):
    if row['SibSp'] > 0 or row['Parch'] > 0:
        return 'not_alone'
    return 'alone'

df['Alone'] = df.apply(alone_check, axis=1)
df
Survived Pclass Sex Age SibSp Parch Fare Cabin Embarked Alone
0 0 3 male 22.0 1 0 7.25 NaN S not_alone
1 1 1 female 38.0 1 0 71.28 C85 C not_alone
2 1 3 female 26.0 0 0 7.92 NaN S alone
3 1 1 female 35.0 1 0 53.10 C123 S not_alone
4 0 3 male 35.0 0 0 8.05 NaN S alone
... ... ... ... ... ... ... ... ... ... ...
886 0 2 male 27.0 0 0 13.00 NaN S alone
887 1 1 female 19.0 0 0 30.00 B42 S alone
888 0 3 female NaN 1 2 23.45 NaN S not_alone
889 1 1 male 26.0 0 0 30.00 C148 C alone
890 0 3 male 32.0 0 0 7.75 NaN Q alone

891 rows × 10 columns

# Способ 2: с помощью lambda-функции
#
df['Alone'] = df.apply(lambda x: 'not_alone' if x['SibSp'] or x['Parch'] > 0 \
                       else 'alone', axis=1)
df
Survived Pclass Sex Age SibSp Parch Fare Cabin Embarked Alone
0 0 3 male 22.0 1 0 7.25 NaN S not_alone
1 1 1 female 38.0 1 0 71.28 C85 C not_alone
2 1 3 female 26.0 0 0 7.92 NaN S alone
3 1 1 female 35.0 1 0 53.10 C123 S not_alone
4 0 3 male 35.0 0 0 8.05 NaN S alone
... ... ... ... ... ... ... ... ... ... ...
886 0 2 male 27.0 0 0 13.00 NaN S alone
887 1 1 female 19.0 0 0 30.00 B42 S alone
888 0 3 female NaN 1 2 23.45 NaN S not_alone
889 1 1 male 26.0 0 0 30.00 C148 C alone
890 0 3 male 32.0 0 0 7.75 NaN Q alone

891 rows × 10 columns