Se desea determinar si una masa en la mama es un tumor benigno o maligno, a partir de las medidas obtenidas de imágenes digitalizadas de la aspiración de una masa mamaria con una aguja fina. Los valores representan las características de los núcleos celulares presentes en la imagen digital.
Se tiene una muestra de 569 ejemplos de resultados de las biopsias. Cada registro contiene 32 variables, las cuales corresponden a tres medidas (media, desviación estándar, peor caso) de diez características diferentes (radius, texture, ...).
En términos de los datos, se desea pronosticar si una masa es benigna o maligna (clase B o M) a partir de las 30 variables.
Fuente de los datos: https://archive.ics.uci.edu/ml/datasets/Breast+Cancer+Wisconsin+(Diagnostic)
In [1]:
# carga de los datos
wbcd <- read.csv("data/wisc_bc_data.csv", stringsAsFactors = FALSE)
In [2]:
# verificación de los datos cargados
str(wbcd)
In [3]:
# se excluye la primera columna ya que no es informativa
wbcd <- wbcd[-1]
In [4]:
# cantidad de casos para cada diagnóstico
table(wbcd$diagnosis)
In [5]:
# se convierte la columna diagnosis en factor
wbcd$diagnosis <-
factor(wbcd$diagnosis,
levels = c("B", "M"), # las letras B y M se convierten en factores
labels = c("Benign", "Malignant")) # se dan nuevos nombres a los factores
In [6]:
# cantidad de casos para cada diagnostico
# note que los nombres son más informativos
table(wbcd$diagnosis)
In [7]:
# se convierte el conteo en probabilidades
round(prop.table(table(wbcd$diagnosis)) * 100, digits = 1)
El problema en términos matemáticos se define de la siguiente forma.
El método k-NN asígna una clase (de las $P$ posibles) al nuevo ejemplo en dos pasos. En el primer paso, determina los $k$ ejemplos más cercanos (distancia) al nuevo ejemplo; en el segundo paso, asigna la clase al nuevo punto por mayoría; es decir, asigna la clase con mayor frecuencia entre los $k$ vecinos más cercnos. Por ejemplo, si se consideran 7 vecinos, de los cuales 5 son "benignos" (y 2 "malignos") entonces el nuevo punto es clasificado como "benigno".
La distancia entre dos puntos $p$ y $q$ es:
$$dist(p, q) = \sqrt{\sum_{i=1}^N (p_i - q_i)^2}$$Ejercicio. ¿Cómo se implementa computacionalmente este algoritmo?
Ya que la escala de las variables numéricas afecta la medición, se pueden realizar dos transformaciones:
Normalización min-max.
$$z_j = \frac{x - \text{min}(x_j)}{\text{max}(x_j) - \text{min}(x_j)}$$Estandarización z.
$$z_j = \frac{x - \text{mean}(x_j)}{\text{std}(x_j) }$$Para variables nominales que representan $S$ categorías se crean $S-1$ variables: la primera variable vale 1 si la variable nominal toma el valor de la primera categoría; la segunda variable vale 2 si la variable nominal toma el valor de la segunda categoría, y así sucesivamente. ¿Por qué se requieren $S-1$ variables para $S$ categorías de la variable nominal?.
In [8]:
# se examina el rango de las variables
summary(wbcd[2:31])
Note que la información visualizada muestra que las variables tienen diferentes rangos. Para corregir este problema se normalizan las variables.
In [9]:
# se define la función de normalización
normalize <-
function(x) {
return ((x - min(x)) / (max(x) - min(x)))
}
In [10]:
# se aplica la función a los datos
wbcd_n <- as.data.frame(lapply(wbcd[2:31], normalize))
In [11]:
# se verifica la transformación
summary(wbcd_n)
In [12]:
# se crean los conjuntos de entrenamiento y prueba
wbcd_train <- wbcd_n[1:469, ]
wbcd_test <- wbcd_n[470:569, ]
# clase de cada ejemplo
wbcd_train_labels <- wbcd[1:469, 1]
wbcd_test_labels <- wbcd[470:569, 1]
In [13]:
# carga la librería
# install.packages("class")
library(class)
wbcd_test_pred <- knn(train = wbcd_train, # conjunto de entrenamiento
test = wbcd_test, # conjutno de prueba
cl = wbcd_train_labels, # clases para el conjunto de entrenamiento
k = 21) # número de vecinos
Para evaluar el desempeño en problemas de clasificación dicotómicos (dos clases mutuamente excluyentes) se usa la siguiente tabla:
Pronostico
P N
P VP FN
Real
N FP VN
VP - Verdadero positivo (correcto)
VN - Verdadero negativo (correcto)
FN - Falso negativo
FP - Falso positivo
In [14]:
#install.packages("gmodels")
library(gmodels)
CrossTable(x = wbcd_test_labels,
y = wbcd_test_pred,
prop.chisq=FALSE)
Ejercicio. Determine si el modelo mejora con $k$ = 21 y se estándarizan las variables. Ayuda: use la función scale
.
Ejercicio. Determine el valor óptimo de $k$ cuándo los datos son normalizados. Ayuda: Calcule la cantidad total de ejemplos mal clasificados para $k$ desde 1 hasta 27.
Ejercicio. Los resutados son dependientes de cómo se partieron los datos en entrenamiento y prueba. Cómo podría calcular de una forma más robusta la cantidad de vecinos?