<jupyter_start><jupyter_text>Kaggle competition: Shelter Animal Outcomes https://www.kaggle.com/c/shelter-animal-outcomes/ **Дано:** данные о кошках и собаках, поступивших в приют**Найти:** что с ними станет? Возьмут в приют / вернётся хозяин / ...<jupyter_code>import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
%matplotlib inline
import seaborn as sns<jupyter_output><empty_output><jupyter_text>грузим всё, что есть<jupyter_code>train = pd.read_csv('data/shelter/train.csv')
test = pd.read_csv('data/shelter/test.csv')
sample_submission = pd.read_csv('data/shelter/sample_submission.csv')<jupyter_output><empty_output><jupyter_text>и смотрим на это<jupyter_code>train.head()
test.head()
sample_submission.head()<jupyter_output><empty_output><jupyter_text>все фичи надо варить, причём одинаково в train и test, лучше их сразу соединим и разделим при этом флагом is_test<jupyter_code>Xtrain = train.drop(['AnimalID', 'OutcomeType', 'OutcomeSubtype'], axis=1)
Xtest = test.drop(['ID'], axis=1)
Xtrain['is_test'] = False
Xtest['is_test'] = True
X = pd.concat([Xtrain, Xtest], axis=0)
X.index = range(len(X))
X.columns = X.columns.str.lower()
X.head()
X.shape
X.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 38185 entries, 0 to 38184
Data columns (total 8 columns):
name              27269 non-null object
datetime          38185 non-null object
animaltype        38185 non-null object
sexuponoutcome    38184 non-null object
ageuponoutcome    38161 non-null object
breed             38185 non-null object
color             38185 non-null object
is_test           38185 non-null bool
dtypes: bool(1), object(7)
memory usage: 2.1+ MB<jupyter_text>Также нам необходимо закодировать значения целевой переменной<jupyter_code>from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
y = le.fit_transform(train.OutcomeType)<jupyter_output><empty_output><jupyter_text>Варим фичи будем в конце матрицы наращивать числовые фичи, не удаляя оригинальные: вдруг что всплывёт **имя** превращаем в:* наличие имени* длину имени* частоту имени<jupyter_code>X['has_name'] = X.name.isnull().astype(int)

X['name_len'] = X.name.str.len()
X.name_len.fillna(X.name_len.median(), inplace=True)

X['name_words_num'] = X.name.str.split().str.len()
X.name_words_num.fillna(X.name_words_num.median(), inplace=True)

names_freq = X.name.value_counts().to_dict()
X['name_freq'] = X.name.apply(lambda x: names_freq.get(x))<jupyter_output><empty_output><jupyter_text>Даты: переводим строки в даты<jupyter_code>X.datetime = pd.to_datetime(X.datetime)
X.datetime.hist()
X['year'] = X.datetime.apply(lambda x: x.year)
X['month'] = X.datetime.apply(lambda x: x.month)
X['day'] = X.datetime.apply(lambda x: x.day)
X['hour'] = X.datetime.apply(lambda x: x.hour + x.minute/60)
X['weekday'] = X.datetime.apply(lambda x: x.weekday())<jupyter_output><empty_output><jupyter_text>С типом животного тут совсем просто<jupyter_code>X.animaltype.value_counts()
X['is_dog'] = (X.animaltype=='Dog').astype(int)<jupyter_output><empty_output><jupyter_text>С полом сложнее..* Neutered, Spayed - стерилизованные* Intact - нетронутые<jupyter_code>X.sexuponoutcome.value_counts()
X.sexuponoutcome.fillna('Unknown', inplace=True)
X['sterilization'] = X.sexuponoutcome.apply(lambda x: x.split()[0])
X.sterilization = X.sterilization.replace({'Neutered': 'Sterilized', 'Spayed': 'Sterilized'})
X['sex'] = X.sexuponoutcome.apply(lambda x: x.split()[-1])
from sklearn import preprocessing
le_sterilization = preprocessing.LabelEncoder()
le_sex = preprocessing.LabelEncoder()
X.sterilization = le_sterilization.fit_transform(X.sterilization)
X.sex = le_sex.fit_transform(X.sex)<jupyter_output><empty_output><jupyter_text>почему мы не вставили сюда сразу числовые фичи? потому что это категориальные фичи, у них по 3 значения<jupyter_code>X.ageuponoutcome.value_counts()[:30]
X.ageuponoutcome.str.split().str[1].value_counts()
X.ageuponoutcome.str.split().str[1].str.rstrip('s').value_counts()
t_dig = X.ageuponoutcome.str.split().str[0].fillna(0).astype(int)
t_int = X.ageuponoutcome.str.split().str[1].str.rstrip('s').replace({'year': 365, 'month': 365/12, 'week':7, 'day':1}).fillna(0)
X["years"] = t_dig*t_int/365<jupyter_output><empty_output><jupyter_text>порода достаточно разнообразна и содержит в себе также некое поле "Mix" и краткошёрстность животного<jupyter_code>X.breed.value_counts()[:10]<jupyter_output><empty_output><jupyter_text>Сегодня сделаем просто: соединим все описания в один большой текст и посчитаем в нём вхождение каждого слова. Флаги наличия самых популярных и включим как фичи<jupyter_code>from collections import Counter
one_big_text = " ".join(X.breed)
words = one_big_text.replace('/',' / ').split()
most_common = Counter(words).most_common()
most_common[:20]<jupyter_output><empty_output><jupyter_text>Можно было бы просто вставить флаги вхождения первых, скажем, 4 слов. Но давайте посмотрим, а насколько они важны? Составим матрицу, состоящую только из вхождения первых N слов, обучим на них дерево и проверим важность фичей<jupyter_code>Xbreed = pd.DataFrame()
for col, num in most_common[:10]:
    Xbreed[col] = X[~X.is_test].breed.str.contains(col).astype(int)
