11 votes

Comment calculer les probabilités conditionnelles dans AnyDice ?

En écrivant l'addendum à cette réponse qui prend en compte la valeur relative des compétences par rapport aux caractéristiques dans le "système 3d20" de l'UE. Neuroshima Je me suis retrouvé à vouloir une réponse à une question d'une simplicité déconcertante : combien de points de compétence sont nécessaires pour réussir si le jet le plus bas est un succès naturel, ou s'il ne l'est pas ? En d'autres termes, je voulais essentiellement tracer les distributions de :

  • le rouleau du milieu sur 3d20, étant donné que le rouleau le plus bas est inférieur à un seuil donné x ; et
  • la somme des rouleaux inférieurs et moyens, étant donné que le rouleau le plus bas est au moins x .

En statistiques, ce serait juste un standard distribution de probabilité conditionnelle par exemple $$p_x(y) = P(Y = y \mid X < x),$$ $$q_x(z) = P(X + Y = z \mid X \ge x),$$ donde \$X\$ y \$Y\$ sont des variables aléatoires (interdépendantes) représentant respectivement le jet le plus bas et le jet moyen de 3d20. Vous pouvez facilement calculer ceci en prenant la distribution conjointe de \$(X,Y)\$. en laissant tomber les cas où la condition (par exemple \$X < x\$ ) échoue, en remettant à l'échelle les probabilités restantes de manière à ce que leur somme soit égale à 1, puis en effectuant éventuellement la somme sur la variable de conditionnement. \$X\$ pour obtenir la distribution marginale de \$Y\$ (ou \$X + Y\$ ).

Malheureusement, il semble qu'il n'y ait pas de moyen intégré simple de le faire dans AnyDice. En fait, il ne semble même pas y avoir de moyen de répondre à des questions de probabilité conditionnelle plus simples comme, par exemple, "quelle est la somme moyenne de 3d6 si la somme obtenue est paire, et si elle est impaire ?".

D'où cette question : Existe-t-il un moyen de calculer une distribution de probabilité conditionnelle dans AnyDice, et si oui, comment ?


Avertissement : Je réalise que cette question peut être à la limite du hors-sujet pour ce site, puisqu'il s'agit plutôt d'une question de programmation ou de mathématiques. Cela dit, elle s'est posée dans un contexte lié au RPG - plus précisément, en écrivant une réponse ici sur RPG.SE - et je soupçonne que la ou les réponses peuvent être utiles à d'autres personnes utilisant AnyDice pour répondre à des questions similaires sur d'autres systèmes. Je laisserai la communauté décider si cette Q&R doit rester ici ou non.

Par ailleurs, j'ai finalement réussi à trouver une solution (un peu bancale mais viable) à mon problème par moi-même, et j'ai donc publié une auto-réponse ci-dessous. Cela dit, d'autres réponses sont plus que bienvenues. S'il existe une meilleure façon de procéder, j'aimerais beaucoup la connaître.

9voto

Carcer Points 60981

Utilisez le résultat "filière vide" pour ignorer les cas qui ne répondent pas aux critères.

Si nous voulons ignorer complètement un certain sous-ensemble de résultats, nous pouvons le faire en utilisant une fonction qui renvoie le "dé vide", d{} pour les cas qui ne répondent pas à nos conditions souhaitées.

Le dé vide d{} semble être un dé spécial qui n'a aucun résultat possible et aucune probabilité associée. Par conséquent, si nous définissons une fonction qui renvoie ce dé vide pour certains cas d'entrée, elle supprime effectivement ces cas de l'ensemble des résultats possibles, et la distribution des résultats qui revient de la fonction est comme si les cas non désirés n'avaient jamais été invoqués.

Voici une fonction simple qui limite simplement l'entrée reçue à un ensemble de valeurs autorisées et rejette les cas qui ne satisfont pas à cette condition :

function: if X:n in RESTRICT:s {
  if X = RESTRICT { result: X }
  result: d{}
}

Étant donné une entrée X si X peut être trouvé dans la séquence des valeurs autorisées RESTRICT tout va bien et nous revenons X ; sinon, nous retournons d{} en attribuant une probabilité nulle à ce résultat particulier. Nous pouvons utiliser cette fonction pour restreindre un jet de 3d6 aux seules valeurs paires ou impaires :

output [if 3d6 in {3,5,7,9,11,13,15,17}] named "3d6 if odd"
output [if 3d6 in {4,6,8,10,12,14,16,18}] named "3d6 if even"

Et on obtient un résultat qui ressemble à ça :

Anydice graph of 3d6 restricted to odd or even

Cela s'étend évidemment à des cas plus intéressants, tels que les règles de Neuroshima données dans la question. Voici un programme qui montre des exemples de ces distributions :

