In [41]:
from IPython.core.display import HTML
HTML("""
<style>

div.cell { /* Tunes the space between cells */
margin-top:1em;
margin-bottom:1em;
}

div.text_cell_render h1 { /* Main titles bigger, centered */
font-size: 2.2em;
line-height:1.4em;
text-align:center;
}

div.text_cell_render h2 { /*  Parts names nearer from text */
margin-bottom: -0.4em;
}


div.text_cell_render { /* Customize text cells */
font-family: 'Times New Roman';
font-size:1.5em;
line-height:1.4em;
padding-left:3em;
padding-right:3em;
}
</style>
""")
Out[41]:

Задача "Бинарные данные"

http://mlbootcamp.ru/round/6/sandbox/

Постановка задачи

Дано 30 бинарных признаков и 3 целевых класса. В качестве критерия качества решения задачи будет приниматься точность классификации, т.е. доля правильно классифицированных объектов.

In [174]:
%matplotlib inline
import pandas as pd
import numpy as np
from scipy import stats
from matplotlib import pyplot as plt
from matplotlib import cm # импортируем цветовые схемы, чтобы рисовать графики.
plt.rcParams['figure.figsize'] = 12, 8 # увеличиваем размер картинок
import seaborn as sns
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import StratifiedKFold, GridSearchCV, cross_val_score
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.linear_model import SGDClassifier, LogisticRegression, RidgeClassifier
from sklearn.dummy import DummyClassifier
from xgboost import XGBClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.feature_extraction.text import CountVectorizer
import statsmodels.api as sm
In [3]:
def read_train():
    X_train = pd.read_csv('data_public/x_train.csv', header=None)
    Y_train = pd.read_csv('data_public/y_train.csv', header=None, names=['target'])
    data = pd.concat([X_train, Y_train], axis=1)
    return data, X_train, Y_train.target
In [4]:
data, X_train, Y_train = read_train()
print(data.shape)
data.head()
(210, 31)
Out[4]:
0 1 2 3 4 5 6 7 8 9 ... 21 22 23 24 25 26 27 28 29 target
0 1 1 1 1 0 0 0 1 0 0 ... 1 1 1 1 0 1 1 0 1 1
1 1 0 1 0 0 0 1 1 0 1 ... 1 1 0 1 0 0 1 0 0 1
2 0 1 1 0 0 0 0 1 1 1 ... 1 0 0 1 1 1 0 1 0 1
3 1 0 1 1 0 1 0 1 1 0 ... 1 0 0 1 0 1 1 1 0 0
4 1 1 0 0 1 0 0 1 0 1 ... 1 0 1 1 1 1 0 1 1 2

5 rows × 31 columns

In [5]:
# проверим пропуски и то как распределены значения признаков
data.describe()
Out[5]:
0 1 2 3 4 5 6 7 8 9 ... 21 22 23 24 25 26 27 28 29 target
count 210.000000 210.000000 210.000000 210.000000 210.000000 210.000000 210.000000 210.000000 210.000000 210.000000 ... 210.000000 210.000000 210.000000 210.000000 210.000000 210.000000 210.000000 210.000000 210.000000 210.000000
mean 0.390476 0.523810 0.528571 0.504762 0.500000 0.538095 0.495238 0.547619 0.504762 0.514286 ... 0.542857 0.504762 0.533333 0.495238 0.438095 0.519048 0.452381 0.557143 0.438095 1.033333
std 0.489023 0.500626 0.500376 0.501172 0.501195 0.499738 0.501172 0.498917 0.501172 0.500990 ... 0.499350 0.501172 0.500080 0.501172 0.497339 0.500831 0.498917 0.497911 0.497339 0.820686
min 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
25% 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
50% 0.000000 1.000000 1.000000 1.000000 0.500000 1.000000 0.000000 1.000000 1.000000 1.000000 ... 1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000
75% 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 ... 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 2.000000
max 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 ... 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 2.000000

8 rows × 31 columns

In [63]:
def examine(clf, X, y):
    cv = StratifiedKFold(n_splits=5)
    scores = cross_val_score(clf, X, y, cv=3, scoring='accuracy')
    print("{}. Score: {} (+/- {})".format(clf.__class__.__name__,scores.mean(), scores.std() * 2))
In [64]:
clf = LogisticRegression()
examine(clf, X_train, Y_train)
LogisticRegression. Score: 0.39528864005287767 (+/- 0.0678279839014812)
In [20]:
clf = DummyClassifier()
examine(clf, X_train, Y_train)
Score: 0.3422885136908407 (+/- 0.1152291878713352)
In [22]:
clf = XGBClassifier()
examine(clf, X_train, Y_train)
Score: 0.3948191564848025 (+/- 0.10276228045050986)

WAT?

DummyClassifier ~ LogisticRegression ~ XGBoost!!! ~> 38% точности?

В любой непонятной ситуации - думай!

Для начала посмотрим на соотношение классов. И судя по графикам, всё весьма хорошо - можно сказать, что классы сбалансированы.

