KOMPUTASI STATISTIKA

~ Ujian Akhir Semester ~


Kontak : \(\downarrow\)
Email
Instagram yyosia
RPubs https://rpubs.com/yosia/

Supervised Learning

# data analysis and wrangling
import pandas as pd
import numpy as np
import random as rnd

# visualization
import seaborn as sns
import matplotlib.pyplot as plt

# machine learning
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC, LinearSVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import Perceptron
from sklearn.linear_model import SGDClassifier
from sklearn.tree import DecisionTreeClassifier

Input Data

Variable Definition Key
survival Survival 0 = No, 1 = Yes
pclass Ticket class 1 = 1st, 2 = 2nd, 3 = 3rd
sex Sex
Age Age in years
sibsp # of siblings/spouses aboard the Titanic
parch # of parents/children aboard the Titanic
ticket Ticket number
fare Passenger fare
cabin Cabin number
embarked Port of Embarkation C = Cherbourg, Q = Queenstown, S = Southampton

Variable Notes

Variable Notes pclass: Status sosial ekonomi 1st = Upper 2nd = Middle 3rd = Lower

age: Umur pecahan jika kurang dari 1. Bila umur ditaksir dalam bentuk xx.5

sibsp: Family relation Sibling = brother, sister, stepbrother, stepsister Spouse = husband, wife (mistresses and fiancés were ignored)

parch: The dataset defines family relations in this way… Parent = mother, father Child = daughter, son, stepdaughter, stepson Some children travelled only with a nanny, therefore parch=0 for them.

train_df = pd.read_csv('input/train.csv')
test_df = pd.read_csv('input/test.csv')
combine = [train_df, test_df]
print(train_df.columns.values)
## ['PassengerId' 'Survived' 'Pclass' 'Name' 'Sex' 'Age' 'SibSp' 'Parch'
##  'Ticket' 'Fare' 'Cabin' 'Embarked']
train_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
test_df.info()
## <class 'pandas.core.frame.DataFrame'>
## RangeIndex: 418 entries, 0 to 417
## Data columns (total 11 columns):
##  #   Column       Non-Null Count  Dtype  
## ---  ------       --------------  -----  
##  0   PassengerId  418 non-null    int64  
##  1   Pclass       418 non-null    int64  
##  2   Name         418 non-null    object 
##  3   Sex          418 non-null    object 
##  4   Age          332 non-null    float64
##  5   SibSp        418 non-null    int64  
##  6   Parch        418 non-null    int64  
##  7   Ticket       418 non-null    object 
##  8   Fare         417 non-null    float64
##  9   Cabin        91 non-null     object 
##  10  Embarked     418 non-null    object 
## dtypes: float64(2), int64(4), object(5)
## memory usage: 36.0+ KB
train_df.describe()
##        PassengerId    Survived      Pclass  ...       SibSp       Parch        Fare
## count   891.000000  891.000000  891.000000  ...  891.000000  891.000000  891.000000
## mean    446.000000    0.383838    2.308642  ...    0.523008    0.381594   32.204208
## std     257.353842    0.486592    0.836071  ...    1.102743    0.806057   49.693429
## min       1.000000    0.000000    1.000000  ...    0.000000    0.000000    0.000000
## 25%     223.500000    0.000000    2.000000  ...    0.000000    0.000000    7.910400
## 50%     446.000000    0.000000    3.000000  ...    0.000000    0.000000   14.454200
## 75%     668.500000    1.000000    3.000000  ...    1.000000    0.000000   31.000000
## max     891.000000    1.000000    3.000000  ...    8.000000    6.000000  512.329200
## 
## [8 rows x 7 columns]
train_df.describe(include=['O'])
##                            Name   Sex  Ticket    Cabin Embarked
## count                       891   891     891      204      889
## unique                      891     2     681      147        3
## top     Braund, Mr. Owen Harris  male  347082  B96 B98        S
## freq                          1   577       7        4      644

• Nama bersifat unik di seluruh kumpulan data (count=unique=891) • Variabel jenis kelamin sebagai dua kemungkinan nilai dengan 65% laki-laki (atas=laki-laki, frekuensi=577/hitung=891). • Nilai kabin memiliki beberapa duplikat di seluruh sampel. Atau beberapa penumpang berbagi kabin. • Memulai mengambil tiga kemungkinan nilai. Port S digunakan oleh sebagian besar penumpang (atas=S) • Fitur tiket memiliki rasio tinggi (22%) dari nilai duplikat (unik=681).

Analyze by pivoting features

train_df[['Pclass', 'Survived']].groupby(['Pclass'], as_index=False).mean().sort_values(by='Survived', ascending=False)
##    Pclass  Survived
## 0       1  0.629630
## 1       2  0.472826
## 2       3  0.242363
train_df[["Sex", "Survived"]].groupby(['Sex'], as_index=False).mean().sort_values(by='Survived', ascending=False)
##       Sex  Survived
## 0  female  0.742038
## 1    male  0.188908
train_df[["SibSp", "Survived"]].groupby(['SibSp'], as_index=False).mean().sort_values(by='Survived', ascending=False)
##    SibSp  Survived
## 1      1  0.535885
## 2      2  0.464286
## 0      0  0.345395
## 3      3  0.250000
## 4      4  0.166667
## 5      5  0.000000
## 6      8  0.000000
train_df[["Parch", "Survived"]].groupby(['Parch'], as_index=False).mean().sort_values(by='Survived', ascending=False)
##    Parch  Survived
## 3      3  0.600000
## 1      1  0.550847
## 2      2  0.500000
## 0      0  0.343658
## 5      5  0.200000
## 4      4  0.000000
## 6      6  0.000000

Analyze by visualizing data

g = sns.FacetGrid(train_df, col='Survived')
g.map(plt.hist, 'Age', bins=20)

# grid = sns.FacetGrid(train_df, col='Pclass', hue='Survived')
grid = sns.FacetGrid(train_df, col='Survived', row='Pclass', height=2.2, aspect=1.6)
grid.map(plt.hist, 'Age', alpha=.5, bins=20)

grid.add_legend();
# grid = sns.FacetGrid(train_df, col='Embarked')
grid = sns.FacetGrid(train_df, row='Embarked', height=2.2, aspect=1.6)
grid.map(sns.pointplot, 'Pclass', 'Survived', 'Sex', palette='deep')

grid.add_legend()

# grid = sns.FacetGrid(train_df, col='Embarked', hue='Survived', palette={0: 'k', 1: 'w'})
grid = sns.FacetGrid(train_df, row='Embarked', col='Survived', height=2.2, aspect=1.6)
grid.map(sns.barplot, 'Sex', 'Fare', alpha=.5, ci=None)

grid.add_legend()

Wrangle data

print("Before", train_df.shape, test_df.shape, combine[0].shape, combine[1].shape)
## Before (891, 12) (418, 11) (891, 12) (418, 11)
train_df = train_df.drop(['Ticket', 'Cabin'], axis=1)
test_df = test_df.drop(['Ticket', 'Cabin'], axis=1)
combine = [train_df, test_df]

"After", train_df.shape, test_df.shape, combine[0].shape, combine[1].shape
## ('After', (891, 10), (418, 9), (891, 10), (418, 9))

Observation

for dataset in combine:
    dataset['Title'] = dataset.Name.str.extract(' ([A-Za-z]+)\.', expand=False)

pd.crosstab(train_df['Title'], train_df['Sex'])
## Sex       female  male
## Title                 
## Capt           0     1
## Col            0     2
## Countess       1     0
## Don            0     1
## Dr             1     6
## Jonkheer       0     1
## Lady           1     0
## Major          0     2
## Master         0    40
## Miss         182     0
## Mlle           2     0
## Mme            1     0
## Mr             0   517
## Mrs          125     0
## Ms             1     0
## Rev            0     6
## Sir            0     1
for dataset in combine:
    dataset['Title'] = dataset['Title'].replace(['Lady', 'Countess','Capt', 'Col',\
    'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare')

    dataset['Title'] = dataset['Title'].replace('Mlle', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Ms', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Mme', 'Mrs')
    
train_df[['Title', 'Survived']].groupby(['Title'], as_index=False).mean()
##     Title  Survived
## 0  Master  0.575000
## 1    Miss  0.702703
## 2      Mr  0.156673
## 3     Mrs  0.793651
## 4    Rare  0.347826
title_mapping = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5}
for dataset in combine:
    dataset['Title'] = dataset['Title'].map(title_mapping)
    dataset['Title'] = dataset['Title'].fillna(0)

train_df.head()
##    PassengerId  Survived  Pclass  ...     Fare Embarked  Title
## 0            1         0       3  ...   7.2500        S      1
## 1            2         1       1  ...  71.2833        C      3
## 2            3         1       3  ...   7.9250        S      2
## 3            4         1       1  ...  53.1000        S      3
## 4            5         0       3  ...   8.0500        S      1
## 
## [5 rows x 11 columns]
train_df = train_df.drop(['Name', 'PassengerId'], axis=1)
test_df = test_df.drop(['Name'], axis=1)
combine = [train_df, test_df]
train_df.shape, test_df.shape
## ((891, 9), (418, 9))
for dataset in combine:
    dataset['Sex'] = dataset['Sex'].map( {'female': 1, 'male': 0} ).astype(int)

train_df.head()
##    Survived  Pclass  Sex   Age  SibSp  Parch     Fare Embarked  Title
## 0         0       3    0  22.0      1      0   7.2500        S      1
## 1         1       1    1  38.0      1      0  71.2833        C      3
## 2         1       3    1  26.0      0      0   7.9250        S      2
## 3         1       1    1  35.0      1      0  53.1000        S      3
## 4         0       3    0  35.0      0      0   8.0500        S      1
# grid = sns.FacetGrid(train_df, col='Pclass', hue='Gender')
grid = sns.FacetGrid(train_df, row='Pclass', col='Sex', height=2.2, aspect=1.6)
grid.map(plt.hist, 'Age', alpha=.5, bins=20)

grid.add_legend()

guess_ages = np.zeros((2,3))
for dataset in combine:
    for i in range(0, 2):
        for j in range(0, 3):
            guess_df = dataset[(dataset['Sex'] == i) & \
                                  (dataset['Pclass'] == j+1)]['Age'].dropna()

            # age_mean = guess_df.mean()
            # age_std = guess_df.std()
            # age_guess = rnd.uniform(age_mean - age_std, age_mean + age_std)

            age_guess = guess_df.median()

            # Convert random age float to nearest .5 age
            guess_ages[i,j] = int( age_guess/0.5 + 0.5 ) * 0.5
            
    for i in range(0, 2):
        for j in range(0, 3):
            dataset.loc[ (dataset.Age.isnull()) & (dataset.Sex == i) & (dataset.Pclass == j+1),\
                    'Age'] = guess_ages[i,j]

    dataset['Age'] = dataset['Age'].astype(int)

