def grm_jml(dataset, options=None)
Estimate parameters for graded response model.
Estimate the discrimination and difficulty parameters for a graded response model using joint maximum likelihood.
dataset
options
discrimination
difficulty
def grm_jml(dataset, options=None):
"""Estimate parameters for graded response model.
Estimate the discrimination and difficulty parameters for
a graded response model using joint maximum likelihood.
Args:
dataset: [n_items, n_participants] 2d array of measured responses
options: dictionary with updates to default options
Returns:
discrimination: (1d array) estimate of item discriminations
difficulty: (2d array) estimates of item diffiulties by item thresholds
Options:
* max_iteration: int
"""
options = validate_estimation_options(options)
responses, item_counts = condition_polytomous_response(dataset)
n_items, n_takers = responses.shape
# Set initial parameter estimates to default
thetas = np.zeros((n_takers,))
# Initialize difficulty parameters for iterations
betas = np.full((item_counts.sum(),), -10000.0)
discrimination = np.ones_like(betas)
cumulative_item_counts = item_counts.cumsum()
start_indices = np.roll(cumulative_item_counts, 1)
start_indices[0] = 0
for ndx in range(n_items):
end_ndx = cumulative_item_counts[ndx]
start_ndx = start_indices[ndx] + 1
betas[start_ndx:end_ndx] = np.linspace(-1, 1,
item_counts[ndx] - 1)
betas_roll = np.roll(betas, -1)
betas_roll[cumulative_item_counts-1] = 10000
for iteration in range(options['max_iteration']):
previous_betas = betas.copy()
#####################
# STEP 1
# Estimate theta, given betas / alpha
# Loops over all persons
#####################
for ndx in range(n_takers):
def _theta_min(theta):
# Solves for ability parameters (theta)
graded_prob = (irt_evaluation(betas, discrimination, theta) -
irt_evaluation(betas_roll, discrimination, theta))
values = graded_prob[responses[:, ndx]]
return -np.log(values).sum()
thetas[ndx] = fminbound(_theta_min, -6, 6)
# Recenter theta to identify model
thetas -= thetas.mean()
thetas /= thetas.std(ddof=1)
#####################
# STEP 2
# Estimate Betas / alpha, given Theta
# Loops over all items
#####################
for ndx in range(n_items):
# pylint: disable=cell-var-from-loop
# Compute ML for static items
start_ndx = start_indices[ndx]
end_ndx = cumulative_item_counts[ndx]
def _alpha_beta_min(estimates):
# Set the estimates int
discrimination[start_ndx:end_ndx] = estimates[0]
betas[start_ndx+1:end_ndx] = estimates[1:]
betas_roll[start_ndx:end_ndx-1] = estimates[1:]
graded_prob = (irt_evaluation(betas, discrimination, thetas) -
irt_evaluation(betas_roll, discrimination, thetas))
values = np.take_along_axis(
graded_prob, responses[None, ndx], axis=0)
np.clip(values, 1e-23, np.inf, out=values)
return -np.log(values).sum()
# Solves jointly for parameters using numerical derivatives
initial_guess = np.concatenate(([discrimination[start_ndx]],
betas[start_ndx+1:end_ndx]))
otpt = fmin_slsqp(_alpha_beta_min, initial_guess,
disp=False, f_ieqcons=_jml_inequality,
bounds=[(.25, 4)] + [(-6, 6)] * (item_counts[ndx]-1))
discrimination[start_ndx:end_ndx] = otpt[0]
betas[start_ndx+1:end_ndx] = otpt[1:]
betas_roll[start_ndx:end_ndx-1] = otpt[1:]
# Check termination criterion
if(np.abs(previous_betas - betas).max() < 1e-3):
break
# Trim difficulties to conform to standard output
# TODO: look where missing values are and place NAN there instead
# of appending them to the end
output_betas = np.full((n_items, item_counts.max()-1), np.nan)
for ndx, (start_ndx, end_ndx) in enumerate(zip(start_indices, cumulative_item_counts)):
output_betas[ndx, :end_ndx-start_ndx-1] = betas[start_ndx+1:end_ndx]
return discrimination[start_indices], output_betas