These work

This commit is contained in:
Andras Schmelczer 2026-02-11 21:32:33 +00:00
parent 3599803589
commit 1588c01b19
19 changed files with 260 additions and 201 deletions

View file

@ -65,6 +65,81 @@ export function formatRelativeTime(isoDate: string): string {
return new Date(isoDate).toLocaleDateString();
}
// Percentile-based scale: maps between percentile space (0100) and absolute values
// using the histogram's CDF. Each percentile step = 1% of data.
export interface PercentileScale {
toValue: (percentile: number) => number;
toPercentile: (value: number) => number;
}
export function buildPercentileScale(hist: {
min: number;
max: number;
p1: number;
p99: number;
counts: number[];
}): PercentileScale {
const n = hist.counts.length;
const total = hist.counts.reduce((a, b) => a + b, 0);
if (n === 0 || total === 0) {
const range = hist.max - hist.min || 1;
return {
toValue: (p) => hist.min + (p / 100) * range,
toPercentile: (v) => ((v - hist.min) / range) * 100,
};
}
// Bin boundaries: [min, p1, ..middle edges.., p99, max]
const boundaries: number[] = [];
if (n === 1) {
boundaries.push(hist.min, hist.max);
} else {
boundaries.push(hist.min, hist.p1);
if (n > 2) {
const middleWidth = (hist.p99 - hist.p1) / (n - 2);
for (let i = 1; i < n - 1; i++) {
boundaries.push(hist.p1 + i * middleWidth);
}
}
boundaries.push(hist.max);
}
// Cumulative fraction: cumFrac[0]=0, cumFrac[n]=1
const cumFrac: number[] = [0];
for (let i = 0; i < n; i++) {
cumFrac.push(cumFrac[i] + hist.counts[i] / total);
}
cumFrac[n] = 1; // ensure exact 1.0
return {
toValue(percentile: number): number {
const target = Math.max(0, Math.min(1, percentile / 100));
if (target <= 0) return boundaries[0];
if (target >= 1) return boundaries[n];
let i = 0;
for (; i < n - 1; i++) {
if (cumFrac[i + 1] > target) break;
}
const binFrac = cumFrac[i + 1] - cumFrac[i];
const t = binFrac > 0 ? (target - cumFrac[i]) / binFrac : 0;
return boundaries[i] + t * (boundaries[i + 1] - boundaries[i]);
},
toPercentile(value: number): number {
if (value <= boundaries[0]) return 0;
if (value >= boundaries[n]) return 100;
let i = 0;
for (; i < n - 1; i++) {
if (boundaries[i + 1] > value) break;
}
const binWidth = boundaries[i + 1] - boundaries[i];
const t = binWidth > 0 ? (value - boundaries[i]) / binWidth : 0;
return (cumFrac[i] + t * (cumFrac[i + 1] - cumFrac[i])) * 100;
},
};
}
// Calculate weighted mean from histogram with outlier bins.
// Bin 0 = [min, p1), bins 1..n-2 = [p1, p99) evenly, bin n-1 = [p99, max].
export function calculateHistogramMean(histogram: {