from sklearn.tree import DecisionTreeClassifier
clf = DecisionTreeClassifier(max_depth=4)
clf.fit(Xbreed, train.OutcomeType)
clf.feature_importances_
Xbreed.columns<jupyter_output><empty_output><jupyter_text>В принципе, оказалось, что влияет по большей части только **Domestic**. Но включим сюда на всякий случай ещё один признак<jupyter_code>X['is_domestic'] = X.breed.str.contains('Domestic').astype(int)
X['is_mix'] = X.breed.str.contains('Mix').astype(int)<jupyter_output><empty_output><jupyter_text>последнее! цвет. Выглядит похоже на проблему породы, повторим те же операции<jupyter_code>X.color.value_counts()[:10]
one_big_text = " ".join(X.color)
words = one_big_text.replace('/',' / ').split()
most_common = Counter(words).most_common()
most_common[:20]
Xcolor = pd.DataFrame()
for col, num in most_common[:10]:
    Xcolor[col] = X[~X.is_test].color.str.contains(col).astype(int)
clf.fit(Xcolor, train.OutcomeType)
clf.feature_importances_
Xcolor.columns<jupyter_output><empty_output><jupyter_text>Здесь важность менее сосредоточена, возьмём первые 5 фичей. Самая важная, Tabby - это полосатость<jupyter_code>X['is_color_tabby'] = X.color.str.contains('Tabby').astype(int)
X['is_color_mix'] = X.color.str.contains('/').astype(int)
X['is_color_white'] = X.color.str.contains('White').astype(int)
X['is_color_black'] = X.color.str.contains('Black').astype(int)
X['is_color_brown'] = X.color.str.contains('Brown').astype(int)<jupyter_output><empty_output><jupyter_text>Осталось проверить итоговую таблицу на пропуски<jupyter_code>X.isnull().sum()
X.name_freq.fillna(X.name_freq.median(), inplace=True)<jupyter_output><empty_output><jupyter_text>Разделим обратно на обучающую и тестовую выборки, дропнув при этом все лишние столбцы, которые были изначально<jupyter_code>Xtrain_prep = X[~X.is_test].drop(Xtrain.columns.str.lower(), axis=1)
Xtest_prep = X[X.is_test].drop(Xtrain.columns.str.lower(), axis=1)
Xtrain_prep.shape<jupyter_output><empty_output><jupyter_text>-----  Всё<jupyter_code>le_y = preprocessing.LabelEncoder()
y = le_y.fit_transform(y)<jupyter_output><empty_output><jupyter_text>Всё, у нас есть Xtrain, Xtest, ytrain. Осталось получить ytrain.Обучим решающее дерево, причём применим кросс-валидацию для подбора гиперпараметра глубины дерева Обращаю внимание, что мы в явном виде указали вид функционала качества: scoring='neg_log_loss'. Выбрана именно эта функция, так как именно она будет оценивать качество на Kaggle<jupyter_code>from sklearn.model_selection import GridSearchCV
depths = np.arange(1,10)
#features_num = np.arange(5,15)
grid = {'max_depth': depths}#, 'max_features': features_num}
gridsearch = GridSearchCV(DecisionTreeClassifier(), grid, scoring='neg_log_loss', cv=5)
%%time
gridsearch.fit(Xtrain_prep, y)
sorted(gridsearch.grid_scores_, key = lambda x: -x.mean_validation_score)
scores = [-x.mean_validation_score for x in gridsearch.grid_scores_]
plt.plot(depths, scores)
plt.scatter(depths, scores)
best_point = np.argmin(scores)
plt.scatter(depths[best_point], scores[best_point], c='g', s=100)<jupyter_output>/Users/kuznetsovn/anaconda/lib/python3.6/site-packages/sklearn/model_selection/_search.py:667: DeprecationWarning: The grid_scores_ attribute was deprecated in version 0.18 in favor of the more elaborate cv_results_ attribute. The grid_scores_ attribute will not be available from 0.20
  DeprecationWarning)<jupyter_text>Так, отлично, спасли себя от переобучения (правда, в Grid можно было указать и другие параметры и найти более оптимальную точку).Фиксируем max_depth=5<jupyter_code>clf_final = DecisionTreeClassifier(max_depth=5)
