mirror of
https://github.com/PostHog/posthog.git
synced 2024-11-24 18:07:17 +01:00
feat(experiments): Calculate secondary metric credible interval (#26138)
This commit is contained in:
parent
854bdb161c
commit
c4df3e0ee2
@ -131,6 +131,7 @@ export function SecondaryMetricsTable({
|
|||||||
countDataForVariant,
|
countDataForVariant,
|
||||||
exposureCountDataForVariant,
|
exposureCountDataForVariant,
|
||||||
conversionRateForVariant,
|
conversionRateForVariant,
|
||||||
|
credibleIntervalForVariant,
|
||||||
experimentMathAggregationForTrends,
|
experimentMathAggregationForTrends,
|
||||||
getHighestProbabilityVariant,
|
getHighestProbabilityVariant,
|
||||||
} = useValues(experimentLogic({ experimentId }))
|
} = useValues(experimentLogic({ experimentId }))
|
||||||
@ -223,6 +224,24 @@ export function SecondaryMetricsTable({
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Credible interval (95%)',
|
||||||
|
render: function Key(_, item: TabularSecondaryMetricResults): JSX.Element {
|
||||||
|
if (item.variant === 'control') {
|
||||||
|
return <em>Baseline</em>
|
||||||
|
}
|
||||||
|
const credibleInterval = credibleIntervalForVariant(targetResults || null, item.variant)
|
||||||
|
if (!credibleInterval) {
|
||||||
|
return <>—</>
|
||||||
|
}
|
||||||
|
const [lowerBound, upperBound] = credibleInterval
|
||||||
|
return (
|
||||||
|
<div className="font-semibold">{`[${lowerBound > 0 ? '+' : ''}${lowerBound.toFixed(
|
||||||
|
2
|
||||||
|
)}%, ${upperBound > 0 ? '+' : ''}${upperBound.toFixed(2)}%]`}</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Win probability',
|
title: 'Win probability',
|
||||||
render: function Key(_, item: TabularSecondaryMetricResults): JSX.Element {
|
render: function Key(_, item: TabularSecondaryMetricResults): JSX.Element {
|
||||||
@ -255,6 +274,25 @@ export function SecondaryMetricsTable({
|
|||||||
return <div>{`${conversionRate.toFixed(2)}%`}</div>
|
return <div>{`${conversionRate.toFixed(2)}%`}</div>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Credible interval (95%)',
|
||||||
|
render: function Key(_, item: TabularSecondaryMetricResults): JSX.Element {
|
||||||
|
if (item.variant === 'control') {
|
||||||
|
return <em>Baseline</em>
|
||||||
|
}
|
||||||
|
|
||||||
|
const credibleInterval = credibleIntervalForVariant(targetResults || null, item.variant)
|
||||||
|
if (!credibleInterval) {
|
||||||
|
return <>—</>
|
||||||
|
}
|
||||||
|
const [lowerBound, upperBound] = credibleInterval
|
||||||
|
return (
|
||||||
|
<div className="font-semibold">{`[${lowerBound > 0 ? '+' : ''}${lowerBound.toFixed(
|
||||||
|
2
|
||||||
|
)}%, ${upperBound > 0 ? '+' : ''}${upperBound.toFixed(2)}%]`}</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Win probability',
|
title: 'Win probability',
|
||||||
render: function Key(_, item: TabularSecondaryMetricResults): JSX.Element {
|
render: function Key(_, item: TabularSecondaryMetricResults): JSX.Element {
|
||||||
|
@ -105,6 +105,18 @@ export interface ExperimentResultCalculationError {
|
|||||||
statusCode: number
|
statusCode: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CachedSecondaryMetricExperimentFunnelsQueryResponse extends CachedExperimentFunnelsQueryResponse {
|
||||||
|
filters?: {
|
||||||
|
insight?: InsightType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CachedSecondaryMetricExperimentTrendsQueryResponse extends CachedExperimentTrendsQueryResponse {
|
||||||
|
filters?: {
|
||||||
|
insight?: InsightType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const experimentLogic = kea<experimentLogicType>([
|
export const experimentLogic = kea<experimentLogicType>([
|
||||||
props({} as ExperimentLogicProps),
|
props({} as ExperimentLogicProps),
|
||||||
key((props) => props.experimentId || 'new'),
|
key((props) => props.experimentId || 'new'),
|
||||||
@ -1261,6 +1273,53 @@ export const experimentLogic = kea<experimentLogicType>([
|
|||||||
return (variantResults[variantResults.length - 1].count / variantResults[0].count) * 100
|
return (variantResults[variantResults.length - 1].count / variantResults[0].count) * 100
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
credibleIntervalForVariant: [
|
||||||
|
() => [],
|
||||||
|
() =>
|
||||||
|
(
|
||||||
|
experimentResults:
|
||||||
|
| Partial<ExperimentResults['result']>
|
||||||
|
| CachedSecondaryMetricExperimentFunnelsQueryResponse
|
||||||
|
| CachedSecondaryMetricExperimentTrendsQueryResponse
|
||||||
|
| null,
|
||||||
|
variantKey: string
|
||||||
|
): [number, number] | null => {
|
||||||
|
const credibleInterval = experimentResults?.credible_intervals?.[variantKey]
|
||||||
|
if (!credibleInterval) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (experimentResults.filters?.insight === InsightType.FUNNELS) {
|
||||||
|
const controlVariant = (experimentResults.variants as FunnelExperimentVariant[]).find(
|
||||||
|
({ key }) => key === 'control'
|
||||||
|
) as FunnelExperimentVariant
|
||||||
|
const controlConversionRate =
|
||||||
|
controlVariant.success_count / (controlVariant.success_count + controlVariant.failure_count)
|
||||||
|
|
||||||
|
if (!controlConversionRate) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the percentage difference between the credible interval bounds of the variant and the control's conversion rate.
|
||||||
|
// This represents the range in which the true percentage change relative to the control is likely to fall.
|
||||||
|
const lowerBound = ((credibleInterval[0] - controlConversionRate) / controlConversionRate) * 100
|
||||||
|
const upperBound = ((credibleInterval[1] - controlConversionRate) / controlConversionRate) * 100
|
||||||
|
return [lowerBound, upperBound]
|
||||||
|
}
|
||||||
|
|
||||||
|
const controlVariant = (experimentResults.variants as TrendExperimentVariant[]).find(
|
||||||
|
({ key }) => key === 'control'
|
||||||
|
) as TrendExperimentVariant
|
||||||
|
|
||||||
|
const controlMean = controlVariant.count / controlVariant.absolute_exposure
|
||||||
|
|
||||||
|
// Calculate the percentage difference between the credible interval bounds of the variant and the control's mean.
|
||||||
|
// This represents the range in which the true percentage change relative to the control is likely to fall.
|
||||||
|
const lowerBound = ((credibleInterval[0] - controlMean) / controlMean) * 100
|
||||||
|
const upperBound = ((credibleInterval[1] - controlMean) / controlMean) * 100
|
||||||
|
return [lowerBound, upperBound]
|
||||||
|
},
|
||||||
|
],
|
||||||
getIndexForVariant: [
|
getIndexForVariant: [
|
||||||
(s) => [s.experimentInsightType],
|
(s) => [s.experimentInsightType],
|
||||||
(experimentInsightType) =>
|
(experimentInsightType) =>
|
||||||
|
Loading…
Reference in New Issue
Block a user