Skip to content

Commit fe6e7de

Browse files
authored
Add AbsoluteUnits transform (#207)
* Add 'AbsoluteUnits' transform * Fix typo * Fix code * Fix tests
1 parent b72a52a commit fe6e7de

File tree

7 files changed

+272
-3
lines changed

7 files changed

+272
-3
lines changed

docs/src/transforms.md

+6
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ DropExtrema
6262
DropUnits
6363
```
6464

65+
## AbsoluteUnits
66+
67+
```@docs
68+
AbsoluteUnits
69+
```
70+
6571
## Map
6672

6773
```@docs

src/TableTransforms.jl

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ using CategoricalArrays
1919
using Random
2020
using NelderMead: optimise
2121

22-
using Unitful: AbstractQuantity
22+
using Unitful: AbstractQuantity, AffineQuantity, AffineUnits, Units
2323

2424
import Distributions: ContinuousUnivariateDistribution
2525
import Distributions: quantile, cdf
@@ -58,6 +58,7 @@ export
5858
DropMissing,
5959
DropExtrema,
6060
DropUnits,
61+
AbsoluteUnits,
6162
Map,
6263
Replace,
6364
Coalesce,

src/transforms.jl

+1
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ include("transforms/filter.jl")
273273
include("transforms/dropmissing.jl")
274274
include("transforms/dropextrema.jl")
275275
include("transforms/dropunits.jl")
276+
include("transforms/absoluteunits.jl")
276277
include("transforms/map.jl")
277278
include("transforms/replace.jl")
278279
include("transforms/coalesce.jl")

src/transforms/absoluteunits.jl

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# ------------------------------------------------------------------
2+
# Licensed under the MIT License. See LICENSE in the project root.
3+
# ------------------------------------------------------------------
4+
5+
"""
6+
AbsoluteUnits()
7+
AbsoluteUnits(:)
8+
9+
Converts the units of all columns in the table to absolute units.
10+
11+
AbsoluteUnits(col₁, col₂, ..., colₙ)
12+
AbsoluteUnits([col₁, col₂, ..., colₙ])
13+
AbsoluteUnits((col₁, col₂, ..., colₙ))
14+
15+
Converts the units of selected columns `col₁`, `col₂`, ..., `colₙ` to absolute units.
16+
17+
AbsoluteUnits(regex)
18+
19+
Converts the units of columns that match with `regex` to absolute units.
20+
21+
# Examples
22+
23+
```julia
24+
AbsoluteUnits()
25+
AbsoluteUnits([2, 3, 5])
26+
AbsoluteUnits([:b, :c, :e])
27+
AbsoluteUnits(("b", "c", "e"))
28+
AbsoluteUnits(r"[bce]")
29+
```
30+
"""
31+
struct AbsoluteUnits{S<:ColSpec} <: StatelessFeatureTransform
32+
colspec::S
33+
end
34+
35+
AbsoluteUnits() = AbsoluteUnits(AllSpec())
36+
AbsoluteUnits(spec) = AbsoluteUnits(colspec(spec))
37+
AbsoluteUnits(cols::T...) where {T<:Col} = AbsoluteUnits(colspec(cols))
38+
39+
isrevertible(::Type{<:AbsoluteUnits}) = true
40+
41+
_absunit(x) = _absunit(x, nonmissingtype(eltype(x)))
42+
_absunit(x, ::Type) = (x, NoUnits)
43+
_absunit(x, ::Type{Q}) where {Q<:AbstractQuantity} = (x, unit(Q))
44+
function _absunit(x, ::Type{Q}) where {Q<:AffineQuantity}
45+
u = unit(Q)
46+
a = absoluteunit(u)
47+
y = map(v -> uconvert(a, v), x)
48+
(y, u)
49+
end
50+
51+
function applyfeat(transform::AbsoluteUnits, feat, prep)
52+
cols = Tables.columns(feat)
53+
names = Tables.columnnames(cols)
54+
snames = choose(transform.colspec, names)
55+
56+
tuples = map(names) do name
57+
x = Tables.getcolumn(cols, name)
58+
name snames ? _absunit(x) : (x, NoUnits)
59+
end
60+
61+
columns = first.(tuples)
62+
units = last.(tuples)
63+
64+
𝒯 = (; zip(names, columns)...)
65+
newfeat = 𝒯 |> Tables.materializer(feat)
66+
newfeat, (snames, units)
67+
end
68+
69+
_revunit(x, ::Units) = x
70+
_revunit(x, u::AffineUnits) = map(v -> uconvert(u, v), x)
71+
72+
function revertfeat(::AbsoluteUnits, newfeat, fcache)
73+
cols = Tables.columns(newfeat)
74+
names = Tables.columnnames(cols)
75+
76+
snames, units = fcache
77+
columns = map(names, units) do name, unit
78+
x = Tables.getcolumn(cols, name)
79+
name snames ? _revunit(x, unit) : x
80+
end
81+
82+
𝒯 = (; zip(names, columns)...)
83+
𝒯 |> Tables.materializer(newfeat)
84+
end

src/transforms/dropunits.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
DropUnits()
77
DropUnits(:)
88
9-
Drop units from all column in the table.
9+
Drop units from all columns in the table.
1010
1111
DropUnits(col₁, col₂, ..., colₙ)
1212
DropUnits([col₁, col₂, ..., colₙ])
@@ -61,7 +61,7 @@ function applyfeat(transform::DropUnits, feat, prep)
6161
end
6262

6363
_addunit(x, ::typeof(NoUnits)) = x
64-
_addunit(x, unit) = [v * unit for v in x]
64+
_addunit(x, u::Units) = map(v -> v * u, x)
6565

6666
function revertfeat(::DropUnits, newfeat, fcache)
6767
cols = Tables.columns(newfeat)

test/transforms.jl

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ transformfiles = [
88
"dropmissing.jl",
99
"dropextrema.jl",
1010
"dropunits.jl",
11+
"absoluteunits.jl",
1112
"map.jl",
1213
"replace.jl",
1314
"coalesce.jl",

test/transforms/absoluteunits.jl

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
@testset "AbsoluteUnits" begin
2+
@test isrevertible(AbsoluteUnits())
3+
4+
a = [7, 4, 4, 7, 4, 1, 1, 6, 4, 7] * u"°C"
5+
b = [4, 5, 4, missing, 6, 6, missing, 4, 4, 1] * u"K"
6+
c = [3.9, 3.8, 3.5, 6.5, 7.7, 1.5, 0.6, 5.7, 4.7, 4.8] * u"K"
7+
d = [6.3, 4.7, 7.6, missing, 1.2, missing, 5.9, 0.2, 1.9, 4.2] * u"°C"
8+
e = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
9+
t = Table(; a, b, c, d, e)
10+
11+
T = AbsoluteUnits()
12+
n, c = apply(T, t)
13+
@test unit(eltype(n.a)) === u"K"
14+
@test unit(nonmissingtype(eltype(n.b))) === u"K"
15+
@test unit(eltype(n.c)) === u"K"
16+
@test unit(nonmissingtype(eltype(n.d))) === u"K"
17+
@test eltype(n.e) === String
18+
@test n.e == t.e
19+
tₒ = revert(T, n, c)
20+
@test t.a == tₒ.a
21+
@test isequal(t.b, tₒ.b)
22+
@test t.c == tₒ.c
23+
@test all(isapprox.(skipmissing(t.d), skipmissing(tₒ.d)))
24+
@test t.e == tₒ.e
25+
26+
# args...
27+
# integers
28+
T = AbsoluteUnits(1, 2)
29+
n, c = apply(T, t)
30+
@test unit(eltype(n.a)) === u"K"
31+
@test unit(nonmissingtype(eltype(n.b))) === u"K"
32+
@test unit(eltype(n.c)) === u"K"
33+
@test unit(nonmissingtype(eltype(n.d))) === u"°C"
34+
tₒ = revert(T, n, c)
35+
@test t.a == tₒ.a
36+
@test isequal(t.b, tₒ.b)
37+
@test t.c == tₒ.c
38+
@test isequal(t.d, tₒ.d)
39+
@test t.e == tₒ.e
40+
41+
# symbols
42+
T = AbsoluteUnits(:a, :b)
43+
n, c = apply(T, t)
44+
@test unit(eltype(n.a)) === u"K"
45+
@test unit(nonmissingtype(eltype(n.b))) === u"K"
46+
@test unit(eltype(n.c)) === u"K"
47+
@test unit(nonmissingtype(eltype(n.d))) === u"°C"
48+
tₒ = revert(T, n, c)
49+
@test t.a == tₒ.a
50+
@test isequal(t.b, tₒ.b)
51+
@test t.c == tₒ.c
52+
@test isequal(t.d, tₒ.d)
53+
@test t.e == tₒ.e
54+
55+
# strings
56+
T = AbsoluteUnits("a", "b")
57+
n, c = apply(T, t)
58+
@test unit(eltype(n.a)) === u"K"
59+
@test unit(nonmissingtype(eltype(n.b))) === u"K"
60+
@test unit(eltype(n.c)) === u"K"
61+
@test unit(nonmissingtype(eltype(n.d))) === u"°C"
62+
tₒ = revert(T, n, c)
63+
@test t.a == tₒ.a
64+
@test isequal(t.b, tₒ.b)
65+
@test t.c == tₒ.c
66+
@test isequal(t.d, tₒ.d)
67+
@test t.e == tₒ.e
68+
69+
# vector
70+
# integers
71+
T = AbsoluteUnits([3, 4])
72+
n, c = apply(T, t)
73+
@test unit(eltype(n.a)) === u"°C"
74+
@test unit(nonmissingtype(eltype(n.b))) === u"K"
75+
@test unit(eltype(n.c)) === u"K"
76+
@test unit(nonmissingtype(eltype(n.d))) === u"K"
77+
tₒ = revert(T, n, c)
78+
@test t.a == tₒ.a
79+
@test isequal(t.b, tₒ.b)
80+
@test t.c == tₒ.c
81+
@test all(isapprox.(skipmissing(t.d), skipmissing(tₒ.d)))
82+
@test t.e == tₒ.e
83+
84+
# symbols
85+
T = AbsoluteUnits([:c, :d])
86+
n, c = apply(T, t)
87+
@test unit(eltype(n.a)) === u"°C"
88+
@test unit(nonmissingtype(eltype(n.b))) === u"K"
89+
@test unit(eltype(n.c)) === u"K"
90+
@test unit(nonmissingtype(eltype(n.d))) === u"K"
91+
tₒ = revert(T, n, c)
92+
@test t.a == tₒ.a
93+
@test isequal(t.b, tₒ.b)
94+
@test t.c == tₒ.c
95+
@test all(isapprox.(skipmissing(t.d), skipmissing(tₒ.d)))
96+
@test t.e == tₒ.e
97+
98+
# strings
99+
T = AbsoluteUnits(["c", "d"])
100+
n, c = apply(T, t)
101+
@test unit(eltype(n.a)) === u"°C"
102+
@test unit(nonmissingtype(eltype(n.b))) === u"K"
103+
@test unit(eltype(n.c)) === u"K"
104+
@test unit(nonmissingtype(eltype(n.d))) === u"K"
105+
tₒ = revert(T, n, c)
106+
@test t.a == tₒ.a
107+
@test isequal(t.b, tₒ.b)
108+
@test t.c == tₒ.c
109+
@test all(isapprox.(skipmissing(t.d), skipmissing(tₒ.d)))
110+
@test t.e == tₒ.e
111+
112+
# tuple
113+
# integers
114+
T = AbsoluteUnits((1, 4, 5))
115+
n, c = apply(T, t)
116+
@test unit(eltype(n.a)) === u"K"
117+
@test unit(nonmissingtype(eltype(n.b))) === u"K"
118+
@test unit(eltype(n.c)) === u"K"
119+
@test unit(nonmissingtype(eltype(n.d))) === u"K"
120+
@test eltype(n.e) === String
121+
@test n.e == t.e
122+
tₒ = revert(T, n, c)
123+
@test t.a == tₒ.a
124+
@test isequal(t.b, tₒ.b)
125+
@test t.c == tₒ.c
126+
@test all(isapprox.(skipmissing(t.d), skipmissing(tₒ.d)))
127+
@test t.e == tₒ.e
128+
129+
# symbols
130+
T = AbsoluteUnits((:a, :d, :e))
131+
n, c = apply(T, t)
132+
@test unit(eltype(n.a)) === u"K"
133+
@test unit(nonmissingtype(eltype(n.b))) === u"K"
134+
@test unit(eltype(n.c)) === u"K"
135+
@test unit(nonmissingtype(eltype(n.d))) === u"K"
136+
@test eltype(n.e) === String
137+
@test n.e == t.e
138+
tₒ = revert(T, n, c)
139+
@test t.a == tₒ.a
140+
@test isequal(t.b, tₒ.b)
141+
@test t.c == tₒ.c
142+
@test all(isapprox.(skipmissing(t.d), skipmissing(tₒ.d)))
143+
@test t.e == tₒ.e
144+
145+
# strings
146+
T = AbsoluteUnits(("a", "d", "e"))
147+
n, c = apply(T, t)
148+
@test unit(eltype(n.a)) === u"K"
149+
@test unit(nonmissingtype(eltype(n.b))) === u"K"
150+
@test unit(eltype(n.c)) === u"K"
151+
@test unit(nonmissingtype(eltype(n.d))) === u"K"
152+
@test eltype(n.e) === String
153+
@test n.e == t.e
154+
tₒ = revert(T, n, c)
155+
@test t.a == tₒ.a
156+
@test isequal(t.b, tₒ.b)
157+
@test t.c == tₒ.c
158+
@test all(isapprox.(skipmissing(t.d), skipmissing(tₒ.d)))
159+
@test t.e == tₒ.e
160+
161+
# regex
162+
T = AbsoluteUnits(r"[ade]")
163+
n, c = apply(T, t)
164+
@test unit(eltype(n.a)) === u"K"
165+
@test unit(nonmissingtype(eltype(n.b))) === u"K"
166+
@test unit(eltype(n.c)) === u"K"
167+
@test unit(nonmissingtype(eltype(n.d))) === u"K"
168+
@test eltype(n.e) === String
169+
@test n.e == t.e
170+
tₒ = revert(T, n, c)
171+
@test t.a == tₒ.a
172+
@test isequal(t.b, tₒ.b)
173+
@test t.c == tₒ.c
174+
@test all(isapprox.(skipmissing(t.d), skipmissing(tₒ.d)))
175+
@test t.e == tₒ.e
176+
end

0 commit comments

Comments
 (0)