adaptivetesting 1.1.4__tar.gz → 1.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/.github/workflows/python-test.yml +2 -2
  2. adaptivetesting-1.1.6/.python-version +1 -0
  3. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/CHANGELOG.md +7 -1
  4. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/PKG-INFO +9 -5
  5. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/README.md +3 -0
  6. adaptivetesting-1.1.6/adaptivetesting/__init__.py +42 -0
  7. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/data/__csv_context.py +19 -8
  8. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/data/__pickle_context.py +14 -8
  9. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/math/estimators/__bayes_modal_estimation.py +3 -2
  10. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/math/estimators/__functions/__bayes.py +40 -12
  11. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/models/__adaptive_test.py +0 -3
  12. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/models/__item_pool.py +18 -2
  13. adaptivetesting-1.1.6/adaptivetesting/tests/test_descriptives.py +50 -0
  14. adaptivetesting-1.1.6/adaptivetesting/tests/test_plots.py +97 -0
  15. adaptivetesting-1.1.6/adaptivetesting/utils/__descriptives.py +63 -0
  16. adaptivetesting-1.1.6/adaptivetesting/utils/__funcs.py +78 -0
  17. adaptivetesting-1.1.6/adaptivetesting/utils/__init__.py +13 -0
  18. adaptivetesting-1.1.6/adaptivetesting/utils/__plots.py +300 -0
  19. adaptivetesting-1.1.6/docs/source/_static/SPEC-0.svg +1 -0
  20. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/pyproject.toml +6 -8
  21. adaptivetesting-1.1.6/uv.lock +4713 -0
  22. adaptivetesting-1.1.4/.python-version +0 -1
  23. adaptivetesting-1.1.4/adaptivetesting/__init__.py +0 -7
  24. adaptivetesting-1.1.4/uv.lock +0 -5414
  25. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/.github/CONTRIBUTING.md +0 -0
  26. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/.github/workflows/publish.yml +0 -0
  27. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/.gitignore +0 -0
  28. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/.gitlab-ci.yml +0 -0
  29. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/.readthedocs.yml +0 -0
  30. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/.vscode/extensions.json +0 -0
  31. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/.vscode/settings.json +0 -0
  32. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/LICENSE +0 -0
  33. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/data/__init__.py +0 -0
  34. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/implementations/__default_implementation.py +0 -0
  35. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/implementations/__init__.py +0 -0
  36. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/implementations/__pre_test.py +0 -0
  37. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/implementations/__semi_implementation.py +0 -0
  38. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/implementations/__test_assembler.py +0 -0
  39. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/math/__gen_response_pattern.py +0 -0
  40. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/math/__init__.py +0 -0
  41. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/math/estimators/__expect_a_posteriori.py +0 -0
  42. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/math/estimators/__functions/__estimators.py +0 -0
  43. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/math/estimators/__init__.py +0 -0
  44. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/math/estimators/__ml_estimation.py +0 -0
  45. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/math/estimators/__prior.py +0 -0
  46. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/math/estimators/__test_information.py +0 -0
  47. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/math/item_selection/__init__.py +0 -0
  48. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/math/item_selection/__maximum_information_criterion.py +0 -0
  49. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/math/item_selection/__urrys_rule.py +0 -0
  50. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/models/__algorithm_exception.py +0 -0
  51. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/models/__init__.py +0 -0
  52. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/models/__item_selection_exception.py +0 -0
  53. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/models/__misc.py +0 -0
  54. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/models/__test_item.py +0 -0
  55. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/models/__test_result.py +0 -0
  56. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/services/__estimator_interface.py +0 -0
  57. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/services/__init__.py +0 -0
  58. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/services/__item_selection_protocol.py +0 -0
  59. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/services/__test_results_interface.py +0 -0
  60. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/simulation/__init__.py +0 -0
  61. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/simulation/__simulation.py +0 -0
  62. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/tests/__init__.py +0 -0
  63. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/tests/test_adaptive_test.py +0 -0
  64. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/tests/test_bayes_modal.py +0 -0
  65. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/tests/test_eap.py +0 -0
  66. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/tests/test_generate_response_pattern.py +0 -0
  67. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/tests/test_information_test.py +0 -0
  68. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/tests/test_item_selection.py +0 -0
  69. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/tests/test_load_test_items.py +0 -0
  70. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/tests/test_mle.py +0 -0
  71. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/tests/test_pretest.py +0 -0
  72. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/tests/test_simulation.py +0 -0
  73. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/tests/test_standard_error.py +0 -0
  74. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/adaptivetesting/tests/test_test_assembler.py +0 -0
  75. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/Makefile +0 -0
  76. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/data.md +0 -0
  77. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/implementations.md +0 -0
  78. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/index.md +0 -0
  79. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/make.bat +0 -0
  80. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/math.md +0 -0
  81. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/models.md +0 -0
  82. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/readme.md +0 -0
  83. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/requirements.txt +0 -0
  84. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/services.md +0 -0
  85. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/simulation.md +0 -0
  86. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/source/_static/logo.svg +0 -0
  87. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/source/_static/main.css +0 -0
  88. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/source/conf.py +0 -0
  89. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/source/data.rst +0 -0
  90. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/source/implementations.rst +0 -0
  91. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/source/index.rst +0 -0
  92. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/source/math.rst +0 -0
  93. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/source/models.rst +0 -0
  94. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/source/services.rst +0 -0
  95. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/source/simulation.rst +0 -0
  96. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/source/tests.rst +0 -0
  97. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/docs/tests.md +0 -0
  98. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/examples/.gitignore +0 -0
  99. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/examples/simulation.py +0 -0
  100. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/examples/testing.py +0 -0
  101. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/images/default.svg +0 -0
  102. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/images/semi-adaptive.svg +0 -0
  103. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/mypy.ini +0 -0
  104. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/requirements.txt +0 -0
  105. {adaptivetesting-1.1.4 → adaptivetesting-1.1.6}/setup.cfg +0 -0
