Générations des individus
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Graph MesAmis</title>
|
||||
<title>Graphe Mes Amis</title>
|
||||
</head>
|
||||
<body>
|
||||
<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",
|
||||
"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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
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 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, {
|
||||
|
||||
Reference in New Issue
Block a user