diff --git a/src/transformations.jl b/src/transformations.jl index 4d15353ad..c3967fc19 100644 --- a/src/transformations.jl +++ b/src/transformations.jl @@ -8,63 +8,72 @@ abstract type AbstractDataTransform end Apply transformation `t` to vector or matrix `x` in place. """ -transform!(t::AbstractDataTransform, x::AbstractArray{<:Real,1}) = transform!(x, t, x) -transform!(t::AbstractDataTransform, x::AbstractArray{<:Real,2}) = transform!(x, t, x) +transform!(t::AbstractDataTransform, x::AbstractMatrix{<:Real}) = + transform!(x, t, x) +transform!(t::AbstractDataTransform, x::AbstractVector{<:Real}) = + (transform!(t, reshape(x, :, 1)); x) """ transform(t::AbstractDataTransform, x) -Return a row-standardized vector or matrix `x` using `t` transformation. +Return a standardized vector or matrix `x` using `t` transformation. """ -transform(t::AbstractDataTransform, x::AbstractArray{<:Real,1}) = transform!(similar(x), t, x) -transform(t::AbstractDataTransform, x::AbstractArray{<:Real,2}) = transform!(similar(x), t, x) +transform(t::AbstractDataTransform, x::AbstractMatrix{<:Real}) = + transform!(similar(x), t, x) +transform(t::AbstractDataTransform, x::AbstractVector{<:Real}) = + vec(transform(t, reshape(x, :, 1))) # reconstruct the original data from transformed values """ reconstruct!(t::AbstractDataTransform, y) -Perform an in-place reconstruction into an original data scale from a row-transformed +Perform an in-place reconstruction into an original data scale from a transformed vector or matrix `y` using `t` transformation. """ -reconstruct!(t::AbstractDataTransform, y::AbstractArray{<:Real,1}) = reconstruct!(y, t, y) -reconstruct!(t::AbstractDataTransform, y::AbstractArray{<:Real,2}) = reconstruct!(y, t, y) +reconstruct!(t::AbstractDataTransform, y::AbstractMatrix{<:Real}) = + reconstruct!(y, t, y) +reconstruct!(t::AbstractDataTransform, y::AbstractVector{<:Real}) = + (reconstruct!(t, reshape(y, :, 1)); y) """ reconstruct(t::AbstractDataTransform, y) -Return a reconstruction of an originally scaled data from a row-transformed vector +Return a reconstruction of an originally scaled data from a transformed vector or matrix `y` using `t` transformation. """ -reconstruct(t::AbstractDataTransform, y::AbstractArray{<:Real,1}) = reconstruct!(similar(y), t, y) -reconstruct(t::AbstractDataTransform, y::AbstractArray{<:Real,2}) = reconstruct!(similar(y), t, y) +reconstruct(t::AbstractDataTransform, y::AbstractMatrix{<:Real}) = + reconstruct!(similar(y), t, y) +reconstruct(t::AbstractDataTransform, y::AbstractVector{<:Real}) = + vec(reconstruct(t, reshape(y, :, 1))) """ Standardization (Z-score transformation) """ struct ZScoreTransform{T<:Real} <: AbstractDataTransform - dim::Int + len::Int + dims::Int mean::Vector{T} scale::Vector{T} - function ZScoreTransform(d::Int, m::Vector{T}, s::Vector{T}) where T + function ZScoreTransform(l::Int, dims::Int, m::Vector{T}, s::Vector{T}) where T lenm = length(m) lens = length(s) - lenm == d || lenm == 0 || throw(DimensionMismatch("Inconsistent dimensions.")) - lens == d || lens == 0 || throw(DimensionMismatch("Inconsistent dimensions.")) - new{T}(d, m, s) + lenm == l || lenm == 0 || throw(DimensionMismatch("Inconsistent dimensions.")) + lens == l || lens == 0 || throw(DimensionMismatch("Inconsistent dimensions.")) + new{T}(l, dims, m, s) end end function Base.getproperty(t::ZScoreTransform, p::Symbol) if p === :indim || p === :outdim - return t.dim + return t.len else return getfield(t, p) end end """ - fit(ZScoreTransform, X; center=true, scale=true) + fit(ZScoreTransform, X; dims=nothing, center=true, scale=true) Fit standardization parameters to `X` and return a `ZScoreTransform` transformation object. @@ -74,6 +83,9 @@ Fit standardization parameters to `X` and return a `ZScoreTransform` transformat # Keyword arguments +* `dims`: if `1` fit standardization parameters in column-wise fashion; + if `2` fit in row-wise fashion. The default is `nothing`, which is equivalent to `dims=2` with a deprecation warning. + * `center`: if `true` (the default) center data so that its mean is zero. * `scale`: if `true` (the default) scale the data so that its variance is equal to one. @@ -88,7 +100,7 @@ julia> X = [0.0 -0.5 0.5; 0.0 1.0 2.0] 0.0 -0.5 0.5 0.0 1.0 2.0 -julia> dt = fit(ZScoreTransform, X) +julia> dt = fit(ZScoreTransform, X, dims=2) ZScoreTransform{Float64}(2, [0.0, 1.0], [0.5, 1.0]) julia> StatsBase.transform(dt, X) @@ -97,67 +109,101 @@ julia> StatsBase.transform(dt, X) -1.0 0.0 1.0 ``` """ -function fit(::Type{ZScoreTransform}, X::AbstractArray{<:Real,2}; center::Bool=true, scale::Bool=true) - d, n = size(X) - n >= 2 || error("X must contain at least two columns.") - +function fit(::Type{ZScoreTransform}, X::AbstractMatrix{<:Real}; + dims::Union{Integer,Nothing}=nothing, center::Bool=true, scale::Bool=true) + if dims == 1 + n, l = size(X) + n >= 2 || error("X must contain at least two rows.") + m, s = mean_and_std(X, 1) + elseif dims == 2 + l, n = size(X) + n >= 2 || error("X must contain at least two columns.") + m, s = mean_and_std(X, 2) + elseif dims === nothing + Base.depwarn("fit(t, x) is deprecated: use fit(t, x, dims=2) instead", :fit) + m, s = mean_and_std(X, 2) + else + throw(DomainError(dims, "fit only accept dims to be 1 or 2.")) + end T = eltype(X) - m, s = mean_and_std(X, 2) - - return ZScoreTransform(d, (center ? vec(m) : zeros(T, 0)), - (scale ? vec(s) : zeros(T, 0))) + return ZScoreTransform(l, dims, (center ? vec(m) : zeros(T, 0)), + (scale ? vec(s) : zeros(T, 0))) end -function transform!(y::AbstractVecOrMat{<:Real}, t::ZScoreTransform, x::AbstractVecOrMat{<:Real}) - d = t.dim - size(x,1) == size(y,1) == d || throw(DimensionMismatch("Inconsistent dimensions.")) - n = size(y,2) - size(x,2) == n || throw(DimensionMismatch("Inconsistent dimensions.")) +function fit(::Type{ZScoreTransform}, X::AbstractVector{<:Real}; + dims::Union{Integer,Nothing}=nothing, center::Bool=true, scale::Bool=true) + if dims == nothing + Base.depwarn("fit(t, x) is deprecated: use fit(t, x, dims=2) instead", :fit) + elseif dims != 1 + throw(DomainError(dims, "fit only accepts dims=1 over a vector. Try fit(t, x, dims=1).")) + end - m = t.mean - s = t.scale + T = eltype(X) + m, s = mean_and_std(X) + return ZScoreTransform(1, dims, (center ? [m] : zeros(T, 0)), + (scale ? [s] : zeros(T, 0))) +end - if isempty(m) - if isempty(s) - if x !== y - copyto!(y, x) +function transform!(y::AbstractMatrix{<:Real}, t::ZScoreTransform, x::AbstractMatrix{<:Real}) + if t.dims == 1 + l = t.len + size(x,2) == size(y,2) == l || throw(DimensionMismatch("Inconsistent dimensions.")) + n = size(y,1) + size(x,1) == n || throw(DimensionMismatch("Inconsistent dimensions.")) + + m = t.mean + s = t.scale + + if isempty(m) + if isempty(s) + if x !== y + copyto!(y, x) + end + else + broadcast!(/, y, x, s') end else - broadcast!(/, y, x, s) - end - else - if isempty(s) - broadcast!(-, y, x, m) - else - broadcast!((x,m,s)->(x-m)/s, y, x, m, s) + if isempty(s) + broadcast!(-, y, x, m') + else + broadcast!((x,m,s)->(x-m)/s, y, x, m', s') + end end + elseif t.dims == 2 + t_ = ZScoreTransform(t.len, 1, t.mean, t.scale) + transform!(y', t_, x') end return y end -function reconstruct!(x::AbstractVecOrMat{<:Real}, t::ZScoreTransform, y::AbstractVecOrMat{<:Real}) - d = t.dim - size(x,1) == size(y,1) == d || throw(DimensionMismatch("Inconsistent dimensions.")) - n = size(y,2) - size(x,2) == n || throw(DimensionMismatch("Inconsistent dimensions.")) - - m = t.mean - s = t.scale - - if isempty(m) - if isempty(s) - if y !== x - copyto!(x, y) +function reconstruct!(x::AbstractMatrix{<:Real}, t::ZScoreTransform, y::AbstractMatrix{<:Real}) + if t.dims == 1 + l = t.len + size(x,2) == size(y,2) == l || throw(DimensionMismatch("Inconsistent dimensions.")) + n = size(y,1) + size(x,1) == n || throw(DimensionMismatch("Inconsistent dimensions.")) + + m = t.mean + s = t.scale + + if isempty(m) + if isempty(s) + if y !== x + copyto!(x, y) + end + else + broadcast!(*, x, y, s') end else - broadcast!(*, x, y, s) - end - else - if isempty(s) - broadcast!(+, x, y, m) - else - broadcast!((y,m,s)->y*s+m, x, y, m, s) + if isempty(s) + broadcast!(+, x, y, m') + else + broadcast!((y,m,s)->y*s+m, x, y, m', s') + end end + elseif t.dims == 2 + t_ = ZScoreTransform(t.len, 1, t.mean, t.scale) + reconstruct!(x', t_, y') end return x end @@ -166,23 +212,24 @@ end Unit range normalization """ struct UnitRangeTransform{T<:Real} <: AbstractDataTransform - dim::Int + len::Int + dims::Int unit::Bool min::Vector{T} scale::Vector{T} - function UnitRangeTransform(d::Int, unit::Bool, min::Vector{T}, max::Vector{T}) where {T} + function UnitRangeTransform(l::Int, dims::Int, unit::Bool, min::Vector{T}, max::Vector{T}) where {T} lenmin = length(min) lenmax = length(max) - lenmin == d || lenmin == 0 || throw(DimensionMismatch("Inconsistent dimensions.")) - lenmax == d || lenmax == 0 || throw(DimensionMismatch("Inconsistent dimensions.")) - new{T}(d, unit, min, max) + lenmin == l || lenmin == 0 || throw(DimensionMismatch("Inconsistent dimensions.")) + lenmax == l || lenmax == 0 || throw(DimensionMismatch("Inconsistent dimensions.")) + new{T}(l, dims, unit, min, max) end end function Base.getproperty(t::UnitRangeTransform, p::Symbol) if p === :indim || p === :outdim - return t.dim + return t.len else return getfield(t, p) end @@ -190,7 +237,7 @@ end # fit a unit transform """ - fit(UnitRangeTransform, X; center=true, scale=true) + fit(UnitRangeTransform, X; dims=nothing, unit=true) Fit a scaling parameters to `X` and return transformation description. @@ -200,9 +247,10 @@ Fit a scaling parameters to `X` and return transformation description. # Keyword arguments -* `center`: if `true` (the default) centere data around zero. +* `dims`: if `1` fit standardization parameters in column-wise fashion; + if `2` fit in row-wise fashion. The default is `nothing`. -* `scale`: if `true` (the default) perform variance scaling. +* `unit`: if `true` (the default) shift the minimum data to zero. # Examples @@ -214,7 +262,7 @@ julia> X = [0.0 -0.5 0.5; 0.0 1.0 2.0] 0.0 -0.5 0.5 0.0 1.0 2.0 -julia> dt = fit(UnitRangeTransform, X) +julia> dt = fit(UnitRangeTransform, X, dims=2) UnitRangeTransform{Float64}(2, true, [-0.5, 0.0], [1.0, 0.5]) julia> StatsBase.transform(dt, X) @@ -223,64 +271,101 @@ julia> StatsBase.transform(dt, X) 0.0 0.5 1.0 ``` """ -function fit(::Type{UnitRangeTransform}, X::AbstractArray{<:Real,2}; unit::Bool=true) - d, n = size(X) - - tmin = X[:, 1] - tmax = X[:, 1] - for j = 2:n - @inbounds for i = 1:d - if X[i, j] < tmin[i] - tmin[i] = X[i, j] - elseif X[i, j] > tmax[i] - tmax[i] = X[i, j] +function fit(::Type{UnitRangeTransform}, X::AbstractMatrix{<:Real}; + dims::Union{Integer,Nothing}=nothing, unit::Bool=true) + if dims == 1 + l, tmin, tmax = _compute_extrema(X) + elseif dims == 2 + l, tmin, tmax = _compute_extrema(X') + elseif dims == nothing + Base.depwarn("fit(t, x) is deprecated: use fit(t, x, dims=2) instead", :fit) + l, tmin, tmax = _compute_extrema(X') + else + throw(DomainError(dims, "fit only accept dims to be 1 or 2.")) + end + + for i = 1:l + @inbounds tmax[i] = 1 / (tmax[i] - tmin[i]) + end + return UnitRangeTransform(l, dims, unit, tmin, tmax) +end + +function _compute_extrema(X::AbstractMatrix{<:Real}) + n, l = size(X) + tmin = X[1, :] + tmax = X[1, :] + for j = 1:l + @inbounds for i = 2:n + if X[i, j] < tmin[j] + tmin[j] = X[i, j] + elseif X[i, j] > tmax[j] + tmax[j] = X[i, j] end end end - for i = 1:d - @inbounds tmax[i] = 1 / (tmax[i] - tmin[i]) + return l, tmin, tmax +end + +function fit(::Type{UnitRangeTransform}, X::AbstractVector{<:Real}; + dims::Union{Integer,Nothing}=nothing, unit::Bool=true) + if dims != 1 + throw(DomainError(dims, "fit only accept dims=1 over a vector. Try fit(t, x, dims=1).")) end - return UnitRangeTransform(d, unit, tmin, tmax) + + l, tmin, tmax = _compute_extrema(reshape(X, :, 1)) + tmax = 1 / (tmax - tmin) + return UnitRangeTransform(1, dims, unit, vec(tmin), vec(tmax)) end -function transform!(y::AbstractVecOrMat{<:Real}, t::UnitRangeTransform, x::AbstractVecOrMat{<:Real}) - d = t.dim - size(x,1) == size(y,1) == d || throw(DimensionMismatch("Inconsistent dimensions.")) - n = size(x,2) - size(y,2) == n || throw(DimensionMismatch("Inconsistent dimensions.")) +function transform!(y::AbstractMatrix{<:Real}, t::UnitRangeTransform, x::AbstractMatrix{<:Real}) + if t.dims == 1 + l = t.len + size(x,2) == size(y,2) == l || throw(DimensionMismatch("Inconsistent dimensions.")) + n = size(x,1) + size(y,1) == n || throw(DimensionMismatch("Inconsistent dimensions.")) - tmin = t.min - tscale = t.scale + tmin = t.min + tscale = t.scale - if t.unit - broadcast!((x,s,m) -> (x-m)*s, y, x, tscale, tmin) - else - broadcast!(*, y, x, tscale) + if t.unit + broadcast!((x,s,m)->(x-m)*s, y, x, tscale', tmin') + else + broadcast!(*, y, x, tscale') + end + elseif t.dims == 2 + t_ = UnitRangeTransform(t.len, 1, t.unit, t.min, t.scale) + transform!(y', t_, x') end return y end -function reconstruct!(x::AbstractVecOrMat{<:Real}, t::UnitRangeTransform, y::AbstractVecOrMat{<:Real}) - d = t.dim - size(x,1) == size(y,1) == d || throw(DimensionMismatch("Inconsistent dimensions.")) - n = size(y,2) - size(x,2) == n || throw(DimensionMismatch("Inconsistent dimensions.")) +function reconstruct!(x::AbstractMatrix{<:Real}, t::UnitRangeTransform, y::AbstractMatrix{<:Real}) + if t.dims == 1 + l = t.len + size(x,2) == size(y,2) == l || throw(DimensionMismatch("Inconsistent dimensions.")) + n = size(y,1) + size(x,1) == n || throw(DimensionMismatch("Inconsistent dimensions.")) - tmin = t.min - tscale = t.scale + tmin = t.min + tscale = t.scale - if t.unit - broadcast!((y,s,m) -> y/s + m, x, y, tscale, tmin) - else - broadcast!(/, x, y, tscale) + if t.unit + broadcast!((y,s,m)->y/s+m, x, y, tscale', tmin') + else + broadcast!(/, x, y, tscale') + end + elseif t.dims == 2 + t_ = UnitRangeTransform(t.len, 1, t.unit, t.min, t.scale) + reconstruct!(x', t_, y') end return x end """ - standardize(DT, X; kwargs...) + standardize(DT, X; dims=nothing, kwargs...) -Return a row-standardized matrix `X` using `DT` transformation which is a subtype of `AbstractDataTransform`: + Return a standardized copy of vector or matrix `X` along dimensions `dims` + using transformation `DT` which is a subtype of `AbstractDataTransform`: - `ZScoreTransform` - `UnitRangeTransform` @@ -290,17 +375,17 @@ Return a row-standardized matrix `X` using `DT` transformation which is a subtyp ```jldoctest julia> using StatsBase -julia> standardize(ZScoreTransform, [0.0 -0.5 0.5; 0.0 1.0 2.0]) +julia> standardize(ZScoreTransform, [0.0 -0.5 0.5; 0.0 1.0 2.0], dims=2) 2×3 Array{Float64,2}: 0.0 -1.0 1.0 -1.0 0.0 1.0 -julia> standardize(UnitRangeTransform, [0.0 -0.5 0.5; 0.0 1.0 2.0]) +julia> standardize(UnitRangeTransform, [0.0 -0.5 0.5; 0.0 1.0 2.0], dims=2) 2×3 Array{Float64,2}: 0.5 0.0 1.0 0.0 0.5 1.0 ``` """ -function standardize(::Type{DT}, X::AbstractArray{<:Real,2}; kwargs...) where {DT<:AbstractDataTransform} +function standardize(::Type{DT}, X::AbstractVecOrMat{<:Real}; kwargs...) where {DT <: AbstractDataTransform} return transform(fit(DT, X; kwargs...), X) end diff --git a/test/transformations.jl b/test/transformations.jl index 393fb27e1..7d8e2b0a9 100644 --- a/test/transformations.jl +++ b/test/transformations.jl @@ -1,66 +1,182 @@ using StatsBase -import StatsBase: transform, reconstruct +import StatsBase: transform, reconstruct, transform!, reconstruct! using Statistics using Test @testset "Transformations" begin + # matrix X = rand(5, 8) + X_ = copy(X) - t = fit(ZScoreTransform, X; center=false, scale=false) + t = fit(ZScoreTransform, X, dims=1, center=false, scale=false) Y = transform(t, X) @test isa(t, AbstractDataTransform) @test isempty(t.mean) @test isempty(t.scale) @test isequal(X, Y) - @test transform(t, X[:,1]) ≈ Y[:,1] - @test reconstruct(t, Y[:,1]) ≈ X[:,1] @test reconstruct(t, Y) ≈ X + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ - t = fit(ZScoreTransform, X; center=false) + X = copy(X_) + t = fit(ZScoreTransform, X, dims=1, center=false) Y = transform(t, X) @test isempty(t.mean) - @test length(t.scale) == 5 - @test Y ≈ X ./ std(X, dims=2) - @test transform(t, X[:,1]) ≈ Y[:,1] - @test reconstruct(t, Y[:,1]) ≈ X[:,1] + @test length(t.scale) == 8 + @test Y ≈ X ./ std(X, dims=1) @test reconstruct(t, Y) ≈ X + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ - t = fit(ZScoreTransform, X; scale=false) + X = copy(X_) + t = fit(ZScoreTransform, X, dims=1, scale=false) Y = transform(t, X) - @test length(t.mean) == 5 + @test length(t.mean) == 8 @test isempty(t.scale) - @test Y ≈ X .- mean(X, dims=2) - @test transform(t, X[:,1]) ≈ Y[:,1] - @test reconstruct(t, Y[:,1]) ≈ X[:,1] + @test Y ≈ X .- mean(X, dims=1) @test reconstruct(t, Y) ≈ X + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ - t = fit(ZScoreTransform, X) + X = copy(X_) + t = fit(ZScoreTransform, X, dims=1) + Y = transform(t, X) + @test length(t.mean) == 8 + @test length(t.scale) == 8 + @test Y ≈ (X .- mean(X, dims=1)) ./ std(X, dims=1) + @test reconstruct(t, Y) ≈ X + @test Y ≈ standardize(ZScoreTransform, X, dims=1) + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ + + X = copy(X_) + t = fit(ZScoreTransform, X, dims=2) Y = transform(t, X) @test length(t.mean) == 5 @test length(t.scale) == 5 @test Y ≈ (X .- mean(X, dims=2)) ./ std(X, dims=2) - @test transform(t, X[:,1]) ≈ Y[:,1] - @test reconstruct(t, Y[:,1]) ≈ X[:,1] @test reconstruct(t, Y) ≈ X + @test Y ≈ standardize(ZScoreTransform, X, dims=2) + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ + + X = copy(X_) + t = fit(UnitRangeTransform, X, dims=1, unit=false) + Y = transform(t, X) + @test length(t.min) == 8 + @test length(t.scale) == 8 + @test Y ≈ X ./ (maximum(X, dims=1) .- minimum(X, dims=1)) + @test reconstruct(t, Y) ≈ X + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ - t = fit(UnitRangeTransform, X) + X = copy(X_) + t = fit(UnitRangeTransform, X, dims=1) + Y = transform(t, X) + @test isa(t, AbstractDataTransform) + @test length(t.min) == 8 + @test length(t.scale) == 8 + @test Y ≈ (X .- minimum(X, dims=1)) ./ (maximum(X, dims=1) .- minimum(X, dims=1)) + @test reconstruct(t, Y) ≈ X + @test Y ≈ standardize(UnitRangeTransform, X, dims=1) + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ + + X = copy(X_) + t = fit(UnitRangeTransform, X, dims=2) Y = transform(t, X) @test isa(t, AbstractDataTransform) @test length(t.min) == 5 @test length(t.scale) == 5 @test Y ≈ (X .- minimum(X, dims=2)) ./ (maximum(X, dims=2) .- minimum(X, dims=2)) - @test transform(t, X[:,1]) ≈ Y[:,1] - @test reconstruct(t, Y[:,1]) ≈ X[:,1] @test reconstruct(t, Y) ≈ X + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ - t = fit(UnitRangeTransform, X; unit=false) + # vector + X = rand(10) + X_ = copy(X) + + t = fit(ZScoreTransform, X, dims=1, center=false, scale=false) Y = transform(t, X) - @test length(t.min) == 5 - @test length(t.scale) == 5 - @test Y ≈ X ./ (maximum(X, dims=2) .- minimum(X, dims=2)) - @test transform(t, X[:,1]) ≈ Y[:,1] - @test reconstruct(t, Y[:,1]) ≈ X[:,1] + @test transform(t, X) ≈ Y + @test reconstruct(t, Y) ≈ X + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ + + X = copy(X_) + t = fit(ZScoreTransform, X, dims=1, center=false) + Y = transform(t, X) + @test Y ≈ X ./ std(X, dims=1) + @test transform(t, X) ≈ Y + @test reconstruct(t, Y) ≈ X + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ + + X = copy(X_) + t = fit(ZScoreTransform, X, dims=1, scale=false) + Y = transform(t, X) + @test Y ≈ X .- mean(X, dims=1) + @test transform(t, X) ≈ Y + @test reconstruct(t, Y) ≈ X + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ + + X = copy(X_) + t = fit(ZScoreTransform, X, dims=1) + Y = transform(t, X) + @test Y ≈ (X .- mean(X, dims=1)) ./ std(X, dims=1) + @test transform(t, X) ≈ Y + @test reconstruct(t, Y) ≈ X + @test Y ≈ standardize(ZScoreTransform, X, dims=1) + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ + + X = copy(X_) + t = fit(UnitRangeTransform, X, dims=1) + Y = transform(t, X) + @test Y ≈ (X .- minimum(X, dims=1)) ./ (maximum(X, dims=1) .- minimum(X, dims=1)) + @test transform(t, X) ≈ Y @test reconstruct(t, Y) ≈ X + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ + + X = copy(X_) + t = fit(UnitRangeTransform, X, dims=1, unit=false) + Y = transform(t, X) + @test Y ≈ X ./ (maximum(X, dims=1) .- minimum(X, dims=1)) + @test transform(t, X) ≈ Y + @test reconstruct(t, Y) ≈ X + @test Y ≈ standardize(UnitRangeTransform, X, dims=1, unit=false) + @test transform!(t, X) === X + @test isequal(X, Y) + @test reconstruct!(t, Y) === Y + @test Y ≈ X_ - @test Y == standardize(UnitRangeTransform, X; unit=false) end