{ "cells": [ { "cell_type": "markdown", "id": "cell-0", "metadata": {}, "source": [ "# Example of a D-efficient RUM design using the Modified Federov algorithm\n", "\n", "This notebook illustrates how to use **ChoiceDesign** with the **Modified Federov** optimisation algorithm. Unlike the random swapping and RSC algorithms — which make small moves within the current design — Modified Federov searches more systematically:\n", "\n", "- At each iteration, a random row is selected from the current design.\n", "- That row is compared against **every row in the full factorial** of attribute levels.\n", "- The candidate that yields the greatest D-error improvement is kept.\n", "\n", "This makes each iteration more expensive than swap or RSC (one D-error evaluation per candidate row), but each accepted move is guaranteed to be the best available replacement for that row.\n", "\n", "The design setup — attributes, utility functions, and stopping criteria — is identical to the `d_efficient_rum_simple` example. The only difference is the `algorithm='federov'` argument passed to `optimise()`.\n", "\n", "## Step 1: Load modules, define design parameters and set attributes" ] }, { "cell_type": "code", "execution_count": 1, "id": "cell-1", "metadata": {}, "outputs": [], "source": [ "from choicedesign.design import EffDesign\n", "from choicedesign.expressions import Attribute, Parameter" ] }, { "cell_type": "markdown", "id": "cell-2", "metadata": {}, "source": [ "The following lines define 2 alternatives with 4 attributes each, named $A$ to $D$:" ] }, { "cell_type": "code", "execution_count": 2, "id": "cell-3", "metadata": {}, "outputs": [], "source": [ "alt1_A = Attribute('alt1_A', [1, 2, 3])\n", "alt1_B = Attribute('alt1_B', [10, 15, 15.5])\n", "alt1_C = Attribute('alt1_C', [0, 3, 5])\n", "alt1_D = Attribute('alt1_D', [0, 1, 2])\n", "\n", "alt2_A = Attribute('alt2_A', [1, 2, 3])\n", "alt2_B = Attribute('alt2_B', [10, 15, 15.5])\n", "alt2_C = Attribute('alt2_C', [0, 3, 5])\n", "alt2_D = Attribute('alt2_D', [0, 1, 2])" ] }, { "cell_type": "markdown", "id": "cell-4", "metadata": {}, "source": [ "## Step 2: Construct the design object and generate the initial design matrix" ] }, { "cell_type": "code", "execution_count": 3, "id": "cell-5", "metadata": {}, "outputs": [], "source": [ "design = EffDesign(\n", " X=[alt1_A, alt1_B, alt1_C, alt1_D,\n", " alt2_A, alt2_B, alt2_C, alt2_D],\n", " ncs=18)" ] }, { "cell_type": "code", "execution_count": 4, "id": "cell-6", "metadata": {}, "outputs": [ { "data": { "application/vnd.microsoft.datawrangler.viewer.v0+json": { "columns": [ { "name": "index", "rawType": "int64", "type": "integer" }, { "name": "alt1_A", "rawType": "float64", "type": "float" }, { "name": "alt1_B", "rawType": "float64", "type": "float" }, { "name": "alt1_C", "rawType": "float64", "type": "float" }, { "name": "alt1_D", "rawType": "float64", "type": "float" }, { "name": "alt2_A", "rawType": "float64", "type": "float" }, { "name": "alt2_B", "rawType": "float64", "type": "float" }, { "name": "alt2_C", "rawType": "float64", "type": "float" }, { "name": "alt2_D", "rawType": "float64", "type": "float" } ], "ref": "1d4b32d4-b660-45bc-b8fc-de1a1698d055", "rows": [ [ "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" ] ], "shape": { "columns": 8, "rows": 18 } }, "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
alt1_Aalt1_Balt1_Calt1_Dalt2_Aalt2_Balt2_Calt2_D
01.010.05.01.02.015.55.02.0
12.015.03.02.03.015.05.00.0
23.010.05.01.02.015.55.01.0
33.015.50.01.01.010.03.02.0
41.015.53.00.01.015.55.00.0
52.015.03.02.03.015.03.00.0
62.010.03.02.02.010.00.01.0
71.010.00.00.03.015.50.00.0
83.015.55.00.01.010.05.02.0
93.010.00.02.02.015.00.00.0
101.010.00.00.01.015.50.00.0
113.015.05.01.03.010.03.01.0
122.015.05.02.01.015.03.01.0
131.015.53.02.03.015.00.02.0
142.015.53.00.03.015.50.01.0
152.015.05.01.02.015.05.02.0
163.015.50.01.02.010.03.02.0
171.015.00.00.01.010.03.01.0
\n", "
" ], "text/plain": [ " alt1_A alt1_B alt1_C alt1_D alt2_A alt2_B alt2_C alt2_D\n", "0 1.0 10.0 5.0 1.0 2.0 15.5 5.0 2.0\n", "1 2.0 15.0 3.0 2.0 3.0 15.0 5.0 0.0\n", "2 3.0 10.0 5.0 1.0 2.0 15.5 5.0 1.0\n", "3 3.0 15.5 0.0 1.0 1.0 10.0 3.0 2.0\n", "4 1.0 15.5 3.0 0.0 1.0 15.5 5.0 0.0\n", "5 2.0 15.0 3.0 2.0 3.0 15.0 3.0 0.0\n", "6 2.0 10.0 3.0 2.0 2.0 10.0 0.0 1.0\n", "7 1.0 10.0 0.0 0.0 3.0 15.5 0.0 0.0\n", "8 3.0 15.5 5.0 0.0 1.0 10.0 5.0 2.0\n", "9 3.0 10.0 0.0 2.0 2.0 15.0 0.0 0.0\n", "10 1.0 10.0 0.0 0.0 1.0 15.5 0.0 0.0\n", "11 3.0 15.0 5.0 1.0 3.0 10.0 3.0 1.0\n", "12 2.0 15.0 5.0 2.0 1.0 15.0 3.0 1.0\n", "13 1.0 15.5 3.0 2.0 3.0 15.0 0.0 2.0\n", "14 2.0 15.5 3.0 0.0 3.0 15.5 0.0 1.0\n", "15 2.0 15.0 5.0 1.0 2.0 15.0 5.0 2.0\n", "16 3.0 15.5 0.0 1.0 2.0 10.0 3.0 2.0\n", "17 1.0 15.0 0.0 0.0 1.0 10.0 3.0 1.0" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "init_design = design.gen_initdesign(seed=42)\n", "init_design" ] }, { "cell_type": "markdown", "id": "cell-7", "metadata": {}, "source": [ "## Step 3: Define the utility functions" ] }, { "cell_type": "code", "execution_count": 5, "id": "cell-8", "metadata": {}, "outputs": [], "source": [ "beta_A = Parameter('beta_A', -0.1)\n", "beta_B = Parameter('beta_B', -0.02)\n", "beta_C = Parameter('beta_C', 0.1)\n", "beta_D = Parameter('beta_D', 0.15)" ] }, { "cell_type": "code", "execution_count": 6, "id": "cell-9", "metadata": {}, "outputs": [], "source": [ "V1 = beta_A * alt1_A + beta_B * alt1_B + beta_C * alt1_C + beta_D * alt1_D\n", "V2 = beta_A * alt2_A + beta_B * alt2_B + beta_C * alt2_C + beta_D * alt2_D\n", "\n", "V = {1: V1, 2: V2}" ] }, { "cell_type": "markdown", "id": "cell-10", "metadata": {}, "source": [ "## Step 4: Optimise using the Modified Federov algorithm\n", "\n", "The `algorithm='federov'` argument selects the Modified Federov algorithm. Note that Federov performs fewer iterations per unit time than swap or RSC, since each iteration evaluates all rows in the full factorial as candidates. A shorter `time_lim` is often sufficient:" ] }, { "cell_type": "code", "execution_count": 7, "id": "cell-11", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Evaluating initial design\n", "Optimization complete 0:00:57 / D-error: 0.028581\n", "Elapsed time: 0:01:00\n", "D-error of initial design: 0.080551\n", "D-error of last stored design: 0.028581\n", "Utility Balance ratio: 93.61 %\n", "Algorithm iterations: 24\n", "\n" ] } ], "source": [ "optimal_design, init_perf, final_perf, final_iter, ubalance_ratio = design.optimise(\n", " init_design=init_design,\n", " V=V,\n", " model='mnl',\n", " algorithm='federov',\n", " time_lim=1,\n", " verbose=True\n", ")" ] }, { "cell_type": "markdown", "id": "cell-12", "metadata": {}, "source": [ "## Step 5: Block the design" ] }, { "cell_type": "code", "execution_count": 8, "id": "cell-13", "metadata": {}, "outputs": [ { "data": { "application/vnd.microsoft.datawrangler.viewer.v0+json": { "columns": [ { "name": "index", "rawType": "int64", "type": "integer" }, { "name": "CS", "rawType": "float64", "type": "float" }, { "name": "alt1_A", "rawType": "float64", "type": "float" }, { "name": "alt1_B", "rawType": "float64", "type": "float" }, { "name": "alt1_C", "rawType": "float64", "type": "float" }, { "name": "alt1_D", "rawType": "float64", "type": "float" }, { "name": "alt2_A", "rawType": "float64", "type": "float" }, { "name": "alt2_B", "rawType": "float64", "type": "float" }, { "name": "alt2_C", "rawType": "float64", "type": "float" }, { "name": "alt2_D", "rawType": "float64", "type": "float" }, { "name": "Block", "rawType": "int64", "type": "integer" } ], "ref": "58300004-bb43-4906-b8a6-376581cf2329", "rows": [ [ "0", "1.0", "1.0", "10.0", "5.0", "1.0", "2.0", "15.5", "5.0", "2.0", "3" ], [ "1", "2.0", "1.0", "10.0", "0.0", "0.0", "3.0", "15.5", "5.0", "2.0", "1" ], [ "2", "3.0", "3.0", "10.0", "5.0", "1.0", "2.0", "15.5", "5.0", "1.0", "2" ], [ "3", "4.0", "1.0", "10.0", "5.0", "0.0", "3.0", "15.5", "0.0", "2.0", "3" ], [ "4", "5.0", "1.0", "10.0", "5.0", "2.0", "3.0", "15.5", "0.0", "0.0", "3" ], [ "5", "6.0", "1.0", "10.0", "5.0", "2.0", "3.0", "15.5", "0.0", "0.0", "1" ], [ "6", "7.0", "1.0", "10.0", "0.0", "0.0", "3.0", "15.5", "5.0", "2.0", "2" ], [ "7", "8.0", "1.0", "10.0", "0.0", "0.0", "3.0", "15.5", "0.0", "0.0", "2" ], [ "8", "9.0", "1.0", "15.5", "5.0", "0.0", "3.0", "10.0", "0.0", "2.0", "1" ], [ "9", "10.0", "1.0", "15.5", "5.0", "0.0", "3.0", "10.0", "0.0", "2.0", "3" ], [ "10", "11.0", "1.0", "15.5", "0.0", "2.0", "3.0", "10.0", "5.0", "0.0", "3" ], [ "11", "12.0", "1.0", "15.5", "5.0", "0.0", "3.0", "10.0", "0.0", "2.0", "1" ], [ "12", "13.0", "1.0", "15.5", "0.0", "0.0", "3.0", "10.0", "5.0", "2.0", "3" ], [ "13", "14.0", "1.0", "15.5", "0.0", "2.0", "3.0", "10.0", "5.0", "0.0", "2" ], [ "14", "15.0", "1.0", "15.5", "5.0", "2.0", "3.0", "10.0", "0.0", "0.0", "2" ], [ "15", "16.0", "2.0", "15.0", "5.0", "1.0", "2.0", "15.0", "5.0", "2.0", "1" ], [ "16", "17.0", "1.0", "10.0", "0.0", "2.0", "3.0", "15.5", "5.0", "0.0", "1" ], [ "17", "18.0", "1.0", "15.0", "0.0", "0.0", "1.0", "10.0", "3.0", "1.0", "2" ] ], "shape": { "columns": 10, "rows": 18 } }, "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
CSalt1_Aalt1_Balt1_Calt1_Dalt2_Aalt2_Balt2_Calt2_DBlock
01.01.010.05.01.02.015.55.02.03
12.01.010.00.00.03.015.55.02.01
23.03.010.05.01.02.015.55.01.02
34.01.010.05.00.03.015.50.02.03
45.01.010.05.02.03.015.50.00.03
56.01.010.05.02.03.015.50.00.01
67.01.010.00.00.03.015.55.02.02
78.01.010.00.00.03.015.50.00.02
89.01.015.55.00.03.010.00.02.01
910.01.015.55.00.03.010.00.02.03
1011.01.015.50.02.03.010.05.00.03
1112.01.015.55.00.03.010.00.02.01
1213.01.015.50.00.03.010.05.02.03
1314.01.015.50.02.03.010.05.00.02
1415.01.015.55.02.03.010.00.00.02
1516.02.015.05.01.02.015.05.02.01
1617.01.010.00.02.03.015.55.00.01
1718.01.015.00.00.01.010.03.01.02
\n", "
" ], "text/plain": [ " CS alt1_A alt1_B alt1_C alt1_D alt2_A alt2_B alt2_C alt2_D \\\n", "0 1.0 1.0 10.0 5.0 1.0 2.0 15.5 5.0 2.0 \n", "1 2.0 1.0 10.0 0.0 0.0 3.0 15.5 5.0 2.0 \n", "2 3.0 3.0 10.0 5.0 1.0 2.0 15.5 5.0 1.0 \n", "3 4.0 1.0 10.0 5.0 0.0 3.0 15.5 0.0 2.0 \n", "4 5.0 1.0 10.0 5.0 2.0 3.0 15.5 0.0 0.0 \n", "5 6.0 1.0 10.0 5.0 2.0 3.0 15.5 0.0 0.0 \n", "6 7.0 1.0 10.0 0.0 0.0 3.0 15.5 5.0 2.0 \n", "7 8.0 1.0 10.0 0.0 0.0 3.0 15.5 0.0 0.0 \n", "8 9.0 1.0 15.5 5.0 0.0 3.0 10.0 0.0 2.0 \n", "9 10.0 1.0 15.5 5.0 0.0 3.0 10.0 0.0 2.0 \n", "10 11.0 1.0 15.5 0.0 2.0 3.0 10.0 5.0 0.0 \n", "11 12.0 1.0 15.5 5.0 0.0 3.0 10.0 0.0 2.0 \n", "12 13.0 1.0 15.5 0.0 0.0 3.0 10.0 5.0 2.0 \n", "13 14.0 1.0 15.5 0.0 2.0 3.0 10.0 5.0 0.0 \n", "14 15.0 1.0 15.5 5.0 2.0 3.0 10.0 0.0 0.0 \n", "15 16.0 2.0 15.0 5.0 1.0 2.0 15.0 5.0 2.0 \n", "16 17.0 1.0 10.0 0.0 2.0 3.0 15.5 5.0 0.0 \n", "17 18.0 1.0 15.0 0.0 0.0 1.0 10.0 3.0 1.0 \n", "\n", " Block \n", "0 3 \n", "1 1 \n", "2 2 \n", "3 3 \n", "4 3 \n", "5 1 \n", "6 2 \n", "7 2 \n", "8 1 \n", "9 3 \n", "10 3 \n", "11 1 \n", "12 3 \n", "13 2 \n", "14 2 \n", "15 1 \n", "16 1 \n", "17 2 " ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "optimal_design_blocked, corr_history = design.gen_blocks(optimal_design, n_blocks=3)\n", "optimal_design_blocked" ] }, { "cell_type": "markdown", "id": "cell-14", "metadata": {}, "source": [ "## (Optional) Evaluate the design" ] }, { "cell_type": "code", "execution_count": 9, "id": "cell-15", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.02858103102201532 93.60915724865137\n" ] } ], "source": [ "perf, ubalance = design.evaluate(optimal_design, V, model='mnl')\n", "print(perf, ubalance)" ] }, { "cell_type": "markdown", "id": "1e9ba8ab", "metadata": {}, "source": [ "## Export the design\n", "\n", "Export the Federov-optimised design to Excel." ] }, { "cell_type": "code", "execution_count": null, "id": "2daf2a8f", "metadata": {}, "outputs": [], "source": [ "attr_names = {\n", " 'alt1_A': 'Attribute A', 'alt2_A': 'Attribute A',\n", " 'alt1_B': 'Attribute B', 'alt2_B': 'Attribute B',\n", " 'alt1_C': 'Attribute C', 'alt2_C': 'Attribute C',\n", " 'alt1_D': 'Attribute D', 'alt2_D': 'Attribute D',\n", "}\n", "design.export_design(optimal_design, attr_names, 'rum_federov_design.xlsx')" ] }, { "cell_type": "markdown", "id": "a61500d0", "metadata": {}, "source": [ "## Save the optimisation summary\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": null, "id": "964dfbe4", "metadata": {}, "outputs": [], "source": [ "design.export_output('rum_federov_output.txt')" ] }, { "cell_type": "markdown", "id": "cell-16", "metadata": {}, "source": [ "## References\n", "\n", "[1] Quan, W., Rose, J. M., Collins, A. T., & Bliemer, M. C. (2011). A comparison of algorithms for generating efficient choice experiments." ] } ], "metadata": { "kernelspec": { "display_name": "choicedesign-oSBhddzi-py3.13", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.1" } }, "nbformat": 4, "nbformat_minor": 5 }