Random Forest
Objetivo
O objetivo geral deste roteiro é utilizar as bibliotecas pandas, numpy, matplotlib e scikit-learn, além de uma base escolhida no Kagle, para treinar e avaliar um algoritmo de Random Tree.
Base de Dados
A base de dados escolhida para a realização deste roteiro foi a MBA Admission Dataset. Esta base possui 6194 linhas e 10 colunas, incluido uma coluna de ID da aplicação e uma coluna de status da admissão, esta é a váriavel dependente que será objeto da classificação.
Análise da Base
A seguir foi feita uma análise do significado e composição de cada coluna presente na base com a finalidade de indentificar possíveis problemas á serem tradados posteriormente.
Esta coluna é composta pelos ID's das aplicações realizadas, ou seja trata-se de um valor numérico lógico, único a cada aplicação, desta forma pode-se afirmar que esta coluna não terá relevância para o algoritmo e deverá ser retirada da base para treinamento.
Esta coluna é preenchida com o genêro do aplicante, contendo apenas valores textuais entre "male" e "female", não incluindo opções como "non-binary", "other" ou "prefer not to inform". Logo, estes dados, por serem textuais e apresentarem binariedade, deverão ser transformados em uma variável dummy para que se atinja um melhor desempenho do algoritmo.
Esta coluna é preenchida com valores booleanos que classificam o aplicantente como "estrangeiro" ou "não-estrangeiro". Logo, estes dados, por serem textuais e apresentarem binariedade, deveriam ser transformados em uma variável dummy para que se atinja um melhor desempenho do algoritmo.
Entretanto, a classificação desta coluna tambem poder ser notada na coluna "race", pois todos os valores nulos presentes na posterior são unicamente referentes a alunos estrangeiros.
Esta coluna representa a performance acadêmica prévia do aplicante, que é calculada a partir do histórico escolar. Neste as notas particulares de cada matéria podem variar de 0 á 4, 0 sendo a pior nota possível e 4 a maior. Neste caso os GPA's dos aplicantes variam entre 2.65 e 3.77, apresentando uma curva normal. Devido ao fato destes valores já serem numéricos estes já estão adequados para o modelo.
Esta coluna representa em que curso o aplicante deseja entrar, podendo assumir um de três valores textuais: "Humanities", "STEM" e "Business". Neste caso, como a variavel é textual e não apresenta binariedade, a técnica correta para o tratamento desta coluna será o Label Enconding, transformando estes valores textuais em valores númericos.
Esta coluna representa a indentificação racial do aplicante, porém tambem há diversas linhas com valor nulo nesta coluna. Ao comparar o preenchimento desta coluna com as demais, percebe-se que o valor desta coluna so se apresenta nulo para estudantes estrangeiros, tornando a coluna "international" redundante.
Desta forma, para otimizar o modelo, devemos remover a coluna "international", prezando pela menor quantidade de colunas possível. E como esta coluna não apresentar binariedade, deverá ser utilizada a técnica de Label Enconding, transformando estes valores textuais e nulos em valores númericos.
Esta coluna representa o desempenho do aplicante na prova de adimissão, variando de 570 á 780, porém estas notas não apresentam uma curva normal, pois há muitos registros de notas menores que a média a mais do que há registos de notas maiores que a média. Devido ao fato destes valores já serem numéricos estes já estão adequados para o modelo.
Esta coluna representa o tempo de experiência prévia do aplicante no mercado, exibida em anos. Os valores podem variar de 1 á 9, apresentando uma curva normal. Devido ao fato destes valores já serem numéricos estes já estão adequados para o modelo.
Esta coluna representa a área de experiência prévia do aplicante no mercado, podendo assumir, nesta base um de quatorze valores textuais. E como esta coluna não apresenta binariedade, deverá ser utilizada a técnica de Label Enconding, transformando estes valores textuais em valores númericos.
Esta coluna apresenta valores em texto para os aplicantes admitos e na lista de espera, além de valores nulos para aqueles que não foram aceitos. Esta coluna é o objeto da classificação e portanto será separada das outras colunas da base, e os valores nulos deveram ser preenchidos.
Pré-processamento
Esta secção visa preparar os dados para o treinamento da árvore de decisão, atendendo as observações e análises feitas no tópico anterior.
| gender | gpa | major | race | gmat | work_exp | work_industry | admission |
|---|---|---|---|---|---|---|---|
| 1 | 2.99 | 0 | 5 | 640 | 5 | 1 | Refused |
| 1 | 3.38 | 0 | 4 | 680 | 4 | 13 | Refused |
| 1 | 3.32 | 1 | 1 | 660 | 5 | 1 | Refused |
| 1 | 3 | 1 | 0 | 610 | 4 | 5 | Refused |
| 0 | 3.32 | 2 | 1 | 670 | 4 | 8 | Refused |
| 1 | 3.04 | 2 | 4 | 600 | 3 | 1 | Refused |
| 0 | 3.15 | 2 | 5 | 580 | 6 | 1 | Refused |
| 0 | 3.07 | 2 | 5 | 650 | 5 | 1 | Refused |
| 1 | 3.31 | 2 | 3 | 630 | 6 | 9 | Refused |
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.preprocessing import LabelEncoder
label_encoder = LabelEncoder()
df = pd.read_csv("./docs/base/MBA.csv")
#Excluir as conlunas não desejadas
df = df.drop(columns= ["application_id", "international"])
#Preencher os valores nulos da coluna "race"
df["race"] = df["race"].fillna("international")
#Preencher os valores nulos da coluna "admission"
df["admission"] = df["admission"].fillna("Refused")
#Label encoding das colunas em texto
df["race"] = label_encoder.fit_transform(df["race"])
df["gender"] = label_encoder.fit_transform(df["gender"])
df["major"] = label_encoder.fit_transform(df["major"])
df["work_industry"] = label_encoder.fit_transform(df["work_industry"])
print(df.sample(frac=.0015).to_markdown(index=False))
| application_id | gender | international | gpa | major | race | gmat | work_exp | work_industry | admission |
|---|---|---|---|---|---|---|---|---|---|
| 5193 | Male | True | 3.24 | Business | nan | 630 | 5 | Nonprofit/Gov | nan |
| 3152 | Male | True | 3.41 | Humanities | nan | 770 | 5 | Investment Management | nan |
| 288 | Male | False | 3.14 | Business | Hispanic | 580 | 2 | PE/VC | nan |
| 3619 | Male | False | 3.34 | Business | Other | 650 | 5 | Other | nan |
| 3925 | Female | False | 3.16 | Business | White | 670 | 5 | PE/VC | nan |
| 2946 | Male | True | 3.01 | Humanities | nan | 570 | 5 | Consulting | nan |
| 4248 | Male | False | 3.42 | STEM | Hispanic | 690 | 4 | Health Care | nan |
| 5474 | Female | False | 3.53 | Business | Black | 650 | 5 | Other | nan |
| 191 | Female | False | 3.32 | Humanities | White | 730 | 5 | Nonprofit/Gov | Admit |
Divisão dos dados
Devido a composição da coluna de admission, a seperação dos dados deve ser feita com maior atenção. Caso esta separação fosse feita com aleatoriedade, haveria a possibilidade de que a base de treinamento tornar-se enviesada. Portanto, esta deve ser executada com proporcionalidade a composição da coluna alvo. Tendo em vista situações como esta o sickit-learn já implementou o sorteamento extratificado como a opção stratify no comando train_test_split().
Além disto para o treinamento foi utilizado uma separação arbitrária da base em 70% treinamento e 30% validação.
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
label_encoder = LabelEncoder()
df = pd.read_csv("./docs/base/MBA.csv")
#Excluir as conlunas não desejadas
df = df.drop(columns= ["application_id", "international"])
#Preencher os valores nulos da coluna "race"
df["race"] = df["race"].fillna("international")
#Preencher os valores nulos da coluna "admission"
df["admission"] = df["admission"].fillna("Refused")
#Label encoding das colunas em texto
df["race"] = label_encoder.fit_transform(df["race"])
df["gender"] = label_encoder.fit_transform(df["gender"])
df["major"] = label_encoder.fit_transform(df["major"])
df["work_industry"] = label_encoder.fit_transform(df["work_industry"])
#Separar em vairaveis indenpendetes e dependente
x = df[["gender", "gpa", "major", "race", "gmat", "work_exp", "work_industry"]]
y = df["admission"]
#Separar em teste e validação
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=27, stratify=y)
Treinamento da Árvore
Accuracy: 0.8386
Importância das Features:
| Feature | Importância | |
|---|---|---|
| 4 | gmat | 0.519098 |
| 1 | gpa | 0.322064 |
| 0 | gender | 0.064424 |
| 3 | race | 0.035490 |
| 6 | work_industry | 0.028160 |
| 5 | work_exp | 0.021203 |
| 2 | major | 0.009562 |
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestClassifier
label_encoder = LabelEncoder()
df = pd.read_csv("./docs/base/MBA.csv")
#Excluir as conlunas não desejadas
df = df.drop(columns= ["application_id", "international"])
#Preencher os valores nulos da coluna "race"
df["race"] = df["race"].fillna("international")
#Preencher os valores nulos da coluna "admission"
df["admission"] = df["admission"].fillna("Refused")
#Label encoding das colunas em texto
df["race"] = label_encoder.fit_transform(df["race"])
df["gender"] = label_encoder.fit_transform(df["gender"])
df["major"] = label_encoder.fit_transform(df["major"])
df["work_industry"] = label_encoder.fit_transform(df["work_industry"])
#Separar em vairaveis indenpendetes e dependente
x = df[["gender", "gpa", "major", "race", "gmat", "work_exp", "work_industry"]]
y = df["admission"]
#Separar em teste e validação
X_train, X_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=42, stratify=y)
# Initialize and train the model
rf = RandomForestClassifier(n_estimators=100, # Number of trees
max_depth=5, # Max depth of trees
max_features='sqrt', # Features per split
random_state=42)
rf.fit(X_train, y_train)
# Predict and evaluate
predictions = rf.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, predictions):.4f}")
feature_importance = pd.DataFrame({
'Feature': rf.feature_names_in_,
'Importância': rf.feature_importances_
})
print("<br>Importância das Features:")
print(feature_importance.sort_values(by='Importância', ascending=False).to_html())
Avaliação do Modelo
Com este treinamento o modelo apresenta 78.48% de precisão, número satisfatório para um modelo de classificação real, e as colunas mais importantes em sua tomada de deicisão são as ponutações gmat e gpa com 51.9% e 32.2% de importância, respectivamente, e a coluna com menor relevancia para o modelo é a major, com 0.9% de importância.
Conclusão
O processo de treinamento da Random Forest consistiu em preparar os dados (remoção de application_id, codificações e alinhamento dos tipos), separar a base em 70% treino e 30% validação com estratificação pela coluna admission, e então ajustar um comitê de árvores de decisão: cada árvore foi treinada em bootstraps do conjunto de treino e, a cada divisão, considerou apenas um subconjunto aleatório de variáveis, o que reduz correlação entre árvores e estabiliza o erro. A previsão final resulta do voto majoritário entre as árvores. A avaliação do modelo obteve 78,48% de acurácia, e as importâncias de atributos, calculadas pelo ganho médio de impureza ao longo das divisões, indicaram GMAT (51,9%) e GPA (32,2%) como os preditores mais influentes, enquanto major apresentou baixa relevância (0,9%).