Skip to content

Commit

Permalink
Add Two-Parameter IRT Model and Corresponding Tests
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
kholdrex committed Jun 9, 2024
1 parent f61c9d4 commit 8e86eac
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

# IrtRuby

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.
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.

## Installation

Expand Down
1 change: 1 addition & 0 deletions lib/irt_ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require "irt_ruby/version"
require "irt_ruby/rasch_model"
require "irt_ruby/two_parameter_model"

module IrtRuby
class Error < StandardError; end
Expand Down
60 changes: 60 additions & 0 deletions lib/irt_ruby/two_parameter_model.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

require "matrix"

module IrtRuby
# A class representing the Two-Parameter model for Item Response Theory.
class TwoParameterModel
def initialize(data)
@data = data
@abilities = Array.new(data.row_count) { rand }
@difficulties = Array.new(data.column_count) { rand }
@discriminations = Array.new(data.column_count) { rand }
@max_iter = 1000
@tolerance = 1e-6
end

def sigmoid(x)
1.0 / (1.0 + Math.exp(-x))
end

def likelihood
likelihood = 0
@data.row_vectors.each_with_index do |row, i|
row.to_a.each_with_index do |response, j|
prob = sigmoid(@discriminations[j] * (@abilities[i] - @difficulties[j]))
if response == 1
likelihood += Math.log(prob)
elsif response.zero?
likelihood += Math.log(1 - prob)
end
end
end
likelihood
end

def update_parameters
last_likelihood = likelihood
@max_iter.times do |_iter|
@data.row_vectors.each_with_index do |row, i|
row.to_a.each_with_index do |response, j|
prob = sigmoid(@discriminations[j] * (@abilities[i] - @difficulties[j]))
error = response - prob
@abilities[i] += 0.01 * error * @discriminations[j]
@difficulties[j] -= 0.01 * error * @discriminations[j]
@discriminations[j] += 0.01 * error * (@abilities[i] - @difficulties[j])
end
end
current_likelihood = likelihood
break if (last_likelihood - current_likelihood).abs < @tolerance

last_likelihood = current_likelihood
end
end

def fit
update_parameters
{ abilities: @abilities, difficulties: @difficulties, discriminations: @discriminations }
end
end
end
2 changes: 1 addition & 1 deletion spec/irt_ruby/rasch_model_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require "irt_ruby"
require "spec_helper"

RSpec.describe IrtRuby::RaschModel do
let(:data) { Matrix[[1, 0, 1], [0, 1, 0], [1, 1, 1]] }
Expand Down
33 changes: 33 additions & 0 deletions spec/irt_ruby/two_parameter_model_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require "spec_helper"

RSpec.describe IrtRuby::TwoParameterModel do
let(:data) { Matrix[[1, 0, 1], [0, 1, 0], [1, 1, 1]] }
let(:model) { IrtRuby::TwoParameterModel.new(data) }

describe "#initialize" do
it "initializes with data" do
expect(model.instance_variable_get(:@data)).to eq(data)
end
end

describe "#sigmoid" do
it "calculates the sigmoid function" do
expect(model.sigmoid(0)).to eq(0.5)
end
end

describe "#likelihood" do
it "calculates the likelihood of the data" do
expect(model.likelihood).to be_a(Float)
end
end

describe "#fit" do
it "fits the model and returns abilities, difficulties, and discriminations" do
result = model.fit
expect(result[:abilities].size).to eq(data.row_count)
expect(result[:difficulties].size).to eq(data.column_count)
expect(result[:discriminations].size).to eq(data.column_count)
end
end
end

0 comments on commit 8e86eac

Please sign in to comment.