Skip to content

Customers choice models

A choice model emulates customers selection criteria while choosing flight booking offers. From a functional perspective, a choice model maps a list of booking offers for a given flight to a selected booking offer, either deterministically or by sampling from some statistical distribution. The selected offer may be none (ie, all offers are rejected), with some probability depending on the type of the model.

Parameters:

NameTypeDescriptionDefault
class_namestr

Identifier of a customers class (eg business, leisure)

required

BaseChoiceModel dataclass

Bases: CustomersModel

Base dataclass for choice models.

Source code in src/rmlab/data/parametric/customers_choice.py
20
21
22
@dataclass
class BaseChoiceModel(CustomersModel):
    """Base dataclass for choice models."""

Cheapest dataclass

Bases: BaseChoiceModel

Parameterless model. Picks the booking offer with the minimum price. Randomly breaking ties if two offers have the same minimum price. The probability of choosing none is zero.

Parameters:

NameTypeDescriptionDefault
class_namestr

Customers class

required
Source code in src/rmlab/data/parametric/customers_choice.py
38
39
40
41
42
43
44
45
46
47
48
49
@dataclass
class Cheapest(BaseChoiceModel):
    """Parameterless model. Picks the booking offer with the minimum price.
    Randomly breaking ties if two offers have the same minimum price.
    The probability of choosing none is zero.

    Args:
      class_name (str): Customers class

    """

    class_name: str

Multinomial dataclass

Bases: BaseChoiceModel

A multinomial logistic model is used to score the list of booking offers according to its price.

This model specifies a price sensitivity parameter \(\beta\) and a rejection probability parameter \(prob_{reject}\) in [0, 1], so that the probability of rejecting all offers is always fixed to \(prob_{reject}\).

The probability of accepting any booking offer is thus \(1 - prob_{reject}\), and the acceptance probabilities of the offers given no-rejection are weighted according to a score s computed from their prices p: $$ s = \exp(-p/ \beta). $$

Note that regarding \(\beta\) sensitivity, its magnitude corresponds to the unit currency (eg: euros, gbps, ..., and not euro cents or gbp pennies, ...).

Parameters:

NameTypeDescriptionDefault
class_namestr

Customers class

required
betafloat
\[\beta\]
required
reject_probfloat

\(prob_{reject}\)

required
Source code in src/rmlab/data/parametric/customers_choice.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@dataclass
class Multinomial(BaseChoiceModel):
    """
    A multinomial logistic model is used to score the list of booking offers according to its price.

    This model specifies a price sensitivity parameter $\\beta$ and a rejection probability
    parameter $prob_{reject}$ in [0, 1], so that the probability of rejecting all offers is always fixed to $prob_{reject}$.

    The probability of accepting any booking offer is thus $1 - prob_{reject}$, and the
    acceptance probabilities of the offers given no-rejection are weighted according to a score *s*
    computed from their prices *p*:
    $$
      s = \exp(-p/ \\beta).
    $$

    Note that regarding $\\beta$ sensitivity, its magnitude corresponds to the *unit*
    currency (eg: *euros*, *gbps*, ..., and **not** *euro cents* or *gbp pennies*, ...).

    Args:
      class_name (str): Customers class
      beta (float): $$\\beta$$
      reject_prob (float): $prob_{reject}$

    """

    class_name: str
    beta: float
    reject_prob: float

    def __post_init__(self):

        if self.beta <= 0:
            raise ValueError(f"Beta must be positive, got `{self.beta}`")

        if self.reject_prob < 0 or self.reject_prob >= 1:
            raise ValueError(f"Reject probability must be in [0, 1)")

MultipleChoiceModels dataclass

Bases: BaseChoiceModel

An aggregation of request models for different customer classes.

Example:

