diff --git a/frontend/src/lib/components/InsightLabel/index.tsx b/frontend/src/lib/components/InsightLabel/index.tsx index 1d83793f68b43..25e4ded8b8294 100644 --- a/frontend/src/lib/components/InsightLabel/index.tsx +++ b/frontend/src/lib/components/InsightLabel/index.tsx @@ -66,7 +66,7 @@ function MathTag({ math, mathProperty, mathHogQL, mathGroupTypeIndex }: MathTagP if (math === 'unique_group' && mathGroupTypeIndex != undefined) { return Unique {aggregationLabel(mathGroupTypeIndex).plural} } - if (math && ['sum', 'avg', 'min', 'max', 'median', 'p90', 'p95', 'p99'].includes(math || '')) { + if (math && ['sum', 'avg', 'min', 'max', 'median', 'p75', 'p90', 'p95', 'p99'].includes(math)) { return ( <> {mathDefinitions[math]?.name || capitalizeFirstLetter(math)} diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index 8defb38c018b0..41d7b3e22de83 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -3906,6 +3906,7 @@ "min_count_per_actor", "max_count_per_actor", "median_count_per_actor", + "p75_count_per_actor", "p90_count_per_actor", "p95_count_per_actor", "p99_count_per_actor" @@ -9099,7 +9100,7 @@ "type": "object" }, "PropertyMathType": { - "enum": ["avg", "sum", "min", "max", "median", "p90", "p95", "p99"], + "enum": ["avg", "sum", "min", "max", "median", "p75", "p90", "p95", "p99"], "type": "string" }, "PropertyOperator": { diff --git a/frontend/src/scenes/cohorts/CohortFilters/constants.tsx b/frontend/src/scenes/cohorts/CohortFilters/constants.tsx index 5db9872a4cca5..753ebddac1a20 100644 --- a/frontend/src/scenes/cohorts/CohortFilters/constants.tsx +++ b/frontend/src/scenes/cohorts/CohortFilters/constants.tsx @@ -89,6 +89,9 @@ export const FIELD_VALUES: Record = { [PropertyMathType.Median]: { label: 'Median', }, + [PropertyMathType.P75]: { + label: '75th percentile', + }, [PropertyMathType.P90]: { label: '90th percentile', }, diff --git a/frontend/src/scenes/trends/mathsLogic.tsx b/frontend/src/scenes/trends/mathsLogic.tsx index 7f907d3f1f246..be5348ef198a3 100644 --- a/frontend/src/scenes/trends/mathsLogic.tsx +++ b/frontend/src/scenes/trends/mathsLogic.tsx @@ -241,6 +241,19 @@ export const PROPERTY_MATH_DEFINITIONS: Record ), category: MathCategory.PropertyValue, }, + [PropertyMathType.P75]: { + name: '75th percentile', + shortName: '75th percentile', + description: ( + <> + Event property 75th percentile. +
+
+ For example 100 events captured with property amount equal to 101..200, result in 175. + + ), + category: MathCategory.PropertyValue, + }, [PropertyMathType.P90]: { name: '90th percentile', shortName: '90th percentile', @@ -315,6 +328,12 @@ export const COUNT_PER_ACTOR_MATH_DEFINITIONS: RecordEvent count per actor 50th percentile., category: MathCategory.EventCountPerActor, }, + [CountPerActorMathType.P75]: { + name: '75th percentile', + shortName: '75th percentile', + description: <>Event count per actor 75th percentile., + category: MathCategory.EventCountPerActor, + }, [CountPerActorMathType.P90]: { name: '90th percentile', shortName: '90th percentile', diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 09fcd30349b0f..e6fa3bbb63db6 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -3652,6 +3652,7 @@ export enum PropertyMathType { Minimum = 'min', Maximum = 'max', Median = 'median', + P75 = 'p75', P90 = 'p90', P95 = 'p95', P99 = 'p99', @@ -3662,6 +3663,7 @@ export enum CountPerActorMathType { Minimum = 'min_count_per_actor', Maximum = 'max_count_per_actor', Median = 'median_count_per_actor', + P75 = 'p75_count_per_actor', P90 = 'p90_count_per_actor', P95 = 'p95_count_per_actor', P99 = 'p99_count_per_actor', diff --git a/posthog/api/documentation.py b/posthog/api/documentation.py index d885aa12a3c84..a47632291c15f 100644 --- a/posthog/api/documentation.py +++ b/posthog/api/documentation.py @@ -161,6 +161,7 @@ class PersonPropertiesSerializer(serializers.Serializer): - `min`: min of a numeric property. - `max`: max of a numeric property. - `median`: median of a numeric property. +- `p75`: 75th percentile of a numeric property. - `p90`: 90th percentile of a numeric property. - `p95` 95th percentile of a numeric property. - `p99`: 99th percentile of a numeric property. diff --git a/posthog/hogql_queries/insights/trends/aggregation_operations.py b/posthog/hogql_queries/insights/trends/aggregation_operations.py index 9e88bfca8b4aa..036c84d8e9786 100644 --- a/posthog/hogql_queries/insights/trends/aggregation_operations.py +++ b/posthog/hogql_queries/insights/trends/aggregation_operations.py @@ -73,6 +73,8 @@ def select_aggregation(self) -> ast.Expr: return self._math_func("max", None) elif self.series.math == "median": return self._math_quantile(0.5, None) + elif self.series.math == "p75": + return self._math_quantile(0.75, None) elif self.series.math == "p90": return self._math_quantile(0.9, None) elif self.series.math == "p95": @@ -106,6 +108,7 @@ def is_count_per_actor_variant(self): "min_count_per_actor", "max_count_per_actor", "median_count_per_actor", + "p75_count_per_actor", "p90_count_per_actor", "p95_count_per_actor", "p99_count_per_actor", @@ -247,6 +250,8 @@ def _actors_inner_select_query( math_func = self._math_func("max", ["total"]) elif self.series.math == "median_count_per_actor": math_func = self._math_quantile(0.5, ["total"]) + elif self.series.math == "p75_count_per_actor": + math_func = self._math_quantile(0.75, ["total"]) elif self.series.math == "p90_count_per_actor": math_func = self._math_quantile(0.9, ["total"]) elif self.series.math == "p95_count_per_actor": diff --git a/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py b/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py index ddc4379b37320..fcaf17321a026 100644 --- a/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py +++ b/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py @@ -32,6 +32,7 @@ [PropertyMathType.MIN, "$browser"], [PropertyMathType.MAX, "$browser"], [PropertyMathType.MEDIAN, "$browser"], + [PropertyMathType.P75, "$browser"], [PropertyMathType.P90, "$browser"], [PropertyMathType.P95, "$browser"], [PropertyMathType.P99, "$browser"], @@ -39,6 +40,7 @@ [CountPerActorMathType.MIN_COUNT_PER_ACTOR, None], [CountPerActorMathType.MAX_COUNT_PER_ACTOR, None], [CountPerActorMathType.MEDIAN_COUNT_PER_ACTOR, None], + [CountPerActorMathType.P75_COUNT_PER_ACTOR, None], [CountPerActorMathType.P90_COUNT_PER_ACTOR, None], [CountPerActorMathType.P95_COUNT_PER_ACTOR, None], [CountPerActorMathType.P99_COUNT_PER_ACTOR, None], @@ -79,6 +81,7 @@ def test_all_cases_return( [PropertyMathType.MIN, False], [PropertyMathType.MAX, False], [PropertyMathType.MEDIAN, False], + [PropertyMathType.P75, False], [PropertyMathType.P90, False], [PropertyMathType.P95, False], [PropertyMathType.P99, False], @@ -86,6 +89,7 @@ def test_all_cases_return( [CountPerActorMathType.MIN_COUNT_PER_ACTOR, True], [CountPerActorMathType.MAX_COUNT_PER_ACTOR, True], [CountPerActorMathType.MEDIAN_COUNT_PER_ACTOR, True], + [CountPerActorMathType.P75_COUNT_PER_ACTOR, True], [CountPerActorMathType.P90_COUNT_PER_ACTOR, True], [CountPerActorMathType.P95_COUNT_PER_ACTOR, True], [CountPerActorMathType.P99_COUNT_PER_ACTOR, True], diff --git a/posthog/hogql_queries/insights/trends/test/test_trends.py b/posthog/hogql_queries/insights/trends/test/test_trends.py index 04d4fc97fd312..216376a7d1817 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends.py @@ -4094,6 +4094,10 @@ def test_max_filtering(self): def test_median_filtering(self): self._test_math_property_aggregation("median", values=range(101, 201), expected_value=150) + @also_test_with_materialized_columns(["some_number"]) + def test_p75_filtering(self): + self._test_math_property_aggregation("p75", values=range(101, 201), expected_value=175) + @also_test_with_materialized_columns(["some_number"]) def test_p90_filtering(self): self._test_math_property_aggregation("p90", values=range(101, 201), expected_value=190) diff --git a/posthog/management/commands/compare_hogql_insights.py b/posthog/management/commands/compare_hogql_insights.py index 9a49af107e063..622b93d0794c2 100644 --- a/posthog/management/commands/compare_hogql_insights.py +++ b/posthog/management/commands/compare_hogql_insights.py @@ -32,10 +32,10 @@ def handle(self, *args, **options): # len(insights) for insight in insights[0:500]: for event in insight.filters.get("events", []): - if event.get("math") in ("median", "p90", "p95", "p99"): + if event.get("math") in ("median", "p75", "p90", "p95", "p99"): event["math"] = "sum" for event in insight.filters.get("actions", []): - if event.get("math") in ("median", "p90", "p95", "p99"): + if event.get("math") in ("median", "p75", "p90", "p95", "p99"): event["math"] = "sum" try: print( # noqa: T201 diff --git a/posthog/models/entity/entity.py b/posthog/models/entity/entity.py index 29cb5d4212d63..0c2cc7e284bdf 100644 --- a/posthog/models/entity/entity.py +++ b/posthog/models/entity/entity.py @@ -20,24 +20,26 @@ "monthly_active", "unique_group", "unique_session", - # TODO: When we are finally on Python 3.11+, inline the below as *PROPERTY_MATH_FUNCTIONS.keys() + "hogql", + # Equivalent to *PROPERTY_MATH_FUNCTIONS.keys(), "sum", "min", "max", "avg", "median", + "p75", "p90", "p95", "p99", - # TODO: When we are finally on Python 3.11+, inline the below as *COUNT_PER_ACTOR_MATH_FUNCTIONS.keys() + # Equivalent to *COUNT_PER_ACTOR_MATH_FUNCTIONS.keys() "min_count_per_actor", "max_count_per_actor", "avg_count_per_actor", "median_count_per_actor", + "p75_count_per_actor", "p90_count_per_actor", "p95_count_per_actor", "p99_count_per_actor", - "hogql", ] diff --git a/posthog/queries/test/test_trends.py b/posthog/queries/test/test_trends.py index 2a03636afbd23..bf2f2f0f035d9 100644 --- a/posthog/queries/test/test_trends.py +++ b/posthog/queries/test/test_trends.py @@ -3611,6 +3611,10 @@ def test_max_filtering(self): def test_median_filtering(self): self._test_math_property_aggregation("median", values=range(101, 201), expected_value=150) + @also_test_with_materialized_columns(["some_number"]) + def test_p75_filtering(self): + self._test_math_property_aggregation("p75", values=range(101, 201), expected_value=175) + @also_test_with_materialized_columns(["some_number"]) def test_p90_filtering(self): self._test_math_property_aggregation("p90", values=range(101, 201), expected_value=190) diff --git a/posthog/queries/trends/util.py b/posthog/queries/trends/util.py index 09f34ff135633..026e14c5e5748 100644 --- a/posthog/queries/trends/util.py +++ b/posthog/queries/trends/util.py @@ -33,6 +33,7 @@ "min": "min", "max": "max", "median": "quantile(0.50)", + "p75": "quantile(0.75)", "p90": "quantile(0.90)", "p95": "quantile(0.95)", "p99": "quantile(0.99)", @@ -43,6 +44,7 @@ "min_count_per_actor": "min", "max_count_per_actor": "max", "median_count_per_actor": "quantile(0.50)", + "p75_count_per_actor": "quantile(0.75)", "p90_count_per_actor": "quantile(0.90)", "p95_count_per_actor": "quantile(0.95)", "p99_count_per_actor": "quantile(0.99)", diff --git a/posthog/schema.py b/posthog/schema.py index 9df93c460b9b5..c76a742d444a3 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -543,6 +543,7 @@ class CountPerActorMathType(StrEnum): MIN_COUNT_PER_ACTOR = "min_count_per_actor" MAX_COUNT_PER_ACTOR = "max_count_per_actor" MEDIAN_COUNT_PER_ACTOR = "median_count_per_actor" + P75_COUNT_PER_ACTOR = "p75_count_per_actor" P90_COUNT_PER_ACTOR = "p90_count_per_actor" P95_COUNT_PER_ACTOR = "p95_count_per_actor" P99_COUNT_PER_ACTOR = "p99_count_per_actor" @@ -1290,6 +1291,7 @@ class PropertyMathType(StrEnum): MIN = "min" MAX = "max" MEDIAN = "median" + P75 = "p75" P90 = "p90" P95 = "p95" P99 = "p99"