“DETECCIÓN Y TRATAMIENTO DE DATOS AUSENTES (MISSING)”

Detección de datos ausentes

#Limpiamos la memoria
rm(list = ls())
ls()
## character(0)
# Verificar el directorio de trabajo
getwd()
## [1] "C:/Users/lenovo/Documents/GitHub/Analisis-Multivariado/SESIONES/Sesión 03"

Cargamos la data

library(readr)
data =  read_delim("CHURNM.CSV", delim = ";", escape_double = FALSE, 
                   locale = locale(encoding = "WINDOWS-1252"),trim_ws = TRUE)

La función head() muestra el contenido de la tabla de datos que acabamos que cargar

#visualizamos la data
head(data)
## # A tibble: 6 × 16
##   IdCliente  Genero Mayor65Age Socio Dependientes MultipleLineas Internet TVPago
##   <chr>      <chr>       <dbl> <chr> <chr>        <chr>          <chr>    <chr> 
## 1 7590-VHVEG Femen…          0 Si    No           No servicio t… DSL      No    
## 2 5575-GNVDE Mascu…          0 No    No           No             DSL      No    
## 3 3668-QPYBK Mascu…          0 No    No           No             DSL      No    
## 4 7795-CFOCW Mascu…          0 No    No           No servicio t… DSL      No    
## 5 9237-HQITU Femen…          0 No    No           No             Fibra O… No    
## 6 9305-CDSKC Femen…          0 No    No           Si             Fibra O… Si    
## # ℹ 8 more variables: TerminoContrato <chr>, FactElectronica <chr>,
## #   MetodoPago <chr>, CargoMensual <dbl>, ImporteTotal <dbl>, pemanencia <dbl>,
## #   ServicioTel <chr>, Churn <chr>

la función str() permite conocer de forma compacta la estructura interna de la tabla de datos indicando el tipo de variables, los rangos de valores y una muestra de dichos valores donde de un vistazo se puede apreciar la presencia de valores ausentes identificados mediante las siglas NA (del inglés, not allowed o no disponible)

# revisar la estructura de los datos
str(data)
## spc_tbl_ [7,043 × 16] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ IdCliente      : chr [1:7043] "7590-VHVEG" "5575-GNVDE" "3668-QPYBK" "7795-CFOCW" ...
##  $ Genero         : chr [1:7043] "Femenino" "Masculino" "Masculino" "Masculino" ...
##  $ Mayor65Age     : num [1:7043] 0 0 0 0 0 0 0 0 0 0 ...
##  $ Socio          : chr [1:7043] "Si" "No" "No" "No" ...
##  $ Dependientes   : chr [1:7043] "No" "No" "No" "No" ...
##  $ MultipleLineas : chr [1:7043] "No servicio tel" "No" "No" "No servicio tel" ...
##  $ Internet       : chr [1:7043] "DSL" "DSL" "DSL" "DSL" ...
##  $ TVPago         : chr [1:7043] "No" "No" "No" "No" ...
##  $ TerminoContrato: chr [1:7043] "Mes a mes" "Un año" "Mes a mes" "Un año" ...
##  $ FactElectronica: chr [1:7043] "Si" "No" "Si" "No" ...
##  $ MetodoPago     : chr [1:7043] "Cheque electronico" "Cheque fisico" "Cheque fisico" "TransfBanca (automatica)" ...
##  $ CargoMensual   : num [1:7043] 29.9 NA 53.9 42.3 70.7 ...
##  $ ImporteTotal   : num [1:7043] 29.9 1889.5 108.2 1840.8 151.7 ...
##  $ pemanencia     : num [1:7043] 1 34 2 45 2 8 22 10 28 62 ...
##  $ ServicioTel    : chr [1:7043] "No" "Si" "Si" NA ...
##  $ Churn          : chr [1:7043] "No" "No" "Si" "No" ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   IdCliente = col_character(),
##   ..   Genero = col_character(),
##   ..   Mayor65Age = col_double(),
##   ..   Socio = col_character(),
##   ..   Dependientes = col_character(),
##   ..   MultipleLineas = col_character(),
##   ..   Internet = col_character(),
##   ..   TVPago = col_character(),
##   ..   TerminoContrato = col_character(),
##   ..   FactElectronica = col_character(),
##   ..   MetodoPago = col_character(),
##   ..   CargoMensual = col_double(),
##   ..   ImporteTotal = col_double(),
##   ..   pemanencia = col_double(),
##   ..   ServicioTel = col_character(),
##   ..   Churn = col_character()
##   .. )
##  - attr(*, "problems")=<externalptr>

