Random distribution gaussienne avec minimum

Résolu/Fermé
pols12 Messages postés 1143 Date d'inscription lundi 22 mars 2010 Statut Membre Dernière intervention 31 juillet 2019 - 25 nov. 2015 à 00:29
pols12 Messages postés 1143 Date d'inscription lundi 22 mars 2010 Statut Membre Dernière intervention 31 juillet 2019 - 6 janv. 2016 à 00:13
Salut !

J'ai 100 éléments à répartir aléatoirement dans un tableau 2D de 25*25.

Pour 1000 éléments, si je prends
alea.nextGaussian()+(1000.0/(25*25)); //alea un objet Random
c'est plutôt correct, car la probabilité d'être en dessous de 0 est faible.

Mais pour 100 éléments, ça ne va plus du tout.

Comment modéliser une loi de probabilité en Java pour qu'elle ait une moyenne de 100/(25*25) et un minimum de 0 (ou au moins une proba très faible d'être négatif) ?
Théoriquement, il faudrait aussi un maximum de 100, mais avec une loi d'écart-type faible ça n'est pas nécessaire.

Merci !

PS : je suis débutant.

8 réponses

KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
25 nov. 2015 à 08:03
Bonjour,

Je ne comprends pas à quoi correspond ton calcul.

Si ta moyenne est 100/(25*25) = 0.16 et ton minimum à 0 ça veut dire que ton maximum sera à 0.32
Mais en quoi obtenir un nombre entre 0 et 0.32 te permet de remplir ton tableau de 25*25 cases ?
0
pols12 Messages postés 1143 Date d'inscription lundi 22 mars 2010 Statut Membre Dernière intervention 31 juillet 2019 119
25 nov. 2015 à 13:30
0.16*25*25 = 100
Si je voulais répartir équitablement ces 100 éléments dans les 25*25 cases, j'en aurais 0.16 par case. Mais je veux pas que ça soit équitable, je veux que ça soit aléatoire.

Il faut donc une distribution en cloche, parce que si je tire en équiprobabilité des nombres entre 0 et 100, mon tableau risque d'être rempli en quelques cases et toutes les suivantes seraient vide.

C'est plus clair ou pas ?
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
25 nov. 2015 à 22:35
Par défaut, nextGaussian va te renvoyer des nombres répartis plus ou moins comme ça :

Courbe obtenue pour 100 millions de tirages arrondis à deux décimales.

On peut donc considérer empiriquement que la probabilité d'avoir un nombre inférieur à -6 ou supérieur à +6 est quasiment nul, et que même si tu dépassais ces deux valeurs on pourrait ignorer le tirage et choisir aléatoirement un autre nombre.

Il ne reste plus qu'à transformer ces résultats obtenus entre -6 et +6 pour les faire coïncider à un intervalle [min, max] voulu tout en conservant la gaussienne.

private static final int EMPIRICAL_MIN = -6, EMPIRICAL_MAX = +6;

public static double nextGaussian(Random random, double min, double max) {
    double next;
    do {
        next = random.nextGaussian();
    } while (next < EMPIRICAL_MIN || next > EMPIRICAL_MAX); // peu probable
    
    return min+(max-min)*(next-EMPIRICAL_MIN)/(EMPIRICAL_MAX-EMPIRICAL_MIN);
}

Remarque : si tu considère que -6 et +6 est trop large il suffit de changer les deux constantes. Par exemple -4 et +4 pourraient très bien aller, ça augmente juste un peu les valeurs les plus basses (0,0001% pour exactement -4 ou +4) et la probabilité de faire une deuxième fois la boucle (0.006% de cas < -4 ou > +4)
0
pols12 Messages postés 1143 Date d'inscription lundi 22 mars 2010 Statut Membre Dernière intervention 31 juillet 2019 119
26 nov. 2015 à 11:48
Merci pour ton aide.
Cependant cette solution ne peut pas convenir parce que la moyenne est de max-min, donc 50, alors que je la veux de 0.16.
En fait, la gaussienne n'est pas adaptée parce que la loi que je veux est asymétrique, contrairement à la loi normale.
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
26 nov. 2015 à 20:05
"la moyenne est de max-min, donc 50, alors que je la veux de 0.16"
Je ne sais pas comment tu as fait pour obtenir 50 avec max-min, mais dans le cas général la moyenne est (min+max)/2. Alors je ne sais pas ce que tu veux faire avec ton 0.16, mais quoi que tu fasses, si tu mets 100 éléments dans 250 cases tu auras toujours 40% de cases remplies, peu importe la distribution que tu utilises.

