Laboratorio 2 – Análisis de datos de fuga de clientes en R

Author

Sebastián Bolaños

Published

September 30, 2025

Introducción

Este informe aborda el análisis del conjunto de datos Telco_Customer_Churn, que contiene 7 043 registros de clientes de una compañía telefónica. Las variables describen el perfil y comportamiento de cada usuario: identificador del cliente, género, si es ciudadano mayor, si tiene pareja, antigüedad en meses (tenure), dependientes, servicios contratados (líneas, internet, seguridad, respaldo, protección, soporte técnico, streaming), tipo de contrato, facturación en papel, método de pago, cargos mensuales (MonthlyCharges), cargos totales acumulados (TotalCharges) y la variable de rotación (Churn), que indica si el cliente abandonó la compañía.

La evaluación se divide en ocho puntos que corresponden a tareas analíticas y visualizaciones. Se realiza la carga e inspección de los datos, estadísticas descriptivas, matriz de correlaciones, diagramas de cajas y barras para explorar relaciones con la rotación, un análisis de regresión lineal y un panel que combina los principales gráficos.

1 – Importar y explorar el conjunto de datos

En primer lugar se cargan los paquetes necesarios y se importa el archivo Telco_Customer_Churn.csv. Ajuste la ruta del archivo según corresponda. También se observan las dimensiones y las primeras filas para tener una idea general de la estructura de los datos.

# Paquetes empleados en el análisis
library(dplyr)     # manipulación de datos
library(tidyr)     # manejo de datos faltantes y pivotes
library(ggplot2)   # gráficos
library(corrplot)  # matriz de correlaciones (opcional)
library(patchwork) # combinar gráficos
library(knitr)     # tablas
library(scales)    # formateo de ejes

# Cargar los datos (modifique la ruta si es necesario)
df <- read.csv("Telco_Customer_Churn.csv", stringsAsFactors = FALSE)

# Convertir variables categóricas de interés en factors
df <- df %>%
  mutate(
    Churn  = factor(Churn),
    gender = factor(gender)
  )

# Vista previa y dimensiones
df %>%
  slice_head(n = 6) %>%
  knitr::kable(caption = "Primeras 6 filas del conjunto de datos")
Primeras 6 filas del conjunto de datos
customerID gender SeniorCitizen Partner Dependents tenure PhoneService MultipleLines InternetService OnlineSecurity OnlineBackup DeviceProtection TechSupport StreamingTV StreamingMovies Contract PaperlessBilling PaymentMethod MonthlyCharges TotalCharges Churn
7590-VHVEG Female 0 Yes No 1 No No phone service DSL No Yes No No No No Month-to-month Yes Electronic check 29.85 29.85 No
5575-GNVDE Male 0 No No 34 Yes No DSL Yes No Yes No No No One year No Mailed check 56.95 1889.50 No
3668-QPYBK Male 0 No No 2 Yes No DSL Yes Yes No No No No Month-to-month Yes Mailed check 53.85 108.15 Yes
7795-CFOCW Male 0 No No 45 No No phone service DSL Yes No Yes Yes No No One year No Bank transfer (automatic) 42.30 1840.75 No
9237-HQITU Female 0 No No 2 Yes No Fiber optic No No No No No No Month-to-month Yes Electronic check 70.70 151.65 Yes
9305-CDSKC Female 0 No No 8 Yes Yes Fiber optic No No Yes No Yes Yes Month-to-month Yes Electronic check 99.65 820.50 Yes
cat("Número de filas:", nrow(df), "\nNúmero de columnas:", ncol(df), "\n\n")
Número de filas: 7043 
Número de columnas: 21 
cat("Variables:\n")
Variables:
print(names(df))
 [1] "customerID"       "gender"           "SeniorCitizen"    "Partner"         
 [5] "Dependents"       "tenure"           "PhoneService"     "MultipleLines"   
 [9] "InternetService"  "OnlineSecurity"   "OnlineBackup"     "DeviceProtection"
[13] "TechSupport"      "StreamingTV"      "StreamingMovies"  "Contract"        
[17] "PaperlessBilling" "PaymentMethod"    "MonthlyCharges"   "TotalCharges"    
[21] "Churn"           

2 – Estadísticas descriptivas

