Tiempo resolución
Defecto /No defecto
Random Forest
Segmentación
Ante la problemática surgida del tiempo de resolución de tramitaciones, se planteó el objetivo de determinar las principales causas que originaban dificultades en dichos tiempos. Para ello recurrimos a análisis descriptivo, que posteriormente nos ayudó a la construcción de un modelo que nos permita comprender el comportamiento.
Tras este proceso hemos visto como las distintas titulaciones y las actividades influyen en el tiempo que se tarda en resolver las solicitudes. Al igual que el mes, en el que se produce un pico en los meses de septiembre lo que lleva a la acumulación de las solicitudes, y el consiguiente retraso. Por último, destacar que el curso también influye sobre el tiempo, variando de un período a otro.
El análisis se centra en la búsqueda de los principales motivos que provocan un retraso en el proceso administrativo de tramitación de documentos en el cegeca de elche.
Además, nos planteamos si existe alguna relación entre los distintos tipos de actividades y el periodo de tramitación. Por otra parte si el tipo de titulación también puede influir en dicho periodo.
Finalmente, también nos interesa conocer si existe un comportamineto estacional en la entrada de solicitudes, para valorar la concentración del trabajo en determinados periodos de tiempo, y además valorar si el curso en qué se realiza la tramitación supone algún efecto sobre el tiempo.
Con todo esto, pretendemos mejorar la eficiencia de este departamento, para que los usuarios estén más satisfechos con el servicio ofertado.
Los datos han sido recogidos durante el transcurso de dos años lectivos, concretamente los cursos 2015/16 y 2016/17 y fueron facilitados por lel organismo CEGECA de la Universidad Miguel Hernández.
La base esta formada por un total de 4120 observaciones y un total de 19 variables.
La variable de interés para nuestro análisis es los días de resolucion. Se trata de una variable contínua que refleja el tiempo que se tardó en resolver la solicitud desde el día en que fue presentada.
El resto de variables explicativas que nos van a ayudar a comprender el comportamiento de los días de resolución son las siguientes:
Otras variables que se incluían en la base, pero que no eran significativas para el análisis, son:
Antes de empezar a tratar con los datos, declaramos como factores aquellas varibles que R no detecta como tal. Además, cambiamos el formato de la fecha, para su mejor manipulación. Y finalmente eliminamos del conjunto de varibales el dni y días desde el origen, ya que son innecesarias y no aportan nada al estudio.
Para una primera inmersión en los datos y su análisis descriptivo emplearemos distintos gráficos, que permitan comprender la naturaleza de los datos de mejor forma. Y así poder crear un modelo que se ajuste de la mejor manera posible a las observaciones.
Para este análisis el modelo seleccionado es el de random forest, que consiste en, utilizando las variables independientes proporcionadas, crear árboles de decisión aleatorios para hacer la clasificación en una categoría o otra. Dicha clasificación se realiza a través de una votación, es decir, se agrupan los resultados de los distintos árboles aleatorios, y al registro se le asigna la categoría predominante.
Hemos elegido este modelo ya que nos indica en la salida qué variables son más importantes. Además nos permite distinguir y elegir las variables más significativas de entre las multitud que se pueden introducir.
Por otra parte, gracias a que utiliza una gran cantidad de árboles, no se limita a un único, nos permite encontrar la solución con la mayor precisión posible, y por consigueinte el menor error.
El modelo final seleccionado para el análisis ha sido el siguiente:
\(\color{#0B6889}{defectos= tipo.titulacion\ + curso\ + centro\ + tipo.actividad\ + mes.solic }\)
Para llevar a cabo el estudio hemos utilizado el software estadístico de R Studio. Hemos recurrido a las siguientes librerías :
Comenzamos haciendo unos histogramas para tener una primera visión de la distribución de la variable días de resolución.
En el primer gráfico, se observa cómo existen muy pocos valores con un tiempo de resolución alto, y haciendo un análisis de los datos nos damos cuenta que únicamente estamos hablando de 7 registros por encima de los 100 días de resolución. Por eso, reducimos la base de datos a los días con tiempo de resolución menor a 100. Se observa que, a pesar de quitar los datos superios a 100, la distribución sigue siendo asimétrica.
En este gráfico se informa sobre los porcentajes de las actividades,el curso y los distintos centros. El tipo de actividad predominante son las practicas, mientras que el centro que destaca es CSSA, seguido por CSJE.
Por otro lado, vemos como el volumen de registros en ambos cursos es similar.
Como se observa, la variable curso resulta de gran interés, ya que en el curso 2015 existe una mayor variabilidad. En el curso 2016 la gran mayoría tardan hasta 30 días, mientras que en el curso 2015 se alargaba mas.
Este gráfico nos muestra la duración de las actividades para los dos cursos. Se destaca la gran variabilidad en función de la actividad. En el curso 2015 el intervalo de variación de las tramitaciones era sensiblemente superior, mientras que estos plazo se consiguieron mejorar un poco en el 2016. Por último, podemos ver como la actividad laboral presenta los mayores tiempos de resolución.
Estos gráficos reflejan como el mayor volumen de solicitudes que recibe el cegeca se produce en los meses de septiembre, debido al inicio del cursos, y el mes de junio, coincidiendo con el final del curso.
En cuanto a los días de resolución, vemos que hay una serie de actividades que tardan más que el resto, como son laborales y estudiosnouni si los solicitamos en los meses de septiembre.
Este gráfico nos muestra el tiempo medio de resolución por el tipo de titulación. Se observa cómo el máster es la titulación en la cual se más tarda en resolver las solicitudes.
En cambio, en este otro gráfico, en el que se representan el volumen de solicitudes de los distintos tipos de actividad para los 3 niveles de titulación, existe una tipologia de actividad que predomina en el máster como son las del tipo laboral, mientras que el resto son más frecuentes en los grados.
Con este gráfico lo que pretendemos es observar si de manera visual, vemos si los centros pueden influir sobre los tiempos de resolución. Observamos que en algunos centros, el volumen de ciertas actividades es muy elevado en comparación con el resto, lo que nos puede llevar a pensar que puede ser significativo en el modelo.
Una vez tenemos las variables seleccionadas, preparamos los datos estableciendo la consideración de que si una solicitud tarda más de 15 días se cosidera un defecto.
##
## Call:
## randomForest(formula = datadef ~ tipo.titulacion + curso + tipo.actividad + mes.solic, data = datos100, importance = TRUE)
## Type of random forest: classification
## Number of trees: 500
## No. of variables tried at each split: 2
##
## OOB estimate of error rate: 19.69%
## Confusion matrix:
## DEFECTO NO_DEFECTO class.error
## DEFECTO 1491 374 0.2005362
## NO_DEFECTO 436 1812 0.1939502
##
## Call:
## randomForest(formula = datadef ~ tipo.titulacion + curso + centro + tipo.actividad + mes.solic, data = datos100, importance = TRUE)
## Type of random forest: classification
## Number of trees: 500
## No. of variables tried at each split: 2
##
## OOB estimate of error rate: 18.19%
## Confusion matrix:
## DEFECTO NO_DEFECTO class.error
## DEFECTO 1446 419 0.2246649
## NO_DEFECTO 329 1919 0.1463523
EL primer modelo, en el que se incluye la variable centro, nos proporciona una precisión del 80%. Mientras que si la incluimos, aumenta hasta un 82%. Si el usuario esta dispuesto a perder ese 2% de precisión, puede escoger el modelo sin la variable centro.
En el modelo que seleccionamos, incluyendo la variable centro, podemos destacar que nos proporciona una precisión del 82%, que se obtiene como el resultado de restarle a 100 el porcentaje de OBB error. El modelo random forest cuenta con dos parámetros (ntree:número de árboles y mtry:variables candidatas a ser nodo principal.) cuya modificación nos puede llevar a una posible mejora de dicho modelo.
Este gráfico muestra la evolucion del error si aumentamos el número de árboles en nuestro modelo. Hay que destacar que, a partir de los 250 árboles, el error comienza a estabilizarse, lo que nos da una idea de que, a pesar que vayamos aumentando el número de árboles, no vamos a conseguir mejorar el modelo. Por tanto el parámetro ntree no nos ayuda a aumentar la precisión.
El parámetro OOB( Out of Bag) nos indica el error global del modelo, mientras que los parámetros de defectos y no defectos, indican la proporción de error dentro de las mismas clases respectivamente.
Por otro lado, tenemos el parámetro mtry, que sirve para establecer el número de variables independientes aleatorios candidatas a ser el nodo principal en cada árbol.
Se puede ver que no existe diferencias significativas entre las 5 posibilidades. Por tanto, la única opción que nos resta, para intentar mejorar el modelo es la introducción de las interacciones en el mismo.
##
## Call:
## randomForest(formula = datadef ~ tipo.titulacion + tipo.titulacion:curso + curso + curso:centro + curso:tipo.actividad + curso:mes.solic + centro + centro:tipo.actividad + centro:mes.solic + centro:mes.solic:curso + tipo.actividad + mes.solic, data = datos100)
## Type of random forest: classification
## Number of trees: 500
## No. of variables tried at each split: 2
##
## OOB estimate of error rate: 18.28%
## Confusion matrix:
## DEFECTO NO_DEFECTO class.error
## DEFECTO 1451 414 0.2219839
## NO_DEFECTO 338 1910 0.1503559
Aquí vemos como intoduciendo más variables al modelo y aumentando su complejidad, la precisión no mejora mucho. De tal forma que, teniendo en cuenta el principio de parsimonia, nos vamos a quedar con el modelo simple.
Con ayuda de este gráfico podemos ver la importancia que tiene cada una de las variables introducidas en nuestro modelo. La que más nos aporta es la de mes en que se realiza la solicitud, ya que si la quitamos de nuestro modelo, hace disminuir en media un 20% de la precisión del modelo.
Con el modelo seleccionado, cogemos 30 observaciones aleatorias del data set para hacer las predicciones y vemos que el modelo se equivoca sólo en 4 a la hora de clasificar.
## dias_de_resolucion
## prediccion DEFECTO NO_DEFECTO
## DEFECTO 11 1
## NO_DEFECTO 3 15
Tras haber analizado a fondo los problemas existentes, podemos tratar de resolver las cuestiones que nos planteábamos al principio y que han sido objeto de nuestro análisis.
El principal objetivo que debíamos tratar de solventar era el excesivo tiempo de tramitación de los documentos. Para ello, decidimos encontrar las causas que provocaban este retraso en los trámites, para que posteriormente el CEGECA supiese que problemas debía abordar.
Por un lado, hemos visto como efectivamente el tipo de actividad influye en los periodos de tramitación. En este caso vemos como las actividades laborales tienden a ser las que más tiempo requieren.
Por otro lado, también nos interesaba saber al igual que con el tipo de actividad si la titulación también influía en los intervalos de resolución. En este sentido, hemos visto como en los masters el tiempo de resolución es sensiblemente superior que en los títulos extintos, donde se dan los tiempos de espera más pequeños.
También se ha observado cómo existe una variación en los tiempos en función del curso en el que se realiza el trámite. De tal manera, en el curso 2015 las variaciones en el tiempo son superiores en comparación con el curso 2016, lo que nos refleja que se ha implementado alguna mejora en el 2016 que ha ayudado a controlar la variación.
Finalmente, también nos planteábamos en un principio si existía una componente estacional tanto en la llegada de solicitudes como en los tiempos de tramitación. En cuanto a las llegadas se observa como septiembre es el mes donde más solicitudes se registran, cosa atribuible al inicio de curso. Esto hecho genera fundamentalmente en el año 2015, aunque también en menor medida en el año 2016 un considerable aumento en los tiempos de tramitación que vemos reflejo en los meses posteriores a septiembre.
Estos son los factores más relevantes que pueden estar generando una congestión en el sistema.Posibles mejoras serían, estudiar el protocolo que se está aplicando en el momento actual y ver pasos innecesarios o repetitivos, e intentar agilizar el proceso. Por otro lado, teniendo en cuenta que el volumen más elevado de solicitudes se produce en el comienzo y fin del curso, se podrían planificar de una manera más eficiente los recursos disponibles.
https://bookdown.org/content/2031/ensambladores-random-forest-parte-i.html
Canal de youtube : StatQuest with Josh Starmer
A continuación se detalla el código que se ha empleado para el estudio de nuestra problema.
datos=(read.csv('datos.csv', stringsAsFactors = TRUE,
colClasses =c(rep('factor',10),
'numeric',rep('factor',7),'numeric')))
datos1<-datos
datos1$solicitud=as.Date(datos1$solicitud,format= "%d/%m/%y")
datos1$resolucion=as.Date(datos1$resolucion,format= "%d/%m/%y")
datosf=datos1[,-c(1,19)]
library(extrafont)
loadfonts(device = "win")
#Gráfico Distribución días
library(ggplot2)
ggplot(data=datosf, aes(x=dias.resol)) +
geom_histogram(bins = 50,color='#004958',fill='#A1DFFF',binwidth = 10,
alpha=0.2,size=1.1)+
xlab('Días de resolución')+
ylab('conteo')+
theme( panel.grid = element_blank(),
axis.title = element_text( #axis titles
family = "Verdana", #font family
size = 14), #font size
axis.text = element_text( #axis text
family = 'Georgia', #axis famuly
size = 14))
datos100<-datosf[-which(datosf$dias.resol>100),]
library(ggplot2)
ggplot(data=datos100, aes(x=dias.resol)) +
geom_histogram(bins = 20,color='#004958',fill='#A1DFFF',binwidth = 10,
alpha=0.2,size=1.1)+
xlab('Días de resolución')+
ylab('conteo')+
theme( panel.grid = element_blank(),
axis.title = element_text( #axis titles
family = "Verdana", #font family
size = 14), #font size
axis.text = element_text( #axis text
family = 'Georgia', #axis famuly
size = 14))
library(ggpubr)
library(scales)
Porcurso <- data.frame(round(prop.table(table(datos100$curso)),3))
names(Porcurso) <- c("curso","porcentaje")
tabla1<-ggtexttable(Porcurso,rows = NULL)
graf1<-ggplot(Porcurso, aes(x="", y=porcentaje, fill=curso)) +
geom_bar(stat="identity", width=1) +
coord_polar("y", start=0)+
geom_text(aes( label = percent(porcentaje)), position = position_stack(vjust = 0.5),
color = "black",size=5)+
theme_void()
Poract <- data.frame(round(prop.table(table(datos100$tipo.actividad)),3))
names(Poract) <- c("tipo.actividad","porcentaje")
tabla2<-ggtexttable(Poract,rows = NULL)
graf2<-ggplot2:: ggplot(Poract, aes(x="", y=porcentaje, fill=tipo.actividad)) +
geom_bar(stat="identity", width=1) +
coord_polar("y", start=0)+
geom_text(aes( label = percent(porcentaje),x=1.35), position = position_stack(vjust = 0.5),
color = "black",size=3)+
theme_void()
Porcent<- as.data.frame(round(prop.table(table(datos100$centro)),3))
names(Porcent) <- c("centro","porcentaje")
tabla3<-ggtexttable(Porcent,rows = NULL)
graf3<-ggplot(Porcent, aes(x="", y=porcentaje, fill=centro)) +
geom_bar(stat="identity", width=1) +
coord_polar("y", start=0)+
geom_text(aes( label = percent(porcentaje),x=1.2), position = position_stack(vjust = 0.5),
color = "black",size=4)+
theme_void()
library(ggplot2)
library(grid)
library(gridExtra)
grid.newpage()
pushViewport(viewport(layout = grid.layout(2, 4)))
vplayout <- function(x, y) viewport(layout.pos.row = x, layout.pos.col = y)
print(graf1, vp = vplayout(2, 1)) # key is to define vplayout
print(graf2, vp = vplayout(1, 1:2))
print(graf3, vp = vplayout(2, 3))
print(tabla1, vp = vplayout(2, 2)) # key is to define vplayout
print(tabla2, vp = vplayout(1, 3))
print(tabla3, vp = vplayout(2, 4))
library(ggridges)
ggplot(datos100, aes( x=dias.resol,y=curso, fill=curso)) +
geom_density_ridges(alpha=0.6, stat="binline", bins=20,
scale = 1) +
theme_ridges()+
geom_vline(xintercept = 30,size=1)+
annotate('text',x=37,y=0.8,label='1 mes')+
xlab('Días resolución')+
ylab('Conteo')
library(dplyr)
library(tidyverse)
actividadm<-datos100 %>% group_by(curso,tipo.actividad) %>%
summarise(media=median(dias.resol),c1=quantile(dias.resol,0.25),
c3=quantile(dias.resol,0.75))
ggplot(actividadm,aes(x=tipo.actividad,y=media,group=curso))+
geom_point(aes(color=curso),position = position_dodge(0.3),size=2)+
geom_errorbar(aes(ymin=c1, ymax=c3,color=curso), width=.3,
position=position_dodge(0.3),size=1)
#estacionalidad volumen
estac<- datos100 %>% group_by(curso,mes.solic) %>%
count()
ggplot(estac,aes(x=mes.solic,y=n,group=curso))+
geom_line(aes(color=curso),size=2)+
geom_point(aes(fill=curso),size=4,shape=23)+
ylab('Volumen')
#resolucion
tiemporesol<- datos100 %>% group_by(mes.solic,curso,tipo.actividad) %>%
count(dias.resol) %>% mutate(media=median(dias.resol*n))
ggplot(tiemporesol,aes(mes.solic,y=media,group=curso))+
geom_point(aes(fill=curso),shape=23,size=3)+
geom_line(aes(color=curso),size=1)+
facet_wrap(~tipo.actividad)
datostitu=datos100 %>% group_by(tipo.titulacion) %>%
summarise(media=median(dias.resol))
ggplot(datostitu,aes(x=tipo.titulacion,y=media))+
geom_bar(aes(fill=tipo.titulacion),stat = 'identity')
conteo<- datos100 %>% group_by(tipo.actividad,tipo.titulacion) %>%
count()
ggplot(conteo,aes(x=tipo.actividad,y=n,fill=tipo.titulacion))+
geom_bar(stat = 'identity',position = 'dodge')+
ylab('Volumen')
volumen<-datos100 %>% group_by(curso,centro,tipo.actividad) %>% count()
ggplot(volumen,aes(x=tipo.actividad,y=n,fill=curso))+
geom_bar(stat = 'identity',position = 'dodge')+ coord_flip()+
facet_wrap(~centro)
library(randomForest)
datadef<- ifelse(test = datos100$dias.resol>15,yes = 'DEFECTO',no='NO_DEFECTO')
datadef<- as.factor(datadef)
datos100$datadef=datadef
set.seed(1247)
model1<- randomForest(datadef~tipo.titulacion +
curso + tipo.actividad + mes.solic,data=datos100,
importance=TRUE
)
model<- randomForest(datadef~tipo.titulacion +
curso + centro + tipo.actividad + mes.solic,data=datos100,
importance=TRUE
)
set.seed(1247)
model<- randomForest(datadef~tipo.titulacion +
curso + centro + tipo.actividad + mes.solic,data=datos100,
importance=TRUE, ntree=1000
)
ooberror<-data.frame(Trees=1:nrow(model$err.rate),
OOB=model$err.rate[,'OOB'],
DEFECTO=model$err.rate[,'DEFECTO'],
NODEFECTO=model$err.rate[,'NO_DEFECTO'])
datosplot<- gather(ooberror,value = 'ERROR',key = 'fuentes',2:4)
ggplot(datosplot,aes(x=Trees,y=ERROR,group=fuentes))+
geom_line(aes(color=fuentes),size=1)
set.seed(1247)
ooberror=vector(length = 5)
for(i in 1:5){
modelotemp<- randomForest(datadef~tipo.titulacion + curso + tipo.actividad + mes.solic + centro
,data=datos100,mtry=i)
ooberror[i]= modelotemp$err.rate[500,'OOB']
}
ooberror<-as.data.frame(ooberror)
ggplot(ooberror, aes(x=1:5, y=ooberror)) + geom_bar(stat = 'identity')+
ylab('ERROR')+xlab('mtry')
set.seed(1200)
modelo2<- randomForest(datadef~tipo.titulacion + tipo.titulacion:curso +
curso + curso:centro + curso:tipo.actividad +
curso:mes.solic + centro + centro:tipo.actividad +
centro:mes.solic + centro:mes.solic:curso +
tipo.actividad + mes.solic ,data=datos100)
barplot(model$importance[,3], xlab = 'Variables', ylab = 'Media Disminución Precisión %')