Avec une distribution gaussienne tu règles surtout le problème "si je tire en équiprobabilité des nombres entre 0 et 100, mon tableau risque d'être rempli en quelques cases et toutes les suivantes seraient vide." même si là encore je me demande pourquoi tu veux tirer entre 0 et 100 alors que tes cases vont jusqu'à 250 !

Exemple :

Random random = new Random();
boolean[] tab = new boolean[250];
for (int i=0; i<100; i++) { // on remplit 100 cases
    int pos;
    do { // on cherche un nombre entre 0 et 250
        pos = (int) Math.floor(nextGaussian(random, 0, 250));
    }
    while (tab[pos]); // tant que la position du tableau est déjà remplie
    tab[pos] = true;
}

// affichage
for (int i=0; i<250; i++)
    System.out.println(i+"\t"+tab[i]);

Ce qui donne bien 100 cases remplies entre 0 sur les 250 que contient le tableau, avec une distribution gaussienne qui favorise bien davantage le milieu du tableau que les bords.
Exemple avec la valeur 1 pour les cases remplies et 0 pour celles restées vides.
0
pols12 Messages postés 1143 Date d'inscription lundi 22 mars 2010 Statut Membre Dernière intervention 31 juillet 2019 119
Modifié par pols12 le 26/11/2015 à 21:09
Je dois mal m'exprimer, tu n'as pas du tout compris ce que je veux.
Le tableau que je veux remplir est en deux dimensions, avec 25 indices pour chaque dimension. Ensuite, ce tableau ne contient pas des booléens, mais des entiers (ou des réels, ça change pas grand chose). Le total des valeurs de toutes les cases du tableau doit être égal à 100.

Voilà le code :
int[][] tableau = new int[25][25];
for(int i=0; i<25; i++)
  for(int j=0; j<25; j++){
    double alea = generer_aleatoire();
    tableau[i][j] = Math.round(alea);
  }


Je veux une fonction generer_aleatoire() qui envoie des nombres de manière aléatoire tels que si je fais la somme de toutes les cases du tableau, je vais trouver (environ) 100.

J'ai proposé deux possibilités dans mon message ci-dessous. Mais c'est empirique, je trouve pas comment calculer la valeur exacte du 0.58 que j'ai mis.
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
26 nov. 2015 à 22:35
Alors, je ne sais toujours pas à quoi correspondent les valeurs 0.16 ou 0.58 que tu cherches à obtenir, mais si je comprends bien ce que tu veux faire c'est ça :
width=25, height=25, objective=100, precision=0.001

Si on fait une coupe sur une ligne, une colonne, ou une diagonale, on obtiendras toujours une gaussienne (de plus en plus déformé quand on s'éloigne du centre).

Voici le code, en reprenant la méthode nextGaussian que je t'ai proposé hier :

// initialisation
Random random = new Random();
int width = 25, height = 25;
double objective = 100;
double precision = 0.001;
double[][] tab = new double[width][height];

// remplissage
for (int i = 0, n = (int) (objective / precision); i < n; i++) {
    int posX = (int) Math.floor(nextGaussian(random, 0, width - 1));
    int posY = (int) Math.floor(nextGaussian(random, 0, height - 1));
    tab[posX][posY] += precision;
}

// affichage
for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++)
        System.out.print(tab[x][y] + "\t");
    System.out.println();
}
0
pols12 Messages postés 1143 Date d'inscription lundi 22 mars 2010 Statut Membre Dernière intervention 31 juillet 2019 119
26 nov. 2015 à 12:02
Bon, du coup, j'ai trouvé deux possibilités qui vont plus ou moins bien :
1) Générer un double dans [0;1] et faire sa propre loi de proba à l'aide de (nombreuses) conditions.
2) Prendre une gaussienne avec une moyenne négative et considérer tout ce qui négatif comme nul.

int nb; // nb d'éléments par case du tableau
//méthode 1 :
double alea =Math.random();
if(alea>0.99999) nb=100;
else if(alea>0.9999) nb=99;
else if(alea>0.9995) nb=98;
...
else nb=0;
//Bref, c'est laborieux, et faut calculer pour avoir des probas qui conviennent,
// là, j'ai mis au pif.

//méthode 2 :
Random ra=new Random();
double alea = ra.nextGaussian()-0.58; // le 0.58 est empirique pour 100 éléments
alea = alea<0 ?0 : alea;
nb = Math.round(alea);

0
pols12 Messages postés 1143 Date d'inscription lundi 22 mars 2010 Statut Membre Dernière intervention 31 juillet 2019 119
26 nov. 2015 à 12:05
Je passe en résoluj, mais si quelqu'un a une meilleure solution, je suis preneur ! :)
0
pols12 Messages postés 1143 Date d'inscription lundi 22 mars 2010 Statut Membre Dernière intervention 31 juillet 2019 119
27 nov. 2015 à 03:20
Non, pas du tout. Je t'ai donné tout mon code, je vois pas comment je peux faire mieux que le répéter, regroupé.

