Engenharia de caracteristicas

Dataset de artigos do NIPS (1987 à 2015)

Esse dataset está disponível no repositório de machine learning da UCI.

Ele é composto por 5812 artigos coletados desde a abertura da primeira conferência e um conjunto de 11463 palavras que compõem os artigos. Esses dados estão dispostos em uma matriz 11463 x 5812, então o vocabulário se encontra nas linhas e os artigos nas colunas.

Os demais dados é a contagem de quantas vezes cada palavra aparece em cada artigo, sendo assim, temos um Bag Of Words dos artigos. Porém a disposição da matriz não está condizente com o que estamos acostumados a trabalhar, sendo que as features se encontram nas linhas. Para tanto, é necessário criar a transposta dessa matriz, para que se torne mais familiar e possamos utiliza-la em nossos métodos.

Após feito a transposta do dataset, ele toma a forma de uma matriz esparsa com a contagem da frequência de cada palavra, como a abaixo.

In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from time import time
%matplotlib notebook
In [2]:
dataset = pd.read_csv('NIPS_1987-2015_transpose.csv')
dataset.head(5)
Out[2]:
Unnamed: 0 abalone abbeel abbott abbreviate abbreviated abc abeles abernethy abilistic ... zhou zhu zien zilberstein zones zoo zoom zou zoubin zurich
0 1987_1 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
1 1987_2 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
2 1987_3 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
3 1987_4 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
4 1987_5 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0

5 rows × 11464 columns

Histograma de maximos

Agora que temos nossa matriz em formado familiar, podemos estuda-la.

O primeiro passo que pode ser tomado é a visualização do histograma da matriz por completo. Para realizar essa tarefa, foi feita a contagem das frequências máximas de cada token, dessa forma podemos agrupar no histograma tokens com frequências iguais e visualizar se realmente todas as 11464 palavras são úteis para nós.

Então, nós primeiro:

  • fazemos a contagem dos máximos de cada token;
  • Retiramos a coluna de ano_id dessa contagem;
  • Criamos um gráfico com ajuda da biblioteca matplotlib com 40 "baldes".
In [3]:
max_feature_counts = dataset.max()
max_feature_counts = max_feature_counts[1:] # Retirando a coluna Xyear_ID
In [4]:
n, bins, patches = plt.hist(max_feature_counts, 40, log=True, ec='black', alpha=0.75, align='mid')
plt.title('Histograma BoW - Repetições', fontsize=20)
plt.xlabel('Maximo encontrado', fontsize=12)
plt.xticks(bins, rotation=90)
plt.ylabel('Frequência', fontsize=12)
ax = plt.gca()
ax.grid(axis='y', linestyle='--', linewidth=1)
plt.show()
print(n, bins)
[  2.32400000e+03   2.40300000e+03   1.28500000e+03   1.09700000e+03
   6.73000000e+02   6.58000000e+02   4.45000000e+02   5.01000000e+02
   3.09000000e+02   3.13000000e+02   3.00000000e+02   1.81000000e+02
   1.84000000e+02   1.29000000e+02   1.49000000e+02   7.40000000e+01
   8.60000000e+01   6.10000000e+01   6.50000000e+01   3.20000000e+01
   3.50000000e+01   3.80000000e+01   2.30000000e+01   2.70000000e+01
   1.00000000e+01   1.80000000e+01   6.00000000e+00   9.00000000e+00
   3.00000000e+00   6.00000000e+00   3.00000000e+00   3.00000000e+00
   4.00000000e+00   2.00000000e+00   3.00000000e+00   0.00000000e+00
   2.00000000e+00   0.00000000e+00   0.00000000e+00   2.00000000e+00] [   1.      5.55   10.1    14.65   19.2    23.75   28.3    32.85   37.4
   41.95   46.5    51.05   55.6    60.15   64.7    69.25   73.8    78.35
   82.9    87.45   92.     96.55  101.1   105.65  110.2   114.75  119.3
  123.85  128.4   132.95  137.5   142.05  146.6   151.15  155.7   160.25
  164.8   169.35  173.9   178.45  183.  ]

Podemos visualizar no histograma que cerca de 7782 tokens tem uma frequência máxima de até 24 vezes e outros 4 tokens tem a frequência máxima acima de 164 vezes. Então podemos afirmar, apenas com base nesse histograma, que 7782 tokens podem ser retirados, já que o máximo de vezes que aparecem em um único artigo não é relevante.