function: INDEX:s at DICE:s if lowest less than MIN:n {
  if (#DICE@DICE >= MIN) { result: d{} }
  result: INDEX@DICE
}

function: INDEX:s at DICE:s if lowest at least MIN:n {
  if (#DICE@DICE < MIN) { result: d{} }
  result: INDEX@DICE
}

MIN: 10

output [2 at 3d20 if lowest less than MIN] named "Middle die of 3d20 if lowest die less than [MIN]"
output [{2,3} at 3d20 if lowest at least MIN] named "Middle and lowest die of 3d20 if lowest die at least [MIN]"

Ces fonctions éliminent d'abord les cas qui ne remplissent pas la condition spécifiée, puis nous donnent les valeurs qui nous intéressent à partir des séquences de dés restantes.

Bien sûr, vous pouvez aussi aborder ce problème dans l'autre sens et définir des fonctions qui font correspondre les résultats non désirés à une valeur fictive (comme -1), puis les faire passer par une fonction de filtrage à la fin qui élimine tous les résultats avec la valeur fictive, bien que faire le filtrage le plus tôt possible soit, je pense, plus efficace dans Anydice et vous permettra probablement de vous en sortir avec des programmes plus complexes/de plus grandes réserves de dés.

Contexte

Je suis tombé sur cette astuce du dé vide en travaillant sur cette réponse à une autre question. En fait, j'ai écrit une fonction simple qui relance récursivement 4d6-droplow jusqu'à ce qu'elle obtienne un 8 ou mieux, mais j'ai réalisé en inspectant que la distribution des résultats qu'elle renvoie ne change pas, quelle que soit la profondeur maximale de la fonction.

Dans Anydice, comme le dit la documentation, dépasser la profondeur maximale de la fonction entraîne simplement le renvoi du dé vide, et j'ai compris à partir de là que cela signifiait que le dé vide était essentiellement un résultat de probabilité nulle qui n'affecte pas la distribution finale des résultats, et que nous pouvons le renvoyer exprès (plutôt qu'accidentellement en dépassant la profondeur de la fonction) si nous voulons ignorer une certaine catégorie d'entrées !

4voto

trjh Points 11

Il s'avère qu'il y a es un moyen de faire cela dans AnyDice, du moins en quelque sorte. C'est un peu compliqué, mais ça marche.

Le secret est le rerolling.

Plus précisément, une façon générale d'échantillonner à partir d'une distribution de probabilité conditionnelle est appelée échantillonnage de rejet . En gros, vous échantillonnez une valeur à partir de la distribution de probabilité originale (non conditionnée), et si elle ne satisfait pas à la condition, vous la rejetez et continuez à rééchantillonner jusqu'à ce que vous obteniez un résultat qui satisfait à la condition.

Et nous pouvons simuler ce processus dans AnyDice. Par exemple, voici une fonction AnyDice simple qui prend un dé et le relance si sa valeur n'est pas dans une plage donnée :

function: restrict ROLL:n to RANGE:s else REROLL:d {
  if ROLL = RANGE { result: ROLL }
  else { result: REROLL }
}
function: restrict ROLL:d to RANGE:s once {
  result: [restrict ROLL to RANGE else ROLL]
}

Cependant, cela ne modélise que un reroll, mais c'est bien. On peut juste l'itérer :

function: restrict ROLL:n to RANGE:s else REROLL:d {
  if ROLL = RANGE { result: ROLL }
  else { result: REROLL }
}
function: restrict ROLL:d to RANGE:s {
  loop I over {1..20} {
    ROLL: [restrict ROLL to RANGE else ROLL]
  }
  result: ROLL
}

Maintenant, vous pourriez regarder ce code et penser que cela ne fait toujours que 20 relances, mais ce n'est pas le cas. Plutôt, il fait effectivement 2 20 soit environ un million de relances ! Cette efficacité surprenante s'explique par le fait que nous mettons à jour la base de données ROLL à chaque itération. Ainsi, lors de la deuxième itération, nous échantillonnons à partir de la distribution déjà relancée, et si l'échantillon est rejeté, nous rééchantillonnons à partir de la distribution déjà relancée. même distribution déjà relancée. Donc, en gros, chaque itération double le nombre effectif de relances.

Un million de rebondissements n'est pas tout à fait infiniment nombreux, mais c'est assez proche pour la plupart des cas. Et si ce n'est vraiment pas suffisant (ce que l'on peut facilement repérer dans le résultat, par le fait que les valeurs supposées rejetées y apparaissent avec une probabilité non nulle), on peut toujours augmenter le nombre d'itérations de 20 à, disons, 30 pour une valeur de milliards d'euros des relances effectives.

De toute façon, voici un exemple d'utilisation de cette fonction :

output [restrict 3d6 to {3,5,7,9,11,13,15,17}] named "3d6 if odd"
output [restrict 3d6 to {4,6,8,10,12,14,16,18}] named "3d6 if even"

et un exemple de la sortie :

Screenshot

(De manière assez surprenante, il s'avère que la moyenne est la même dans les deux cas).


Mais comment pouvons-nous utiliser cette fonction pour traiter des cas plus complexes, comme l'exemple original de "milieu de 3d20 si le plus bas est inférieur à \$x\$ ", où la variable dont on veut la distribution n'est pas la même que celle sur laquelle on veut la conditionner ?

Eh bien, une façon assez simple est d'écrire une fonction qui prend le jet d'entrée (ici, 3d20) comme une séquence et le fait correspondre à la sortie que nous voulons (c'est-à-dire le jet du milieu, dans ce cas) tout en également mettre en correspondance tous les cas rejetés avec un résultat fictif tel que -1. Ensuite, nous pouvons simplement utiliser la fonction ci-dessus pour rejeter le résultat fictif, et obtenir la distribution conditionnelle que nous voulons, Par exemple, comme ceci :

function: middle of ROLL:s if lowest in RANGE:s {
  if 3@ROLL = RANGE { result: 2@ROLL } \ assumes a three die pool! \
  else { result: -1 }
}

MAX: 10
DIST: [middle of 3d20 if lowest in {1..MAX}]

output DIST named "middle of 3d20 if lowest <= [MAX] (else -1)"
output [restrict DIST to {1..20}] named "middle of 3d20 if lowest <= [MAX] (conditional)"

Le script que j'ai écrit para la réponse originale qui a inspiré cette question-réponse est similaire, bien qu'elle utilise une astuce supplémentaire de réétiquetage des dés (décrite dans la réponse) pour calculer facilement le nombre de points de compétence nécessaires pour ramener les deux plus bas résultats sous le seuil.

AlleGamers.com

AlleGamers est une communauté de gamers qui cherche à élargir la connaissance des jeux vidéo.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X