Preprocesamiento en R

Limpieza, Reducción de Dimensionalidad, Transformación e Integración de datos

Juan Manuel Fernandez


Bases de Datos Masivas - UNLu

Preprocessing...


  • Limpieza de datos
    • Datos Faltantes
    • Manejo de Ruido
    • Detección de Outliers


  • Integración de datos
    • Diversas fuentes de datos
    • Diferente representación


  • Reducción de dimensionalidad
    • Atributos Correlacionados
    • Test de Chi-Cuadrado
    • Componentes Principales (PCA)


  • Transformación de datos
    • Discretización
    • Normalización

Datos Faltantes

Vamos a ver algunos tips para implementar las siguientes técnicas en R:

  • Missing Values (Valores Faltantes)
    • Análisis de Faltantes
    • Registros completos
    • Imputaciones (Media, Hot deck)
    • Análisis gráfico de los métodos de imputación

Análisis de Faltantes

Cargamos iris y generamos datos faltantes aleatoriamente:

for(i in 1:4) {
  for(j in 1:5) {
    inst.aleat<-sample(1:nrow(iris), 1, replace=F)
    iris[inst.aleat, i]<-NA
  }
}

Podemos ver, por ejemplo, las instancias con que poseen faltante en una variable:

iris[is.na(iris$Sepal.Length),]
    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
13            NA         3.0          1.4         0.1     setosa
56            NA         2.8          4.5         1.3 versicolor
70            NA         2.5          3.9         1.1 versicolor
91            NA         2.6          4.4         1.2 versicolor
140           NA         3.1          5.4         2.1  virginica

Análisis de Faltantes (++)


Podríamos contar la cantidad de faltantes para una variable:

sum(is.na(iris$Sepal.Length))
[1] 5


También podemos analizar la proporción de faltantes sobre el total de instancias:

round(sum(is.na(iris$Sepal.Length))/nrow(iris)*100,2)
[1] 3.33

Valores Faltantes: Registros Completos

Si quisieramos trabajar únicamente con las instancias del dataset con registros completos:

iris.reg_completos<-na.omit(iris)
nrow(iris.reg_completos)
[1] 130

Simplemente podemos realizar los cálculos removiendo los faltantes:

print(mean(iris$Petal.Length))
[1] NA
print(mean(iris$Petal.Length, na.rm = TRUE))
[1] 3.726897

Valores Faltantes: Imputación por la Media


Seleccionamos las instancias con valor faltante y las reemplazamos por la media de ese atributo:

# Sustitución por la media
iris.imp<-iris
iris.imp$media<-iris$Sepal.Length

iris.imp$media[is.na(iris.imp$media)]<-mean(iris.imp$media, na.rm = TRUE)

# Verificamos que no quedan faltantes
sum(is.na(iris.imp$media))
[1] 0

Valores Faltantes: Imputación Hot Deck

Para hot deck, vamos a utilizar la librería VIM. La función hotdeck imputará los datos directamente sobre el atributo del parámetro y generará un nuevo atributo -boolean- que indica las instancias imputadas:

# Cargamos la librería
library(VIM)

# Definimos un dataframe auxiliar para no perder la variable original
df_aux<-hotdeck(iris, variable="Sepal.Length")
iris.imp$hotdeck<-df_aux$Sepal.Length
iris.imp$hotdeckbool<-df_aux$Sepal.Length_imp

# Verificamos que no existen faltantes
sum(is.na(iris.imp$hotdeck))
[1] 0

Análisis Gráfico de los métodos de imputación

Ahora, analizamos gráficamente la distribución original y su variación luego de realizar las imputaciones:

# Quitamos los atributos que no vamos a usar y renombramos Sepal.Length
iris.imp<-iris.imp[,-c(2:5)]
names(iris.imp)[1]<-"original"

# Analisis grafico de los resultados
plot(density(iris.imp$original, na.rm=TRUE), type = "l", col="red", ylab = "Original", ylim=c(0,0.5))
lines(density(iris.imp$media, na.rm=TRUE), type = "l", col="blue")
lines(density(iris.imp$hotdeck, na.rm=TRUE), type = "l", col="yellow")
legend(7, 0.5, legend=c("Original", "Media", 'Hotdeck'), col=c("red", "blue", 'green','yellow', "black"), lty=1, cex=0.8)

Análisis Gráfico de los métodos de imputación (++)

Obtenemos los siguientes gráficos de densidad:

