Skip to content

Paiements incitatifs : listes de prix, enchères BDM, élicitation des croyances et bonus Prolific

Rémunérez les participants selon leurs réponses dans Potato : listes de prix de Holt & Laury, enchères BDM et règle de score binarisée, tirées après l'étude et versées comme bonus Prolific.

Nouveau dans la v2.12.0

Potato peut rémunérer les participants en fonction de leurs réponses, comme le font les études d'économie expérimentale. Trois types d'annotation recueillent ces réponses :

annotation_typeDemandePayé par
price_listL'option A ou B à chaque ligne d'une liste de prix multiplempl_random_row
bdmLe montant maximal que le participant paierait pour quelque chosebdm (Becker, DeGroot & Marschak, 1964)
belief_elicitationUne probabilité, ou une distribution de 100 points sur des intervallesbsr, la règle de score binarisée (Hossain & Okui, 2013)

Le bloc incentives: indique comment chaque schéma est payé. Une fois qu'un participant a terminé, Potato tire les nombres aléatoires, calcule le gain et écrit un fichier de bonus au format d'import groupé de Prolific. Il ne paie jamais personne de lui-même.

Quand ont lieu les tirages

Rien n'est tiré pendant que le participant répond. La page ne voit jamais de prix, de ligne tirée ni de seuil ; aucun d'eux ne peut donc influencer une réponse. Les tirages ont lieu quand vous lancez l'export incentives ou appelez la route d'administration, et uniquement pour les participants qui ont terminé l'étude.

Chaque tirage reçoit une graine dérivée de l'identifiant utilisateur, du nom de la règle, de l'item ou de la page auquel appartient la réponse, et d'un incentives.seed privé. Relancer le calcul sur les mêmes réponses donne les mêmes tirages, ajouter un item laisse inchangés les tirages des autres items, et chaque tirage est écrit dans bonuses.csv.

Listes de prix

La liste de Holt & Laury (2002), avec au plus un point de bascule :

yaml
annotation_schemes:
  - annotation_type: price_list
    name: risk
    description: For each decision, choose the option you prefer.
    preset: holt_laury
    single_switch: true
    label_requirement: {required: true}

Le préréglage comporte dix lignes. L'option A paie 2,00 $ avec la probabilité p et 1,60 $ sinon, l'option B paie 3,85 $ avec la probabilité p et 0,10 $ sinon, et p va de 0,1, 0,2, ... à 1,0. scale: 10 paie dix fois ces montants.

Une liste de préférence temporelle oppose un montant immédiat à des montants plus élevés plus tard :

yaml
  - annotation_type: price_list
    name: patience
    description: Which would you rather receive?
    preset: time_preference
    amount_now: 10
    amounts_later: [10, 10.5, 11, 12, 13, 15]
    delay_label: in 4 weeks

Les lignes peuvent aussi être écrites à la main. a_pays et b_pays sont ce que paie la règle d'incitation, soit un nombre pour un montant certain, soit [[probability, amount], ...] pour une loterie ; sans eux, la liste peut être affichée mais pas payée :

yaml
  - annotation_type: price_list
    name: insurance
    description: Choose one option in each row.
    rows:
      - {a: "$5 for sure", b: "50% chance of $12, otherwise $0",
         a_pays: 5, b_pays: [[0.5, 12], [0.5, 0]]}
      - {a: "$6 for sure", b: "50% chance of $12, otherwise $0",
         a_pays: 6, b_pays: [[0.5, 12], [0.5, 0]]}
CléValeur par défautSignification
presetaucuneholt_laury ou time_preference. Indiquez soit preset, soit rows
rowsaucuneAu moins deux {a, b, a_pays, b_pays, a_after, b_after}. a_after et b_after désignent un délai de paiement, par exemple in 4 weeks
scale1Multiplie chaque montant d'un préréglage. Non autorisé avec rows
currency$Symbole affiché avant les montants
single_switchfalseLimite à une seule bascule de A vers B
option_labels[Option A, Option B]En-têtes de colonnes
amount_now, amounts_lateraucunetime_preference uniquement : le montant certain immédiat, et un montant ultérieur par ligne
delay_label, now_labelin 4 weeks, todaytime_preference uniquement : quand chaque option paie

Avec single_switch: true, choisir B à une ligne choisit aussi B à toutes les lignes suivantes, et choisir A choisit aussi A à toutes les lignes précédentes. Les lignes remplies de cette façon clignotent brièvement, pour que le participant voie ce qui a changé. Sans ce réglage, chaque ligne est indépendante, et la réponse enregistre combien de fois le participant a basculé. Chaque ligne est un groupe de boutons radio ; les touches fléchées et Tab y fonctionnent donc comme dans tout groupe de boutons radio.