clf_final.fit(Xtrain_prep, y)<jupyter_output><empty_output><jupyter_text>Делаем предсказания<jupyter_code>y_pred_proba = clf_final.predict_proba(Xtest_prep)
y_pred = clf_final.predict(Xtest_prep)<jupyter_output><empty_output><jupyter_text>Формируем сабмит<jupyter_code>submit = pd.DataFrame(y_pred_proba, columns=sample_submission.columns[1:])
submit['ID'] = sample_submission.ID
submit = submit[[submit.columns[-1]]+list(submit.columns[:-1])]
submit.to_csv('data/shelter/res/submit.csv', index=False)<jupyter_output><empty_output><jupyter_text>Что оказалось под капотом?<jupyter_code>from sklearn.tree import export_graphviz

def get_tree_dot_view(clf, feature_names=None, class_names=None):
    print(export_graphviz(clf, out_file=None, filled=True, feature_names=feature_names, class_names=class_names))<jupyter_output><empty_output><jupyter_text>http://www.webgraphviz.com<jupyter_code>pd.Series(le.inverse_transform(y)).value_counts().sort_index()
get_tree_dot_view(clf_final, list(Xtrain_prep.columns), list(le.classes_))<jupyter_output>digraph Tree {
node [shape=box, style="filled", color="black"] ;
0 [label="sterilization <= 0.5\ngini = 0.6779\nsamples = 26729\nvalue = [10769, 197, 1555, 4786, 9422]\nclass = Adoption", fillcolor="#e5813914"] ;
1 [label="has_name <= 0.5\ngini = 0.4874\nsamples = 7036\nvalue = [361, 135, 878, 778, 4884]\nclass = Transfer", fillcolor="#e539c0a6"] ;
0 -> 1 [labeldistance=2.5, labelangle=45, headlabel="True"] ;
2 [label="years <= 0.5417\ngini = 0.6361\nsamples = 2988\nvalue = [348, 50, 283, 715, 1592]\nclass = Transfer", fillcolor="#e539c062"] ;
1 -> 2 ;
3 [label="hour <= 16.4583\ngini = 0.5135\nsamples = 1323\nvalue = [275, 44, 57, 72, 875]\nclass = Transfer", fillcolor="#e539c092"] ;
2 -> 3 ;
4 [label="hour <= 11.225\ngini = 0.4388\nsamples = 1018\nvalue = [152, 39, 39, 44, 744]\nclass = Transfer", fillcolor="#e539c0ae"] ;
3 -> 4 ;
5 [label="gini = 0.673\nsamples = 115\nvalue = [16, 22, 14, 5, 58]\nclass = Transfer", fillcolor="#e539c063"] ;
4 -> 5 ;
6 [label="gini = 0.3972\nsamples = [...]<jupyter_text>Сохраним для следующего ноутбука<jupyter_code>Xtrain_prep.to_pickle('data/shelter/res/xtrain.pkl')
Xtest_prep.to_pickle('data/shelter/res/xtest.pkl')<jupyter_output><empty_output><jupyter_text>Также [можно](http://scikit-learn.org/stable/modules/model_persistence.html) сохранять и модели:<jupyter_code>from sklearn.externals import joblib
joblib.dump(clf, 'data/shelter/res/clf_decisiontree_maxdepth5.pkl')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13941327661083044, 'fraction_numerical': 0.030665603119737657, 'mean_word_length': 7.176811594202898, 'pattern_counts': {'":': 0, '<': 112, '<?xml version=': 0, '>': 112, 'https://': 1, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '27', 'dir': 'jupyter-structured-clean-dedup', 'id': '288349', 'license': 'MIT', 'n_tokens_mistral': 4739, 'n_tokens_neox': 4652, 'n_words': 775, 'path': 'Lectures notebooks/(Lectures notebooks) netology Machine learning/3. DecisionTreeClassifier/2 competition.ipynb', 'repo_name': 'Alex110117/data_analysis', 'size': '11283'}