Rapport individus
This commit is contained in:
@@ -28,11 +28,11 @@
|
||||
<canvas id="ageChart" width="600" height="400"></canvas>
|
||||
|
||||
<div style="display: flex;">
|
||||
<div><canvas id="wealthChart" width="300" height="300"></canvas></div>
|
||||
<div><canvas id="educationChart" width="300" height="300"></canvas></div>
|
||||
<div><canvas id="wealthChart" width="300" height="300"></canvas></div>
|
||||
</div>
|
||||
<div><canvas id="heatmapChart" width="200" height="200"></canvas>
|
||||
<div><canvas id="radarChart" width="200" height="200"></canvas>
|
||||
<div><canvas id="heatmapChart" width="700" height="200"></canvas>
|
||||
<div><canvas id="radarChart" width="200" height="100"></canvas>
|
||||
<script type="module" src="/src/individus.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -76,9 +76,11 @@ function randomSex(age:number) {
|
||||
return Math.random() < c ? "F" : "M";
|
||||
}
|
||||
|
||||
function randomSport(age:number) {
|
||||
function randomSport(sexe: "M" | "F", 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);
|
||||
let meanSport = Math.min(Math.max(0.65 - 0.0065 * (age - 18), 0.05), 0.9);
|
||||
|
||||
if (sexe === "F") meanSport -= 0.15;
|
||||
|
||||
// Paramètres de la distribution bêta
|
||||
const a = Math.max(meanSport * 6, 0.5);
|
||||
@@ -130,9 +132,18 @@ function randomWealth(education:number) {
|
||||
return wealth;
|
||||
}
|
||||
|
||||
function randomLecture(education:number, age:number) {
|
||||
function randomLecture(sexe: "M" | "F", education:number, age:number) {
|
||||
// Base : 15% + effet de l'éducation + effet de l'âge
|
||||
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
|
||||
|
||||
// Femmes plus lectrices : ajout d’un bonus
|
||||
if (sexe === "F") meanRead += 0.16;
|
||||
|
||||
// Bonus sénior : lecture plus fréquente chez les séniors
|
||||
if (age > 60) meanRead += 0.09;
|
||||
|
||||
// Clipping entre 0.02 et 0.98
|
||||
meanRead = Math.min(Math.max(meanRead, 0.02), 0.98);
|
||||
|
||||
// Paramètres de la distribution bêta
|
||||
const a = Math.max(meanRead * 7, 0.5);
|
||||
@@ -144,6 +155,10 @@ function randomLecture(education:number, age:number) {
|
||||
return reading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Musique pratique indépendante de l'âge
|
||||
* @returns valeur normalisée (0-1)
|
||||
*/
|
||||
function randomMusique() {
|
||||
return jStat.beta.sample(2, 2);
|
||||
}
|
||||
@@ -158,8 +173,8 @@ export function generate(n: number) {
|
||||
|
||||
const etudes = randomEducation(age);
|
||||
const richesse = randomWealth(etudes);
|
||||
const sport = randomSport(age);
|
||||
const lecture = randomLecture(etudes, age);
|
||||
const sport = randomSport(sexe, age);
|
||||
const lecture = randomLecture(sexe, etudes, age);
|
||||
const musique = randomMusique();
|
||||
|
||||
individus.push({ id: i, prenom, sexe, age, etudes, richesse, sport, lecture, musique });
|
||||
|
||||
314
src/individus.ts
314
src/individus.ts
@@ -2,12 +2,15 @@ import './style.css';
|
||||
import { generate } from "./individual";
|
||||
import Chart from "chart.js/auto";
|
||||
import ChartDataLabels from "chartjs-plugin-datalabels";
|
||||
import { jStat } from "jstat";
|
||||
import { MatrixController, MatrixElement } from 'chartjs-chart-matrix';
|
||||
import { jStat } from "jstat";
|
||||
|
||||
const individus = generate(1000);
|
||||
const individualMale = individus.filter(i => i.sexe === "M");
|
||||
const individualFemale = individus.filter(i => i.sexe === "F");
|
||||
|
||||
// Fonction utilitaire pour afficher les pourcentages
|
||||
const percentage = (value: number, total: number) => ((value / total) * 100).toFixed(1) + "%";
|
||||
const percentage = (value: number, total: number) => ((value / total) * 100).toFixed(0) + "%";
|
||||
|
||||
function formatStats(ages: number[]) {
|
||||
return {
|
||||
@@ -20,10 +23,16 @@ function formatStats(ages: number[]) {
|
||||
};
|
||||
}
|
||||
|
||||
function histogramData(ages: number[]) {
|
||||
return ageClasses.map((a, idx) =>
|
||||
ages.filter(age => age >= a && age < (ageClasses[idx + 1] ?? 80)).length
|
||||
);
|
||||
}
|
||||
|
||||
const Ages:number[] = individus.map(i => i.age);
|
||||
// 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);
|
||||
const AgesH = individualMale.map(i => i.age);
|
||||
const AgesF = individualFemale.map(i => i.age);
|
||||
|
||||
const stats = formatStats(Ages);
|
||||
for (const [key, value] of Object.entries(stats)) {
|
||||
@@ -33,11 +42,6 @@ for (const [key, value] of Object.entries(stats)) {
|
||||
// Construire des classes d'âge
|
||||
const ageClasses = Array.from({ length: 20 }, (_, i) => i * 3 + 18);
|
||||
|
||||
function histogramData(ages: number[]) {
|
||||
return ageClasses.map((a, idx) =>
|
||||
ages.filter(age => age >= a && age < (ageClasses[idx + 1] ?? 80)).length
|
||||
);
|
||||
}
|
||||
|
||||
Chart.register(ChartDataLabels,MatrixController, MatrixElement);
|
||||
|
||||
@@ -46,6 +50,59 @@ Chart.defaults.set('plugins.datalabels', {
|
||||
font: { weight: "bold" }
|
||||
});
|
||||
|
||||
/**
|
||||
* Doughnut Sexe
|
||||
*/
|
||||
const nbMales = individualMale.length;
|
||||
const nbFemales = individus.length - nbMales;
|
||||
|
||||
new Chart(document.getElementById("genreChart") as HTMLCanvasElement, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: ["Hommes", "Femmes"],
|
||||
datasets: [{
|
||||
data: [nbMales, nbFemales],
|
||||
backgroundColor: ["#4A90E2", "#FF69B4"],
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
plugins: { legend: { position: "bottom" },
|
||||
datalabels: {
|
||||
formatter: (value, context) => {
|
||||
const total = context.chart.data.datasets[0].data.reduce((a: number, b: number) => a + b, 0);
|
||||
return percentage(value, total);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Doughnut Classes d'âges
|
||||
*/
|
||||
const classes = {
|
||||
Jeune: individus.filter(i => i.age <= 30),
|
||||
Adulte: individus.filter(i => i.age > 30 && i.age <= 60),
|
||||
Senior: individus.filter(i => i.age > 60)
|
||||
};
|
||||
|
||||
new Chart(document.getElementById("classeChart") as HTMLCanvasElement, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: ["Jeunes (≤30)", "Adultes (31–60)", "Seniors (>60)"],
|
||||
datasets: [{
|
||||
data: [classes.Jeune.length, classes.Adulte.length, classes.Senior.length],
|
||||
backgroundColor: ["#81C784", "#FFD54F", "#E57373"],
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
plugins: { legend: { position: "bottom" } },
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Histogramme des ages
|
||||
*/
|
||||
new Chart(document.getElementById("ageChart") as HTMLCanvasElement, {
|
||||
type: "bar",
|
||||
data: {
|
||||
@@ -64,7 +121,7 @@ new Chart(document.getElementById("ageChart") as HTMLCanvasElement, {
|
||||
],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
responsive: false,
|
||||
plugins: {
|
||||
title: { display: true, text: "Distribution des âges par sexe" },
|
||||
datalabels: false
|
||||
@@ -76,41 +133,21 @@ new Chart(document.getElementById("ageChart") as HTMLCanvasElement, {
|
||||
},
|
||||
});
|
||||
|
||||
const hommes = individus.filter(i => i.sexe === "M").length;
|
||||
const femmes = individus.length - hommes;
|
||||
|
||||
new Chart(document.getElementById("genreChart") as HTMLCanvasElement, {
|
||||
|
||||
/**
|
||||
* Éducation
|
||||
*/
|
||||
new Chart(document.getElementById("educationChart") as HTMLCanvasElement, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: ["Hommes", "Femmes"],
|
||||
labels: ["Bac", "+2", "+3", "+5"],
|
||||
datasets: [{
|
||||
data: [hommes, femmes],
|
||||
backgroundColor: ["#4A90E2", "#FF69B4"],
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
plugins: { legend: { position: "bottom" },
|
||||
datalabels: {
|
||||
formatter: (value, context) => {
|
||||
const total = context.chart.data.datasets[0].data.reduce((a: number, b: number) => a + b, 0);
|
||||
return percentage(value, total);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const jeunes = individus.filter(i => i.age <= 30).length;
|
||||
const adultes = individus.filter(i => i.age > 30 && i.age <= 60).length;
|
||||
const seniors = individus.filter(i => i.age > 60).length;
|
||||
|
||||
new Chart(document.getElementById("classeChart") as HTMLCanvasElement, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: ["Jeunes (≤30)", "Adultes (31–60)", "Seniors (>60)"],
|
||||
datasets: [{
|
||||
data: [jeunes, adultes, seniors],
|
||||
backgroundColor: ["#81C784", "#FFD54F", "#E57373"],
|
||||
data: [ individus.filter(i => i.etudes == 0).length,
|
||||
individus.filter(i => i.etudes == 1).length,
|
||||
individus.filter(i => i.etudes == 2).length,
|
||||
individus.filter(i => i.etudes == 3).length,],
|
||||
backgroundColor: ["#55e0d9ff", "#81C784", "#FFD54F", "#E57373"],
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
@@ -118,6 +155,9 @@ new Chart(document.getElementById("classeChart") as HTMLCanvasElement, {
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Richesse
|
||||
*/
|
||||
new Chart(document.getElementById("wealthChart") as HTMLCanvasElement, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
@@ -140,92 +180,156 @@ new Chart(document.getElementById("wealthChart") as HTMLCanvasElement, {
|
||||
},
|
||||
});
|
||||
|
||||
new Chart(document.getElementById("educationChart") as HTMLCanvasElement, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: ["Bac", "+2", "+3", "+5"],
|
||||
datasets: [{
|
||||
data: [ individus.filter(i => i.etudes == 0).length,
|
||||
individus.filter(i => i.etudes == 1).length,
|
||||
individus.filter(i => i.etudes == 2).length,
|
||||
individus.filter(i => i.etudes == 3).length,],
|
||||
backgroundColor: ["#81C784", "#FFD54F", "#E57373"],
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
plugins: { legend: { position: "bottom" } },
|
||||
},
|
||||
/**
|
||||
* HeatMap
|
||||
*/
|
||||
const activites = ["Lecture", "Musique", "Sport"];
|
||||
const ages = Array.from({ length: 74 - 18 + 1 }, (_, i) => i + 18);
|
||||
|
||||
// Labels Y : chaque activité doublée pour H/F
|
||||
const yLabels = activites.flatMap(a => [`${a} H`, `${a} F`]);
|
||||
|
||||
// Génération des données
|
||||
const dataHeatmap = ages.flatMap((age, x) => {
|
||||
const window = individus.filter(i => i.age >= age - 1 && i.age <= age + 1); // moyenne glissante
|
||||
|
||||
// Pour chaque activité
|
||||
return activites.flatMap((activite, y) => {
|
||||
// map pour extraire les valeurs
|
||||
// const valeurs = window.map(i => i[activite as keyof typeof i]);
|
||||
|
||||
const hommes = window.filter(i => i.sexe === 'M');
|
||||
const femmes = window.filter(i => i.sexe === 'F');
|
||||
|
||||
// reduce pour la moyenne
|
||||
const moyenneH = hommes.length > 0
|
||||
? hommes.map(i => i[activite.toLowerCase() as keyof typeof i])
|
||||
.reduce((sum, val) => sum + val, 0) / hommes.length
|
||||
: 0;
|
||||
|
||||
const moyenneF = femmes.length > 0
|
||||
? femmes.map(i => i[activite.toLowerCase() as keyof typeof i])
|
||||
.reduce((sum, val) => sum + val, 0) / femmes.length
|
||||
: 0;
|
||||
|
||||
return [
|
||||
{ x, y: y * 2, v: moyenneH },
|
||||
{ x, y: y * 2 + 1, v: moyenneF }
|
||||
];
|
||||
});
|
||||
});
|
||||
|
||||
new Chart(document.getElementById('heatmapChart') as HTMLCanvasElement, {
|
||||
type: 'matrix',
|
||||
|
||||
new Chart(document.getElementById("heatmapChart") as HTMLCanvasElement, {
|
||||
type: "matrix",
|
||||
data: {
|
||||
labels: individus.map((_, i) => `Individu ${i + 1}`),
|
||||
datasets: [{
|
||||
label: 'Activités',
|
||||
data: individus.map((individu, i) => [
|
||||
{ x: 0, y: i, v: individu.sport },
|
||||
{ x: 1, y: i, v: individu.musique },
|
||||
{ x: 2, y: i, v: individu.lecture }
|
||||
]).flat(),
|
||||
backgroundColor: ({ v }) => {
|
||||
const r = Math.floor(255 - v * 100);
|
||||
const g = Math.floor(255 - v * 50);
|
||||
const b = Math.floor(255);
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
},
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255, 255, 255, 0.6)',
|
||||
width: ({ chart }) => (chart.chartArea?.width ?? 0) / 3 - 1,
|
||||
height: ({ chart }) => (chart.chartArea?.height ?? 0) / individus.length - 1
|
||||
}]
|
||||
datasets: [
|
||||
{
|
||||
label: 'Activités par age et par sexe',
|
||||
data: dataHeatmap,
|
||||
borderWidth: 0,
|
||||
backgroundColor: ({ raw }: { raw: any }) => {
|
||||
const mean = 0.5; // ou calculé dynamiquement
|
||||
const contrastFactor = 2; // augmente les écarts
|
||||
let v = (raw.v - mean) * contrastFactor + mean;
|
||||
v = Math.min(Math.max(v, 0), 1); // clip sur [0,1]
|
||||
const light = 90; // OKLCH clair
|
||||
const dark = 40; // OKLCH foncé
|
||||
const c = Math.floor(light - (light - dark) * v);
|
||||
return `oklch(${c}% 0.12 145)`; // vert
|
||||
},
|
||||
width: ({ chart }: { chart: any }) =>
|
||||
(chart.chartArea?.width ?? 0) / ages.length,
|
||||
height: ({ chart }: { chart: any }) =>
|
||||
(chart.chartArea?.height ?? 0) / yLabels.length - 1
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
x: {
|
||||
type: 'category',
|
||||
labels: ['Sport', 'Musique', 'Lecture'],
|
||||
offset: true
|
||||
},
|
||||
y: {
|
||||
type: 'category',
|
||||
labels: individus.map((_, i) => `Individu ${i + 1}`),
|
||||
offset: true
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
datalabels: false,
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: ({ raw }) => `Valeur : ${raw.v.toFixed(2)}`
|
||||
title: (items) => {
|
||||
const raw = (items[0] as any).raw;
|
||||
return `${yLabels[raw.y]} - ${ages[raw.x]} ans`;
|
||||
},
|
||||
label: ({ raw }: { raw: any }) =>
|
||||
` ${(raw.v * 100).toFixed(0)}%`
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'linear',
|
||||
min: -0.5,
|
||||
max: ages.length - 0.5,
|
||||
ticks: {
|
||||
autoSkip: false,
|
||||
callback: (_, i) => i % 5 === 0 ? ages[i] ?? '' : '',
|
||||
stepSize: 1
|
||||
},
|
||||
offset: false,
|
||||
grid: { display: false }
|
||||
},
|
||||
y: {
|
||||
type: 'linear',
|
||||
min: -0.5,
|
||||
max: yLabels.length - 0.5,
|
||||
position: 'left',
|
||||
ticks: {
|
||||
callback: (_, i) => yLabels[i] ?? '',
|
||||
crossAlign: 'near',
|
||||
align: 'end',
|
||||
stepSize: 1
|
||||
},
|
||||
offset: false,
|
||||
grid: { display: false },
|
||||
reverse: false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const datasets = Object.entries(classes).map(([label, group], idx) => {
|
||||
|
||||
const colors = [
|
||||
"rgba(75,192,192,0.2)",
|
||||
"rgba(255,99,132,0.2)",
|
||||
"rgba(255,206,86,0.2)"
|
||||
];
|
||||
|
||||
const borderColors = [
|
||||
"rgba(75,192,192,1)",
|
||||
"rgba(255,99,132,1)",
|
||||
"rgba(255,206,86,1)"
|
||||
];
|
||||
|
||||
return {
|
||||
label,
|
||||
data: [
|
||||
jStat.mean(group.map(i => i.etudes)) / 3, // normalisation si besoin
|
||||
jStat.mean(group.map(i => i.richesse)) / 3,
|
||||
jStat.mean(group.map(i => i.sport)),
|
||||
jStat.mean(group.map(i => i.musique)),
|
||||
jStat.mean(group.map(i => i.lecture))
|
||||
],
|
||||
backgroundColor: colors[idx],
|
||||
borderColor: borderColors[idx],
|
||||
pointBackgroundColor: borderColors[idx]
|
||||
};
|
||||
});
|
||||
|
||||
new Chart(document.getElementById("radarChart") as HTMLCanvasElement, {
|
||||
type: "radar",
|
||||
data: {
|
||||
labels: ["Etudes", "Richesse", "Sport", "Musique", "Lecture"],
|
||||
datasets: [{
|
||||
label: "Valeurs moyennes",
|
||||
data: [
|
||||
individus[10].etudes / 3,
|
||||
individus[10].richesse / 3,
|
||||
individus[10].sport,
|
||||
individus[10].musique,
|
||||
individus[10].lecture
|
||||
],
|
||||
backgroundColor: "rgba(54,162,235,0.2)",
|
||||
borderColor: "rgba(54,162,235,1)",
|
||||
pointBackgroundColor: "rgba(54,162,235,1)"
|
||||
}]
|
||||
labels: ["Études", "Richesse", "Sport", "Musique", "Lecture"],
|
||||
datasets: datasets,
|
||||
},
|
||||
options: {
|
||||
scales: { r: { min: 0, max: 1, ticks: { stepSize: 0.1 } } },
|
||||
scales: { r: { min: 0, max: 1, ticks: { stepSize: 0.25 } } },
|
||||
plugins: { legend: { position: "top" }, datalabels: false }
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user