fix(experiments): mathematically correctly aggregate ratios like ACOS and ROAS before averaging

This commit is contained in:
Christian Vidal Wolf
2026-02-25 13:44:59 +01:00
parent b39831c417
commit 30466a94ff
+25
View File
@@ -149,6 +149,31 @@ function splitPeriods(
function avgMetric(data: ComputedWeeklyMetrics[], metric: string, durationWeeks: number): number { function avgMetric(data: ComputedWeeklyMetrics[], metric: string, durationWeeks: number): number {
if (data.length === 0 || durationWeeks <= 0) return 0; if (data.length === 0 || durationWeeks <= 0) return 0;
// For rates and ratios, we must sum the raw absolute components across all included weeks
// and THEN calculate the ratio, otherwise averaging percentages yields mathematically incorrect results.
if (metric === 'cvr') {
const totalUnits = data.reduce((s, w) => s + w.units, 0);
const totalSessions = data.reduce((s, w) => s + w.sessions, 0);
return totalSessions > 0 ? (totalUnits / totalSessions) * 100 : 0;
}
if (metric === 'ctr') {
const totalClicks = data.reduce((s, w) => s + w.clicks, 0);
const totalImpressions = data.reduce((s, w) => s + w.impressions, 0);
return totalImpressions > 0 ? (totalClicks / totalImpressions) * 100 : 0;
}
if (metric === 'roas') {
const totalRevenue = data.reduce((s, w) => s + w.revenue, 0);
const totalCost = data.reduce((s, w) => s + w.cost, 0);
return totalCost > 0 ? totalRevenue / totalCost : 0;
}
if (metric === 'acos') {
const totalRevenue = data.reduce((s, w) => s + w.revenue, 0);
const totalCost = data.reduce((s, w) => s + w.cost, 0);
return totalRevenue > 0 ? (totalCost / totalRevenue) * 100 : 0;
}
// For absolute quantities (units, revenue, sessions), we sum them and divide by the duration
const sum = data.reduce((s, w) => s + getMetricValue(w, metric), 0); const sum = data.reduce((s, w) => s + getMetricValue(w, metric), 0);
return sum / durationWeeks; return sum / durationWeeks;
} }