import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from time import time
%autosave 300
%matplotlib notebook
Name | Data Type | Measurement | Description |
---|---|---|---|
Elevation | quantitative | meters | Elevation in meters |
Aspect | quantitative | azimuth 0 to 360 | Aspect in degrees azimuth |
Slope | quantitative | degrees | Slope in degrees |
Horizontal_Distance_To_Hydrology | quantitative | meters | Horz Dist to nearest surface water features |
Vertical_Distance_To_Hydrology | quantitative | meters | Vert Dist to nearest surface water features |
Horizontal_Distance_To_Roadways | quantitative | meters | Horz Dist to nearest roadway |
Hillshade_9am | quantitative | 0 to 255 index | Hillshade index at 9am, summer solstice |
Hillshade_Noon | quantitative | 0 to 255 index | Hillshade index at noon, summer soltice |
Hillshade_3pm | quantitative | 0 to 255 index | Hillshade index at 3pm, summer solstice |
Horizontal_Distance_To_Fire_Points | quantitative | meters | Horz Dist to nearest wildfire ignition points |
Wilderness_Area (4 binary columns) | qualitative | 0 (absence) or 1 (presence) | Wilderness area designation |
Soil_Type (40 binary columns) | qualitative | 0 (absence) or 1 (presence) | Soil Type designation |
Cover_Type (7 types) | integer | 1 to 7 | Forest Cover Type designation |
names = ['Elevation', 'Aspect', 'Slope', 'Horizontal_Distance_To_Hydrology',
'Vertical_Distance_To_Hydrology', 'Horizontal_Distance_To_Roadways',
'Hillshade_9am', 'Hillshade_Noon', 'Hillshade_3pm',
'Horizontal_Distance_To_Fire_Points', 'Wilderness_Area1',
'Wilderness_Area2', 'Wilderness_Area3', 'Wilderness_Area4',
]
names = names + ['Soil{}'.format(i) for i in range(1,41)] + ['Target']
t0 = time()
dataset = pd.read_csv('covtype.data', header=None, names=names)
print('Import Time: {:.2f}s'.format(time() - t0))
print(dataset.shape)
dataset.head()
dataset.iloc[:, :10].describe()
Para as colunas quantitativas talvez seja necessário criar uma escala dos valores, de forma que tais colunas não recebam mais importância que as demais.
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
pca = TSNE(n_components=2)
print('Iniciando redução de Dimensionalidade')
vis = pca.fit_transform(data.iloc[:1000, :-1], dataset.iloc[:1000, -1])
print('TSNE complete!')
data_array = np.array(dataset.iloc[:1000])
colors = {1:'red', 2: 'orange', 3: 'yellow', 4:'green', 5:'blue', 6:'indigo', 7:'violet'}
print(vis.shape[0])
for i in range(vis.shape[0]):
print(i, end='\r')
plt.scatter(vis[i,0], vis[i,1], c=colors[data_array[i,-1]])
print('Plot Complete')
plt.title('TSNE')
plt.show()
import seaborn
def plot_corr_matrix(data,annot=False):
seaborn.heatmap(data.corr(), cmap="YlGnBu", annot=annot)
plt.show()
plot_corr_matrix(data)
def plot_coor_matrix(corr_matrix, headers=None, title=''):
headers = headers or corr_matrix.columns
plt.matshow(corr_matrix)
plt.xticks(range(len(corr_matrix)), headers, rotation='vertical')
plt.yticks(range(len(corr_matrix)), headers)
clb = plt.colorbar()
print('\t'+title)
plt.show()
matrix_corr = dataset.iloc[:, :10].corr()
headers = names
plot_coor_matrix(matrix_corr, headers, 'Pearson Corr')
def test_corr(matrix_corr, verbose=1):
size = len(matrix_corr)
reject = []
for i in matrix_corr.columns:
for j in matrix_corr.columns:
if (i == j):
continue
x = np.fabs(matrix_corr[i][j])
not_rejected = ((i, j) not in reject) and ((j, i) not in reject)
if ((x > 0.798) and not_rejected):
reject.append((i, j))
if (verbose > 0):
print('Reject either \'{0}\' or \'{1}\'.'.format(
i, j
))
if len(reject) < 1:
print('No colums to reject.')
return reject
reject_index = test_corr(matrix_corr)
Visto os resultados da avaliação por correlação, nenhuma das colunas necessita ser retirada.
target_sums = [(dataset.loc[:, 'Target'] == i).sum() for i in range(1, 8)]
target_perc = [(target_sums[i] * 100) / dataset.shape[0] for i in range(len(target_sums))]
for i in range(len(target_perc)):
print('Class {} has {}\tsamples which is\t{:.3f}%\tof the total'.format(i+1,
target_sums[i],
target_perc[i]
))
O dataset necessita de uma re-distribuição, visto que a classe 4 é quase insignificante sob o todo e as classes 1 e 2 se sobressaem em quantidade.
Após a análise sobre as características quantitativas, partimos para a análise das demais caracteristicas, que representam 80% das caracteristicas.
Primeiramente, plotamos um gráfico de violino, que analisa a distribuição de cada caracteristica por classe. Sendo assim, podemos ver como uma feature se apresenta dentro de uma classe e como cada uma esta distinta das demais, baseando-se em apenas uma feature.
import seaborn as sns
%matplotlib inline
for i in range(0, dataset.shape[1]):
fig = sns.violinplot(data=dataset, x='Target', y=dataset.columns[i])
plt.show()
plt.close()
Após a análise de distribuição por meio de violinos, podemos visualizar essa mesma distribuição para caracteristicas qualitativas no formato de gráfico de barras, onde vemos a quantidade da recorrência que ocorre a aparição positiva de uma feature em determinada classe.
def calc_binary_hit(dataset, columns):
hit = []
rows = [dataset[dataset['Target'] == i].index.tolist() for i in range(1,8)]
for i, col in enumerate(columns):
col_i = 10 + i
h = [(dataset.iloc[row, col_i] == 1).sum() for row in rows]
hit.append((col, h))
return hit
hit = calc_binary_hit(dataset, dataset.columns[10:])
hit
def plot_count(hit):
%matplotlib inline
ind = np.arange(1,8)
for col, hit_map in hit:
fig, ax = plt.subplots()
plt.bar(ind, hit_map)
ax.set_xticks(ind)
ax.set_title('{} distribution'.format(col))
ax.set_xlabel('Target')
plt.show()
plt.close()
plot_count(hit)
Com isso podemos visualizar algumas coisas.
Uma boa prática é sempre checar seu conjunto de dados para amostras que possuem alguma forma de inconsistência.
for i in range(len(dataset)):
qualitative_features = sum(dataset.iloc[i, 10:])
if qualitative_features == 0:
print('Remove index {}'.format(i))
else:
print('checked {}'.format(i), end='\r')
print('Finished Checking')
from sklearn.feature_selection import SelectFromModel
from sklearn.tree import DecisionTreeClassifier
select = SelectFromModel(DecisionTreeClassifier())
data_selection = select.fit_transform(dataset.iloc[:,:-1], dataset.iloc[:,-1])
data_base = pd.DataFrame(data_selection)
data_base['classe'] = dataset.iloc[:,-1]
data_base.describe()
data_base.to_csv('ignores/feature_selection.csv')<div id="preloader"></div>
<script>
JQuery(document).ready(function($) {
$(window).load(function() {
$('#preloader').fadeOut('slow', function() {
$(this).remove();
});
});
});
</script>
<style type="text/css">
div#preloader {
position: fixed;
left: 0;
top: 0;
z-index: 999;
width: 100%;
overflow: visible;
background: #fff url('http://preloaders.net/preloaders/720/Moving%20line.gif') no-repeat center center;
}
</style><div class="floating-left">
<form action="javascript:code_toggle()">
<input type="submit" id="codeButton" value="Mostrar Código" class="showButton">
</form>
<form action="javascript:out_toggle()">
<input type="submit" id="outButton" value="Mostrar Output" class="showButton">
</form>
</div>
<script>
function code_toggle() {
if (code_shown) {
$('div.input').hide(500);
$('#codeButton').val('Mostrar Código')
} else {
$('div.input').show(500);
$('#codeButton').val('Esconder Código')
}
code_shown = !code_shown
}
function out_toggle() {
if (out_shown) {
$('div.output_stream').hide(500);
$('div.output_stdout').hide(500);
$('div.output_text').hide(500);
$('#outButton').val('Mostrar Output')
} else {
$('div.output_stream').show(500);
$('div.output_stdout').show(500);
$('div.output_text').show(500);
$('#outButton').val('Esconder Output')
}
out_shown = !out_shown
}
$(document).ready(function() {
code_shown = false;
out_shown = false;
$('div.input').hide();
$('div.output_stream').hide();
$('div.output_stdout').hide();
$('div.output_text').hide();
})
</script>
<style type="text/css">
.floating-left {
display: flex;
flex-direction: column;
flex-flow: column;
flex-wrap: wrap;
height: 100px;
position: fixed;
justify-content: space-between;
left: 15px;
}
.showButton{
border : solid 2px rgba(255, 255, 255, 0.15);
border-radius : 15px;
-webkit-box-shadow : 6px 6px 5px rgba(0,0,0,0.1);
box-shadow: 4px 5px 6px 0px rgba(0, 0, 0, 0.32);
font-size : 16px;
background-color : #fafafa;
}
.showButton:hover{
background-color: #ddd;
}
</style>
Visto que as classes 1 e 2 representam 85% das amostras é necessário a criação de uma nova distribuição. Nesse teste sugerimos a nova proporção de 40% para as duas primeiras classes e a 60% para as demais classes.
Sendo os 40% divididos entre 45% para a primeira classe e 55% para a segunda, com o intuito de manter a proporção inicial.
def calc_percs(dataset, two_perc=.60):
target_sums = [(dataset.loc[:, 'Target'] == i).sum() for i in range(1, 8)]
new_perc = sum(target_sums[2:])
hundred = int(new_perc/two_perc)
first_class = int(hundred*(1-two_perc)*0.45)
second_class = int(hundred*(1-two_perc)*0.55)
return (first_class, second_class)
from random import sample
first_class_count, second_class_count = calc_percs(dataset)
first_indexes = dataset[dataset['Target'] == 1].index.tolist()
second_indexes = dataset[dataset['Target'] == 2].index.tolist()
target_1_drop = sample(first_indexes, len(first_indexes)-first_class_count)
target_2_drop = sample(second_indexes, len(second_indexes)-second_class_count)
new_dt = dataset.copy()
new_dt = new_dt.drop(target_1_drop+target_2_drop, axis=0)
new_dt = new_dt.reset_index(drop=True)
new_dt.shape
[(new_dt.loc[:, 'Target'] == i).sum() for i in range(1, 8)]
new_hit = calc_binary_hit(new_dt, new_dt.columns[10:])
plot_count(new_hit)
Visto que as colunas 'Soil7','Soil8' e 'Soil15' não possuem dados significativos para quaisquer classe, tais caracteristicas podem ser descartadas.
new_dt = new_dt.drop(['Soil7','Soil8','Soil15'], axis=1)
Para as caracteristicas quatitativas, é uma boa prática realizar a normalização desses dados para que não existe uma discrepância muito grande de valores entre as amostras, dando assim importâncias e pesos muito diferentes para valores distantes.
from sklearn.preprocessing import StandardScaler
y = new_dt.loc[:, 'Target'].values
X = new_dt.iloc[:, :-1].values
ssc = StandardScaler()
X = ssc.fit_transform(X[:, 0:10])
X = np.concatenate((X, new_dt.iloc[:, 10:-1].values), axis=1)
Após a escala das caracteristicas quantitativas, devemos salvar o modelo para utilizarmos posteriormente em classificações de amostras que não estejam em conformidade com a escala criada.
from sklearn.externals import joblib
joblib.dump(ssc, 'stdscaler.pkl')
Após a normalização é feita a junção das caracteristicas quantitativas com as qualitativas. Ao final podemos randomizar a distribuição das amostras para assegurar a distribuição igual das amostras.
from random import shuffle
std_dataset = np.concatenate((X, y.reshape(y.shape[0],1)), axis=1)
shuffle(std_dataset)
std_dataset.shape
Ao final salvamos os conjuntos de dados em arquivos para futuro treinamento.
new_dt.to_csv('143k.csv', index=False)
new_dt_std = pd.DataFrame(data=std_dataset, columns=new_dt.columns)
new_dt_std.to_csv('143k_std.csv', index=False)
# Save dropped samples to future evaluation
dataset.iloc[target_1_drop+target_2_drop, :].to_csv('143k_val.csv', index=False)
Para maiores testes foram criados novos conjuntos de dados que alteram apenas a distribuição das primeiras duas classes dentro do montante de amostras.
def pre_process(dataset, ssc, perc):
new_dt = dataset.copy()
val_dt = None
if perc < 80:
first_class_count, second_class_count = calc_percs(dataset, perc)
first_indexes = dataset[dataset['Target'] == 1].index.tolist()
second_indexes = dataset[dataset['Target'] == 2].index.tolist()
target_1_drop = sample(first_indexes, len(first_indexes)-first_class_count)
target_2_drop = sample(second_indexes, len(second_indexes)-second_class_count)
new_dt = new_dt.drop(target_1_drop+target_2_drop, axis=0)
new_dt = new_dt.reset_index(drop=True)
val_dt = dataset.iloc[target_1_drop+target_2_drop, :]
new_dt = new_dt.drop(['Soil7','Soil8','Soil15'], axis=1)
y = new_dt.loc[:, 'Target'].values
X = new_dt.iloc[:, :-1].values
X = ssc.transform(X[:, 0:10])
X = np.concatenate((X, new_dt.iloc[:, 10:-1].values), axis=1)
X = np.concatenate((X, y.reshape(y.shape[0],1)), axis=1)
new_dt = pd.DataFrame(data=X, columns=new_dt.columns)
return (new_dt, val_dt)
new_dataset, validation_dataset = pre_process(dataset, ssc, 0.50)
new_dataset.to_csv('50-50_std.csv', index=False)
validation_dataset.to_csv('50-50_val.csv', index=False)
new_dataset, validation_dataset = pre_process(dataset, ssc, 0.40)
new_dataset.to_csv('60-40_std.csv', index=False)
validation_dataset.to_csv('60-40_val.csv', index=False)
new_dataset, validation_dataset = pre_process(dataset, ssc, 0.30)
new_dataset.to_csv('70-30_std.csv', index=False)
validation_dataset.to_csv('70-30_val.csv', index=False)
new_dataset, validation_dataset = pre_process(dataset, ssc, 0.90)
new_dataset.to_csv('All_std.csv', index=False)