La función summary() muestra un resumen general de las variables de la tabla, mostrando los valores: mínimo,máximo, media, mediana, primer y tercer cuartil para las variables numéricas, indicandoademás el número específico de valores NA presentes en cada una.

# Resumen estadística de las variables numéricos
summary(data)
##   IdCliente            Genero            Mayor65Age        Socio          
##  Length:7043        Length:7043        Min.   :0.0000   Length:7043       
##  Class :character   Class :character   1st Qu.:0.0000   Class :character  
##  Mode  :character   Mode  :character   Median :0.0000   Mode  :character  
##                                        Mean   :0.1621                     
##                                        3rd Qu.:0.0000                     
##                                        Max.   :1.0000                     
##                                                                           
##  Dependientes       MultipleLineas       Internet            TVPago         
##  Length:7043        Length:7043        Length:7043        Length:7043       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##  TerminoContrato    FactElectronica     MetodoPago         CargoMensual   
##  Length:7043        Length:7043        Length:7043        Min.   : 18.25  
##  Class :character   Class :character   Class :character   1st Qu.: 35.45  
##  Mode  :character   Mode  :character   Mode  :character   Median : 70.35  
##                                                           Mean   : 64.74  
##                                                           3rd Qu.: 89.90  
##                                                           Max.   :118.75  
##                                                           NA's   :564     
##   ImporteTotal       pemanencia    ServicioTel           Churn          
##  Min.   :  18.85   Min.   : 0.00   Length:7043        Length:7043       
##  1st Qu.: 402.55   1st Qu.: 9.00   Class :character   Class :character  
##  Median :1411.35   Median :29.00   Mode  :character   Mode  :character  
##  Mean   :2293.12   Mean   :32.39                                        
##  3rd Qu.:3798.38   3rd Qu.:55.00                                        
##  Max.   :8684.80   Max.   :72.00                                        
##  NA's   :604       NA's   :716

Visualización de distribuciones para la variable MetodoPago

library(ggplot2)
ggplot(data = data) +
  geom_bar(mapping = aes(x = MetodoPago))

VERIFICACIÓN DE VALORES MISSING

La presencia de datos ausentes, perdidos, missing values, o celdas vacías, representados habitualmente en R como NA, es una problemática habitual en muchos conjuntos datos. La mayoría de las veces se debe a fallos en la transcripción de los datos o problemas durante la recogida de datos, por ejemplo, debido a la imposibilidad para obtener cierta medida u observación. Tratar con conjuntos de datos en los que existen datos ausentes puede generar problemas a la hora de aplicar diferentes análisis estadísticos o en la generación de representaciones gráficas. A fin de evitar problemas futuros, es necesario aprender a detectar y aplicar algún tipo de tratamiento.

La función is.na().Es la función más sencilla para evaluar los valores ausentes ya que presenta un valor lógico de TRUE si existe un valor ausente n una fila (funciona en vector, lista, matrices y data frame).

# Devuelve un vector lógico
head(is.na(data))
##      IdCliente Genero Mayor65Age Socio Dependientes MultipleLineas Internet
## [1,]     FALSE  FALSE      FALSE FALSE        FALSE          FALSE    FALSE
## [2,]     FALSE  FALSE      FALSE FALSE        FALSE          FALSE    FALSE
## [3,]     FALSE  FALSE      FALSE FALSE        FALSE          FALSE    FALSE
## [4,]     FALSE  FALSE      FALSE FALSE        FALSE          FALSE    FALSE
## [5,]     FALSE  FALSE      FALSE FALSE        FALSE          FALSE    FALSE
## [6,]     FALSE  FALSE      FALSE FALSE        FALSE          FALSE    FALSE
##      TVPago TerminoContrato FactElectronica MetodoPago CargoMensual
## [1,]  FALSE           FALSE           FALSE      FALSE        FALSE
## [2,]  FALSE           FALSE           FALSE      FALSE         TRUE
## [3,]  FALSE           FALSE           FALSE      FALSE        FALSE
## [4,]  FALSE           FALSE           FALSE      FALSE        FALSE
## [5,]  FALSE           FALSE           FALSE      FALSE        FALSE
## [6,]  FALSE           FALSE           FALSE      FALSE        FALSE
##      ImporteTotal pemanencia ServicioTel Churn
## [1,]        FALSE      FALSE       FALSE FALSE
## [2,]        FALSE      FALSE       FALSE FALSE
## [3,]        FALSE      FALSE       FALSE FALSE
## [4,]        FALSE      FALSE        TRUE FALSE
## [5,]        FALSE      FALSE       FALSE FALSE
## [6,]        FALSE      FALSE       FALSE FALSE

