<jupyter_start><jupyter_text>Импорт датасета<jupyter_code>raw_dataset = pd.read_csv("dataset.csv", sep=';', decimal=',', index_col=0)

pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', 100)<jupyter_output><empty_output><jupyter_text>Шапка<jupyter_code>raw_dataset.head()<jupyter_output><empty_output><jupyter_text>Характеристики датасета  Размер<jupyter_code>raw_dataset.shape<jupyter_output><empty_output><jupyter_text>Признаки<jupyter_code>raw_dataset.columns<jupyter_output><empty_output><jupyter_text>Описание признаков<jupyter_code>def make_description(df):
    """функция возвращает описание признаков: количество пропусков, тип, количество уникальных значений"""
    description = pd.concat([df.isna().sum(), df.dtypes, 
                             df.nunique(), df.nunique()/df.shape[0]], axis=1)
    description.rename(columns={0: "num of NaN", 1: "dtypes", 2: "nunique", 3: "% unique"}, inplace=True)
    return description
make_description(raw_dataset)<jupyter_output><empty_output><jupyter_text>Категориальные и вещественные признаки, целевые признаки<jupyter_code># Категориальные признаки
def list_categorical_features(df):
    """Возвращает список категориальных признаков"""
    # Get  columns whose data type is object i.e. string
    filtered_features = df.dtypes[df.dtypes == np.object]
    # list of columns whose data type is object i.e. string
    num_features_list = list(filtered_features.index)
    return num_features_list


categorical_features = list_categorical_features(raw_dataset)


# Вещественные признаки
def list_numerical_features(df):
    """Возвращает список категориальных признаков"""
    # Get  columns whose data type is object i.e. string
    filtered_features = df.dtypes[df.dtypes == np.float]
    # list of columns whose data type is object i.e. string
    num_features_list = list(filtered_features.index)
    return num_features_list
    

numerical_features = list_numerical_features(raw_dataset)


# целевые признаки (принадлежат к вещественному типу)
target_features = ['химшлак последний Al2O3', 'химшлак последний CaO',
       'химшлак последний FeO', 'химшлак последний MgO',
       'химшлак последний MnO', 'химшлак последний R',
       'химшлак последний SiO2']

print("{} категориальных признаков".format(len(categorical_features)))
print(categorical_features)
print("\n{} вещественных признаков".format(len(numerical_features)))
print(numerical_features)
print("\n{} целевых признаков".format(len(target_features)))
print(target_features)<jupyter_output>4 категориальных признаков
['nplv', 'DT', 'МАРКА', 'ПРОФИЛЬ']

