Skip to content

Commit

Permalink
Add tests, bug fix
Browse files Browse the repository at this point in the history
  • Loading branch information
yufongpeng committed Dec 7, 2023
1 parent 4f01e8c commit c7d831b
Show file tree
Hide file tree
Showing 21 changed files with 772 additions and 79 deletions.
8 changes: 1 addition & 7 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,4 @@ Dictionaries = "0.3"
GLM = "1.9"
TypedTables = "1.4"
ThreadsX = "0.1"
julia = "1.9"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]
julia = "1.9"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This package provides two basic wrappers, `ColumnDataTable{A, T}` and `RowDataTa
|`analytes`|`Vector{A}` stored in field `config`, analytes in user-defined types.|`Vector{A}`, analytes in user-defined types.|
|`table`|Tabular data of type `T`|same|

`ColumnDataTable` can be created by `ColumnDataTable(table, sample_name; analyte_fn, analyte_name)`. By default, `analyte_name` includes all properties of `table` without `sample_name`. `RowDataTable` can be created by `RowDataTable(table, analyte_name; analyte_fn, sample_name)`. By default, `analyte_name` includes all properties of `table` without `sample_name`.
To add new samples to `ColumnDataTable{A, T}`, user can directly modify `table`; for `RowDataTable{A, T}`, user have to modify `sample_name` as well. To add new analytes, user can directly modify `table`, and modify `config` for `ColumnDataTable{A, T}` (`config` is a `TypedTables.Table`) and `analytes` for `RowDataTable{A, T}`.

The package provides another two wrappers, `MethodTable{A, T}`, and `AnalysisTable{A, T} <: AbstractAnalysisTable{A, T}`.
Expand Down
40 changes: 34 additions & 6 deletions src/QuantitativeAnalysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ module QuantitativeAnalysis
using GLM, CSV, TypedTables, LinearAlgebra, Dictionaries, ThreadsX
export MultipleCalibration, SingleCalibration,
ColumnDataTable, RowDataTable, AnalysisTable, MethodTable,
Batch, calibration,
Batch, calibration, update_calibration!,
read_calibration, read_datatable, read_analysistable, read_methodtable, read_batch,
cal_range, lloq, uloq, accuracy, accuracy!, set_accuracy, set_accuracy!, update_accuracy!,
inv_predict, inv_predict!, inv_predict_accuracy!, set_inv_predict, set_inv_predict!, update_accuracy!,
inv_predict, inv_predict!, inv_predict_accuracy!, set_inv_predict, set_inv_predict!, update_inv_predict!,
relative_signal, set_relative_signal, set_relative_signal!, update_relative_signal!,
quantification, quantification!, set_quantification, set_quantification!, update_quantification!,
find_analyte, get_analyte, find_sample, get_sample, set_isd!,
Expand Down Expand Up @@ -40,20 +40,32 @@ struct ColumnDataTable{A, T} <: AbstractDataTable{A, T}
end

"""
ColumnDataTable(sample_name, analyte_name, analytes, table)
ColumnDataTable(sample_name::Symbol, analyte_name::Vector{Symbol}, analytes::Vector, table)
ColumnDataTable(sample_name::Symbol, analyte_name::Vector, table; analyte_fn = default_analyte_fn)
ColumnDataTable(sample_name::Symbol, table; analyte_name = setdiff(propertynames(table), [sample_name]), analyte_fn = default_analyte_fn)
ColumnDataTable(table, sample_name::Symbol; analyte_name = setdiff(propertynames(table), [sample_name]), analyte_fn = default_analyte_fn)
A user-friendly contructor for `ColumnDataTable`. See the documentation of the type for detail description.
User-friendly contructors for `ColumnDataTable`. See the documentation of the type for detail description.
"""
function ColumnDataTable(sample_name::Symbol, analyte_name::Vector{Symbol}, analytes::Vector{A}, table::T) where {A, T}
function ColumnDataTable(sample_name::Symbol, analyte_name::Vector{Symbol}, analytes::Vector, table)
length(analyte_name) == length(analytes) || throw(ArgumentError("Arguments `analyte_name` and `analytes` should have the same length."))
ColumnDataTable(sample_name, Table(; analyte_name, analytes), table)
end

ColumnDataTable(sample_name::Symbol, analyte_name::Vector, table; analyte_fn = default_analyte_fn) =
ColumnDataTable(sample_name, Symbol.(analyte_name), analyte_fn.(string.(analyte_name)), table)
ColumnDataTable(sample_name::Symbol, table; analyte_name = setdiff(propertynames(table), [sample_name]), analyte_fn = default_analyte_fn) =
ColumnDataTable(sample_name, analyte_name, table; analyte_fn)
ColumnDataTable(table, sample_name::Symbol; analyte_name = setdiff(propertynames(table), [sample_name]), analyte_fn = default_analyte_fn) =
ColumnDataTable(sample_name, analyte_name, table; analyte_fn)

function getproperty(tbl::ColumnDataTable, property::Symbol)
if property in [:analyte_name, :analytes]
getproperty(getfield(tbl, :config), property)
elseif property == :samples
Symbol.(getproperty(tbl.table, tbl.sample_name))
elseif !in(property, fieldnames(ColumnDataTable))
getproperty(getfield(tbl, :table), property)
else
getfield(tbl, property)
end
Expand Down Expand Up @@ -84,9 +96,25 @@ struct RowDataTable{A, T} <: AbstractDataTable{A, T}
end
end

"""
RowDataTable(sample_name::Vector{Symbol}, analyte_name::Symbol, table; analyte_fn = default_analyte_fn)
RowDataTable(analyte_name::Symbol, table; sample_name = setdiff(propertynames(table), [analyte_name]), analyte_fn = default_analyte_fn)
RowDataTable(table, analyte_name::Symbol; sample_name = setdiff(propertynames(table), [analyte_name]), analyte_fn = default_analyte_fn)
User-friendly contructors for `RowDataTable`. See the documentation of the type for detail description.
"""
RowDataTable(sample_name::Vector{Symbol}, analyte_name::Symbol, table; analyte_fn = default_analyte_fn) =
RowDataTable(sample_name, analyte_name, analyte_fn.(string.(getproperty(table, analyte_name))), table)
RowDataTable(analyte_name::Symbol, table; sample_name = setdiff(propertynames(table), [analyte_name]), analyte_fn = default_analyte_fn) =
RowDataTable(sample_name, analyte_name, table; analyte_fn)
RowDataTable(table, analyte_name::Symbol; sample_name = setdiff(propertynames(table), [analyte_name]), analyte_fn = default_analyte_fn) =
RowDataTable(sample_name, analyte_name, table; analyte_fn)

function getproperty(tbl::RowDataTable{A}, property::Symbol) where A
if property == :samples
getfield(tbl, :sample_name)
elseif !in(property, fieldnames(RowDataTable))
getproperty(getfield(tbl, :table), property)
else
getfield(tbl, property)
end
Expand Down Expand Up @@ -212,7 +240,7 @@ MethodTable(
analyte_map::Table{NamedTuple{(:analytes, :isd, :calibration), Tuple{A, Int, Int}}, 1, NamedTuple{(:analytes, :isd, :calibration), Tuple{Vector{A}, Vector{Int}, Vector{Int}}}},
level_map::Vector{Int},
conctable::AbstractDataTable{<: A, T},
signaltable::AbstractDataTable{<: A, S}) where {A, T, S} = MethodTable{A, promote_type{T, S}}(signal, analyte_map, level_map, conctable, signaltable)
signaltable::AbstractDataTable{<: A, S}) where {A, T, S} = MethodTable{promote_type(T, S)}(signal, analyte_map, level_map, conctable, signaltable)

function getproperty(tbl::MethodTable, p::Symbol)
if p == :analytes
Expand Down
10 changes: 5 additions & 5 deletions src/cal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ relative_signal(batch::Batch, dt::AbstractDataTable) = relative_signal(batch.met
Calculate relative signal, update or insert the value into `at` or a copy of `at` at index `relsig`, and return the object using `getproperty(at, signal)` as signal data.
"""
set_relative_signal(at::AbstractDataTable, batch::Batch; signal = batch.method.signal, relsig = :relative_signal) =
set_relative_signal(at::AnalysisTable, batch::Batch; signal = batch.method.signal, relsig = :relative_signal) =
set_relative_signal(at, batch.method; signal, relsig)
function set_relative_signal(at::AnalysisTable, method::MethodTable; signal = method.signal, relsig = :relative_signal)
result = relative_signal(method, getproperty(at, signal))
Expand Down Expand Up @@ -185,7 +185,7 @@ Quantify `analyte`, and return the result as a vector using data in `dt` as sign
quantification(cal::SingleCalibration) = [cal.conc]
quantification(cal::MultipleCalibration) = inv_predict(cal, cal.table.y)
function quantification(cal::AbstractCalibration, dt::AbstractDataTable; analyte = cal.analyte)
isnothing(last(analyte)) > 0 || return inv_predict(cal, get_analyte(dt, first(analyte)))
isnothing(last(analyte)) && return inv_predict(cal, get_analyte(dt, first(analyte)))
inv_predict(cal, get_analyte(dt, first(analyte)) ./ get_analyte(dt, last(analyte)))
end

Expand Down Expand Up @@ -399,7 +399,6 @@ It returns `GLM` object for non-mutating version and input object for mutating v
calfit!(batch::Batch) = (calfit!.(batch.calibration); batch)
function calfit!(cal::MultipleCalibration)
cal.model = calfit(cal.table, cal.formula, cal.type, cal.zero, cal.weight)
update_calibration!(batch::Batch, cal_id::Int)
cal
end

Expand All @@ -423,8 +422,9 @@ function update_calibration!(cal::MultipleCalibration, method::MethodTable)
isd = isd_of(method, first(cal.analyte))
cal.analyte = (first(cal.analyte), isd)
ord = sortperm(method.level_map)
cal.table.y .= isnothing(isd) ? get_analyte(method.signaltable, analyte)[ord] : (get_analyte(method.signaltable, analyte) ./ get_analyte(method.signaltable, isd))[ord],
cal.include .= true
cal.table.y .= isnothing(isd) ? get_analyte(method.signaltable, first(cal.analyte))[ord] : (get_analyte(method.signaltable, first(cal.analyte)) ./ get_analyte(method.signaltable, isd))[ord]
cal.table.include .= true
cal.formula = get_formula(cal)
calfit!(cal)
inv_predict_accuracy!(cal)
end
Expand Down
3 changes: 2 additions & 1 deletion src/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,8 @@ end
"""
Calibration.read(file::String, T; table_type = T, analyte_fn = default_analyte_fn) -> Batch{A, table_type}
Read ".batch" file into julia as `Batch`. `T` is the sink function for tabular data, `table_type` is `T` parameter in the type signature of `Batch` which determines the underlying table type, and `analyte_fn` is responsible for converting `analyte` in string type into user defined type `A`.
Read ".batch" file into julia as `Batch`. `T` is the sink function for tabular data, `table_type` is `T` parameter in the type signature of `Batch` which determines the underlying table type
, and `analyte_fn` is responsible for converting `analyte` in string type into user defined type `A`.
If `table_type` is set to `nothing`, table type will be determined automatically which may be too restrict when using parameterized table types.
Expand Down
2 changes: 1 addition & 1 deletion src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ get_analyte(dt::RowDataTable, id::Int) = collect(getproperties(dt.table[id], Tup
function get_analyte(dt::RowDataTable{A}, analyte::B) where {A, B <: A}
id = findfirst(==(analyte), dt.analytes)
isnothing(id) && throw(ArgumentError("Analyte $analyte is not in the table"))
collect(getproperties(dt.table[id], Tuple(dt.sample_name)))
[getproperty(dt.table, p)[id] for p in dt.sample_name]
end
get_analyte(dt::ColumnDataTable, id::Int) = getproperty(dt.table, dt.analyte_name[id])
function get_analyte(dt::ColumnDataTable{A}, analyte::B) where {A, B <: A}
Expand Down
Loading

0 comments on commit c7d831b

Please sign in to comment.