A mesma afirmação não é verdadeira para o restante do vocabulário, já que uma alta frequência máxima não nos diz se esse token está presente em apenas poucos artigos ou não. O que nos leva para nossa próxima análise.

Estudo dos termos frequentes

Próximo passo é então estudar quais são os termos mais frequentes no nosso vocabulário, para então ter uma ideia de qual é a nossa amostra.

Para isso é feita a ordenação das frequências máximas já coletadas e estudado cada token individualmente na ordem das suas frequências máximas.

In [5]:
sorted_values = max_feature_counts.sort_values(ascending=False)
sorted_columns = sorted_values.axes[0]
In [6]:
print('{} - {}\t{} - {} - {}\t{} - {}'.format('token', 
                                             'freq.', 
                                             'mediana', 
                                             'media',
                                             'Som. freq.',
                                             'qnt. !=0',
                                             'qnt. ==0'))
for index, s in enumerate(sorted_columns):
    if index > 69:
        break
    count_raw = np.array(dataset.loc[:, s])
    non_zeros = np.sort([t for t in count_raw if t != 0])
    zeros = [t for t in count_raw if t == 0]
    
    print('%s - %0.f\t%0.f - %0.f - %0.f\t%0.f - %0.f' % (s, 
                                                 sorted_values[index], 
                                                 np.median(non_zeros), 
                                                 np.mean(non_zeros),
                                                 np.sum(non_zeros),
                                                 len(non_zeros),
                                                 len(zeros)))
token - freq.	mediana - media - Som. freq.	qnt. !=0 - qnt. ==0
reward - 183	7 - 12 - 6530	523 - 5288
choice - 180	1 - 2 - 6179	2617 - 3194
gaze - 168	3 - 11 - 612	58 - 5753
clustering - 167	2 - 9 - 10354	1197 - 4614
shape - 160	1 - 4 - 4009	1113 - 4698
noise - 159	3 - 7 - 17104	2564 - 3247
risk - 156	2 - 7 - 5659	826 - 4985
tensor - 155	2 - 11 - 2803	252 - 5559
matrix - 153	5 - 10 - 31721	3283 - 2528
tree - 151	3 - 10 - 10908	1140 - 4671
data - 150	10 - 14 - 72295	5022 - 789
norm - 149	2 - 6 - 8893	1548 - 4263
policy - 148	11 - 19 - 10811	563 - 5248
model - 144	10 - 16 - 80080	4909 - 902
kernel - 144	4 - 11 - 18411	1626 - 4185
survival - 143	1 - 4 - 349	79 - 5732
map - 142	2 - 5 - 8973	1803 - 4008
chain - 141	2 - 4 - 3932	980 - 4831
learning - 141	10 - 15 - 76406	5058 - 753
scene - 137	2 - 5 - 3016	610 - 5201
lambda - 136	2 - 8 - 595	75 - 5736
reference - 134	1 - 3 - 1962	779 - 5032
teaching - 134	1 - 5 - 424	88 - 5723
saliency - 133	4 - 13 - 1347	104 - 5707
top - 133	2 - 3 - 6737	2183 - 3628
causal - 132	2 - 8 - 2170	271 - 5540
domains - 132	1 - 3 - 2488	907 - 4904
loss - 131	2 - 7 - 14564	2012 - 3799
tracking - 128	1 - 5 - 2222	490 - 5321
influence - 128	1 - 2 - 2273	997 - 4814
macro - 125	1 - 6 - 391	62 - 5749
motion - 125	2 - 8 - 5453	721 - 5090
segmentation - 125	2 - 6 - 3906	653 - 5158
feedback - 125	2 - 4 - 3527	792 - 5019
step - 125	3 - 4 - 16314	3642 - 2169
stop - 124	1 - 2 - 949	467 - 5344
graph - 124	3 - 9 - 14980	1676 - 4135
rank - 123	3 - 8 - 8351	1093 - 4718
change - 121	1 - 3 - 6152	2275 - 3536
image - 121	4 - 11 - 22313	2022 - 3789
topic - 120	1 - 8 - 5097	625 - 5186
order - 120	3 - 4 - 20473	4618 - 1193
dirichlet - 120	3 - 6 - 2961	473 - 5338
latent - 119	4 - 9 - 9713	1081 - 4730
means - 119	1 - 3 - 7905	2906 - 2905
ranking - 119	2 - 8 - 3395	413 - 5398
task - 119	2 - 5 - 14449	3025 - 2786
decision - 119	2 - 5 - 8500	1708 - 4103
attribute - 118	1 - 4 - 1254	291 - 5520
content - 118	1 - 3 - 1403	515 - 5296
weak - 118	1 - 3 - 2905	853 - 4958
context - 118	1 - 3 - 6897	2207 - 3604
head - 118	1 - 5 - 1540	332 - 5479
team - 117	1 - 3 - 437	151 - 5660
representatives - 117	1 - 4 - 193	47 - 5764
search - 116	2 - 5 - 9316	2030 - 3781
annotator - 116	1 - 11 - 220	20 - 5791
jigsaw - 116	1 - 20 - 122	6 - 5805
layer - 116	4 - 8 - 11645	1440 - 4371
examples - 115	2 - 4 - 13043	2963 - 2848
pdmm - 115	115 - 115 - 115	1 - 5810
time - 114	6 - 10 - 46402	4794 - 1017
annotations - 113	1 - 5 - 633	131 - 5680
treewidth - 113	2 - 8 - 461	55 - 5756
region - 113	2 - 4 - 5384	1518 - 4293
embedding - 112	2 - 6 - 2891	508 - 5303
object - 112	2 - 8 - 10713	1337 - 4474
network - 111	5 - 11 - 33429	3044 - 2767
networks - 111	3 - 7 - 22496	3243 - 2568
lattice - 111	2 - 4 - 784	177 - 5634