Enchères BDM

yaml
annotation_schemes:
  - annotation_type: bdm
    name: voucher
    description: What is the most you would pay for a voucher worth $3.00?
    min: 0
    max: 5
    step: 0.05
    input: number
    instructions: >-
      You have $5.00 to spend. A price between $0.00 and $5.00 is drawn after
      the study. If your bid is at least the price, you buy the voucher and pay
      the price, not your bid; otherwise you keep the $5.00.
    label_requirement: {required: true}

max est obligatoire, et min vaut 0 par défaut. input: slider affiche un curseur au lieu d'un champ numérique, avec un curseur creux au milieu qui se remplit dès que le participant le déplace ; un curseur non touché n'est donc pas lu comme une enchère. L'intervalle de tirage et la dotation se trouvent dans la règle d'incitation, pas dans le schéma ; écrivez donc les mêmes nombres dans instructions pour que le participant connaisse la règle sous laquelle il enchérit.

Élicitation des croyances

Une probabilité :

yaml
annotation_schemes:
  - annotation_type: belief_elicitation
    name: forecast
    description: How likely is it that this happens?
    mode: point
    left_label: Certainly not
    right_label: Certainly
    label_requirement: {required: true}

Une distribution :

yaml
  - annotation_type: belief_elicitation
    name: rainfall
    description: How much rain will fall next week?
    mode: bins
    bins: ["0-9 mm", "10-19 mm", "20-29 mm", "30 mm or more"]
    label_requirement: {required: true}

En mode point, le curseur va de 0 à 100 % et démarre creux à 50 % ; un curseur non touché n'est donc pas lu comme une réponse de 50 %. En mode bins, le participant saisit un nombre entier de points pour chaque intervalle, et un total courant indique combien de points restent à placer. Tant que le total n'est pas exactement 100, la question se déclare incomplète.

Configurer le paiement

yaml
incentives:
  enabled: true
  currency: USD
  seed: "a private string, kept out of the repository"
  pay: one_random_item            # one_random_item | all | sum
  max_bonus: 6.00
  only_finished: true
  rules:
    - scheme: risk                # a price_list scheme
      method: mpl_random_row
    - scheme: voucher             # a bdm scheme
      method: bdm
      endowment: 5.00
      item_value: 3.00
      draw: {min: 0, max: 5, step: 0.25}
    - scheme: forecast            # a belief_elicitation scheme
      method: bsr
      prize: 2.00
      outcome_field: outcome
CléValeur par défautSignification
enabledfalseActive l'export incentives et la route d'administration
currencyUSDLibellé écrit dans bonuses.csv. Prolific paie dans la devise propre de l'étude ; réglez donc cette valeur pour qu'elle corresponde
seedaucuneTexte privé mélangé à chaque tirage. Sans lui, quiconque dispose du code pourrait calculer les tirages avant de répondre, et l'export avertit quand il manque ou qu'il a encore la valeur de l'exemple
payone_random_itemone_random_item paie une décision tirée au hasard parmi toutes les décisions auxquelles le participant a répondu. all paie la somme de toutes les décisions, et sum est un autre nom pour all
max_bonusaucunePlafond du bonus d'un participant
only_finishedtrueNe tire et ne paie que pour les participants arrivés à la fin de l'étude
rulesobligatoireUne entrée par schéma payé, chacune avec scheme (le name du schéma) et method

Une règle peut désigner un schéma de annotation_schemes, auquel on répond une fois par item, ou un schéma sur une page de phase comme poststudy, auquel on répond une seule fois. potato validate vérifie que le schéma existe et qu'il a le bon type pour la méthode.

mpl_random_row

Une ligne de la liste de prix du participant est tirée au hasard, chaque ligne étant équiprobable, et l'option qu'il a choisie à cette ligne est jouée. Un second tirage u dans [0, 1) choisit l'issue de la loterie, les issues étant placées bout à bout dans l'ordre où elles sont listées. Une liste comportant une ligne sans réponse n'est pas payée, et une liste avec plus d'une bascule est payée et marquée multiple switching.

bdm

CléSignification
endowmentSomme que détient le participant pour cette décision, 0 par défaut. Avec draw, elle doit être au moins égale à draw.max, pour qu'un achat ne coûte jamais plus que ce que le participant a reçu
draw{min, max} pour un prix tiré uniformément et arrondi au centime. Ajoutez step pour tirer l'une des valeurs min, min + step, ..., max
price_fieldÀ la place de draw, un champ de l'item contenant un prix fixé à l'avance
item_value, value_fieldCe que l'objet vaut en argent pour le participant qui l'achète, sous forme de nombre ou de champ de l'item. 0 par défaut, pour un objet physique remis séparément