@@ -16,7 +16,7 @@ jobs:
16
16
  strategy:
17
17
  fail-fast: false
18
18
  matrix:
19
- python-version: ["3.10", "3.11", "3.12"]
19
+ python-version: ["3.12", "3.13", "3.14"]
20
20
 
21
21
  steps:
22
22
  - uses: actions/checkout@v3
@@ -42,7 +42,7 @@ jobs:
42
42
  strategy:
43
43
  fail-fast: false
44
44
  matrix:
45
- python-version: ["3.10", "3.11", "3.12"]
45
+ python-version: ["3.12", "3.13", "3.14"]
46
46
 
47
47
  steps:
48
48
  - uses: actions/checkout@v3
@@ -0,0 +1 @@
1
+ 3.13
@@ -1,3 +1,9 @@
1
+ # Version 1.1.6
2
+ - Bug Fig #49 (optimization interval issue in BayesModal)
3
+
4
+ # Version 1.1.5
5
+ - Bug Fix #46 (numerical stability issue concerning Bayesian ability estimation)
6
+
1
7
  # Version 1.1.4
2
8
  - Bug Fix #43 (optimization interval in `maximize_posterior` function)
3
9
 
@@ -7,4 +13,4 @@
7
13
  # Version 1.1.2
8
14
 