plot of chunk unnamed-chunk-10

Limpieza de datos: Manejo de Ruido

En limpieza de ruido, vamos a trabajar con Binning por:

  • Frecuencias Iguales (Equal Freq)
  • Anchos Iguales (Equal Width)


Vamos a trabajar con el paquete infotheo para el binning y luego haremos las imputaciones en función del cálculo de la medida de tendencia central elegida

Limpieza de datos: Manejo de Ruido

Manejo de Ruido por Binning: Equal Freq // Equal Width

library(infotheo)
data("iris")
# Discretize recibe el atributo, el método de binning y la cantidad de bins
bin_eq_freq <- discretize(iris$Sepal.Width,"equalfreq", 5)

# Nos copiamos el atributo original
bin_eq_freq$Sepal.Width = iris$Sepal.Width

# Por cada bin calculamos la media y reemplazamos en el atributo suavizado
for(bin in 1:5){
  bin_eq_freq$suavizado[ bin_eq_freq$X==bin] = mean(bin_eq_freq$Sepal.Width[ bin_eq_freq$X==bin])
}

Limpieza de datos: Manejo de Ruido (++)

# grafico Sepal.Width ordenado de menor a mayor
plot(sort(iris$Sepal.Width,decreasing = FALSE) , type = "l", col="red", ylab = "Sepal.Width")
# Agrego la serie suavizada
lines(sort(bin_eq_freq$suavizado),type = "l", col="blue")

plot of chunk unnamed-chunk-12

Detección de Outliers

  • Análisis gráfico de outliers
  • Técnicas de detección y tratamiento de outliers
    • Detección de outliers mediante Rango intercuartil
    • Detección de outliers mediante desvíos de la media

Limpieza de datos: Detección de Outliers

Busquemos outliers en el atributo Road_55db del dataset ruidoso:

ruidoso=read.csv('ruidoso.txt')
data = ruidoso$Road_55dB
plot(sort(data, decreasing = FALSE))

plot of chunk unnamed-chunk-13

Hay outliers? Cuales?

Análisis del atributo ruidoso$Road_55dB

Observemos analíticamente la distribución de la variable:
Media:

mean(data)
[1] 159228.8

Mínimo:

min(data)
[1] 7600

Máximo:

max(data)
[1] 3108200

Detección de outliers: Análisis gráfico (++)

Mediante boxplot podemos observar gráficamente la distribución de la variable:

plot of chunk unnamed-chunk-17

Un criterio de detección de outliers podría ser eliminar los datos que se encuentran por fuera (abajo/arriba) de los “bigotes”.

Análisis del atributo ruidoso$Road_55dB (++)

Observemos gráficamente la distribución de la variable mediante boxplot:

boxplot(data)

plot of chunk unnamed-chunk-18

Claramente, la distribución de la variable no es “normal”, o si? Muchas veces los outliers “esconden” la distribución real de un feature.

Detección de Outliers mediante IRQ

  • Detección de outliers mediante el IRQ*1,5 (Rango intercuartil)
data.riq<-IQR(data)
print(data.riq)
[1] 59950
cuantiles<-quantile(data, c(0.25, 0.5, 0.75), type = 7)
print(cuantiles)
  25%   50%   75% 
18950 37550 78900 

Detección de Outliers mediante IRQ (++)

Multiplicamos el cuantil 1 por 1.5 para determinar la barrera MENOR para la detección de outliers:

outliers_min<-as.numeric(cuantiles[1])-1.5*data.riq
print(outliers_min)
[1] -70975

Y multiplicamos el cuantil 1 por 1.5 para determinar la barrera MAYOR para la detección de outliers:

outliers_max<-as.numeric(cuantiles[3])+1.5*data.riq
print(outliers_max)
[1] 168825

Ruidoso$Road_55dB "sin" outliers

plot(sort(data[data>outliers_min & data<outliers_max], decreasing = FALSE))

plot of chunk unnamed-chunk-23

boxplot(sort(data[data>outliers_min & data<outliers_max], decreasing = FALSE))

plot of chunk unnamed-chunk-24

Detección de Outliers mediante Desvíos de la Media

Otra alternativa es realizar detección de outliers utilizando alguna medida de tendencia central.

Detección por N desvíos de la media (En el ejemplo N=3):

N=3
data<-ruidoso$Road_55dB
desvio<-sd(data)
print(desvio)
[1] 484751.3
outliers_max<-mean(data)+N*desvio
print(outliers_max)
[1] 1613483
outliers_min<-mean(data)-N*desvio
print(outliers_min)
[1] -1295025