Solo las columnas de tipo numérico son consideradas para las estadísticas descriptivas. Para cada variable se calcula la media, mediana, desviación estándar y extremos mínimos y máximos.

# Identificar columnas numéricas
num_cols <- sapply(df, is.numeric)

# Calcular estadísticos básicos para cada variable numérica
estadisticas <- df %>%
  summarise(across(
    .cols = which(num_cols),
    .fns  = list(
      media   = ~ mean(.x, na.rm = TRUE),
      mediana = ~ median(.x, na.rm = TRUE),
      sd      = ~ sd(.x, na.rm = TRUE),
      min     = ~ min(.x, na.rm = TRUE),
      max     = ~ max(.x, na.rm = TRUE)
    ),
    .names = "{.col}_{.fn}"
  )) %>%
  pivot_longer(everything(), names_to = c("variable", "estadistico"), names_sep = "_", values_to = "valor") %>%
  pivot_wider(names_from = estadistico, values_from = valor)

# Mostrar tabla redondeando valores numéricos
estadisticas %>%
  mutate(across(where(is.numeric), ~ round(.x, 3))) %>%
  knitr::kable(caption = "Estadísticas descriptivas de las variables numéricas")
Estadísticas descriptivas de las variables numéricas
variable media mediana sd min max
SeniorCitizen 0.162 0.000 0.369 0.00 1.00
tenure 32.371 29.000 24.559 0.00 72.00
MonthlyCharges 64.762 70.350 30.090 18.25 118.75
TotalCharges 2283.300 1397.475 2266.771 18.80 8684.80

3 – Matriz de correlación

Se calcula la matriz de correlaciones de Pearson entre las variables numéricas para evaluar relaciones lineales entre ellas. Se presenta la matriz tanto en forma de tabla como en un mapa de calor opcional con el paquete corrplot.

# Subconjunto numérico sin valores faltantes
num_df <- df[, num_cols]
num_df <- num_df[complete.cases(num_df), ]

# Matriz de correlaciones
M <- cor(num_df, use = "pairwise.complete.obs")

# Tabla redondeada
M %>%
  round(3) %>%
  as.data.frame() %>%
  tibble::rownames_to_column("Variable") %>%
  knitr::kable(caption = "Matriz de correlaciones (Pearson) entre variables numéricas")
Matriz de correlaciones (Pearson) entre variables numéricas
Variable SeniorCitizen tenure MonthlyCharges TotalCharges
SeniorCitizen 1.000 0.016 0.220 0.102
tenure 0.016 1.000 0.247 0.826
MonthlyCharges 0.220 0.247 1.000 0.651
TotalCharges 0.102 0.826 0.651 1.000
# Mapa de calor (opcional)
corrplot(M, method = "color", addCoef.col = "black", tl.col = "black", number.cex = 0.6)

Las mayores correlaciones se observan entre MonthlyCharges y TotalCharges, y entre estas con la antigüedad tenure, ya que los cargos totales aumentan con el tiempo y los cargos mensuales.

4 – Diagrama de cajas: cargos mensuales según rotación

Se construye un diagrama de cajas para comparar los cargos mensuales (MonthlyCharges) de los clientes que se han retirado (Churn = Yes) con los que permanecen (Churn = No).

# Boxplot de cargos mensuales por rotación
p4_box <- ggplot(df, aes(x = Churn, y = MonthlyCharges)) +
  geom_boxplot(fill = "lightgoldenrodyellow", color = "gray30") +
  labs(
    title = "Cargos mensuales según rotación",
    x = "Rotación (Churn)",
    y = "Cargos mensuales"
  ) +
  theme_minimal()

p4_box

Se observa que los clientes que se han retirado tienden a tener cargos mensuales ligeramente superiores en comparación con quienes permanecen, aunque la variabilidad es similar en ambos grupos.

5 – Diagrama de cajas: cargos totales según rotación

Ahora se compara la distribución de los cargos totales acumulados (TotalCharges) entre los grupos de rotación.

# Asegurarse de que TotalCharges es numérico
if(!is.numeric(df$TotalCharges)) df$TotalCharges <- as.numeric(df$TotalCharges)

# Eliminar casos con NA en TotalCharges o Churn
p5_data <- df %>% select(Churn, TotalCharges) %>% drop_na()