9
15
  - The `ItemPool` class now correctly honors the `id` field of items. (#30, #31)
10
- - Examples and the readme file have been updated accordingly.
16
+ - Examples and the readme file have been updated accordingly.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adaptivetesting
3
- Version: 1.1.4
3
+ Version: 1.1.6
4
4
  Summary: adaptivetesting is a Python package that can be used to simulate and evaluate custom CAT scenarios as well as implement them in real-world testing scenarios from a single codebase
5
5
  Project-URL: Homepage, https://github.com/condecon/adaptivetesting
6
6
  Project-URL: Documentation, https://github.com/condecon/adaptivetesting/tree/main/docs
@@ -9,14 +9,18 @@ Project-URL: Issues, https://github.com/condecon/adaptivetesting/issues
9
9
  Author-email: Jonas Engicht <dev@condecon.de>
10
10
  License-File: LICENSE
11
11
  Keywords: computerized-adaptive-testing,item-response-theory,psychology,statistics
12
- Requires-Python: >=3.10
12
+ Requires-Python: >=3.12
13
+ Requires-Dist: matplotlib>=3.9.0
13
14
  Requires-Dist: numpy>=2.0.0
14
- Requires-Dist: pandas>=2.2.0
15
- Requires-Dist: scipy>=1.15.0
16
- Requires-Dist: tqdm>=4.67.1
15
+ Requires-Dist: pandas>=2.3.0
16
+ Requires-Dist: scipy>=1.12.0
17
+ Requires-Dist: tqdm>=4.66.0
17
18
  Description-Content-Type: text/markdown
18
19
 
19
20
  # adaptivetesting
21
+ [![SPEC 0 — Minimum Supported Dependencies](docs/source/_static/SPEC-0.svg)](https://scientific-python.org/specs/spec-0000/)
22
+
23
+
20
24
 
21
25
  <img src="/docs/source/_static/logo.svg" style="width: 100%">
22
26
  </img>
@@ -1,4 +1,7 @@
1
1
  # adaptivetesting
2
+ [![SPEC 0 — Minimum Supported Dependencies](docs/source/_static/SPEC-0.svg)](https://scientific-python.org/specs/spec-0000/)
3
+
4
+
2
5
 
3
6
  <img src="/docs/source/_static/logo.svg" style="width: 100%">
4
7
  </img>
@@ -0,0 +1,42 @@
1
+ from . import data
2
+ from . import implementations
3
+ from . import math
4
+ from . import models
5
+ from . import services
6
+ from . import simulation
7
+ from . import tests
8
+
9
+ from .data.__csv_context import CSVContext
10
+ from .data.__pickle_context import PickleContext
11
+
12
+ from .implementations.__default_implementation import DefaultImplementation
13
+ from .implementations.__pre_test import PreTest
14
+ from .implementations.__semi_implementation import SemiAdaptiveImplementation
15
+ from .implementations.__test_assembler import TestAssembler
16
+
17
+ from .math.__gen_response_pattern import generate_response_pattern
18
+ from .math.estimators.__ml_estimation import MLEstimator
19
+ from .math.estimators.__bayes_modal_estimation import BayesModal
20
+ from .math.estimators.__expect_a_posteriori import ExpectedAPosteriori
21
+ from .math.estimators.__prior import Prior, NormalPrior, CustomPrior, CustomPriorException
22
+ from .math.estimators.__functions.__estimators import probability_y0, probability_y1, maximize_likelihood_function, likelihood
23
+ from .math.estimators.__functions.__bayes import maximize_posterior
24
+ from .math.estimators.__test_information import test_information_function, item_information_function, prior_information_function
25
+ from .math.item_selection.__maximum_information_criterion import maximum_information_criterion
26
+ from .math.item_selection.__urrys_rule import urrys_rule
27
+
28
+ from .models.__adaptive_test import AdaptiveTest
29
+ from .models.__algorithm_exception import AlgorithmException
30
+ from .models.__item_pool import ItemPool
31
+ from .models.__item_selection_exception import ItemSelectionException
32
+ from .models.__test_item import TestItem
33
+ from .models.__test_result import TestResult
34
+ from .models.__misc import ResultOutputFormat, StoppingCriterion
35
+
36
+ from .services.__estimator_interface import IEstimator
37
+ from .services.__test_results_interface import ITestResults
38
+ from .services.__item_selection_protocol import ItemSelectionStrategy
39
+
40
+ from .simulation.__simulation import Simulation, SimulationPool, setup_simulation_and_start
41
+
42
+ from .utils.__descriptives import bias, average_absolute_deviation, rmse
@@ -3,6 +3,7 @@ import csv
3
3
  import pathlib
4
4
  from ..models.__test_result import TestResult
5
5
  from ..services.__test_results_interface import ITestResults
6
+ from dataclasses import fields
6
7
 
7
8
 
8
9
  class CSVContext(ITestResults):
@@ -46,12 +47,22 @@ class CSVContext(ITestResults):
46
47
  file.close()
47
48
 
48
49
  def load(self) -> List[TestResult]:
49
- """Loads results from the database.
50
- The implementation of this method is required
51
- by the interface. However, it does not have
52
- any implemented functionality and will throw an error
53
- if used.
54
-
55
- Returns: List[TestResult]
50
+ """Loads test results from a CSV file for a specific participant and simulation.
51
+ Reads the CSV file located at `data/{simulation_id}/{participant_id}.csv`,
52
+ parses each row into a `TestResult` object,
53
+ and returns a list of these objects.
54
+
55
+ Returns:
56
+ List[TestResult]: A list of `TestResult` objects loaded from the CSV file.
56
57
  """
57
- raise NotImplementedError("This function is not implemented.")
58
+ foldername = f"data/{self.simulation_id}"
59
+
60
+ fieldnames = list(fields(TestResult))
61
+ test_results: list[TestResult] = []
62
+ with open(f"{foldername}/{self.participant_id}.csv", "r", encoding="utf-8") as file:
63
+ reader = csv.DictReader(file, fieldnames=fieldnames)
64
+ for row in reader:
65
+ test_result = TestResult.from_dict(row)
66
+ test_results.append(test_result)
67
+ file.close()
68
+ return test_results
@@ -40,12 +40,18 @@ class PickleContext(ITestResults):
40
40
  file.close()
41
41
 
42
42
  def load(self) -> List[TestResult]:
43
- """Loads results from the database.
44
- The implementation of this method is required
45
- by the interface. However, is does not have
46
- any implemented functionality and will throw an error
47
- if used.
48
-
49
- Returns: List[TestResult]
43
+ """Loads and returns a list of TestResult objects for a specific participant and simulation.
44
+ The method reads a pickle file located at 'data/{simulation_id}/{participant_id}.pickle'
45
+ and deserializes its contents into a list of TestResult instances.
46
+ Returns:
47
+ List[TestResult]: The list of test results loaded from the pickle file.
48
+ Raises:
49
+ FileNotFoundError: If the specified pickle file does not exist.
50
+ pickle.UnpicklingError: If the file cannot be unpickled.
50
51
  """
51
- raise NotImplementedError("This function is not implemented.")
52
+ foldername = f"data/{self.simulation_id}"
53
+
54
+ with open(f"{foldername}/{self.participant_id}.pickle", "rb") as file:
55
+ test_results: list[TestResult] = pickle.load(file)
56
+ file.close()
57
+ return test_results
@@ -62,7 +62,8 @@ class BayesModal(IEstimator):
62
62
  self.c,
63
63
  self.d,
64
64
  self.response_pattern,
65
- self.prior
65
+ self.prior,
66
+ self.optimization_interval
66
67
  )
67
68
  # else, we have to calculate the full posterior distribution
68
69
  # because the optimizers do not correctly identify the maximum of the function
@@ -91,7 +92,7 @@ class BayesModal(IEstimator):
91
92
  ])