Para visualizar melhor o dataset utilizamos de valores como:

  • Frequência máxima encontrada para o token;
  • Mediana e média;
  • Soma das frequências;
  • Quantidade de artigos que possuem o token e não possuem.

Podemos notar que algumas peculiaridades com esses valores:

  • Existem palavras que possuem alta frequência na maioria dos artigos, apenas comparando a mediana e a média, onde ambas tem um alto valor;
  • Existem palavras similares;
  • Existem tokens que aparecem em poucos artigos;
    • Com alta frequência (ssda 3 artigos, 110 máx);
    • Com baixa frequência (cooperate 34, 5 máx);
  • Existem tokens que aparecem em muitos artigos (data 5022 artigos);

Com base nisso podemos retirar os tokens que se encaixam em tais peculiaridades, para tentar reduzir a dimensionalidade do nosso problema.

Verificando vocabulário

Logo após o estudo do dataset pela frequência dos tokens, podemos estudar como nosso vocabulário está construido. Faremos isso com o intuito de ver para quais tokens existem outros similares no mesmo vocabulário.

Essa analise é pertinente, já que podemos reduzir vários tokens a apenas um somando as suas frequências, e dessa forma reduzindo drasticamente nossa dimensionalidade.

Para gerar essa análise utilizaremos a biblioteca NLTK e seu PorterStemmer para verificar quais tokens tem a mesma raiz gramatical e agrupa-los.

In [7]:
import nltk
from nltk.stem.porter import PorterStemmer

columns = dataset.columns[1:]
stemmer = PorterStemmer()
steammed_columns = [stemmer.stem(word) for word in columns]
In [8]:
repeated_tokens = []
for token in sorted(set(steammed_columns)):
    indexes = [i for i, x in enumerate(steammed_columns) if x == token]
    if len(indexes) > 1:
        repeated_tokens.append((token, indexes))
In [9]:
repeated_tokens[:10]
Out[9]:
[('abbrevi', [3, 4]),
 ('abil', [9, 10]),
 ('absolut', [19, 20]),
 ('absorb', [21, 22]),
 ('abstract', [24, 25, 26, 27]),
 ('abund', [28, 29]),
 ('acceler', [33, 34, 35, 36, 37]),
 ('accept', [38, 39, 40, 41, 42, 43]),
 ('access', [44, 45, 46, 47]),
 ('accompani', [49, 50])]

Com essa análise podemos verificar que existem diversos tokens repetidos em nossa base, onde um token , pode chegar a ter mais de 20 outros similares. ('gener' 21)

In [10]:
import nltk
from nltk.stem.porter import PorterStemmer

