Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": 20,
"name": "frequentist-five-variants",
"name": "frequentist-five-variants-custom-baseline",
"description": null,
"start_date": "2025-05-25T13:33:00Z",
"end_date": null,
Expand Down Expand Up @@ -149,10 +149,17 @@
"uuid": "fb633a4e-f3a9-429a-8b89-3f4e6967a71a"
}
],
"primary_metrics_ordered_uuids": ["645b7e87-0e3b-4543-8e75-6234d5310e64", "9f7d52f5-175a-4210-a1a9-804d8aca19af"],
"secondary_metrics_ordered_uuids": ["a89f1069-8f38-44c7-955e-fd325c5370b2", "fb633a4e-f3a9-429a-8b89-3f4e6967a71a"],
"primary_metrics_ordered_uuids": [
"645b7e87-0e3b-4543-8e75-6234d5310e64",
"9f7d52f5-175a-4210-a1a9-804d8aca19af"
],
"secondary_metrics_ordered_uuids": [
"a89f1069-8f38-44c7-955e-fd325c5370b2",
"fb633a4e-f3a9-429a-8b89-3f4e6967a71a"
],
"stats_config": {
"method": "frequentist"
"method": "frequentist",
"baseline_variant_key": "test-2"
},
"conclusion": null,
"conclusion_comment": null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"baseline": {
"key": "control",
"number_of_samples": 660,
"sum": 79.0,
"sum_squares": 79.0
"key": "test-2",
"number_of_samples": 678,
"sum": 85.0,
"sum_squares": 85.0
},
"cache_key": "cache_1fc548df0bb59cf67f3f74b10519d471",
"cache_target_age": "2025-06-13T15:06:04.080661Z",
Expand All @@ -24,41 +24,53 @@
"timezone": "UTC",
"variant_results": [
{
"confidence_interval": [-0.32664160293090333, 0.2427953597535722],
"key": "test-1",
"confidence_interval": [
-0.3194458396982118,
0.2289645562757519
],
"key": "control",
"method": "frequentist",
"number_of_samples": 654,
"p_value": 0.7727344039571313,
"number_of_samples": 660,
"p_value": 0.7462428557191743,
"significant": false,
"sum": 75.0,
"sum_squares": 75.0
"sum": 79.0,
"sum_squares": 79.0
},
{
"confidence_interval": [-0.25342258906256815, 0.34819126835012276],
"key": "test-2",
"confidence_interval": [
-0.35199939978481265,
0.18146513103143994
],
"key": "test-1",
"method": "frequentist",
"number_of_samples": 678,
"p_value": 0.7573533369503214,
"number_of_samples": 654,
"p_value": 0.5306892004774055,
"significant": false,
"sum": 85.0,
"sum_squares": 85.0
"sum": 75.0,
"sum_squares": 75.0
},
{
"confidence_interval": [-0.36928387310192495, 0.17097786932502407],
"confidence_interval": [
-0.39306123133072324,
0.1132454349974191
],
"key": "test-3",
"method": "frequentist",
"number_of_samples": 677,
"p_value": 0.4716076357323562,
"p_value": 0.2784838659844888,
"significant": false,
"sum": 73.0,
"sum_squares": 73.0
},
{
"confidence_interval": [-0.24380565365903328, 0.375172144240116],
"confidence_interval": [
-0.2722028498636957,
0.30714495268907815
],
"key": "test-4",
"method": "frequentist",
"number_of_samples": 635,
"p_value": 0.6772180225948241,
"p_value": 0.905831712196401,
"significant": false,
"sum": 81.0,
"sum_squares": 81.0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"baseline": {
"key": "control",
"number_of_samples": 352,
"sum": 84.0,
"sum_squares": 84.0
"key": "test-2",
"number_of_samples": 370,
"sum": 70.0,
"sum_squares": 70.0
},
"cache_key": "cache_8928ab74ad19c9caa977c219e466cf0f",
"cache_target_age": "2025-06-13T13:34:57.174521Z",
Expand All @@ -24,41 +24,53 @@
"timezone": "UTC",
"variant_results": [
{
"confidence_interval": [0.07793147405845363, 0.7465883891354428],
"confidence_interval": [
-0.09497276429818707,
0.6177000370254597
],
"key": "control",
Comment thread
capy-ai[bot] marked this conversation as resolved.
"method": "frequentist",
"number_of_samples": 352,
"p_value": 0.15029635976120037,
"significant": false,
"sum": 84.0,
"sum_squares": 84.0
},
{
"confidence_interval": [
0.3245041084920392,
1.2382425371275347
],
"key": "test-1",
"method": "frequentist",
"number_of_samples": 362,
"p_value": 0.01572972589790078,
"p_value": 0.0008279139453490902,
"significant": true,
"sum": 122.0,
"sum_squares": 122.0
},
{
"confidence_interval": [-0.4311719086701697, 0.01675749425575529],
"key": "test-2",
"method": "frequentist",
"number_of_samples": 370,
"p_value": 0.0697285633853224,
"significant": false,
"sum": 70.0,
"sum_squares": 70.0
},
{
"confidence_interval": [-0.16152208160745096, 0.40087708286721413],
"confidence_interval": [
0.031254975811512764,
0.7933859916865975
],
"key": "test-3",
"method": "frequentist",
"number_of_samples": 378,
"p_value": 0.40368585103186083,
"significant": false,
"p_value": 0.03398496530623474,
"significant": true,
"sum": 101.0,
"sum_squares": 101.0
},
{
"confidence_interval": [-0.30251617183973945, 0.23040298151568636],
"confidence_interval": [
-0.14099855656887744,
0.5727648733192194
],
"key": "test-4",
"method": "frequentist",
"number_of_samples": 313,
"p_value": 0.7905480103716265,
"p_value": 0.23533276612512544,
"significant": false,
"sum": 72.0,
"sum_squares": 72.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ function ChecklistItem({
variants,
getInsightType,
}: ChecklistItemProps): JSX.Element {
const baselineVariantKey = experiment.stats_config?.baseline_variant_key ?? 'control'
const failureText: Record<ResultErrorCode, string> = {
[ResultErrorCode.NO_CONTROL_VARIANT]: 'Events with the control variant not received',
[ResultErrorCode.NO_CONTROL_VARIANT]: `Events with the baseline variant (${baselineVariantKey}) not received`,
[ResultErrorCode.NO_TEST_VARIANT]: 'Events with at least one test variant not received',
[ResultErrorCode.NO_EXPOSURES]: 'Exposure events not received',
}

const successText: Record<ResultErrorCode, string> = {
[ResultErrorCode.NO_CONTROL_VARIANT]: 'Events with the control variant received',
[ResultErrorCode.NO_CONTROL_VARIANT]: `Events with the baseline variant (${baselineVariantKey}) received`,
[ResultErrorCode.NO_TEST_VARIANT]: 'Events with at least one test variant received',
[ResultErrorCode.NO_EXPOSURES]: 'Exposure events have been received',
}
Expand Down Expand Up @@ -77,8 +78,10 @@ function ChecklistItem({
value: hasMissingExposure
? variants.map((variant) => variant.key)
: errorCode === ResultErrorCode.NO_CONTROL_VARIANT
? ['control']
: variants.slice(1).map((variant) => variant.key),
? [baselineVariantKey]
: variants
.filter((variant) => variant.key !== baselineVariantKey)
.map((variant) => variant.key),
operator: 'exact',
type: 'event',
},
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/scenes/experiments/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,17 @@ export const CONCLUSION_DISPLAY_CONFIG: Record<
> = {
[ExperimentConclusion.Won]: {
title: 'Won',
description: 'The test variant(s) outperformed the control with statistical significance.',
description: 'The test variant(s) outperformed the baseline with statistical significance.',
color: 'bg-success',
},
[ExperimentConclusion.Lost]: {
title: 'Lost',
description: 'The test variant(s) underperformed compared to the control with statistical significance.',
description: 'The test variant(s) underperformed compared to the baseline with statistical significance.',
color: 'bg-danger',
},
[ExperimentConclusion.Inconclusive]: {
title: 'Inconclusive',
description: 'No significant difference was detected between the variant(s) and the control.',
description: 'No significant difference was detected between the variant(s) and the baseline.',
color: 'bg-warning',
},
[ExperimentConclusion.StoppedEarly]: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ function ChecklistItem({
getInsightType: (metric: any) => InsightType
metric: any
}): JSX.Element {
const baselineVariantKey = experiment.stats_config?.baseline_variant_key ?? 'control'
const failureText: Record<ResultErrorCode, string> = {
[ResultErrorCode.NO_CONTROL_VARIANT]: 'Events with the control variant not received',
[ResultErrorCode.NO_CONTROL_VARIANT]: `Events with the baseline variant (${baselineVariantKey}) not received`,
[ResultErrorCode.NO_TEST_VARIANT]: 'Events with at least one test variant not received',
[ResultErrorCode.NO_EXPOSURES]: 'Exposure events not received',
}

const successText: Record<ResultErrorCode, string> = {
[ResultErrorCode.NO_CONTROL_VARIANT]: 'Events with the control variant received',
[ResultErrorCode.NO_CONTROL_VARIANT]: `Events with the baseline variant (${baselineVariantKey}) received`,
[ResultErrorCode.NO_TEST_VARIANT]: 'Events with at least one test variant received',
[ResultErrorCode.NO_EXPOSURES]: 'Exposure events have been received',
}
Expand Down Expand Up @@ -72,8 +73,10 @@ function ChecklistItem({
value: hasMissingExposure
? variants.map((variant: any) => variant.key)
: errorCode === ResultErrorCode.NO_CONTROL_VARIANT
? ['control']
: variants.slice(1).map((variant: any) => variant.key),
? [baselineVariantKey]
: variants
.filter((variant: any) => variant.key !== baselineVariantKey)
.map((variant: any) => variant.key),
operator: 'exact',
type: 'event',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ export default meta
type Story = StoryObj<{}>

// Small delay to ensure charts render completely
export const ExperimentFrequentistFiveVariants: Story = { play: makeDelay(500) }
export const ExperimentFrequentistFiveVariantsCustomBaseline: Story = { play: makeDelay(500) }
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ def __init__(self, *args, **kwargs):
self.variants = [variant["key"] for variant in self.feature_flag.variants]
if self.experiment.holdout:
self.variants.append(f"holdout-{self.experiment.holdout.id}")
stats_config = self.experiment.stats_config or {}
self.baseline_variant_key = stats_config.get("baseline_variant_key") or CONTROL_VARIANT_KEY

self.prepared_funnels_query = self._prepare_funnel_query()
self.funnels_query_runner = FunnelsQueryRunner(
Expand Down Expand Up @@ -144,7 +146,7 @@ def _prepare_funnel_query(self) -> FunnelsQuery:
def _get_variants_with_base_stats(
self, funnels_result: FunnelsQueryResponse
) -> tuple[ExperimentVariantFunnelsBaseStats, list[ExperimentVariantFunnelsBaseStats]]:
control_variant: Optional[ExperimentVariantFunnelsBaseStats] = None
baseline_variant: Optional[ExperimentVariantFunnelsBaseStats] = None
test_variants = []

for result in funnels_result.results:
Expand All @@ -158,8 +160,8 @@ def _get_variants_with_base_stats(

breakdown_value = cast(list[str], first_step["breakdown_value"])[0]

if breakdown_value == CONTROL_VARIANT_KEY:
control_variant = ExperimentVariantFunnelsBaseStats(
if breakdown_value == self.baseline_variant_key:
baseline_variant = ExperimentVariantFunnelsBaseStats(
key=breakdown_value,
success_count=int(success),
failure_count=int(failure),
Expand All @@ -171,10 +173,10 @@ def _get_variants_with_base_stats(
)
)

if control_variant is None:
raise ValueError("Control variant not found in count results")
if baseline_variant is None:
raise ValueError(f"Baseline variant '{self.baseline_variant_key}' not found in count results")

return control_variant, test_variants
return baseline_variant, test_variants

def _validate_event_variants(self, funnels_result: FunnelsQueryResponse):
errors = {
Expand All @@ -193,15 +195,15 @@ def _validate_event_variants(self, funnels_result: FunnelsQueryResponse):
if event_dict.get("order") == 0:
eventsWithOrderZero.append(event_dict)

# Check if "control" is present
# Check if the baseline variant is present
for event in eventsWithOrderZero:
event_variant = event.get("breakdown_value", [None])[0]
if event_variant == "control":
if event_variant == self.baseline_variant_key:
errors[ExperimentNoResultsErrorKeys.NO_CONTROL_VARIANT] = False
break

# Check if at least one of the test variants is present
test_variants = [variant for variant in self.variants if variant != "control"]
test_variants = [variant for variant in self.variants if variant != self.baseline_variant_key]
for event in eventsWithOrderZero:
event_variant = event.get("breakdown_value", [None])[0]
if event_variant in test_variants:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def __init__(
]

stats_config = self.experiment.stats_config or {}
self.baseline_variant_key = stats_config.get("baseline_variant_key", CONTROL_VARIANT_KEY)
self.baseline_variant_key = stats_config.get("baseline_variant_key") or CONTROL_VARIANT_KEY

self.date_range = get_experiment_date_range(self.experiment, self.team, self.override_end_date)
self.date_range_query = QueryDateRange(
Expand Down
Loading