La función any(is.na()) devuelve TRUE si la tabla presenta al menos un valor ausente, sin indicar el número de valores perdidos que presenta la tabla, ni la posición

any(is.na(data)) 
## [1] TRUE

la función sum(is.na()), permite determinar el número de valores ausentes

# Devuelve el número de NAs que presenta la tabla
sum(is.na(data)) 
## [1] 2608

mean(is.na()),muestra el porcentaje de valores perdidos que presenta la tabla con la cual estamos trabajando

# Devuelve el % de valores perdidos
mean(is.na(data)) 
## [1] 0.02314355

En ocasiones conviene realizar una detección de valores ausentes por columnas, en lugar de por filas, para identificar si alguna de las variables del dataset presenta un determinado nivel de datos perdidos. Para ello podemos utilizar la función colMeans(is.na()) y la función colSums(is.na()).

# Detección del número de valores perdidos en cada una de las columnas que presenta la tabla
colSums(is.na(data))
##       IdCliente          Genero      Mayor65Age           Socio    Dependientes 
##               0               0               0               0               0 
##  MultipleLineas        Internet          TVPago TerminoContrato FactElectronica 
##               0               0               0               0               0 
##      MetodoPago    CargoMensual    ImporteTotal      pemanencia     ServicioTel 
##               0             564             604             716             724 
##           Churn 
##               0
# Detección del % de valores perdidos en cada una de las columnas que presenta la tabla
colMeans(is.na(data), round(2))
##       IdCliente          Genero      Mayor65Age           Socio    Dependientes 
##      0.00000000      0.00000000      0.00000000      0.00000000      0.00000000 
##  MultipleLineas        Internet          TVPago TerminoContrato FactElectronica 
##      0.00000000      0.00000000      0.00000000      0.00000000      0.00000000 
##      MetodoPago    CargoMensual    ImporteTotal      pemanencia     ServicioTel 
##      0.00000000      0.08007951      0.08575891      0.10166122      0.10279710 
##           Churn 
##      0.00000000
# Determinación del porcentaje de valores perdidos respecto del total de datos para cada columna
porcentajeMiss = function(x) {
  sum(is.na(x)) / length(x)*100
  }

apply(data, 2, porcentajeMiss)
##       IdCliente          Genero      Mayor65Age           Socio    Dependientes 
##        0.000000        0.000000        0.000000        0.000000        0.000000 
##  MultipleLineas        Internet          TVPago TerminoContrato FactElectronica 
##        0.000000        0.000000        0.000000        0.000000        0.000000 
##      MetodoPago    CargoMensual    ImporteTotal      pemanencia     ServicioTel 
##        0.000000        8.007951        8.575891       10.166122       10.279710 
##           Churn 
##        0.000000

Determinar los valores existentes en cada columna del set de datos.

# valores que tiene la columna MetodoPago
unique(data$MetodoPago)
## [1] "Cheque electronico"          "Cheque fisico"              
## [3] "TransfBanca (automatica)"    "TarjetaCredito (automatico)"

Determinar cuantos valores NA hay en cada columna.

apply(data, MARGIN = -1, function(x) sum(is.na(x))) 
##       IdCliente          Genero      Mayor65Age           Socio    Dependientes 
##               0               0               0               0               0 
##  MultipleLineas        Internet          TVPago TerminoContrato FactElectronica 
##               0               0               0               0               0 
##      MetodoPago    CargoMensual    ImporteTotal      pemanencia     ServicioTel 
##               0             564             604             716             724 
##           Churn 
##               0

