Random Forest
Introdução
A Random Forest é um método de aprendizado supervisionado baseado em conjunto de árvores de decisão. A ideia central é treinar várias árvores em amostras bootstrap do conjunto de treino, introduzindo aleatoriedade tanto nas amostras quanto na seleção de atributos em cada divisão. A predição final resulta do voto da maioria (classificação) ou da média (regressão). Essa estratégia reduz overfitting típico de árvores individuais, melhora a generalização e mantém boa interpretabilidade via importância de atributos. Além disso, por ser baseada em árvores, lida naturalmente com relações não lineares e interações entre variáveis e não exige padronização das features para funcionar — embora possamos manter o mesmo pipeline de pré-processamento para consistência com os outros modelos do projeto.
Exploração dos Dados
O Dataset
Para esse projeto foi utilizada o Dataset Fitness Classification Dataset. Essa Base de dados posuí 2.000 linhas e 11 colunas. A variável dependente que será usada como objeto de classificação é a is_fit, ela indica se a pessoa é fit (1) ou não fit (0).
Análise dos dados
Tipo: numérica contínua
O que é: idade em anos.
Para que serve: pode relacionar-se com hábitos e condição física.
Ação necessária: nenhuma obrigatória; só checar faixas implausíveis (não observei no geral).
Tipo: numérica contínua
O que é: altura em centímetros.
Para que serve: isoladamente costuma ter pouco poder; combinada ao peso forma o BMI.
Ação necessária: checar valores muito fora do plausível. Sugestão: considerar substituir altura e peso por bmi(Índice de Massa Corporal).
Tipo: numérica contínua
O que é: peso em quilogramas.
Para que serve: junto com a altura permite calcular BMI = peso(kg) / (altura(m))², que costuma ser mais informativo para a árvore.
Ação necessária: manter como numérica ou criar bmi e remover height_cm/weight_kg das features (deixando só o bmi).
Tipo: numérica contínua
O que é: frequência cardíaca (bpm).
Para que serve: indicador de condicionamento cardiovascular; pode ajudar na separação das classes.
Ação necessária: nenhuma obrigatória; apenas conferir plausibilidade de valores extremos.
Tipo: numérica contínua
O que é: medida sintética de pressão arterial fornecida pelo dataset.
Para que serve: sinal de saúde geral que pode complementar a predição.
Ação necessária: nenhuma obrigatória; só verificar extremos muito fora do usual.
Tipo: numérica contínua
O que é: horas de sono por dia.
Para que serve: hábito de descanso; costuma ter correlação com “estar fit”.
Ação necessária: possui valores ausentes (160 valores); imputar com a mediana.
Tipo: numérica contínua (escala)
O que é: qualidade da nutrição (escala contínua, ex.: 0–10).
Para que serve: proxy de alimentação saudável; geralmente relevante.
Ação necessária: nenhuma; manter como numérica (só garantir faixa válida).
Tipo: numérica contínua (escala)
O que é: nível de atividade física (escala contínua, ex.: 0–10).
Para que serve: costuma ser uma das variáveis mais importantes para is_fit.
Ação necessária: nenhuma; manter como numérica (garantir faixa válida).
Tipo: categórica binária
O que é: status de tabagismo (sim/não).
Para que serve: fator de estilo de vida; pode ajudar a separar perfis.
Ação necessária: tipos mistos no bruto (“yes/no” e “1/0”). Padronizar para binário numérico (no→0, yes→1) e converter para int.
Tipo: categórica binária
O que é: gênero (F/M).
Para que serve: possível moderador de outros efeitos; em geral fraco sozinho.
Ação necessária: codificar para numérico (F→0, M→1) e converter para int.
Tipo: categórica binária (target)
O que é: rótulo de condição física (1 = fit, 0 = não fit).
Para que serve: variável dependente a ser prevista.
Ação necessária: checar balanceamento das classes.
Pré-processamento
Nesta etapa tratei e preparei os dados para treinar a Árvore de Decisão. Antes do tratamento, a base apresentava valores ausentes em sleep_hours, tipos mistos em smokes (valores como yes/no e 0/1 ao mesmo tempo) e variáveis categóricas em texto (gender com F/M). Abaixo, o que foi feito:
• Padronização de categóricas
-
smokes: normalizei rótulos e converti para binário numérico (no→0, yes→1, cobrindo também 0/1 em string).
-
gender: converti F→0 e M→1.
• Valores ausentes
- sleep_hours: converti para numérico e imputei a mediana.
• Tipos e consistência
- Garanti que as variáveis contínuas ficaram em formato numérico, sem strings residuais/espaços.
• Criação de nova variável
- Criei a variável BMI (peso(kg) / altura(m)²) para avaliar seu impacto. Na exploração, mantenho height_cm e weight_kg para referência; na modelagem, comparo dois cenários: (A) sem BMI (altura + peso) e (B) com apenas BMI, evitando usar os três juntos no mesmo modelo para não introduzir redundância.
| age | height_cm | weight_kg | heart_rate | blood_pressure | sleep_hours | nutrition_quality | activity_index | smokes | gender | is_fit |
|---|---|---|---|---|---|---|---|---|---|---|
| 66 | 181 | 84 | 69.6 | 132.3 | 5.3 | 5.75 | 1.09 | 0 | M | 0 |
| 73 | 163 | 77 | 64.6 | 98.6 | null | 5.71 | 4.38 | yes | M | 0 |
| 25 | 157 | 90 | 96.9 | 108.3 | null | 3.72 | 1.24 | yes | M | 0 |
| 54 | 189 | 87 | 69.3 | 113 | 5.8 | 6.1 | 3.43 | 0 | M | 1 |
| 79 | 169 | 46 | 58.7 | 105.1 | null | 1.83 | 1.92 | no | M | 0 |
| 18 | 193 | 96 | 65.2 | 131.5 | 10.5 | 6.67 | 2.8 | yes | M | 1 |
| 72 | 194 | 58 | 48 | 141.3 | 7.8 | 7.05 | 1.13 | 0 | F | 0 |
| 52 | 197 | 78 | 83.9 | 124.8 | 6.1 | 6.76 | 3.47 | no | F | 1 |
| 41 | 189 | 112 | 56.6 | 98.9 | 9.0 | 5.88 | 2.55 | yes | F | 0 |
| 25 | 186 | 100 | 58.8 | 111.3 | 7.0 | 7.52 | 2.97 | yes | M | 1 |
| 53 | 169 | 78 | 105.6 | 108.9 | 7.4 | 0.18 | 1.51 | yes | M | 0 |
| 79 | 160 | 94 | 65.4 | 107.9 | 5.0 | 5.56 | 2.12 | 0 | M | 0 |
| 73 | 151 | 62 | 67.2 | 117.9 | 7.3 | 6.83 | 1.51 | 0 | F | 0 |
| 33 | 153 | 103 | 70 | 140.5 | 7.7 | 8.51 | 3.48 | no | F | 0 |
| 45 | 159 | 85 | 74.7 | 132.6 | 7.3 | 1.75 | 1.26 | 0 | F | 0 |
import pandas as pd
df = pd.read_csv("./src/fitness_dataset.csv")
df["sleep_hours"] = df["sleep_hours"].fillna(df["sleep_hours"].median())
df["smokes"] = (
df["smokes"].astype(str).str.strip().str.lower()
.map({"yes": 1, "no": 0, "1": 1, "0": 0})
).astype(int)
df["gender"] = df["gender"].replace({"F": 0, "M": 1}).astype(int)
h_m = pd.to_numeric(df["height_cm"], errors="coerce") / 100.0
bmi = pd.to_numeric(df["weight_kg"], errors="coerce") / (h_m**2)
df["bmi"] = bmi.replace([float("inf"), float("-inf")], pd.NA).fillna(bmi.median())
print(df.sample(n=15).to_markdown(index=False))
| age | height_cm | weight_kg | heart_rate | blood_pressure | sleep_hours | nutrition_quality | activity_index | smokes | gender | is_fit | bmi |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 53 | 169 | 78 | 105.6 | 108.9 | 7.4 | 0.18 | 1.51 | 1 | 1 | 0 | 27.31 |
| 75 | 153 | 67 | 85 | 111.9 | 7.9 | 9.51 | 2.11 | 0 | 1 | 1 | 28.6215 |
| 37 | 181 | 78 | 71.8 | 152.7 | 8.2 | 8.77 | 3.87 | 1 | 0 | 0 | 23.8088 |
| 45 | 179 | 71 | 68.2 | 115.8 | 7.1 | 3.11 | 3.18 | 0 | 0 | 0 | 22.1591 |
| 55 | 167 | 57 | 74.2 | 90 | 8.5 | 4.12 | 2.01 | 1 | 1 | 0 | 20.4382 |
| 22 | 184 | 63 | 76 | 116.1 | 8.2 | 6.74 | 3.59 | 0 | 0 | 1 | 18.6082 |
| 24 | 160 | 82 | 68.4 | 132.8 | 4.7 | 1.91 | 3.29 | 1 | 1 | 0 | 32.0312 |
| 18 | 167 | 59 | 84.7 | 135.7 | 8.8 | 1.43 | 2.21 | 0 | 0 | 0 | 21.1553 |
| 72 | 152 | 91 | 79.5 | 148.2 | 8 | 5.37 | 4 | 0 | 1 | 0 | 39.3871 |
| 34 | 177 | 96 | 60 | 128.9 | 7.2 | 6.85 | 3.4 | 0 | 0 | 1 | 30.6425 |
| 63 | 155 | 68 | 68.6 | 137.8 | 5.7 | 3 | 4.64 | 0 | 0 | 1 | 28.3039 |
| 48 | 197 | 61 | 81.4 | 121.9 | 5.2 | 0.44 | 1.19 | 0 | 0 | 0 | 15.718 |
| 65 | 177 | 115 | 80.5 | 117.6 | 6.4 | 5.21 | 3.61 | 1 | 1 | 0 | 36.7072 |
| 22 | 178 | 92 | 90.2 | 133.2 | 6.8 | 4.53 | 1.06 | 1 | 0 | 1 | 29.0367 |
| 29 | 160 | 84 | 58.9 | 131.5 | 6 | 3.89 | 1.11 | 0 | 0 | 0 | 32.8125 |
Divisão dos Dados
Os dados foram divididos em treino (80%) e teste (20%) com o parâmetro random_state=42 para garantir reprodutibilidade e stratify=y para manter a proporção entre as classes is_fit.
Como a Random Forest é composta por árvores de decisão, não há necessidade de padronização das variáveis numéricas, porém a mesma estrutura de features foi mantida para comparação com os demais modelos.
Shape X: (2000, 9) Proporção 'is_fit'=1: 0.3995
Treinamento do Modelo
O modelo foi configurado com 300 árvores (n_estimators=300), seleção aleatória de atributos (max_features="sqrt") e profundidade ilimitada (max_depth=None), permitindo que cada árvore se adapte ao padrão dos dados de forma independente. O treinamento foi realizado sobre o conjunto de treino e avaliado com base na acurácia, balanced accuracy e matriz de confusão.
Acurácia: 0.7600 Balanced Accuracy: 0.7375 Classification Report: precision recall f1-score support 0 0.7727 0.8500 0.8095 240 1 0.7353 0.6250 0.6757 160 accuracy 0.7600 400 macro avg 0.7540 0.7375 0.7426 400 weighted avg 0.7578 0.7600 0.7560 400
Avaliação do Modelo
A Random Forest apresentou desempenho consistente e maior estabilidade em comparação com modelos individuais de árvore ou com o KNN. A matriz de confusão mostrou uma redução significativa nos erros de classificação, e o valor de Balanced Accuracy indicou boa capacidade de generalização entre as classes.
A análise da importância das variáveis revelou que os fatores mais determinantes para o modelo são:
- activity_index
- nutrition_quality
- bmi
- heart_rate
Essas variáveis refletem, respectivamente, o nível de atividade física, a qualidade da alimentação e os indicadores fisiológicos de saúde.
Conclusão
O uso da Random Forest no dataset de fitness demonstrou excelente equilíbrio entre desempenho e interpretabilidade. O modelo foi capaz de identificar padrões consistentes que diferenciam indivíduos “fit” e “não fit”, destacando-se pela robustez e pela redução de sobreajuste em relação à árvore de decisão simples.
A análise confirma que hábitos e parâmetros fisiológicos — especialmente atividade física, nutrição e índice de massa corporal — são fatores-chave para a previsão da condição física. Como próximos passos, recomenda-se explorar a otimização de hiperparâmetros (max_depth, min_samples_leaf, n_estimators) e realizar validação cruzada para refinar ainda mais a performance do modelo.