train_df.head()
##    Survived  Pclass  Sex  Age  SibSp  Parch     Fare Embarked  Title
## 0         0       3    0   22      1      0   7.2500        S      1
## 1         1       1    1   38      1      0  71.2833        C      3
## 2         1       3    1   26      0      0   7.9250        S      2
## 3         1       1    1   35      1      0  53.1000        S      3
## 4         0       3    0   35      0      0   8.0500        S      1
train_df['AgeBand'] = pd.cut(train_df['Age'], 5)
train_df[['AgeBand', 'Survived']].groupby(['AgeBand'], as_index=False).mean().sort_values(by='AgeBand', ascending=True)
##          AgeBand  Survived
## 0  (-0.08, 16.0]  0.550000
## 1   (16.0, 32.0]  0.337374
## 2   (32.0, 48.0]  0.412037
## 3   (48.0, 64.0]  0.434783
## 4   (64.0, 80.0]  0.090909
for dataset in combine:    
    dataset.loc[ dataset['Age'] <= 16, 'Age'] = 0
    dataset.loc[(dataset['Age'] > 16) & (dataset['Age'] <= 32), 'Age'] = 1
    dataset.loc[(dataset['Age'] > 32) & (dataset['Age'] <= 48), 'Age'] = 2
    dataset.loc[(dataset['Age'] > 48) & (dataset['Age'] <= 64), 'Age'] = 3
    dataset.loc[ dataset['Age'] > 64, 'Age']
## 33     66
## 54     65
## 96     71
## 116    70
## 280    65
## 456    65
## 493    71
## 630    80
## 672    70
## 745    70
## 851    74
## Name: Age, dtype: int32
## 81    67
## 96    76
## Name: Age, dtype: int32
train_df.head()
##    Survived  Pclass  Sex  Age  ...     Fare  Embarked  Title       AgeBand
## 0         0       3    0    1  ...   7.2500         S      1  (16.0, 32.0]
## 1         1       1    1    2  ...  71.2833         C      3  (32.0, 48.0]
## 2         1       3    1    1  ...   7.9250         S      2  (16.0, 32.0]
## 3         1       1    1    2  ...  53.1000         S      3  (32.0, 48.0]
## 4         0       3    0    2  ...   8.0500         S      1  (32.0, 48.0]
## 
## [5 rows x 10 columns]
train_df = train_df.drop(['AgeBand'], axis=1)
combine = [train_df, test_df]
train_df.head()
##    Survived  Pclass  Sex  Age  SibSp  Parch     Fare Embarked  Title
## 0         0       3    0    1      1      0   7.2500        S      1
## 1         1       1    1    2      1      0  71.2833        C      3
## 2         1       3    1    1      0      0   7.9250        S      2
## 3         1       1    1    2      1      0  53.1000        S      3
## 4         0       3    0    2      0      0   8.0500        S      1
for dataset in combine:
    dataset['FamilySize'] = dataset['SibSp'] + dataset['Parch'] + 1

train_df[['FamilySize', 'Survived']].groupby(['FamilySize'], as_index=False).mean().sort_values(by='Survived', ascending=False)
##    FamilySize  Survived
## 3           4  0.724138
## 2           3  0.578431
## 1           2  0.552795
## 6           7  0.333333
## 0           1  0.303538
## 4           5  0.200000
## 5           6  0.136364
## 7           8  0.000000
## 8          11  0.000000
for dataset in combine:
    dataset['IsAlone'] = 0
    dataset.loc[dataset['FamilySize'] == 1, 'IsAlone'] = 1

train_df[['IsAlone', 'Survived']].groupby(['IsAlone'], as_index=False).mean()
##    IsAlone  Survived
## 0        0  0.505650
## 1        1  0.303538
train_df = train_df.drop(['Parch', 'SibSp', 'FamilySize'], axis=1)
test_df = test_df.drop(['Parch', 'SibSp', 'FamilySize'], axis=1)
combine = [train_df, test_df]

train_df.head()
##    Survived  Pclass  Sex  Age     Fare Embarked  Title  IsAlone
## 0         0       3    0    1   7.2500        S      1        0
## 1         1       1    1    2  71.2833        C      3        0
## 2         1       3    1    1   7.9250        S      2        1
## 3         1       1    1    2  53.1000        S      3        0
## 4         0       3    0    2   8.0500        S      1        1
for dataset in combine:
    dataset['Age*Class'] = dataset.Age * dataset.Pclass

train_df.loc[:, ['Age*Class', 'Age', 'Pclass']].head(10)
##    Age*Class  Age  Pclass
## 0          3    1       3
## 1          2    2       1
## 2          3    1       3
## 3          2    2       1
## 4          6    2       3
## 5          3    1       3
## 6          3    3       1
## 7          0    0       3
## 8          3    1       3
## 9          0    0       2
freq_port = train_df.Embarked.dropna().mode()[0]
for dataset in combine:
    dataset['Embarked'] = dataset['Embarked'].fillna(freq_port)
    