Visualización

(Visdat y Naniar).Estas librerías permiten visualizar en forma más expedita la proporción de datos perdidos dentro de un conjunto de datos.

library(visdat) # visualización de datos
library(naniar)
library(mice)

Para determinar el patrón de datos hacemos uso de la librería mice.

md.pattern(data,rotate.names = T)

##      IdCliente Genero Mayor65Age Socio Dependientes MultipleLineas Internet
## 4787         1      1          1     1            1              1        1
## 539          1      1          1     1            1              1        1
## 532          1      1          1     1            1              1        1
## 68           1      1          1     1            1              1        1
## 442          1      1          1     1            1              1        1
## 53           1      1          1     1            1              1        1
## 49           1      1          1     1            1              1        1
## 9            1      1          1     1            1              1        1
## 414          1      1          1     1            1              1        1
## 48           1      1          1     1            1              1        1
## 47           1      1          1     1            1              1        1
## 4            1      1          1     1            1              1        1
## 41           1      1          1     1            1              1        1
## 3            1      1          1     1            1              1        1
## 7            1      1          1     1            1              1        1
##              0      0          0     0            0              0        0
##      TVPago TerminoContrato FactElectronica MetodoPago Churn CargoMensual
## 4787      1               1               1          1     1            1
## 539       1               1               1          1     1            1
## 532       1               1               1          1     1            1
## 68        1               1               1          1     1            1
## 442       1               1               1          1     1            1
## 53        1               1               1          1     1            1
## 49        1               1               1          1     1            1
## 9         1               1               1          1     1            1
## 414       1               1               1          1     1            0
## 48        1               1               1          1     1            0
## 47        1               1               1          1     1            0
## 4         1               1               1          1     1            0
## 41        1               1               1          1     1            0
## 3         1               1               1          1     1            0
## 7         1               1               1          1     1            0
##           0               0               0          0     0          564
##      ImporteTotal pemanencia ServicioTel     
## 4787            1          1           1    0
## 539             1          1           0    1
## 532             1          0           1    1
## 68              1          0           0    2
## 442             0          1           1    1
## 53              0          1           0    2
## 49              0          0           1    2
## 9               0          0           0    3
## 414             1          1           1    1
## 48              1          1           0    2
## 47              1          0           1    2
## 4               1          0           0    3
## 41              0          1           1    2
## 3               0          1           0    3
## 7               0          0           1    3
##               604        716         724 2608

Visualización gráfica de proporción de datos perdidos y donde se producen.

vis_dat(data)

Determinación del porcentaje de datos perdidos.

vis_miss(data ,sort_miss = TRUE) 

cluster agrupa los datos faltantes

vis_miss(data ,sort_miss = TRUE, cluster = TRUE)

Según los resultados el porcentaje de missing, respecto al total es 2.3% (Muy pequeño)

El porcentaje de missing de acuerdo a cada columna esta entre 1-5% (manejable)

Tratamiento de datos ausentes

Existen varias maneras de tratar con valores ausentes:

Rellenar los valores con la media, mediana o el valor más frecuente de la variable.

  • Completar los valores que faltan por el valor que esté directamente antes o después en la fila o columna.
  • Completar todos los datos faltantes con 0, si se trata de valores numéricos. Esta opción es poco aconsejable ya que puedes modificar de manera significativa los resultados.
  • Eliminar las filas que presenten valores ausentes, siempre y cuando el conjunto de datos sea lo suficientemente grande y no se pierde información relevante al eliminar esas filas.
  • Una forma abrupta de tratamiento que depende del contexto de análisis, es eliminar las variables que presentan un porcentaje mayor del 50% de datos ausentes.

Primer caso: Eliminar todos los missing

data_sin_missing = na.omit(data)
dim(data_sin_missing)
## [1] 4787   16

Segundo Caso: Reemplazamos con la mediana y moda

library(DMwR2)
data_imp_mtc = centralImputation(data) # Mediana (numérico), moda (no numérico)
dim(data_imp_mtc)
## [1] 7043   16
vis_miss(data_imp_mtc)

