CertSafari
    Databricks Certified Associate Developer for Apache Spark· Lessons

    Domain 3 · Lesson 14/32

    Spark DataFrame Aggregations: count, mean, approx_count_distinct, summary

    Perform aggregate operations on DataFrames such as count, approximate count distinct, and mean, summary.

    15 min read
    3.12% of exam
    11 sources
    Published 3 Oct 2026
    Docs as of 30 Sep 2026

    What you will be able to do

    • Tell apart DataFrame.count(), functions.count(col) and GroupedData.count(), and predict how each one treats nulls
    • Calculate a mean with functions.mean/avg through select or agg, and explain why the result column is named avg(...)
    • Use approx_count_distinct, set its rsd parameter, and know when to use count_distinct instead
    • Choose between describe() and summary(), and ask summary() for only the statistics you need

    Key concept

    Aggregate methods vs aggregate functions — Some aggregates are methods on the DataFrame itself, such as count(), describe() and summary(), and you call them directly. Others are functions in pyspark.sql.functions, such as count, mean and approx_count_distinct, which return a Column that you pass to select() or agg(). Most exam questions on this topic come down to which kind of call you have and what it returns.

    1.count: three calls with the same name

    The word count shows up in three places in PySpark, and each one returns something different. The simplest is the DataFrame method df.count(). It is an action that returns a plain Python int holding the number of rows. You get a number back, not a DataFrame or a Column.

    DataFrame.count() returns an int row countpython
    df = spark.createDataFrame(
        [(14, "Tom"), (23, "Alice"), (16, "Bob")], ["age", "name"])
    
    df.count()
    # 3

    The second is the aggregate function sf.count(col) from pyspark.sql.functions. It returns a Column, so you use it inside select() or agg(), and the result comes back as a one-row DataFrame. What you pass it changes the answer. Passing sf.expr("*") counts every row, and the output header is literally count(1). Passing a specific column counts only the rows where that column is not null.

    count("*") counts all four rows, null included. The header reads count(1).python
    from pyspark.sql import functions as sf
    df = spark.createDataFrame([(None,), ("a",), ("b",), ("c",)], schema=["alphabets"])
    df.select(sf.count(sf.expr("*"))).show()
    
    +--------+
    |count(1)|
    +--------+
    |       4|
    +--------+

    On that same DataFrame, df.select(sf.count(df.alphabets)) returns 3. The documentation shows the same split across two columns: for rows (1, "apple"), (2, "banana"), (3, None), count(id) is 3 and count(fruit) is 2. The third form is GroupedData.count(), which you call after groupBy. It counts the records in each group and returns a DataFrame with a count column next to the grouping key.

    The three count calls compared
    CallReturnsWhat it counts
    df.count()intNumber of rows in the DataFrame
    sf.count(sf.expr("*"))Column (header count(1))All rows, nulls included
    sf.count(df.alphabets)Column (header count(alphabets))Non-null values in that column
    df.groupBy(...).count()DataFrameNumber of records for each group

    Checkpoint 1 of 8· Check yourself

    A DataFrame has the rows (1, "apple"), (2, "banana") and (3, None) with columns id and fruit. What does df.select(sf.count(df.id), sf.count(df.fruit)) show?

    Checkpoint 2 of 8· Exam question

    A data engineer needs the count of distinct values in a `user_id` column across tens of millions of rows. Exact precision is not required and query latency matters more than accuracy. Which line correctly fills the blank? ```python from pyspark.sql import functions as F result = df.select(F.________("user_id")).collect()[0][0] ```

    Sources123

    2.mean and avg, through select or agg

    Counting tells you how many rows there are. Averaging tells you something about their values. pyspark.sql.functions has both avg(col) and mean(col), and they are the same function under two names. Because mean is an alias, Spark labels the output column avg(age) even when your code calls sf.mean("age"). If a question shows avg(...) in a result header, don't conclude that the code must have called avg.

    avg ignores the null row: (2 + 4) / 2 = 3.0python
    import pyspark.sql.functions as sf
    df = spark.createDataFrame([(1982, None), (1990, 2), (2000, 4)], ["birth", "age"])
    df.select(sf.avg("age")).show()
    
    +--------+
    |avg(age)|
    +--------+
    |     3.0|
    +--------+

    The answer is 3.0. Nulls are left out of both the sum and the divisor, so a null is not treated as zero. The Spark SQL reference for mean says the same thing and adds one edge case: if a group is empty or holds only nulls, the result is NULL.

    So far the examples have used select(). The other common entry point is df.agg(), which aggregates over the whole DataFrame and is shorthand for df.groupBy().agg(). It accepts two forms. One is a dictionary that maps a column name to the name of an aggregate function. The other is one or more aggregate Column expressions, like the ones you have been building with sf.

    The two forms DataFrame.agg accepts: a dictionary and a Column expressionpython
    from pyspark.sql import functions as sf
    df = spark.createDataFrame([(2, "Alice"), (5, "Bob")], schema=["age", "name"])
    df.agg({"age": "max"}).show()
    # +--------+
    # |max(age)|
    # +--------+
    # |       5|
    # +--------+
    df.agg(sf.min(df.age)).show()
    # +--------+
    # |min(age)|
    # +--------+
    # |       2|
    # +--------+

    After a groupBy, the same alias appears as a shortcut method: GroupedData.mean(*cols) and GroupedData.avg(*cols) both compute the average of each numeric column for each group. For example, df.groupBy("name").avg("age") returns 2.5 for Alice (ages 2 and 3) and 7.5 for Bob (ages 5 and 10).

    Checkpoint 3 of 8· Check yourself

    You run df.select(sf.mean("age")).show(). What header does the result column have?

    Sources4563

    3.approx_count_distinct: trading exactness for speed

    count and mean always return exact answers. Counting distinct values exactly can be expensive on very large data, and Databricks names approximation as the way to make it cheaper. The function for this is sf.approx_count_distinct(col, rsd=None). It returns a Column that estimates the number of distinct values in a column. Like the other aggregate functions, you use it inside agg() or select().

    approx_count_distinct parameters
    ParameterTypeMeaning
    colpyspark.sql.Column or column nameThe column whose distinct values are counted
    rsdfloat, optionalMaximum allowed relative standard deviation, default 0.05. Below 0.01, count_distinct is more efficient.

    Checkpoint 4 of 8· Fill the gap

    Which function completes this sample so that it estimates the number of distinct fruits?

    from pyspark.sql import functions as sf
    df = spark.createDataFrame([("apple",), ("orange",), ("apple",), ("banana",)], ['fruit'])
    df.agg(sf. ? ("fruit")).show()

    The function counts distinct values in one column. To count distinct combinations across several columns, the documentation first packs them into a struct with sf.struct("name", "value") and passes the struct column in. For rows (Alice, 1), (Alice, 2), (Bob, 3), (Bob, 3), that returns 3. Small examples like these come back exact. On a larger input the estimate starts to drift from the true count.

    Default rsd vs rsd=0.1 on 100,000 distinct ids. Neither result is exact.python
    from pyspark.sql import functions as sf
    spark.range(100000).agg(
        sf.approx_count_distinct("id").alias('with_default_rsd'),
        sf.approx_count_distinct("id", 0.1).alias('with_rsd_0.1')
    ).show()
    
    +----------------+------------+
    |with_default_rsd|with_rsd_0.1|
    +----------------+------------+
    |           95546|      102065|
    +----------------+------------+

    The default rsd of 0.05 lands at 95546, and the looser 0.1 lands at 102065. A larger rsd permits more error, and the estimate can fall above or below the true value. Lowering rsd makes the estimate tighter, but the documentation puts a floor on how far that is worth taking. Below 0.01, you are better off using the exact count_distinct.

    Checkpoint 5 of 8· Check yourself

    You need a distinct count with a relative standard deviation tighter than 0.01. What do the docs recommend?

    Checkpoint 6 of 8· Exam question

    A DataFrame `orders` has 1,000 rows, and its `discount_code` column is null for 300 of them. What does the following code return, and why? ```python orders.select(F.count("discount_code")).collect()[0][0] ```

    Sources78

    4.describe and summary: many statistics in one call

    The functions above compute one statistic at a time. When you want a quick profile of a DataFrame, two DataFrame methods compute several at once: describe() and summary(). Both work on numeric and string columns, and both return a new DataFrame whose first column, summary, names the statistic in each row. Both are documented as tools for exploratory data analysis, and Databricks makes no promise that the layout of the result stays backward compatible. That makes them poor choices to feed into downstream pipeline code.

    describe() returns a fixed set of statistics: count, mean, stddev, min and maxpython
    df.describe(['age']).show()
    # +-------+----+
    # |summary| age|
    # +-------+----+
    # |  count|   3|
    # |   mean|12.0|
    # | stddev| 1.0|
    # |    min|  11|
    # |    max|  13|
    # +-------+----+

    describe(*cols) takes a column name or a list of column names and covers every column if you pass none. It always returns the same five statistics. summary(*statistics) works the other way round. You choose columns beforehand with select(), and its arguments name the statistics you want. Called with no arguments, it returns count, mean, stddev, min, the 25%, 50% and 75% percentiles, and max. You can also request arbitrary approximate percentiles written as percentages. The describe documentation points to summary when you need more statistics or want to choose which ones are computed.

    summary() with a chosen list of statistics, including two percentilespython
    df.select("age", "weight", "height").summary("count", "min", "25%", "75%", "max").show()
    # +-------+---+------+------+
    # |summary|age|weight|height|
    # +-------+---+------+------+
    # |  count|  3|     3|     3|
    # |    min| 11|  37.8| 142.2|
    # |    25%| 11|  37.8| 142.2|
    # |    75%| 13|  44.1| 150.5|
    # |    max| 13|  44.1| 150.5|
    # +-------+---+------+------+
    describe() vs summary()
    Aspectdescribe(*cols)summary(*statistics)
    Arguments nameColumns to describeStatistics to compute
    Default outputcount, mean, stddev, min, maxcount, mean, stddev, min, 25%, 50%, 75%, max
    PercentilesNoneAny approximate percentile written as a percentage, such as 75%
    ReturnsDataFrame with a summary columnDataFrame with a summary column

    Checkpoint 7 of 8· Match them up

    Match each call to what it produces

    Tap a term, then the definition that fits it.

    Checkpoint 8 of 8· Exam question

    A data engineer wants to compute, for each `region`, the number of orders and the average `order_total`, with the average column labeled `avg_total`. Which code produces this result?

    Sources910

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.sf.count(df.col) counts every row in the DataFrame, the same way df.count() does.Why is that wrong?

      A count on a named column skips nulls in that column. Only count("*"), shown as count(1), or the DataFrame method df.count() counts every row.

      Covered in count: three calls with the same name

    2. 2.approx_count_distinct returns the exact distinct count, and you should keep lowering rsd for better results.Why is that wrong?

      It returns an estimate: 95546 for 100,000 distinct ids at the default rsd. Below an rsd of 0.01, the exact count_distinct is the more efficient choice.

      Covered in approx_count_distinct: trading exactness for speed

    3. 3.describe() and summary() are interchangeable, and both include percentiles.Why is that wrong?

      describe() returns only count, mean, stddev, min and max. summary() adds percentiles and lets you choose which statistics to compute.

      Covered in describe and summary: many statistics in one call

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 2.
      “Count non-null values in a specific column”
      ↩︎ count: three calls with the same name
      “Count non-null values in a specific column”
      ↩︎ Exam trap 1
      “Count non-null values in multiple columns”
      ↩︎ Checkpoint
    2. 3.
      “Counts the number of records for each group.”
      ↩︎ count: three calls with the same name
      “Computes average values for each numeric column for each group. mean is an alias.”
      ↩︎ mean and avg, through select or agg
    3. 4.
      “Returns the average of the values in a group. An alias of avg.”
      ↩︎ mean and avg, through select or agg
    4. 5.
      “Nulls within the group are ignored. If a group is empty or consists only of nulls the result is NULL.”
      ↩︎ mean and avg, through select or agg
    5. 6.
      “Aggregate on the entire DataFrame without groups (shorthand for df.groupBy().agg()).”
      ↩︎ mean and avg, through select or agg
    6. 7.
      “using approximation for aggregates can accelerate query processing and reduce costs when you don't require precise results.”
      ↩︎ approx_count_distinct: trading exactness for speed
    7. 8.
      “The maximum allowed relative standard deviation (default = 0.05).”
      ↩︎ approx_count_distinct: trading exactness for speed
      “If rsd < 0.01, it would be more efficient to use count_distinct.”
      ↩︎ approx_count_distinct: trading exactness for speed
      “If rsd < 0.01, it would be more efficient to use count_distinct.”
      ↩︎ Exam trap 2
      “estimates the approximate distinct count of elements in a specified column or a group of columns.”
      ↩︎ Prediction
    8. 9.
      “Computes basic statistics for numeric and string columns.”
      ↩︎ describe and summary: many statistics in one call
      “Use summary for expanded statistics and control over which statistics to compute.”
      ↩︎ describe and summary: many statistics in one call
    9. 10.
      “This function is meant for exploratory data analysis”
      ↩︎ describe and summary: many statistics in one call
      “Available statistics are: count, mean, stddev, min, max, arbitrary approximate percentiles specified as a percentage (e.g., 75%).”
      ↩︎ Exam trap 3

    Also cited

    Ready to test yourself?

    Practise Databricks Certified Associate Developer for Apache Spark in quiz mode.

    Spotted a mistake, or was something unclear? Tell us.