Générations des individus
This commit is contained in:
@@ -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
12
individus.html
Normal 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
6
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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
160
src/individual.ts
Normal 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
50
src/individus.ts
Normal 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" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
38
src/main.ts
38
src/main.ts
@@ -1,7 +1,7 @@
|
|||||||
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 ---
|
||||||
const graph = new Graph();
|
const graph = new Graph();
|
||||||
@@ -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, {
|
||||||
|
|||||||
Reference in New Issue
Block a user