Tercer Caso: Reemplazo con la media, mediana y moda

library(VIM)
data_imp_mtc2 = initialise(data, method="median") #Media (continuo), Mediana (discreto), moda (No numérico)
dim(data_imp_mtc2) 
## [1] 7043   16
vis_miss(data_imp_mtc2) 

“DETECCIÓN Y TRATAMIENTO DE VALORES ATÍPICOS (OUTLIERS)”

Gráfico de caja (boxplot) de una variable ImporteTotal respecto al método de pago

library(plotly)
plot_ly (data =  data, x = ~ImporteTotal, y = ~ MetodoPago,type = "box", 
         color = ~ MetodoPago)

Calculamos los valores estandarizados de cada columna utilizando la función scale()

# Estandarizar de las variables del DataFrame 
data_estandar = cbind(scale(data[,12:14]),data[,1:11], data[,15:16])
head(data_estandar)
##   CargoMensual ImporteTotal  pemanencia  IdCliente    Genero Mayor65Age Socio
## 1   -1.1598031   -0.9946796 -1.27834077 7590-VHVEG  Femenino          0    Si
## 2           NA   -0.1773864  0.06567824 5575-GNVDE Masculino          0    No
## 3   -0.3619047   -0.9602677 -1.23761292 3668-QPYBK Masculino          0    No
## 4   -0.7458933   -0.1988115  0.51368458 7795-CFOCW Masculino          0    No
## 5    0.1982864   -0.9411500 -1.23761292 9237-HQITU  Femenino          0    No
## 6    1.1607514   -0.6471987 -0.99324583 9305-CDSKC  Femenino          0    No
##   Dependientes  MultipleLineas     Internet TVPago TerminoContrato
## 1           No No servicio tel          DSL     No       Mes a mes
## 2           No              No          DSL     No          Un año
## 3           No              No          DSL     No       Mes a mes
## 4           No No servicio tel          DSL     No          Un año
## 5           No              No Fibra Optica     No       Mes a mes
## 6           No              Si Fibra Optica     Si       Mes a mes
##   FactElectronica               MetodoPago ServicioTel Churn
## 1              Si       Cheque electronico          No    No
## 2              No            Cheque fisico          Si    No
## 3              Si            Cheque fisico          Si    Si
## 4              No TransfBanca (automatica)        <NA>    No
## 5              Si       Cheque electronico          Si    Si
## 6              Si       Cheque electronico          Si    Si

Detección de valores Outliers.

Establecemos un límite para considerar valores atípicos, por ejemplo, si el puntaje Z es mayor a 3 o menor a -3. Finalmente, utilizamos la función apply() para verificar si algún valor en cada fila del DataFrame estandarizado excede el límite establecido, y seleccionamos las filas que cumplan esta condición.

Identificando valores outliers a partir de los estandarizados - rango intercuartílico (IQR)

library(dplyr)
library(outliers)
data_imp_mtc[, 12:14]
## # A tibble: 7,043 × 3
##    CargoMensual ImporteTotal pemanencia
##           <dbl>        <dbl>      <dbl>
##  1         29.8         29.8          1
##  2         70.4       1890.          34
##  3         53.8        108.           2
##  4         42.3       1841.          45
##  5         70.7        152.           2
##  6         99.6        820.           8
##  7         89.1       1949.          22
##  8         29.8        302.          10
##  9        105.        3046.          28
## 10         70.4       3488.          62
## # ℹ 7,033 more rows
# Estandarizar los datos sin missing utilizando la función scale:
df_scaled = as.data.frame(scale(data_imp_mtc[, 12:14]))
head(df_scaled)
##   CargoMensual ImporteTotal  pemanencia
## 1   -1.2231168   -0.9991209 -1.33265903
## 2    0.1787729   -0.1498013  0.08401196
## 3   -0.3923673   -0.9633605 -1.28972960
## 4   -0.7921655   -0.1720659  0.55623563
## 5    0.1908880   -0.9434937 -1.28972960
## 6    1.1929795   -0.6380236 -1.03215306
# Calculamos RIQ para cada variable utilizando la función IQR y luego identificar los outliers utilizando un umbral arbitrario (por ejemplo, 1.5 veces el IQR)

