Skip to content

Commit 3dbc259

Browse files
authored
Merge pull request #7 from JuliaPsychometricsBazaar/add-slope-intercept-mirt-item-bank
Add SlopeInterceptMirtItemBank
2 parents 39e8bf3 + aec6c73 commit 3dbc259

File tree

3 files changed

+157
-1
lines changed

3 files changed

+157
-1
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "FittedItemBanks"
22
uuid = "3f797b09-34e4-41d7-acf6-3302ae3248a5"
33
authors = ["Frankie Robertson <frankie@robertson.name>"]
4-
version = "0.7.0"
4+
version = "0.7.1"
55

66
[deps]
77
ArraysOfArrays = "65a8f2f4-9b39-5baf-92e2-a9cc46fdf018"

src/FittedItemBanks.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export AbstractItemBank
99
export GuessAndSlipItemBank
1010
export GuessItemBank, SlipItemBank, FixedGuessItemBank, FixedSlipItemBank
1111
export TransferItemBank, SlopeInterceptTransferItemBank
12+
export CdfMirtItemBank, SlopeInterceptMirtItemBank
1213
export ItemBank2PL, ItemBank3PL, ItemBank4PL
1314
export ItemBankMirt2PL, ItemBankMirt3PL, ItemBankMirt4PL
1415
export NominalItemBank, GPCMItemBank

