From 1b4c64aa796a0c4dfcdf4fccd7ef06571a4e8d2b Mon Sep 17 00:00:00 2001 From: p-gw Date: Wed, 15 May 2024 10:57:03 +0200 Subject: [PATCH 01/12] implement irf for polytomous rasch models --- docs/src/api.md | 3 + src/ItemResponseFunctions.jl | 10 ++- src/irf.jl | 65 ++++++++++++++++++- src/model_types.jl | 61 +++++++++++++++++ .../generalized_partial_credit_model.jl | 22 +++++++ 5 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 test/models/generalized_partial_credit_model.jl diff --git a/docs/src/api.md b/docs/src/api.md index e902fad..bf8ddec 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -10,6 +10,9 @@ OneParameterLogisticModel TwoParameterLogisticModel ThreeParameterLogisticModel FourParameterLogisticModel +PartialCreditModel +GeneralizedPartialCreditModel +RatingScaleModel ``` ## Functions diff --git a/src/ItemResponseFunctions.jl b/src/ItemResponseFunctions.jl index a337f06..a4de5ac 100644 --- a/src/ItemResponseFunctions.jl +++ b/src/ItemResponseFunctions.jl @@ -8,7 +8,7 @@ using SimpleUnPack import AbstractItemResponseModels: response_type, Dichotomous -using LogExpFunctions: logistic +using LogExpFunctions: logistic, cumsum!, softmax! using DocStringExtensions: SIGNATURES, TYPEDEF, METHODLIST export DichotomousItemResponseModel, @@ -19,7 +19,13 @@ export DichotomousItemResponseModel, ThreePL, ThreeParameterLogisticModel, TwoPL, - TwoParameterLogisticModel + TwoParameterLogisticModel, + GPCM, + GeneralizedPartialCreditModel, + PCM, + PartialCreditModel, + RSM, + RatingScaleModel include("model_types.jl") include("irf.jl") diff --git a/src/irf.jl b/src/irf.jl index 5e0bab7..bf84268 100644 --- a/src/irf.jl +++ b/src/irf.jl @@ -20,7 +20,7 @@ julia> irf(OnePL, 0.0, (; b = 0.5)) ### 2 Parameter Logistic Model ```jldoctest -julia> beta = (; a = 1.5, b = 0.5); +julia> beta = (a = 1.5, b = 0.5); julia> irf(TwoPL, 0.0, beta) 0.32082130082460697 @@ -28,7 +28,7 @@ julia> irf(TwoPL, 0.0, beta) ### 3 Parameter Logistic Model ```jldoctest -julia> beta = (; a = 1.5, b = 0.5, c = 0.2); +julia> beta = (a = 1.5, b = 0.5, c = 0.2); julia> irf(ThreePL, 0.0, beta) 0.4566570406596856 @@ -36,11 +36,55 @@ julia> irf(ThreePL, 0.0, beta) ### 4 Parameter Logistic Model ```jldoctest -julia> beta = (; a = 1.5, b = 0.5, c = 0.2, d = 0.8); +julia> beta = (a = 1.5, b = 0.5, c = 0.2, d = 0.8); julia> irf(FourPL, 0.0, beta) 0.3924927804947642 ``` + +### Partial Credit Model +```jldoctest +julia> beta = (b = -0.3, t = [-0.5, 1.3, -0.2]); + +julia> irf(PCM, 0.0, beta) +4-element Vector{Float64}: + 0.09656592461423529 + 0.07906149218108449 + 0.3915941342939724 + 0.4327784489107078 + +julia> irf(PCM, 0.0, beta, 3) +0.3915941342939724 +``` + +### Generalized Partial Credit Model +```jldoctest +julia> beta = (a = 1.3, b = 0.25, t = [0.0, 1.0]); + +julia> irf(GPCM, 0.0, beta) +3-element Vector{Float64}: + 0.27487115408319557 + 0.1986019275522736 + 0.5265269183645309 + +julia> irf(GPCM, 0.0, beta, 1) +0.27487115408319557 +``` + +### Rating Scale Model +```jldoctest +julia> beta = (b = 0.0, t = zeros(2)); + +julia> irf(RSM, 0.0, beta) +3-element Vector{Float64}: + 0.3333333333333333 + 0.3333333333333333 + 0.3333333333333333 + +julia> irf(RSM, 0.0, beta, 3) +0.3333333333333333 +``` + """ function irf(M::Type{OnePL}, theta::Real, beta::Real, y = 1) prob = logistic(theta - beta) @@ -64,3 +108,18 @@ function irf(M::Type{FourPL}, theta::Real, beta::NamedTuple, y = 1) prob = c + (d - c) * logistic(a * (theta - b)) return ifelse(y == 1, prob, 1 - prob) end + +function irf(M::Type{GPCM}, theta, beta) + @unpack a, b, t = beta + extended = zeros(length(t) + 1) + @. extended[2:end] = a * (theta - b + t) + cumsum!(extended, extended) + softmax!(extended, extended) + return extended +end + +irf(M::Type{GPCM}, theta, beta, y) = irf(M, theta, beta)[y] +irf(M::Type{PCM}, theta, beta) = irf(GPCM, theta, merge(beta, (; a = 1.0))) +irf(M::Type{PCM}, theta, beta, y) = irf(GPCM, theta, merge(beta, (; a = 1.0)), y) +irf(M::Type{RSM}, theta, beta) = irf(PCM, theta, beta) +irf(M::Type{RSM}, theta, beta, y) = irf(PCM, theta, beta, y) diff --git a/src/model_types.jl b/src/model_types.jl index d61fec3..9553bcb 100644 --- a/src/model_types.jl +++ b/src/model_types.jl @@ -81,3 +81,64 @@ The item parameters `beta` must be a destructurable object with the following fi """ abstract type FourParameterLogisticModel <: DichotomousItemResponseModel end const FourPL = FourParameterLogisticModel + +""" + $(TYPEDEF) + +An abstract type representing a Generalized Partial Credit Model with an item category +response function given by + +``P(Y_{ij} = y,| \\theta_i, \\boldsymbol{\\beta}_j) = + \\frac{\\exp \\sum_{s=1}^y (a_j (\\theta_i - b_j + t_{js}))} + {1 + \\sum_{k=1}^{K_j} \\exp \\sum_{s=1}^k (a_j (\\theta_i - b_j + t_{js}))}`` + +The item parameters `beta` must be a destructurable object with the following fields: + +- `a`: the item discrimination +- `b`: the item difficulty (location) +- `t`: a vector of threshold parameters + +**Alias:** `GPCM` +""" +abstract type GeneralizedPartialCreditModel <: ItemResponseModel end +const GPCM = GeneralizedPartialCreditModel + +""" + $(TYPEDEF) + +An abstract type representing a Partial Credit Model with an item category response function +given by + +``P(Y_{ij} = y,| \\theta_i, \\boldsymbol{\\beta}_j) = + \\frac{\\exp \\sum_{s=1}^y (\\theta_i - b_j + t_{js})} + {1 + \\sum_{k=1}^{K_j} \\exp \\sum_{s=1}^k (\\theta_i - b_j + t_{js})}`` + +The item parameters `beta` must be a destructurable object with the following fields: + +- `b`: the item difficulty (location) +- `t`: a vector of threshold parameters + +**Alias:** `PCM` +""" +abstract type PartialCreditModel <: ItemResponseModel end +const PCM = PartialCreditModel + +""" + $(TYPEDEF) + +An abstract type representing a Rating Scale Model with an item category response function +given by + +``P(Y_{ij} = y,| \\theta_i, \\boldsymbol{\\beta}_j) = + \\frac{\\exp \\sum_{s=1}^y (\\theta_i - b_j + t_{s})} + {1 + \\sum_{k=1}^{K_j} \\exp \\sum_{s=1}^k (\\theta_i - b_j + t_{s})}`` + +The item parameters `beta` must be a destructurable object with the following fields: + +- `b`: the item difficulty (location) +- `t`: a vector of threshold parameters + +**Alias:** `RSM` +""" +abstract type RatingScaleModel <: ItemResponseModel end +const RSM = RatingScaleModel diff --git a/test/models/generalized_partial_credit_model.jl b/test/models/generalized_partial_credit_model.jl new file mode 100644 index 0000000..bbe01d9 --- /dev/null +++ b/test/models/generalized_partial_credit_model.jl @@ -0,0 +1,22 @@ +@testset "GeneralizedPartialCreditModel" begin + @testset "irf" begin + beta = (a = 1.0, b = 0.0, t = zeros(3)) + @test length(irf(GPCM, 0.0, beta)) == length(beta.t) + 1 + @test sum(irf(GPCM, 0.0, beta)) ≈ 1.0 + @test irf(GPCM, 0.0, beta) == fill(0.25, 4) + @test irf(GPCM, -Inf, beta) == [1.0, 0.0, 0.0, 0.0] + @test irf(GPCM, Inf, beta) == [0.0, 0.0, 0.0, 1.0] + end + + @testset "iif" begin + + end + + @testset "expected_score" begin + + end + + @testset "information" begin + + end +end From f885a43875ba641a4ef0d3f6ea248a6ffc148034 Mon Sep 17 00:00:00 2001 From: p-gw Date: Wed, 15 May 2024 11:05:06 +0200 Subject: [PATCH 02/12] run tests --- test/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/runtests.jl b/test/runtests.jl index 99ecd6a..729e149 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,4 +7,5 @@ using ItemResponseFunctions include("models/two_parameter_logistic.jl") include("models/three_parameter_logistic.jl") include("models/four_parameter_logistic.jl") + include("models/generalized_partial_credit_model.jl") end From 645c6563ab2e453224c6e0a077091abc17e19e8a Mon Sep 17 00:00:00 2001 From: p-gw Date: Wed, 15 May 2024 15:34:41 +0200 Subject: [PATCH 03/12] fix broken test in GPCM --- test/models/generalized_partial_credit_model.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/models/generalized_partial_credit_model.jl b/test/models/generalized_partial_credit_model.jl index bbe01d9..6592bbb 100644 --- a/test/models/generalized_partial_credit_model.jl +++ b/test/models/generalized_partial_credit_model.jl @@ -5,7 +5,8 @@ @test sum(irf(GPCM, 0.0, beta)) ≈ 1.0 @test irf(GPCM, 0.0, beta) == fill(0.25, 4) @test irf(GPCM, -Inf, beta) == [1.0, 0.0, 0.0, 0.0] - @test irf(GPCM, Inf, beta) == [0.0, 0.0, 0.0, 1.0] + @test_broken irf(GPCM, Inf, beta) == [0.0, 0.0, 0.0, 1.0] # issues with Inf in softmax! + @test irf(GPCM, 1e16, beta) == [0.0, 0.0, 0.0, 1.0] end @testset "iif" begin From 1c07f8e644ce0c141b5c25bfe12ea8c3d46f9159 Mon Sep 17 00:00:00 2001 From: Philipp Gewessler Date: Thu, 16 May 2024 16:55:35 +0200 Subject: [PATCH 04/12] add remaining tests --- src/ItemResponseFunctions.jl | 4 +++- src/irf.jl | 2 ++ src/model_types.jl | 21 +++++++++++++++++++ test/models/generalized_rating_scale_model.jl | 4 ++++ test/models/partial_credit_model.jl | 4 ++++ test/models/rating_scale_model.jl | 4 ++++ test/runtests.jl | 3 +++ 7 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 test/models/generalized_rating_scale_model.jl create mode 100644 test/models/partial_credit_model.jl create mode 100644 test/models/rating_scale_model.jl diff --git a/src/ItemResponseFunctions.jl b/src/ItemResponseFunctions.jl index a4de5ac..eaa4bce 100644 --- a/src/ItemResponseFunctions.jl +++ b/src/ItemResponseFunctions.jl @@ -25,7 +25,9 @@ export DichotomousItemResponseModel, PCM, PartialCreditModel, RSM, - RatingScaleModel + RatingScaleModel, + GRSM, + GeneralizedRatingScaleModel include("model_types.jl") include("irf.jl") diff --git a/src/irf.jl b/src/irf.jl index bf84268..00aa95f 100644 --- a/src/irf.jl +++ b/src/irf.jl @@ -123,3 +123,5 @@ irf(M::Type{PCM}, theta, beta) = irf(GPCM, theta, merge(beta, (; a = 1.0))) irf(M::Type{PCM}, theta, beta, y) = irf(GPCM, theta, merge(beta, (; a = 1.0)), y) irf(M::Type{RSM}, theta, beta) = irf(PCM, theta, beta) irf(M::Type{RSM}, theta, beta, y) = irf(PCM, theta, beta, y) +irf(M::Type{GRSM}, theta, beta) = irf(GPCM, theta, beta) +irf(M::Type{GRSM}, theta, beta, y) = irf(GPCM, theta, beta, y) diff --git a/src/model_types.jl b/src/model_types.jl index 9553bcb..aa85db7 100644 --- a/src/model_types.jl +++ b/src/model_types.jl @@ -142,3 +142,24 @@ The item parameters `beta` must be a destructurable object with the following fi """ abstract type RatingScaleModel <: ItemResponseModel end const RSM = RatingScaleModel + +""" + $(TYPEDEF) + +An abstract type representing a Generalized Rating ScaleModel with an item category +response function given by + +``P(Y_{ij} = y,| \\theta_i, \\boldsymbol{\\beta}_j) = + \\frac{\\exp \\sum_{s=1}^y (a_j (\\theta_i - b_j + t_{s}))} + {1 + \\sum_{k=1}^{K_j} \\exp \\sum_{s=1}^k (a_j (\\theta_i - b_j + t_{s}))}`` + +The item parameters `beta` must be a destructurable object with the following fields: + +- `a`: the item discrimination +- `b`: the item difficulty (location) +- `t`: a vector of threshold parameters + +**Alias:** `GRSM` +""" +abstract type GeneralizedRatingScaleModel <: ItemResponseModel end +const GRSM = GeneralizedRatingScaleModel diff --git a/test/models/generalized_rating_scale_model.jl b/test/models/generalized_rating_scale_model.jl new file mode 100644 index 0000000..ae0cbde --- /dev/null +++ b/test/models/generalized_rating_scale_model.jl @@ -0,0 +1,4 @@ +@testset "GeneralizedRatingScaleModel" begin + beta = (a = 1.3, b = 0.2, t = randn(4)) + @test irf(GRSM, 0.0, beta) == irf(GPCM, 0.0, beta) +end diff --git a/test/models/partial_credit_model.jl b/test/models/partial_credit_model.jl new file mode 100644 index 0000000..03a3e88 --- /dev/null +++ b/test/models/partial_credit_model.jl @@ -0,0 +1,4 @@ +@testset "PartialCreditModel" begin + beta = (a = 1.0, b = 0.0, t = randn(3)) + @test irf(PCM, 0.0, beta) == irf(GPCM, 0.0, beta) +end diff --git a/test/models/rating_scale_model.jl b/test/models/rating_scale_model.jl new file mode 100644 index 0000000..5294387 --- /dev/null +++ b/test/models/rating_scale_model.jl @@ -0,0 +1,4 @@ +@testset "RatingScaleModel" begin + beta = (a = 1.0, b = 0.0, t = randn(2)) + @test irf(RSM, 0.0, beta) == irf(GPCM, 0.0, beta) +end diff --git a/test/runtests.jl b/test/runtests.jl index 729e149..87367b7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,4 +8,7 @@ using ItemResponseFunctions include("models/three_parameter_logistic.jl") include("models/four_parameter_logistic.jl") include("models/generalized_partial_credit_model.jl") + include("models/partial_credit_model.jl") + include("models/rating_scale_model.jl") + include("models/generalized_rating_scale_model.jl") end From 56334c3e56fdc8d3c2fdb26d5b4d763a8b1df6ae Mon Sep 17 00:00:00 2001 From: Philipp Gewessler Date: Thu, 16 May 2024 17:52:34 +0200 Subject: [PATCH 05/12] add remaining functions for polytomous models --- src/ItemResponseFunctions.jl | 4 ++- src/expected_score.jl | 46 ++++++++++++++++++++++++++++++++++- src/iif.jl | 47 ++++++++++++++++++++++++++++++++++++ src/information.jl | 22 +++++++++++++++++ src/model_types.jl | 15 +++++++++--- 5 files changed, 128 insertions(+), 6 deletions(-) diff --git a/src/ItemResponseFunctions.jl b/src/ItemResponseFunctions.jl index eaa4bce..6149163 100644 --- a/src/ItemResponseFunctions.jl +++ b/src/ItemResponseFunctions.jl @@ -27,12 +27,14 @@ export DichotomousItemResponseModel, RSM, RatingScaleModel, GRSM, - GeneralizedRatingScaleModel + GeneralizedRatingScaleModel, + partial_credit include("model_types.jl") include("irf.jl") include("iif.jl") include("expected_score.jl") include("information.jl") +include("scoring_functions.jl") end diff --git a/src/expected_score.jl b/src/expected_score.jl index 7e8343f..a295eb9 100644 --- a/src/expected_score.jl +++ b/src/expected_score.jl @@ -62,9 +62,53 @@ function expected_score( ) where {T<:Real,F} score = zero(T) - for beta in betas, y in 0:1 + for beta in betas + score += expected_score(M, theta, beta; scoring_function) + end + + return score +end + +function expected_score( + M::Type{<:DichotomousItemResponseModel}, + theta::T, + beta; + scoring_function::F = identity, +) where {T<:Real,F} + score = zero(T) + for y in 0:1 score += irf(M, theta, beta, y) * scoring_function(y) end + return score +end + +function expected_score( + M::Type{<:PolytomousItemResponseModel}, + theta::T, + betas::AbstractVector; + scoring_function::F = identity, +) where {T<:Real,F} + score = zero(T) + + for beta in betas + score += expected_score(M, theta, beta; scoring_function) + end + + return score +end + +function expected_score( + M::Type{<:PolytomousItemResponseModel}, + theta::T, + beta; + scoring_function::F = identity, +) where {T<:Real,F} + score = zero(T) + probs = irf(M, theta, beta) + + for (category, prob) in enumerate(probs) + score += prob * scoring_function(category) + end return score end diff --git a/src/iif.jl b/src/iif.jl index 66f1736..f699bd1 100644 --- a/src/iif.jl +++ b/src/iif.jl @@ -64,3 +64,50 @@ function iif(M::Type{FourPL}, theta, beta::NamedTuple, y = 1) return info end + +""" + $(SIGNATURES) +""" +function iif(M::Type{GPCM}, theta, beta; scoring_function::F = identity) where {F} + probs = irf(M, theta, beta) + score = expected_score(M, theta, beta) + + info = zero(theta) + + for (category, prob) in enumerate(probs) + info += (scoring_function(category) - score)^2 * prob + end + + info *= beta.a^2 + + return info +end + +function iif(M::Type{GPCM}, theta, beta, y) + prob = irf(M, theta, beta, y) + return prob * iif(M, theta, beta) +end + +function iif(M::Type{PCM}, theta, beta; scoring_function) + return iif(GPCM, theta, merge(beta, (; a = 1.0)); scoring_function) +end + +function iif(M::Type{PCM}, theta, beta, y; scoring_function) + return iif(GPCM, theta, merge(beta, (; a = 1.0)), y; scoring_function) +end + +function iif(M::Type{RSM}, theta, beta; scoring_function) + return iif(PCM, theta, beta; scoring_function) +end + +function iif(M::Type{RSM}, theta, beta, y; scoring_function) + return iif(PCM, theta, beta, y; scoring_function) +end + +function iif(M::Type{GRSM}, theta, beta, y; scoring_function) + return iif(GPCM, theta, beta, y; scoring_function) +end + +function iif(M::Type{GRSM}, theta, beta, y; scoring_function) + return iif(GPCM, theta, beta, y; scoring_function) +end diff --git a/src/information.jl b/src/information.jl index 69d05e3..ea99c71 100644 --- a/src/information.jl +++ b/src/information.jl @@ -57,3 +57,25 @@ function information( return info end + +function information( + M::Type{<:PolytomousItemResponseModel}, + theta, + beta; + scoring_function::F = identity, +) where {F} + return iif(M, theta, beta; scoring_function) +end + +function information( + M::Type{<:PolytomousItemResponseModel}, + theta::T, + betas::AbstractVector; + scoring_function::F = identity, +) where {T<:Real,F} + info = zero(T) + for beta in betas + info += information(M, theta, beta; scoring_function) + end + return info +end diff --git a/src/model_types.jl b/src/model_types.jl index aa85db7..584baaa 100644 --- a/src/model_types.jl +++ b/src/model_types.jl @@ -7,6 +7,13 @@ abstract type DichotomousItemResponseModel <: ItemResponseModel end response_type(::Type{<:DichotomousItemResponseModel}) = Dichotomous +""" + $(TYPEDEF) + +An abstract type representing an item response model with polytomous responses. +""" +abstract type PolytomousItemResponseModel <: ItemResponseModel end + """ $(TYPEDEF) @@ -100,7 +107,7 @@ The item parameters `beta` must be a destructurable object with the following fi **Alias:** `GPCM` """ -abstract type GeneralizedPartialCreditModel <: ItemResponseModel end +abstract type GeneralizedPartialCreditModel <: PolytomousItemResponseModel end const GPCM = GeneralizedPartialCreditModel """ @@ -120,7 +127,7 @@ The item parameters `beta` must be a destructurable object with the following fi **Alias:** `PCM` """ -abstract type PartialCreditModel <: ItemResponseModel end +abstract type PartialCreditModel <: PolytomousItemResponseModel end const PCM = PartialCreditModel """ @@ -140,7 +147,7 @@ The item parameters `beta` must be a destructurable object with the following fi **Alias:** `RSM` """ -abstract type RatingScaleModel <: ItemResponseModel end +abstract type RatingScaleModel <: PolytomousItemResponseModel end const RSM = RatingScaleModel """ @@ -161,5 +168,5 @@ The item parameters `beta` must be a destructurable object with the following fi **Alias:** `GRSM` """ -abstract type GeneralizedRatingScaleModel <: ItemResponseModel end +abstract type GeneralizedRatingScaleModel <: PolytomousItemResponseModel end const GRSM = GeneralizedRatingScaleModel From 218abcc46302bf39ac3c6139e3212ba909c7c68c Mon Sep 17 00:00:00 2001 From: Philipp Gewessler Date: Thu, 16 May 2024 17:52:44 +0200 Subject: [PATCH 06/12] add scoring functions --- src/scoring_functions.jl | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/scoring_functions.jl diff --git a/src/scoring_functions.jl b/src/scoring_functions.jl new file mode 100644 index 0000000..42f4b37 --- /dev/null +++ b/src/scoring_functions.jl @@ -0,0 +1,3 @@ +function partial_credit(n; max_score = 1) + return x -> (x - 1) / (n - 1) * max_score +end From 887d6a43e399fd04de654736f70d8ea309708ef5 Mon Sep 17 00:00:00 2001 From: Philipp Gewessler Date: Fri, 24 May 2024 11:59:36 +0200 Subject: [PATCH 07/12] add 1PLG and code clean up --- src/ItemResponseFunctions.jl | 6 ++- src/expected_score.jl | 42 ++++++++++++------ src/iif.jl | 65 +++++++++------------------- src/irf.jl | 83 +++++++++++++++++++++++++----------- src/model_types.jl | 54 +++++++++++++++++++++++ src/utils.jl | 22 ++++++++++ 6 files changed, 187 insertions(+), 85 deletions(-) create mode 100644 src/utils.jl diff --git a/src/ItemResponseFunctions.jl b/src/ItemResponseFunctions.jl index 6149163..c2a0d9a 100644 --- a/src/ItemResponseFunctions.jl +++ b/src/ItemResponseFunctions.jl @@ -16,6 +16,8 @@ export DichotomousItemResponseModel, FourParameterLogisticModel, OnePL, OneParameterLogisticModel, + OnePLG, + OneParameterLogisticPlusGuessingModel, ThreePL, ThreeParameterLogisticModel, TwoPL, @@ -28,9 +30,11 @@ export DichotomousItemResponseModel, RatingScaleModel, GRSM, GeneralizedRatingScaleModel, - partial_credit + partial_credit, + irf! include("model_types.jl") +include("utils.jl") include("irf.jl") include("iif.jl") include("expected_score.jl") diff --git a/src/expected_score.jl b/src/expected_score.jl index a295eb9..85354d0 100644 --- a/src/expected_score.jl +++ b/src/expected_score.jl @@ -55,12 +55,12 @@ julia> expected_score(FourPL, 0.0, betas) """ function expected_score( - M::Type{<:DichotomousItemResponseModel}, - theta::T, + M::Type{<:ItemResponseModel}, + theta, betas::AbstractVector; scoring_function::F = identity, -) where {T<:Real,F} - score = zero(T) +) where {F} + score = zero(theta) for beta in betas score += expected_score(M, theta, beta; scoring_function) @@ -71,27 +71,32 @@ end function expected_score( M::Type{<:DichotomousItemResponseModel}, - theta::T, - beta; + theta, + beta::Union{<:Real,NamedTuple}; scoring_function::F = identity, -) where {T<:Real,F} - score = zero(T) +) where {F} + score = zero(theta) + for y in 0:1 score += irf(M, theta, beta, y) * scoring_function(y) end + return score end +# for models with non-item specific thresholds vector holding category probabilities can be +# pre-allocated function expected_score( - M::Type{<:PolytomousItemResponseModel}, + M::Type{<:Union{RSM,GRSM}}, theta::T, betas::AbstractVector; scoring_function::F = identity, ) where {T<:Real,F} score = zero(T) + probs = zeros(T, length(first(betas).t) + 1) for beta in betas - score += expected_score(M, theta, beta; scoring_function) + score += _expected_score(M, probs, theta, beta; scoring_function) end return score @@ -100,11 +105,22 @@ end function expected_score( M::Type{<:PolytomousItemResponseModel}, theta::T, - beta; + beta::NamedTuple; scoring_function::F = identity, ) where {T<:Real,F} - score = zero(T) - probs = irf(M, theta, beta) + probs = zeros(T, length(beta.t) + 1) + return _expected_score(M, probs, theta, beta; scoring_function) +end + +function _expected_score( + M::Type{<:PolytomousItemResponseModel}, + probs, + theta, + beta::NamedTuple; + scoring_function::F = identity, +) where {F} + score = zero(theta) + irf!(M, probs, theta, beta) for (category, prob) in enumerate(probs) score += prob * scoring_function(category) diff --git a/src/iif.jl b/src/iif.jl index f699bd1..d5df07a 100644 --- a/src/iif.jl +++ b/src/iif.jl @@ -33,23 +33,6 @@ julia> iif(FourPL, 0.0, (a = 2.1, b = -1.5, c = 0.15, d = 0.9)) ``` """ -function iif(M::Type{OnePL}, theta::Real, beta::Real, y = 1) - prob = irf(M, theta, beta, y) - return prob * (1 - prob) -end - -function iif(M::Type{OnePL}, theta, beta, y = 1) - return iif(FourPL, theta, merge(beta, (a = 1.0, c = 0.0, d = 1.0)), y) -end - -function iif(M::Type{TwoPL}, theta, beta, y = 1) - return iif(FourPL, theta, merge(beta, (; c = 0.0, d = 1.0)), y) -end - -function iif(M::Type{ThreePL}, theta, beta, y = 1) - return iif(FourPL, theta, merge(beta, (; d = 1.0)), y) -end - function iif(M::Type{FourPL}, theta, beta::NamedTuple, y = 1) @unpack a, b, c, d = beta prob = irf(M, theta, beta, y) @@ -65,10 +48,19 @@ function iif(M::Type{FourPL}, theta, beta::NamedTuple, y = 1) return info end -""" - $(SIGNATURES) -""" +function iif(M::Type{<:DichotomousItemResponseModel}, theta, beta, y = 1) + pars = merge_pars(M, beta) + return iif(FourPL, theta, pars, y) +end + +function iif(M::Type{OnePL}, theta::Real, beta::Real, y = 1) + prob = irf(M, theta, beta, y) + return prob * (1 - prob) +end + function iif(M::Type{GPCM}, theta, beta; scoring_function::F = identity) where {F} + @unpack a = beta + probs = irf(M, theta, beta) score = expected_score(M, theta, beta) @@ -78,36 +70,17 @@ function iif(M::Type{GPCM}, theta, beta; scoring_function::F = identity) where { info += (scoring_function(category) - score)^2 * prob end - info *= beta.a^2 + info *= a^2 return info end -function iif(M::Type{GPCM}, theta, beta, y) - prob = irf(M, theta, beta, y) - return prob * iif(M, theta, beta) -end - -function iif(M::Type{PCM}, theta, beta; scoring_function) - return iif(GPCM, theta, merge(beta, (; a = 1.0)); scoring_function) -end - -function iif(M::Type{PCM}, theta, beta, y; scoring_function) - return iif(GPCM, theta, merge(beta, (; a = 1.0)), y; scoring_function) -end - -function iif(M::Type{RSM}, theta, beta; scoring_function) - return iif(PCM, theta, beta; scoring_function) -end - -function iif(M::Type{RSM}, theta, beta, y; scoring_function) - return iif(PCM, theta, beta, y; scoring_function) +function iif(M::Type{<:PolytomousItemResponseModel}, theta, beta) + pars = has_discrimination(M) ? beta : merge(beta, (; a = 1.0)) + return iif(GPCM, theta, pars) end -function iif(M::Type{GRSM}, theta, beta, y; scoring_function) - return iif(GPCM, theta, beta, y; scoring_function) -end - -function iif(M::Type{GRSM}, theta, beta, y; scoring_function) - return iif(GPCM, theta, beta, y; scoring_function) +function iif(M::Type{<:PolytomousItemResponseModel}, theta, beta, y) + prob = irf(M, theta, beta, y) + return prob * iif(M, theta, beta) end diff --git a/src/irf.jl b/src/irf.jl index 00aa95f..c9571dc 100644 --- a/src/irf.jl +++ b/src/irf.jl @@ -8,6 +8,9 @@ ability value `theta` given item parameters `beta`. If `response_type(M) == Dichotomous`, then the item response function is evaluated for a correct response (`y = 1`) by default. +## Models with polytomous responses +if `y` is omitted, then the item (category) response function for all categories is returend. + ## Examples ### 1 Parameter Logistic Model ```jldoctest @@ -86,42 +89,72 @@ julia> irf(RSM, 0.0, beta, 3) ``` """ -function irf(M::Type{OnePL}, theta::Real, beta::Real, y = 1) - prob = logistic(theta - beta) +function irf(M::Type{FourPL}, theta::Real, beta::NamedTuple, y = 1) + @unpack a, b, c, d = beta + prob = c + (d - c) * logistic(a * (theta - b)) return ifelse(y == 1, prob, 1 - prob) end -function irf(M::Type{OnePL}, theta, beta, y = 1) - return irf(FourPL, theta, merge(beta, (a = 1.0, c = 0.0, d = 1.0)), y) +function irf(M::Type{<:DichotomousItemResponseModel}, theta, beta, y = 1) + pars = merge_pars(M, beta) + return irf(FourPL, theta, pars, y) end -function irf(M::Type{TwoPL}, theta, beta, y = 1) - return irf(FourPL, theta, merge(beta, (; c = 0.0, d = 1.0)), y) +function irf(M::Type{OnePL}, theta::Real, beta::Real, y = 1) + prob = logistic(theta - beta) + return ifelse(y == 1, prob, 1 - prob) end -function irf(M::Type{ThreePL}, theta, beta, y = 1) - return irf(FourPL, theta, merge(beta, (; d = 1.0)), y) +function irf(M::Type{GPCM}, theta, beta) + @unpack t = beta + probs = similar(t, length(t) + 1) + return irf!(M, probs, theta, beta) end -function irf(M::Type{FourPL}, theta::Real, beta::NamedTuple, y = 1) - @unpack a, b, c, d = beta - prob = c + (d - c) * logistic(a * (theta - b)) - return ifelse(y == 1, prob, 1 - prob) +function irf(M::Type{<:PolytomousItemResponseModel}, theta, beta) + pars = has_discrimination(M) ? beta : merge(beta, (; a = 1.0)) + return irf(GPCM, theta, pars) end -function irf(M::Type{GPCM}, theta, beta) +irf(M::Type{<:PolytomousItemResponseModel}, theta, beta, y) = irf(M, theta, beta)[y] + +""" + $(SIGNATURES) + +An in-place version of [`irf`](@ref) for polytomous item response models. +Provides efficient computation by mutating `probs` in-place, thus avoiding allocation of an +output vector. + +## Examples +```jldoctest +julia> beta = (a = 1.2, b = 0.3, t = zeros(3)); + +julia> probs = zeros(length(beta.t) + 1); + +julia> irf!(GPCM, probs, 0.0, beta) +4-element Vector{Float64}: + 0.3961927292844976 + 0.2764142877832629 + 0.19284770477416754 + 0.13454527815807202 + +julia> probs +4-element Vector{Float64}: + 0.3961927292844976 + 0.2764142877832629 + 0.19284770477416754 + 0.13454527815807202 +``` +""" +function irf!(M::Type{GPCM}, probs, theta, beta) @unpack a, b, t = beta - extended = zeros(length(t) + 1) - @. extended[2:end] = a * (theta - b + t) - cumsum!(extended, extended) - softmax!(extended, extended) - return extended + probs[1] = 0.0 + @. probs[2:end] = a * (theta - b + t) + cumsum!(probs, probs) + softmax!(probs, probs) + return probs end -irf(M::Type{GPCM}, theta, beta, y) = irf(M, theta, beta)[y] -irf(M::Type{PCM}, theta, beta) = irf(GPCM, theta, merge(beta, (; a = 1.0))) -irf(M::Type{PCM}, theta, beta, y) = irf(GPCM, theta, merge(beta, (; a = 1.0)), y) -irf(M::Type{RSM}, theta, beta) = irf(PCM, theta, beta) -irf(M::Type{RSM}, theta, beta, y) = irf(PCM, theta, beta, y) -irf(M::Type{GRSM}, theta, beta) = irf(GPCM, theta, beta) -irf(M::Type{GRSM}, theta, beta, y) = irf(GPCM, theta, beta, y) +function irf!(M::Type{<:PolytomousItemResponseModel}, probs, theta, beta) + return irf!(GPCM, probs, theta, beta) +end diff --git a/src/model_types.jl b/src/model_types.jl index 584baaa..c6021b1 100644 --- a/src/model_types.jl +++ b/src/model_types.jl @@ -32,6 +32,32 @@ following fields: abstract type OneParameterLogisticModel <: DichotomousItemResponseModel end const OnePL = OneParameterLogisticModel +has_discrimination(::Type{OnePL}) = false +has_lower_asymptote(::Type{OnePL}) = false +has_upper_asymptote(::Type{OnePL}) = false + +""" + $(TYPEDEF) + +An abstract representation of the 1 Parameter Logistic + Guessing Model with an item +response function given by + +``P(Y_{ij}=1|\\theta_i,\\boldsymbol{\\beta}_j) = c + (1 - c) \\cdot \\mathrm{logistic}(\\theta_i - b_j)`` + +The item parameters `beta` must be a destructurable object with the following fields: + +- `b`: the item difficulty (location) +- `c`: the lower asymptote + +**Alias:** `OnePLG` +""" +abstract type OneParameterLogisticPlusGuessingModel <: DichotomousItemResponseModel end +const OnePLG = OneParameterLogisticPlusGuessingModel + +has_discrimination(::Type{OnePLG}) = false +has_lower_asymptote(::Type{OnePLG}) = true +has_upper_asymptote(::Type{OnePLG}) = false + """ $(TYPEDEF) @@ -50,6 +76,10 @@ The item parameters `beta` must be a destructurable object with the following fi abstract type TwoParameterLogisticModel <: DichotomousItemResponseModel end const TwoPL = TwoParameterLogisticModel +has_discrimination(::Type{TwoPL}) = true +has_lower_asymptote(::Type{TwoPL}) = false +has_upper_asymptote(::Type{TwoPL}) = false + """ $(TYPEDEF) @@ -69,6 +99,10 @@ The item parameters `beta` must be a destructurable object with the following fi abstract type ThreeParameterLogisticModel <: DichotomousItemResponseModel end const ThreePL = ThreeParameterLogisticModel +has_discrimination(::Type{ThreePL}) = true +has_lower_asymptote(::Type{ThreePL}) = true +has_upper_asymptote(::Type{ThreePL}) = false + """ $(TYPEDEF) @@ -89,6 +123,10 @@ The item parameters `beta` must be a destructurable object with the following fi abstract type FourParameterLogisticModel <: DichotomousItemResponseModel end const FourPL = FourParameterLogisticModel +has_discrimination(::Type{FourPL}) = true +has_lower_asymptote(::Type{FourPL}) = true +has_upper_asymptote(::Type{FourPL}) = true + """ $(TYPEDEF) @@ -110,6 +148,10 @@ The item parameters `beta` must be a destructurable object with the following fi abstract type GeneralizedPartialCreditModel <: PolytomousItemResponseModel end const GPCM = GeneralizedPartialCreditModel +has_discrimination(::Type{GPCM}) = true +has_lower_asymptote(::Type{GPCM}) = false +has_upper_asymptote(::Type{GPCM}) = false + """ $(TYPEDEF) @@ -130,6 +172,10 @@ The item parameters `beta` must be a destructurable object with the following fi abstract type PartialCreditModel <: PolytomousItemResponseModel end const PCM = PartialCreditModel +has_discrimination(::Type{PCM}) = false +has_lower_asymptote(::Type{PCM}) = false +has_upper_asymptote(::Type{PCM}) = false + """ $(TYPEDEF) @@ -150,6 +196,10 @@ The item parameters `beta` must be a destructurable object with the following fi abstract type RatingScaleModel <: PolytomousItemResponseModel end const RSM = RatingScaleModel +has_discrimination(::Type{RSM}) = false +has_lower_asymptote(::Type{RSM}) = false +has_upper_asymptote(::Type{RSM}) = false + """ $(TYPEDEF) @@ -170,3 +220,7 @@ The item parameters `beta` must be a destructurable object with the following fi """ abstract type GeneralizedRatingScaleModel <: PolytomousItemResponseModel end const GRSM = GeneralizedRatingScaleModel + +has_discrimination(::Type{GRSM}) = true +has_lower_asymptote(::Type{GRSM}) = false +has_upper_asymptote(::Type{GRSM}) = false diff --git a/src/utils.jl b/src/utils.jl new file mode 100644 index 0000000..b4df3e1 --- /dev/null +++ b/src/utils.jl @@ -0,0 +1,22 @@ +""" + $(SIGNATURES) + +Append parameters to `beta` based on `M`s traits. +""" +function merge_pars(M::Type{<:ItemResponseModel}, beta) + pars = deepcopy(beta) + + if !has_discrimination(M) + pars = merge(pars, (; a = 1.0)) + end + + if !has_lower_asymptote(M) + pars = merge(pars, (; c = 0.0)) + end + + if !has_upper_asymptote(M) + pars = merge(pars, (; d = 1.0)) + end + + return pars +end From f704f72590a4ace334e6c3381acee17bad52d655 Mon Sep 17 00:00:00 2001 From: Philipp Gewessler Date: Fri, 24 May 2024 12:00:05 +0200 Subject: [PATCH 08/12] add docs --- Project.toml | 2 +- docs/src/api.md | 3 +++ docs/src/index.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 3c5b8d5..205e1db 100644 --- a/Project.toml +++ b/Project.toml @@ -11,12 +11,12 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69" SimpleUnPack = "ce78b400-467f-4804-87d8-8f486da07d0a" [compat] -julia = "1.8" AbstractItemResponseModels = "0.2" DocStringExtensions = "0.9" LogExpFunctions = "0.3" Reexport = "1" SimpleUnPack = "1" +julia = "1.8" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/docs/src/api.md b/docs/src/api.md index bf8ddec..f27a4a7 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -7,17 +7,20 @@ CurrentModule = ItemResponseFunctions ## Types ```@docs OneParameterLogisticModel +OneParameterLogisticPlusGuessingModel TwoParameterLogisticModel ThreeParameterLogisticModel FourParameterLogisticModel PartialCreditModel GeneralizedPartialCreditModel RatingScaleModel +GeneralizedRatingScaleModel ``` ## Functions ```@docs irf +irf! iif expected_score information diff --git a/docs/src/index.md b/docs/src/index.md index a17a469..d704310 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -6,3 +6,45 @@ [![Coverage](https://codecov.io/gh/p-gw/ItemResponseFunctions.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/p-gw/ItemResponseFunctions.jl) [ItemResponseFunctions.jl](https://github.com/p-gw/ItemResponseFunctions.jl) implements basic functions for Item Response Theory models. It is built based on the interface designed in [AbstractItemResponseModels.jl](https://github.com/JuliaPsychometrics/AbstractItemResponseModels.jl). + +## Installation +You can install ItemResponseFunctions.jl from Github + +```julia +] add https://github.com/p-gw/ItemResponseFunctions.jl.git +``` + +## Usage +ItemResponseFunctions.jl exports the following functions for Item Response Theory models: + +- `irf`: The item response function +- `iif`: The item information function +- `expected_score`: The expected score / test response function +- `information`: The test information function + +Calling the function requires a model type `M`, a person ability `theta` and item parameters `beta`. +For a simple 1-Parameter Logistic model, + +```julia +using ItemResponseFunctions + +beta = (; b = 0.5) + +irf(OnePL, 0.0, beta) +iif(OnePL, 0.0, beta) +``` + +evaluates the item response function and item information function at ability value `0.0` for an item with difficulty `0.5`. + +Given an array of item parameters (a test) and an ability value, the test response function and test information can be calculated by + +```julia +betas = [ + (; b = -0.3), + (; b = 0.25), + (; b = 1.0), +] + +expected_score(OnePL, 0.0, betas) +information(OnePL, 0.0, betas) +``` From 27b233224013752ce2214108887c78d55eaa3837 Mon Sep 17 00:00:00 2001 From: Philipp Gewessler Date: Fri, 24 May 2024 12:11:11 +0200 Subject: [PATCH 09/12] use merge_pars --- src/iif.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iif.jl b/src/iif.jl index 2fe74b2..4d9b306 100644 --- a/src/iif.jl +++ b/src/iif.jl @@ -81,7 +81,7 @@ function iif( beta; scoring_function::F = identity, ) where {F} - pars = has_discrimination(M) ? beta : merge(beta, (; a = 1.0)) + pars = merge_pars(GPCM, beta) return iif(GPCM, theta, pars; scoring_function) end From 5190a509a7533f1ac41f43fd467d7c04ba11807f Mon Sep 17 00:00:00 2001 From: Philipp Gewessler Date: Fri, 24 May 2024 12:16:03 +0200 Subject: [PATCH 10/12] add trait tests --- test/models/four_parameter_logistic.jl | 4 ++++ test/models/generalized_partial_credit_model.jl | 5 +++++ test/models/generalized_rating_scale_model.jl | 5 +++++ test/models/one_parameter_logistic.jl | 5 +++++ test/models/partial_credit_model.jl | 5 +++++ test/models/rating_scale_model.jl | 5 +++++ test/models/three_parameter_logistic.jl | 4 ++++ test/models/two_parameter_logistic.jl | 4 ++++ test/runtests.jl | 1 + 9 files changed, 38 insertions(+) diff --git a/test/models/four_parameter_logistic.jl b/test/models/four_parameter_logistic.jl index 7d0c21a..36e9179 100644 --- a/test/models/four_parameter_logistic.jl +++ b/test/models/four_parameter_logistic.jl @@ -1,6 +1,10 @@ @testset "FourParameterLogisticModel" begin T = FourParameterLogisticModel + @test has_discrimination(T) == true + @test has_lower_asymptote(T) == true + @test has_upper_asymptote(T) == true + @testset "irf" begin beta = (a = 1.0, b = 0.0, c = 0.0, d = 1.0) @test irf(T, 0.0, beta) == 0.5 diff --git a/test/models/generalized_partial_credit_model.jl b/test/models/generalized_partial_credit_model.jl index a2e598f..7c7502c 100644 --- a/test/models/generalized_partial_credit_model.jl +++ b/test/models/generalized_partial_credit_model.jl @@ -1,4 +1,9 @@ @testset "GeneralizedPartialCreditModel" begin + + @test has_discrimination(GPCM) == true + @test has_lower_asymptote(GPCM) == false + @test has_upper_asymptote(GPCM) == false + @testset "irf" begin beta = (a = 1.0, b = 0.0, t = zeros(3)) @test length(irf(GPCM, 0.0, beta)) == length(beta.t) + 1 diff --git a/test/models/generalized_rating_scale_model.jl b/test/models/generalized_rating_scale_model.jl index 3532a0c..1a2fc68 100644 --- a/test/models/generalized_rating_scale_model.jl +++ b/test/models/generalized_rating_scale_model.jl @@ -1,4 +1,9 @@ @testset "GeneralizedRatingScaleModel" begin + + @test has_discrimination(GRSM) == true + @test has_lower_asymptote(GRSM) == false + @test has_upper_asymptote(GRSM) == false + beta = (a = 1.3, b = 0.2, t = randn(4)) @test irf(GRSM, 0.0, beta) == irf(GPCM, 0.0, beta) @test irf(GRSM, 0.0, beta, 1) == irf(GPCM, 0.0, beta, 1) diff --git a/test/models/one_parameter_logistic.jl b/test/models/one_parameter_logistic.jl index fdacd98..1082f30 100644 --- a/test/models/one_parameter_logistic.jl +++ b/test/models/one_parameter_logistic.jl @@ -1,5 +1,10 @@ @testset "OneParameterLogisticModel" begin T = OneParameterLogisticModel + + @test has_discrimination(T) == false + @test has_lower_asymptote(T) == false + @test has_upper_asymptote(T) == false + @testset "irf" begin @test irf(T, 0.0, 0.0, 1) == 0.5 @test irf(T, 0.0, 0.0, 0) == 0.5 diff --git a/test/models/partial_credit_model.jl b/test/models/partial_credit_model.jl index f5c5b93..cedde79 100644 --- a/test/models/partial_credit_model.jl +++ b/test/models/partial_credit_model.jl @@ -1,4 +1,9 @@ @testset "PartialCreditModel" begin + + @test has_discrimination(PCM) == false + @test has_lower_asymptote(PCM) == false + @test has_upper_asymptote(PCM) == false + beta = (a = 1.0, b = 0.0, t = randn(3)) @test irf(PCM, 0.0, beta) == irf(GPCM, 0.0, beta) @test irf(PCM, 0.0, beta, 1) == irf(GPCM, 0.0, beta, 1) diff --git a/test/models/rating_scale_model.jl b/test/models/rating_scale_model.jl index 776ed8d..673a8b6 100644 --- a/test/models/rating_scale_model.jl +++ b/test/models/rating_scale_model.jl @@ -1,4 +1,9 @@ @testset "RatingScaleModel" begin + + @test has_discrimination(RSM) == false + @test has_lower_asymptote(RSM) == false + @test has_upper_asymptote(RSM) == false + beta = (a = 1.0, b = 0.0, t = randn(2)) @test irf(RSM, 0.0, beta) == irf(GPCM, 0.0, beta) @test irf(RSM, 0.0, beta, 1) == irf(GPCM, 0.0, beta, 1) diff --git a/test/models/three_parameter_logistic.jl b/test/models/three_parameter_logistic.jl index 62efa2a..5808bfb 100644 --- a/test/models/three_parameter_logistic.jl +++ b/test/models/three_parameter_logistic.jl @@ -1,6 +1,10 @@ @testset "ThreeParameterLogisticModel" begin T = ThreeParameterLogisticModel + @test has_discrimination(T) == true + @test has_lower_asymptote(T) == true + @test has_upper_asymptote(T) == false + @testset "irf" begin beta = (a = 1.5, b = 0.0, c = 0.2) @test irf(T, 0.0, beta) ≈ 0.5 + beta.c / 2 diff --git a/test/models/two_parameter_logistic.jl b/test/models/two_parameter_logistic.jl index a79d0a1..26c48ff 100644 --- a/test/models/two_parameter_logistic.jl +++ b/test/models/two_parameter_logistic.jl @@ -1,6 +1,10 @@ @testset "TwoParameterLogisticModel" begin T = TwoParameterLogisticModel + @test has_discrimination(T) == true + @test has_lower_asymptote(T) == false + @test has_upper_asymptote(T) == false + @testset "irf" begin @test irf(T, 0.0, (a = 1.5, b = 0.0)) == 0.5 @test irf(T, Inf, (a = 1.5, b = 0.0)) == 1.0 diff --git a/test/runtests.jl b/test/runtests.jl index 21d8f3d..9ce9742 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,7 @@ using Test using ItemResponseFunctions +using ItemResponseFunctions: has_discrimination, has_upper_asymptote, has_lower_asymptote @testset "ItemResponseFunctions.jl" begin include("models/one_parameter_logistic.jl") From facc0a1de66ba4df66fce25b8a766f2ce128366d Mon Sep 17 00:00:00 2001 From: Philipp Gewessler Date: Fri, 24 May 2024 12:25:15 +0200 Subject: [PATCH 11/12] add tests for 1PLG --- test/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/runtests.jl b/test/runtests.jl index 9ce9742..42b8c5e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,6 +5,7 @@ using ItemResponseFunctions: has_discrimination, has_upper_asymptote, has_lower_ @testset "ItemResponseFunctions.jl" begin include("models/one_parameter_logistic.jl") + include("models/one_parameter_logistic_plus_guessing.jl") include("models/two_parameter_logistic.jl") include("models/three_parameter_logistic.jl") include("models/four_parameter_logistic.jl") From 77de849e9ac4f517a1981049c991e831512862fc Mon Sep 17 00:00:00 2001 From: Philipp Gewessler Date: Fri, 24 May 2024 12:31:23 +0200 Subject: [PATCH 12/12] add test file... --- .../one_parameter_logistic_plus_guessing.jl | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 test/models/one_parameter_logistic_plus_guessing.jl diff --git a/test/models/one_parameter_logistic_plus_guessing.jl b/test/models/one_parameter_logistic_plus_guessing.jl new file mode 100644 index 0000000..a96f818 --- /dev/null +++ b/test/models/one_parameter_logistic_plus_guessing.jl @@ -0,0 +1,43 @@ +@testset "OneParameterLogisticPlusGuessingModel" begin + T = OneParameterLogisticPlusGuessingModel + + @test has_discrimination(T) == false + @test has_lower_asymptote(T) == true + @test has_upper_asymptote(T) == false + + @testset "irf" begin + @test irf(T, 0.0, (; b = 0.0, c = 0.0), 1) == 0.5 + @test irf(T, 0.0, (; b = 0.0, c = 0.0), 0) == 0.5 + @test irf(T, -Inf, (; b = 0.0, c = 0.0), 1) == 0.0 + @test irf(T, Inf, (; b = 0.0, c = 0.0), 1) == 1.0 + + @test irf(T, -Inf, (; b = 0.0, c = 0.2), 1) == 0.2 + @test irf(T, Inf, (; b = 0.0, c = 0.2), 1) == 1.0 + end + + @testset "iif" begin + beta = (b = 0.0, c = 0.0) + @test iif(T, 0.0, beta, 1) == 0.25 + @test iif(T, -Inf, beta, 1) == 0.0 + @test iif(T, Inf, beta, 1) == 0.0 + + beta = (b = 0.0, c = 0.1) + @test iif(T, -Inf, beta, 1) == 0.0 + @test iif(T, Inf, beta, 1) == 0.0 + end + + @testset "expected_score" begin + beta = (b = 0.0, c = 0.2) + betas = fill(beta, 10) + @test expected_score(T, 0.0, betas) ≈ 0.6 * 10 + @test expected_score(T, -Inf, betas) ≈ 0.2 * 10 + @test expected_score(T, Inf, betas) ≈ 1.0 * 10 + end + + @testset "information" begin + beta = (b = 0.0, c = 0.25) + betas = fill(beta, 5) + @test information(T, -Inf, betas) == 0.0 + @test information(T, Inf, betas) == 0.0 + end +end