Ruidoso$Road_55dB "sin" outliers

plot(sort(data[data>outliers_min & data<outliers_max], decreasing = FALSE))

plot of chunk unnamed-chunk-28

boxplot(sort(data[data>outliers_min & data<outliers_max], decreasing = FALSE))

plot of chunk unnamed-chunk-29

Integración de datos de múltiples fuentes

Existen, varias operaciones para integrar datos, por ejemplo merge:

productos<-data.frame(Codigo=c(45, 46), Denominacion=c("Licuadora", "TV 4k"), Precio=c(1245.10, 14742))
head(productos)
  Codigo Denominacion  Precio
1     45    Licuadora  1245.1
2     46        TV 4k 14742.0
stock<-data.frame(Cod=c(45, 46), stock=c(8650, 145))
dataset<-merge(productos, stock, by.x = "Codigo",  by.y = "Cod")
head(dataset)
  Codigo Denominacion  Precio stock
1     45    Licuadora  1245.1  8650
2     46        TV 4k 14742.0   145


Bonus Track: Librerías sqldf y dplyr.

Bonus Track para Integración/Manipulación de datos: sqldf y dplyr

Con sqldf vamos a manipular los dataframes como si fueran tablas sql:

library(sqldf)
join_string = "SELECT Codigo, Denominacion, Precio, stock as Stock FROM productos p INNER JOIN stock s ON p.Codigo=s.Cod"
sql_query = sqldf(join_string,stringsAsFactors = FALSE)
head(sql_query)
  Codigo Denominacion  Precio Stock
1     45    Licuadora  1245.1  8650
2     46        TV 4k 14742.0   145

Otra librería muy conocida de R para la manipulación de dataframes es dplyr:

library(dplyr)
data.dplyr = inner_join(productos, stock, by = c("Codigo" = "Cod"))
head(data.dplyr)
  Codigo Denominacion  Precio stock
1     45    Licuadora  1245.1  8650
2     46        TV 4k 14742.0   145

Integración de datos de múltiples fuentes (++)

Además, como vimos antes, debemos tener en cuenta:

  • Diferentes nombres de atributos,
names(stock)
[1] "Cod"   "stock"
names(stock)[1]="Codigo"
names(stock)
[1] "Codigo" "stock" 
  • Diferente representación de los mismos datos,
celsius=c(26,32)
fahrenheit=(celsius*1.8)+32
print(fahrenheit)
[1] 78.8 89.6
  • Diferente granularidad.
library(lubridate)
fechas <- c(as.Date("2011-06-26"), as.Date("2013-07-15"))
meses <- c(5, 8)
todos <- cbind(meses, month(fechas))

Reducción de dimensionalidad

Vamos a ver algunos tips para implementar las siguientes técnicas en R:

  • Reducing Highly Correlated Columns
  • Test de Chi-Cuadrado
  • Análisis de Componentes Principales (PCA)

Reducción dimensionalidad: Atrib. Correlacionados

Primero debemos analizar si hay candidatos, podemos hacerlo gráficamente con un heatmap:

library(gplots)
library(RColorBrewer)
# Reducing Highly Correlated Columns
dev.off()
ds.cor=cor(iris[,-c(5)], use="complete.obs")
heatmap.2(ds.cor,
          cellnote = round(ds.cor,1), 
          main = "Correlación",
          notecol="black",     
          density.info="none", 
          trace="none",        
          margins =c(12,12),    
          col=brewer.pal('RdYlBu', n=5),  
          dendrogram="none",     
          Colv="NA")            

Reducción dimensionalidad: Atrib. Correlacionados

El gráfico de heatmap presenta información sobre la correlación entre las variables, con colores de referencia:

plot of chunk unnamed-chunk-37

Atributos Correlacionados (++)

Vamos a hacer el análisis “a mano” y con la librería “caret”:

data.numeric<-na.omit(iris[,-c(5)])

# Calculo matriz de correlacion
matriz.correlacion<-cor(data.numeric)

# Verifico la Correlación con la matríz
print(matriz.correlacion)
             Sepal.Length Sepal.Width Petal.Length Petal.Width