bus_choice_model = Multinomial(class_name="business", beta=30, reject_prob=0.5
lei_choice_model = Cheapest(class_name="leisure", beta=10)
rnd_choice_model = Random(class_name="careless")

mul_chouce_model = MultipleChoiceModels(bus_choice_model, lei_choice_model, rnd_choice_model)

JSON representation:

{
  "business" : {"type" : "mnl_multiple_reject","beta": 140.0, "reject_prob": 0.5},
  "leisure": {"type": "cheapest"},
  "careless": {"type": "random"}
}

Source code in src/rmlab/data/parametric/customers_choice.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
@dataclass
class MultipleChoiceModels(BaseChoiceModel):
    """An aggregation of request models for different customer classes.

    Example:
    ```py
    bus_choice_model = Multinomial(class_name="business", beta=30, reject_prob=0.5
    lei_choice_model = Cheapest(class_name="leisure", beta=10)
    rnd_choice_model = Random(class_name="careless")

    mul_chouce_model = MultipleChoiceModels(bus_choice_model, lei_choice_model, rnd_choice_model)
    ```

    JSON representation:
    ```json
    {
      "business" : {"type" : "mnl_multiple_reject","beta": 140.0, "reject_prob": 0.5},
      "leisure": {"type": "cheapest"},
      "careless": {"type": "random"}
    }
    ```
    """

    models: List[BaseChoiceModel]

Random dataclass

Bases: BaseChoiceModel

Parameterless model. Picks a single booking offer randomly regardless of any parameter. The probability of choosing none is zero.

Parameters:

NameTypeDescriptionDefault
class_namestr

Customers class

required
Source code in src/rmlab/data/parametric/customers_choice.py
25
26
27
28
29
30
31
32
33
34
35
@dataclass
class Random(BaseChoiceModel):
    """Parameterless model. Picks a single booking offer randomly regardless of any parameter.
    The probability of choosing none is zero.

    Args:
      class_name (str): Customers class

    """

    class_name: str

make_customers_choice_model_from_json(filename_or_dict)

Make a request model instance from a json representation (from file or dict).

Parameters:

NameTypeDescriptionDefault
filename_or_dictUnion[str, dict]

JSON filename or dictionary in json format

required

Raises:

TypeDescription
ValueError

If JSON is not a dict

ValueError

If model type is invalid

ValueError

If JSON content is empty

Returns:

TypeDescription
BaseChoiceModel

A customers request model instance

Example from dict:

my_dict = dict()
my_dict["business"] = {"type": "poisson", "beta": 30.0, "scale": 400, "lambda_seats": 1.5}
my_dict["leisure"] = {"type": "poisson", "beta": 10.0, "scale": 300, "lambda_seats": 3}

my_choice_model = make_from_json(my_dict)

Example from file: my_choice_model.json

{
  "business" : {"type" : "mnl_multiple_reject","beta": 140.0, "reject_prob": 0.5},
  "leisure": {"type": "cheapest"},
  "careless": {"type": "random"}
}
my_choice_model = make_customers_choice_model_from_json("my_choice_model.json")

Source code in src/rmlab/data/parametric/customers_choice.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
def make_customers_choice_model_from_json(
    filename_or_dict: Union[str, dict]
) -> BaseChoiceModel:
    """Make a request model instance from a json representation (from file or dict).

    Args:
        filename_or_dict (Union[str, dict]): JSON filename or dictionary in json format

    Raises:
        ValueError: If JSON is not a dict
        ValueError: If model type is invalid
        ValueError: If JSON content is empty

    Returns:
        A customers request model instance


    Example from dict:
    ```py
    my_dict = dict()
    my_dict["business"] = {"type": "poisson", "beta": 30.0, "scale": 400, "lambda_seats": 1.5}
    my_dict["leisure"] = {"type": "poisson", "beta": 10.0, "scale": 300, "lambda_seats": 3}

    my_choice_model = make_from_json(my_dict)
    ```

    Example from file:
    `my_choice_model.json`
    ```json
    {
      "business" : {"type" : "mnl_multiple_reject","beta": 140.0, "reject_prob": 0.5},
      "leisure": {"type": "cheapest"},
      "careless": {"type": "random"}
    }
    ```
    ```py
    my_choice_model = make_customers_choice_model_from_json("my_choice_model.json")
    ```
    """

    model_id, filename, md5hash, content = parse_data_from_json(filename_or_dict)

    if not isinstance(content, dict):
        raise ValueError(f"Expected dict format in {filename_or_dict}")

    content: dict

    if len(content) > 0:
        models = list()
        for class_name, fields in content.items():
            if fields["type"] == "random":
                models.append(
                    Random(
                        id=model_id,
                        filename=filename,
                        cnt=json.dumps(content),
                        hash=md5hash,
                        kind=CustomersModelKind.CHOICE,
                        extension=FileExtensions.JSON,
                        class_name=class_name,
                    )
                )
            elif fields["type"] == "cheapest":
                models.append(
                    Cheapest(
                        id=model_id,
                        filename=filename,
                        cnt=json.dumps(content),
                        hash=md5hash,
                        kind=CustomersModelKind.CHOICE,
                        extension=FileExtensions.JSON,
                        class_name=class_name,
                    )
                )
            elif fields["type"] == "mnl_multiple_reject":
                models.append(
                    Multinomial(
                        id=model_id,
                        filename=filename,
                        cnt=json.dumps(content),
                        hash=md5hash,
                        kind=CustomersModelKind.CHOICE,
                        extension=FileExtensions.JSON,
                        class_name=class_name,
                        beta=float(fields["beta"]),
                        reject_prob=float(fields["reject_prob"]),
                    )
                )
            else:
                raise ValueError(f'Unknown request model type {fields["type"]}')

        if len(models) > 1:
            return MultipleChoiceModels(
                id=model_id,
                filename=filename,
                cnt=json.dumps(content),
                hash=md5hash,
                kind=CustomersModelKind.CHOICE,
                extension=FileExtensions.JSON,
                models=models,
            )
        else:
            return models[0]

    else:
        raise ValueError(f"Empty content")