From 0d1ebd1020d7c1f01f42c892dc1f91b7062ce78e Mon Sep 17 00:00:00 2001 From: medina5 Date: Fri, 17 Oct 2025 19:45:26 +0200 Subject: [PATCH] =?UTF-8?q?G=C3=A9n=C3=A9rations=20des=20individus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 2 +- individus.html | 12 ++++ package-lock.json | 6 ++ package.json | 1 + src/individual.ts | 160 ++++++++++++++++++++++++++++++++++++++++++++++ src/individus.ts | 50 +++++++++++++++ src/main.ts | 38 ++++------- 7 files changed, 243 insertions(+), 26 deletions(-) create mode 100644 individus.html create mode 100644 src/individual.ts create mode 100644 src/individus.ts diff --git a/index.html b/index.html index 7a69a90..aea8fbe 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Graph MesAmis + Graphe Mes Amis
diff --git a/individus.html b/individus.html new file mode 100644 index 0000000..ba5d664 --- /dev/null +++ b/individus.html @@ -0,0 +1,12 @@ + + + + + Distribution des âges + + +

Distribution des âges selon le genre

+ + + + diff --git a/package-lock.json b/package-lock.json index 3b1650b..368d230 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "chart.js": "^4.5.1", "graphology": "^0.26.0", "graphology-layout-forceatlas2": "^0.10.1", + "jstat": "^1.9.6", "sigma": "^3.0.2" }, "devDependencies": { @@ -461,6 +462,11 @@ "graphology-types": ">=0.23.0" } }, + "node_modules/jstat": { + "version": "1.9.6", + "resolved": "https://registry.npmjs.org/jstat/-/jstat-1.9.6.tgz", + "integrity": "sha512-rPBkJbK2TnA8pzs93QcDDPlKcrtZWuuCo2dVR0TFLOJSxhqfWOVCSp8aV3/oSbn+4uY4yw1URtLpHQedtmXfug==" + }, "node_modules/lightningcss": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", diff --git a/package.json b/package.json index 108d5ff..7aa6de9 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "chart.js": "^4.5.1", "graphology": "^0.26.0", "graphology-layout-forceatlas2": "^0.10.1", + "jstat": "^1.9.6", "sigma": "^3.0.2" } } diff --git a/src/individual.ts b/src/individual.ts new file mode 100644 index 0000000..e222dbd --- /dev/null +++ b/src/individual.ts @@ -0,0 +1,160 @@ +import { jStat } from "jstat"; + +function randomChoice(ens:Array) { + return ens[Math.floor(Math.random() * ens.length)]; +} + +function choiceAge() { + const r = Math.random(); + + if (r < 0.35) { + const age = jStat.normal.sample(25, 4); + return Math.min(30, Math.max(18, Math.round(age))); + } + + if (r < 0.75) { + const age = jStat.normal.sample(40, 6); + return Math.min(60, Math.max(31, Math.round(age))); + } + + const age = jStat.normal.sample(66, 5); + return Math.min(78, Math.max(61, Math.round(age))); +} +function randomFirstName(sex:string, age:number) { + const pool = [ + [ + [ "Lucas","Léo","Nathan","Gabriel","Maël","Hugo","Ethan","Noah","Arthur","Gabin", + "Tom","Medhi","Evan","Sacha","Rayan","Mathis","Enzo","Théo","Isaac","Liam" + ], + [ "Julien","Maxime","Alexandre","Pierre","Antoine","Romain","Jérémy","Kevin","Nicolas","Benjamin", + "Florian","Vincent","Michaël","Samuel","Baptiste","Yann","Cédric","Quentin","Thomas","Adrien" + ], + [ "Jean","Michel","Christian","Philippe","Daniel","Patrick","Bernard","Alain","Jacques","Guy", + "Louis","André","Roger","Maurice","Robert","Henri","Marc","Gérard","Serge","Raymond" + ] + ], + [ + [ "Emma","Léa","Manon","Chloé","Jade","Lina","Lola","Anna","Zoé","Mila", + "Camille","Nina","Darya","Léna","Louise","Inès","Julia","Samia","Clara","Maya" + ], + [ "Marine","Laura","Céline","Charlotte","Elodie","Marion","Sophie","Julie","Amélie","Amandine", + "Valérie","Aurélie","Isabelle","Caroline","Sonia","Laurence","Cécile","Stéphanie","Sandrine","Emilie" + ], + [ "Marie","Monique","Françoise","Denise","Nicole","Pierrette","Madeleine","Colette","Agnès","Simone", + "Geneviève","Jacqueline","Jeanne","Yvonne","Raymonde","Thérèse","Lucienne","Gisèle","Marguerite","Suzanne" + ] + ] + ]; + + let a = 2; + + if (age <= 30) + a = 0; + else if (age <= 50) + a = 1; + + return randomChoice(pool[sex == 'M' ? 0 : 1][a]); +} + +function randomSex(age:number) { + let c = 0.55; + + if (age < 30) + c = 0.51; + else if (age < 55) + c = 0.45; + + return Math.random() < c ? "F" : "M"; +} + +function randomSport(age:number) { + // Moyenne de sport qui diminue avec l’âge + const meanSport = Math.min(Math.max(0.65 - 0.006 * (age - 18), 0.05), 0.9); + + // Paramètres de la distribution bêta + const a = Math.max(meanSport * 6, 0.5); + const b = Math.max((1 - meanSport) * 6, 0.5); + + // Tirage aléatoire selon Beta(a, b) + const sport = jStat.beta.sample(a, b); + + return sport; +} + +function randomEducation(age:number) { + // --- Niveau d'études influencé par âge --- + const base = [0.25, 0.35, 0.25, 0.15]; + const ageEffect = (30 - age) / 60; + const adjust = [-0.05, 0.00, 0.03, 0.02].map(v => v * (1 + ageEffect)); + + // Calcul des probabilités corrigées + let probs = base.map((b, i) => Math.min(0.9, Math.max(0.01, b + adjust[i]))); + + // Normalisation + const total = jStat.sum(probs); + probs = probs.map(p => p / total); + + // Sélection pondérée : équivalent à np.random.choice + const values = [0, 1, 2, 3]; + const cumProbs = jStat.cumsum(probs); + const r = Math.random(); + const idx = cumProbs.findIndex(p => r <= p); + return values[idx >= 0 ? idx : values.length - 1]; +} + +function randomWealth(education:number) { + // Ajoute un bruit gaussien (moyenne 0, écart-type 0.9) + const wealthCont = education + jStat.normal.sample(0, 0.9); + + // Classification en 4 niveaux selon la valeur continue + let wealth; + if (wealthCont < 0.5) { + wealth = 0; + } else if (wealthCont < 1.5) { + wealth = 1; + } else if (wealthCont < 2.5) { + wealth = 2; + } else { + wealth = 3; + } + + return wealth; +} + +function randomLecture(education:number, age:number) { + let meanRead = 0.15 + 0.18 * education + 0.002 * (age - 18); + meanRead = Math.min(Math.max(meanRead, 0.02), 0.98); // équivalent de np.clip + + // Paramètres de la distribution bêta + const a = Math.max(meanRead * 7, 0.5); + const b = Math.max((1 - meanRead) * 7, 0.5); + + // Tirage aléatoire + const reading = jStat.beta.sample(a, b); + + return reading; +} + +function randomMusique() { + return jStat.beta.sample(2, 2); +} + +export function generate(n: number) { + + const individus = []; + for (let i = 0; i < n; i++) { + const age = choiceAge(); + const sexe = randomSex(age); + const prenom = randomFirstName(sexe, age); + + const etudes = randomEducation(age); + const richesse = randomWealth(etudes); + const sport = randomSport(age); + const lecture = randomLecture(etudes, age); + const musique = randomMusique(); + + individus.push({ id: i, prenom, sexe, age, etudes, richesse, sport, lecture, musique }); + } + + return individus; +} diff --git a/src/individus.ts b/src/individus.ts new file mode 100644 index 0000000..bb5fb32 --- /dev/null +++ b/src/individus.ts @@ -0,0 +1,50 @@ +// src/main.ts +import { generate } from "./individual"; +import Chart from "chart.js/auto"; + +const individus = generate(1000); +console.log(individus); + +// Séparer les âges par sexe +const agesH = individus.filter(i => i.sexe === "M").map(i => i.age); +const agesF = individus.filter(i => i.sexe === "F").map(i => i.age); + +// Construire des classes d'âge +const ageClasses = Array.from({ length: 13 }, (_, i) => i * 5 + 18); // [10,20,30,...,70] + +function histogramData(ages: number[]) { + return ageClasses.map((a, idx) => + ages.filter(age => age >= a && age < (ageClasses[idx + 1] ?? 80)).length + ); +} + +const ctx = document.getElementById("ageChart") as HTMLCanvasElement; + +new Chart(ctx, { + type: "bar", + data: { + labels: ageClasses.map(a => `${a}-${a+9}`), + datasets: [ + { + label: "Hommes", + data: histogramData(agesH), + backgroundColor: "rgba(54, 162, 235, 0.6)", + }, + { + label: "Femmes", + data: histogramData(agesF), + backgroundColor: "rgba(255, 99, 132, 0.6)", + } + ], + }, + options: { + responsive: true, + plugins: { + title: { display: true, text: "Distribution des âges par sexe" }, + }, + scales: { + x: { stacked: false }, + y: { beginAtZero: true, max: 100, title: { display: true, text: "Nombre d'individus" } }, + }, + }, +}); diff --git a/src/main.ts b/src/main.ts index 6ca176a..e118df3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ import './style.css' -import Graph from "https://esm.sh/graphology"; -import Sigma from "https://esm.sh/sigma"; -import forceAtlas2 from "graphology-layout-forceatlas2"; +import Graph from "graphology"; +import Sigma from "sigma"; +import forceAtlas2 from "graphology-layout-forceatlas2"; // --- Génération de données de base --- const graph = new Graph(); @@ -24,14 +24,8 @@ for (let i = 0; i < N; i++) { }); } -// --- Ajout de quelques liens initiaux --- -for (let i = 0; i < N * 1.5; i++) { - const a = `n${Math.floor(Math.random() * N)}`; - const b = `n${Math.floor(Math.random() * N)}`; - if (a !== b && !graph.hasEdge(a, b)) { - graph.addEdge(a, b, { color: "#da1515ff", size: 1 }); - } -} +let total = N * 1.5 + // --- Calcul du layout ForceAtlas2 --- /* @@ -48,11 +42,13 @@ const container = document.getElementById("app"); const renderer = new Sigma(graph, container, { renderLabels: true }); // --- Fonctions dynamiques --- -function addLink(source, target) { - if (!graph.hasEdge(source, target)) { - graph.addEdge(source, target, { color: "#666" }); - renderer.refresh(); +function addLink() { + const a = `n${Math.floor(Math.random() * N)}`; + const b = `n${Math.floor(Math.random() * N)}`; + if (a !== b && !graph.hasEdge(a, b)) { + graph.addEdge(a, b, { color: "#da1515ff", size: 1 }); } + if (total-- > 0) setTimeout(() => addLink(), 1000); else running = false; } function removeLink(source, target) { @@ -63,11 +59,8 @@ function removeLink(source, target) { } // --- Exemple d’évolution dynamique --- -//setTimeout(() => addLink("n0", "n5"), 3000); -//setTimeout(() => removeLink("n1", "n2"), 6000); - -//console.log("Graph loaded:", graph.order, "nodes,", graph.size, "edges"); - +setTimeout(() => addLink(), 1000); +setTimeout(() => removeLink("n1", "n2"), 8000); // --- Animation du layout --- // On crée une "simulation" ForceAtlas2 en incrémentant les positions à chaque frame. @@ -86,11 +79,6 @@ function stepLayout() { // Lancement stepLayout(); -// Arrêt automatique après 5 secondes -setTimeout(() => { - running = false; - console.log("Layout stabilisé"); -}, 5000); /* const layout = new ForceAtlas2Layout(graph, {