В пакете caret
есть функция preProcess
, в которой реализован ряд методов препроцессинга данных: “center”, “scale”, “BoxCox”, “YeoJohnson”, “expoTrans”, “range”, “knnImpute”, “bagImpute”, “medianImpute”, “pca”, “ica”, “spatialSign”, “zv”, “nzv”, “conditionalX”.
Из них “bagImpute”, “pca”, “ica”, “spatialSign” у меня не получилось использовать (например, “pca” и “ica” нужны для одновременного преобразования групп переменных, а не по одной), а что с “bagImpute” и “spatialSign” пошло не так я так и не понял…
Кроме того, из книги А.Б. Шипунова и др. “Наглядная статистика. Используем R!” я почерпнул еще несколько методов:
Ряд перечисленных в книге преобразований не работает с отрицательными значениям. Поэтому я сначала шкалирую переменную (scale(x)
), а затем добавляю 3, чтобы > 99% значений стали положительными, а затем применяю вышеозначенные дополнительные преобразования.
После всех трансформаций я снова провожу финальное шкалирование (scale(x)
).
Функция строит график типа barplot с нанесенными поверх него точками данных, а также на графике подписывается значение p-level
для теста shapiro.test(x)
, с помощью которого можно определить нормальность распределения трансформированных данных. Нулевая гипотеза этого теста заключается в том, что данные распледелены нормально (подписи красного цвета), а если p-level < 0.1
, то значит эта гипотеза отвергается и распределение нельзя считать нормальным (подписи серого цвета).
Как правило, метод BoxCox
справляется лучше всего с задачей.
compareTransformations = function(var.nonscaled){
require(caret)
require(ipred)
if (is.data.frame(var.nonscaled)) var.nonscaled = var.nonscaled[[1]]
if (is.matrix(var.nonscaled)) var.nonscaled = as.numeric(var.nonscaled)
var.scaled = scale(var.nonscaled)
len = length(var.nonscaled)
# different types of transformations
pp.list = c( "BoxCox", "YeoJohnson", "expoTrans", "range", "knnImpute", "medianImpute", "zv", "nzv", "conditionalX")
dt = data.frame(transform=rep("scaled",len), data=var.scaled)
for (pp in pp.list){
pp.obj = preProcess(x=data.frame(x=var.nonscaled), method=pp)
pp.res = predict(pp.obj, data.frame(x=var.nonscaled))
dt = rbind(dt, data.frame(transform=rep(pp,len),data=scale(pp.res[,1])))
}
# log
dt = rbind(dt, data.frame(transform=rep("log",len),data=scale(log(var.scaled+3))))
# sqrt
dt = rbind(dt, data.frame(transform=rep("sqrt",len),data=scale(sqrt(var.scaled+3))))
# 1/(data+1)
dt = rbind(dt, data.frame(transform=rep("1/(data+1)",len),data=scale(1/(var.scaled+1))))
# data^2
dt = rbind(dt, data.frame(transform=rep("data^2",len),data=scale(var.scaled^2)))
# logit
dt = rbind(dt, data.frame(transform=rep("logit",len),data=scale((var.scaled+3)/(1-(var.scaled+3)))))
# arcsin
dt = rbind(dt, data.frame(transform=rep("arcsin",len),data=scale(asin(scale(sqrt(var.scaled+3))/3))))
dt.sh = c()
dt.list = list()
for (pp in unique(dt[,1])){
dt.list[[pp]] = dt[dt[,1]==pp,2]
dt.sh = c(dt.sh,sprintf("%.3f",shapiro.test(dt.list[[pp]])$p.value))
}
dt.sh.col = numeric(length(dt.sh))
dt.sh.col[dt.sh>0.1] = "red"
dt.sh.col[dt.sh<=0.1] = "gray"
g = ggplot(dt,aes(x=transform, y=data)) +
geom_boxplot(fill="cyan", size=.9) +
geom_jitter(color="blue",alpha=0.3) +
theme_bw(base_size = 16) +
theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust=0.2)) +
geom_text(data=data.frame(), aes(x=1:length(unique(dt[,1])), y=rep(-4, length(unique(dt[,1]))), label=dt.sh),col=dt.sh.col) +
coord_cartesian(ylim=c(-5,5))
print(g)
dt.list
}
Вот пример использования функции. На входе - переменная, имеющая гамма-распределение.
set.seed(2016)
y = rgamma(500, 5)
transformedData = compareTransformations(y)
str(transformedData)
List of 16
$ scaled : num [1:500] -0.9695 -1.1105 0.0982 0.6961 1.0574 ...
$ BoxCox : num [1:500] -1.008 -1.227 0.255 0.786 1.071 ...
$ YeoJohnson : num [1:500] -1.019 -1.236 0.256 0.792 1.078 ...
$ expoTrans : num [1:500] -1.045 -1.25 0.253 0.817 1.112 ...
$ range : num [1:500] -0.9695 -1.1105 0.0982 0.6961 1.0574 ...
$ knnImpute : num [1:500] -0.9695 -1.1105 0.0982 0.6961 1.0574 ...
$ medianImpute: num [1:500] -0.9695 -1.1105 0.0982 0.6961 1.0574 ...
$ zv : num [1:500] -0.9695 -1.1105 0.0982 0.6961 1.0574 ...
$ nzv : num [1:500] -0.9695 -1.1105 0.0982 0.6961 1.0574 ...
$ conditionalX: num [1:500] -0.9695 -1.1105 0.0982 0.6961 1.0574 ...
$ log : num [1:500] -1.023 -1.242 0.261 0.798 1.081 ...
$ sqrt : num [1:500] -1.008 -1.187 0.182 0.758 1.084 ...
$ 1/(data+1) : num [1:500] 1.1121 -0.2703 0.059 0.0483 0.0449 ...
$ data^2 : num [1:500] -0.0343 0.139 -0.5837 -0.3032 0.0709 ...
$ logit : num [1:500] -0.578 -0.885 0.408 0.619 0.707 ...
$ arcsin : num [1:500] -0.989 -1.177 0.198 0.771 1.106 ...
А вот другой пример.
y = 1/rnorm(500, 40)*rf(500,40,5)
transformedData = compareTransformations(y)