train_df[['Embarked', 'Survived']].groupby(['Embarked'], as_index=False).mean().sort_values(by='Survived', ascending=False)
##   Embarked  Survived
## 0        C  0.553571
## 1        Q  0.389610
## 2        S  0.339009
for dataset in combine:
    dataset['Embarked'] = dataset['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)

train_df.head()
##    Survived  Pclass  Sex  Age     Fare  Embarked  Title  IsAlone  Age*Class
## 0         0       3    0    1   7.2500         0      1        0          3
## 1         1       1    1    2  71.2833         1      3        0          2
## 2         1       3    1    1   7.9250         0      2        1          3
## 3         1       1    1    2  53.1000         0      3        0          2
## 4         0       3    0    2   8.0500         0      1        1          6
test_df['Fare'].fillna(test_df['Fare'].dropna().median(), inplace=True)
test_df.head()
##    PassengerId  Pclass  Sex  Age     Fare  Embarked  Title  IsAlone  Age*Class
## 0          892       3    0    2   7.8292         2      1        1          6
## 1          893       3    1    2   7.0000         0      3        0          6
## 2          894       2    0    3   9.6875         2      1        1          6
## 3          895       3    0    1   8.6625         0      1        1          3
## 4          896       3    1    1  12.2875         0      3        0          3
train_df['FareBand'] = pd.qcut(train_df['Fare'], 4)
train_df[['FareBand', 'Survived']].groupby(['FareBand'], as_index=False).mean().sort_values(by='FareBand', ascending=True)
##           FareBand  Survived
## 0   (-0.001, 7.91]  0.197309
## 1   (7.91, 14.454]  0.303571
## 2   (14.454, 31.0]  0.454955
## 3  (31.0, 512.329]  0.581081
for dataset in combine:
    dataset.loc[ dataset['Fare'] <= 7.91, 'Fare'] = 0
    dataset.loc[(dataset['Fare'] > 7.91) & (dataset['Fare'] <= 14.454), 'Fare'] = 1
    dataset.loc[(dataset['Fare'] > 14.454) & (dataset['Fare'] <= 31), 'Fare']   = 2
    dataset.loc[ dataset['Fare'] > 31, 'Fare'] = 3
    dataset['Fare'] = dataset['Fare'].astype(int)

train_df = train_df.drop(['FareBand'], axis=1)
combine = [train_df, test_df]
    
train_df.head(10)
##    Survived  Pclass  Sex  Age  Fare  Embarked  Title  IsAlone  Age*Class
## 0         0       3    0    1     0         0      1        0          3
## 1         1       1    1    2     3         1      3        0          2
## 2         1       3    1    1     1         0      2        1          3
## 3         1       1    1    2     3         0      3        0          2
## 4         0       3    0    2     1         0      1        1          6
## 5         0       3    0    1     1         2      1        1          3
## 6         0       1    0    3     3         0      1        1          3
## 7         0       3    0    0     2         0      4        0          0
## 8         1       3    1    1     1         0      3        0          3
## 9         1       2    1    0     2         1      3        0          0
test_df.head(10)
##    PassengerId  Pclass  Sex  Age  Fare  Embarked  Title  IsAlone  Age*Class
## 0          892       3    0    2     0         2      1        1          6
## 1          893       3    1    2     0         0      3        0          6
## 2          894       2    0    3     1         2      1        1          6
## 3          895       3    0    1     1         0      1        1          3
## 4          896       3    1    1     1         0      3        0          3
## 5          897       3    0    0     1         0      1        1          0
## 6          898       3    1    1     0         2      2        1          3
## 7          899       2    0    1     2         0      1        0          2
## 8          900       3    1    1     0         1      3        1          3
## 9          901       3    0    1     2         0      1        0          3

Model, Predict and Solve

X_train = train_df.drop("Survived", axis=1)
Y_train = train_df["Survived"]
X_test  = test_df.drop("PassengerId", axis=1).copy()
X_train.shape, Y_train.shape, X_test.shape
## ((891, 8), (891,), (418, 8))

Logistic Regression

Regresi Logistik adalah model yang berguna untuk dijalankan di awal pada workflow. Regresi logistik mengukur hubungan antara variabel dependen kategori (fitur) dan satu atau lebih variabel independen (fitur) dengan memperkirakan probabilitas menggunakan fungsi logistik, yang merupakan distribusi logistik kumulatif.

logreg = LogisticRegression()
logreg.fit(X_train, Y_train)
LogisticRegression()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Y_pred = logreg.predict(X_test)
acc_log = round(logreg.score(X_train, Y_train) * 100, 2)
acc_log
## 80.36
coeff_df = pd.DataFrame(train_df.columns.delete(0))
coeff_df.columns = ['Feature']
coeff_df["Correlation"] = pd.Series(logreg.coef_[0])

coeff_df.sort_values(by='Correlation', ascending=False)
##      Feature  Correlation
## 1        Sex     2.201619
## 5      Title     0.397888
## 2        Age     0.287011
## 4   Embarked     0.261473
## 6    IsAlone     0.126553
## 3       Fare    -0.086655
## 7  Age*Class    -0.311069
## 0     Pclass    -0.750700

KNN or k-Nearest Neighbors

Dalam pengenalan pola, algoritma k-Nearest Neighbors (atau singkatnya k-NN) adalah metode non-parametrik yang digunakan untuk klasifikasi dan regresi. Sebuah sampel diklasifikasikan berdasarkan suara mayoritas terdekatnya, dengan sampel dimasukan ke kelas yang paling umum di antara k tetangga terdekatnya (k adalah bilangan bulat positif, biasanya kecil). Jika k = 1, maka objek hanya ditugaskan ke kelas tetangga terdekat.

knn = KNeighborsClassifier(n_neighbors = 3)
knn.fit(X_train, Y_train)
KNeighborsClassifier(n_neighbors=3)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Y_pred = knn.predict(X_test)
acc_knn = round(knn.score(X_train, Y_train) * 100, 2)
acc_knn
## 83.84

Gaussian Naive Bayes

Dalam pembelajaran mesin, pengklasifikasi naive Bayes adalah sejenis pengklasifikasi probabilistik sederhana berdasarkan penerapan teorema Bayes dengan asumsi independensi yang kuat (naif) di antara fitur-fiturnya. Pengklasifikasi Naive Bayes sangat terukur, membutuhkan sejumlah parameter linier dalam jumlah variabel (fitur) dalam masalah pembelajaran.

gaussian = GaussianNB()
gaussian.fit(X_train, Y_train)
GaussianNB()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Y_pred = gaussian.predict(X_test)
acc_gaussian = round(gaussian.score(X_train, Y_train) * 100, 2)
acc_gaussian
## 72.28

Linear SVC (Support Vector Classification)

Metode Linear Support Vector Classifier (SVC) menerapkan fungsi kernel linier untuk melakukan klasifikasi dan bekerja dengan baik dengan jumlah sampel yang banyak.

linear_svc = LinearSVC()
linear_svc.fit(X_train, Y_train)
LinearSVC()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Y_pred = linear_svc.predict(X_test)
acc_linear_svc = round(linear_svc.score(X_train, Y_train) * 100, 2)
acc_linear_svc
## 79.01

Decision Tree

Model ini menggunakan pohon keputusan sebagai model prediksi yang memetakan fitur (cabang pohon) ke kesimpulan tentang nilai target (daun pohon). Model pohon di mana variabel target dapat mengambil sekumpulan nilai terbatas disebut pohon klasifikasi; dalam struktur pohon ini, daun mewakili label kelas dan cabang mewakili konjungsi fitur yang mengarah ke label kelas tersebut. Pohon keputusan di mana variabel target dapat mengambil nilai kontinu (biasanya bilangan real) disebut pohon regresi.

decision_tree = DecisionTreeClassifier()
decision_tree.fit(X_train, Y_train)
DecisionTreeClassifier()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Y_pred = decision_tree.predict(X_test)
acc_decision_tree = round(decision_tree.score(X_train, Y_train) * 100, 2)
acc_decision_tree
## 86.76

Random Forest

Model Random Forest berikutnya adalah salah satu yang paling populer. Random Forest atau hutan keputusan acak adalah metode pembelajaran ansambel untuk klasifikasi, regresi, dan tugas lainnya, yang beroperasi dengan membangun banyak pohon keputusan (n_estimator=100) pada waktu pelatihan dan mengeluarkan kelas yang merupakan mode kelas (klasifikasi) atau prediksi rata-rata (regresi) dari masing-masing pohon.

random_forest = RandomForestClassifier(n_estimators=100)
random_forest.fit(X_train, Y_train)
RandomForestClassifier()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Y_pred = random_forest.predict(X_test)
random_forest.score(X_train, Y_train)
## 0.867564534231201
acc_random_forest = round(random_forest.score(X_train, Y_train) * 100, 2)
acc_random_forest
## 86.76

Model Evaluation

Kita sekarang dapat memberi peringkat evaluasi kita terhadap semua model untuk memilih yang terbaik untuk masalah ini.

models = pd.DataFrame({
    'Model': [ 'KNN', 'Logistic Regression', 
              'Random Forest', 'Naive Bayes', 'Linear SVC', 
              'Decision Tree'],
    'Score': [ acc_knn, acc_log, 
              acc_random_forest, acc_gaussian, 
               acc_linear_svc, acc_decision_tree]})
models.sort_values(by='Score', ascending=False)
##                  Model  Score
## 2        Random Forest  86.76
## 5        Decision Tree  86.76
## 0                  KNN  83.84
## 1  Logistic Regression  80.36
## 4           Linear SVC  79.01
## 3          Naive Bayes  72.28
submission = pd.DataFrame({
        "PassengerId": test_df["PassengerId"],
        "Survived": Y_pred
    })
submission
##      PassengerId  Survived
## 0            892         0
## 1            893         0
## 2            894         0
## 3            895         0
## 4            896         1
## ..           ...       ...
## 413         1305         0
## 414         1306         1
## 415         1307         0
## 416         1308         0
## 417         1309         1
## 
## [418 rows x 2 columns]

Unsupervised Learning

Input Data

import numpy as np 
import pandas as pd 
import matplotlib.pyplot as plt # for data visualization
import seaborn as sns # for statistical data visualization
df = pd.read_csv('input/Live.csv')
df.info()
## <class 'pandas.core.frame.DataFrame'>
## RangeIndex: 7050 entries, 0 to 7049
## Data columns (total 16 columns):
##  #   Column            Non-Null Count  Dtype  
## ---  ------            --------------  -----  
##  0   status_id         7050 non-null   object 
##  1   status_type       7050 non-null   object 
##  2   status_published  7050 non-null   object 
##  3   num_reactions     7050 non-null   int64  
##  4   num_comments      7050 non-null   int64  
##  5   num_shares        7050 non-null   int64  
##  6   num_likes         7050 non-null   int64  
##  7   num_loves         7050 non-null   int64  
##  8   num_wows          7050 non-null   int64  
##  9   num_hahas         7050 non-null   int64  
##  10  num_sads          7050 non-null   int64  
##  11  num_angrys        7050 non-null   int64  
##  12  Column1           0 non-null      float64
##  13  Column2           0 non-null      float64
##  14  Column3           0 non-null      float64
##  15  Column4           0 non-null      float64
## dtypes: float64(4), int64(9), object(3)
## memory usage: 881.4+ KB
df.isnull().sum()
## status_id              0
## status_type            0
## status_published       0
## num_reactions          0
## num_comments           0
## num_shares             0
## num_likes              0
## num_loves              0
## num_wows               0
## num_hahas              0
## num_sads               0
## num_angrys             0
## Column1             7050
## Column2             7050
## Column3             7050
## Column4             7050
## dtype: int64
df.drop(['Column1', 'Column2', 'Column3', 'Column4'], axis=1, inplace=True)

EDA

len(df['status_id'].unique())
## 6997
len(df['status_published'].unique())
## 6913
len(df['status_type'].unique())
## 4
df.drop(['status_id', 'status_published'], axis=1, inplace=True)
df.info()
## <class 'pandas.core.frame.DataFrame'>
## RangeIndex: 7050 entries, 0 to 7049
## Data columns (total 10 columns):
##  #   Column         Non-Null Count  Dtype 
## ---  ------         --------------  ----- 
##  0   status_type    7050 non-null   object
##  1   num_reactions  7050 non-null   int64 
##  2   num_comments   7050 non-null   int64 
##  3   num_shares     7050 non-null   int64 
##  4   num_likes      7050 non-null   int64 
##  5   num_loves      7050 non-null   int64 
##  6   num_wows       7050 non-null   int64 
##  7   num_hahas      7050 non-null   int64 
##  8   num_sads       7050 non-null   int64 
##  9   num_angrys     7050 non-null   int64 
## dtypes: int64(9), object(1)
## memory usage: 550.9+ KB
df.head()
##   status_type  num_reactions  num_comments  ...  num_hahas  num_sads  num_angrys
## 0       video            529           512  ...          1         1           0
## 1       photo            150             0  ...          0         0           0
## 2       video            227           236  ...          1         0           0
## 3       photo            111             0  ...          0         0           0
## 4       photo            213             0  ...          0         0           0
## 
## [5 rows x 10 columns]

Convert categorical variable into integers

X = df
y = df['status_type']

from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
X['status_type'] = le.fit_transform(X['status_type'])
y = le.transform(y)
X.head()
##    status_type  num_reactions  num_comments  ...  num_hahas  num_sads  num_angrys
## 0            3            529           512  ...          1         1           0
## 1            1            150             0  ...          0         0           0
## 2            3            227           236  ...          1         0           0
## 3            1            111             0  ...          0         0           0
## 4            1            213             0  ...          0         0           0
## 
## [5 rows x 10 columns]

Feature Scaling

cols = X.columns

from sklearn.preprocessing import MinMaxScaler

ms = MinMaxScaler()
X = ms.fit_transform(X)
X = pd.DataFrame(X, columns=[cols])
X.head()
##   status_type num_reactions num_comments  ... num_hahas  num_sads num_angrys
## 0    1.000000      0.112314     0.024393  ...  0.006369  0.019608        0.0
## 1    0.333333      0.031847     0.000000  ...  0.000000  0.000000        0.0
## 2    1.000000      0.048195     0.011243  ...  0.006369  0.000000        0.0
## 3    0.333333      0.023567     0.000000  ...  0.000000  0.000000        0.0
## 4    0.333333      0.045223     0.000000  ...  0.000000  0.000000        0.0
## 
## [5 rows x 10 columns]

K-Means model

Use elbow method to find optimal number of clusters

from sklearn.cluster import KMeans
cs = []
for i in range(1, 11):
    kmeans = KMeans(n_clusters = i, init = 'k-means++', max_iter = 300, n_init = 10, random_state = 0)
    kmeans.fit(X)
    cs.append(kmeans.inertia_)
KMeans(n_clusters=10, random_state=0)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
plt.plot(range(1, 11), cs)
plt.title('The Elbow Method')
plt.xlabel('Number of clusters')
plt.ylabel('CS')
plt.show()

from sklearn.cluster import KMeans

kmeans = KMeans(n_clusters=2,random_state=0)

kmeans.fit(X)
KMeans(n_clusters=2, random_state=0)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
labels = kmeans.labels_

# check how many of the samples were correctly labeled

correct_labels = sum(y == labels)

print("Result: %d out of %d samples were correctly labeled." % (correct_labels, y.size))
## Result: 63 out of 7050 samples were correctly labeled.
print('Accuracy score: {0:0.2f}'. format(correct_labels/float(y.size)))
## Accuracy score: 0.01
kmeans = KMeans(n_clusters=3, random_state=0)

kmeans.fit(X)

# check how many of the samples were correctly labeled
KMeans(n_clusters=3, random_state=0)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
labels = kmeans.labels_

correct_labels = sum(y == labels)
print("Result: %d out of %d samples were correctly labeled." % (correct_labels, y.size))
## Result: 138 out of 7050 samples were correctly labeled.
print('Accuracy score: {0:0.2f}'. format(correct_labels/float(y.size)))
## Accuracy score: 0.02
kmeans = KMeans(n_clusters=4, random_state=0)

kmeans.fit(X)

# check how many of the samples were correctly labeled
KMeans(n_clusters=4, random_state=0)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
labels = kmeans.labels_

correct_labels = sum(y == labels)
print("Result: %d out of %d samples were correctly labeled." % (correct_labels, y.size))
## Result: 4340 out of 7050 samples were correctly labeled.
print('Accuracy score: {0:0.2f}'. format(correct_labels/float(y.size)))
## Accuracy score: 0.62
LS0tDQp0aXRsZTogIktPTVBVVEFTSSBTVEFUSVNUSUtBIg0Kc3VidGl0bGU6ICJ+IFVqaWFuIEFraGlyIFNlbWVzdGVyIH4iDQphdXRob3I6ICJZb3NpYSINCmRhdGU6ICAiYHIgZm9ybWF0KFN5cy5EYXRlKCksICclQiAlZCwgJVknKWAiDQpvdXRwdXQ6DQogIHJtZGZvcm1hdHM6OnJlYWR0aGVkb3duOiAgICMgaHR0cHM6Ly9naXRodWIuY29tL2p1YmEvcm1kZm9ybWF0cw0KICAgIHNlbGZfY29udGFpbmVkOiB0cnVlDQogICAgdGh1bWJuYWlsczogdHJ1ZQ0KICAgIGxpZ2h0Ym94OiB0cnVlDQogICAgZ2FsbGVyeTogdHJ1ZQ0KICAgIGxpYl9kaXI6IGxpYnMNCiAgICBkZl9wcmludDogInBhZ2VkIg0KICAgIGNvZGVfZm9sZGluZzogInNob3ciDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgY3NzOiAic3R5bGUuY3NzIg0KDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KHJldGljdWxhdGUpDQpsaWJyYXJ5KFJjcHApDQp1c2VfY29uZGFlbnYoInB5M18xMCIsIHJlcXVpcmVkID0gVFJVRSkNCmBgYA0KDQoNCjxicj4NCg0KPGltZyBzdHlsZT0iZmxvYXQ6IHJpZ2h0OyBtYXJnaW46IDBweCA1MHB4IDBweCA1MHB4OyB3aWR0aDozMCUiIHNyYz0icG90by5qcGciLz4gDQoNCnwNCjotLS0tIHw6LS0tLQ0KKipLb250YWsqKnwgKio6ICRcZG93bmFycm93JCoqDQpFbWFpbHwgeW9zaWEueW9zaWFAc3R1ZGVudC5tYXRhbmF1bml2ZXJzaXR5LmFjLmlkDQpJbnN0YWdyYW0gfCBbeXlvc2lhXShodHRwczovL3d3dy5pbnN0YWdyYW0uY29tL3l5b3NpYS8pIA0KUlB1YnMgIHwgaHR0cHM6Ly9ycHVicy5jb20veW9zaWEvIA0KDQoqKioNCg0KIyBTdXBlcnZpc2VkIExlYXJuaW5nDQoNCmBgYHtweXRob259DQojIGRhdGEgYW5hbHlzaXMgYW5kIHdyYW5nbGluZw0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgcmFuZG9tIGFzIHJuZA0KDQojIHZpc3VhbGl6YXRpb24NCmltcG9ydCBzZWFib3JuIGFzIHNucw0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KDQojIG1hY2hpbmUgbGVhcm5pbmcNCmZyb20gc2tsZWFybi5saW5lYXJfbW9kZWwgaW1wb3J0IExvZ2lzdGljUmVncmVzc2lvbg0KZnJvbSBza2xlYXJuLnN2bSBpbXBvcnQgU1ZDLCBMaW5lYXJTVkMNCmZyb20gc2tsZWFybi5lbnNlbWJsZSBpbXBvcnQgUmFuZG9tRm9yZXN0Q2xhc3NpZmllcg0KZnJvbSBza2xlYXJuLm5laWdoYm9ycyBpbXBvcnQgS05laWdoYm9yc0NsYXNzaWZpZXINCmZyb20gc2tsZWFybi5uYWl2ZV9iYXllcyBpbXBvcnQgR2F1c3NpYW5OQg0KZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgUGVyY2VwdHJvbg0KZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgU0dEQ2xhc3NpZmllcg0KZnJvbSBza2xlYXJuLnRyZWUgaW1wb3J0IERlY2lzaW9uVHJlZUNsYXNzaWZpZXINCmBgYA0KDQojIyBJbnB1dCBEYXRhDQoNCnwgVmFyaWFibGUgfCBEZWZpbml0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgS2V5ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwtLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnwgc3Vydml2YWwgfCBTdXJ2aXZhbCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgMCA9IE5vLCAxID0gWWVzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgcGNsYXNzICAgfCBUaWNrZXQgY2xhc3MgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgMSA9IDFzdCwgMiA9IDJuZCwgMyA9IDNyZCAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgc2V4ICAgICAgfCBTZXggICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgQWdlICAgICAgfCBBZ2UgaW4geWVhcnMgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgc2lic3AgICAgfCAjIG9mIHNpYmxpbmdzL3Nwb3VzZXMgYWJvYXJkIHRoZSBUaXRhbmljIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgcGFyY2ggICAgfCAjIG9mIHBhcmVudHMvY2hpbGRyZW4gYWJvYXJkIHRoZSBUaXRhbmljIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgdGlja2V0ICAgfCBUaWNrZXQgbnVtYmVyICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgZmFyZSAgICAgfCBQYXNzZW5nZXIgZmFyZSAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgY2FiaW4gICAgfCBDYWJpbiBudW1iZXIgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgZW1iYXJrZWQgfCBQb3J0IG9mIEVtYmFya2F0aW9uICAgICAgICAgICAgICAgICAgICAgIHwgQyA9ICAgQ2hlcmJvdXJnLCBRID0gUXVlZW5zdG93biwgUyA9IFNvdXRoYW1wdG9uICAgIHwNCg0KIyMjIFZhcmlhYmxlIE5vdGVzIA0KDQpWYXJpYWJsZSBOb3Rlcw0KcGNsYXNzOiBTdGF0dXMgc29zaWFsIGVrb25vbWkgDQoxc3QgPSBVcHBlcg0KMm5kID0gTWlkZGxlDQozcmQgPSBMb3dlcg0KDQphZ2U6IFVtdXIgcGVjYWhhbiBqaWthIGt1cmFuZyBkYXJpIDEuIEJpbGEgdW11ciBkaXRha3NpciBkYWxhbSBiZW50dWsgeHguNQ0KDQpzaWJzcDogRmFtaWx5IHJlbGF0aW9uDQpTaWJsaW5nID0gYnJvdGhlciwgc2lzdGVyLCBzdGVwYnJvdGhlciwgc3RlcHNpc3Rlcg0KU3BvdXNlID0gaHVzYmFuZCwgd2lmZSAobWlzdHJlc3NlcyBhbmQgZmlhbmPDqXMgd2VyZSBpZ25vcmVkKQ0KDQpwYXJjaDogVGhlIGRhdGFzZXQgZGVmaW5lcyBmYW1pbHkgcmVsYXRpb25zIGluIHRoaXMgd2F5Li4uDQpQYXJlbnQgPSBtb3RoZXIsIGZhdGhlcg0KQ2hpbGQgPSBkYXVnaHRlciwgc29uLCBzdGVwZGF1Z2h0ZXIsIHN0ZXBzb24NClNvbWUgY2hpbGRyZW4gdHJhdmVsbGVkIG9ubHkgd2l0aCBhIG5hbm55LCB0aGVyZWZvcmUgcGFyY2g9MCBmb3IgdGhlbS4NCg0KYGBge3B5dGhvbn0NCnRyYWluX2RmID0gcGQucmVhZF9jc3YoJ2lucHV0L3RyYWluLmNzdicpDQp0ZXN0X2RmID0gcGQucmVhZF9jc3YoJ2lucHV0L3Rlc3QuY3N2JykNCmNvbWJpbmUgPSBbdHJhaW5fZGYsIHRlc3RfZGZdDQpgYGANCg0KYGBge3B5dGhvbn0NCnByaW50KHRyYWluX2RmLmNvbHVtbnMudmFsdWVzKQ0KYGBgDQoNCmBgYHtweXRob259DQp0cmFpbl9kZi5pbmZvKCkNCnRlc3RfZGYuaW5mbygpDQpgYGANCg0KYGBge3B5dGhvbn0NCnRyYWluX2RmLmRlc2NyaWJlKCkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KdHJhaW5fZGYuZGVzY3JpYmUoaW5jbHVkZT1bJ08nXSkNCmBgYA0KDQrigKIJTmFtYSBiZXJzaWZhdCB1bmlrIGRpIHNlbHVydWgga3VtcHVsYW4gZGF0YSAoY291bnQ9dW5pcXVlPTg5MSkNCuKAoglWYXJpYWJlbCBqZW5pcyBrZWxhbWluIHNlYmFnYWkgZHVhIGtlbXVuZ2tpbmFuIG5pbGFpIGRlbmdhbiA2NSUgbGFraS1sYWtpIChhdGFzPWxha2ktbGFraSwgZnJla3VlbnNpPTU3Ny9oaXR1bmc9ODkxKS4NCuKAoglOaWxhaSBrYWJpbiBtZW1pbGlraSBiZWJlcmFwYSBkdXBsaWthdCBkaSBzZWx1cnVoIHNhbXBlbC4gQXRhdSBiZWJlcmFwYSBwZW51bXBhbmcgYmVyYmFnaSBrYWJpbi4NCuKAoglNZW11bGFpIG1lbmdhbWJpbCB0aWdhIGtlbXVuZ2tpbmFuIG5pbGFpLiBQb3J0IFMgZGlndW5ha2FuIG9sZWggc2ViYWdpYW4gYmVzYXIgcGVudW1wYW5nIChhdGFzPVMpDQrigKIJRml0dXIgdGlrZXQgbWVtaWxpa2kgcmFzaW8gdGluZ2dpICgyMiUpIGRhcmkgbmlsYWkgZHVwbGlrYXQgKHVuaWs9NjgxKS4NCg0KIyMgQW5hbHl6ZSBieSBwaXZvdGluZyBmZWF0dXJlcw0KDQpgYGB7cHl0aG9ufQ0KdHJhaW5fZGZbWydQY2xhc3MnLCAnU3Vydml2ZWQnXV0uZ3JvdXBieShbJ1BjbGFzcyddLCBhc19pbmRleD1GYWxzZSkubWVhbigpLnNvcnRfdmFsdWVzKGJ5PSdTdXJ2aXZlZCcsIGFzY2VuZGluZz1GYWxzZSkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KdHJhaW5fZGZbWyJTZXgiLCAiU3Vydml2ZWQiXV0uZ3JvdXBieShbJ1NleCddLCBhc19pbmRleD1GYWxzZSkubWVhbigpLnNvcnRfdmFsdWVzKGJ5PSdTdXJ2aXZlZCcsIGFzY2VuZGluZz1GYWxzZSkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KdHJhaW5fZGZbWyJTaWJTcCIsICJTdXJ2aXZlZCJdXS5ncm91cGJ5KFsnU2liU3AnXSwgYXNfaW5kZXg9RmFsc2UpLm1lYW4oKS5zb3J0X3ZhbHVlcyhieT0nU3Vydml2ZWQnLCBhc2NlbmRpbmc9RmFsc2UpDQpgYGANCg0KYGBge3B5dGhvbn0NCnRyYWluX2RmW1siUGFyY2giLCAiU3Vydml2ZWQiXV0uZ3JvdXBieShbJ1BhcmNoJ10sIGFzX2luZGV4PUZhbHNlKS5tZWFuKCkuc29ydF92YWx1ZXMoYnk9J1N1cnZpdmVkJywgYXNjZW5kaW5nPUZhbHNlKQ0KYGBgDQoNCiMjIEFuYWx5emUgYnkgdmlzdWFsaXppbmcgZGF0YQ0KDQpgYGB7cHl0aG9ufQ0KZyA9IHNucy5GYWNldEdyaWQodHJhaW5fZGYsIGNvbD0nU3Vydml2ZWQnKQ0KZy5tYXAocGx0Lmhpc3QsICdBZ2UnLCBiaW5zPTIwKQ0KYGBgDQoNCmBgYHtweXRob259DQojIGdyaWQgPSBzbnMuRmFjZXRHcmlkKHRyYWluX2RmLCBjb2w9J1BjbGFzcycsIGh1ZT0nU3Vydml2ZWQnKQ0KZ3JpZCA9IHNucy5GYWNldEdyaWQodHJhaW5fZGYsIGNvbD0nU3Vydml2ZWQnLCByb3c9J1BjbGFzcycsIGhlaWdodD0yLjIsIGFzcGVjdD0xLjYpDQpncmlkLm1hcChwbHQuaGlzdCwgJ0FnZScsIGFscGhhPS41LCBiaW5zPTIwKQ0KZ3JpZC5hZGRfbGVnZW5kKCk7DQpgYGANCg0KYGBge3B5dGhvbn0NCiMgZ3JpZCA9IHNucy5GYWNldEdyaWQodHJhaW5fZGYsIGNvbD0nRW1iYXJrZWQnKQ0KZ3JpZCA9IHNucy5GYWNldEdyaWQodHJhaW5fZGYsIHJvdz0nRW1iYXJrZWQnLCBoZWlnaHQ9Mi4yLCBhc3BlY3Q9MS42KQ0KZ3JpZC5tYXAoc25zLnBvaW50cGxvdCwgJ1BjbGFzcycsICdTdXJ2aXZlZCcsICdTZXgnLCBwYWxldHRlPSdkZWVwJykNCmdyaWQuYWRkX2xlZ2VuZCgpDQpgYGANCg0KYGBge3B5dGhvbn0NCiMgZ3JpZCA9IHNucy5GYWNldEdyaWQodHJhaW5fZGYsIGNvbD0nRW1iYXJrZWQnLCBodWU9J1N1cnZpdmVkJywgcGFsZXR0ZT17MDogJ2snLCAxOiAndyd9KQ0KZ3JpZCA9IHNucy5GYWNldEdyaWQodHJhaW5fZGYsIHJvdz0nRW1iYXJrZWQnLCBjb2w9J1N1cnZpdmVkJywgaGVpZ2h0PTIuMiwgYXNwZWN0PTEuNikNCmdyaWQubWFwKHNucy5iYXJwbG90LCAnU2V4JywgJ0ZhcmUnLCBhbHBoYT0uNSwgY2k9Tm9uZSkNCmdyaWQuYWRkX2xlZ2VuZCgpDQpgYGANCg0KIyMgV3JhbmdsZSBkYXRhDQoNCmBgYHtweXRob259DQpwcmludCgiQmVmb3JlIiwgdHJhaW5fZGYuc2hhcGUsIHRlc3RfZGYuc2hhcGUsIGNvbWJpbmVbMF0uc2hhcGUsIGNvbWJpbmVbMV0uc2hhcGUpDQoNCnRyYWluX2RmID0gdHJhaW5fZGYuZHJvcChbJ1RpY2tldCcsICdDYWJpbiddLCBheGlzPTEpDQp0ZXN0X2RmID0gdGVzdF9kZi5kcm9wKFsnVGlja2V0JywgJ0NhYmluJ10sIGF4aXM9MSkNCmNvbWJpbmUgPSBbdHJhaW5fZGYsIHRlc3RfZGZdDQoNCiJBZnRlciIsIHRyYWluX2RmLnNoYXBlLCB0ZXN0X2RmLnNoYXBlLCBjb21iaW5lWzBdLnNoYXBlLCBjb21iaW5lWzFdLnNoYXBlDQpgYGANCg0KIyMjIE9ic2VydmF0aW9uDQoNCmBgYHtweXRob259DQpmb3IgZGF0YXNldCBpbiBjb21iaW5lOg0KICAgIGRhdGFzZXRbJ1RpdGxlJ10gPSBkYXRhc2V0Lk5hbWUuc3RyLmV4dHJhY3QoJyAoW0EtWmEtel0rKVwuJywgZXhwYW5kPUZhbHNlKQ0KDQpwZC5jcm9zc3RhYih0cmFpbl9kZlsnVGl0bGUnXSwgdHJhaW5fZGZbJ1NleCddKQ0KYGBgDQoNCmBgYHtweXRob259DQpmb3IgZGF0YXNldCBpbiBjb21iaW5lOg0KICAgIGRhdGFzZXRbJ1RpdGxlJ10gPSBkYXRhc2V0WydUaXRsZSddLnJlcGxhY2UoWydMYWR5JywgJ0NvdW50ZXNzJywnQ2FwdCcsICdDb2wnLFwNCiAJJ0RvbicsICdEcicsICdNYWpvcicsICdSZXYnLCAnU2lyJywgJ0pvbmtoZWVyJywgJ0RvbmEnXSwgJ1JhcmUnKQ0KDQogICAgZGF0YXNldFsnVGl0bGUnXSA9IGRhdGFzZXRbJ1RpdGxlJ10ucmVwbGFjZSgnTWxsZScsICdNaXNzJykNCiAgICBkYXRhc2V0WydUaXRsZSddID0gZGF0YXNldFsnVGl0bGUnXS5yZXBsYWNlKCdNcycsICdNaXNzJykNCiAgICBkYXRhc2V0WydUaXRsZSddID0gZGF0YXNldFsnVGl0bGUnXS5yZXBsYWNlKCdNbWUnLCAnTXJzJykNCiAgICANCnRyYWluX2RmW1snVGl0bGUnLCAnU3Vydml2ZWQnXV0uZ3JvdXBieShbJ1RpdGxlJ10sIGFzX2luZGV4PUZhbHNlKS5tZWFuKCkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KdGl0bGVfbWFwcGluZyA9IHsiTXIiOiAxLCAiTWlzcyI6IDIsICJNcnMiOiAzLCAiTWFzdGVyIjogNCwgIlJhcmUiOiA1fQ0KZm9yIGRhdGFzZXQgaW4gY29tYmluZToNCiAgICBkYXRhc2V0WydUaXRsZSddID0gZGF0YXNldFsnVGl0bGUnXS5tYXAodGl0bGVfbWFwcGluZykNCiAgICBkYXRhc2V0WydUaXRsZSddID0gZGF0YXNldFsnVGl0bGUnXS5maWxsbmEoMCkNCg0KdHJhaW5fZGYuaGVhZCgpDQpgYGANCg0KYGBge3B5dGhvbn0NCnRyYWluX2RmID0gdHJhaW5fZGYuZHJvcChbJ05hbWUnLCAnUGFzc2VuZ2VySWQnXSwgYXhpcz0xKQ0KdGVzdF9kZiA9IHRlc3RfZGYuZHJvcChbJ05hbWUnXSwgYXhpcz0xKQ0KY29tYmluZSA9IFt0cmFpbl9kZiwgdGVzdF9kZl0NCnRyYWluX2RmLnNoYXBlLCB0ZXN0X2RmLnNoYXBlDQpgYGANCg0KYGBge3B5dGhvbn0NCmZvciBkYXRhc2V0IGluIGNvbWJpbmU6DQogICAgZGF0YXNldFsnU2V4J10gPSBkYXRhc2V0WydTZXgnXS5tYXAoIHsnZmVtYWxlJzogMSwgJ21hbGUnOiAwfSApLmFzdHlwZShpbnQpDQoNCnRyYWluX2RmLmhlYWQoKQ0KYGBgDQoNCmBgYHtweXRob259DQojIGdyaWQgPSBzbnMuRmFjZXRHcmlkKHRyYWluX2RmLCBjb2w9J1BjbGFzcycsIGh1ZT0nR2VuZGVyJykNCmdyaWQgPSBzbnMuRmFjZXRHcmlkKHRyYWluX2RmLCByb3c9J1BjbGFzcycsIGNvbD0nU2V4JywgaGVpZ2h0PTIuMiwgYXNwZWN0PTEuNikNCmdyaWQubWFwKHBsdC5oaXN0LCAnQWdlJywgYWxwaGE9LjUsIGJpbnM9MjApDQpncmlkLmFkZF9sZWdlbmQoKQ0KYGBgDQoNCmBgYHtweXRob259DQpndWVzc19hZ2VzID0gbnAuemVyb3MoKDIsMykpDQpmb3IgZGF0YXNldCBpbiBjb21iaW5lOg0KICAgIGZvciBpIGluIHJhbmdlKDAsIDIpOg0KICAgICAgICBmb3IgaiBpbiByYW5nZSgwLCAzKToNCiAgICAgICAgICAgIGd1ZXNzX2RmID0gZGF0YXNldFsoZGF0YXNldFsnU2V4J10gPT0gaSkgJiBcDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGRhdGFzZXRbJ1BjbGFzcyddID09IGorMSldWydBZ2UnXS5kcm9wbmEoKQ0KDQogICAgICAgICAgICAjIGFnZV9tZWFuID0gZ3Vlc3NfZGYubWVhbigpDQogICAgICAgICAgICAjIGFnZV9zdGQgPSBndWVzc19kZi5zdGQoKQ0KICAgICAgICAgICAgIyBhZ2VfZ3Vlc3MgPSBybmQudW5pZm9ybShhZ2VfbWVhbiAtIGFnZV9zdGQsIGFnZV9tZWFuICsgYWdlX3N0ZCkNCg0KICAgICAgICAgICAgYWdlX2d1ZXNzID0gZ3Vlc3NfZGYubWVkaWFuKCkNCg0KICAgICAgICAgICAgIyBDb252ZXJ0IHJhbmRvbSBhZ2UgZmxvYXQgdG8gbmVhcmVzdCAuNSBhZ2UNCiAgICAgICAgICAgIGd1ZXNzX2FnZXNbaSxqXSA9IGludCggYWdlX2d1ZXNzLzAuNSArIDAuNSApICogMC41DQogICAgICAgICAgICANCiAgICBmb3IgaSBpbiByYW5nZSgwLCAyKToNCiAgICAgICAgZm9yIGogaW4gcmFuZ2UoMCwgMyk6DQogICAgICAgICAgICBkYXRhc2V0LmxvY1sgKGRhdGFzZXQuQWdlLmlzbnVsbCgpKSAmIChkYXRhc2V0LlNleCA9PSBpKSAmIChkYXRhc2V0LlBjbGFzcyA9PSBqKzEpLFwNCiAgICAgICAgICAgICAgICAgICAgJ0FnZSddID0gZ3Vlc3NfYWdlc1tpLGpdDQoNCiAgICBkYXRhc2V0WydBZ2UnXSA9IGRhdGFzZXRbJ0FnZSddLmFzdHlwZShpbnQpDQoNCnRyYWluX2RmLmhlYWQoKQ0KYGBgDQoNCmBgYHtweXRob259DQp0cmFpbl9kZlsnQWdlQmFuZCddID0gcGQuY3V0KHRyYWluX2RmWydBZ2UnXSwgNSkNCnRyYWluX2RmW1snQWdlQmFuZCcsICdTdXJ2aXZlZCddXS5ncm91cGJ5KFsnQWdlQmFuZCddLCBhc19pbmRleD1GYWxzZSkubWVhbigpLnNvcnRfdmFsdWVzKGJ5PSdBZ2VCYW5kJywgYXNjZW5kaW5nPVRydWUpDQpgYGANCg0KYGBge3B5dGhvbn0NCmZvciBkYXRhc2V0IGluIGNvbWJpbmU6ICAgIA0KICAgIGRhdGFzZXQubG9jWyBkYXRhc2V0WydBZ2UnXSA8PSAxNiwgJ0FnZSddID0gMA0KICAgIGRhdGFzZXQubG9jWyhkYXRhc2V0WydBZ2UnXSA+IDE2KSAmIChkYXRhc2V0WydBZ2UnXSA8PSAzMiksICdBZ2UnXSA9IDENCiAgICBkYXRhc2V0LmxvY1soZGF0YXNldFsnQWdlJ10gPiAzMikgJiAoZGF0YXNldFsnQWdlJ10gPD0gNDgpLCAnQWdlJ10gPSAyDQogICAgZGF0YXNldC5sb2NbKGRhdGFzZXRbJ0FnZSddID4gNDgpICYgKGRhdGFzZXRbJ0FnZSddIDw9IDY0KSwgJ0FnZSddID0gMw0KICAgIGRhdGFzZXQubG9jWyBkYXRhc2V0WydBZ2UnXSA+IDY0LCAnQWdlJ10NCnRyYWluX2RmLmhlYWQoKQ0KYGBgDQoNCmBgYHtweXRob259DQp0cmFpbl9kZiA9IHRyYWluX2RmLmRyb3AoWydBZ2VCYW5kJ10sIGF4aXM9MSkNCmNvbWJpbmUgPSBbdHJhaW5fZGYsIHRlc3RfZGZdDQp0cmFpbl9kZi5oZWFkKCkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KZm9yIGRhdGFzZXQgaW4gY29tYmluZToNCiAgICBkYXRhc2V0WydGYW1pbHlTaXplJ10gPSBkYXRhc2V0WydTaWJTcCddICsgZGF0YXNldFsnUGFyY2gnXSArIDENCg0KdHJhaW5fZGZbWydGYW1pbHlTaXplJywgJ1N1cnZpdmVkJ11dLmdyb3VwYnkoWydGYW1pbHlTaXplJ10sIGFzX2luZGV4PUZhbHNlKS5tZWFuKCkuc29ydF92YWx1ZXMoYnk9J1N1cnZpdmVkJywgYXNjZW5kaW5nPUZhbHNlKQ0KYGBgDQoNCmBgYHtweXRob259DQpmb3IgZGF0YXNldCBpbiBjb21iaW5lOg0KICAgIGRhdGFzZXRbJ0lzQWxvbmUnXSA9IDANCiAgICBkYXRhc2V0LmxvY1tkYXRhc2V0WydGYW1pbHlTaXplJ10gPT0gMSwgJ0lzQWxvbmUnXSA9IDENCg0KdHJhaW5fZGZbWydJc0Fsb25lJywgJ1N1cnZpdmVkJ11dLmdyb3VwYnkoWydJc0Fsb25lJ10sIGFzX2luZGV4PUZhbHNlKS5tZWFuKCkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KdHJhaW5fZGYgPSB0cmFpbl9kZi5kcm9wKFsnUGFyY2gnLCAnU2liU3AnLCAnRmFtaWx5U2l6ZSddLCBheGlzPTEpDQp0ZXN0X2RmID0gdGVzdF9kZi5kcm9wKFsnUGFyY2gnLCAnU2liU3AnLCAnRmFtaWx5U2l6ZSddLCBheGlzPTEpDQpjb21iaW5lID0gW3RyYWluX2RmLCB0ZXN0X2RmXQ0KDQp0cmFpbl9kZi5oZWFkKCkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KZm9yIGRhdGFzZXQgaW4gY29tYmluZToNCiAgICBkYXRhc2V0WydBZ2UqQ2xhc3MnXSA9IGRhdGFzZXQuQWdlICogZGF0YXNldC5QY2xhc3MNCg0KdHJhaW5fZGYubG9jWzosIFsnQWdlKkNsYXNzJywgJ0FnZScsICdQY2xhc3MnXV0uaGVhZCgxMCkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KZnJlcV9wb3J0ID0gdHJhaW5fZGYuRW1iYXJrZWQuZHJvcG5hKCkubW9kZSgpWzBdDQpmb3IgZGF0YXNldCBpbiBjb21iaW5lOg0KICAgIGRhdGFzZXRbJ0VtYmFya2VkJ10gPSBkYXRhc2V0WydFbWJhcmtlZCddLmZpbGxuYShmcmVxX3BvcnQpDQogICAgDQp0cmFpbl9kZltbJ0VtYmFya2VkJywgJ1N1cnZpdmVkJ11dLmdyb3VwYnkoWydFbWJhcmtlZCddLCBhc19pbmRleD1GYWxzZSkubWVhbigpLnNvcnRfdmFsdWVzKGJ5PSdTdXJ2aXZlZCcsIGFzY2VuZGluZz1GYWxzZSkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KZm9yIGRhdGFzZXQgaW4gY29tYmluZToNCiAgICBkYXRhc2V0WydFbWJhcmtlZCddID0gZGF0YXNldFsnRW1iYXJrZWQnXS5tYXAoIHsnUyc6IDAsICdDJzogMSwgJ1EnOiAyfSApLmFzdHlwZShpbnQpDQoNCnRyYWluX2RmLmhlYWQoKQ0KYGBgDQoNCmBgYHtweXRob259DQp0ZXN0X2RmWydGYXJlJ10uZmlsbG5hKHRlc3RfZGZbJ0ZhcmUnXS5kcm9wbmEoKS5tZWRpYW4oKSwgaW5wbGFjZT1UcnVlKQ0KdGVzdF9kZi5oZWFkKCkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KdHJhaW5fZGZbJ0ZhcmVCYW5kJ10gPSBwZC5xY3V0KHRyYWluX2RmWydGYXJlJ10sIDQpDQp0cmFpbl9kZltbJ0ZhcmVCYW5kJywgJ1N1cnZpdmVkJ11dLmdyb3VwYnkoWydGYXJlQmFuZCddLCBhc19pbmRleD1GYWxzZSkubWVhbigpLnNvcnRfdmFsdWVzKGJ5PSdGYXJlQmFuZCcsIGFzY2VuZGluZz1UcnVlKQ0KYGBgDQoNCmBgYHtweXRob259DQpmb3IgZGF0YXNldCBpbiBjb21iaW5lOg0KICAgIGRhdGFzZXQubG9jWyBkYXRhc2V0WydGYXJlJ10gPD0gNy45MSwgJ0ZhcmUnXSA9IDANCiAgICBkYXRhc2V0LmxvY1soZGF0YXNldFsnRmFyZSddID4gNy45MSkgJiAoZGF0YXNldFsnRmFyZSddIDw9IDE0LjQ1NCksICdGYXJlJ10gPSAxDQogICAgZGF0YXNldC5sb2NbKGRhdGFzZXRbJ0ZhcmUnXSA+IDE0LjQ1NCkgJiAoZGF0YXNldFsnRmFyZSddIDw9IDMxKSwgJ0ZhcmUnXSAgID0gMg0KICAgIGRhdGFzZXQubG9jWyBkYXRhc2V0WydGYXJlJ10gPiAzMSwgJ0ZhcmUnXSA9IDMNCiAgICBkYXRhc2V0WydGYXJlJ10gPSBkYXRhc2V0WydGYXJlJ10uYXN0eXBlKGludCkNCg0KdHJhaW5fZGYgPSB0cmFpbl9kZi5kcm9wKFsnRmFyZUJhbmQnXSwgYXhpcz0xKQ0KY29tYmluZSA9IFt0cmFpbl9kZiwgdGVzdF9kZl0NCiAgICANCnRyYWluX2RmLmhlYWQoMTApDQpgYGANCg0KYGBge3B5dGhvbn0NCnRlc3RfZGYuaGVhZCgxMCkNCmBgYA0KDQojIyBNb2RlbCwgUHJlZGljdCBhbmQgU29sdmUNCg0KYGBge3B5dGhvbn0NClhfdHJhaW4gPSB0cmFpbl9kZi5kcm9wKCJTdXJ2aXZlZCIsIGF4aXM9MSkNCllfdHJhaW4gPSB0cmFpbl9kZlsiU3Vydml2ZWQiXQ0KWF90ZXN0ICA9IHRlc3RfZGYuZHJvcCgiUGFzc2VuZ2VySWQiLCBheGlzPTEpLmNvcHkoKQ0KWF90cmFpbi5zaGFwZSwgWV90cmFpbi5zaGFwZSwgWF90ZXN0LnNoYXBlDQpgYGANCg0KIyMjIExvZ2lzdGljIFJlZ3Jlc3Npb24NCg0KUmVncmVzaSBMb2dpc3RpayBhZGFsYWggbW9kZWwgeWFuZyBiZXJndW5hIHVudHVrIGRpamFsYW5rYW4gZGkgYXdhbCBwYWRhIHdvcmtmbG93LiBSZWdyZXNpIGxvZ2lzdGlrIG1lbmd1a3VyIGh1YnVuZ2FuIGFudGFyYSB2YXJpYWJlbCBkZXBlbmRlbiBrYXRlZ29yaSAoZml0dXIpIGRhbiBzYXR1IGF0YXUgbGViaWggdmFyaWFiZWwgaW5kZXBlbmRlbiAoZml0dXIpIGRlbmdhbiBtZW1wZXJraXJha2FuIHByb2JhYmlsaXRhcyBtZW5nZ3VuYWthbiBmdW5nc2kgbG9naXN0aWssIHlhbmcgbWVydXBha2FuIGRpc3RyaWJ1c2kgbG9naXN0aWsga3VtdWxhdGlmLg0KDQpgYGB7cHl0aG9ufQ0KbG9ncmVnID0gTG9naXN0aWNSZWdyZXNzaW9uKCkNCmxvZ3JlZy5maXQoWF90cmFpbiwgWV90cmFpbikNCllfcHJlZCA9IGxvZ3JlZy5wcmVkaWN0KFhfdGVzdCkNCmFjY19sb2cgPSByb3VuZChsb2dyZWcuc2NvcmUoWF90cmFpbiwgWV90cmFpbikgKiAxMDAsIDIpDQphY2NfbG9nDQpgYGANCg0KYGBge3B5dGhvbn0NCmNvZWZmX2RmID0gcGQuRGF0YUZyYW1lKHRyYWluX2RmLmNvbHVtbnMuZGVsZXRlKDApKQ0KY29lZmZfZGYuY29sdW1ucyA9IFsnRmVhdHVyZSddDQpjb2VmZl9kZlsiQ29ycmVsYXRpb24iXSA9IHBkLlNlcmllcyhsb2dyZWcuY29lZl9bMF0pDQoNCmNvZWZmX2RmLnNvcnRfdmFsdWVzKGJ5PSdDb3JyZWxhdGlvbicsIGFzY2VuZGluZz1GYWxzZSkNCmBgYA0KDQoNCiMjIyBLTk4gb3Igay1OZWFyZXN0IE5laWdoYm9ycw0KDQpEYWxhbSBwZW5nZW5hbGFuIHBvbGEsIGFsZ29yaXRtYSBrLU5lYXJlc3QgTmVpZ2hib3JzIChhdGF1IHNpbmdrYXRueWEgay1OTikgYWRhbGFoIG1ldG9kZSBub24tcGFyYW1ldHJpayB5YW5nIGRpZ3VuYWthbiB1bnR1ayBrbGFzaWZpa2FzaSBkYW4gcmVncmVzaS4gU2VidWFoIHNhbXBlbCBkaWtsYXNpZmlrYXNpa2FuIGJlcmRhc2Fya2FuIHN1YXJhIG1heW9yaXRhcyB0ZXJkZWthdG55YSwgZGVuZ2FuIHNhbXBlbCBkaW1hc3VrYW4ga2Uga2VsYXMgeWFuZyBwYWxpbmcgdW11bSBkaSBhbnRhcmEgayB0ZXRhbmdnYSB0ZXJkZWthdG55YSAoayBhZGFsYWggYmlsYW5nYW4gYnVsYXQgcG9zaXRpZiwgYmlhc2FueWEga2VjaWwpLiBKaWthIGsgPSAxLCBtYWthIG9iamVrIGhhbnlhIGRpdHVnYXNrYW4ga2Uga2VsYXMgdGV0YW5nZ2EgdGVyZGVrYXQuDQoNCmBgYHtweXRob259DQprbm4gPSBLTmVpZ2hib3JzQ2xhc3NpZmllcihuX25laWdoYm9ycyA9IDMpDQprbm4uZml0KFhfdHJhaW4sIFlfdHJhaW4pDQpZX3ByZWQgPSBrbm4ucHJlZGljdChYX3Rlc3QpDQphY2Nfa25uID0gcm91bmQoa25uLnNjb3JlKFhfdHJhaW4sIFlfdHJhaW4pICogMTAwLCAyKQ0KYWNjX2tubg0KYGBgDQoNCiMjIyBHYXVzc2lhbiBOYWl2ZSBCYXllcw0KDQpEYWxhbSBwZW1iZWxhamFyYW4gbWVzaW4sIHBlbmdrbGFzaWZpa2FzaSBuYWl2ZSBCYXllcyBhZGFsYWggc2VqZW5pcyBwZW5na2xhc2lmaWthc2kgcHJvYmFiaWxpc3RpayBzZWRlcmhhbmEgYmVyZGFzYXJrYW4gcGVuZXJhcGFuIHRlb3JlbWEgQmF5ZXMgZGVuZ2FuIGFzdW1zaSBpbmRlcGVuZGVuc2kgeWFuZyBrdWF0IChuYWlmKSBkaSBhbnRhcmEgZml0dXItZml0dXJueWEuIFBlbmdrbGFzaWZpa2FzaSBOYWl2ZSBCYXllcyBzYW5nYXQgdGVydWt1ciwgbWVtYnV0dWhrYW4gc2VqdW1sYWggcGFyYW1ldGVyIGxpbmllciBkYWxhbSBqdW1sYWggdmFyaWFiZWwgKGZpdHVyKSBkYWxhbSBtYXNhbGFoIHBlbWJlbGFqYXJhbi4NCg0KYGBge3B5dGhvbn0NCmdhdXNzaWFuID0gR2F1c3NpYW5OQigpDQpnYXVzc2lhbi5maXQoWF90cmFpbiwgWV90cmFpbikNCllfcHJlZCA9IGdhdXNzaWFuLnByZWRpY3QoWF90ZXN0KQ0KYWNjX2dhdXNzaWFuID0gcm91bmQoZ2F1c3NpYW4uc2NvcmUoWF90cmFpbiwgWV90cmFpbikgKiAxMDAsIDIpDQphY2NfZ2F1c3NpYW4NCmBgYA0KDQoNCiMjIyBMaW5lYXIgU1ZDIChTdXBwb3J0IFZlY3RvciBDbGFzc2lmaWNhdGlvbikNCg0KTWV0b2RlIExpbmVhciBTdXBwb3J0IFZlY3RvciBDbGFzc2lmaWVyIChTVkMpIG1lbmVyYXBrYW4gZnVuZ3NpIGtlcm5lbCBsaW5pZXIgdW50dWsgbWVsYWt1a2FuIGtsYXNpZmlrYXNpIGRhbiBiZWtlcmphIGRlbmdhbiBiYWlrIGRlbmdhbiBqdW1sYWggc2FtcGVsIHlhbmcgYmFueWFrLg0KDQpgYGB7cHl0aG9ufQ0KbGluZWFyX3N2YyA9IExpbmVhclNWQygpDQpsaW5lYXJfc3ZjLmZpdChYX3RyYWluLCBZX3RyYWluKQ0KWV9wcmVkID0gbGluZWFyX3N2Yy5wcmVkaWN0KFhfdGVzdCkNCmFjY19saW5lYXJfc3ZjID0gcm91bmQobGluZWFyX3N2Yy5zY29yZShYX3RyYWluLCBZX3RyYWluKSAqIDEwMCwgMikNCmFjY19saW5lYXJfc3ZjDQpgYGANCg0KIyMjIERlY2lzaW9uIFRyZWUNCg0KTW9kZWwgaW5pIG1lbmdndW5ha2FuIHBvaG9uIGtlcHV0dXNhbiBzZWJhZ2FpIG1vZGVsIHByZWRpa3NpIHlhbmcgbWVtZXRha2FuIGZpdHVyIChjYWJhbmcgcG9ob24pIGtlIGtlc2ltcHVsYW4gdGVudGFuZyBuaWxhaSB0YXJnZXQgKGRhdW4gcG9ob24pLiBNb2RlbCBwb2hvbiBkaSBtYW5hIHZhcmlhYmVsIHRhcmdldCBkYXBhdCBtZW5nYW1iaWwgc2VrdW1wdWxhbiBuaWxhaSB0ZXJiYXRhcyBkaXNlYnV0IHBvaG9uIGtsYXNpZmlrYXNpOyBkYWxhbSBzdHJ1a3R1ciBwb2hvbiBpbmksIGRhdW4gbWV3YWtpbGkgbGFiZWwga2VsYXMgZGFuIGNhYmFuZyBtZXdha2lsaSBrb25qdW5nc2kgZml0dXIgeWFuZyBtZW5nYXJhaCBrZSBsYWJlbCBrZWxhcyB0ZXJzZWJ1dC4gUG9ob24ga2VwdXR1c2FuIGRpIG1hbmEgdmFyaWFiZWwgdGFyZ2V0IGRhcGF0IG1lbmdhbWJpbCBuaWxhaSBrb250aW51IChiaWFzYW55YSBiaWxhbmdhbiByZWFsKSBkaXNlYnV0IHBvaG9uIHJlZ3Jlc2kuDQoNCmBgYHtweXRob259DQpkZWNpc2lvbl90cmVlID0gRGVjaXNpb25UcmVlQ2xhc3NpZmllcigpDQpkZWNpc2lvbl90cmVlLmZpdChYX3RyYWluLCBZX3RyYWluKQ0KWV9wcmVkID0gZGVjaXNpb25fdHJlZS5wcmVkaWN0KFhfdGVzdCkNCmFjY19kZWNpc2lvbl90cmVlID0gcm91bmQoZGVjaXNpb25fdHJlZS5zY29yZShYX3RyYWluLCBZX3RyYWluKSAqIDEwMCwgMikNCmFjY19kZWNpc2lvbl90cmVlDQpgYGANCg0KIyMjIFJhbmRvbSBGb3Jlc3QNCg0KTW9kZWwgUmFuZG9tIEZvcmVzdCBiZXJpa3V0bnlhIGFkYWxhaCBzYWxhaCBzYXR1IHlhbmcgcGFsaW5nIHBvcHVsZXIuIFJhbmRvbSBGb3Jlc3QgYXRhdSBodXRhbiBrZXB1dHVzYW4gYWNhayBhZGFsYWggbWV0b2RlIHBlbWJlbGFqYXJhbiBhbnNhbWJlbCB1bnR1ayBrbGFzaWZpa2FzaSwgcmVncmVzaSwgZGFuIHR1Z2FzIGxhaW5ueWEsIHlhbmcgYmVyb3BlcmFzaSBkZW5nYW4gbWVtYmFuZ3VuIGJhbnlhayBwb2hvbiBrZXB1dHVzYW4gKG5fZXN0aW1hdG9yPTEwMCkgcGFkYSB3YWt0dSBwZWxhdGloYW4gZGFuIG1lbmdlbHVhcmthbiBrZWxhcyB5YW5nIG1lcnVwYWthbiBtb2RlIGtlbGFzIChrbGFzaWZpa2FzaSkgYXRhdSBwcmVkaWtzaSByYXRhLXJhdGEgKHJlZ3Jlc2kpIGRhcmkgbWFzaW5nLW1hc2luZyBwb2hvbi4NCg0KYGBge3B5dGhvbn0NCnJhbmRvbV9mb3Jlc3QgPSBSYW5kb21Gb3Jlc3RDbGFzc2lmaWVyKG5fZXN0aW1hdG9ycz0xMDApDQpyYW5kb21fZm9yZXN0LmZpdChYX3RyYWluLCBZX3RyYWluKQ0KWV9wcmVkID0gcmFuZG9tX2ZvcmVzdC5wcmVkaWN0KFhfdGVzdCkNCnJhbmRvbV9mb3Jlc3Quc2NvcmUoWF90cmFpbiwgWV90cmFpbikNCmFjY19yYW5kb21fZm9yZXN0ID0gcm91bmQocmFuZG9tX2ZvcmVzdC5zY29yZShYX3RyYWluLCBZX3RyYWluKSAqIDEwMCwgMikNCmFjY19yYW5kb21fZm9yZXN0DQpgYGANCg0KIyMgTW9kZWwgRXZhbHVhdGlvbg0KDQpLaXRhIHNla2FyYW5nIGRhcGF0IG1lbWJlcmkgcGVyaW5na2F0IGV2YWx1YXNpIGtpdGEgdGVyaGFkYXAgc2VtdWEgbW9kZWwgdW50dWsgbWVtaWxpaCB5YW5nIHRlcmJhaWsgdW50dWsgbWFzYWxhaCBpbmkuDQoNCmBgYHtweXRob259DQptb2RlbHMgPSBwZC5EYXRhRnJhbWUoew0KICAgICdNb2RlbCc6IFsgJ0tOTicsICdMb2dpc3RpYyBSZWdyZXNzaW9uJywgDQogICAgICAgICAgICAgICdSYW5kb20gRm9yZXN0JywgJ05haXZlIEJheWVzJywgJ0xpbmVhciBTVkMnLCANCiAgICAgICAgICAgICAgJ0RlY2lzaW9uIFRyZWUnXSwNCiAgICAnU2NvcmUnOiBbIGFjY19rbm4sIGFjY19sb2csIA0KICAgICAgICAgICAgICBhY2NfcmFuZG9tX2ZvcmVzdCwgYWNjX2dhdXNzaWFuLCANCiAgICAgICAgICAgICAgIGFjY19saW5lYXJfc3ZjLCBhY2NfZGVjaXNpb25fdHJlZV19KQ0KbW9kZWxzLnNvcnRfdmFsdWVzKGJ5PSdTY29yZScsIGFzY2VuZGluZz1GYWxzZSkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0Kc3VibWlzc2lvbiA9IHBkLkRhdGFGcmFtZSh7DQogICAgICAgICJQYXNzZW5nZXJJZCI6IHRlc3RfZGZbIlBhc3NlbmdlcklkIl0sDQogICAgICAgICJTdXJ2aXZlZCI6IFlfcHJlZA0KICAgIH0pDQpzdWJtaXNzaW9uDQpgYGANCg0KIyBVbnN1cGVydmlzZWQgTGVhcm5pbmcNCg0KIyMgSW5wdXQgRGF0YQ0KDQpgYGB7cHl0aG9ufQ0KaW1wb3J0IG51bXB5IGFzIG5wIA0KaW1wb3J0IHBhbmRhcyBhcyBwZCANCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQgIyBmb3IgZGF0YSB2aXN1YWxpemF0aW9uDQppbXBvcnQgc2VhYm9ybiBhcyBzbnMgIyBmb3Igc3RhdGlzdGljYWwgZGF0YSB2aXN1YWxpemF0aW9uDQpgYGANCg0KYGBge3B5dGhvbn0NCmRmID0gcGQucmVhZF9jc3YoJ2lucHV0L0xpdmUuY3N2JykNCmRmLmluZm8oKQ0KYGBgDQpgYGB7cHl0aG9ufQ0KZGYuaXNudWxsKCkuc3VtKCkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KZGYuZHJvcChbJ0NvbHVtbjEnLCAnQ29sdW1uMicsICdDb2x1bW4zJywgJ0NvbHVtbjQnXSwgYXhpcz0xLCBpbnBsYWNlPVRydWUpDQpgYGANCg0KIyMgRURBDQoNCmBgYHtweXRob259DQpsZW4oZGZbJ3N0YXR1c19pZCddLnVuaXF1ZSgpKQ0KbGVuKGRmWydzdGF0dXNfcHVibGlzaGVkJ10udW5pcXVlKCkpDQpsZW4oZGZbJ3N0YXR1c190eXBlJ10udW5pcXVlKCkpDQpgYGANCg0KYGBge3B5dGhvbn0NCmRmLmRyb3AoWydzdGF0dXNfaWQnLCAnc3RhdHVzX3B1Ymxpc2hlZCddLCBheGlzPTEsIGlucGxhY2U9VHJ1ZSkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KZGYuaW5mbygpDQpgYGANCg0KYGBge3B5dGhvbn0NCmRmLmhlYWQoKQ0KYGBgDQoNCiMjIENvbnZlcnQgY2F0ZWdvcmljYWwgdmFyaWFibGUgaW50byBpbnRlZ2Vycw0KDQpgYGB7cHl0aG9ufQ0KWCA9IGRmDQp5ID0gZGZbJ3N0YXR1c190eXBlJ10NCg0KZnJvbSBza2xlYXJuLnByZXByb2Nlc3NpbmcgaW1wb3J0IExhYmVsRW5jb2Rlcg0KDQpsZSA9IExhYmVsRW5jb2RlcigpDQpYWydzdGF0dXNfdHlwZSddID0gbGUuZml0X3RyYW5zZm9ybShYWydzdGF0dXNfdHlwZSddKQ0KeSA9IGxlLnRyYW5zZm9ybSh5KQ0KWC5oZWFkKCkNCmBgYA0KDQojIyBGZWF0dXJlIFNjYWxpbmcgDQoNCmBgYHtweXRob259DQpjb2xzID0gWC5jb2x1bW5zDQoNCmZyb20gc2tsZWFybi5wcmVwcm9jZXNzaW5nIGltcG9ydCBNaW5NYXhTY2FsZXINCg0KbXMgPSBNaW5NYXhTY2FsZXIoKQ0KWCA9IG1zLmZpdF90cmFuc2Zvcm0oWCkNClggPSBwZC5EYXRhRnJhbWUoWCwgY29sdW1ucz1bY29sc10pDQpYLmhlYWQoKQ0KYGBgDQoNCiMjIEstTWVhbnMgbW9kZWwgDQoNClVzZSBlbGJvdyBtZXRob2QgdG8gZmluZCBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycw0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBza2xlYXJuLmNsdXN0ZXIgaW1wb3J0IEtNZWFucw0KY3MgPSBbXQ0KZm9yIGkgaW4gcmFuZ2UoMSwgMTEpOg0KICAgIGttZWFucyA9IEtNZWFucyhuX2NsdXN0ZXJzID0gaSwgaW5pdCA9ICdrLW1lYW5zKysnLCBtYXhfaXRlciA9IDMwMCwgbl9pbml0ID0gMTAsIHJhbmRvbV9zdGF0ZSA9IDApDQogICAga21lYW5zLmZpdChYKQ0KICAgIGNzLmFwcGVuZChrbWVhbnMuaW5lcnRpYV8pDQpwbHQucGxvdChyYW5nZSgxLCAxMSksIGNzKQ0KcGx0LnRpdGxlKCdUaGUgRWxib3cgTWV0aG9kJykNCnBsdC54bGFiZWwoJ051bWJlciBvZiBjbHVzdGVycycpDQpwbHQueWxhYmVsKCdDUycpDQpwbHQuc2hvdygpDQpgYGANCg0KYGBge3B5dGhvbn0NCmZyb20gc2tsZWFybi5jbHVzdGVyIGltcG9ydCBLTWVhbnMNCg0Ka21lYW5zID0gS01lYW5zKG5fY2x1c3RlcnM9MixyYW5kb21fc3RhdGU9MCkNCg0Ka21lYW5zLmZpdChYKQ0KDQpsYWJlbHMgPSBrbWVhbnMubGFiZWxzXw0KDQojIGNoZWNrIGhvdyBtYW55IG9mIHRoZSBzYW1wbGVzIHdlcmUgY29ycmVjdGx5IGxhYmVsZWQNCg0KY29ycmVjdF9sYWJlbHMgPSBzdW0oeSA9PSBsYWJlbHMpDQoNCnByaW50KCJSZXN1bHQ6ICVkIG91dCBvZiAlZCBzYW1wbGVzIHdlcmUgY29ycmVjdGx5IGxhYmVsZWQuIiAlIChjb3JyZWN0X2xhYmVscywgeS5zaXplKSkNCg0KcHJpbnQoJ0FjY3VyYWN5IHNjb3JlOiB7MDowLjJmfScuIGZvcm1hdChjb3JyZWN0X2xhYmVscy9mbG9hdCh5LnNpemUpKSkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0Ka21lYW5zID0gS01lYW5zKG5fY2x1c3RlcnM9MywgcmFuZG9tX3N0YXRlPTApDQoNCmttZWFucy5maXQoWCkNCg0KIyBjaGVjayBob3cgbWFueSBvZiB0aGUgc2FtcGxlcyB3ZXJlIGNvcnJlY3RseSBsYWJlbGVkDQpsYWJlbHMgPSBrbWVhbnMubGFiZWxzXw0KDQpjb3JyZWN0X2xhYmVscyA9IHN1bSh5ID09IGxhYmVscykNCnByaW50KCJSZXN1bHQ6ICVkIG91dCBvZiAlZCBzYW1wbGVzIHdlcmUgY29ycmVjdGx5IGxhYmVsZWQuIiAlIChjb3JyZWN0X2xhYmVscywgeS5zaXplKSkNCnByaW50KCdBY2N1cmFjeSBzY29yZTogezA6MC4yZn0nLiBmb3JtYXQoY29ycmVjdF9sYWJlbHMvZmxvYXQoeS5zaXplKSkpDQpgYGANCg0KYGBge3B5dGhvbn0NCmttZWFucyA9IEtNZWFucyhuX2NsdXN0ZXJzPTQsIHJhbmRvbV9zdGF0ZT0wKQ0KDQprbWVhbnMuZml0KFgpDQoNCiMgY2hlY2sgaG93IG1hbnkgb2YgdGhlIHNhbXBsZXMgd2VyZSBjb3JyZWN0bHkgbGFiZWxlZA0KbGFiZWxzID0ga21lYW5zLmxhYmVsc18NCg0KY29ycmVjdF9sYWJlbHMgPSBzdW0oeSA9PSBsYWJlbHMpDQpwcmludCgiUmVzdWx0OiAlZCBvdXQgb2YgJWQgc2FtcGxlcyB3ZXJlIGNvcnJlY3RseSBsYWJlbGVkLiIgJSAoY29ycmVjdF9sYWJlbHMsIHkuc2l6ZSkpDQpwcmludCgnQWNjdXJhY3kgc2NvcmU6IHswOjAuMmZ9Jy4gZm9ybWF0KGNvcnJlY3RfbGFiZWxzL2Zsb2F0KHkuc2l6ZSkpKQ0KYGBgDQoNCiMgUmVmZXJlbmNlDQoNCjEuIGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vY29kZS9zdGFydHVwc2NpL3RpdGFuaWMtZGF0YS1zY2llbmNlLXNvbHV0aW9ucy9ub3RlYm9vaw0KDQoyLiBodHRwczovL3d3dy5rYWdnbGUuY29tL2NvZGUvcHJhc2hhbnQxMTEvay1tZWFucy1jbHVzdGVyaW5nLXdpdGgtcHl0aG9uL25vdGVib29rDQo=