Limpieza, Reducción de Dimensionalidad, Transformación e Integración de datos
Juan Manuel Fernandez
Bases de Datos Masivas - UNLu
Vamos a ver algunos tips para implementar las siguientes técnicas en R:
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
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
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
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
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
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)
Obtenemos los siguientes gráficos de densidad:
En limpieza de ruido, vamos a trabajar con Binning por:
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
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])
}
# 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")
Busquemos outliers en el atributo Road_55db del dataset ruidoso:
ruidoso=read.csv('ruidoso.txt')
data = ruidoso$Road_55dB
plot(sort(data, decreasing = FALSE))
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
Mediante boxplot podemos observar gráficamente la distribución de la variable:
Un criterio de detección de outliers podría ser eliminar los datos que se encuentran por fuera (abajo/arriba) de los “bigotes”.
Observemos gráficamente la distribución de la variable mediante boxplot:
boxplot(data)
Claramente, la distribución de la variable no es “normal”, o si? Muchas veces los outliers “esconden” la distribución real de un feature.
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
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
outliers_max<-as.numeric(cuantiles[3])+1.5*data.riq
print(outliers_max)
[1] 168825
plot(sort(data[data>outliers_min & data<outliers_max], decreasing = FALSE))
boxplot(sort(data[data>outliers_min & data<outliers_max], decreasing = FALSE))
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
plot(sort(data[data>outliers_min & data<outliers_max], decreasing = FALSE))
boxplot(sort(data[data>outliers_min & data<outliers_max], decreasing = FALSE))
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.
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
Además, como vimos antes, debemos tener en cuenta:
names(stock)
[1] "Cod" "stock"
names(stock)[1]="Codigo"
names(stock)
[1] "Codigo" "stock"
celsius=c(26,32)
fahrenheit=(celsius*1.8)+32
print(fahrenheit)
[1] 78.8 89.6
library(lubridate)
fechas <- c(as.Date("2011-06-26"), as.Date("2013-07-15"))
meses <- c(5, 8)
todos <- cbind(meses, month(fechas))
Vamos a ver algunos tips para implementar las siguientes técnicas en R:
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")
El gráfico de heatmap presenta información sobre la correlación entre las variables, con colores de referencia:
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
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.
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
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.
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
plot(pca.iris, type="l")
par(mfrow=c(1,2))
biplot(pca.iris)
biplot(pca.iris, choices = c(3,4))
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
Las técnicas que vamos a trabajar en esta clase son las siguientes:
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)
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
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