In [23]:
sns.countplot(data=data, x='target')
Out[23]:
<matplotlib.axes._subplots.AxesSubplot at 0x7eff1b530278>

Логично посмотреть на форму распределения признаков по каждому классу.

Строим для каждого элемента выборки график по его бинарным признакам. Разделим по цветам 3 разных класса. Чем чаще линия проходит через одни и те же точки, тем на графике она более толстая. По оси ординат откладывается среднее значение для данного класса принимать определенное значение признака.

Из графика видно, что в целом есть некоторые отличия в средних значениях, но в целом основная масса находится в середине. Отсюда можно сделать предположение, что признаки хорошо выровнены.

In [24]:
points = [[],[],[]]
for j in range(3):
    for row in data[data['target'] == j].drop(['target'], axis=1).iterrows():
         points[j].append(row[1])

colors = ['red', 'blue', 'green']
for j in range(3):
    sns.tsplot(points[j], err_style="boot_traces", n_boot=800, color=colors[j])
In [25]:
colors = ['red', 'blue', 'green']
for j in range(3):
    sns.tsplot(points[j], n_boot=200, color=colors[j])
In [72]:
points = [[],[],[]]
for j in range(3):
    for row in data[data['target'] == j].drop(['target'], axis=1).T.iterrows():
         points[j].append(row[1])

colors = ['red', 'blue', 'green']
for j in range(3):
    sns.tsplot(points[j], n_boot=800, color=colors[j])

Среднее по всем строкам

In [84]:
for j in range(3):
    x = data[data['target'] == j].drop(['target'], axis=1)
    plt.plot(x.mean(axis=1).sort_values().reset_index()[0], color=colors[j])

Такое ощущение, что синий график как будто немного тяжелее, чем зеленый и красный вместе. Это наверное как-то отражает внутреннюю структуру данных.

Взаимная корреляции

Cтандартный метод оценки степени корреляции двух последовательностей. Она часто используется для поиска в длинной последовательности более короткой заранее известной. Значительная корреляция между двумя случайными величинами всегда является свидетельством существования некоторой статистической связи в данной выборке, но эта связь не обязательно должна наблюдаться для другой выборки и иметь причинно-следственный характер.

Судя по графику признаки вообще почти не коррелируют друг с другом и с целевой переменной. Это очень странно.

In [45]:
corr = data.corr()
mask = np.zeros_like(corr)
mask[np.triu_indices_from(mask)] = True
with sns.axes_style("white"):
    sns.heatmap(corr, square=True, mask=mask)

Статистические характеристики

Если мы не знаем ничего про данные, то логично посмотреть на статистику по этим данным.

  • Эксцесс (kurtosis)

Это мера остроты пика распределения случайной величины. Коэффициент эксцесса равен 0 для нормального распределения.

$$\gamma_2 = \frac{\mu_4}{\sigma^4}-3$$
  • среднее - первый момент
  • дисперсия - второй момент
  • ассиметрия - третий момент. Коэффициент асимметрии положителен, если правый хвост распределения длиннее левого, и отрицателен в противном случае.
$$\gamma_1 = \frac{\mu_3}{\sigma^3}$$
  • энтропия - мера неопределенности
  • нормальность - специальный статистический тест, учитывающий эксцесс и ассиметрию, чтобы определить, похоже ли распределение на нормальное или нет.
In [48]:
def prepare_statistics(X_train, Y_train):
    d = pd.DataFrame(index=X_train.index)
    d['target'] = Y_train
    d['kurtosis'] = X_train.kurtosis(axis=1)
    d['entropy'] = X_train.apply(lambda row: stats.entropy(row), axis=1)
    d['std'] = X_train.std(axis=1)
    d['skew'] = X_train.skew(axis=1)
    d['normal_stat'] = X_train.apply(lambda row: stats.normaltest(row)[0], axis=1)
    d['mean'] = X_train.mean(axis=1)
    return d

d = prepare_statistics(X_train, Y_train)
d.head()
Out[48]:
target kurtosis entropy std skew normal_stat mean
0 1 -2.126913 2.772589 0.507416 -0.140769 222.464936 0.533333
1 1 -2.126913 2.772589 0.507416 -0.140769 222.464936 0.533333
2 1 -2.148148 2.708050 0.508548 0.000000 279.252501 0.500000
3 0 -2.062056 2.833213 0.504007 -0.283443 132.604517 0.566667
4 2 -1.784005 2.944439 0.490133 -0.582933 37.087925 0.633333
In [31]:
sns.pairplot(d, hue='target')
Out[31]:
<seaborn.axisgrid.PairGrid at 0x10ac97828>
In [36]:
X = d.drop(['target', 'std', 'normal_stat', 'mean', 'skew'], axis=1)
Y = d['target']
X.head(2)
Out[36]:
kurtosis entropy
0 -2.126913 2.772589
1 -2.126913 2.772589
In [17]:
sns.lmplot(x='kurtosis', y='entropy', col='target', hue='target', data=d, y_jitter=.04)
Out[17]:
<seaborn.axisgrid.FacetGrid at 0x10bd1ed30>