# Gráfico
p5_box <- ggplot(p5_data, aes(x = Churn, y = TotalCharges)) +
  geom_boxplot(fill = "mistyrose", color = "gray30") +
  scale_y_continuous(labels = dollar_format(prefix = "$")) +
  labs(
    title = "Cargos totales según rotación",
    x = "Rotación (Churn)",
    y = "Cargos totales acumulados"
  ) +
  theme_minimal()

p5_box

Los clientes que se retiraron muestran cargos totales algo más altos. Sin embargo, la dispersión amplia indica que existen clientes con altos cargos totales en ambos grupos.

6 – Diagrama de barras apiladas: género y rotación

Se analiza la proporción de géneros dentro de cada categoría de rotación mediante un diagrama de barras apiladas normalizado.

# Contar clientes por rotación y género
gender_churn_tab <- df %>% count(Churn, gender, name = "count")

# Gráfico de barras apiladas (proporciones)
p6_bar <- ggplot(gender_churn_tab, aes(x = Churn, y = count, fill = gender)) +
  geom_col(position = "fill") +
  scale_y_continuous(labels = percent_format()) +
  labs(
    title = "Distribución de género según rotación",
    x = "Rotación (Churn)",
    y = "Proporción",
    fill = "Género"
  ) +
  theme_minimal()

p6_bar

La distribución de género es similar en ambos grupos de rotación, lo que sugiere que esta variable no tiene un impacto notable en la decisión de abandonar la compañía.

7 – Relación entre antigüedad y cargos mensuales

Se evalúa la relación lineal entre la antigüedad (tenure) y los cargos mensuales (MonthlyCharges), calculando la correlación de Pearson y ajustando un modelo de regresión lineal simple.

# Eliminar registros incompletos
tm_df <- df %>% select(tenure, MonthlyCharges) %>% drop_na()

# Correlación de Pearson
corr_tm <- cor(tm_df$tenure, tm_df$MonthlyCharges)

# Modelo lineal simple
tm_model <- lm(MonthlyCharges ~ tenure, data = tm_df)
coef_model <- coef(tm_model)

# Gráfico de dispersión con recta de regresión
p7_scatter <- ggplot(tm_df, aes(x = tenure, y = MonthlyCharges)) +
  geom_point(alpha = 0.7) +
  geom_smooth(method = "lm", se = TRUE, color = "blue") +
  labs(
    title    = "Relación entre antigüedad y cargos mensuales",
    subtitle = paste0("r = ", round(corr_tm, 3)),
    x        = "Antigüedad (meses)",
    y        = "Cargos mensuales"
  ) +
  theme_minimal()

p7_scatter

# Tabla de parámetros
parametros <- data.frame(
  Parámetro = c("Intercepto", "Pendiente"),
  Valor     = round(coef_model, 4)
)

knitr::kable(parametros, caption = "Parámetros del modelo lineal (MonthlyCharges ~ tenure)")
Parámetros del modelo lineal (MonthlyCharges ~ tenure)
Parámetro Valor
(Intercept) Intercepto 54.9298
tenure Pendiente 0.3037

El coeficiente de correlación ((r = ) 0.248) indica una relación positiva moderada: clientes con mayor antigüedad tienden a pagar cargos mensuales algo mayores. La pendiente del modelo (0.3) sugiere que cada mes adicional de permanencia se asocia con un incremento promedio en el cargo mensual.

8 – Panel de gráficos

Se combinan los gráficos de los puntos 4, 5, 6 y 7 en un panel 2x2 mediante la librería patchwork. Esto facilita la comparación visual de los distintos aspectos analizados.

# Utilizar patchwork para organizar el panel
(p4_box | p5_box) / (p6_bar | p7_scatter)


Conclusión general

Los análisis realizados permiten concluir que los clientes que se retiran tienden a presentar cargos mensuales y totales ligeramente superiores, aunque las diferencias no son tan marcadas debido a la alta variabilidad. El género no parece influir en la rotación, pues la proporción de hombres y mujeres es similar en ambos grupos. Por otro lado, existe una relación positiva moderada entre la antigüedad y los cargos mensuales, lo cual indica que los clientes más antiguos pueden tener planes más costosos o contratar más servicios. No obstante, el valor de (R^2) sugiere que otros factores no evaluados aquí también influyen en el monto de la factura.