Voilà le code qui résout mon problème, parce que c'est exactement ce que je veux, même s'il y a le 0.58 qui est empirique et non théorique.
public static int generer_aleatoire(){
	Random ra=new Random();
	double alea = ra.nextGaussian()-0.58; // le 0.58 est empirique pour 100 éléments
	alea = alea<0 ?0 : alea;
	return (int) Math.round(alea);
}
	
public static void main(String[] args) {
	int[][] tableau = new int[25][25];
	for(int i=0; i<25; i++)
	  for(int j=0; j<25; j++){
	    double alea = generer_aleatoire();
	    tableau[i][j] = (int) Math.round(alea);
	  }

	// affichage
	for (int y = 0; y < 25; y++) {
	    for (int x = 0; x < 25; x++)
	        System.out.print(tableau[x][y] + "\t");
	    System.out.println();
	}


Si on exécute ce code plusieurs fois, les éléments change de place, certaines cases se remplissent et d'autres non, il est même possible d'avoir des cases avec un 3 voire un 4, etc.

Bonne continuation ;)
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
27 nov. 2015 à 21:12
Ton problème est compliqué à comprendre parce que je ne vois pas du tout l'intérêt que tu as à faire un tel code... Mais cela dit j'ai encore plein de propositions :-)

Donc si je comprends mieux (tu me diras), le 0.58 tu peux l'obtenir rétroactivement, c'est à dire que tu peux remplir aléatoirement toutes tes cases, calculer la somme que ça fait et ce qui te manque (ou ce que tu as en trop) pour atteindre ton 100 attendu, et repasser sur chaque case pour ajouter la différence.

Random random = new Random();
double[][] tableau = new double[25][25];
double objective = 100;

double total = 0;
for (int i = 0; i < 25; i++) {
    for (int j = 0; j < 25; j++) {
        double val = random.nextGaussian();
        tableau[i][j] = val;
        total += val;
    }
}

double delta = (objective - total) / (25 * 25);
System.out.println("delta=" + delta);

for (int i = 0; i < 25; i++) {
    for (int j = 0; j < 25; j++) {
        tableau[i][j] += delta;
        System.out.print(tableau[i][j] + "\t");
    }
    System.out.println();
}

Remarque : la valeur delta suit également une gaussienne et je tombe bien sur 0.16 comme valeur moyenne, mais c'est trop peu probable pour utiliser systématiquement cette valeur, il vaut mieux un delta calculé dynamiquement.

Courbe obtenue pour 10 millions de calculs de delta arrondis à trois décimales.pour un objectif de 100 sur une grille 25x25.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
pols12 Messages postés 1143 Date d'inscription lundi 22 mars 2010 Statut Membre Dernière intervention 31 juillet 2019 119
27 nov. 2015 à 22:43
Bon, ba on continue si tu n'es pas à bout.. :P
J'aime bien ton idée, mais il y a un souci : je ne veux pas de négatifs, 0 au minimum. On peut faire la correction dans le premier remplissage, mais du coup delta sera forcément négatif et donc on aura des négatifs à la fin.

Je vais te donner un exemple du but de ce code. J'espère que tu ne vas pas me dire que c'est totalement stupide... ^^
Imaginons que le tableau soit une grille à gratter.
Dans la grille, il y a 100€ répartis aléatoirement.
Chaque case peut contenir jusqu'à 100€, mais c'est très peu probable. En pratique, il est très rare de trouver plus de 3€ derrière une case.
Et bien sûr, il n'y a aucune case qui nous fait perdre de l'argent si on la gratte.


0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
28 nov. 2015 à 16:32
Si tu ne veux jamais de valeurs négatives alors il faut reprendre la méthode que j'ai mis plus haut qui te permet d'avoir un nombre aléatoire entre les valeurs min et max (par exemple 0 et 1), sauf que pour normaliser ta somme il vaudrait mieux multiplier plutôt qu'ajouter.

Random random = new Random();
double[][] tableau = new double[25][25];
double objective = 100;

double total = 0;
for (int i = 0; i < 25; i++) {
    for (int j = 0; j < 25; j++) {
        double val = nextGaussian(random,0,1);
        tableau[i][j] = val;
        total += val;
    }
}

double delta = objective/total;
System.out.println("delta=" + delta);

for (int i = 0; i < 25; i++) {
    for (int j = 0; j < 25; j++) {
        tableau[i][j] *= delta;
        System.out.println(tableau[i][j]);
    }
}


"Dans la grille, il y a 100€ répartis aléatoirement. Chaque case peut contenir jusqu'à 100€, mais c'est très peu probable. En pratique, il est très rare de trouver plus de 3€ derrière une case."