92
93
 
93
94
  # add prior
94
- unmarginalized_posterior = lik_values * self.prior.pdf(mu)
95
+ unmarginalized_posterior = lik_values * np.log(self.prior.pdf(mu))
95
96
  # find argmin and return mu
96
97
  estimate_index = np.argmin(unmarginalized_posterior)
97
98
  return float(mu[estimate_index].astype(float))
@@ -1,8 +1,8 @@
1
1
  import numpy as np
2
2
  from scipy.optimize import minimize_scalar, OptimizeResult # type: ignore
3
- from .__estimators import likelihood
4
3
  from ..__prior import Prior
5
4
  from ....models.__algorithm_exception import AlgorithmException
5
+ from .__estimators import probability_y0, probability_y1
6
6
 
7
7
 
8
8
  def maximize_posterior(
@@ -18,15 +18,15 @@ def maximize_posterior(
18
18
 
19
19
  Args:
20
20
  a (np.ndarray): item parameter a
21
-
21
+
22
22
  b (np.ndarray): item parameter b
23
-
23
+
24
24
  c (np.ndarray): item parameter c
25
-
25
+
26
26
  d (np.ndarray): item parameter d
27
-
27
+
28
28
  response_pattern (np.ndarray): response pattern (simulated or user generated)
29
-
29
+
30
30
  prior (Prior): prior distribution
31
31
 
32
32
  optimization_interval (Tuple[float, float]): interval used for the optimization function
@@ -34,15 +34,43 @@ def maximize_posterior(
34
34
  Returns:
35
35
  float: Bayes Modal estimator for the given parameters
36
36
  """
37
- def posterior(mu) -> np.ndarray:
38
- return likelihood(mu, a, b, c, d, response_pattern) * prior.pdf(mu)
39
-
40
- result: OptimizeResult = minimize_scalar(lambda mu: posterior(mu),
37
+ def log_posterior(mu) -> np.ndarray:
38
+ p1 = probability_y1(mu, a, b, c, d)
39
+ p0 = probability_y0(mu, a, b, c, d)
40
+
41
+ log_likelihood = np.sum(
42
+ (response_pattern * np.log(p1 + 1e-300)) + (
43
+ (1 - response_pattern) * np.log(p0 + 1e-300)
44
+ ))
45
+ log_prior = np.log(prior.pdf(mu) + 1e-300)
46
+
47
+ return log_likelihood + log_prior
48
+
49
+ result: OptimizeResult = minimize_scalar(lambda mu: -log_posterior(mu),
41
50
  bounds=optimization_interval,
42
51
  method="bounded") # type: ignore
43
-
52
+
44
53
  if not result.success:
45
54
  raise AlgorithmException(f"Optimization failed: {result.message}")
46
-
55
+
47
56
  else:
48
57
  return float(result.x)
58
+
59
+
60
+ def likelihood(mu: np.ndarray,
61
+ a: np.ndarray,
62
+ b: np.ndarray,
63
+ c: np.ndarray,
64
+ d: np.ndarray,
65
+ response_pattern: np.ndarray
66
+ ):
67
+
68
+ p1 = probability_y1(mu, a, b, c, d)
69
+ p0 = probability_y0(mu, a, b, c, d)
70
+
71
+ log_likelihood = np.sum(
72
+ (response_pattern * np.log(p1 + 1e-300)) + (
73
+ (1 - response_pattern) * np.log(p0 + 1e-300)
74
+ ))
75
+
76
+ return -np.exp(log_likelihood)
@@ -2,7 +2,6 @@ from typing import List
2
2
  import abc
3
3
  import copy
4
4
  from .__test_item import TestItem
5
- from ..math.item_selection.__urrys_rule import urrys_rule
6
5
  from ..math.__gen_response_pattern import generate_response_pattern
7
6
  from .__test_result import TestResult
8
7
  from .__item_pool import ItemPool
@@ -114,8 +113,6 @@ class AdaptiveTest(abc.ABC):
114
113
  TestItem: selected item
115
114
  """
116
115
  raise NotImplementedError("This functionality is not implemented by default.")
117
- item = urrys_rule(self.item_pool.test_items, self.ability_level)
118
- return item
119
116
 
120
117
  @abc.abstractmethod
121
118
  def estimate_ability_level(self) -> tuple[float, float]:
@@ -110,7 +110,9 @@ class ItemPool:
110
110
 
111
111
  simulated_responses (List[int]): simulated responses
112
112
 
113
- ids (List[int]): item IDs
113
+ ids (List[int]): item IDs. If the argument is set to `None`
114
+ all items are numbered in the order in which they are
115
+ passed to the function.
114
116
 
115
117
  Returns:
116
118
  List[TestItem]: item pool
@@ -147,6 +149,9 @@ class ItemPool:
147
149
  raise ValueError("Length of ids and b has to be the same.")
148
150
  for i, id_ in enumerate(ids):
149
151
  items[i].id = id_
152
+ else:
153
+ for i in range(len(b)):
154
+ items[i].id = i
150
155
 
151
156
  item_pool = ItemPool(items)
152
157
  item_pool.simulated_responses = simulated_responses
@@ -169,7 +174,9 @@ class ItemPool:
169
174
  Args:
170
175
  source (dict[str, List[float]]): item pool dictionary
171
176
  simulated_responses (List[int]): simulated responses
172
- ids (List[int]): item IDs
177
+ ids (List[int]): item IDs. If the argument is set to `None`
178
+ all items are numbered in the order in which they are
179
+ passed to the function.
173
180
 
174
181
  Returns:
175
182
  List[TestItem]: item pool
@@ -211,6 +218,8 @@ class ItemPool:
211
218
 
212
219
  if ids is not None:
213
220
  item.id = ids[i]
221
+ else:
222
+ item.id = i
214
223
 
215
224
  items.append(item)
216
225
 
@@ -223,6 +232,13 @@ class ItemPool:
223
232
  """Creates item pool from a pandas DataFrame.
224
233
  Required columns are: `a`, `b`, `c`, `d`.
225
234
  Each column has to contain float values.
235
+
236
+ A `id` column can be added to assign
237
+ each test item a unique identifier.
238
+ If there is no `id` column
239
+ all items are numbered in the order in which they are
240
+ passed to the function.
241
+
226
242
  A `simulated_responses` (int values) column can be added to
227
243
  the DataFrame to provide simulated responses.
228
244
 
@@ -0,0 +1,50 @@
1
+ import unittest
2
+ from unittest.mock import patch
3
+ import numpy as np
4
+ from adaptivetesting import rmse, bias, average_absolute_deviation, TestResult
5
+ from adaptivetesting.models.__misc import ResultOutputFormat
6
+
7
+
8
+ class DummyResult:
9
+ def __init__(self, ability_estimation, true_ability_level):
10
+ self.ability_estimation = ability_estimation
11
+ self.true_ability_level = true_ability_level
12
+
13
+
14
+ class TestDescriptives(unittest.TestCase):
15
+ @patch('adaptivetesting.utils.__descriptives.load_final_test_results')
16
+ def test_bias(self, mock_load):
17
+ mock_load.return_value = [
18
+ TestResult(None, 1.0, None, None, None, 0.5), # type: ignore
19
+ TestResult(None, 2.0, None, None, None, 1.5), # type: ignore
20
+ TestResult(None, 3.0, None, None, None, 2.5), # type: ignore
21
+ ]
22
+ result = bias('sim1', ['p1', 'p2', 'p3'], ResultOutputFormat.CSV)
23
+ expected = np.mean([1.0 - 0.5, 2.0 - 1.5, 3.0 - 2.5])
24
+ self.assertAlmostEqual(result, expected) # type: ignore
25
+
26
+ @patch('adaptivetesting.utils.__descriptives.load_final_test_results')
27
+ def test_average_absolute_deviation(self, mock_load):
28
+ mock_load.return_value = [
29
+ TestResult(None, 1.0, None, None, None, 0.5), # type: ignore
30
+ TestResult(None, 2.0, None, None, None, 1.5), # type: ignore
31
+ TestResult(None, 3.0, None, None, None, 2.5), # type: ignore
32
+ ]
33
+ result = average_absolute_deviation('sim1', ['p1', 'p2', 'p3'], ResultOutputFormat.CSV)
34
+ expected = np.mean([abs(1.0 - 0.5), abs(2.0 - 1.5), abs(3.0 - 2.5)])
35
+ self.assertAlmostEqual(result, expected) # type: ignore
36
+
37
+ @patch('adaptivetesting.utils.__descriptives.load_final_test_results')
38
+ def test_rmse(self, mock_load):
39
+ mock_load.return_value = [
40
+ TestResult(None, 1.0, None, None, None, 0.5), # type: ignore
41
+ TestResult(None, 2.0, None, None, None, 1.5), # type: ignore
42
+ TestResult(None, 3.0, None, None, None, 2.5), # type: ignore
43
+ ]
44
+ result = rmse('sim1', ['p1', 'p2', 'p3'], ResultOutputFormat.CSV)
45
+ expected = np.sqrt(np.mean([(1.0 - 0.5) ** 2, (2.0 - 1.5) ** 2, (3.0 - 2.5) ** 2]))
46
+ self.assertAlmostEqual(result, expected)
47
+
48
+
49
+ if __name__ == '__main__':
50
+ unittest.main()
@@ -0,0 +1,97 @@
1
+ # type: ignore
2
+ import unittest
3
+ from unittest.mock import patch
4
+ from adaptivetesting.utils import (
5
+ plot_final_ability_estimates,
6
+ plot_icc,
7
+ plot_iif,
8
+ plot_exposure_rate,
9
+ plot_test_information,
10
+ plot_theta_estimation_trace
11
+ )
12
+ from adaptivetesting.models.__test_item import TestItem
13
+ from adaptivetesting.models.__misc import ResultOutputFormat
14
+ import matplotlib
15
+
16
+
17
+ class TestPlots(unittest.TestCase):
18
+ @classmethod
19
+ def setUpClass(cls):
20
+ cls.dummy_item = TestItem()
21
+ cls.dummy_item.id = 1
22
+ cls.dummy_item.a = 1.0
23
+ cls.dummy_item.b = 0.0
24
+ cls.dummy_item.c = 0.2
25
+ cls.dummy_item.d = 1.0
26
+ cls.dummy_items = [cls.dummy_item for _ in range(3)]
27
+ cls.simulation_id = 'sim1'
28
+ cls.participant_ids = ['p1', 'p2']
29
+ cls.output_format = ResultOutputFormat.CSV
30
+
31
+ @patch('adaptivetesting.utils.__plots.load_final_test_results')
32
+ def test_plot_final_ability_estimates(self, mock_load):
33
+ # Mock return value: list of objects with .ability_estimation and .true_ability_level
34
+ class DummyResult:
35
+ def __init__(self, est, true):
36
+ self.ability_estimation = est
37
+ self.true_ability_level = true
38
+ mock_load.return_value = [DummyResult(1.0, 1.2), DummyResult(0.5, 0.7)]
39
+ fig, ax = plot_final_ability_estimates(
40
+ self.simulation_id,
41
+ self.participant_ids,
42
+ self.output_format
43
+ )
44
+ self.assertIsInstance(fig, matplotlib.figure.Figure)
45
+ self.assertIsNotNone(ax)
46
+
47
+ def test_plot_icc(self):
48
+ fig, ax = plot_icc(self.dummy_item)
49
+ self.assertIsInstance(fig, matplotlib.figure.Figure)
50
+ self.assertIsNotNone(ax)
51
+
52
+ def test_plot_iif(self):
53
+ fig, ax = plot_iif(self.dummy_item)
54
+ self.assertIsInstance(fig, matplotlib.figure.Figure)
55
+ self.assertIsNotNone(ax)
56
+
57
+ @patch('adaptivetesting.utils.__plots.load_test_results_single_participant')
58
+ def test_plot_exposure_rate(self, mock_load):
59
+ # Mock return value: list of objects with .showed_item["id"]
60
+ class DummyResult:
61
+ def __init__(self, item_id):
62
+ self.showed_item = {"id": item_id}
63
+ mock_load.return_value = [
64
+ DummyResult("item1"), DummyResult("item2")
65
+ ]
66
+ fig, ax = plot_exposure_rate(
67
+ self.simulation_id,
68
+ self.participant_ids,
69
+ self.output_format
70
+ )
71
+ self.assertIsInstance(fig, matplotlib.figure.Figure)
72
+ self.assertIsNotNone(ax)
73
+
74
+ def test_plot_test_information(self):
75
+ fig, ax = plot_test_information(self.dummy_items)
76
+ self.assertIsInstance(fig, matplotlib.figure.Figure)
77
+ self.assertIsNotNone(ax)
78
+
79
+ @patch('adaptivetesting.utils.__plots.load_test_results_single_participant')
80
+ def test_plot_theta_estimation_trace(self, mock_load):
81
+ # Mock return value: list of objects with .true_ability_level and .ability_estimation
82
+ class DummyResult:
83
+ def __init__(self, true, est):
84
+ self.true_ability_level = true
85
+ self.ability_estimation = est
86
+ mock_load.return_value = [DummyResult(1.0, 0.9), DummyResult(1.2, 1.1)]
87
+ fig, ax = plot_theta_estimation_trace(
88
+ self.simulation_id,
89
+ self.participant_ids[0],
90
+ self.output_format
91
+ )
92
+ self.assertIsInstance(fig, matplotlib.figure.Figure)
93
+ self.assertIsNotNone(ax)
94
+
95
+
96
+ if __name__ == "__main__":
97
+ unittest.main()
@@ -0,0 +1,63 @@
1
+ from ..models.__misc import ResultOutputFormat
2
+ import numpy as np
3
+ from .__funcs import load_final_test_results
4
+
5
+
6
+ def bias(simulation_id: str, participant_ids: list[str], output_format: ResultOutputFormat) -> np.floating:
7
+ """
8
+ Calculates the bias of ability estimations for a set of participants in a simulation.
9
+ Bias is defined as the mean difference between estimated abilities and true ability levels.
10
+ Args:
11
+ simulation_id (str): Identifier for the simulation.
12
+ participant_ids (list[str]): List of participant identifiers.
13
+ output_format (ResultOutputFormat): Format in which the results are output.
14
+ Returns:
15
+ np.floating: The mean bias of ability estimations.
16
+ """
17
+ final_test_results = load_final_test_results(simulation_id, participant_ids, output_format)
18
+ estimations = np.array([result.ability_estimation for result in final_test_results], dtype=np.float64)
19
+ true_abilities = np.array([result.true_ability_level for result in final_test_results], dtype=np.float64)
20
+
21
+ return np.mean(estimations - true_abilities)
22
+
23
+
24
+ def average_absolute_deviation(simulation_id: str,
25
+ participant_ids: list[str],
26
+ output_format: ResultOutputFormat) -> np.floating:
27
+ """
28
+ Calculates the average absolute deviation between estimated abilities
29
+ and true ability levels for a set of participants.
30
+
31
+ Args:
32
+ simulation_id (str): Identifier for the simulation run.
33
+ participant_ids (list[str]): List of participant identifiers.
34
+ output_format (ResultOutputFormat): Format in which the results are returned.
35
+ Returns:
36
+ np.floating: The mean absolute deviation between estimated and true abilities.
37
+ """
38
+ final_test_results = load_final_test_results(simulation_id, participant_ids, output_format)
39
+ estimations = np.array([result.ability_estimation for result in final_test_results], dtype=np.float64)
40
+ true_abilities = np.array([result.true_ability_level for result in final_test_results], dtype=np.float64)
41
+
42
+ return np.mean(np.abs(estimations - true_abilities))
43
+
44
+
45
+ def rmse(simulation_id: str,
46
+ participant_ids: list[str],
47
+ output_format: ResultOutputFormat) -> np.floating:
48
+ """
49
+ Calculates the root mean squared error (RMSE) between
50
+ estimated abilities and true ability levels for a set of participants.
51
+
52
+ Args:
53
+ simulation_id (str): Identifier for the simulation run.
54
+ participant_ids (list[str]): List of participant IDs to include in the calculation.
55
+ output_format (ResultOutputFormat): Format in which the test results are returned.
56
+ Returns:
57
+ np.floating: The RMSE value between estimated and true abilities.
58
+ """
59
+ final_test_results = load_final_test_results(simulation_id, participant_ids, output_format)
60
+ estimations = np.array([result.ability_estimation for result in final_test_results], dtype=np.float64)
61
+ true_abilities = np.array([result.true_ability_level for result in final_test_results], dtype=np.float64)
62
+
63
+ return np.sqrt(np.mean((estimations - true_abilities) ** 2))
@@ -0,0 +1,78 @@
1
+ from ..models.__test_result import TestResult
2
+ from ..data.__csv_context import CSVContext
3
+ from ..data.__pickle_context import PickleContext
4
+ from ..services.__test_results_interface import ITestResults
5
+ from ..models.__misc import ResultOutputFormat
6
+
7
+
8
+ def load_final_test_results(simulation_id: str,
9
+ participant_ids: list[str],
10
+ output_format: ResultOutputFormat) -> list[TestResult]:
11
+ """
12
+ Loads the final test results for a list of participants from the specified simulation or adaptive test.
13
+ Depending on the output format (CSV or PICKLE), this function initializes the appropriate context,
14
+ loads all test results for each participant, and selects the final result (assumed to be the last entry).
15
+ The final results are collected and returned as a list.
16
+ Args:
17
+ simulation_id (str): The identifier for the simulation from which to load results.
18
+ participant_ids (list[str]): A list of participant IDs whose results are to be loaded.
19
+ output_format (ResultOutputFormat): The format in which results are stored (CSV or PICKLE).
20
+ Returns:
21
+ list[TestResult]: A list containing the final test result for each participant.
22
+ Raises:
23
+ ValueError: If the output_format is not set to either ResultOutputFormat.CSV or ResultOutputFormat.PICKLE.
24
+ """
25
+ # load data
26
+ final_test_results: list[TestResult] = []
27
+ context: ITestResults
28
+
29
+ if output_format is ResultOutputFormat.CSV:
30
+ for id in participant_ids:
31
+ context = CSVContext(simulation_id, participant_id=id)
32
+ test_results = context.load()
33
+ # select final result
34
+ final_result = test_results[-1]
35
+ final_test_results.append(final_result)
36
+
37
+ if output_format is ResultOutputFormat.PICKLE:
38
+ for id in participant_ids:
39
+ context = PickleContext(simulation_id, participant_id=id)
40
+ test_results = context.load()
41
+ # select final result
42
+ final_result = test_results[-1]
43
+ final_test_results.append(final_result)
44
+ else:
45
+ raise ValueError("output_format is not correctly set to either PICKLE of CSV.")
46
+
47
+ return final_test_results
48
+
49
+
50
+ def load_test_results_single_participant(simulation_id: str,
51
+ participant_id: str,
52
+ output_format: ResultOutputFormat) -> list[TestResult]:
53
+ """
54
+ Loads the test results for a participant from the specified simulation or adaptive test.
55
+ Depending on the output format (CSV or PICKLE), this function reads the available CSV or PICKLE files.
56
+
57
+ Args:
58
+ simulation_id (str): The identifier for the simulation from which to load results.
59
+ participant_id (str): Participant ID whose results are to be loaded.
60
+ output_format (ResultOutputFormat): The format in which results are stored (CSV or PICKLE).
61
+ Returns:
62
+ list[TestResult]: A list containing the test results for the participant.
63
+ Raises:
64
+ ValueError: If the output_format is not set to either ResultOutputFormat.CSV or ResultOutputFormat.PICKLE.
65
+ """
66
+ context: ITestResults
67
+
68
+ if output_format is ResultOutputFormat.CSV:
69
+ context = CSVContext(simulation_id=simulation_id,
70
+ participant_id=participant_id)
71
+ return context.load()
72
+
73
+ if output_format is ResultOutputFormat.PICKLE:
74
+ context = PickleContext(simulation_id=simulation_id,
75
+ participant_id=participant_id)
76
+ return context.load()
77
+ else:
78
+ raise ValueError("output_format is not correctly set to either PICKLE of CSV.")
@@ -0,0 +1,13 @@
1
+ from .__descriptives import bias, average_absolute_deviation, rmse
2
+
3
+ from .__funcs import load_final_test_results, load_test_results_single_participant
4
+
5
+ from .__plots import (
6
+ plot_exposure_rate,
7
+ plot_final_ability_estimates,
8
+ plot_icc,
9
+ plot_iif,
10
+ plot_exposure_rate,
11
+ plot_test_information,
12
+ plot_theta_estimation_trace
13
+ )