Detección de Anomalías

En el presente documento realizaremos una inspección a la base de datos que corresponde a nuestro tema de interés, que es el agrupamiento de los consumidores de un supermercado según sus preferencias y de esta manera enviar esta información al equipo de marketing.

Comenzamos con un resumen de la base de datos que se esta usando:

library(readxl)

A <- read_excel("A.xlsx")
summary(A)
##   Invoice ID           Branch              City           Customer type     
##  Length:1000        Length:1000        Length:1000        Length:1000       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##     Gender          Product line        Unit price           Quantity    
##  Length:1000        Length:1000        Length:1000        Min.   : 1.00  
##  Class :character   Class :character   Class :character   1st Qu.: 3.00  
##  Mode  :character   Mode  :character   Mode  :character   Median : 5.00  
##                                                           Mean   : 5.51  
##                                                           3rd Qu.: 8.00  
##                                                           Max.   :10.00  
##     Tax 5%             Total               Date          
##  Length:1000        Length:1000        Length:1000       
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##                                                          
##                                                          
##                                                          
##       Time                       Payment              cogs          
##  Min.   :1899-12-31 10:00:00   Length:1000        Length:1000       
##  1st Qu.:1899-12-31 12:43:00   Class :character   Class :character  
##  Median :1899-12-31 15:19:00   Mode  :character   Mode  :character  
##  Mean   :1899-12-31 15:24:42                                        
##  3rd Qu.:1899-12-31 18:15:00                                        
##  Max.   :1899-12-31 20:59:00                                        
##  gross margin percentage gross income          Rating         
##  Min.   :4.762e+09       Length:1000        Length:1000       
##  1st Qu.:4.762e+09       Class :character   Class :character  
##  Median :4.762e+09       Mode  :character   Mode  :character  
##  Mean   :4.762e+09                                            
##  3rd Qu.:4.762e+09                                            
##  Max.   :4.762e+09

Dado que el documento es muy extenso (pues cuenta con 1000 entradas con 11 características) recurriremos a funciones de R para poder analizar la base de datos.

Corrección

Como se detallo en el trabajo anterior correspondiente al Preprosesamiento de los Datos y en un Análisis de Componentes Principales (ACP) las tres componentes más representativas eran "Unit Price", "Quantity" y "Tax 5%".

Como pudimos notar al inicio, "Unit Price" y "Tax %%" son consideradas de tipo character:

is.character(A$`Unit price`)
## [1] TRUE
is.character(A$`Tax 5%`)
## [1] TRUE

Por lo que procedemos a transformarlas en tipo numeric. Así:

A$`Unit price` = as.numeric(A$`Unit price`)
A$`Tax 5%` = as.numeric(A$`Tax 5%`)

Así, presentamos la base de datos corregida y dejando las variables relevantes para nuestro trabajo:

data <- A[,7:9]
summary(data)
##    Unit price       Quantity         Tax 5%        
##  Min.   :10.08   Min.   : 1.00   Min.   :     0.5  
##  1st Qu.:32.88   1st Qu.: 3.00   1st Qu.:  3785.5  
##  Median :55.23   Median : 5.00   Median : 13266.0  
##  Mean   :55.67   Mean   : 5.51   Mean   : 41433.7  
##  3rd Qu.:77.94   3rd Qu.: 8.00   3rd Qu.: 33183.2  
##  Max.   :99.96   Max.   :10.00   Max.   :448785.0

Valores Faltantes

Verificaremos si existen valores faltantes en nuestra vase de datos. Para eso:

missing(data)
## [1] FALSE

Como se esperaba, al ser una base de datos extraída de "kaggle.com" no existen valores faltantes en nuestra base de datos.

Visualización de valores atípicos

Para poder visualizar los posibles valores atípicos (anómalos) consideraremos la función "boxplot" para las tres variables "Unit Price", "Quantity" y "Tax 5%", así:

boxplot(data$`Unit price`)

boxplot(data$Quantity)

boxplot(data$`Tax 5%`)

Como podemos observar en las variables "Unit Price" y "Quantity" no presentan datos anómalos visibles, al contrario de "Tax 5%", que presentan muchos valores anómalos y esto puede deberse a las políticas que rijan en el país con respecto a los impuestos. Por lo que para el estudio del presente trabajo vamos a centrarnos en la variable "Tax 5%".

Estudio de la variable

Ahora, veremos la normalidad de nuestra variable con el histograma, y mediante laa aplicación de dos pruebas estadísticas de normalidad, así

library(nortest)
library(normtest)
library(moments)
hist(data$`Tax 5%`)