80 вещественных признаков
['t вып-обр', 't обработка', 't под током', 't продувка', 'ПСН гр.', 'чист расход C', 'чист расход Cr', 'чист расход Mn', 'чист расход Si', 'чист расход V', 'температура первая', 'температура последняя', 'Ar (интенс.)', 'N2 (интенс.)', 'эл. энергия (интенс.)', 'произв жидкая сталь', 'произв количество обработок', 'произв  количество плавок', 'произв количество плавок (цел)', 'расход газ Ar', 'расход газ  N2', 'расход C пров.', 'сыпуч известь РП', 'сыпуч кварцит', 'сыпуч кокс пыль УСТК', 'сыпуч  кокс. мелочь (сух.)', 'сыпуч  кокс. мелочь КМ1', 'сыпуч  шпат плав.', 'ферспл CaC2', 'ферспл FeMo', 'ферспл FeSi-75', 'ферспл FeV азот.', 'ферспл  FeV-80', 'ферспл  Mn5Si65Al0.5', 'ферспл  Ni H1 пласт.', 'ферспл SiMn18', 'ферспл  ферванит', 'ферспл  фх850А', 'эл. энергия', 'химсталь первый Al_1', 'химсталь первый C_1', 'химсталь первый Cr_1', 'химсталь первый Cu_1', 'химсталь первый Mn_1', 'химсталь первый M[...]<jupyter_text>Обработка датасета  Удаление ненужных признаковПризнаки "nplv" и "DT", очевидно, не несут в себе полезной информации - их можно удалить.<jupyter_code>dataset = raw_dataset.drop(["nplv", "DT"], axis=1)<jupyter_output><empty_output><jupyter_text>Также стоит отметить, что есть признаки со всего 1 уникальным значением помимо NaN - очевидно, что они не несут в себе полезную информацию и их можно удалить.<jupyter_code>def list_features_with_single_value(df):
    features_list = []
    for feature in df.columns:
        if df[feature].nunique() == 1:
            features_list.append(feature)
    return features_list


features_with_single_value = list_features_with_single_value(dataset)

print("Список признаков с единственным уникальным значением:")
print(features_with_single_value)

dataset = dataset.drop(features_with_single_value, axis=1)<jupyter_output>Список признаков с единственным уникальным значением:
['произв  количество плавок', 'произв количество плавок (цел)', 'сыпуч кварцит']<jupyter_text>Удаление столбцов и строк, где много пропусков Удалим столбцы, в которых >33% значений NaN:<jupyter_code>def list_features_with_lots_of_nan(df):
    features_list = []
    for feature in df.columns:
        if feature in target_features: # не рассматриваем целевые признаки
            continue
        num_of_nan = dataset[feature].isna().sum()
        if num_of_nan > dataset.shape[0] / 3:
            features_list.append(feature)
    return features_list
    

features_with_lots_of_nan = list_features_with_lots_of_nan(dataset)

print("Столбцы с большим количеством пропусков:")
print(features_with_lots_of_nan)

dataset = dataset.drop(features_with_lots_of_nan, axis=1)<jupyter_output>Столбцы с большим количеством пропусков:
['N2 (интенс.)', 'расход газ  N2', 'расход C пров.', 'сыпуч  кокс. мелочь (сух.)', 'ферспл FeMo', 'ферспл FeSi-75', 'ферспл FeV азот.', 'ферспл  Ni H1 пласт.', 'ферспл  ферванит']<jupyter_text>Удалим строки, в которых >33% NaN:<jupyter_code>def list_rows_with_lots_of_nan(df):
    rows_list = []
    numbers_of_nan = df.isna().sum(axis=1)
    for index, count in enumerate(numbers_of_nan):
        if count > df.shape[1] / 3:
            rows_list.append(index)
    return rows_list


rows_with_lots_of_nan = list_rows_with_lots_of_nan(dataset)
print("Строк с большим количеством пропусков: {}".format(len(rows_with_lots_of_nan)))

dataset = dataset.drop(rows_with_lots_of_nan, axis=0)<jupyter_output>Строк с большим количеством пропусков: 598<jupyter_text>Результат<jupyter_code>print("Размер датасета на текущий момент: {}".format(dataset.shape))
make_description(dataset)<jupyter_output><empty_output><jupyter_text>Работа с категориальными признаками<jupyter_code>categorical_features = list_categorical_features(dataset)
categorical_features<jupyter_output><empty_output><jupyter_text>Удаление редких признаковПризнаки, которые встречаются редко (<20%), можно объединить в одну категорию "rare"<jupyter_code>def find_frequent_labels(df, var, rare_perc):
    """Ищет часто встречающиеся значения"""
    df = df.copy()
    
    tmp = df.groupby(var)[var].count() / len(df)
    
    return tmp[tmp > rare_perc].index


for feature in categorical_features:
    frequent_labels = find_frequent_labels(dataset, feature, 0.2)
    print(feature)
    print(frequent_labels)
    
    # заменяет редкие значения признаков на строку "rare"
    dataset[feature] = np.where(dataset[feature].isin(frequent_labels), dataset[feature], 'rare')
dataset["МАРКА"].value_counts()
dataset["ПРОФИЛЬ"].value_counts()<jupyter_output><empty_output><jupyter_text>Кодирование методом OHE<jupyter_code>dummies = pd.get_dummies(dataset[categorical_features])

dataset = pd.concat([dummies, dataset.drop(categorical_features, axis=1)], axis=1)<jupyter_output><empty_output><jupyter_text>Работа с вещественными признаками  Заполнение NaNПосмотрим текущее количество пропусков:<jupyter_code>numerical_features = list_numerical_features(dataset.drop(target_features, axis=1))

make_description(dataset[numerical_features]).sort_values('num of NaN', ascending=False)<jupyter_output><empty_output><jupyter_text>Наибольший интерес представляют признаки, где пропущено >10% значений, посмотрим на их распределения:<jupyter_code>top_missing_features = ['химшлак первый SiO2_1', 'химшлак первый Al2O3_1',
                        'сыпуч известь РП', 'ПСН гр.']

for feature in top_missing_features:
    sns.displot(dataset[feature])
    print(feature)
    print("Min: {}, max: {}".format(dataset[feature].min(), dataset[feature].max()))
    print("Mean: {}, median: {}, mode: {}, std: {}".format(dataset[feature].mean(),
                                                           dataset[feature].median(),
                                                           dataset[feature].mode()[0],
                                                           dataset[feature].std()))
    print("Number of NaN: {}".format(dataset[feature].isna().sum()))
    print()<jupyter_output>химшлак первый SiO2_1
Min: 8.2, max: 32.8
Mean: 22.70491353859137, median: 22.7, mode: 21.7, std: 2.3938346032440148
Number of NaN: 1701

химшлак первый Al2O3_1
Min: 1.8, max: 18.5
Mean: 4.113897089835528, median: 4.0, mode: 3.7, std: 0.9282205411870541
Number of NaN: 1701

сыпуч известь РП
Min: 0.003, max: 0.73
Mean: 0.1321788260167713, median: 0.11599999999999999, mode: 0.052000000000000005, std: 0.07976305924740652
Number of NaN: 1673

ПСН гр.
Min: 1.0, max: 6.702207899999999
Mean: 3.004414482568112, median: 3.0, mode: 2.0, std: 0.7385729350651123
Number of NaN: 1194<jupyter_text>Заполнение средними<jupyter_code>def fill_na_with_mean(df):
    df = df.copy()
    for column in df.columns:
        df[column].fillna(df[column].mean(), inplace=True)
    return df


dataset[numerical_features] = fill_na_with_mean(dataset[numerical_features])

print("осталось {} пустых значений".format(dataset[numerical_features].isna().sum().sum()))<jupyter_output>осталось 0 пустых значений<jupyter_text>Удаление выбросовУдалим выбросы с помощью Isolation Forest<jupyter_code>numerical_features = list_numerical_features(dataset.drop(target_features, axis=1))

isfor = IsolationForest()
isfor.fit(dataset[numerical_features])
isfor_pred = isfor.predict(dataset[numerical_features])

dataset = dataset[isfor_pred==1]
print("Размер датасета на текущий момент: {}".format(dataset.shape))<jupyter_output>Размер датасета на текущий момент: (6237, 73)<jupyter_text>Количество уникальных значений и вариацияПосмотрим на вещественные признаки с точки зрения количества уникальных значений:<jupyter_code>make_description(dataset[numerical_features]).sort_values('nunique')<jupyter_output><empty_output><jupyter_text>5 признаков имеют совсем небольшое число уникальных значений, рассмотрим их:<jupyter_code>top_low_unique_features = ['произв количество обработок', 
                           'химсталь последний Ti',
                           'химсталь первый Al_1',
                           'химсталь первый Ti_1',
                           'ферспл CaC2']

for feature in top_low_unique_features:
    print(dataset[feature].value_counts())
    print()<jupyter_output>1.0    6206
2.0      28
3.0       3
Name: произв количество обработок, dtype: int64

0.003    4723
0.002     935
0.004     527
0.001      43
0.005       9
Name: химсталь последний Ti, dtype: int64

0.002    4579
0.001    1488
0.003     162
0.004       8
Name: химсталь первый Al_1, dtype: int64

0.002    3678
0.001    2299
0.003     250
0.004       7
0.007       1
0.005       1
0.000       1
Name: <NAME> Ti_1, dtype: int64

0.020000    6011
0.020086     184
0.030000      22
0.010000       8
0.020000       6
0.050000       4
0.040000       2
Name: <NAME>aC2, dtype: int64<jupyter_text>Дальнейшие действия зависят от интерпретации полученного результата. Пока что попробуем просто удалить столбец, т.к. почти все объекты принадлежат классу "1", количество объектов других классов <1%.<jupyter_code>dataset = dataset.drop(['произв количество обработок'], axis=1)<jupyter_output><empty_output><jupyter_text>Итог<jupyter_code>dataset.shape
make_description(dataset[target_features])<jupyter_output><empty_output><jupyter_text>ВыводыИсходный размер датасета - 7041 объект и 84 признака, среди которых 7 целевых признаков.В процессе анализа и предобработки были выполнены следующие действия:- Удалены признаки "nplv" и "DT", как не несущие в себе какой-либо полезной информации- Удалены признаки, в которых >33% пропусков- Удалены объекты, у которых пропущено >33% признаков- У категориальных признаков значения, число которых <20% были объединены в одну категорию rare:  - У признака "МАРКА" осталось 3 значения  - У признака "ПРОФИЛЬ" осталось 2 значения- Категориальные признаки были закодированы методом OHE- Оставшиеся NaN в вещественных признаках были заполнены средними- Выбросы были удалены с помощью Isolation Forest- Был удален признак 'произв количество обработок', т.к. его можно было отнести к категориальному и большая часть значений принадлежала одному классуВ результате был получен датасет размером (6237, 72), в котором отсутствуют пропуски в независимых признаках. В зависимых (целевых) признаках объекты с пропусками будут удаляться на следующем этапе отдельно для каждой модели.  Выгрузка выборки<jupyter_code>dataset.to_csv('preprocessed_data.csv')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1069572636314279, 'fraction_numerical': 0.04250368416970449, 'mean_word_length': 4.972209356183418, 'pattern_counts': {'":': 0, '<': 90, '<?xml version=': 0, '>': 95, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 3, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '23', 'dir': 'jupyter-structured-clean-dedup', 'id': '163855', 'license': 'MIT', 'n_tokens_mistral': 5254, 'n_tokens_neox': 5456, 'n_words': 1031, 'path': 'Melekhin/1_Preprocessing_JuniorReport.ipynb', 'repo_name': 'comptech-winter-school/steel-alloying', 'size': '12904'}