Repository: mathesar-foundation/mathesar

Issue:
Support for `mean` aggregation function while summarizing columns
## Problem
Mathesar UI does not support `mean` as an aggregation function though `Mean` is extensively used to get brief overview of data.
## Proposed solution
It would be nice to support `mean` as a transformation step while summarizing columns.


## Additional context
This feature request is a part of GSoC '23 project, [Add More Summarization Functions](https://summerofcode.withgoogle.com/programs/2023/projects/D9XvkQCN).



Discussion:

Resolving patch:
diff --git a/db/functions/base.py b/db/functions/base.py
--- a/db/functions/base.py
+++ b/db/functions/base.py
@@ -419,6 +419,18 @@ def to_sa_expression(column_expr):
         return min(column_expr)
 
 
+class Mean(DBFunction):
+    id = 'mean'
+    name = 'mean'
+    hints = tuple([
+        hints.aggregation,
+    ])
+
+    @staticmethod
+    def to_sa_expression(column_expr):
+        return sa_call_sql_function('avg', column_expr, return_type=PostgresType.NUMERIC)
+
+
 class ArrayAgg(DBFunction):
     id = 'aggregate_to_array'
     name = 'aggregate to array'
diff --git a/mathesar/models/query.py b/mathesar/models/query.py
--- a/mathesar/models/query.py
+++ b/mathesar/models/query.py
@@ -6,7 +6,7 @@
 from db.transforms.operations.deserialize import deserialize_transformation
 from db.transforms.operations.serialize import serialize_transformation
 from db.transforms.base import Summarize
-from db.functions.base import Count, ArrayAgg, Sum, Median, Mode, Percentage_True, Max, Min
+from db.functions.base import Count, ArrayAgg, Sum, Median, Mode, Percentage_True, Max, Min, Mean
 from db.functions.packed import DistinctArrayAgg
 
 from mathesar.api.exceptions.query_exceptions.exceptions import DeletedColumnAccess
@@ -425,6 +425,7 @@ def _get_default_display_name_for_agg_output_alias(
             Mode.id: " mode",
             Percentage_True.id: " percentage true",
             Min.id: "min",
+            Mean.id: " mean",
         }
         suffix_to_add = map_of_agg_function_to_suffix.get(agg_function)
         if suffix_to_add:


Test patch:
diff --git a/mathesar/tests/api/query/test_aggregation_functions.py b/mathesar/tests/api/query/test_aggregation_functions.py
--- a/mathesar/tests/api/query/test_aggregation_functions.py
+++ b/mathesar/tests/api/query/test_aggregation_functions.py
@@ -171,6 +171,68 @@ def test_count_aggregation(library_ma_tables, get_uid, client):
     assert sorted(actual_records, key=lambda x: x['Checkout Month']) == expect_records
 
 
+def test_mean_aggregation(library_ma_tables, get_uid, client):
+    _ = library_ma_tables
+    checkouts = {
+        t["name"]: t for t in client.get("/api/db/v0/tables/").json()["results"]
+    }["Checkouts"]
+    columns = {
+        c["name"]: c for c in checkouts["columns"]
+    }
+    request_data = {
+        "name": get_uid(),
+        "base_table": checkouts["id"],
+        "initial_columns": [
+            {"id": columns["Checkout Time"]["id"], "alias": "Checkout Time"},
+            {"id": columns["Patron"]["id"], "alias": "Patron"},
+        ],
+        "display_names": {
+            "Checkout Month": "Month",
+            "Mean": "Mean of patron",
+        },
+        "display_options": {
+            "Checkout Time": {
+                display_option_origin: "Checkout Time",
+            },
+            "Patron": {
+                display_option_origin: "Patron",
+            },
+        },
+        "transformations": [
+            {
+                "spec": {
+                    "grouping_expressions": [
+                        {
+                            "input_alias": "Checkout Time",
+                            "output_alias": "Checkout Month",
+                            "preproc": "truncate_to_month",
+                        }
+                    ],
+                    "aggregation_expressions": [
+                        {
+                            "input_alias": "Patron",
+                            "output_alias": "Mean",
+                            "function": "mean",
+                        }
+                    ]
+                },
+                "type": "summarize",
+            }
+        ]
+    }
+    response = client.post('/api/db/v0/queries/', data=request_data)
+    assert response.status_code == 201
+    query_id = response.json()['id']
+    expect_records = [
+        {'Checkout Month': '2022-05', 'Mean': 16.641025641025642},
+        {'Checkout Month': '2022-06', 'Mean': 11.461538461538462},
+        {'Checkout Month': '2022-07', 'Mean': 18.06896551724138},
+        {'Checkout Month': '2022-08', 'Mean': 12.6},
+    ]
+    actual_records = client.get(f'/api/db/v0/queries/{query_id}/records/').json()['results']
+    assert sorted(actual_records, key=lambda x: x['Checkout Month']) == expect_records
+
+
 def test_sum_aggregation(library_ma_tables, get_uid, client):
     _ = library_ma_tables
     checkouts = {