Una consultora de desarrollo de software busca mejorar sus procesos de gestión de proyectos y la calidad del software que produce. Para ello, ha recopilado datos de sus últimos 1000 proyectos con el objetivo de:
Desarrollar modelos predictivos para estimar el nivel de éxito de futuros proyectos.
Descubrir patrones y grupos de proyectos similares.
Desplegar una herramienta de apoyo a la toma de decisiones basada en la estimación de éxito.
Descripción de variables
Variables independientes
id_proyecto:
Identificador único del proyecto
Formato: PROJXXXX donde XXXX es un número secuencial
Sin valores faltantes ni problemas de calidad
fecha_inicio:
Fecha en que comenzó el proyecto
Rango: 2020-01-01 a 2023-12-31
Sin valores faltantes ni problemas de calidad
tamano_equipo:
Número de desarrolladores en el equipo
Rango válido: 3-15 personas
Puede contener valores NA y valores fuera de rango
complejidad_proyecto:
Nivel de complejidad técnica y funcional
Escala: 1 (muy simple) a 10 (extremadamente complejo)
Puede contener valores NA y valores fuera de rango
metodologia:
Enfoque metodológico utilizado
Valores válidos: “Agil”, “Cascada”, “Hibrida”
Puede contener inconsistencias en mayúsculas/minúsculas, valores NA y valores no válidos
Puede contener variaciones en la escritura, valores NA y valores no válidos
tiene_pruebas_automatizadas:
Indica si el proyecto implementó pruebas automatizadas
Valor booleano: TRUE/FALSE
Puede contener valores NA
cobertura_revision_codigo:
Porcentaje del código que pasa por revisión
Rango válido: 0 a 0.4 (0% a 40%)
Puede contener valores NA y valores fuera de rango
duracion_meses:
Duración real del proyecto en meses
Unidad: meses
Calculada a partir de fecha_inicio y complejidad:
Complejidad 1-3: 1-3 meses
Complejidad 4-6: 3-6 meses
Complejidad 7-8: 6-12 meses
Complejidad 9-10: 12-24 meses
Sin valores problemáticos
errores_por_kloc: - Número de errores por cada mil líneas de código - Valor mínimo: 0 - Puede contener valores atípicos que requieren limpieza
puntuacion_calidad: - Métrica compuesta de calidad del código - Escala: 0-100 - Puede contener valores fuera de rango
productividad_equipo: - Medida de la productividad del equipo - Escala: 0-100 - Puede contener valores NA
satisfaccion_cliente: - Nivel de satisfacción reportado por el cliente - Escala: 0-100 - Puede contener valores fuera de rango y valores atípicos que requieren limpieza
Variable dependiente
exito_proyecto: - Clasificación final del éxito del proyecto - Valores: “Bajo”, “Medio”, “Alto” - Sin valores faltantes ni problemas de calidad
Preguntas a responder
A partir de la información proporcionada, se responden las siguientes preguntas:
¿Qué exactamente deseamos hacer?
Estimar el nivel de éxito de futuros proyectos a partir de registros históricos
¿Es factible alcanzar lo que buscamos con los datos disponibles?
Si, las variables proporcionadas aparentemente parecen ser suficientes para alcanzar la clasificación.
¿Cómo podemos lograrlo?
Aplicar técnicas predictivas de Machine Learning.
¿Qué tipo de problema se va a resolver?
Problema de clasificación (éxito de proyectos).
¿El objetivo es?
Predecir a nivel categórico (éxito de proyecto).
Precisando:
Variable a predecir:
Éxito del proyecto (exito_proyecto): clasificación del éxito del proyecto de software en función de las características proporcionadas en proyectos previos. Clases disponibles:
Bajo: Proyecto que no alcanza objetivos principales, presenta problemas significativos y genera insatisfacción en cliente y equipo.
Medio: Proyecto que cumple requisitos básicos con algunos inconvenientes menores, manteniendo niveles aceptables de calidad y satisfacción.
Alto: Proyecto que cumple objetivos superando expectativas, con cliente satisfecho, código de calidad y equipo altamente productivo.
Etapa 2: Adquisición de datos
En esta etapa, se procederá a la adquisición de los datos necesarios para el análisis. Los datos serán leídos desde un archivo CSV y se inspeccionarán para asegurar que se hayan cargado correctamente.
# Cargar librerías necesariaslibrary(tidyverse)library(lubridate) # Para trabajar con fechaslibrary(skimr) # Para resumen de datoslibrary(DataExplorer) # Para análisis exploratoriolibrary(corrplot) # Para matrices de correlaciónlibrary(scales) # Para formato de escalaslibrary(knitr) # Para tablaslibrary(gridExtra) # Para organizar gráficoslibrary(GGally) # Para GGally# Cargar datosdatos_raw <-read.csv("datos_proyectos_software.csv")# Mostrar estructura inicialglimpse(datos_raw)
En esta etapa, realizaremos el preprocesamiento de los datos para asegurarnos de que estén listos para el análisis posterior. Esto incluye: a. limpieza, b. transformación y c. análisis exploratorio de datos (EDA)
Para comenzar, contestamos las siguientes preguntas:
¿Existen valores ausentes o datos faltantes inconsistentes en las variables?
# Visualizar datos faltantesplot_missing(datos_raw)
Observamos que si existen algunos valores ausentes o faltantes en 7 de las 14 variables.
¿Existen valores fuera de rango en las variables?
# Visualizar datos fuera de rangofuera_de_rango <- datos_raw %>%summarise(# Verificar si las variables numéricas (9) tienen valores fuera de rangotamano_equipo_fr =sum(tamano_equipo <3| tamano_equipo >15, na.rm =TRUE),complejidad_fr =sum(complejidad_proyecto <1| complejidad_proyecto >10, na.rm =TRUE),cobertura_fr =sum(cobertura_revision_codigo <0| cobertura_revision_codigo >1, na.rm =TRUE), duracion_meses_fr =sum(duracion_meses <0 , na.rm =TRUE),errores_por_kloc_fr=sum(errores_por_kloc <0 , na.rm =TRUE),puntuacion_calidad_fr =sum(puntuacion_calidad <0| puntuacion_calidad >100, na.rm =TRUE),productividad_equipo_fr =sum(productividad_equipo <0| productividad_equipo >100, na.rm =TRUE),satisfaccion_cliente_fr =sum(satisfaccion_cliente <0| satisfaccion_cliente >100, na.rm =TRUE), )kable(gather(fuera_de_rango), col.names =c("Variable", "Casos fuera de rango"))
Variable
Casos fuera de rango
tamano_equipo_fr
74
complejidad_fr
69
cobertura_fr
32
duracion_meses_fr
0
errores_por_kloc_fr
6
puntuacion_calidad_fr
100
productividad_equipo_fr
0
satisfaccion_cliente_fr
26
Observamos que existen 6 de las 8 variables numéricas con valores fuera de rango.
¿Existen valores inconsistentes en las variables?
# Verificar inconsistencias (diversas formas de escribir misma categoría)datos_raw %>%select(metodologia, stack_tecnologico, exito_proyecto, tiene_pruebas_automatizadas) %>%map(table)
Observamos que existen 2 variables categóricas (metodologia y stack_tecnologico) que presentan diversas formas de escritura de una misma categoría.
¿Existen valores atípicos en las variables?
# Función para detectar valores atípicos usando IQR# Un valor atípico es aquel que está muy alejado del grueso de los datos - imagina una persona de 3 metros de altura cuando la mayoría mide entre 1.50 y 1.90 metros. La fórmula solo pone números a qué consideramos "muy alejado".detectar_atipicos <-function(x) { q1 <-quantile(x, 0.25, na.rm =TRUE) q3 <-quantile(x, 0.75, na.rm =TRUE) iqr <- q3 - q1 limite_inferior <- q1 -1.5* iqr limite_superior <- q3 +1.5* iqrreturn(list(inf = limite_inferior, sup = limite_superior))}# Cálculo de límites para valores atípicos para las 2 variables que en descripción se dijo tenian valores atipicoslimites_errores <-detectar_atipicos(datos_raw$errores_por_kloc) limites_satisfaccion <-detectar_atipicos(datos_raw$satisfaccion_cliente)# Para cada variable, contar cuántos valores atípicos hayatipicos <- datos_raw %>%summarise(atipicos_errores =sum(!between(errores_por_kloc, limites_errores$inf, limites_errores$sup)), atipicos_satisfaccion =sum(!between(satisfaccion_cliente, limites_satisfaccion$inf, limites_satisfaccion$sup)) )kable(gather(atipicos), col.names =c("Variable", "Atípicos"))
Variable
Atípicos
atipicos_errores
94
atipicos_satisfaccion
43
Se observan que efectivamente ambas variables presentan valores atípicos.
a. limpieza
Se realiza la limpieza para cada una de los problemas de calidad encontrados
# Resumen de la limpiezaresumen_limpieza <-data.frame(Metrica =c("Registros originales", "Registros después de limpieza","Registros eliminados", "Porcentaje de datos conservados"),Valor =c(nrow(datos_raw), nrow(datos_clean),nrow(datos_raw) -nrow(datos_clean),round(nrow(datos_clean) /nrow(datos_raw) *100, 2) ))kable(resumen_limpieza)
Metrica
Valor
Registros originales
1000.0
Registros después de limpieza
896.0
Registros eliminados
104.0
Porcentaje de datos conservados
89.6
Se eliminan cerca de 100 instancias. Lo que representa un 10% de pérdida aproximadamente. Un porcentaje dentro de lo permitido.
b. transformación
Para procesos de transformación, únicamente se numerizan las variables categóricas (opcionalmente se pudo haber normalizado las variables, usando por ejemplo, normalización z).
# Transformación de datos categóricos a numéricosdatos_clean <- datos_clean %>%mutate(# Transformar metodologiametodologia_num =case_when( metodologia =="Agil"~1, metodologia =="Cascada"~2, metodologia =="Hibrida"~3 ),# Transformar stack_tecnologicostack_tecnologico_num =case_when( stack_tecnologico =="Java"~1, stack_tecnologico =="Python"~2, stack_tecnologico =="JavaScript"~3, stack_tecnologico =="C++"~4, stack_tecnologico ==".NET"~5 ),# Transformar tiene_pruebas_automatizadaspruebas_automatizadas_num =if_else(tiene_pruebas_automatizadas ==TRUE, 1, 2),# Convertir la variable a clasificar 'exito_proyecto' en factorexito_proyecto_factor =factor(exito_proyecto, levels =c("Bajo", "Medio", "Alto"), labels =c(1, 2, 3)) ) # Contar los registros que existen de cada clase de uso del suelo datos_clean %>%select_if(is.factor) %>%count(exito_proyecto_factor)
exito_proyecto_factor n
1 1 153
2 2 224
3 3 519
Se observa la creación de 4 nuevas variables con la información numerizada de las variables metodologia, stack_tecnologico, pruebas_automatizadas y exito_proyecto.
c. análisis exploratorio de datos (EDA)
Distribución de datos
¿Cuál es la distribución de cada variable en el conjunto de datos?
Distribución de variables cuantitativas
Para explorar la distribución de las variables cuantitativas las convertimos en un formato largo y generamos histogramas.
# Seleccionar solo las variables numéricasdatos_numericos <- datos_clean %>%select(where(is.numeric)) # Excluir variables cualitativas# Convertir el conjunto de datos en formato largodatos_long <-pivot_longer(datos_numericos, cols =everything())# Trazar histogramas facetas para todas las variables numéricasggplot(datos_long, aes(x = value)) +geom_histogram(aes(y = ..density..), bins =30, fill ="skyblue", alpha =0.7) +geom_density(color ="red") +facet_wrap(~name, scales ="free") +labs(title ="Distribución de variables cuantitativas", x ="Valor", y ="Frecuencia") +theme_minimal()
Distribución de variables cualitativas
Para la variable cualitativa exito_proyecto, utilizamos un gráfico de barras para visualizar su distribución.
# Análisis de variables cualitativasvars_categoricas <- datos_clean %>%select(metodologia, stack_tecnologico, tiene_pruebas_automatizadas, exito_proyecto)# Crear gráficos de barrasplots_categoricas <-map(names(vars_categoricas), function(var) {ggplot(datos_clean, aes_string(x = var, fill = var)) +geom_bar() +labs(title =str_to_title(str_replace_all(var, "_", " "))) +theme_minimal() +theme(axis.text.x =element_text(angle =45, hjust =1),legend.position ="none")})# Mostrar plots en griddo.call(grid.arrange, c(plots_categoricas, ncol =3))
A partir de la distribución de las variables observamos que el proceso de limpieza fue exitoso toda vez que no existen problemas de calidad de datos.
Correlaciones
El análisis de correlación es fundamental para comprender cómo se relacionan las diferentes variables entre sí dentro de un conjunto de datos. Por ello se plantea la interrogante ¿Existen correlaciones entre pares de variables independientes en el conjunto de datos?
# Seleccionar variables independientes para correlacióndatos_correlacion <- datos_clean %>%select(tamano_equipo, complejidad_proyecto, metodologia_num, stack_tecnologico_num, pruebas_automatizadas_num, cobertura_revision_codigo, duracion_meses, errores_por_kloc, puntuacion_calidad, productividad_equipo, satisfaccion_cliente) # Matriz de correlaciónmatriz_correlacion <-cor(datos_correlacion, use ="complete.obs")# Visualizar matriz de correlacióncorrplot(matriz_correlacion, method ="color",type ="upper",addCoef.col ="black",tl.col ="black",tl.srt =45,diag =FALSE)
Como identificamos pares de variables con alta correlación (mas del 0.50), procedemos a eliminar una de ellas para reducir la multicolinealidad de los datos.
¿Hay correlación entre cada variable independiente y la variable objetivo?
La idea es identificar qué variables independientes tienen la mayor influencia en la variable objetivo (exito_num). Esto es crucial para modelos predictivos y para entender los factores más relevantes que afectan el fenómeno estudiado. Variables con alta correlación con la variable objetivo son generalmente más importantes en los modelos predictivos.
# Correlaciones con la variable objetivodatos_clean$exito_num <-as.numeric(datos_clean$exito_proyecto_factor)correlaciones_objetivo <-data.frame(Variable =names(datos_correlacion),Correlacion =cor(datos_correlacion, datos_clean$exito_num)) %>%arrange(desc(abs(Correlacion)))# Visualizar correlaciones con la variable objetivoggplot(correlaciones_objetivo, aes(x =reorder(Variable, abs(Correlacion)), y = Correlacion)) +geom_bar(stat ="identity", fill ="skyblue") +coord_flip() +labs(title ="Correlaciones con éxito del proyecto",x ="Variables",y ="Coeficiente de correlación") +theme_minimal() +theme(axis.text.y =element_text(size =8)) # Hacer los nombres de variables más legibles
# Mostrar tabla de correlacioneskable(correlaciones_objetivo, col.names =c("Variable", "Correlación con éxito"),digits =3)
Variable
Correlación con éxito
satisfaccion_cliente
satisfaccion_cliente
0.922
puntuacion_calidad
puntuacion_calidad
0.194
metodologia_num
metodologia_num
-0.098
tamano_equipo
tamano_equipo
-0.047
errores_por_kloc
errores_por_kloc
-0.037
stack_tecnologico_num
stack_tecnologico_num
-0.029
pruebas_automatizadas_num
pruebas_automatizadas_num
-0.026
duracion_meses
duracion_meses
0.020
Para las variables independientes y exito_num se realiza el comportamiento para cada una de las clases:
# Preparar datos con variables seleccionadas y éxitodatos_viz <- datos_clean %>%select(all_of(names(datos_correlacion)), exito_proyecto) %>%mutate(exito_proyecto =factor(exito_proyecto, levels =c("Bajo", "Medio", "Alto"))) # Asegurar orden correcto de factores# Crear matriz de gráficos de dispersiónGGally::ggpairs(datos_viz, legend =1,mapping = ggplot2::aes(colour = exito_proyecto),lower =list(continuous =wrap("smooth", alpha =0.3, size =0.1))) +theme_minimal() +theme(legend.position ="bottom",axis.text =element_text(size =8),strip.text =element_text(size =8))
De la gráfica se observa que ciertas variables parecen tener alguna relación (se muestrán con asteriscos) con ciertas clases de la variable dependiente. Esto da idea de que estás variables pueden ser buenas al momento de realizar predicciones.
Estadística descriptiva
# Cargar las bibliotecas necesariaslibrary(dplyr) # Para manipulación de datoslibrary(ggplot2) # Para visualizaciónlibrary(tidyr) # Para transformación de datoslibrary(gridExtra) # Para organizar múltiples gráficoslibrary(lubridate) # Para manejo de fechaslibrary(knitr) # Para presentación de tablas# Calcular estadísticas descriptivas detalladas para cada variable numérica# agrupadas por nivel de éxito del proyectostats_descriptivas <- datos_viz %>%group_by(exito_proyecto) %>%summarise(across(where(is.numeric),list(media =~round(mean(., na.rm =TRUE), 2), # Media con 2 decimalesmediana =~round(median(., na.rm =TRUE), 2), # Mediana con 2 decimalesdesv_est =~round(sd(., na.rm =TRUE), 2), # Desviación estándarcv =~round(sd(., na.rm =TRUE) /mean(., na.rm =TRUE) *100, 2), # Coeficiente de variaciónq25 =~round(quantile(., 0.25, na.rm =TRUE), 2), # Primer cuartilq75 =~round(quantile(., 0.75, na.rm =TRUE), 2), # Tercer cuartiliqr =~round(IQR(., na.rm =TRUE), 2), # Rango intercuartílicomin =~round(min(., na.rm =TRUE), 2), # Valor mínimomax =~round(max(., na.rm =TRUE), 2) # Valor máximo ) ) ) %>%# Ordenar por nivel de éxito (Bajo, Medio, Alto)arrange(factor(exito_proyecto, levels =c("Bajo", "Medio", "Alto")))# Mostrar las estadísticas descriptivas en formato tabularprint(kable(stats_descriptivas, caption ="Estadísticas descriptivas por nivel de éxito",format ="pipe"))
# Preparar datos temporales agregados por mes y nivel de éxitodatos_temporales <- datos_clean %>%# Convertir fecha_inicio a formato mesmutate(mes =floor_date(as_date(fecha_inicio), "month")) %>%# Agrupar por mes y nivel de éxitogroup_by(mes, exito_proyecto) %>%# Calcular promedios mensuales de métricas clavesummarise(# Métricas del equipotamano_equipo_promedio =mean(tamano_equipo, na.rm =TRUE),# Métricas de calidaderrores_promedio =mean(errores_por_kloc, na.rm =TRUE),puntuacion_calidad_promedio =mean(puntuacion_calidad, na.rm =TRUE),# Métricas de satisfacción y duraciónsatisfaccion_promedio =mean(satisfaccion_cliente, na.rm =TRUE),duracion_meses_promedio =mean(duracion_meses, na.rm =TRUE),# Contar número de proyectosn_proyectos =n() ) %>%ungroup()# Definir variables para visualización temporalmetricas_temporales <-c("tamano_equipo_promedio"="Tamaño del Equipo","errores_promedio"="Errores por KLOC","puntuacion_calidad_promedio"="Puntuación de Calidad","satisfaccion_promedio"="Satisfacción del Cliente","duracion_meses_promedio"="Duración (Meses)")# Crear gráficos de tendencia temporalplots_tendencia <-lapply(names(metricas_temporales), function(var) {ggplot(datos_temporales, aes_string(x ="mes", y = var, color ="exito_proyecto", group ="exito_proyecto")) +# Línea de tendenciageom_line(size =0.8) +# Suavizado para ver tendencia generalgeom_smooth(method ="loess", se =FALSE, alpha =0.3, size =1) +# Etiquetas y títuloslabs(title = metricas_temporales[var],x ="Fecha",y ="Valor Promedio",color ="Nivel de Éxito") +# Tema y formatotheme_minimal() +theme(axis.text.x =element_text(angle =45, hjust =1),legend.position ="bottom",plot.title =element_text(hjust =0.5, face ="bold"),panel.grid.minor =element_blank() ) +# Escala de colores personalizadascale_color_manual(values =c("Bajo"="#FF9999", "Medio"="#66B2FF", "Alto"="#99FF99"))})# Mostrar gráficos en una cuadrículado.call(grid.arrange, c(plots_tendencia, ncol =2))
Preparación para siguientes prácticas
# Guardar datos limpios para siguientes prácticaswrite.csv(datos_viz, "datos_proyectos_software_limpio.csv", row.names =FALSE)
Los datos limpios generados en esta práctica servirán como base para: - Práctica 2: Implementación de modelos de árboles de decisión para clasificación - Práctica 3: Análisis de clustering para identificar patrones - Práctica 4: Desarrollo de una aplicación Shiny para predicciones