Sepal.Length    1.0000000  -0.1175698    0.8717538   0.8179411
Sepal.Width    -0.1175698   1.0000000   -0.4284401  -0.3661259
Petal.Length    0.8717538  -0.4284401    1.0000000   0.9628654
Petal.Width     0.8179411  -0.3661259    0.9628654   1.0000000

Atributos Correlacionados (+++)

Ahora lo hacemos con la librería Caret:

library(caret)

# Buscamos atributos con correlaciones superiores a 0.75
highlyCorrelated <- findCorrelation(matriz.correlacion, cutoff=0.75)

# Imprimimos los nombres de los atributos que cumplen con la condición anterior
print(names(data.numeric[,highlyCorrelated]))
[1] "Petal.Length" "Petal.Width" 

Luego deberíamos analizar eliminar esos atributos.

Test de Chi-Cuadrado

En datos de tipo cualitativos/nominales: Test de Chi-Cuadrado

Hacemos la tabla de contingencia:

library(MASS)
tbl_cont = table(survey$Smoke, survey$Exer)
print(tbl_cont)

        Freq None Some
  Heavy    7    1    3
  Never   87   18   84
  Occas   12    3    4
  Regul    9    1    7




Luego aplicamos el Test de Chi-cuadrado:

chisq.test(tbl_cont)

    Pearson's Chi-squared test

data:  tbl_cont
X-squared = 5.4885, df = 6, p-value = 0.4828

Análisis de Componentes Principales

data("iris")

#Tomo los datos y les quito la clase
iris.sin.clase <- iris[,-c(5)]
iris.escalado <- data.frame(scale(iris.sin.clase))

# Corro el análisis en CP
pca.iris <- princomp(iris.escalado, cor=F)
print(pca.iris)
Call:
princomp(x = iris.escalado, cor = F)

Standard deviations:
   Comp.1    Comp.2    Comp.3    Comp.4 
1.7026571 0.9528572 0.3818095 0.1434459 

 4  variables and  150 observations.

Análisis de Componentes Principales (++)

summary(pca.iris)
Importance of components:
                          Comp.1    Comp.2     Comp.3      Comp.4
Standard deviation     1.7026571 0.9528572 0.38180950 0.143445939
Proportion of Variance 0.7296245 0.2285076 0.03668922 0.005178709
Cumulative Proportion  0.7296245 0.9581321 0.99482129 1.000000000

Análisis de Componentes Principales (+++)

plot(pca.iris, type="l")

plot of chunk unnamed-chunk-44

Análisis de Componentes Principales (++++)

par(mfrow=c(1,2))
biplot(pca.iris)
biplot(pca.iris, choices = c(3,4))

plot of chunk unnamed-chunk-45

Análisis de Componentes Principales (+++++)

loadings(pca.iris)

Loadings:
             Comp.1 Comp.2 Comp.3 Comp.4
Sepal.Length  0.521  0.377  0.720  0.261
Sepal.Width  -0.269  0.923 -0.244 -0.124
Petal.Length  0.580        -0.142 -0.801
Petal.Width   0.565        -0.634  0.524

               Comp.1 Comp.2 Comp.3 Comp.4
SS loadings      1.00   1.00   1.00   1.00
Proportion Var   0.25   0.25   0.25   0.25
Cumulative Var   0.25   0.50   0.75   1.00

Transformación de datos

Las técnicas que vamos a trabajar en esta clase son las siguientes:

  • Discretización de datos
library(infotheo)

data("iris")

# Armo los bins según Igual frecuencia
bin_eq_freq <- discretize(iris$Sepal.Width,"equalfreq", 5)

# Armo los bins según Igual frecuencia
bin_eq_width <- discretize(iris$Sepal.Width,"equalwidth", 5)


Transformación e datos (++)

  • Normalización (Por ejemplo a través de scale)
valores.escalados <-scale(iris$Sepal.Width)

valores.zscore<-(iris$Sepal.Width-mean(iris$Sepal.Width))/sd(iris$Sepal.Width)

head(iris$Sepal.Width, n = 5)
[1] 3.5 3.0 3.2 3.1 3.6
head(valores.escalados, n = 5)
            [,1]
[1,]  1.01560199
[2,] -0.13153881
[3,]  0.32731751
[4,]  0.09788935
[5,]  1.24503015


Detección de Outliers mediante Z-Score

Otra variante es trabajar a través de la métrica de z-score:
Cálculo de Z-Score:

data<-ruidoso
data$zscore<-(data$Road_55dB-mean(data$Road_55dB))/sd(data$Road_55dB)
umbral<-2