Example of a D-efficient RUM design using the RSC algorithm
This notebook illustrates how to use ChoiceDesign with the RSC (Relabelling, Swapping, Cycling) optimisation algorithm. RSC extends the basic random swapping approach with two additional move types:
Relabelling (R): swaps all occurrences of two level values in a column.
Swapping (S): swaps the values of two individual rows in a column (same as the default algorithm).
Cycling (C): rotates all values in a column by one position.
Each iteration, a column and a move type are chosen at random. The move is kept if it improves the D-error.
The design setup — attributes, utility functions, and stopping criteria — is identical to the d_efficient_rum_simple example. The only difference is the algorithm='rsc' argument passed to optimise().
Step 1: Load modules, define design parameters and set attributes
[1]:
from choicedesign.design import EffDesign
from choicedesign.expressions import Attribute, Parameter
The following lines define 2 alternatives with 4 attributes each, named \(A\) to \(D\):
[2]:
alt1_A = Attribute('alt1_A', [1, 2, 3])
alt1_B = Attribute('alt1_B', [10, 15, 15.5])
alt1_C = Attribute('alt1_C', [0, 3, 5])
alt1_D = Attribute('alt1_D', [0, 1, 2])
alt2_A = Attribute('alt2_A', [1, 2, 3])
alt2_B = Attribute('alt2_B', [10, 15, 15.5])
alt2_C = Attribute('alt2_C', [0, 3, 5])
alt2_D = Attribute('alt2_D', [0, 1, 2])
Step 2: Construct the design object and generate the initial design matrix
[3]:
design = EffDesign(
X=[alt1_A, alt1_B, alt1_C, alt1_D,
alt2_A, alt2_B, alt2_C, alt2_D],
ncs=18)
[4]:
init_design = design.gen_initdesign(seed=42)
init_design
[4]:
| alt1_A | alt1_B | alt1_C | alt1_D | alt2_A | alt2_B | alt2_C | alt2_D | |
|---|---|---|---|---|---|---|---|---|
| 0 | 1.0 | 10.0 | 5.0 | 1.0 | 2.0 | 15.5 | 5.0 | 2.0 |
| 1 | 2.0 | 15.0 | 3.0 | 2.0 | 3.0 | 15.0 | 5.0 | 0.0 |
| 2 | 3.0 | 10.0 | 5.0 | 1.0 | 2.0 | 15.5 | 5.0 | 1.0 |
| 3 | 3.0 | 15.5 | 0.0 | 1.0 | 1.0 | 10.0 | 3.0 | 2.0 |
| 4 | 1.0 | 15.5 | 3.0 | 0.0 | 1.0 | 15.5 | 5.0 | 0.0 |
| 5 | 2.0 | 15.0 | 3.0 | 2.0 | 3.0 | 15.0 | 3.0 | 0.0 |
| 6 | 2.0 | 10.0 | 3.0 | 2.0 | 2.0 | 10.0 | 0.0 | 1.0 |
| 7 | 1.0 | 10.0 | 0.0 | 0.0 | 3.0 | 15.5 | 0.0 | 0.0 |
| 8 | 3.0 | 15.5 | 5.0 | 0.0 | 1.0 | 10.0 | 5.0 | 2.0 |
| 9 | 3.0 | 10.0 | 0.0 | 2.0 | 2.0 | 15.0 | 0.0 | 0.0 |
| 10 | 1.0 | 10.0 | 0.0 | 0.0 | 1.0 | 15.5 | 0.0 | 0.0 |
| 11 | 3.0 | 15.0 | 5.0 | 1.0 | 3.0 | 10.0 | 3.0 | 1.0 |
| 12 | 2.0 | 15.0 | 5.0 | 2.0 | 1.0 | 15.0 | 3.0 | 1.0 |
| 13 | 1.0 | 15.5 | 3.0 | 2.0 | 3.0 | 15.0 | 0.0 | 2.0 |
| 14 | 2.0 | 15.5 | 3.0 | 0.0 | 3.0 | 15.5 | 0.0 | 1.0 |
| 15 | 2.0 | 15.0 | 5.0 | 1.0 | 2.0 | 15.0 | 5.0 | 2.0 |
| 16 | 3.0 | 15.5 | 0.0 | 1.0 | 2.0 | 10.0 | 3.0 | 2.0 |
| 17 | 1.0 | 15.0 | 0.0 | 0.0 | 1.0 | 10.0 | 3.0 | 1.0 |
Step 3: Define the utility functions
[5]:
beta_A = Parameter('beta_A', -0.1)
beta_B = Parameter('beta_B', -0.02)
beta_C = Parameter('beta_C', 0.1)
beta_D = Parameter('beta_D', 0.15)
[6]:
V1 = beta_A * alt1_A + beta_B * alt1_B + beta_C * alt1_C + beta_D * alt1_D
V2 = beta_A * alt2_A + beta_B * alt2_B + beta_C * alt2_C + beta_D * alt2_D
V = {1: V1, 2: V2}
Step 4: Optimise using the RSC algorithm
The algorithm='rsc' argument selects the RSC algorithm. All other arguments are identical to the default 'swap' algorithm. The optimisation runs for 1 minute:
[7]:
optimal_design, init_perf, final_perf, final_iter, ubalance_ratio = design.optimise(
init_design=init_design,
V=V,
model='mnl',
algorithm='rsc',
time_lim=1,
verbose=True
)
Evaluating initial design
Optimization complete 0:00:59 / D-error: 0.033939
Elapsed time: 0:01:00
D-error of initial design: 0.080551
D-error of last stored design: 0.033939
Utility Balance ratio: 94.88 %
Algorithm iterations: 148538
Step 5: Block the design
[8]:
optimal_design_blocked, corr_history = design.gen_blocks(optimal_design, n_blocks=3)
optimal_design_blocked
[8]:
| CS | alt1_A | alt1_B | alt1_C | alt1_D | alt2_A | alt2_B | alt2_C | alt2_D | Block | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1.0 | 1.0 | 10.0 | 5.0 | 1.0 | 3.0 | 15.5 | 0.0 | 1.0 | 2 |
| 1 | 2.0 | 2.0 | 15.5 | 3.0 | 2.0 | 2.0 | 10.0 | 3.0 | 0.0 | 3 |
| 2 | 3.0 | 3.0 | 15.0 | 5.0 | 0.0 | 1.0 | 15.0 | 0.0 | 2.0 | 3 |
| 3 | 4.0 | 1.0 | 15.5 | 0.0 | 1.0 | 3.0 | 10.0 | 5.0 | 1.0 | 1 |
| 4 | 5.0 | 3.0 | 15.0 | 3.0 | 0.0 | 1.0 | 15.0 | 3.0 | 2.0 | 1 |
| 5 | 6.0 | 2.0 | 10.0 | 0.0 | 2.0 | 2.0 | 15.5 | 5.0 | 0.0 | 3 |
| 6 | 7.0 | 2.0 | 15.0 | 5.0 | 2.0 | 2.0 | 15.0 | 0.0 | 0.0 | 1 |
| 7 | 8.0 | 1.0 | 10.0 | 3.0 | 2.0 | 3.0 | 15.5 | 3.0 | 0.0 | 2 |
| 8 | 9.0 | 3.0 | 10.0 | 3.0 | 0.0 | 1.0 | 15.5 | 3.0 | 2.0 | 2 |
| 9 | 10.0 | 3.0 | 10.0 | 0.0 | 2.0 | 1.0 | 15.5 | 5.0 | 0.0 | 1 |
| 10 | 11.0 | 1.0 | 15.0 | 3.0 | 0.0 | 3.0 | 15.0 | 3.0 | 2.0 | 3 |
| 11 | 12.0 | 3.0 | 15.5 | 5.0 | 1.0 | 1.0 | 10.0 | 0.0 | 1.0 | 1 |
| 12 | 13.0 | 3.0 | 15.5 | 3.0 | 2.0 | 1.0 | 10.0 | 3.0 | 0.0 | 3 |
| 13 | 14.0 | 1.0 | 15.0 | 5.0 | 1.0 | 3.0 | 15.0 | 0.0 | 1.0 | 2 |
| 14 | 15.0 | 2.0 | 10.0 | 0.0 | 0.0 | 2.0 | 15.5 | 5.0 | 2.0 | 2 |
| 15 | 16.0 | 2.0 | 15.0 | 5.0 | 0.0 | 2.0 | 15.0 | 0.0 | 2.0 | 2 |
| 16 | 17.0 | 2.0 | 15.5 | 0.0 | 1.0 | 2.0 | 10.0 | 5.0 | 1.0 | 3 |
| 17 | 18.0 | 1.0 | 15.5 | 0.0 | 1.0 | 3.0 | 10.0 | 5.0 | 1.0 | 1 |
(Optional) Evaluate the design
[9]:
perf, ubalance = design.evaluate(optimal_design, V, model='mnl')
print(perf, ubalance)
0.033939284712415994 94.87569390189945
Export the design
Export the RSC-optimised design to Excel.
[ ]:
attr_names = {
'alt1_A': 'Attribute A', 'alt2_A': 'Attribute A',
'alt1_B': 'Attribute B', 'alt2_B': 'Attribute B',
'alt1_C': 'Attribute C', 'alt2_C': 'Attribute C',
'alt1_D': 'Attribute D', 'alt2_D': 'Attribute D',
}
design.export_design(optimal_design, attr_names, 'rum_rsc_design.xlsx')
Save the optimisation summary
After calling optimise(), the method export_output() writes a plain-text summary of the optimisation run — design configuration, stopping criteria, criterion values, utility balance, elapsed time, and iteration count — to a file.
[ ]:
design.export_output('rum_rsc_output.txt')
References
[1] Quan, W., Rose, J. M., Collins, A. T., & Bliemer, M. C. (2011). A comparison of algorithms for generating efficient choice experiments.