Il est impossible d'avoir une case avec 100€. Il faudrait pour ça avoir un tirage entre 0 et 1 qui soit égal à 1 (ce qui est quasiment impossible) et qu'en plus les 624 autres tirages soient exactement à 0... Même 3€ est improbable.

J'ai fait les tests sur 100 000 grilles (chacune totalise 100€ réparties sur 625 cases), et si on arrondis les tirages aléatoires aux centimes en moyenne on obtient une répartition comme ceci :

Remarque : la forme de ces résultats dépend des paramètres
EMPIRICAL_MIN = -6
et
EMPIRICAL_MAX = +6
que j'ai utilisé dans ma méthode nextGaussian, mais on pourrait jouer sur ces valeurs pour que la probabilité d'avoir 0 ou 1 quand on tire un nombre entre 0 et 1 soit beaucoup plus probable, par exemple avec 0 et +3 on ne se servirait que d'une partie de la courbe, la probabilité d'avoir 0 serait maximale et celle d'avoir 1 serait faible mais non nulle


Remarque : même si la probabilité de tirer 1 entre 0 et 1 serait plus importante, ça ne veut toujours pas dire que cela correspond à la probabilité d'avoir 100€. Si tu veux que l'on puisse avoir 100€ alors il faut augmenter le total de la grille.
0
pols12 Messages postés 1143 Date d'inscription lundi 22 mars 2010 Statut Membre Dernière intervention 31 juillet 2019 119
29 nov. 2015 à 18:49
OK, parfait, merci beaucoup !!! :)
Cette adaptation de la gaussienne devrait générer les valeurs que j'attends. :)

Sinon, je me suis dit que pour créer ma propre loi de probabilité continue, plutôt que de me baser sur la loi normale, je pouvais simplement écrire la fonction voulue. Par exemple en entrant des valeurs dans un tableur et en demandant à ce qu'il me fasse l'approximation de la fonction correspondante. Après, il ne resterait « plus » qu'à adapter la fonction pour que son intégrale sur l'intervalle de définition fasse 1.
Bref, des math's et re- des math's... :) Donc j'ai pas du tout envie de m'y atteler maintenant, la solution de la Gaussienne me suffit amplement.

Ouf, MERCI KX, on en est arrivé à bout ! :P
0
pols12 Messages postés 1143 Date d'inscription lundi 22 mars 2010 Statut Membre Dernière intervention 31 juillet 2019 119
5 janv. 2016 à 00:03
KX, je me permets de te resolliciter à nouveau.

En effet, un problème que je n'avais pas vu a été oublié. Dans ma grille, j'ai besoin d'entiers et non de réels.

Comment puis-je faire l'arrondi en conservant un total égal à objective ?

En espérant que tu pourras m'aider. :)

Bonne année !
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
5 janv. 2016 à 10:12
Si je reprends un peu la discussion, le but du programme était de repartir 100 € sur 625 cases soit environ 0.16€ par case, en pratique ça dépassait rarement 0.25€.
Alors comment veux tu mettre des entiers là dessus ?

Remarque : sans information supplémentaire de ta part, je considérerai que tu utilises ces deux codes là (ce sont les dernières versions dans la discussion) :
https://forums.commentcamarche.net/forum/affich-32823135-random-distribution-gaussienne-avec-minimum#3
https://forums.commentcamarche.net/forum/affich-32823135-random-distribution-gaussienne-avec-minimum#13
0
pols12 Messages postés 1143 Date d'inscription lundi 22 mars 2010 Statut Membre Dernière intervention 31 juillet 2019 119
6 janv. 2016 à 00:13
Oui, j'étais bien sur ce code. Cependant, pour pouvoir avoir des entiers, j'ai repensé tout le code.

J'ai donc un nouveau code, qui se passe de nextGaussian. En fait c'était bien simple... :)

// On tire aléatoirement les coordonnées des emplacements
LinkedList<Integer> coord = new LinkedList<>();
for(int i=0; i<100; i++){
	coord.add((int) (Math.random()*25)); //tirage aléatoire de la ligne
	coord.add((int) (Math.random()*25)); //tirage aléatoire de la colonne
}

//On parcourt les listes de coordonnées et on fait les sommes pour chaque case
int[][] tableau = new int[25][25]; //la valeur par défaut est 0
for(int i=0; i< 200; i+=2)
	tableau[coord.get(i)][coord.get(i+1)]+=1;


C'était tristement simple... Je sais pas pourquoi je me suis empêtré dans une histoire de gaussienne. Même si théoriquement, la proba doit suivre une loi normale quand même je pense. Mais on s'en fout tout bonnement. :)


Cette fois, je pense bien que je n'aurai plus à déterrer le sujet... :P
0