Une enchère égale ou supérieure au prix achète au prix, pas au montant de l'enchère ; gain = dotation - prix + valeur de l'objet. Une enchère inférieure n'achète pas, et gain = dotation. Comme le prix ne dépend pas de l'enchère, enchérir autre chose que la valeur de l'objet n'aide jamais le participant et lui coûte parfois, et c'est cette propriété qui rend la procédure incitative.

bsr

CléSignification
prizeLe montant versé en cas de gain
outcome_fieldLe champ de l'item qui contient ce qui s'est produit : 1 ou 0 (ou true/false, yes/no) pour une réponse ponctuelle, et un libellé d'intervalle ou un indice d'intervalle à partir de 0 pour une réponse par intervalles
outcomeÀ la place de outcome_field, une valeur unique pour une question posée une seule fois, comme une question sur une page de phase

Le prix est versé quand la perte quadratique de la réponse est inférieure à un seuil tiré uniformément dans [0, K]. Pour une réponse ponctuelle p sur un événement binaire, loss = (1{event} - p)² avec K = 1. Pour une réponse par intervalles, loss = Σ (1{bin happened} - p_bin)² avec K = 2. Un participant qui veut maximiser ses chances d'obtenir le prix a intérêt à indiquer ce qu'il croit, quelle que soit son attitude face au risque, car les seuls gains possibles sont le prix et rien.

L'issue réelle doit être fournie avant le calcul des paiements, soit dans le fichier de données sous outcome_field, soit renseignée une fois les événements survenus. Potato retire outcome_field et price_field des données d'item envoyées à la page ; tenez-les donc aussi à l'écart du texte de l'item. Un participant dont la décision tirée n'a pas encore d'issue est listé comme en attente et laissé hors du fichier Prolific jusqu'à ce que l'issue soit renseignée.

Vérifier les tirages

bonuses.csv compte une ligne par décision rémunérée, avec le participant, quelle règle a payé quelle réponse, la graine, chaque nombre aléatoire utilisé (en JSON dans draws), le gain, s'il compte dans le bonus, le total du participant après max_bonus, et une note qui indique pourquoi une décision n'a pas été payée. Pour vérifier un paiement à la main, lisez les tirages et recalculez le gain à partir des règles ci-dessus. Reproduire les tirages eux-mêmes nécessite les mêmes réponses, les mêmes noms de règles et le même incentives.seed.

Payer par Prolific

Les participants ont besoin d'un identifiant Prolific ; connectez-les donc depuis l'URL de l'étude avec login: {type: url_direct, url_argument: PROLIFIC_PID} ou avec crowdsourcing: {provider: prolific}.

Depuis l'export :

bash
python -m potato.export --config config.yaml --format incentives --output ./bonuses/

Téléversez bonuses_prolific.csv dans la boîte de dialogue de bonus groupés de Prolific. Chaque ligne est <participant_id>,<amount>, avec deux décimales et sans en-tête. L'export laisse de côté, avec un avertissement pour chacun, les participants à qui rien n'est dû, ceux qui n'ont pas d'identifiant Prolific, ceux qui n'ont pas terminé, et ceux dont l'option choisie paie plus tard.

Depuis l'API d'administration, avec un jeton Prolific configuré :

bash
# Dry run: nothing is sent to Prolific
curl -H "X-API-Key: $KEY" http://localhost:8000/admin/api/crowd/incentive_bonuses
 
# Pay: echo the dry run's prolific_total
curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
     -d '{"pay": true, "expected_total": "12.40", "study_id": "<study id>"}' \
     http://localhost:8000/admin/api/crowd/incentive_bonuses

Le paiement nécessite "pay": true et un expected_total égal au prolific_total de la simulation. Si une réponse a changé depuis la simulation, les totaux diffèrent et la requête est refusée avec le statut 409.

Chaque lot payé est enregistré dans <task_dir>/incentive_payments.json. Un lot qui inclut un participant déjà payé par un lot antérieur est refusé avec le statut 409, ce qui empêche un double clic ou une deuxième exécution de paiement de payer deux fois les mêmes personnes. Pour ne payer que les participants qu'aucun lot enregistré n'a payés, ajoutez "skip_paid": true et fixez expected_total au unpaid_total de la simulation. Pour payer de nouveau délibérément un lot enregistré, ajoutez "repeat": true.

Exemple

bash
python potato/flask_server.py start examples/advanced/incentivized-elicitation/config.yaml -p 8000

L'exemple pose quatre questions de croyance, une par item, payées par bsr à partir de l'outcome de chaque item, puis une page post-étude avec une liste de prix de Holt & Laury et une enchère BDM pour un bon d'une valeur de 3,00 $.

Pour aller plus loin

Pour les détails d'implémentation, consultez la documentation source.