adaptivetesting 1.2b1__tar.gz → 1.2.1__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.
- adaptivetesting-1.2.1/PKG-INFO +103 -0
- adaptivetesting-1.2.1/README.md +82 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/__init__.py +11 -1
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/data/__read_prev_items_exp_cont.py +25 -4
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/implementations/__test_assembler.py +12 -2
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/__gen_response_pattern.py +26 -2
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/content_balancing/__constraint.py +1 -1
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/content_balancing/__functions.py +5 -4
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/content_balancing/__weighted_penalty_model.py +59 -4
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/estimators/__bayes_modal_estimation.py +2 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/estimators/__expect_a_posteriori.py +2 -0
- adaptivetesting-1.2.1/adaptivetesting/math/estimators/__functions/__bayes.py +74 -0
- adaptivetesting-1.2.1/adaptivetesting/math/estimators/__functions/__estimators.py +182 -0
- adaptivetesting-1.2.1/adaptivetesting/math/estimators/__functions/__poly/__gpcm.py +70 -0
- adaptivetesting-1.2.1/adaptivetesting/math/estimators/__functions/__poly/__grm.py +77 -0
- adaptivetesting-1.2.1/adaptivetesting/math/estimators/__functions/__poly/__init__.py +4 -0
- adaptivetesting-1.2.1/adaptivetesting/math/estimators/__functions/__poly/__poly_math.py +193 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/estimators/__ml_estimation.py +4 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/estimators/__test_information.py +39 -3
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/exposure_control/__mpi_exposure_control.py +1 -1
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/exposure_control/__randomesque.py +1 -1
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/item_selection/__maximum_information_criterion.py +2 -1
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/models/__adaptive_test.py +2 -1
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/models/__test_item.py +5 -5
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/services/__estimator_interface.py +1 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/simulation/__simulation.py +36 -19
- adaptivetesting-1.2.1/adaptivetesting/tests/__init__.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_generate_response_pattern.py +12 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/utils/__funcs.py +1 -1
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/utils/__plots.py +23 -17
- adaptivetesting-1.2.1/adaptivetesting.egg-info/PKG-INFO +103 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting.egg-info/SOURCES.txt +7 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/pyproject.toml +1 -1
- adaptivetesting-1.2b1/PKG-INFO +0 -374
- adaptivetesting-1.2b1/README.md +0 -353
- adaptivetesting-1.2b1/adaptivetesting.egg-info/PKG-INFO +0 -374
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/LICENSE +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/data/__csv_context.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/data/__init__.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/data/__pickle_context.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/implementations/__default_implementation.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/implementations/__init__.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/implementations/__pre_test.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/implementations/__semi_implementation.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/__init__.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/content_balancing/__content_balancing.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/content_balancing/__init__.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/content_balancing/__maximum_priority_index.py +0 -0
- {adaptivetesting-1.2b1/adaptivetesting/tests → adaptivetesting-1.2.1/adaptivetesting/math/estimators/__functions}/__init__.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/estimators/__init__.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/estimators/__prior.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/exposure_control/__exposure_control.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/exposure_control/__init__.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/item_selection/__init__.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/item_selection/__urrys_rule.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/models/__algorithm_exception.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/models/__init__.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/models/__item_pool.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/models/__item_selection_exception.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/models/__misc.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/models/__test_result.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/services/__init__.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/services/__item_selection_protocol.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/services/__test_results_interface.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/simulation/__init__.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_adaptive_test.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_bayes_modal.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_content_balacing.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_descriptives.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_eap.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_exposure_control_mpi.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_full_tests.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_information_test.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_item_selection.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_load_test_items.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_mle.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_plots.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_poly_models.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_pretest.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_priors.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_simulation.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_standard_error.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_test_assembler.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/tests/test_weighted_penalty_model.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/utils/__descriptives.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/utils/__init__.py +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting.egg-info/dependency_links.txt +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting.egg-info/requires.txt +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting.egg-info/top_level.txt +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/setup.cfg +0 -0
- {adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/setup.py +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: adaptivetesting
|
|
3
|
+
Version: 1.2.1
|
|
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
|
+
Author-email: Jonas Engicht <dev@condecon.de>
|
|
6
|
+
Project-URL: Homepage, https://github.com/condecon/adaptivetesting
|
|
7
|
+
Project-URL: Documentation, https://github.com/condecon/adaptivetesting/wiki
|
|
8
|
+
Project-URL: Repository, https://github.com/condecon/adaptivetesting.git
|
|
9
|
+
Project-URL: Issues, https://github.com/condecon/adaptivetesting/issues
|
|
10
|
+
Keywords: statistics,psychology,item-response-theory,computerized-adaptive-testing
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: numpy>=2.1.0
|
|
15
|
+
Requires-Dist: pandas>=2.3.0
|
|
16
|
+
Requires-Dist: scipy>=1.15.0
|
|
17
|
+
Requires-Dist: tqdm>=4.66.0
|
|
18
|
+
Requires-Dist: matplotlib>=3.10.0
|
|
19
|
+
Requires-Dist: numdifftools>=0.9.42
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# adaptivetesting
|
|
23
|
+
|
|
24
|
+
<img src="/docs/_static/logo.svg" style="width: 100%">
|
|
25
|
+
</img>
|
|
26
|
+
|
|
27
|
+
**An open-source Python package for simplified, customizable Computerized Adaptive Testing (CAT) using Bayesian methods.**
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## Key Features
|
|
31
|
+
|
|
32
|
+
- **Bayesian Methods**: Built-in support for Bayesian ability estimation with customizable priors
|
|
33
|
+
- **Flexible Architecture**: Object-oriented design with abstract classes for easy extension
|
|
34
|
+
- **Item Response Theory**: Full support for 1PL, 2PL, 3PL, and 4PL models, GRM, GPCM
|
|
35
|
+
- **Multiple Estimators**:
|
|
36
|
+
- Maximum Likelihood Estimation (MLE)
|
|
37
|
+
- Bayesian Modal Estimation (BM)
|
|
38
|
+
- Expected A Posteriori (EAP)
|
|
39
|
+
- **Item Selection Strategies**: Maximum information criterion
|
|
40
|
+
- **Content Balancing**: Maximum Priority Index, Weighted Penalty Model
|
|
41
|
+
- **Exposure Control**: Randomesque Item Selection, Maximum Priority Index
|
|
42
|
+
- **Simulation Framework**: Comprehensive tools for CAT simulation and evaluation
|
|
43
|
+
- **Real-world Application**: Direct transition from simulation to production testing
|
|
44
|
+
- **Stopping Criteria**: Support for standard error and test length criteria
|
|
45
|
+
- **Data Management**: Built-in support for CSV and pickle data formats
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
Install from PyPI using pip:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install adaptivetesting
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
For the latest development version:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install git+https://github.com/condecon/adaptivetesting
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Documentation
|
|
62
|
+
You can find our documentation in the [GitHub wiki](https://github.com/condecon/adaptivetesting/wiki).
|
|
63
|
+
|
|
64
|
+
## Contributing
|
|
65
|
+
|
|
66
|
+
We welcome contributions! Please see our [GitHub repository](https://github.com/condecon/adaptivetesting) for:
|
|
67
|
+
|
|
68
|
+
- Issue tracking
|
|
69
|
+
- Feature requests
|
|
70
|
+
- Pull request guidelines
|
|
71
|
+
- Development setup
|
|
72
|
+
|
|
73
|
+
## Research and Applications
|
|
74
|
+
|
|
75
|
+
This package is designed for researchers and practitioners in:
|
|
76
|
+
|
|
77
|
+
- Educational assessment
|
|
78
|
+
- Psychological testing
|
|
79
|
+
- Cognitive ability measurement
|
|
80
|
+
- Adaptive learning systems
|
|
81
|
+
- Psychometric research
|
|
82
|
+
|
|
83
|
+
The package facilitates the transition from research simulation to real-world testing applications without requiring major code modifications.
|
|
84
|
+
|
|
85
|
+
## Citation
|
|
86
|
+
If you use this package for your academic work, please provide the following reference:
|
|
87
|
+
Engicht, J., Bee, R. M., & Koch, T. (2025). Customizable Bayesian Adaptive Testing with Python – The adaptivetesting Package. Open Science Framework. https://doi.org/10.31219/osf.io/d2xge_v1
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
@online{engichtCustomizableBayesianAdaptive2025,
|
|
91
|
+
title = {Customizable {{Bayesian Adaptive Testing}} with {{Python}} – {{The}} Adaptivetesting {{Package}}},
|
|
92
|
+
author = {Engicht, Jonas and Bee, R. Maximilian and Koch, Tobias},
|
|
93
|
+
date = {2025-08-06},
|
|
94
|
+
eprinttype = {Open Science Framework},
|
|
95
|
+
doi = {10.31219/osf.io/d2xge_v1},
|
|
96
|
+
url = {https://osf.io/d2xge_v1},
|
|
97
|
+
pubstate = {prepublished}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
This project is licensed under the terms specified in the [LICENSE](LICENSE) file.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# adaptivetesting
|
|
2
|
+
|
|
3
|
+
<img src="/docs/_static/logo.svg" style="width: 100%">
|
|
4
|
+
</img>
|
|
5
|
+
|
|
6
|
+
**An open-source Python package for simplified, customizable Computerized Adaptive Testing (CAT) using Bayesian methods.**
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Key Features
|
|
10
|
+
|
|
11
|
+
- **Bayesian Methods**: Built-in support for Bayesian ability estimation with customizable priors
|
|
12
|
+
- **Flexible Architecture**: Object-oriented design with abstract classes for easy extension
|
|
13
|
+
- **Item Response Theory**: Full support for 1PL, 2PL, 3PL, and 4PL models, GRM, GPCM
|
|
14
|
+
- **Multiple Estimators**:
|
|
15
|
+
- Maximum Likelihood Estimation (MLE)
|
|
16
|
+
- Bayesian Modal Estimation (BM)
|
|
17
|
+
- Expected A Posteriori (EAP)
|
|
18
|
+
- **Item Selection Strategies**: Maximum information criterion
|
|
19
|
+
- **Content Balancing**: Maximum Priority Index, Weighted Penalty Model
|
|
20
|
+
- **Exposure Control**: Randomesque Item Selection, Maximum Priority Index
|
|
21
|
+
- **Simulation Framework**: Comprehensive tools for CAT simulation and evaluation
|
|
22
|
+
- **Real-world Application**: Direct transition from simulation to production testing
|
|
23
|
+
- **Stopping Criteria**: Support for standard error and test length criteria
|
|
24
|
+
- **Data Management**: Built-in support for CSV and pickle data formats
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Install from PyPI using pip:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install adaptivetesting
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For the latest development version:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install git+https://github.com/condecon/adaptivetesting
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Documentation
|
|
41
|
+
You can find our documentation in the [GitHub wiki](https://github.com/condecon/adaptivetesting/wiki).
|
|
42
|
+
|
|
43
|
+
## Contributing
|
|
44
|
+
|
|
45
|
+
We welcome contributions! Please see our [GitHub repository](https://github.com/condecon/adaptivetesting) for:
|
|
46
|
+
|
|
47
|
+
- Issue tracking
|
|
48
|
+
- Feature requests
|
|
49
|
+
- Pull request guidelines
|
|
50
|
+
- Development setup
|
|
51
|
+
|
|
52
|
+
## Research and Applications
|
|
53
|
+
|
|
54
|
+
This package is designed for researchers and practitioners in:
|
|
55
|
+
|
|
56
|
+
- Educational assessment
|
|
57
|
+
- Psychological testing
|
|
58
|
+
- Cognitive ability measurement
|
|
59
|
+
- Adaptive learning systems
|
|
60
|
+
- Psychometric research
|
|
61
|
+
|
|
62
|
+
The package facilitates the transition from research simulation to real-world testing applications without requiring major code modifications.
|
|
63
|
+
|
|
64
|
+
## Citation
|
|
65
|
+
If you use this package for your academic work, please provide the following reference:
|
|
66
|
+
Engicht, J., Bee, R. M., & Koch, T. (2025). Customizable Bayesian Adaptive Testing with Python – The adaptivetesting Package. Open Science Framework. https://doi.org/10.31219/osf.io/d2xge_v1
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
@online{engichtCustomizableBayesianAdaptive2025,
|
|
70
|
+
title = {Customizable {{Bayesian Adaptive Testing}} with {{Python}} – {{The}} Adaptivetesting {{Package}}},
|
|
71
|
+
author = {Engicht, Jonas and Bee, R. Maximilian and Koch, Tobias},
|
|
72
|
+
date = {2025-08-06},
|
|
73
|
+
eprinttype = {Open Science Framework},
|
|
74
|
+
doi = {10.31219/osf.io/d2xge_v1},
|
|
75
|
+
url = {https://osf.io/d2xge_v1},
|
|
76
|
+
pubstate = {prepublished}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
This project is licensed under the terms specified in the [LICENSE](LICENSE) file.
|
|
@@ -42,4 +42,14 @@ from .services.__item_selection_protocol import ItemSelectionStrategy
|
|
|
42
42
|
|
|
43
43
|
from .simulation.__simulation import Simulation, SimulationPool, setup_simulation_and_start
|
|
44
44
|
|
|
45
|
-
from .utils.__descriptives import bias, average_absolute_deviation, rmse
|
|
45
|
+
from .utils.__descriptives import bias, average_absolute_deviation, rmse
|
|
46
|
+
from .utils.__funcs import load_final_test_results, load_test_results_single_participant
|
|
47
|
+
from .utils.__plots import (
|
|
48
|
+
plot_exposure_rate,
|
|
49
|
+
plot_final_ability_estimates,
|
|
50
|
+
plot_icc,
|
|
51
|
+
plot_iif,
|
|
52
|
+
plot_exposure_rate,
|
|
53
|
+
plot_test_information,
|
|
54
|
+
plot_theta_estimation_trace
|
|
55
|
+
)
|
{adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/data/__read_prev_items_exp_cont.py
RENAMED
|
@@ -9,8 +9,16 @@ from .__pickle_context import PickleContext
|
|
|
9
9
|
def read_prev_items(format: Literal["CSV", "PICKLE"],
|
|
10
10
|
test_id: str,
|
|
11
11
|
participant_ids: list[str]) -> list[TestItem]:
|
|
12
|
-
"""Read previously administered items
|
|
13
|
-
to perform exposure control
|
|
12
|
+
"""Read previously administered items.
|
|
13
|
+
This function is internally used to perform exposure control.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
format (Literal["CSV", "PICKLE"]): The format of the results: CSV or PICKLE.
|
|
17
|
+
test_id (str): The test ID / simulation ID.
|
|
18
|
+
participant_ids (list[str]): The participant IDs.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
list[TestItem]: A list of TestItem objects.
|
|
14
22
|
"""
|
|
15
23
|
all_shown_test_items = []
|
|
16
24
|
|
|
@@ -23,9 +31,21 @@ def read_prev_items(format: Literal["CSV", "PICKLE"],
|
|
|
23
31
|
return all_shown_test_items
|
|
24
32
|
|
|
25
33
|
|
|
26
|
-
@deprecated("This function
|
|
27
|
-
" with revision branch")
|
|
34
|
+
@deprecated("This function will be removed in future releases.")
|
|
28
35
|
def read_single_participant(test_id: str, participant_id: str, format: Literal["CSV", "PICKLE"]) -> list[TestResult]:
|
|
36
|
+
"""
|
|
37
|
+
Reads the test results of a particular participant.
|
|
38
|
+
Args:
|
|
39
|
+
test_id (str): The test ID / simulation ID.
|
|
40
|
+
participant_id (str): The participant ID.
|
|
41
|
+
format (Literal["CSV", "PICKLE"]): The format of the results: CSV or PICKLE.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
list[TestResult]: A list of TestResult objects.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
ValueError: If the format is not supported.
|
|
48
|
+
"""
|
|
29
49
|
context: CSVContext | PickleContext
|
|
30
50
|
if format == "CSV":
|
|
31
51
|
context = CSVContext(test_id, participant_id)
|
|
@@ -33,3 +53,4 @@ def read_single_participant(test_id: str, participant_id: str, format: Literal["
|
|
|
33
53
|
if format == "PICKLE":
|
|
34
54
|
context = PickleContext(test_id, participant_id)
|
|
35
55
|
return context.load()
|
|
56
|
+
raise ValueError(f"Unknown format: {format}")
|
{adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/implementations/__test_assembler.py
RENAMED
|
@@ -133,6 +133,15 @@ class TestAssembler(AdaptiveTest):
|
|
|
133
133
|
content_balancing (CONTENT_BALANCING, optional): Selected content balancing strategy. Defaults to None.
|
|
134
134
|
If a content balancing strategy is specified, the item selection strategy will be ignored.
|
|
135
135
|
|
|
136
|
+
content_balancing_args (ContentBalancingArgs, optional): Arguments for the content balancing strategy.
|
|
137
|
+
Defaults to None.
|
|
138
|
+
|
|
139
|
+
exposure_control (EXPOSURE_CONTROL, optional): Exposure control strategy. Defaults to None.
|
|
140
|
+
If an exposure control strategy is specified, the item selection strategy will be ignored.
|
|
141
|
+
|
|
142
|
+
exposure_control_args (ExposureControlArgs, optional): Arguments for the exposure control strategy.
|
|
143
|
+
Defaults to None.
|
|
144
|
+
|
|
136
145
|
pretest (bool, optional): Whether to run a pretest phase before the main test. Defaults to False.
|
|
137
146
|
|
|
138
147
|
pretest_seed (int | None, optional): Random seed for pretest item selection. Defaults to None.
|
|
@@ -166,6 +175,7 @@ class TestAssembler(AdaptiveTest):
|
|
|
166
175
|
initial_ability_level,
|
|
167
176
|
simulation,
|
|
168
177
|
debug,
|
|
178
|
+
model_type=model_type,
|
|
169
179
|
**kwargs)
|
|
170
180
|
|
|
171
181
|
def estimate_ability_level(self):
|
|
@@ -345,7 +355,7 @@ class TestAssembler(AdaptiveTest):
|
|
|
345
355
|
The result of the superclass's run_test_once() method.
|
|
346
356
|
"""
|
|
347
357
|
# check if to run pretest
|
|
348
|
-
if self.__pretest
|
|
358
|
+
if self.__pretest:
|
|
349
359
|
pretest = PreTest(
|
|
350
360
|
self.item_pool.test_items,
|
|
351
361
|
self.__pretest_seed
|
|
@@ -353,7 +363,7 @@ class TestAssembler(AdaptiveTest):
|
|
|
353
363
|
# get selected items
|
|
354
364
|
random_items = pretest.select_random_item_quantile()
|
|
355
365
|
for item in random_items:
|
|
356
|
-
if self.simulation
|
|
366
|
+
if self.simulation:
|
|
357
367
|
response = self.item_pool.get_item_response(item)
|
|
358
368
|
else:
|
|
359
369
|
# not simulation
|
{adaptivetesting-1.2b1 → adaptivetesting-1.2.1}/adaptivetesting/math/__gen_response_pattern.py
RENAMED
|
@@ -13,7 +13,7 @@ def generate_response_pattern(ability: float,
|
|
|
13
13
|
model: Literal["GRM", "GPCM"] | None = None,
|
|
14
14
|
seed: int | None = None) -> list[int]:
|
|
15
15
|
"""Generates a response pattern for a given ability level
|
|
16
|
-
and item difficulties.
|
|
16
|
+
and item difficulties. A seed may be set for reproducibility.
|
|
17
17
|
|
|
18
18
|
Args:
|
|
19
19
|
ability (float): participants ability
|
|
@@ -48,7 +48,19 @@ def generate_response_pattern(ability: float,
|
|
|
48
48
|
|
|
49
49
|
def gen_pattern_dichotomous(ability: float,
|
|
50
50
|
items: list[TestItem],
|
|
51
|
-
rng: np.random.RandomState):
|
|
51
|
+
rng: np.random.RandomState) -> list[int]:
|
|
52
|
+
"""
|
|
53
|
+
Generates a response pattern for a given ability level for an IRT
|
|
54
|
+
model with dichotomous test items (e.g., 4PL, 3PL).
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
ability (float): participants ability
|
|
58
|
+
items (list[TestItem]): test items
|
|
59
|
+
rng (np.random.RandomState): random state (numpy object)
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
list[int]: generated response pattern
|
|
63
|
+
"""
|
|
52
64
|
responses: list[int] = []
|
|
53
65
|
|
|
54
66
|
for item in items:
|
|
@@ -82,6 +94,18 @@ def gen_pattern_poly(
|
|
|
82
94
|
model: Literal["GRM", "GPCM"],
|
|
83
95
|
rng: np.random.RandomState
|
|
84
96
|
) -> list[int]:
|
|
97
|
+
"""
|
|
98
|
+
Generates a response pattern for a given ability level for polytomous items.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
ability (float): participants ability
|
|
102
|
+
items (list[TestItem]): test items
|
|
103
|
+
model (literal, optional): model type (GRM, or GPCM)
|
|
104
|
+
rng (np.random.RandomState, optional): random state (numpy object)
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
list[int]: response pattern
|
|
108
|
+
"""
|
|
85
109
|
responses: list[int] = []
|
|
86
110
|
# loop through all items
|
|
87
111
|
for item in items:
|
|
@@ -21,6 +21,7 @@ def compute_priority_index(item: TestItem,
|
|
|
21
21
|
required_items (int): number of items required to be shown per constraint
|
|
22
22
|
shown_items (int): number of items already shown per constraint
|
|
23
23
|
current_ability (float): currently estimated ability level
|
|
24
|
+
model (Literal['GRM', 'GPCM'] | None): model type. Required for polytomous items.
|
|
24
25
|
|
|
25
26
|
Returns:
|
|
26
27
|
float: priority index of an item
|
|
@@ -103,7 +104,7 @@ def compute_expected_difference(proportion: float,
|
|
|
103
104
|
constraint_target (float): constraint target
|
|
104
105
|
|
|
105
106
|
Returns:
|
|
106
|
-
float:
|
|
107
|
+
float: expected difference
|
|
107
108
|
"""
|
|
108
109
|
expected_difference = proportion - constraint_target
|
|
109
110
|
return expected_difference
|
|
@@ -154,9 +155,9 @@ def compute_total_content_penalty_value_for_item(item: TestItem,
|
|
|
154
155
|
|
|
155
156
|
Args:
|
|
156
157
|
item (TestItem): given test item
|
|
157
|
-
shown_items:
|
|
158
|
-
available_items:
|
|
159
|
-
constraints (list[Constraint]):
|
|
158
|
+
shown_items: shown item
|
|
159
|
+
available_items: available items in the test
|
|
160
|
+
constraints (list[Constraint]): constraints assigned to the test and its items
|
|
160
161
|
|
|
161
162
|
Returns:
|
|
162
163
|
float: total content penalty value
|
|
@@ -21,7 +21,7 @@ ITEM_GROUP = Literal["green", "orange", "yellow", "red", None]
|
|
|
21
21
|
class WeightedPenaltyModel(ContentBalancing):
|
|
22
22
|
"""This content balancing method follows Shin et al. (2009)
|
|
23
23
|
and allows to apply constraints to the item selection.
|
|
24
|
-
The users can define a custom weight for the item information and the
|
|
24
|
+
The users can define a custom weight for the item information and the constraint.
|
|
25
25
|
|
|
26
26
|
References
|
|
27
27
|
------------
|
|
@@ -98,7 +98,7 @@ class WeightedPenaltyModel(ContentBalancing):
|
|
|
98
98
|
max_content_penalty = max(content_penalties)
|
|
99
99
|
min_content_penalty = min(content_penalties)
|
|
100
100
|
|
|
101
|
-
self.
|
|
101
|
+
self.calculate_weighted_penalty_for_all_items(
|
|
102
102
|
item_information_list=item_information_list,
|
|
103
103
|
max_item=max_item,
|
|
104
104
|
content_penalties=content_penalties,
|
|
@@ -114,7 +114,16 @@ class WeightedPenaltyModel(ContentBalancing):
|
|
|
114
114
|
# order items
|
|
115
115
|
self.order_candidate_items()
|
|
116
116
|
|
|
117
|
-
def calculate_information(self,
|
|
117
|
+
def calculate_information(self,
|
|
118
|
+
model: Literal['GRM', 'GPCM'] | None = None) -> list[float]:
|
|
119
|
+
"""
|
|
120
|
+
Calculates the item information for every item
|
|
121
|
+
Args:
|
|
122
|
+
model: model type. Required for polytomous models.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
list of item information
|
|
126
|
+
"""
|
|
118
127
|
information_list = [
|
|
119
128
|
float(item_information_function(
|
|
120
129
|
ability=self.ability,
|
|
@@ -126,6 +135,7 @@ class WeightedPenaltyModel(ContentBalancing):
|
|
|
126
135
|
return information_list
|
|
127
136
|
|
|
128
137
|
def calculate_content_penalties(self) -> list[float]:
|
|
138
|
+
"""Calculate content penalty values for every item"""
|
|
129
139
|
content_penalties = [
|
|
130
140
|
compute_total_content_penalty_value_for_item(
|
|
131
141
|
item=item,
|
|
@@ -137,12 +147,13 @@ class WeightedPenaltyModel(ContentBalancing):
|
|
|
137
147
|
]
|
|
138
148
|
return content_penalties
|
|
139
149
|
|
|
140
|
-
def
|
|
150
|
+
def calculate_weighted_penalty_for_all_items(self,
|
|
141
151
|
item_information_list: list[float],
|
|
142
152
|
max_item: float,
|
|
143
153
|
content_penalties: list[float],
|
|
144
154
|
max_content_penalty: float,
|
|
145
155
|
min_content_penalty: float):
|
|
156
|
+
"""Calculate the weighted penalty value for every item"""
|
|
146
157
|
for i, item in enumerate(self.items):
|
|
147
158
|
weighted_penalty_value = self.calculate_weighted_penalty_value(
|
|
148
159
|
item_information=item_information_list[i],
|
|
@@ -157,6 +168,8 @@ class WeightedPenaltyModel(ContentBalancing):
|
|
|
157
168
|
self.eligible_items.append((item, weighted_penalty_value, None))
|
|
158
169
|
|
|
159
170
|
def get_constraint_group_assignments(self) -> list[tuple[Constraint, CONSTRAINT_GROUP]]:
|
|
171
|
+
"""Assign every constraint to a constraint group depending on the number
|
|
172
|
+
of items shown from each specific constraint."""
|
|
160
173
|
group_assignment: list[tuple[Constraint, CONSTRAINT_GROUP]] = []
|
|
161
174
|
for constraint in self.constraints:
|
|
162
175
|
# calculate proportion of the constraint
|
|
@@ -183,6 +196,15 @@ class WeightedPenaltyModel(ContentBalancing):
|
|
|
183
196
|
@staticmethod
|
|
184
197
|
def assign_color_group_per_proportion(constraint: Constraint,
|
|
185
198
|
prop: float):
|
|
199
|
+
"""
|
|
200
|
+
Implements constraint assignment logic.
|
|
201
|
+
Args:
|
|
202
|
+
constraint (Constraint): constraint
|
|
203
|
+
prop (float): proportion
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
tuple[Constraint, str]: constraint and assigned group
|
|
207
|
+
"""
|
|
186
208
|
if constraint.lower is not None and constraint.upper is not None:
|
|
187
209
|
if prop <= constraint.lower:
|
|
188
210
|
return (constraint, "A")
|
|
@@ -195,6 +217,12 @@ class WeightedPenaltyModel(ContentBalancing):
|
|
|
195
217
|
|
|
196
218
|
def form_list_of_candidate_items(self,
|
|
197
219
|
group_assignment: list[tuple[Constraint, CONSTRAINT_GROUP]]) -> None:
|
|
220
|
+
"""
|
|
221
|
+
Form a list of candidate items according to the constraint group assignment.
|
|
222
|
+
Args:
|
|
223
|
+
group_assignment (list[tuple[Constraint, CONSTRAINT_GROUP]): list of constraints and assigned groups
|
|
224
|
+
|
|
225
|
+
"""
|
|
198
226
|
for i, item_entry in enumerate(self.eligible_items):
|
|
199
227
|
item, weighted_penalty_value, _ = item_entry
|
|
200
228
|
# find associated constraint
|
|
@@ -214,6 +242,17 @@ class WeightedPenaltyModel(ContentBalancing):
|
|
|
214
242
|
associated_constraints: list[tuple[Constraint, Literal['A', 'B', 'C']]],
|
|
215
243
|
weighted_penalty_value: float
|
|
216
244
|
):
|
|
245
|
+
"""
|
|
246
|
+
Assign items according to the constraint group assignment to an item group (color group)
|
|
247
|
+
Args:
|
|
248
|
+
item (TestItem): item
|
|
249
|
+
associated_constraints (list[tuple[Constraint, Literal['A', 'B', 'C']]]):
|
|
250
|
+
constraints relevant for this item
|
|
251
|
+
weighted_penalty_value (float): calculated weighted penalty value of this item
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
tuple[TestItem, float, str]: item, weighted penalty value and assigned color
|
|
255
|
+
"""
|
|
217
256
|
group_set = set([group for _, group in associated_constraints])
|
|
218
257
|
|
|
219
258
|
# if all associated constraints A or B -> green group
|
|
@@ -232,6 +271,8 @@ class WeightedPenaltyModel(ContentBalancing):
|
|
|
232
271
|
return (item, weighted_penalty_value, None)
|
|
233
272
|
|
|
234
273
|
def order_candidate_items(self):
|
|
274
|
+
"""Order candidate items according to the color group assignment.
|
|
275
|
+
"""
|
|
235
276
|
# between group ordering: green, orange, yellow, red
|
|
236
277
|
# within group ordering: ascending order of weighted penalty value
|
|
237
278
|
self.eligible_items = sorted(
|
|
@@ -251,6 +292,20 @@ class WeightedPenaltyModel(ContentBalancing):
|
|
|
251
292
|
constraint_weight: float,
|
|
252
293
|
information_weight: float
|
|
253
294
|
) -> float:
|
|
295
|
+
"""
|
|
296
|
+
Calculate the weighted penalty value of an item.
|
|
297
|
+
Args:
|
|
298
|
+
content_penalty: content penalty value
|
|
299
|
+
minimum_total_content_penalty: minimum total content penalty value
|
|
300
|
+
maximum_total_content_penalty: maximum total content penalty value
|
|
301
|
+
item_information: item information
|
|
302
|
+
max_information: maximum item information
|
|
303
|
+
constraint_weight: constraint weight
|
|
304
|
+
information_weight: information weight
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
weighted penalty value
|
|
308
|
+
"""
|
|
254
309
|
# reference content penalty
|
|
255
310
|
total_content_penalty_value = content_penalty
|
|
256
311
|
|
|
@@ -31,6 +31,8 @@ class BayesModal(IEstimator):
|
|
|
31
31
|
prior (Prior): prior distribution
|
|
32
32
|
|
|
33
33
|
optimization_interval (Tuple[float, float]): interval used for the optimization function
|
|
34
|
+
|
|
35
|
+
model (Literal["GRM", "GPCM"], optional): model type (required for polytomous models)
|
|
34
36
|
"""
|
|
35
37
|
super().__init__(response_pattern, items, optimization_interval)
|
|
36
38
|
|
|
@@ -31,6 +31,8 @@ class ExpectedAPosteriori(BayesModal):
|
|
|
31
31
|
prior (Prior): prior distribution
|
|
32
32
|
|
|
33
33
|
optimization_interval (Tuple[float, float]): interval used for the optimization function
|
|
34
|
+
|
|
35
|
+
model (Literal["GRM", "GPCM"], optional): model type (required for polytomous models)
|
|
34
36
|
"""
|
|
35
37
|
super().__init__(response_pattern, items, prior, optimization_interval)
|
|
36
38
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.optimize import minimize_scalar, OptimizeResult # type: ignore
|
|
3
|
+
from ..__prior import Prior
|
|
4
|
+
from ....models.__algorithm_exception import AlgorithmException
|
|
5
|
+
from .__estimators import log_likelihood
|
|
6
|
+
from .__estimators import probability_y0, probability_y1
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def maximize_posterior(
|
|
10
|
+
a: np.ndarray,
|
|
11
|
+
b: np.ndarray,
|
|
12
|
+
c: np.ndarray,
|
|
13
|
+
d: np.ndarray,
|
|
14
|
+
response_pattern: np.ndarray,
|
|
15
|
+
prior: Prior,
|
|
16
|
+
optimization_interval: tuple[float, float] = (-10, 10)
|
|
17
|
+
) -> float:
|
|
18
|
+
"""Get the maximum of the posterior distribution
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
a (np.ndarray): item parameter a
|
|
22
|
+
b (np.ndarray): item parameter b
|
|
23
|
+
c (np.ndarray): item parameter c
|
|
24
|
+
d (np.ndarray): item parameter d
|
|
25
|
+
response_pattern (np.ndarray): response pattern (simulated or user generated)
|
|
26
|
+
prior (Prior): prior distribution
|
|
27
|
+
optimization_interval (Tuple[float, float]): interval used for the optimization function
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
float: Bayes Modal estimator for the given parameters
|
|
31
|
+
"""
|
|
32
|
+
def log_posterior(mu):
|
|
33
|
+
log_likelihood_res = log_likelihood(mu, a, b, c, d, response_pattern)
|
|
34
|
+
|
|
35
|
+
if hasattr(prior, "logpdf"):
|
|
36
|
+
log_prior = prior.logpdf(mu)
|
|
37
|
+
else:
|
|
38
|
+
log_prior = np.log(np.clip(prior.pdf(mu), 1e-300, None))
|
|
39
|
+
|
|
40
|
+
log_post = log_likelihood_res + log_prior
|
|
41
|
+
|
|
42
|
+
if not np.isfinite(log_post):
|
|
43
|
+
return -1e300
|
|
44
|
+
else:
|
|
45
|
+
return float(log_post.ravel()[0])
|
|
46
|
+
|
|
47
|
+
result: OptimizeResult = minimize_scalar(lambda mu: -log_posterior(mu),
|
|
48
|
+
bounds=optimization_interval,
|
|
49
|
+
method="bounded") # type: ignore
|
|
50
|
+
|
|
51
|
+
if not result.success:
|
|
52
|
+
raise AlgorithmException(f"Optimization failed: {result.message}")
|
|
53
|
+
|
|
54
|
+
else:
|
|
55
|
+
return float(result.x)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def likelihood(mu: np.ndarray,
|
|
59
|
+
a: np.ndarray,
|
|
60
|
+
b: np.ndarray,
|
|
61
|
+
c: np.ndarray,
|
|
62
|
+
d: np.ndarray,
|
|
63
|
+
response_pattern: np.ndarray
|
|
64
|
+
):
|
|
65
|
+
|
|
66
|
+
p1 = probability_y1(mu, a, b, c, d)
|
|
67
|
+
p0 = probability_y0(mu, a, b, c, d)
|
|
68
|
+
|
|
69
|
+
log_likelihood = np.sum(
|
|
70
|
+
(response_pattern * np.log(p1 + 1e-300)) + (
|
|
71
|
+
(1 - response_pattern) * np.log(p0 + 1e-300)
|
|
72
|
+
))
|
|
73
|
+
|
|
74
|
+
return -np.exp(log_likelihood)
|