Soy estudiante de MsC Data en Universitat Oberta de Catalunya, con esta serie de informes busco ofrecer un blog personal que permitan evidenciar el crecimiento académico que se puede ir adquiriendo con el paso del Master y como R Studio es una herramienta de gran uso.
En este nuevo informe, realizaremos un ánalisis descriptivo y predictivo de una fuente de datos real que ha sido modificada para el caso de estudio, representandola en un mapa por posiciones geograficas
Librerias
# Librerias
library(dplyr) # Para tratamiento de datos
library(ggplot2) # para graficos
library(leaflet) # para mapas
library(cluster)
library(mclust)
library(factoextra)
library(fpc)
library(dbscan)
Identificar sectores geograficos donde se caé constantemente la red móvil, proponiendo ubicaciones especificas para iniciar el levantamiento de redes operadoras de telefonía móvil en el departamento de Cundinamarca, Colombia.
En esta actividad se realizará un análisis sobre un grupo de clientes de una compañia de telefonía que han decidido compartir su ubicación cuando una de las llamadas se cae por falta de conexión. Se plantea como objetivo lograr identificar clusters (grupos) de clientes por posición geográfica, con el fin de identificar las zonas demayor perdida de señal, logrando que la alta gerencia de la compañia de telefonía tome decisiones que garanticen la prestación de un mejor servicio.
Los métodos de clustering que se aplicarán en este documento son:
El set de datos ha sido tomado del curso de Ciudadano de Datos impartido por la Alianza CAOBA (Alianza Big Data para Colombia) al cual asistí hace algún tiempo. El set cuenta con diferentes variables, entre las cuales estan compartir, lat y lon que seran las variables que utilizaremos para extraer los clientes que decidieron compartir su ubicación al momento de la caida de la llamada. Este set puede ser tratado para la aplicación de algoritmos supervisados y no supervisados.
clientes_telefonia<- read.csv("clientes_telefonia.csv", sep = ",", na.strings = "NA", header = T,
fill = T, row.names = NULL, fileEncoding="utf-8" )
names(clientes_telefonia) <- c("estado","meses","ingresos","casa",
"precio_dispositivo","promedio_duracion_llamada","satisfaccion","sobrecargo",
"saldo_restante","compartir","lon","lat")
Para empezar, el archivo cuenta con 23162 observaciones y 12 variables (estado, meses, ingresos, casa, precio_dispositivo, promedio_duracion_llamada, satisfaccion, sobrecargo, saldo_restante, compartir, lon, lat). Los tipos de variables son:
str(clientes_telefonia)
## 'data.frame': 23162 obs. of 12 variables:
## $ estado : Factor w/ 2 levels "RETIRADO","VINCULADO": 2 2 1 1 1 2 1 1 2 1 ...
## $ meses : num 26 23 38 37 19 19 20 32 41 19 ...
## $ ingresos : num 4074840 8574088 795993 8115015 8187499 ...
## $ casa : num 8.83e+08 1.74e+09 9.50e+07 8.30e+07 6.77e+08 ...
## $ precio_dispositivo : num 1444153 2157661 452809 2617184 666069 ...
## $ promedio_duracion_llamada: num 2.3 3.7 16.8 17.6 0 2.2 14.8 1.3 3 17.8 ...
## $ satisfaccion : num 1.76 3.36 8.36 8.96 8.81 ...
## $ sobrecargo : num 11.2 0 350.6 230.9 131.5 ...
## $ saldo_restante : num 20.3 53.5 13.9 45.7 67.2 72 69.5 19 64.6 19.9 ...
## $ compartir : Factor w/ 2 levels "NO","SI": 1 1 1 1 1 1 1 1 1 1 ...
## $ lon : num 0 0 0 0 0 0 0 0 0 0 ...
## $ lat : num 0 0 0 0 0 0 0 0 0 0 ...
Una vista inicial al set de datos nos permite observar:
head(clientes_telefonia)
estado <fctr> | meses <dbl> | ingresos <dbl> | casa <dbl> | precio_dispositivo <dbl> | ||
---|---|---|---|---|---|---|
1 | VINCULADO | 26 | 4074840 | 8.830e+08 | 1444153 | |
2 | VINCULADO | 23 | 8574088 | 1.745e+09 | 2157661 | |
3 | RETIRADO | 38 | 795993 | 9.500e+07 | 452809 | |
4 | RETIRADO | 37 | 8115015 | 8.300e+07 | 2617184 | |
5 | RETIRADO | 19 | 8187499 | 6.770e+08 | 666069 | |
6 | VINCULADO | 19 | 7250225 | 4.240e+08 | 548116 |
Como se evidencia en la visualización cargada anteriormente, existe la variable compartir, la cual indica cuando un cliente ha decidido compartir su posición geográfica, una vez se ha caido su señal telefónica en una llamada. Procediendo con el objetivo, lo primero que procedemos a realizar es un filtrado de los clientes que SI desean compartir su ubicación y nos resta seleccionar solo las variables lat y lon, con el fin de agrupar por posición geográfica
clientes_telefonia_compartido<- subset(clientes_telefonia, clientes_telefonia$compartir == "SI")
clientes_posicion<- clientes_telefonia_compartido[, 11:12]
Revisamos la existencia de valores atípicos (outliers) a través del boxplot, en caso de que existan, estos se veran representados a distancias visualmente destacadas en el siguiente gráfico:
ggplot(data = clientes_posicion, mapping=aes(x = clientes_posicion$lon, y = clientes_posicion$lat)) +geom_boxplot() +
geom_jitter(width = 0.1) + theme_bw() + theme(legend.position = "none")
colSums(is.na(clientes_posicion))
## lon lat
## 0 0
colSums(clientes_posicion=="?")
## lon lat
## 0 0
Para la representación gráfica se ha usado la librería LeafLet, la cual tiene entre sus caracteristicas el agrupamiento de individuos y el cambio de indicador (icono de representación). A continuación se representan la posición geográfica de aquellos clientes que decidieron compartir su posición en el momento de perdida de señal:
leaflet( data = clientes_posicion) %>%
addTiles() %>%
addMarkers(~lon,~lat, clusterOptions = markerClusterOptions() )
Como se puede observar, tenemos 1221 pines en nuestro mapa, los cuales han sido agrupados por proximidad. Sin embargo, procederemos a aplicar algoritmos de clustering con el fin de conocer agrupaciones por caracteristicas similares de observaciones
Se debe recordar que el algoritmo Kmeans es un algoritmo en el que se reparten las observaciones en k grupos de forma que la suma de las varianzas internas de todos sea la menos posible. Es decir, el mejor cluster es aquel cuya varianza interna (intra-cluster variation) es la más pequeña posible.
Uno de los factores más complejos en la aplicación de algoritmos de Clustering es identificar el K (numero de clusters). Se puede plantear que el mejor modelo es aquel que ofrece la menor suma de los cuadrados de las distancias de los puntos de cada grupo con respecto a su centro (withinss), con la mayor separacion entre centros de grupos (betweenss). Por lo que a continuación se probará con hasta 10 k posibles. Para esto, se hará uso de la función silhouette, la cual devuelve para cada muestra del set, el clúster dónde ha sido asignado, el clúster vecino y el valor de la silueta. Por lo tanto, calculando la media de la tercera columna podemos obtener una estimación de la calidad del agrupamiento:
d <- daisy(clientes_posicion)
resultados <- rep(0, 10)
for (i in c(2,3,4,5,6,7,8,9,10))
{
fit <- kmeans(clientes_posicion, i)
y_cluster <- fit$cluster
sk <- silhouette(y_cluster, d)
resultados[i] <- mean(sk[,3])
}
Una vez extraida la media del valor de la silueta ( mean(sk[,3]) ) y almacenando los valores de los clusters, se procede a gráficarlos para hacer más fácil la compresión. Es de mencionar que a mayor pico en el primer segmento, mejor es la calidad de agrupamiento:
plot(2:10,resultados[2:10],type="o",col="blue",pch=0,xlab="Numero de clusters",ylab="Silueta")
En este caso, a través de silhouette se han identificado 4 clusters optimos. Sin embargo, procederemos a realizar el calculo del numero K a través del método elbow. Este método consiste en, a través de la grafica representativa, seleccionar el valor en el que se encuentra la curva “codo” tras iterar el mismo set de datos por diferentes clusters.
resultados <- rep(0, 10)
for (i in c(2,3,4,5,6,7,8,9,10))
{
fit <- kmeans(clientes_posicion, i)
resultados[i] <- fit$tot.withinss
}
plot(2:10,resultados[2:10],type="o",col="blue",pch=0,xlab="Número de clusters",ylab="tot.tot.withinss")
Observando la gráfica anterior (como parte del método del codo) se puede identificar que en un k = 4, se empieza a estabilizar la gráfica.
Una vez identificado el mejor valor para k, procedemos a comparar visualmente los campos:
clientes.kmeans <- kmeans(clientes_posicion, 4)
ggplot(data = clientes_posicion, aes(x=lat, y=lon, color=as.factor(clientes.kmeans$cluster))) +
geom_point(size = 3) +
labs(title = "Kmeans con k=4") +
theme_bw() +
theme(legend.position = "none")
Al aplicar clustering basado en el algoritmo kmeans, se observa un agrupamiento interesante, ya que refleja una identificacion de sectores geograficos en los cuales optimizar la red de telefonia. Ahora procedemos a identificar cada individuo en un cluster y a graficarlo. Para esto, usamos mutate y groupby:
clientes_posicion <- clientes_posicion %>% mutate(Grupo_Kmeans = clientes.kmeans$cluster)
clientes.grupo.kmeans <- clientes_posicion %>% group_by(Grupo_Kmeans)
summarise(clientes.grupo.kmeans,observaciones = n())
Grupo_Kmeans <int> | observaciones <int> | |||
---|---|---|---|---|
1 | 220 | |||
2 | 425 | |||
3 | 367 | |||
4 | 209 |
Ahora nos encargaremos de identificar los sectores en los cuales se propone el levantamiento de una antena, para esto obtenemos la media de cada cluster y graficamos en el mapa las coordenadas:
ubicacion_antena_kmeans <- as.data.frame(clientes.kmeans$centers , row.names = FALSE)
leaflet( data = ubicacion_antena_kmeans) %>%
addTiles() %>%
addMarkers(~lon,~lat, popup = ~lat, label = ~lon)
Recordemos que el clustering basado en modelos (MCLUST) considera que las observaciones proceden de diferentes distribuciones, utilizando el algoritmo de Expectation Maximization para identificar volumen, forma y orientacion de cada uno de los modelos del cluster.
Se procede a realizar la aplicación del algoritmo MCLUST. Lo primero que hacemos es identificar el k propuesto por mclust, para esto utilizamos la libreria mclust y proponemos utilizar hasta un maximo de 10 clusters:
clientes.mclust <- Mclust(data = clientes_posicion[,1:2], G = 1:10)
fviz_mclust(clientes.mclust, "BIC", palette = "jco")
Para nuestra sorpresa, mclust ha definido el maximo de 10 clusters, 6 mas que kmeans a traves del metodo de elbow. Ahora graficamos los clusters:
fviz_mclust(clientes.mclust, "uncertainty", palette = "jco")
Como se realizo con kmeans, agregamos una nueva variable con el grupo al que pertenece cada cliente como resultado de la aplicacion del algoritmo mclust.
clientes_posicion <- clientes_posicion %>% mutate(Grupo_Mclust = clientes.mclust$classification)
clientes.grupo.mclust <- clientes_posicion %>% group_by(Grupo_Mclust)
summarise(clientes.grupo.mclust,observaciones = n())
Grupo_Mclust <dbl> | observaciones <int> | |||
---|---|---|---|---|
1 | 191 | |||
2 | 89 | |||
3 | 89 | |||
4 | 150 | |||
5 | 151 | |||
6 | 140 | |||
7 | 114 | |||
8 | 103 | |||
9 | 106 | |||
10 | 88 |
Ahora nos encargaremos de identificar los sectores en los cuales se propone el levantamiento de una antena, para esto obtenemos la media de cada cluster y graficamos en el mapa:
ubicacion_antena_mclust <- as.data.frame(t(clientes.mclust$parameters[["mean"]]), row.names = FALSE)
leaflet( data = ubicacion_antena_mclust) %>%
addTiles() %>%
addMarkers(~ubicacion_antena_mclust$lon,~ubicacion_antena_mclust$lat)
Iniciamos recordando que este algoritmo identifica clusters de manera intuitiva gracias a la alta densidad de observaciones, es decir, de manera visual como lo haria el cerebro humano. Es importante mencionar que los algoritmos como kmeans, HC, medoids, entre otros, agrupan basado en formas esfericas o convexas que no presenten altos indices de ruido (outliers). Es aqui donde este algoritmo se destaca, ya que para para agrupar una observacion en un cluster, tienen que existir un minimo de observaciones dentro de este con un radio de proximidad pequeño, es decir que cada cluster estará separado por espacios vacios o pocas observaciones.
Ahora, entendiendo lo anterior, procedemos a aplicar el algoritmo DBSCAN, el cual requiere de dos parametros: * ϵ-neighborhood (eps): radio para definir el área de una observación vecina. * Minimum points (minPts): número mínimo de observaciones dentro de la región ϵ-neighborhood.
Para obtener el eps, se emplea la función kNNdistplot:
dbscan::kNNdistplot(clientes_posicion[,1:2], k = 10)
Como se observa, el punto de inflexión esta alrededor de los 0.005. Ahora aplicamos el algoritmo y graficamos los clusters
clientes.dbscan <- dbscan(clientes_posicion[,1:2], eps = 0.005, MinPts = 10)
fviz_cluster(clientes.dbscan, data = clientes_posicion[,1:2], stand = FALSE,
ellipse = TRUE, show.clust.cent = TRUE, ellipse.type = "t",
geom = "point",palette = "jco", ggtheme = theme_classic())
Como podemos observar, el algoritmo ha identificado 6 clusters, los cuales a simple vista parecen adecuados. Como se ha hecho con los anteriores algoritmos, procedemos a agregar la variable con el grupo correspondiente:
clientes_posicion <- clientes_posicion %>% mutate(Grupo_dbscan = clientes.dbscan$cluster)
clientes.grupo.dbscan<- clientes_posicion %>% group_by(Grupo_dbscan)
summarise(clientes.grupo.dbscan,observaciones = n())
Grupo_dbscan <int> | observaciones <int> | |||
---|---|---|---|---|
0 | 97 | |||
1 | 201 | |||
2 | 325 | |||
3 | 76 | |||
4 | 151 | |||
5 | 270 | |||
6 | 101 |
Se evidencian la cración de un cluster = 0, el cual hace referencia a 97 observaciones que no han sido categorizadas, las cuales pueden ser clasificados como outliers.
Ahora nos encargaremos de identificar los sectores en los cuales se propone el levantamiento de una antena, para esto se realiza el calculo de la media de cada cluster y se grafica en el mapa:
ubicacion_antena_dbcan <- summarise(
select(
group_by(clientes_posicion, Grupo_dbscan),
lat:lon
),
media.dbscan.lat = mean(lat, na.rm = TRUE),
media.dbscan.lon = mean(lon, na.rm = TRUE)
)
ubicacion_antena_dbcan <- ubicacion_antena_dbcan[2:7,]
leaflet( data = ubicacion_antena_dbcan) %>%
addTiles() %>%
addMarkers(~media.dbscan.lon,~media.dbscan.lat)