jb.norm.test(data$`Tax 5%`)
## 
##  Jarque-Bera test for normality
## 
## data:  data$`Tax 5%`
## JB = 4322.2, p-value < 2.2e-16
lillie.test(data$`Tax 5%`)
## 
##  Lilliefors (Kolmogorov-Smirnov) normality test
## 
## data:  data$`Tax 5%`
## D = 0.30857, p-value < 2.2e-16

Por lo que como el contraste de hipótesis en el test Jarque-Bera y en el test de Kolmogorov-Smirnov es:

      H0:Los datos tienden una distri. Normal
      
      H1:Los datos no tienden una distri. Normal
      

y como el p-valor es menor que la confiabilidaad al 95%, se tiene que la variable Taz 5% no cumplle la normalidad por lo que no tiene sentido aplicar la prueba de Grubb.

Anomalías por vecino más cercano

Primero observamos a nuestra variable Tax 5% en contraste con las demás (Unit price y Quantity)

plot( data$`Tax 5%`,data$`Unit price`)

plot( data$`Tax 5%`,data$Quantity)

Luego, tomemos el primer contraste (Tax 5% vs United price), obsevamos su matriz de distancias y su distancia promedio a los vecinos más cercanos, de la cual obtenemos la más grande, que será la que nos dé el valor atípico.

library(FNN)
m1 <- cbind(data$`Tax 5%`, data$`Unit price`)
knn1 <- get.knn(data =m1, k = 5)
names(knn1)
## [1] "nn.index" "nn.dist"
head(knn1$nn.dist, 10)
##               [,1]         [,2]         [,3]         [,4]         [,5]
##  [1,]  101.3717140 6310.0256735 1.022000e+04 12190.025636 12860.007340
##  [2,]    1.2844454    1.5404220 2.226477e+00     3.052188     3.052937
##  [3,]  270.6426603 1100.0504950 1.890000e+03  2140.022531  2880.052441
##  [4,]   12.0300665   83.8360072 9.898623e+01   124.000387   191.920069
##  [5,] 1540.0000629 2160.0894694 3.220000e+03 10080.014234 13770.009436
##  [6,] 1060.1656416 1680.0000686 3.220000e+03 13300.009648 14280.000583
##  [7,]   47.2861756   84.0004667 1.105399e+02   117.312617   175.123659
##  [8,]    0.7379024    0.8720665 9.838699e-01     1.162755     2.437314
##  [9,]   10.0005000   20.7487927 3.618411e+01    36.858162    52.325902
## [10,]   25.8642611   29.9675091 4.424686e+01    91.578908    92.870022
score1 <- rowMeans(knn1$nn.dist)
which.max(score1)
## [1] 210

De igual manera tomamos el segundo contraste (Tax 5% vs Quantity), obsevamos su matriz de distancias y su distancia promedio a los vecinos más cercanos, de la cual obtenemos la más grande, que será la que nos dé el valor atípico.

library(FNN)
m2 <- cbind(data$`Tax 5%`, data$Quantity)
knn2 <- get.knn(data =m2, k = 5)
names(knn2)
## [1] "nn.index" "nn.dist"
head(knn2$nn.dist, 10)
##              [,1]        [,2]        [,3]         [,4]         [,5]
##  [1,]  100.019998 6310.000317 10220.00000 12190.000164 12860.000156
##  [2,]    0.540000    1.131769     1.28000     1.352072     1.365613
##  [3,]  270.007407 1100.001818  1890.00000  2140.000935  2880.000694
##  [4,]    3.605551   83.294658    97.02062   124.000000   191.010471
##  [5,] 1540.000000 2160.000926  3220.00000 10080.000198 13770.000145
##  [6,] 1060.001887 1680.000000  3220.00000 13300.000150 14280.000000
##  [7,]   44.045431   84.000000   107.07474   116.017240   173.046237
##  [8,]    0.330000    0.390000     0.44000     0.520000     1.090000
##  [9,]   10.000000   10.198039    29.15476    32.062439    37.013511
## [10,]   12.369317   22.022716    35.01428    89.005618    89.022469
score2 <- rowMeans(knn2$nn.dist)
which.max(score2)
## [1] 210

En ambos casos obtenemos un valor atípico en la observación 210.

Conclusiones

Se sabe que el problema de estas observaciones atípicas requiere una especial atención, pues se ha demostrado que el efecto de los valores atípicos puede tener consecuencias perversas en cualquier análisis que se realicen posteriormente sobre los datos, estimaciones que serán sesgadas, siendo el sesgo la relación que existe entre el valor atípico y las variables explicativas incluidas en un modelo. De esta forma, la detección de los valores atípicos en los datos se presenta como indispensable, presentándose en este trabajo una metodología útil para de detección de los valores atípicos.