Skip to content

Commit 3572405

Browse files
authored
Add log-ratio transforms (#213)
* Add log-ratio transforms * Update tests * Fix revert * Fix typo
1 parent d50d285 commit 3572405

File tree

10 files changed

+234
-0
lines changed

10 files changed

+234
-0
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ version = "1.16.0"
66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
88
CategoricalArrays = "324d7699-5711-5eae-9e2f-1d82baa6b597"
9+
CoDa = "5900dafe-f573-5c72-b367-76665857777b"
910
ColumnSelectors = "9cc86067-7e36-4c61-b350-1ac9833d277f"
1011
DataScienceTraits = "6cb2f572-2d2b-4ba6-bdb3-e710fa044d6c"
1112
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
@@ -23,6 +24,7 @@ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
2324
[compat]
2425
AbstractTrees = "0.4"
2526
CategoricalArrays = "0.10"
27+
CoDa = "1.2"
2628
ColumnSelectors = "0.1"
2729
DataScienceTraits = "0.1"
2830
Distributions = "0.25"

docs/src/transforms.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,24 @@ Closure
200200
Remainder
201201
```
202202

203+
## ALR
204+
205+
```@docs
206+
ALR
207+
```
208+
209+
## CLR
210+
211+
```@docs
212+
CLR
213+
```
214+
215+
## ILR
216+
217+
```@docs
218+
ILR
219+
```
220+
203221
## RowTable
204222

205223
```@docs

src/TableTransforms.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ using LinearAlgebra
1313
using DataScienceTraits
1414
using CategoricalArrays
1515
using Random
16+
using CoDa
1617

1718
using TransformsBase: Transform, Identity,
1819
using DataScienceTraits: SciType, Continuous, coerce
@@ -80,6 +81,9 @@ export
8081
ProjectionPursuit,
8182
Closure,
8283
Remainder,
84+
ALR,
85+
CLR,
86+
ILR,
8387
RowTable,
8488
ColTable,
8589
,

src/transforms.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ include("transforms/eigenanalysis.jl")
290290
include("transforms/projectionpursuit.jl")
291291
include("transforms/closure.jl")
292292
include("transforms/remainder.jl")
293+
include("transforms/logratio.jl")
293294
include("transforms/rowtable.jl")
294295
include("transforms/coltable.jl")
295296
include("transforms/parallel.jl")

src/transforms/logratio.jl

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# ------------------------------------------------------------------
2+
# Licensed under the MIT License. See LICENSE in the project root.
3+
# ------------------------------------------------------------------
4+
5+
"""
6+
LogRatio
7+
8+
Parent type of all log-ratio transforms.
9+
10+
See also [`ALR`](@ref), [`CLR`](@ref), [`ILR`](@ref).
11+
"""
12+
abstract type LogRatio <: StatelessFeatureTransform end
13+
14+
# log-ratio transform interface
15+
function refvar end
16+
function newvars end
17+
function oldvars end
18+
function applymatrix end
19+
function revertmatrix end
20+
21+
isrevertible(::Type{<:LogRatio}) = true
22+
23+
assertions(::LogRatio) = [SciTypeAssertion{Continuous}()]
24+
25+
function applyfeat(transform::LogRatio, feat, prep)
26+
cols = Tables.columns(feat)
27+
names = Tables.columnnames(cols) |> collect
28+
29+
# reference variable
30+
rvar = refvar(transform, names)
31+
@assert rvar names "invalid reference variable"
32+
rind = findfirst(==(rvar), names)
33+
34+
# permute columns if necessary
35+
perm = rind lastindex(names)
36+
pfeat = if perm
37+
popat!(names, rind)
38+
push!(names, rvar)
39+
feat |> Select(names)
40+
else
41+
feat
42+
end
43+
44+
# apply transform
45+
X = Tables.matrix(pfeat)
46+
Y = applymatrix(transform, X)
47+
48+
# new variable names
49+
newnames = newvars(transform, names)
50+
51+
# return same table type
52+
𝒯 = (; zip(newnames, eachcol(Y))...)
53+
newfeat = 𝒯 |> Tables.materializer(feat)
54+
55+
newfeat, (rvar, rind, perm)
56+
end
57+
58+
function revertfeat(transform::LogRatio, newfeat, fcache)
59+
cols = Tables.columns(newfeat)
60+
names = Tables.columnnames(cols)
61+
62+
# revert transform
63+
Y = Tables.matrix(newfeat)
64+
X = revertmatrix(transform, Y)
65+
66+
# retrieve cache
67+
rvar, rind, perm = fcache
68+
69+
# original variable names
70+
onames = oldvars(transform, names, rvar)
71+
72+
# revert the permutation if necessary
73+
if perm
74+
n = length(onames)
75+
inds = collect(1:(n - 1))
76+
insert!(inds, rind, n)
77+
onames = onames[inds]
78+
X = X[:, inds]
79+
end
80+
81+
# return same table type
82+
𝒯 = (; zip(onames, eachcol(X))...)
83+
𝒯 |> Tables.materializer(newfeat)
84+
end
85+
86+
87+
# ----------------
88+
# IMPLEMENTATIONS
89+
# ----------------
90+
91+
include("logratio/alr.jl")
92+
include("logratio/clr.jl")
93+
include("logratio/ilr.jl")

src/transforms/logratio/alr.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# ------------------------------------------------------------------
2+
# Licensed under the MIT License. See LICENSE in the project root.
3+
# ------------------------------------------------------------------
4+
5+
"""
6+
ALR([refvar])
7+
8+
Additive log-ratio transform.
9+
10+
Optionally, specify the reference variable `refvar` for the ratios.
11+
Default to the last column of the input table.
12+
"""
13+
struct ALR{T<:Union{Symbol,Nothing}} <: LogRatio
14+
refvar::T
15+
end
16+
17+
ALR() = ALR(nothing)
18+
19+
refvar(transform::ALR, names) = isnothing(transform.refvar) ? last(names) : transform.refvar
20+
21+
newvars(::ALR, names) = collect(names)[begin:(end - 1)]
22+
23+
oldvars(::ALR, names, rvar) = [collect(names); rvar]
24+
25+
applymatrix(::ALR, X) = mapslices(alr Composition, X, dims=2)
26+
27+
revertmatrix(::ALR, Y) = mapslices(components alrinv, Y, dims=2)

src/transforms/logratio/clr.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# ------------------------------------------------------------------
2+
# Licensed under the MIT License. See LICENSE in the project root.
3+
# ------------------------------------------------------------------
4+
5+
"""
6+
CLR()
7+
8+
Centered log-ratio transform.
9+
"""
10+
struct CLR <: LogRatio end
11+
12+
refvar(::CLR, names) = last(names)
13+
14+
newvars(::CLR, names) = collect(names)
15+
16+
oldvars(::CLR, names, rvar) = collect(names)
17+
18+
applymatrix(::CLR, X) = mapslices(clr Composition, X, dims=2)
19+
20+
revertmatrix(::CLR, Y) = mapslices(components clrinv, Y, dims=2)

src/transforms/logratio/ilr.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# ------------------------------------------------------------------
2+
# Licensed under the MIT License. See LICENSE in the project root.
3+
# ------------------------------------------------------------------
4+
5+
"""
6+
ILR([refvar])
7+
8+
Isometric log-ratio transform.
9+
10+
Optionally, specify the reference variable `refvar` for the ratios.
11+
Default to the last column of the input table.
12+
"""
13+
struct ILR{T<:Union{Symbol,Nothing}} <: LogRatio
14+
refvar::T
15+
end
16+
17+
ILR() = ILR(nothing)
18+
19+
refvar(transform::ILR, names) = isnothing(transform.refvar) ? last(names) : transform.refvar
20+
21+
newvars(::ILR, names) = collect(names)[begin:(end - 1)]
22+
23+
oldvars(::ILR, names, rvar) = [collect(names); rvar]
24+
25+
applymatrix(::ILR, X) = mapslices(ilr Composition, X, dims=2)
26+
27+
revertmatrix(::ILR, Y) = mapslices(components ilrinv, Y, dims=2)

test/transforms.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ transformfiles = [
2626
"projectionpursuit.jl",
2727
"closure.jl",
2828
"remainder.jl",
29+
"logratio.jl",
2930
"rowtable.jl",
3031
"coltable.jl",
3132
"sequential.jl",

test/transforms/logratio.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
@testset "LogRatio" begin
2+
@test isrevertible(ALR())
3+
@test isrevertible(CLR())
4+
@test isrevertible(ILR())
5+
6+
a = [1.0, 0.0, 1.0]
7+
b = [2.0, 2.0, 2.0]
8+
c = [3.0, 3.0, 0.0]
9+
t = Table(; a, b, c)
10+
11+
T = ALR()
12+
n, c = apply(T, t)
13+
@test n == t |> ALR(:c)
14+
talr = revert(T, n, c)
15+
T = CLR()
16+
n, c = apply(T, t)
17+
tclr = revert(T, n, c)
18+
T = ILR()
19+
n, c = apply(T, t)
20+
@test n == t |> ILR(:c)
21+
tilr = revert(T, n, c)
22+
@test Tables.matrix(talr) Tables.matrix(tclr)
23+
@test Tables.matrix(tclr) Tables.matrix(tilr)
24+
@test Tables.matrix(talr) Tables.matrix(tilr)
25+
26+
# permute columns
27+
a = [1.0, 0.0, 1.0]
28+
b = [2.0, 2.0, 2.0]
29+
c = [3.0, 3.0, 0.0]
30+
t1 = Table(; a, c, b)
31+
t2 = Table(; c, a, b)
32+
33+
T = ALR(:c)
34+
n1, c1 = apply(T, t1)
35+
n2, c2 = apply(T, t2)
36+
@test n1 == n2
37+
tₒ = revert(T, n1, c1)
38+
@test Tables.schema(tₒ).names == (:a, :c, :b)
39+
tₒ = revert(T, n2, c2)
40+
@test Tables.schema(tₒ).names == (:c, :a, :b)
41+
end

0 commit comments

Comments
 (0)