Este tutorial recoge los pasos básicos para llevar a cabo un clasificador k-NN (k-Nearest Neighbors) haciendo uso del paquete class de R y de su función knn(). En este ejemplo realizaremos un clasificador que actua sobre las biopsias de posibles casos de cancer de pecho.
La fuente de datos de este ejemplo la obtendremos de la UCI Machine Learning Repository, concretamente de la siguiente URL que contiene los 569 casos con 32 variables en cada caso.
if(!file.exists('myfile.csv')) # descargamos el archivo sólo si no se encuentra ya en nuestro directorio de trabajo
{
url <- "http://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data"
download.file(url, destfile = 'myfile.csv', method = 'curl')
}
data <- read.csv('myfile.csv', header = FALSE, sep=',')
Empezamos echando un breve vistazo a los datos descargados y así tener una idea clara las características de los datos con los que estamos trabajando.
head(data)
## V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
## 1 842302 M 17.99 10.38 122.80 1001.0 0.11840 0.27760 0.3001 0.14710
## 2 842517 M 20.57 17.77 132.90 1326.0 0.08474 0.07864 0.0869 0.07017
## 3 84300903 M 19.69 21.25 130.00 1203.0 0.10960 0.15990 0.1974 0.12790
## 4 84348301 M 11.42 20.38 77.58 386.1 0.14250 0.28390 0.2414 0.10520
## 5 84358402 M 20.29 14.34 135.10 1297.0 0.10030 0.13280 0.1980 0.10430
## 6 843786 M 12.45 15.70 82.57 477.1 0.12780 0.17000 0.1578 0.08089
## V11 V12 V13 V14 V15 V16 V17 V18 V19
## 1 0.2419 0.07871 1.0950 0.9053 8.589 153.40 0.006399 0.04904 0.05373
## 2 0.1812 0.05667 0.5435 0.7339 3.398 74.08 0.005225 0.01308 0.01860
## 3 0.2069 0.05999 0.7456 0.7869 4.585 94.03 0.006150 0.04006 0.03832
## 4 0.2597 0.09744 0.4956 1.1560 3.445 27.23 0.009110 0.07458 0.05661
## 5 0.1809 0.05883 0.7572 0.7813 5.438 94.44 0.011490 0.02461 0.05688
## 6 0.2087 0.07613 0.3345 0.8902 2.217 27.19 0.007510 0.03345 0.03672
## V20 V21 V22 V23 V24 V25 V26 V27 V28 V29
## 1 0.01587 0.03003 0.006193 25.38 17.33 184.60 2019.0 0.1622 0.6656 0.7119
## 2 0.01340 0.01389 0.003532 24.99 23.41 158.80 1956.0 0.1238 0.1866 0.2416
## 3 0.02058 0.02250 0.004571 23.57 25.53 152.50 1709.0 0.1444 0.4245 0.4504
## 4 0.01867 0.05963 0.009208 14.91 26.50 98.87 567.7 0.2098 0.8663 0.6869
## 5 0.01885 0.01756 0.005115 22.54 16.67 152.20 1575.0 0.1374 0.2050 0.4000
## 6 0.01137 0.02165 0.005082 15.47 23.75 103.40 741.6 0.1791 0.5249 0.5355
## V30 V31 V32
## 1 0.2654 0.4601 0.11890
## 2 0.1860 0.2750 0.08902
## 3 0.2430 0.3613 0.08758
## 4 0.2575 0.6638 0.17300
## 5 0.1625 0.2364 0.07678
## 6 0.1741 0.3985 0.12440
str(data)
## 'data.frame': 569 obs. of 32 variables:
## $ V1 : int 842302 842517 84300903 84348301 84358402 843786 844359 84458202 844981 84501001 ...
## $ V2 : Factor w/ 2 levels "B","M": 2 2 2 2 2 2 2 2 2 2 ...
## $ V3 : num 18 20.6 19.7 11.4 20.3 ...
## $ V4 : num 10.4 17.8 21.2 20.4 14.3 ...
## $ V5 : num 122.8 132.9 130 77.6 135.1 ...
## $ V6 : num 1001 1326 1203 386 1297 ...
## $ V7 : num 0.1184 0.0847 0.1096 0.1425 0.1003 ...
## $ V8 : num 0.2776 0.0786 0.1599 0.2839 0.1328 ...
## $ V9 : num 0.3001 0.0869 0.1974 0.2414 0.198 ...
## $ V10: num 0.1471 0.0702 0.1279 0.1052 0.1043 ...
## $ V11: num 0.242 0.181 0.207 0.26 0.181 ...
## $ V12: num 0.0787 0.0567 0.06 0.0974 0.0588 ...
## $ V13: num 1.095 0.543 0.746 0.496 0.757 ...
## $ V14: num 0.905 0.734 0.787 1.156 0.781 ...
## $ V15: num 8.59 3.4 4.58 3.44 5.44 ...
## $ V16: num 153.4 74.1 94 27.2 94.4 ...
## $ V17: num 0.0064 0.00522 0.00615 0.00911 0.01149 ...
## $ V18: num 0.049 0.0131 0.0401 0.0746 0.0246 ...
## $ V19: num 0.0537 0.0186 0.0383 0.0566 0.0569 ...
## $ V20: num 0.0159 0.0134 0.0206 0.0187 0.0188 ...
## $ V21: num 0.03 0.0139 0.0225 0.0596 0.0176 ...
## $ V22: num 0.00619 0.00353 0.00457 0.00921 0.00511 ...
## $ V23: num 25.4 25 23.6 14.9 22.5 ...
## $ V24: num 17.3 23.4 25.5 26.5 16.7 ...
## $ V25: num 184.6 158.8 152.5 98.9 152.2 ...
## $ V26: num 2019 1956 1709 568 1575 ...
## $ V27: num 0.162 0.124 0.144 0.21 0.137 ...
## $ V28: num 0.666 0.187 0.424 0.866 0.205 ...
## $ V29: num 0.712 0.242 0.45 0.687 0.4 ...
## $ V30: num 0.265 0.186 0.243 0.258 0.163 ...
## $ V31: num 0.46 0.275 0.361 0.664 0.236 ...
## $ V32: num 0.1189 0.089 0.0876 0.173 0.0768 ...
Nuestro dataframe está compuesto por 569 observaciones de 32 variables. La primera variable representa un código de identificación que en el caso que nos ocupa no tiene ninguna utilidad por lo que podremos eliminarlo más adelante. La segunda variable representa el resultado de la biopsia como “B” en caso de tratarse de un tumor benigno y con “M” en el caso de tumor maligno. Las 30 variables restantes representan diferentes características del núcleo de la célula. Realmente se miden 10 características pero de cada una de ellas se representan con 3 valores: media, error estandar, el mayor valor de:
Dado que el dataframe no contiene cabecera, será necesario incluirla y asi facilitar el trabajo posterior de análisis. Para ayudarnos en la denominación de cada una de las variables es recomendable revisar el documento breast-cancer-wiscosin.names que contiene un breve descripción de todas las variables y como están organizadas en el dataframe.
Vamos a añadir las cabeceras al dataframe original. Conviene resaltar que los nombres de las variables deben ser lo suficientemente explícitos como para poder entender el contenido de las mismas a simple vista. Para ello vamos a crear un vector de nombres con 32 elementos que contendrá el nombre de las variables de forma descriptiva.
Las observaciones están organizadas en el dataframe de manera que de la columna 3 al 12 serán los valores medios de las variables del 13 al 22 serán los errores estandar y del 23 al 32 serán los valores máximos. La columna 1 corresponde al ID de la obserbación y la columna 2 el diagnóstico.
La siguiente secuencia de comandos nos permite crear la cabecera con el nombre de todas las variables y en el orden correcto en el dataframe.
basic_names <- c("Radius","Texture","Perimeter","Area","Smoothness","Compactness","Concavity","Concave_Points","Simmetry","Fractal_Dimension")
basic_names_mean <- paste(basic_names,"Mean",sep="_")
basic_names_error_st <- paste(basic_names,"ErrorST", sep="_")
basic_names_largest <- paste(basic_names,"Largest",sep="_")
data_names <- c("ID","Diagnosis",basic_names_mean, basic_names_error_st, basic_names_largest)
print(data_names)
## [1] "ID" "Diagnosis"
## [3] "Radius_Mean" "Texture_Mean"
## [5] "Perimeter_Mean" "Area_Mean"
## [7] "Smoothness_Mean" "Compactness_Mean"
## [9] "Concavity_Mean" "Concave_Points_Mean"
## [11] "Simmetry_Mean" "Fractal_Dimension_Mean"
## [13] "Radius_ErrorST" "Texture_ErrorST"
## [15] "Perimeter_ErrorST" "Area_ErrorST"
## [17] "Smoothness_ErrorST" "Compactness_ErrorST"
## [19] "Concavity_ErrorST" "Concave_Points_ErrorST"
## [21] "Simmetry_ErrorST" "Fractal_Dimension_ErrorST"
## [23] "Radius_Largest" "Texture_Largest"
## [25] "Perimeter_Largest" "Area_Largest"
## [27] "Smoothness_Largest" "Compactness_Largest"
## [29] "Concavity_Largest" "Concave_Points_Largest"
## [31] "Simmetry_Largest" "Fractal_Dimension_Largest"
Ahora simplemente tenemos que modificar nuestro dataframe original con los nombres de las variables que hemos obtenido. Para ello crearemos una copia del data frame original y trabajaremos sobre este último hasta darle un formato acorde con los análisis que queremos realizar.
data_ok <- data
names(data_ok)<-data_names
summary(data_ok)
## ID Diagnosis Radius_Mean Texture_Mean
## Min. : 8670 B:357 Min. : 6.981 Min. : 9.71
## 1st Qu.: 869218 M:212 1st Qu.:11.700 1st Qu.:16.17
## Median : 906024 Median :13.370 Median :18.84
## Mean : 30371831 Mean :14.127 Mean :19.29
## 3rd Qu.: 8813129 3rd Qu.:15.780 3rd Qu.:21.80
## Max. :911320502 Max. :28.110 Max. :39.28
## Perimeter_Mean Area_Mean Smoothness_Mean Compactness_Mean
## Min. : 43.79 Min. : 143.5 Min. :0.05263 Min. :0.01938
## 1st Qu.: 75.17 1st Qu.: 420.3 1st Qu.:0.08637 1st Qu.:0.06492
## Median : 86.24 Median : 551.1 Median :0.09587 Median :0.09263
## Mean : 91.97 Mean : 654.9 Mean :0.09636 Mean :0.10434
## 3rd Qu.:104.10 3rd Qu.: 782.7 3rd Qu.:0.10530 3rd Qu.:0.13040
## Max. :188.50 Max. :2501.0 Max. :0.16340 Max. :0.34540
## Concavity_Mean Concave_Points_Mean Simmetry_Mean
## Min. :0.00000 Min. :0.00000 Min. :0.1060
## 1st Qu.:0.02956 1st Qu.:0.02031 1st Qu.:0.1619
## Median :0.06154 Median :0.03350 Median :0.1792
## Mean :0.08880 Mean :0.04892 Mean :0.1812
## 3rd Qu.:0.13070 3rd Qu.:0.07400 3rd Qu.:0.1957
## Max. :0.42680 Max. :0.20120 Max. :0.3040
## Fractal_Dimension_Mean Radius_ErrorST Texture_ErrorST
## Min. :0.04996 Min. :0.1115 Min. :0.3602
## 1st Qu.:0.05770 1st Qu.:0.2324 1st Qu.:0.8339
## Median :0.06154 Median :0.3242 Median :1.1080
## Mean :0.06280 Mean :0.4052 Mean :1.2169
## 3rd Qu.:0.06612 3rd Qu.:0.4789 3rd Qu.:1.4740
## Max. :0.09744 Max. :2.8730 Max. :4.8850
## Perimeter_ErrorST Area_ErrorST Smoothness_ErrorST
## Min. : 0.757 Min. : 6.802 Min. :0.001713
## 1st Qu.: 1.606 1st Qu.: 17.850 1st Qu.:0.005169
## Median : 2.287 Median : 24.530 Median :0.006380
## Mean : 2.866 Mean : 40.337 Mean :0.007041
## 3rd Qu.: 3.357 3rd Qu.: 45.190 3rd Qu.:0.008146
## Max. :21.980 Max. :542.200 Max. :0.031130
## Compactness_ErrorST Concavity_ErrorST Concave_Points_ErrorST
## Min. :0.002252 Min. :0.00000 Min. :0.000000
## 1st Qu.:0.013080 1st Qu.:0.01509 1st Qu.:0.007638
## Median :0.020450 Median :0.02589 Median :0.010930
## Mean :0.025478 Mean :0.03189 Mean :0.011796
## 3rd Qu.:0.032450 3rd Qu.:0.04205 3rd Qu.:0.014710
## Max. :0.135400 Max. :0.39600 Max. :0.052790
## Simmetry_ErrorST Fractal_Dimension_ErrorST Radius_Largest
## Min. :0.007882 Min. :0.0008948 Min. : 7.93
## 1st Qu.:0.015160 1st Qu.:0.0022480 1st Qu.:13.01
## Median :0.018730 Median :0.0031870 Median :14.97
## Mean :0.020542 Mean :0.0037949 Mean :16.27
## 3rd Qu.:0.023480 3rd Qu.:0.0045580 3rd Qu.:18.79
## Max. :0.078950 Max. :0.0298400 Max. :36.04
## Texture_Largest Perimeter_Largest Area_Largest Smoothness_Largest
## Min. :12.02 Min. : 50.41 Min. : 185.2 Min. :0.07117
## 1st Qu.:21.08 1st Qu.: 84.11 1st Qu.: 515.3 1st Qu.:0.11660
## Median :25.41 Median : 97.66 Median : 686.5 Median :0.13130
## Mean :25.68 Mean :107.26 Mean : 880.6 Mean :0.13237
## 3rd Qu.:29.72 3rd Qu.:125.40 3rd Qu.:1084.0 3rd Qu.:0.14600
## Max. :49.54 Max. :251.20 Max. :4254.0 Max. :0.22260
## Compactness_Largest Concavity_Largest Concave_Points_Largest
## Min. :0.02729 Min. :0.0000 Min. :0.00000
## 1st Qu.:0.14720 1st Qu.:0.1145 1st Qu.:0.06493
## Median :0.21190 Median :0.2267 Median :0.09993
## Mean :0.25427 Mean :0.2722 Mean :0.11461
## 3rd Qu.:0.33910 3rd Qu.:0.3829 3rd Qu.:0.16140
## Max. :1.05800 Max. :1.2520 Max. :0.29100
## Simmetry_Largest Fractal_Dimension_Largest
## Min. :0.1565 Min. :0.05504
## 1st Qu.:0.2504 1st Qu.:0.07146
## Median :0.2822 Median :0.08004
## Mean :0.2901 Mean :0.08395
## 3rd Qu.:0.3179 3rd Qu.:0.09208
## Max. :0.6638 Max. :0.20750
Ya tenemos nuestro dataframe con los nombres de las variables suficientemente descriptivos.
La variable ID no tendrá ninguna utilidad en nuestro clasificador por lo que la vamos a eliminar del dataframe.
data_ok$ID <- NULL
A continuación vamos a comprobar si todas las observaciones están completas y no falta ningún valor. Si se diera el caso, tendriamos que decidir si omitir las observaciones incompletas o completar las mismas con valores promedio.
colSums(is.na(data_ok))
## Diagnosis Radius_Mean
## 0 0
## Texture_Mean Perimeter_Mean
## 0 0
## Area_Mean Smoothness_Mean
## 0 0
## Compactness_Mean Concavity_Mean
## 0 0
## Concave_Points_Mean Simmetry_Mean
## 0 0
## Fractal_Dimension_Mean Radius_ErrorST
## 0 0
## Texture_ErrorST Perimeter_ErrorST
## 0 0
## Area_ErrorST Smoothness_ErrorST
## 0 0
## Compactness_ErrorST Concavity_ErrorST
## 0 0
## Concave_Points_ErrorST Simmetry_ErrorST
## 0 0
## Fractal_Dimension_ErrorST Radius_Largest
## 0 0
## Texture_Largest Perimeter_Largest
## 0 0
## Area_Largest Smoothness_Largest
## 0 0
## Compactness_Largest Concavity_Largest
## 0 0
## Concave_Points_Largest Simmetry_Largest
## 0 0
## Fractal_Dimension_Largest
## 0
Podemos comprobar como en nuestro dataframe todas las observaciones están completas y por tanto continuar con el análisis.
Observamos ahora los rangos de valores que toman cada una de las variables. En algunos casos hay grandes variaciones en magnitud por lo que será recomendable normalizar los mismos entre 0 y 1 en todos los casos.
summary(data_ok)
## Diagnosis Radius_Mean Texture_Mean Perimeter_Mean
## B:357 Min. : 6.981 Min. : 9.71 Min. : 43.79
## M:212 1st Qu.:11.700 1st Qu.:16.17 1st Qu.: 75.17
## Median :13.370 Median :18.84 Median : 86.24
## Mean :14.127 Mean :19.29 Mean : 91.97
## 3rd Qu.:15.780 3rd Qu.:21.80 3rd Qu.:104.10
## Max. :28.110 Max. :39.28 Max. :188.50
## Area_Mean Smoothness_Mean Compactness_Mean Concavity_Mean
## Min. : 143.5 Min. :0.05263 Min. :0.01938 Min. :0.00000
## 1st Qu.: 420.3 1st Qu.:0.08637 1st Qu.:0.06492 1st Qu.:0.02956
## Median : 551.1 Median :0.09587 Median :0.09263 Median :0.06154
## Mean : 654.9 Mean :0.09636 Mean :0.10434 Mean :0.08880
## 3rd Qu.: 782.7 3rd Qu.:0.10530 3rd Qu.:0.13040 3rd Qu.:0.13070
## Max. :2501.0 Max. :0.16340 Max. :0.34540 Max. :0.42680
## Concave_Points_Mean Simmetry_Mean Fractal_Dimension_Mean
## Min. :0.00000 Min. :0.1060 Min. :0.04996
## 1st Qu.:0.02031 1st Qu.:0.1619 1st Qu.:0.05770
## Median :0.03350 Median :0.1792 Median :0.06154
## Mean :0.04892 Mean :0.1812 Mean :0.06280
## 3rd Qu.:0.07400 3rd Qu.:0.1957 3rd Qu.:0.06612
## Max. :0.20120 Max. :0.3040 Max. :0.09744
## Radius_ErrorST Texture_ErrorST Perimeter_ErrorST Area_ErrorST
## Min. :0.1115 Min. :0.3602 Min. : 0.757 Min. : 6.802
## 1st Qu.:0.2324 1st Qu.:0.8339 1st Qu.: 1.606 1st Qu.: 17.850
## Median :0.3242 Median :1.1080 Median : 2.287 Median : 24.530
## Mean :0.4052 Mean :1.2169 Mean : 2.866 Mean : 40.337
## 3rd Qu.:0.4789 3rd Qu.:1.4740 3rd Qu.: 3.357 3rd Qu.: 45.190
## Max. :2.8730 Max. :4.8850 Max. :21.980 Max. :542.200
## Smoothness_ErrorST Compactness_ErrorST Concavity_ErrorST
## Min. :0.001713 Min. :0.002252 Min. :0.00000
## 1st Qu.:0.005169 1st Qu.:0.013080 1st Qu.:0.01509
## Median :0.006380 Median :0.020450 Median :0.02589
## Mean :0.007041 Mean :0.025478 Mean :0.03189
## 3rd Qu.:0.008146 3rd Qu.:0.032450 3rd Qu.:0.04205
## Max. :0.031130 Max. :0.135400 Max. :0.39600
## Concave_Points_ErrorST Simmetry_ErrorST Fractal_Dimension_ErrorST
## Min. :0.000000 Min. :0.007882 Min. :0.0008948
## 1st Qu.:0.007638 1st Qu.:0.015160 1st Qu.:0.0022480
## Median :0.010930 Median :0.018730 Median :0.0031870
## Mean :0.011796 Mean :0.020542 Mean :0.0037949
## 3rd Qu.:0.014710 3rd Qu.:0.023480 3rd Qu.:0.0045580
## Max. :0.052790 Max. :0.078950 Max. :0.0298400
## Radius_Largest Texture_Largest Perimeter_Largest Area_Largest
## Min. : 7.93 Min. :12.02 Min. : 50.41 Min. : 185.2
## 1st Qu.:13.01 1st Qu.:21.08 1st Qu.: 84.11 1st Qu.: 515.3
## Median :14.97 Median :25.41 Median : 97.66 Median : 686.5
## Mean :16.27 Mean :25.68 Mean :107.26 Mean : 880.6
## 3rd Qu.:18.79 3rd Qu.:29.72 3rd Qu.:125.40 3rd Qu.:1084.0
## Max. :36.04 Max. :49.54 Max. :251.20 Max. :4254.0
## Smoothness_Largest Compactness_Largest Concavity_Largest
## Min. :0.07117 Min. :0.02729 Min. :0.0000
## 1st Qu.:0.11660 1st Qu.:0.14720 1st Qu.:0.1145
## Median :0.13130 Median :0.21190 Median :0.2267
## Mean :0.13237 Mean :0.25427 Mean :0.2722
## 3rd Qu.:0.14600 3rd Qu.:0.33910 3rd Qu.:0.3829
## Max. :0.22260 Max. :1.05800 Max. :1.2520
## Concave_Points_Largest Simmetry_Largest Fractal_Dimension_Largest
## Min. :0.00000 Min. :0.1565 Min. :0.05504
## 1st Qu.:0.06493 1st Qu.:0.2504 1st Qu.:0.07146
## Median :0.09993 Median :0.2822 Median :0.08004
## Mean :0.11461 Mean :0.2901 Mean :0.08395
## 3rd Qu.:0.16140 3rd Qu.:0.3179 3rd Qu.:0.09208
## Max. :0.29100 Max. :0.6638 Max. :0.20750
También podemos visualizar gráficamente la relación existente entre cada una de las variables y si dicha relación podría ayudarnos a clasificar la tipología del tumor. Dado que el número de variables es elevado no podemos representar en un único gráfico la matriz gráfica de correlaciones. Esta tarea la llevaremos a cabo mediante tres matrices de correlación diferentes aprovechando que las variables ya se encuentran agrupadas en tres grupos dentro del dataframe: Mean, ErrorST y Largest:
panel.cor <- function(x,y) {
par(usr=c(0,1,0,1))
r <- as.character(round(cor(x,y,method="pearson"),2)) # por defecto la función cor() calcula el coeficiente de correlación de Pearson como medida de la correlación lineal
txt <- paste0("R = ", r)
text(0.5, 0.5, txt, cex = 1)
}
upper.panel<-function(x,y) {
points(x,y, pch=19, cex=0.5, col =c("green","red")[data_ok$Diagnosis])
}
pairs(data_ok[2:11], main="Mean Char.", lower.panel = panel.cor, upper.panel = upper.panel)
pairs(data_ok[12:21], main="Std Error Char.", lower.panel = panel.cor, upper.panel = upper.panel)
pairs(data_ok[22:31], main="Largest Char.", lower.panel = panel.cor, upper.panel = upper.panel)
A la vista de los resultados, puede resultar interesante hacer un zoom específico sobre alguna de estas relacciones para visualizar con más detalle el agrupamiento que se produce:
library(ggplot2)
qplot(Radius_Mean, Perimeter_Mean, data=data_ok, col =Diagnosis, geom=c("point","smooth"), main = "????")
## `geom_smooth()` using method = 'loess'
En ambos gráficos puede observarse una interesante dependencia de las variables usadas que podrían determinar el tipo de vino. Del mismo modo también sería posible a través de estos análisis previos eliminar alguna variable si consideramos que la misma no colabora positivamente a realizar la clasificación.