def extract_repeated_tokens(tokens):
    stemmer = PorterStemmer()
    steammed_columns = [stemmer.stem(word) for word in tokens]
    
    repeated_tokens = []
    for token in sorted(set(steammed_columns)):
        indexes = [i for i, x in enumerate(steammed_columns) if x == token]
        if len(indexes) > 1:
            repeated_tokens.append((token, indexes))
    
    return repeated_tokens

Remodelar dataset

Agora que as analises foram feitas, já sabemos os passos que podemos seguir para reduzir a dimensionalidade do nosso problema.

Agora para conseguirmos retirar o máximo de tokens sem perder informações valiosas do nosso dataset temos que seguir uma ordem fixa para reduçao de tal dimensionalidade.

  • O primeiro passo é remover as colunas que possuem tokens similares a outros, dessa forma podemos agrupar vários tokens em uma só coluna, sem perda de informação.
  • O segundo passo é remover os tokens com baixas frequências máximas.
  • O terceiro passo é remover as colunas com base na proporção de artigos que elas cobrem.
  • Então por último é remover os artigos nulos, que não possuem qualquer ocorrência dos tokens selecionados.
In [11]:
y = dataset.iloc[:, 0]

Removendo colunas comuns ao steamming

Com a redução de features por steamming conseguimos reduzir o número de features de 11464 para 7168, representando uma redução de 37% das features.

In [12]:
def remove_repeated_tokens(data, r_tokens, verbose=0):
    columns_to_drop = []
    j = 0
    for token, indexes in r_tokens:
        for i in range(1, len(indexes)):
            data.iloc[:, indexes[0]] += data.iloc[:, indexes[i]]
            
            columns = dataset.columns[1:]
            column_to_drop = columns[indexes[i]]
            column_to_add = columns[indexes[0]]
            
            columns_to_drop.append(column_to_drop)

            if verbose > 1:
                print('Droped\t\'{}\'\tand summed with\t\'{}\'\tToken - {} - {}'
                      .format(column_to_drop, column_to_add, token, indexes))
            elif verbose > 0:
                if j < 15:
                    print('Droped\t\'{}\'\tand summed with\t\'{}\'\tToken - {} - {}'
                      .format(column_to_drop, column_to_add, token, indexes))
        j+=1
    data = data.drop(columns_to_drop, axis=1)
    return data
In [13]:
X = dataset.copy()
X = X.drop('Unnamed: 0', axis=1)
t0 = time()
X = remove_repeated_tokens(X, repeated_tokens, verbose = 1)
print('\n\nNew Dimensions: {}'.format(X.shape))
print('Execution time %.3f' % (time() - t0))
Droped	'abbreviated'	and summed with	'abbreviate'	Token - abbrevi - [3, 4]
Droped	'ability'	and summed with	'abilities'	Token - abil - [9, 10]
Droped	'absolutely'	and summed with	'absolute'	Token - absolut - [19, 20]
Droped	'absorbing'	and summed with	'absorbed'	Token - absorb - [21, 22]
Droped	'abstraction'	and summed with	'abstract'	Token - abstract - [24, 25, 26, 27]
Droped	'abstractions'	and summed with	'abstract'	Token - abstract - [24, 25, 26, 27]
Droped	'abstracts'	and summed with	'abstract'	Token - abstract - [24, 25, 26, 27]
Droped	'abundant'	and summed with	'abundance'	Token - abund - [28, 29]
Droped	'accelerated'	and summed with	'accelerate'	Token - acceler - [33, 34, 35, 36, 37]
Droped	'accelerating'	and summed with	'accelerate'	Token - acceler - [33, 34, 35, 36, 37]
Droped	'acceleration'	and summed with	'accelerate'	Token - acceler - [33, 34, 35, 36, 37]
Droped	'accelerations'	and summed with	'accelerate'	Token - acceler - [33, 34, 35, 36, 37]
Droped	'acceptable'	and summed with	'accept'	Token - accept - [38, 39, 40, 41, 42, 43]
Droped	'acceptance'	and summed with	'accept'	Token - accept - [38, 39, 40, 41, 42, 43]
Droped	'accepted'	and summed with	'accept'	Token - accept - [38, 39, 40, 41, 42, 43]
Droped	'accepting'	and summed with	'accept'	Token - accept - [38, 39, 40, 41, 42, 43]
Droped	'accepts'	and summed with	'accept'	Token - accept - [38, 39, 40, 41, 42, 43]
Droped	'accessed'	and summed with	'access'	Token - access - [44, 45, 46, 47]
Droped	'accesses'	and summed with	'access'	Token - access - [44, 45, 46, 47]
Droped	'accessible'	and summed with	'access'	Token - access - [44, 45, 46, 47]
Droped	'accompaniment'	and summed with	'accompanied'	Token - accompani - [49, 50]
Droped	'accomplished'	and summed with	'accomplish'	Token - accomplish - [51, 52]
Droped	'accordance'	and summed with	'accord'	Token - accord - [53, 54, 55]
Droped	'according'	and summed with	'accord'	Token - accord - [53, 54, 55]
Droped	'accounted'	and summed with	'account'	Token - account - [57, 58, 59, 60]
Droped	'accounting'	and summed with	'account'	Token - account - [57, 58, 59, 60]
Droped	'accounts'	and summed with	'account'	Token - account - [57, 58, 59, 60]
Droped	'accumulated'	and summed with	'accumulate'	Token - accumul - [61, 62, 63, 64, 65, 66]
Droped	'accumulates'	and summed with	'accumulate'	Token - accumul - [61, 62, 63, 64, 65, 66]
Droped	'accumulating'	and summed with	'accumulate'	Token - accumul - [61, 62, 63, 64, 65, 66]
Droped	'accumulation'	and summed with	'accumulate'	Token - accumul - [61, 62, 63, 64, 65, 66]
Droped	'accumulator'	and summed with	'accumulate'	Token - accumul - [61, 62, 63, 64, 65, 66]
Droped	'accurately'	and summed with	'accurate'	Token - accur - [69, 70]


