Entrega Individual
Introdução
- Exploração de dados: Ao selecionar uma base no kaggle referentes a cinco tipos de remédio, remédio A, B, C, X e Y, tem como objetivo prever qual remédio o paciente teria uma resposta melhor. As colunas presentes nesse dataset são Idade, Sexo, Pressão Arterial, nivel de colesterol, nivel de sódio para potássio no sangue e remédio que seria nossa target.
Colunas
- Age (Idade): Essa coluna temos a idade dos pacientes, com a idade minima presente de 15, idade média de 44,3 e maxima de 74 sendo do tipo Integer.
- Sex (Sexo): Essa coluna tem o sexo de cada paciente, divididos em 52% Masculino e 48% feminino, dados do tipo String.
- Blood Pressure (Pressão Arterial): Essa coluna tem os niveis de pressão arterial de cada paciente sendo dividida em 39% HIGH, 29% NORMAL e 32% LOW, dados do tipo String.
- Cholesterol (nivel de colesterol): Essa coluna tem os niveis de colesterol de cada paciente sendo divididos em 52% HIGH e 49% NORMAL, dados do tipo String.
- Na_to_K (sódio para potássio): Essa coluna tem os a razão de sódio para potássio no sangue de um paciente, com a minima de 6,27, media de 16,1 e maxima de 38,2, dados do tipo Float/Decimal.
- Drug (remédio): Essa coluna tem os remédio de melhor resposta para o paciente, dados do tipo String.
Age | Sex | BP | Cholesterol | Na_to_K | Drug |
---|---|---|---|---|---|
36 | M | LOW | NORMAL | 11.424 | drugX |
16 | F | HIGH | NORMAL | 15.516 | drugY |
18 | F | NORMAL | NORMAL | 8.75 | drugX |
59 | F | LOW | HIGH | 10.444 | drugC |
47 | M | LOW | NORMAL | 33.542 | drugY |
51 | M | HIGH | HIGH | 18.295 | drugY |
18 | F | HIGH | NORMAL | 24.276 | drugY |
28 | F | NORMAL | HIGH | 12.879 | drugX |
42 | M | HIGH | NORMAL | 12.766 | drugA |
66 | F | NORMAL | NORMAL | 8.107 | drugX |
Pré-processamento
Primeiro foi feita uma verificação em todas as colunas procurando valores faltantes e substituindo eles pela mediana em valores numéricos ou pela moda em variáveis categóricas. Como vimos na descrição das colunas temos três que possuem dados categóricos do tipo String, sendo elas Sex(Sexo), Blood Pressure(Pressão Arterial) e Cholesterol(nivel de colesterol), para conseguirmos utilizar essas informações é necessario convertelas em numeros, oque foi feito utilizando a biblioteca scikit-learn que possui a função LabelEncoder(), em seguida aplicamos dois tipos de escalonamento às colunas numéricas Age e Na_to_K: padronização (z-score) e normalização min-max.
N-Age | Sex | BP | Cholesterol | N-Na_to_K | Drug |
---|---|---|---|---|---|
0.4 | 1 | 1 | 1 | 0.130411 | drugX |
0 | 0 | 0 | 1 | 0.291292 | drugY |
0.04 | 0 | 2 | 1 | 0.0252801 | drugX |
0.86 | 0 | 1 | 0 | 0.0918813 | drugC |
0.62 | 1 | 1 | 1 | 1 | drugY |
0.7 | 1 | 0 | 0 | 0.40055 | drugY |
0.04 | 0 | 0 | 1 | 0.635699 | drugY |
0.24 | 0 | 2 | 0 | 0.187615 | drugX |
0.52 | 1 | 0 | 1 | 0.183173 | drugA |
1 | 0 | 2 | 1 | 0 | drugX |
import pandas as pd
from sklearn.preprocessing import LabelEncoder
# Preprocess the data
def preprocess(df):
# Fill missing values
df['Age'].fillna(df['Age'].median(), inplace=True)
df['Sex'].fillna(df['Sex'].mode()[0], inplace=True)
df['BP'].fillna(df['BP'].mode()[0], inplace=True)
df['Cholesterol'].fillna(df['Cholesterol'].mode()[0], inplace=True)
df['Na_to_K'].fillna(df['Na_to_K'].median(), inplace=True)
df['Drug'].fillna(df['Drug'].mode()[0], inplace=True)
# Convert categorical variables
label_encoder = LabelEncoder()
df['Sex'] = label_encoder.fit_transform(df['Sex'])
df['BP'] = label_encoder.fit_transform(df['BP'])
df['Cholesterol'] = label_encoder.fit_transform(df['Cholesterol'])
# Select features
features = ['Age', 'Sex', 'BP', 'Cholesterol', 'Na_to_K', 'Drug']
return df[features]
# Load the dataset
df = pd.read_csv('https://raw.githubusercontent.com/alexandremartinelli11/machine-learning/refs/heads/main/data/kaggle/drug200.csv')
df = df.sample(n=10, random_state=42)
# Preprocessing
df = preprocess(df)
# Display the first few rows of the dataset
print(df.to_markdown(index=False))
Age | N-Age | Z-Age | Na_to_K | N-Na_to_K | Z-Na_to_K | |
---|---|---|---|---|---|---|
95 | 36 | 0.4 | -0.117416 | 11.424 | 0.130411 | -0.526121 |
15 | 16 | 0 | -1.23566 | 15.516 | 0.291292 | -0.0105705 |
30 | 18 | 0.04 | -1.12384 | 8.75 | 0.0252801 | -0.863018 |
158 | 59 | 0.86 | 1.16857 | 10.444 | 0.0918813 | -0.649591 |
128 | 47 | 0.62 | 0.49762 | 33.542 | 1 | 2.26052 |
115 | 51 | 0.7 | 0.72127 | 18.295 | 0.40055 | 0.339555 |
69 | 18 | 0.04 | -1.12384 | 24.276 | 0.635699 | 1.0931 |
170 | 28 | 0.24 | -0.564715 | 12.879 | 0.187615 | -0.342806 |
174 | 42 | 0.52 | 0.218058 | 12.766 | 0.183173 | -0.357043 |
45 | 66 | 1 | 1.55996 | 8.107 | 0 | -0.944029 |
import pandas as pd
from sklearn.preprocessing import LabelEncoder
def standardization(df):
df['Z-Age'] = df['Age'].apply(lambda x: (x-df['Age'].mean())/df['Age'].std())
df['N-Age'] = df['Age'].apply(lambda x: (x-df['Age'].min())/(df['Age'].max()-df['Age'].min()))
df['Z-Na_to_K'] = df['Na_to_K'].apply(lambda x: (x-df['Na_to_K'].mean())/df['Na_to_K'].std())
df['N-Na_to_K'] = df['Na_to_K'].apply(lambda x: (x-df['Na_to_K'].min())/(df['Na_to_K'].max()-df['Na_to_K'].min()))
df = df[['Age', 'N-Age', 'Z-Age', 'Na_to_K', 'N-Na_to_K', 'Z-Na_to_K']].dropna()
print(df.head(10).to_markdown())
def preprocess(df):
# Fill missing values
df['Age'].fillna(df['Age'].median(), inplace=True)
df['Sex'].fillna(df['Sex'].mode()[0], inplace=True)
df['BP'].fillna(df['BP'].mode()[0], inplace=True)
df['Cholesterol'].fillna(df['Cholesterol'].mode()[0], inplace=True)
df['Na_to_K'].fillna(df['Na_to_K'].median(), inplace=True)
df['Drug'].fillna(df['Drug'].mode()[0], inplace=True)
# Convert categorical variables
label_encoder = LabelEncoder()
df['Sex'] = label_encoder.fit_transform(df['Sex'])
df['BP'] = label_encoder.fit_transform(df['BP'])
df['Cholesterol'] = label_encoder.fit_transform(df['Cholesterol'])
# Select features
features = ['Age', 'Sex', 'BP', 'Cholesterol', 'Na_to_K', 'Drug']
return df[features]
# Load the dataset
df = pd.read_csv('https://raw.githubusercontent.com/alexandremartinelli11/machine-learning/refs/heads/main/data/kaggle/drug200.csv')
df = df.sample(n=10, random_state=42)
# Preprocessing
df = preprocess(df)
standardization(df)
Divisão dos Dados
O conjunto de dados foi dividido em 70% para treino e 30% para validação, garantindo que o modelo fosse treinado em uma parte significativa das observações, mas ainda avaliado em dados não vistos. O uso do conjunto de validação tem como objetivo detectar e reduzir o risco de overfitting.
Treinamento do Modelo
Acurácia: 0.56%
Matriz de Confusão:
Classe Pred 0 | Classe Pred 1 | Classe Pred 2 | Classe Pred 3 | Classe Pred 4 | |
---|---|---|---|---|---|
Classe Real 0 | 0 | 0 | 0 | 0 | 17 |
Classe Real 1 | 0 | 0 | 0 | 0 | 13 |
Classe Real 2 | 0 | 0 | 0 | 0 | 11 |
Classe Real 3 | 0 | 0 | 0 | 43 | 0 |
Classe Real 4 | 0 | 0 | 0 | 30 | 46 |
import numpy as np
import matplotlib.pyplot as plt
from io import StringIO
import pandas as pd
from scipy.stats import mode
from sklearn.preprocessing import LabelEncoder
from sklearn.cluster import KMeans
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.decomposition import PCA
plt.figure(figsize=(12, 10))
def standardization(df):
df['Z-Age'] = df['Age'].apply(lambda x: (x - df['Age'].mean()) / df['Age'].std())
df['N-Age'] = df['Age'].apply(lambda x: (x - df['Age'].min()) / (df['Age'].max() - df['Age'].min()))
df['Z-Na_to_K'] = df['Na_to_K'].apply(lambda x: (x - df['Na_to_K'].mean()) / df['Na_to_K'].std())
df['N-Na_to_K'] = df['Na_to_K'].apply(lambda x: (x - df['Na_to_K'].min()) / (df['Na_to_K'].max() - df['Na_to_K'].min()))
features = ['N-Age', 'Sex', 'BP', 'Cholesterol', 'N-Na_to_K', 'Drug']
return df[features]
def preprocess(df):
# Fill missing values
df['Age'].fillna(df['Age'].median(), inplace=True)
df['Sex'].fillna(df['Sex'].mode()[0], inplace=True)
df['BP'].fillna(df['BP'].mode()[0], inplace=True)
df['Cholesterol'].fillna(df['Cholesterol'].mode()[0], inplace=True)
df['Na_to_K'].fillna(df['Na_to_K'].median(), inplace=True)
df['Drug'].fillna(df['Drug'].mode()[0], inplace=True)
# Convert categorical variables
label_encoder = LabelEncoder()
df['Sex'] = label_encoder.fit_transform(df['Sex'])
df['BP'] = label_encoder.fit_transform(df['BP'])
df['Cholesterol'] = label_encoder.fit_transform(df['Cholesterol'])
df['Drug'] = label_encoder.fit_transform(df['Drug'])
features = ['Age', 'Sex', 'BP', 'Cholesterol', 'Na_to_K', 'Drug']
return df[features]
label_encoder = LabelEncoder()
# Load dataset
df = pd.read_csv('https://raw.githubusercontent.com/alexandremartinelli11/machine-learning/refs/heads/main/data/kaggle/drug200.csv')
# Preprocessing
d = preprocess(df.copy())
d = standardization(d)
X = d[['N-Age', 'BP', 'Cholesterol', 'N-Na_to_K']]
y = d['Drug']
#Separar em teste e validação
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Reduzir para 2 dimensões com PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_train)
# Treinar KMeans
kmeans = KMeans(n_clusters=5, init="k-means++", max_iter=100, random_state=42)
labels = kmeans.fit_predict(X_pca)
# Mapear clusters para classes reais por voto majoritário
cluster_map = {}
for c in np.unique(labels):
mask = labels == c
majority_class = mode(y_train[mask], keepdims=False)[0]
cluster_map[c] = majority_class
# Reatribuir clusters como classes previstas
y_pred = np.array([cluster_map[c] for c in labels])
# Calcular acurácia e matriz de confusão
acc = accuracy_score(y_train, y_pred)
cm = confusion_matrix(y_train, y_pred)
cm_df = pd.DataFrame(
cm,
index=[f"Classe Real {cls}" for cls in np.unique(y_train)],
columns=[f"Classe Pred {cls}" for cls in np.unique(y_train)]
)
print(f"Acurácia: {acc:.2f}%")
print("<br>Matriz de Confusão:")
print(cm_df.to_html())
# Também projetar os centróides no PCA
centroids_pca = kmeans.cluster_centers_
# Plot results
plt.figure(figsize=(10, 8))
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=labels, cmap='viridis', s=50)
plt.scatter(centroids_pca[:, 0], centroids_pca[:, 1],
c='red', marker='*', s=200, label='Centroids')
plt.title('K-Means Clustering (PCA 2D)')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.legend()
# Save plot to buffer
buffer = StringIO()
plt.savefig(buffer, format="svg", transparent=True)
print(buffer.getvalue())
Avaliação do Modelo
O K-Means, mesmo após redução com PCA, não conseguiu reproduzir bem as cinco classes do conjunto de dados. A matriz de confusão mostra que os clusters formados concentram-se principalmente em duas classes, indicando que o algoritmo não separou adequadamente todas as categorias. Assim, a acurácia obtida reflete apenas a sobreposição entre clusters e classes reais. Uma possível melhoria seria aumentar a quantidade de dados.