outliers_IQR = df_scaled %>%
  mutate_if(is.numeric, funs(outlier = ifelse(abs(.) > 1.5 * IQR(.), "Yes", "No")))

head(outliers_IQR, 10)
##    CargoMensual ImporteTotal  pemanencia CargoMensual_outlier
## 1    -1.2231168   -0.9991209 -1.33265903                   No
## 2     0.1787729   -0.1498013  0.08401196                   No
## 3    -0.3923673   -0.9633605 -1.28972960                   No
## 4    -0.7921655   -0.1720659  0.55623563                   No
## 5     0.1908880   -0.9434937 -1.28972960                   No
## 6     1.1929795   -0.6380236 -1.03215306                   No
## 7     0.8277959   -0.1224444 -0.43114113                   No
## 8    -1.2265782   -0.8748731 -0.94629421                   No
## 9     1.3712445    0.3784060 -0.17356458                   No
## 10    0.1787729    0.5802258  1.28603583                   No
##    ImporteTotal_outlier pemanencia_outlier
## 1                    No                 No
## 2                    No                 No
## 3                    No                 No
## 4                    No                 No
## 5                    No                 No
## 6                    No                 No
## 7                    No                 No
## 8                    No                 No
## 9                    No                 No
## 10                   No                 No

Método basado en estadísticas: Outliers bivariado

# boxplot con las variables estandarizadas
boxplot(data_estandar[,1:3])

Eliminamos los Outliers

Usando el rango intercuartil (IQR)

### Usando el rango intercuartil (IQR) - Cargo mensual 
q1C = quantile(df_scaled$CargoMensual, 0.25)
q3C = quantile(df_scaled$CargoMensual, 0.75)

# Calcular Q1, Q3 y IQR
iqrC = q3C - q1C

# Calcular los límites para identificar los outliers
lim_infC = q1C - 1.5 * iqrC
lim_supC = q3C + 1.5 * iqrC

# Eliminar los outliers
datos_sin_outliers_Cargo = df_scaled$CargoMensual[df_scaled$CargoMensual >= lim_infC & df_scaled$ImporteTotal <= lim_supC]

# Imprimir el resultado
head(datos_sin_outliers_Cargo) 
## [1] -1.2231168  0.1787729 -0.3923673 -0.7921655  0.1908880  1.1929795
#Grafica
boxplot(datos_sin_outliers_Cargo, main = "Boxplot de la variable Importe Cargo Mensual ") 

q1I = quantile(df_scaled$ImporteTotal, 0.25)
q3I = quantile(df_scaled$ImporteTotal, 0.75)

# Calcular Q1, Q3 y IQR
iqrI = q3I - q1I

# Calcular los límites para identificar los outliers
lim_infI = q1I - 1.5 * iqrI
lim_supI = q3I + 1.5 * iqrI

# Eliminar los outliers
datos_sin_outliers_Importe = df_scaled$ImporteTotal[df_scaled$ImporteTotal >= lim_infI & df_scaled$ImporteTotal <= lim_supI]

# Imprimir el resultado
head(datos_sin_outliers_Importe) 
## [1] -0.9991209 -0.1498013 -0.9633605 -0.1720659 -0.9434937 -0.6380236
#Grafica
boxplot(datos_sin_outliers_Importe, main = "Boxplot de la variable Importe Total") #Grafica

q1P = quantile(df_scaled$pemanencia, 0.25)
q3P = quantile(df_scaled$pemanencia, 0.75)
# Calcular Q1, Q3 y IQR
iqrP = q3P - q1P

# Calcular los límites para identificar los outliers
lim_infP = q1P - 1.5 * iqrP
lim_supP = q3P + 1.5 * iqrP

# Eliminar los outliers
datos_sin_outliers_Permanencia = df_scaled$pemanencia[df_scaled$pemanencia >= lim_infP & df_scaled$pemanencia <= lim_supP]

# Imprimir el resultado
head(datos_sin_outliers_Permanencia) 
## [1] -1.33265903  0.08401196 -1.28972960  0.55623563 -1.28972960 -1.03215306
#Grafica
boxplot(datos_sin_outliers_Permanencia, main = "Boxplot de la variable Permanencia") #Grafica