New Dimensions: (5811, 7168)
Execution time 3.374
In [14]:
def make_comparative_feature_graph(dataset, X):
    features_count = [dataset.shape[1], X.shape[1]]

    fig, ax = plt.subplots()
    cplt, novo = plt.bar(np.arange(1, 3), features_count)
    novo.set_facecolor('r')
    plt.ylabel('Features')
    plt.xlabel('Datasets')
    plt.title('Redução de Features')
    ax.set_xticks(np.arange(1, 3))
    ax.set_xticklabels(['Original', 'Novo'])

    rects = ax.patches
    for rect, label in zip(rects, features_count):
        height = rect.get_height()
        ax.text(rect.get_x() + rect.get_width()/2, height + 5, label, ha='center', va='bottom')

    plt.show()
    
make_comparative_feature_graph(dataset, X)

Observando colunas com alta/baixa frequência

Agora podemos observar o nosso novo conjunto de dados e como a máxima frequência dos tokens se comportam nele.

Faremos novamente um histograma para as frequências máximas. Com o novo histograma podemos perceber que o número de tokens com a frequência máxima abaixo de 24 vezes, caiu para aproximadamente 4297 tokens.

In [15]:
def count_max_ocurrences(dataset):
    max_feature_counts = dataset.max()

    sorted_values = max_feature_counts.sort_values(ascending=False)
    sorted_columns = sorted_values.axes[0]

    return (max_feature_counts, sorted_values, sorted_columns)
In [16]:
max_feature_counts, sorted_values, sorted_columns = count_max_ocurrences(X)
In [21]:
def make_histogram(arr, title, n_partitions=40, verbose=0):
    n, bins, patches = plt.hist(arr, n_partitions, log=True, ec='black', alpha=0.75, align='mid')
    plt.title(title, fontsize=20)
    plt.xlabel('Maximo encontrado', fontsize=12)
    plt.xticks(bins, rotation=90)
    plt.ylabel('Frequência', fontsize=12)
    ax = plt.gca()
    ax.grid(axis='y', linestyle='--', linewidth=1)
    plt.show()

    if verbose > 0:
        print(n, bins)
