These work
This commit is contained in:
parent
3599803589
commit
1588c01b19
19 changed files with 260 additions and 201 deletions
|
|
@ -65,6 +65,81 @@ export function formatRelativeTime(isoDate: string): string {
|
|||
return new Date(isoDate).toLocaleDateString();
|
||||
}
|
||||
|
||||
// Percentile-based scale: maps between percentile space (0–100) 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: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue