Générations des individus

This commit is contained in:
2025-10-17 19:45:26 +02:00
parent 02abde44a2
commit 0d1ebd1020
7 changed files with 243 additions and 26 deletions

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Graph MesAmis</title> <title>Graphe Mes Amis</title>
</head> </head>
<body> <body>
<div id="app" style="width: 100vw; height: 100vh; background: white"></div> <div id="app" style="width: 100vw; height: 100vh; background: white"></div>

12
individus.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Distribution des âges</title>
</head>
<body style="max-width: 700px; margin: 2rem auto; font-family: sans-serif;">
<h3>Distribution des âges selon le genre</h3>
<canvas id="ageChart" width="600" height="400"></canvas>
<script type="module" src="/src/individus.ts"></script>
</body>
</html>

6
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"chart.js": "^4.5.1", "chart.js": "^4.5.1",
"graphology": "^0.26.0", "graphology": "^0.26.0",
"graphology-layout-forceatlas2": "^0.10.1", "graphology-layout-forceatlas2": "^0.10.1",
"jstat": "^1.9.6",
"sigma": "^3.0.2" "sigma": "^3.0.2"
}, },
"devDependencies": { "devDependencies": {
@@ -461,6 +462,11 @@
"graphology-types": ">=0.23.0" "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": { "node_modules/lightningcss": {
"version": "1.30.2", "version": "1.30.2",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",

View File

@@ -19,6 +19,7 @@
"chart.js": "^4.5.1", "chart.js": "^4.5.1",
"graphology": "^0.26.0", "graphology": "^0.26.0",
"graphology-layout-forceatlas2": "^0.10.1", "graphology-layout-forceatlas2": "^0.10.1",
"jstat": "^1.9.6",
"sigma": "^3.0.2" "sigma": "^3.0.2"
} }
} }

160
src/individual.ts Normal file
View File

@@ -0,0 +1,160 @@
import { jStat } from "jstat";
function randomChoice(ens:Array<any>) {
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;
}

50
src/individus.ts Normal file
View File

@@ -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" } },
},
},
});

View File

@@ -1,6 +1,6 @@
import './style.css' import './style.css'
import Graph from "https://esm.sh/graphology"; import Graph from "graphology";
import Sigma from "https://esm.sh/sigma"; import Sigma from "sigma";
import forceAtlas2 from "graphology-layout-forceatlas2"; import forceAtlas2 from "graphology-layout-forceatlas2";
// --- Génération de données de base --- // --- Génération de données de base ---
@@ -24,14 +24,8 @@ for (let i = 0; i < N; i++) {
}); });
} }
// --- Ajout de quelques liens initiaux --- let total = N * 1.5
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 });
}
}
// --- Calcul du layout ForceAtlas2 --- // --- Calcul du layout ForceAtlas2 ---
/* /*
@@ -48,11 +42,13 @@ const container = document.getElementById("app");
const renderer = new Sigma(graph, container, { renderLabels: true }); const renderer = new Sigma(graph, container, { renderLabels: true });
// --- Fonctions dynamiques --- // --- Fonctions dynamiques ---
function addLink(source, target) { function addLink() {
if (!graph.hasEdge(source, target)) { const a = `n${Math.floor(Math.random() * N)}`;
graph.addEdge(source, target, { color: "#666" }); const b = `n${Math.floor(Math.random() * N)}`;
renderer.refresh(); 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) { function removeLink(source, target) {
@@ -63,11 +59,8 @@ function removeLink(source, target) {
} }
// --- Exemple dévolution dynamique --- // --- Exemple dévolution dynamique ---
//setTimeout(() => addLink("n0", "n5"), 3000); setTimeout(() => addLink(), 1000);
//setTimeout(() => removeLink("n1", "n2"), 6000); setTimeout(() => removeLink("n1", "n2"), 8000);
//console.log("Graph loaded:", graph.order, "nodes,", graph.size, "edges");
// --- Animation du layout --- // --- Animation du layout ---
// On crée une "simulation" ForceAtlas2 en incrémentant les positions à chaque frame. // On crée une "simulation" ForceAtlas2 en incrémentant les positions à chaque frame.
@@ -86,11 +79,6 @@ function stepLayout() {
// Lancement // Lancement
stepLayout(); stepLayout();
// Arrêt automatique après 5 secondes
setTimeout(() => {
running = false;
console.log("Layout stabilisé");
}, 5000);
/* /*
const layout = new ForceAtlas2Layout(graph, { const layout = new ForceAtlas2Layout(graph, {