Skip to content

Commit 212ec5b

Browse files
authored
Add Two-Parameter IRT Model and Corresponding Tests (#3)
- Implement Two-Parameter Model (2PL) for Item Response Theory - Add tests for Two-Parameter Model - Update main library file to include the new model - Adjust README for the new model usage
1 parent f61c9d4 commit 212ec5b

5 files changed

Lines changed: 96 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
# IrtRuby
33

4-
IrtRuby is a Ruby gem that provides a simple implementation of the Rasch model for Item Response Theory (IRT). It allows you to estimate the abilities of individuals and the difficulties of items based on their responses to a set of items.
4+
IrtRuby is a Ruby gem that provides implementations of the Rasch model and the Two-Parameter model for Item Response Theory (IRT). It allows you to estimate the abilities of individuals and the difficulties of items based on their responses to a set of items.
55

66
## Installation
77

lib/irt_ruby.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require "irt_ruby/version"
44
require "irt_ruby/rasch_model"
5+
require "irt_ruby/two_parameter_model"
56

67
module IrtRuby
78
class Error < StandardError; end
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# frozen_string_literal: true
2+
3+
require "matrix"
4+
5+
module IrtRuby
6+
# A class representing the Two-Parameter model for Item Response Theory.
7+
class TwoParameterModel
8+
def initialize(data)
9+
@data = data
10+
@abilities = Array.new(data.row_count) { rand }
11+
@difficulties = Array.new(data.column_count) { rand }
12+
@discriminations = Array.new(data.column_count) { rand }
13+
@max_iter = 1000
14+
@tolerance = 1e-6
15+
end
16+
17+
def sigmoid(x)
18+
1.0 / (1.0 + Math.exp(-x))
19+
end
20+
21+
def likelihood
22+
likelihood = 0
23+
@data.row_vectors.each_with_index do |row, i|
24+
row.to_a.each_with_index do |response, j|
25+
prob = sigmoid(@discriminations[j] * (@abilities[i] - @difficulties[j]))
26+
if response == 1
27+
likelihood += Math.log(prob)
28+
elsif response.zero?
29+
likelihood += Math.log(1 - prob)
30+
end
31+
end
32+
end
33+
likelihood
34+
end
35+
36+
def update_parameters
37+
last_likelihood = likelihood
38+
@max_iter.times do |_iter|
39+
@data.row_vectors.each_with_index do |row, i|
40+
row.to_a.each_with_index do |response, j|
41+
prob = sigmoid(@discriminations[j] * (@abilities[i] - @difficulties[j]))
42+
error = response - prob
43+
@abilities[i] += 0.01 * error * @discriminations[j]
44+
@difficulties[j] -= 0.01 * error * @discriminations[j]
45+
@discriminations[j] += 0.01 * error * (@abilities[i] - @difficulties[j])
46+
end
47+
end
48+
current_likelihood = likelihood
49+
break if (last_likelihood - current_likelihood).abs < @tolerance
50+
51+
last_likelihood = current_likelihood
52+
end
53+
end
54+
55+
def fit
56+
update_parameters
57+
{ abilities: @abilities, difficulties: @difficulties, discriminations: @discriminations }
58+
end
59+
end
60+
end

spec/irt_ruby/rasch_model_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
require "irt_ruby"
3+
require "spec_helper"
44

55
RSpec.describe IrtRuby::RaschModel do
66
let(:data) { Matrix[[1, 0, 1], [0, 1, 0], [1, 1, 1]] }
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
require "spec_helper"
2+
3+
RSpec.describe IrtRuby::TwoParameterModel do
4+
let(:data) { Matrix[[1, 0, 1], [0, 1, 0], [1, 1, 1]] }
5+
let(:model) { IrtRuby::TwoParameterModel.new(data) }
6+
7+
describe "#initialize" do
8+
it "initializes with data" do
9+
expect(model.instance_variable_get(:@data)).to eq(data)
10+
end
11+
end
12+
13+
describe "#sigmoid" do
14+
it "calculates the sigmoid function" do
15+
expect(model.sigmoid(0)).to eq(0.5)
16+
end
17+
end
18+
19+
describe "#likelihood" do
20+
it "calculates the likelihood of the data" do
21+
expect(model.likelihood).to be_a(Float)
22+
end
23+
end
24+
25+
describe "#fit" do
26+
it "fits the model and returns abilities, difficulties, and discriminations" do
27+
result = model.fit
28+
expect(result[:abilities].size).to eq(data.row_count)
29+
expect(result[:difficulties].size).to eq(data.column_count)
30+
expect(result[:discriminations].size).to eq(data.column_count)
31+
end
32+
end
33+
end

0 commit comments

Comments
 (0)