In [22]:
make_histogram(sorted_values, 'Histograma Freq Max - Novo dataset', verbose = 1)
[  1.59600000e+03   1.37200000e+03   7.35000000e+02   5.94000000e+02
   4.27000000e+02   4.28000000e+02   3.30000000e+02   2.21000000e+02
   2.59000000e+02   1.82000000e+02   1.74000000e+02   1.28000000e+02
   9.10000000e+01   9.90000000e+01   7.80000000e+01   8.30000000e+01
   6.00000000e+01   4.00000000e+01   3.80000000e+01   3.10000000e+01
   3.50000000e+01   2.80000000e+01   2.50000000e+01   1.90000000e+01
   2.10000000e+01   1.40000000e+01   1.10000000e+01   1.10000000e+01
   1.30000000e+01   6.00000000e+00   3.00000000e+00   0.00000000e+00
   4.00000000e+00   3.00000000e+00   2.00000000e+00   1.00000000e+00
   2.00000000e+00   1.00000000e+00   0.00000000e+00   3.00000000e+00] [   1.     6.6   12.2   17.8   23.4   29.    34.6   40.2   45.8   51.4
   57.    62.6   68.2   73.8   79.4   85.    90.6   96.2  101.8  107.4
  113.   118.6  124.2  129.8  135.4  141.   146.6  152.2  157.8  163.4
  169.   174.6  180.2  185.8  191.4  197.   202.6  208.2  213.8  219.4
  225. ]

Removendo colunas baseado na proporção

Por último podemos reduzir ainda mais nosso dataset com base na proporção dos tokens nos artigos. Onde tokens que aparecem em poucos artigos ou tokens que aparecem em quase todos os artigos, não são úteis para discriminar nossas amostras das demais.

Para tanto, utilizamos a faixa de features entre 10% e 80% de proporção.

In [19]:
def remove_low_high_proportion(dataset):
    tokens = dataset.columns
    remove_tokens = []
    
    for token in tokens:
        token_proportion = len(dataset[dataset[token]>0]) / len(dataset)
        if token_proportion < 0.1 or token_proportion > 0.8:
            remove_tokens.append(token)
    
    dataset = dataset.drop(remove_tokens, axis=1)
    
    return (dataset, remove_tokens)
In [20]:
X, tokens_removed = remove_low_high_proportion(X)
tokens_removed[:10]
Out[20]:
['abalone',
 'abbeel',
 'abbott',
 'abbreviate',
 'abc',
 'abeles',
 'abernethy',
 'abilistic',
 'ables',
 'abnormal']

Aplicando a remoção por proporção podemos retirar 51% do dataset original, ficando ao final com apenas 10% do conjunto de dados inicial.

In [23]:
make_comparative_feature_graph(dataset, X)

Removendo colunas baseado na frequência

Com isso podemos agora remover os tokens com baixa contagem em sua frequência.

Para realizar a redução, tomamos por base a contagem feita no histograma, onde verificamos que existem vários mais de 4 mil tokens com frequencia abaixo de 24 vezes. Porém, não podemos ter certeza de que tokens com frequências próximas a 24 vezes não sejam úteis para classificar algum artigo, então utilizamos de algumas regras baseado na frequência máxima para reduzir nosso problema.

Um token será removido caso:

  • Ele tenha frequência menor que 65 vezes e apareça em menos de 80% dos artigos;
  • Ele tenha uma baixa variabilidade em mais de 60% dos artigos;
  • Ele tenha a frequência máxima abaixo de 18 vezes.

E um token será enviado para análise de remoção caso a frequência máxima dele for entre 18 e 24 vezes.

In [24]:
def remove_low_counting_tokens(dataset, sorted_columns, sorted_values, bounds=[65,4,18,24]):
    drop_columns = []
    not_sure_to_drop = []
    report = []
    
    for index, s in enumerate(sorted_columns):
        count_raw = np.array(dataset.loc[:, s])
        non_zeros = [t for t in count_raw if t != 0]
        median = np.median(count_raw)
        mean = np.mean(count_raw)
        
        # Check if the feature has a high frequency among at least 80% of the samples
        if sorted_values[index] < bounds[0] and len(non_zeros) >= (0.8 * dataset.shape[0]):
            drop_colum = (s, sorted_values[index], median, len(non_zeros))
            report.append(drop_colum)
            drop_columns.append(s)
        # Check if the column is constant throughout at least 60% of the samples
        elif abs(median - mean) <= bounds[1] and len(non_zeros) >= (0.6 * dataset.shape[0]):
            drop_colum = (s, sorted_values[index], median, len(non_zeros))
            report.append(drop_colum)
            drop_columns.append(s)
        # Drop if the max frequency is less than 18
        elif sorted_values[index] < bounds[2]:    
            drop_colum = (s, sorted_values[index], median, len(non_zeros))
            report.append(drop_colum)
            drop_columns.append(s)
        # Sent to check if the max frequency is higher than 18 and less than 24
        elif sorted_values[index] < bounds[3]:    
            drop_colum = (s, sorted_values[index], median, len(non_zeros))
            not_sure_to_drop.append(s)
        else:
            continue

    dataset = dataset.drop(drop_columns, axis=1)
    
    return (dataset, not_sure_to_drop, report, drop_columns)
