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 @@
[](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