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"