src/cdf_mirt_items.jl

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,158 @@ function spec_description(item_bank::CdfMirtItemBank, level)
172172
end
173173
end
174174
end
175+
176+
"""
177+
```julia
178+
struct $(FUNCTIONNAME) <: AbstractItemBank
179+
$(FUNCTIONNAME)(distribution, difficulties, discriminations) -> $(FUNCTIONNAME)
180+
DomainType(::SlopeInterceptMirtItemBank) = VectorContinuousDomain()
181+
ResponseType(::SlopeInterceptMirtItemBank) = BooleanResponse()
182+
```
183+
184+
This item bank corresponds to the slope-intercept version of MIRT in the
185+
literature. Its items feature multidimensional discriminations and its learners
186+
multidimensional abilities, but item difficulties are single-dimensional.
187+
"""
188+
struct SlopeInterceptMirtItemBank{DistT <: ContinuousUnivariateDistribution} <: AbstractItemBank
189+
distribution::DistT
190+
intercepts::Vector{Float64}
191+
slopes::Matrix{Float64}
192+
193+
function SlopeInterceptMirtItemBank(
194+
distribution::DistT,
195+
intercepts::Vector{Float64},
196+
slopes::Matrix{Float64}
197+
) where {DistT <: ContinuousUnivariateDistribution}
198+
if size(slopes, 2) != length(intercepts)
199+
error(
200+
"Number of items in first (only) dimension of difficulties " *
201+
"should match number of item in 2nd dimension of discriminations"
202+
)
203+
end
204+
new{typeof(distribution)}(distribution, intercepts, slopes)
205+
end
206+
end
207+
208+
function SlopeInterceptMirtItemBank(item_bank::CdfMirtItemBank)
209+
SlopeInterceptMirtItemBank(
210+
item_bank.distribution,
211+
[diff * sum(disc) for (diff, disc) in zip(item_bank.difficulties, eachcol(item_bank.discriminations))],
212+
item_bank.discriminations
213+
)
214+
end
215+
216+
function CdfMirtItemBank(item_bank::SlopeInterceptMirtItemBank)
217+
CdfMirtItemBank(
218+
item_bank.distribution,
219+
[intercept / sum(slope) for (intercept, slope) in zip(item_bank.intercepts, eachcol(item_bank.slopes))],
220+
item_bank.slopes
221+
)
222+
end
223+
224+
DomainType(::SlopeInterceptMirtItemBank) = VectorContinuousDomain()
225+
ResponseType(::SlopeInterceptMirtItemBank) = BooleanResponse()
226+
227+
function Base.length(item_bank::SlopeInterceptMirtItemBank)
228+
length(item_bank.intercepts)
229+
end
230+
231+
function subset(item_bank::SlopeInterceptMirtItemBank, idxs)
232+
SlopeInterceptMirtItemBank(
233+
item_bank.distribution,
234+
item_bank.intercepts[idxs],
235+
item_bank.slopes[:, idxs]
236+
)
237+
end
238+
239+
function domdims(item_bank::SlopeInterceptMirtItemBank)
240+
size(item_bank.slopes, 1)
241+
end
242+
243+
function _mirt_norm_abil_si(θ, intercept, slope)
244+
dot(θ, slope) .- intercept
245+
end
246+
247+
function norm_abil(ir::ItemResponse{<:SlopeInterceptMirtItemBank}, θ)
248+
_mirt_norm_abil_si(θ, ir.item_bank.intercepts[ir.index],
249+
@view ir.item_bank.slopes[:, ir.index])
250+
end
251+
252+
function resp_vec(ir::ItemResponse{<:SlopeInterceptMirtItemBank}, θ)
253+
resp1 = resp(ir, θ)
254+
SVector(1.0 - resp1, resp1)
255+
end
256+
257+
#=
258+
function item_domain(
259+
ir::ItemResponse{<:SlopeInterceptMirtItemBank}; reference_point, mass = default_mass, left_mass = mass, right_mass = mass)
260+
ndims = domdims(ir.item_bank)
261+
z_lo = quantile(ir.item_bank.distribution, left_mass)
262+
z_hi = quantile(ir.item_bank.distribution, 1.0 - right_mass)
263+
lo = fill(Inf, ndims)
264+
hi = fill(-Inf, ndims)
265+
difficulty = ir.item_bank.difficulties[ir.index]
266+
discrimination = @view ir.item_bank.discriminations[:, ir.index]
267+
diff_disc = sum(difficulty .* discrimination)
268+
function nearest_point(z)
269+
c = z + diff_disc
270+
t = (c - dot(reference_point, discrimination)) / sum(discrimination .^ 2)
271+
return reference_point .+ t .* discrimination
272+
end
273+
function update_bounds!(point)
274+
for i in 1:ndims
275+
if point[i] < lo[i]
276+
lo[i] = point[i]
277+
end
278+
if point[i] > hi[i]
279+
hi[i] = point[i]
280+
end
281+
end
282+
end
283+
update_bounds!(nearest_point(z_lo))
284+
update_bounds!(nearest_point(z_hi))
285+
return (lo, hi)
286+
end
287+
=#
288+
289+
function resp(ir::ItemResponse{<:SlopeInterceptMirtItemBank}, outcome::Bool, θ)
290+
if outcome
291+
resp(ir, θ)
292+
else
293+
cresp(ir, θ)
294+
end
295+
end
296+
297+
function resp(ir::ItemResponse{<:SlopeInterceptMirtItemBank}, θ)
298+
cdf(ir.item_bank.distribution, norm_abil(ir, θ))
299+
end
300+
301+
function cresp(ir::ItemResponse{<:SlopeInterceptMirtItemBank}, θ)
302+
ccdf(ir.item_bank.distribution, norm_abil(ir, θ))
303+
end
304+
305+
function item_params(item_bank::SlopeInterceptMirtItemBank, idx)
306+
(; intercept = item_bank.intercepts[idx],
307+
slop = @view item_bank.slopes[:, idx])
308+
end
309+
310+
function spec_description(item_bank::SlopeInterceptMirtItemBank, level)
311+
dim = length(item_bank.slopes)
312+
if item_bank.distribution == normal_scaled_logistic
313+
if level == :long
314+
return "Two parameter $(dim)-dimensional slope-intercept multidimensional item bank with normal scaled logistic distribution"
315+
elseif level == :short
316+
return "2PL MIRT $(dim)d"
317+
else
318+
return "2pl_mirt_$(dim)d"
319+
end
320+
else
321+
if level == :long
322+
return "Two parameter $(dim)-dimensional slope-intercept multidimensional item bank with unknown transfer function"
323+
elseif level == :short
324+
return "2P MIRT $(dim)d"
325+
else
326+
return "2p_mirt_$(dim)d"
327+
end
328+
end
329+
end

0 commit comments

Comments
 (0)