In [25]:
max_feature_counts, sorted_values, sorted_columns = count_max_ocurrences(X)
X, columns_to_check, report, columns_droped = remove_low_counting_tokens(X, sorted_columns, sorted_values)
print(len(columns_droped), len(columns_to_check))
print(X.shape)
X.head()
389 93
(5811, 852)
Out[25]:
absolute accept access accord account accuracies accurate across act action ... wide width window wise within word world worst write year
0 0 0 1 1 0 0 0 0 0 0 ... 0 0 0 0 0 1 0 0 1 0
1 1 0 0 0 0 0 0 3 0 0 ... 1 0 0 0 0 0 0 0 0 1
2 0 1 0 0 0 0 0 0 0 0 ... 1 0 0 0 1 0 0 0 3 1
3 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 2 0 1 0 0 0
4 1 0 0 0 0 0 0 0 0 0 ... 3 2 0 2 2 0 0 1 0 0

5 rows × 852 columns

Com isso conseguimos reduzir ainda mais nosso dataset, chegando a um percentual de 7% do dataset inicial.

In [26]:
make_comparative_feature_graph(dataset, X)

Removendo linhas nulas

Agora devemos remover todo artigo nulo, que não houver contagem das features selecionadas.

In [27]:
def remove_nul_rows(dataset):
    remove_rows = []
    
    for i in range(len(dataset)):
        total_words = sum(dataset.iloc[i, :])
        if total_words == 0:
            remove_rows.append(i)
    
    indexes_to_drop = dataset.index[remove_rows]
    dataset = dataset.drop(indexes_to_drop, axis=0)
    
    return (dataset, indexes_to_drop)
In [28]:
X, indexes_removed = remove_nul_rows(X)
print('Removidos {} artigos: {}'.format(len(indexes_removed), list(indexes_removed)))
Removidos 7 artigos: [2452, 3277, 4062, 4099, 4204, 4228, 5595]

Remodelando em um passo

Agora podemos executar todos os passos de uma só vez para facilitar.

In [55]:
dataset = pd.read_csv('NIPS_1987-2015_transpose.csv')
y = dataset.iloc[:, 0]
X = dataset.copy()
X = X.drop('Unnamed: 0', axis=1)

t0 = time()

repeated_tokens = extract_repeated_tokens(X.columns)
X = remove_repeated_tokens(X, repeated_tokens)
max_feature_counts, sorted_values, sorted_columns = count_max_ocurrences(X)
X, columns_to_check, report, columns_droped = remove_low_counting_tokens(X, sorted_columns, sorted_values)
X, outliers = remove_low_high_proportion(X)
X, nul_rows = remove_nul_rows(X)
y = y.drop(nul_rows, axis=0)

print('New Dimensions: {}'.format(X.shape))
print('Execution time %.3fs' % (time() - t0))
New Dimensions: (5804, 852)
Execution time 67.027s

Por fim temos este histograma para as features selecionadas.

In [28]:
max_feature_counts, sorted_values, sorted_columns = count_max_ocurrences(X)
make_histogram(sorted_values, 'Histograma Freq Max - Final')

Salvando novo dataset remodelado

Ao final apenas nos resta salvar o novo conjunto de dados para que possamos então usa-lo futuramente.

In [32]:
new_X = X.assign(y=pd.Series(y).values)
new_X.to_csv('NIPS_1987-2015_remodeled_755.csv', index=False)

Uma variação para teste seria salvar apenas as 300 primeiras palavras com as maiores ocorrências no conjunto por inteiro.

In [33]:
lcolumns = X.sum(0).nlargest(300).index.tolist()
X1 = X[lcolumns]
X1 = X1.assign(y=pd.Series(y).values)
X1.to_csv('NIPS_1987-2015_remodeled_300.csv', index=False)