diff --git a/.gitignore b/.gitignore
index ede7de6..c917664 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
.vscode/
/**/.DS_Store
node_modules
+*_files/
diff --git a/README.md b/README.md
index 2c9decb..d9e55aa 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# RheumaComposites.jl
+# RheumaComposites.jl
[![Build Status](https://github.com/simonsteiger/RheumaComposites.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/simonsteiger/RheumaComposites.jl/actions/workflows/CI.yml?query=branch%3Amain)
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://simonsteiger.github.io/RheumaComposites.jl/dev/)
diff --git a/docs/src/examples/categorisation.md b/docs/src/examples/categorisation.md
index 1e26e5f..4b462d2 100644
--- a/docs/src/examples/categorisation.md
+++ b/docs/src/examples/categorisation.md
@@ -37,12 +37,12 @@ The cutoffs used per composite and category are:
| Moderate | ``\leq`` 5.1 | ``\leq`` 4.6 | ``\leq`` 26.0 | ``\leq`` 22.0 |
| High | ``>`` 5.1 | ``>`` 4.6 | ``>`` 26.0 | ``>`` 22.0 |
-Internally, these are saved in a `NamedTuple` which you can import with `import RheumaComposites: cont_cutoff`.
+Internally, these are saved in a `NamedTuple` which you can import with `import RheumaComposites: cutoff`.
To retrieve the cutoff for a Moderate CDAI, you would:
````@example categorisation
-import RheumaComposites: cont_cutoff
-cont_cutoff.CDAI.moderate
+import RheumaComposites: cutoff
+cutoff.CDAI.moderate
````
Note that this only returns the boundary value of the respective category, and that the inclusion of this value varies across **both** composites and categories.
diff --git a/docs/src/index.md b/docs/src/index.md
index 4bd8a69..b1ff11c 100644
--- a/docs/src/index.md
+++ b/docs/src/index.md
@@ -2,7 +2,7 @@
EditURL = "https://github.com/simonsteiger/RheumaComposites.jl/blob/main/README.md"
```
-# RheumaComposites.jl
+# RheumaComposites.jl
[![Build Status](https://github.com/simonsteiger/RheumaComposites.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/simonsteiger/RheumaComposites.jl/actions/workflows/CI.yml?query=branch%3Amain)
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://simonsteiger.github.io/RheumaComposites.jl/dev/)
diff --git a/examples/categorisation.jl b/examples/categorisation.jl
index 49a8b45..6ade4ad 100644
--- a/examples/categorisation.jl
+++ b/examples/categorisation.jl
@@ -27,12 +27,12 @@ The cutoffs used per composite and category are:
| Moderate | ``\leq`` 5.1 | ``\leq`` 4.6 | ``\leq`` 26.0 | ``\leq`` 22.0 |
| High | ``>`` 5.1 | ``>`` 4.6 | ``>`` 26.0 | ``>`` 22.0 |
-Internally, these are saved in a `NamedTuple` which you can import with `import RheumaComposites: cont_cutoff`.
+Internally, these are saved in a `NamedTuple` which you can import with `import RheumaComposites: cutoff`.
To retrieve the cutoff for a Moderate CDAI, you would:
=#
-import RheumaComposites: cont_cutoff
-cont_cutoff.CDAI.moderate
+import RheumaComposites: cutoff
+cutoff.CDAI.moderate
#=
Note that this only returns the boundary value of the respective category, and that the inclusion of this value varies across **both** composites and categories.
diff --git a/src/functions/categorise.jl b/src/functions/categorise.jl
index 5e6d617..e4db5d2 100644
--- a/src/functions/categorise.jl
+++ b/src/functions/categorise.jl
@@ -1,63 +1,41 @@
"""
- categorise(x::ContinuousComposite)
+ categorise(::Type{T}, s::Real) where {T<:ContinuousComposite}
-Convert `x` to a discrete value.
+Convert score `s` to a discrete value using `SDAI` thresholds.
+
+The same functionality exists for other `ContinuousComposites`.
# Examples
```jldoctest
-julia> DAS28ESR(tjc=4, sjc=5, pga=12u"mm", apr=44u"mm/hr") |> categorise
-"Moderate"
+julia> categorise(SDAI, 3.6)
+"low"
```
"""
-function categorise(::Type{DAS28ESR}, v)
- out = v < cont_cutoff.DAS28ESR.remission ? "Remission" :
- v <= cont_cutoff.DAS28ESR.low ? "Low" :
- v <= cont_cutoff.DAS28ESR.moderate ? "Moderate" :
- "High"
- return out
+function categorise(::Type{T}, s::Real) where {T<:ContinuousComposite}
+ return seq_check(s, getproperty(cont_cutoff_funs, Symbol(T)))
end
-
-function categorise(::Type{DAS28CRP}, v)
- out = v < cont_cutoff.DAS28CRP.remission ? "Remission" :
- v <= cont_cutoff.DAS28CRP.low ? "Low" :
- v <= cont_cutoff.DAS28CRP.moderate ? "Moderate" :
- "High"
- return out
-end
-
-function categorise(::Type{SDAI}, v)
- out = v < cont_cutoff.SDAI.remission ? "Remission" :
- v <= cont_cutoff.SDAI.low ? "Low" :
- v <= cont_cutoff.SDAI.moderate ? "Moderate" :
- "High"
- return out
-end
-
-function categorise(::Type{CDAI}, v)
- out = v < cont_cutoff.CDAI.remission ? "Remission" :
- v <= cont_cutoff.CDAI.low ? "Low" :
- v <= cont_cutoff.CDAI.moderate ? "Moderate" :
- "High"
- return out
-end
-
-categorise(x::ContinuousComposite) = categorise(typeof(x), score(x))
+# This implementation is roughly half as 2.5 times slower than hard coding
+# cutoffs into each categorise function
+# If performance is ever critical, I should change to the more verbose but faster version
"""
- categorise(::Type{SDAI}, v)
-
-Convert `v` to a discrete value using `SDAI` thresholds.
-
-The same functionality exists for other `ContinuousComposites`.
+ categorise(x::ContinuousComposite)
-See also [`DAS28ESR`](@ref), [`DAS28CRP`](@ref).
+Convert `x` to a discrete value.
# Examples
```jldoctest
-julia> categorise(SDAI, 3.6)
-"Low"
+julia> DAS28ESR(tjc=4, sjc=5, pga=12u"mm", apr=44u"mm/hr") |> categorise
+"moderate"
```
"""
+categorise(x::ContinuousComposite) = categorise(typeof(x), score(x))
+
+"""
+ categorise(x::Faceted{<:ContinuousComposite})
+
+Convert the `root` composite of `x` to a discrete value.
+"""
categorise(x::Faceted{<:ContinuousComposite}) = categorise(typeof(x.root), score(x.root))
diff --git a/src/functions/isremission.jl b/src/functions/isremission.jl
index 142c668..e6f9b90 100644
--- a/src/functions/isremission.jl
+++ b/src/functions/isremission.jl
@@ -1,24 +1,7 @@
-"""
- isremission(x::AbstractComposite)
-
-Check whether a composite fulfils remission criteria.
-
-# Examples
-
-```jldoctest
-julia> DAS28ESR(tjc=4, sjc=5, pga=44u"mm", apr=23u"mm/hr") |> isremission
-false
-julia> BooleanRemission(tjc=1, sjc=0, pga=14u"mm", crp=0.4u"mg/dl") |>
- revised |>
- isremission
-true
-```
-"""
-isremission(::Type{DAS28ESR}, x) = score(x) < cont_cutoff.DAS28ESR.remission
-isremission(::Type{DAS28CRP}, x) = score(x) < cont_cutoff.DAS28CRP.remission
-
-isremission(::Type{SDAI}, x) = score(x) <= cont_cutoff.SDAI.remission
-isremission(::Type{CDAI}, x) = score(x) <= cont_cutoff.CDAI.remission
+function isremission(::Type{T}, x::AbstractComposite) where {T<:ContinuousComposite}
+ cut = getproperty(cont_cutoff_funs, Symbol(T))
+ return cut.remission(score(x))
+end
isremission(::Type{PGA}, x) = x.value <= 10.0u"mm"
isremission(::Type{SJC}, x) = x.value == 0
@@ -48,5 +31,39 @@ function isremission(::Type{<:Revised{<:BooleanComposite}}, x)
return out
end
+"""
+ isremission(x::AbstractComposite)
+
+Check whether a composite fulfils remission criteria.
+
+# Examples
+
+```jldoctest
+julia> DAS28ESR(tjc=4, sjc=5, pga=44u"mm", apr=23u"mm/hr") |> isremission
+false
+julia> BooleanRemission(tjc=1, sjc=0, pga=14u"mm", crp=0.4u"mg/dl") |>
+ revised |>
+ isremission
+true
+```
+"""
isremission(x::AbstractComposite) = isremission(typeof(x), x)
+
+"""
+ isremission(::Type{T}, s::Real) where {T<:ContinuousComposite}
+
+Check whether a composite fulfils remission criteria.
+
+# Examples
+
+```jldoctest
+julia> isremission(DAS28ESR, 3.9)
+false
+```
+"""
+function isremission(::Type{T}, s::Real) where {T<:ContinuousComposite}
+ cut = getproperty(cont_cutoff_funs, Symbol(T))
+ return cut.remission(s)
+end
+
isremission(x::AbstractComponent) = isremission(typeof(x), x)
diff --git a/src/functions/weight.jl b/src/functions/weight.jl
index 00e16a0..d50b219 100644
--- a/src/functions/weight.jl
+++ b/src/functions/weight.jl
@@ -15,15 +15,12 @@ weight(x::Subset{N,T}) where {N,T} = weight(WeightingScheme(T), x)
weight(::IsUnweightable, x::T) where {T} = throw(ErrorException("$(typeof(x)) type is unweightable."))
-weight(::IsUnweighted, x::T) where {T} = ustrip.(getproperty.(Ref(x), fieldnames(T)))
+weight(::IsUnweighted, x::T) where {T} = ustrip.(getproperty.(Ref(x), components(x)))
function map_weights(weights, x)
- weighted_values = map(components(x)) do component
- component_value = ustrip(getproperty(root(x), component))
- component_weight = getproperty(weights, component)
- component_weight(component_value)
- end
- return weighted_values
+ component_values = ustrip.(getproperty.(Ref(x), components(x)))
+ component_weights = getproperty.(Ref(weights), components(x))
+ return map((w, v) -> w(v), component_weights, component_values)
end
function weight(::IsWeighted, x::DAS28)
diff --git a/src/types/boolean.jl b/src/types/boolean.jl
index 7300b36..c90015a 100644
--- a/src/types/boolean.jl
+++ b/src/types/boolean.jl
@@ -25,7 +25,7 @@ struct BooleanRemission <: BooleanComposite
valid_joints.([tjc, sjc])
valid_vas(pga)
valid_apr(crp)
- return new(tjc, sjc, pga, uconvert(units.brem_crp, crp))
+ return new(tjc, sjc, uconvert(units.brem_vas, pga), uconvert(units.brem_crp, crp))
end
end
diff --git a/src/types/cdai.jl b/src/types/cdai.jl
index 479b23c..7276658 100644
--- a/src/types/cdai.jl
+++ b/src/types/cdai.jl
@@ -16,10 +16,10 @@ Store component measures of the Clinical Disease Activity Index, or CDAI.
# Categories
-- ``<`` $(cont_cutoff.CDAI.low) = Remission
-- ``\\leq`` $(cont_cutoff.CDAI.low) = Low
-- ``\\leq`` $(cont_cutoff.CDAI.moderate) = Moderate
-- ``>`` $(cont_cutoff.CDAI.moderate) = High
+- ``<`` $(cutoff.CDAI.low) = Remission
+- ``\\leq`` $(cutoff.CDAI.low) = Low
+- ``\\leq`` $(cutoff.CDAI.moderate) = Moderate
+- ``>`` $(cutoff.CDAI.moderate) = High
# External links
diff --git a/src/types/das28.jl b/src/types/das28.jl
index dc99020..606e2c0 100644
--- a/src/types/das28.jl
+++ b/src/types/das28.jl
@@ -29,10 +29,10 @@ Store the component measures of the DAS28CRP.
# Categories
-- ``<`` $(cont_cutoff.DAS28CRP.low) = Remission
-- ``\\leq`` $(cont_cutoff.DAS28CRP.low) = Low
-- ``\\leq`` $(cont_cutoff.DAS28CRP.moderate) = Moderate
-- ``>`` $(cont_cutoff.DAS28CRP.moderate) = High
+- ``<`` $(cutoff.DAS28CRP.low) = Remission
+- ``\\leq`` $(cutoff.DAS28CRP.low) = Low
+- ``\\leq`` $(cutoff.DAS28CRP.moderate) = Moderate
+- ``>`` $(cutoff.DAS28CRP.moderate) = High
# External links
@@ -83,10 +83,10 @@ Store the component measures of the DAS28ESR.
# Categories
-- ``<`` $(cont_cutoff.DAS28ESR.low) = Remission
-- ``\\leq`` $(cont_cutoff.DAS28ESR.low) = Low
-- ``\\leq`` $(cont_cutoff.DAS28ESR.moderate) = Moderate
-- ``>`` $(cont_cutoff.DAS28ESR.moderate) = High
+- ``<`` $(cutoff.DAS28ESR.low) = Remission
+- ``\\leq`` $(cutoff.DAS28ESR.low) = Low
+- ``\\leq`` $(cutoff.DAS28ESR.moderate) = Moderate
+- ``>`` $(cutoff.DAS28ESR.moderate) = High
# External links
diff --git a/src/types/sdai.jl b/src/types/sdai.jl
index 570ec27..f473b01 100644
--- a/src/types/sdai.jl
+++ b/src/types/sdai.jl
@@ -17,10 +17,10 @@ Store component measures of the Simplified Disease Activity Index, or SDAI.
# Categories
-- ``\\leq`` $(cont_cutoff.SDAI.low) = Remission
-- ``\\leq`` $(cont_cutoff.SDAI.low) = Low
-- ``\\leq`` $(cont_cutoff.SDAI.moderate) = Moderate
-- ``>`` $(cont_cutoff.SDAI.moderate) = High
+- ``\\leq`` $(cutoff.SDAI.low) = Remission
+- ``\\leq`` $(cutoff.SDAI.low) = Low
+- ``\\leq`` $(cutoff.SDAI.moderate) = Moderate
+- ``>`` $(cutoff.SDAI.moderate) = High
# External links
diff --git a/src/utils/auxfuns.jl b/src/utils/auxfuns.jl
index c9640a0..8c6c63f 100644
--- a/src/utils/auxfuns.jl
+++ b/src/utils/auxfuns.jl
@@ -5,3 +5,23 @@ function values_flatten(x::NamedTuple)
collect
return property_vec
end
+
+function seq_check(x::Real, conds::NamedTuple)
+ funs = values(conds)
+ i = 1
+ for fun in funs
+ fun(x) && break
+ i += 1
+ end
+ return string(keys(conds)[i])
+end
+
+#=
+function seq_check(x::BooleanComposite, conds::NamedTuple)
+ funs = values(conds)
+ for fun in funs
+ fun(x) || return false
+ end
+ return true
+end
+=#
diff --git a/src/utils/constants.jl b/src/utils/constants.jl
index fbf5421..e9cb77b 100644
--- a/src/utils/constants.jl
+++ b/src/utils/constants.jl
@@ -1,4 +1,4 @@
-cont_cutoff = (
+cutoff = (
DAS28ESR=(
remission=2.6,
low=3.2,
@@ -19,13 +19,47 @@ cont_cutoff = (
low=10.0,
moderate=22.0
),
+ BooleanRemission=(
+ tjc=1,
+ sjc=1,
+ pga=10u"mm",
+ crp=1.0u"mg/dL",
+ ),
)
bool_cutoff_funs = (
- tjc=(x; offset = 0) -> tjc(x) <= 1 + offset,
- sjc=(x; offset = 0) -> sjc(x) <= 1 + offset,
- pga=(x; offset = 0u"mm") -> pga(x) <= 10u"mm" + offset,
- crp=(x; offset = 0u"mg/dL") -> crp(x) <= 1.0u"mg/dL" + offset,
+ tjc=(x; offset = 0) -> tjc(x) <= cutoff.BooleanRemission.tjc + offset,
+ sjc=(x; offset = 0) -> sjc(x) <= cutoff.BooleanRemission.sjc + offset,
+ pga=(x; offset = 0u"mm") -> pga(x) <= cutoff.BooleanRemission.pga + offset,
+ crp=(x; offset = 0u"mg/dL") -> crp(x) <= cutoff.BooleanRemission.crp + offset,
+)
+
+# TODO add a check that asserts that all continuous composites have an entry here
+cont_cutoff_funs = (
+ DAS28ESR=(
+ remission=(x) -> x < cutoff.DAS28ESR.remission,
+ low=(x) -> x <= cutoff.DAS28ESR.low,
+ moderate=(x) -> x <= cutoff.DAS28ESR.moderate,
+ high=(x) -> x > cutoff.DAS28ESR.moderate,
+ ),
+ DAS28CRP=(
+ remission=(x) -> x < cutoff.DAS28CRP.remission,
+ low=(x) -> x <= cutoff.DAS28CRP.low,
+ moderate=(x) -> x <= cutoff.DAS28CRP.moderate,
+ high=(x) -> x > cutoff.DAS28CRP.moderate,
+ ),
+ SDAI=(
+ remission=(x) -> x <= cutoff.SDAI.remission,
+ low=(x) -> x <= cutoff.SDAI.low,
+ moderate=(x) -> x <= cutoff.SDAI.moderate,
+ high=(x) -> x > cutoff.SDAI.moderate,
+ ),
+ CDAI=(
+ remission=(x) -> x <= cutoff.CDAI.remission,
+ low=(x) -> x <= cutoff.CDAI.low,
+ moderate=(x) -> x <= cutoff.CDAI.moderate,
+ high=(x) -> x > cutoff.CDAI.moderate,
+ ),
)
weights_das28esr = (
diff --git a/test/types/boolean.jl b/test/types/boolean.jl
index 132ebcb..3dba7ba 100644
--- a/test/types/boolean.jl
+++ b/test/types/boolean.jl
@@ -1,4 +1,4 @@
-boolrem = BooleanRemission(tjc=1, sjc=0, pga=14u"mm", crp=0.4u"mg/dl")
+boolrem = BooleanRemission(tjc=1, sjc=0, pga=14u"mm", crp=0.4u"mg/dL")
@testset "Original BoolRem" begin
@test boolrem isa AbstractComposite
diff --git a/test/types/cdai.jl b/test/types/cdai.jl
index 1d992fc..963f5da 100644
--- a/test/types/cdai.jl
+++ b/test/types/cdai.jl
@@ -33,8 +33,8 @@ end
end
@testset "Categorise CDAI" begin
- @test categorise(cdai) == "Remission"
- @test categorise.(CDAI, [2.79, 2.81]) == ["Remission", "Low"]
- @test categorise.(CDAI, [9.99, 10.01]) == ["Low", "Moderate"]
- @test categorise.(CDAI, [21.99, 22.01]) == ["Moderate", "High"]
+ @test categorise(cdai) == "remission"
+ @test categorise.(CDAI, [2.79, 2.81]) == ["remission", "low"]
+ @test categorise.(CDAI, [9.99, 10.01]) == ["low", "moderate"]
+ @test categorise.(CDAI, [21.99, 22.01]) == ["moderate", "high"]
end
diff --git a/test/types/das28.jl b/test/types/das28.jl
index e20e948..643d51b 100644
--- a/test/types/das28.jl
+++ b/test/types/das28.jl
@@ -52,10 +52,10 @@ end
end
@testset "Categorise DAS28ESR" begin
- @test categorise(das28e) == "Moderate"
- @test categorise.(DAS28ESR, [2.59, 2.61]) == ["Remission", "Low"]
- @test categorise.(DAS28ESR, [3.19, 3.21]) == ["Low", "Moderate"]
- @test categorise.(DAS28ESR, [5.09, 5.11]) == ["Moderate", "High"]
+ @test categorise(das28e) == "moderate"
+ @test categorise.(DAS28ESR, [2.59, 2.61]) == ["remission", "low"]
+ @test categorise.(DAS28ESR, [3.19, 3.21]) == ["low", "moderate"]
+ @test categorise.(DAS28ESR, [5.09, 5.11]) == ["moderate", "high"]
end
@testset "Construct DAS28CRP" begin
@@ -88,12 +88,16 @@ end
@test faceted(das28c, facets) isa Faceted{<:ContinuousComposite}
@test score(faceted(das28c, facets)) == score(das28c)
@test sum(decompose(faceted(das28c, facets), digits=5)) ≈ 1.0 atol = 1e-5
- @test try faceted(das28c, (abc=[:tjc, :pga], cde=[:tjc, :apr])) catch e; e isa ErrorException end
+ @test try
+ faceted(das28c, (abc=[:tjc, :pga], cde=[:tjc, :apr]))
+ catch e
+ e isa ErrorException
+ end
end
@testset "Categorise DAS28CRP" begin
- @test categorise(das28c) == "Moderate"
- @test categorise.(DAS28CRP, [2.39, 2.51]) == ["Remission", "Low"]
- @test categorise.(DAS28CRP, [2.89, 2.91]) == ["Low", "Moderate"]
- @test categorise.(DAS28CRP, [4.59, 4.61]) == ["Moderate", "High"]
+ @test categorise(das28c) == "moderate"
+ @test categorise.(DAS28CRP, [2.39, 2.51]) == ["remission", "low"]
+ @test categorise.(DAS28CRP, [2.89, 2.91]) == ["low", "moderate"]
+ @test categorise.(DAS28CRP, [4.59, 4.61]) == ["moderate", "high"]
end
diff --git a/test/types/sdai.jl b/test/types/sdai.jl
index faffd8d..cbdeb9b 100644
--- a/test/types/sdai.jl
+++ b/test/types/sdai.jl
@@ -35,8 +35,8 @@ end
end
@testset "Categorise SDAI" begin
- @test categorise(sdai) == "Remission"
- @test categorise.(SDAI, [3.29, 3.31]) == ["Remission", "Low"]
- @test categorise.(SDAI, [10.99, 11.01]) == ["Low", "Moderate"]
- @test categorise.(SDAI, [25.99, 26.01]) == ["Moderate", "High"]
+ @test categorise(sdai) == "remission"
+ @test categorise.(SDAI, [3.29, 3.31]) == ["remission", "low"]
+ @test categorise.(SDAI, [10.99, 11.01]) == ["low", "moderate"]
+ @test categorise.(SDAI, [25.99, 26.01]) == ["moderate", "high"]
end