What you will be able to do
- Aggregate grouped data with groupBy().agg() in both its dict form and its Column form
- Choose between count(*), count(col), count_distinct and approx_count_distinct
- Predict what mean returns when a column contains nulls
- Use summary() and describe() to profile a DataFrame, and know how they differ
1.groupBy().agg(): the entry point for aggregation
Once a dataset is clean, the next step is usually to aggregate it: count rows per key, average a measure, or estimate how many unique values a column has. In PySpark you group rows with groupBy and then call agg, which computes the aggregates and returns a new DataFrame. agg accepts its expressions in two forms. In the first, a dict maps a column name to the name of an aggregate function, such as {"*": "count"}. In the second, you pass aggregate Column expressions such as sf.min(df.age), and each expression defines one aggregation.
df = spark.createDataFrame(
[(2, "Alice"), (3, "Alice"), (5, "Bob"), (10, "Bob")], ["age", "name"])
# Group-by name, and count each group.
df.groupBy(df.name).agg({"*": "count"}).sort("name").show()
# +-----+--------+
# | name|count(1)|
# +-----+--------+
# |Alice| 2|
# | Bob| 2|
# +-----+--------+agg accepts built-in aggregation functions such as avg, max, min, sum and count, as well as group aggregate pandas UDFs created with pandas_udf. The two kinds can't be mixed: built-ins and pandas UDFs have to go in separate agg calls. Every function on this page can also be used without groupBy, through df.agg(...) or df.select(...), in which case it aggregates over the whole DataFrame.
Checkpoint 1 of 6· Check yourself
A developer writes df.groupBy("name").agg(sf.avg("age"), my_pandas_min_udf(df.age)). What happens according to the documentation?
The agg reference states that built-in aggregation functions and group aggregate pandas UDFs can't be combined in a single call. Use separate agg calls.
“Built-in aggregation functions and group aggregate pandas UDFs cannot be mixed in a single call to this function.”Source: docs.databricks.com
Sources1
2.Counting rows, non-null values and distinct values
"Count" can mean several different things. sf.count returns the number of items in a group, but what it counts depends on its argument. sf.count(sf.expr("*")) counts rows. sf.count(column) counts only the non-null values in that column.
df = spark.createDataFrame(
[(1, "apple"), (2, "banana"), (3, None)], schema=["id", "fruit"])
df.select(sf.count(sf.expr("*"))).show()The documented result is count(id) = 3 and count(fruit) = 2. Counting unique values needs a different function. sf.count_distinct(col, *cols) returns an exact distinct count. You can pass it several columns, and it then counts distinct combinations of their values. On the values 1, 1 and 3 it returns 2.
from pyspark.sql import functions as sf
df = spark.createDataFrame([(1,), (1,), (3,)], ["value"])
df.select(sf.count_distinct(df.value)).show()Checkpoint 2 of 6· Exam question
A silver table of customer support tickets contains a `ticket_id` column that should be unique, but a schema issue upstream produced duplicate `ticket_id` values where the associated `updated_at` timestamp differs between copies. The engineer wants to keep only the row with the latest `updated_at` for each `ticket_id`. Which approach accomplishes this?
Correct answer: A — Sort the DataFrame by `updated_at` in descending order and apply `dropDuplicates(["ticket_id"])`, since sorting first controls which row per key is retained
- A. Correct — ordering the DataFrame by `updated_at` descending before calling `dropDuplicates(["ticket_id"])` makes the retained row for each key the one encountered first in that order, which is the most recently updated ticket.
- B. `distinct()` compares every column, so two rows sharing a `ticket_id` but differing in `updated_at` are already distinct rows and both would be kept, leaving the duplicate `ticket_id` problem unresolved.
- C. Deduplicating on both `ticket_id` and `updated_at` together only removes rows that are exact duplicates on that pair, so the two differently-timestamped copies of the same ticket would both survive.
- D. Filtering to tickets that appear exactly once and unioning that back with the original data would reintroduce every duplicate row rather than resolving them down to the latest update.
3.approx_count_distinct: trading precision for speed
Exact distinct counts can be expensive on very large tables. Databricks points out that using approximation for aggregates can speed up query processing and cut costs when you don't need a precise answer. Spark SQL provides native approximate aggregates, including approx_count_distinct, approx_percentile and approx_top_k. sf.approx_count_distinct(col, rsd=None) returns an estimate of the distinct count of a column, or of several columns when they are combined into a struct.
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 rsd parameter is the maximum allowed relative standard deviation, and it defaults to 0.05. A larger rsd allows a looser estimate. If you need an rsd below 0.01, the documentation says count_distinct is the more efficient choice. At that level of precision the approximation no longer saves anything.
| Function | What it returns | Exact? |
|---|---|---|
| count("*") / count(sf.expr("*")) | Number of rows | Yes |
| count(col) | Number of non-null values in col | Yes |
| count_distinct(col, *cols) | Number of distinct values or combinations | Yes |
| approx_count_distinct(col, rsd) | Estimated number of distinct values, default rsd 0.05 | No |
Checkpoint 3 of 6· Check yourself
A requirement says the distinct count of user IDs must have a relative standard deviation below 0.5% (0.005). Which function should you use?
The documentation says that for rsd < 0.01, count_distinct is more efficient than approx_count_distinct. count would count non-null values, not distinct ones.
“If rsd < 0.01, it would be more efficient to use count_distinct.”Source: docs.databricks.com
4.mean: an alias of avg that skips nulls
sf.mean(col) returns the average of the values in a group. It is an alias of avg, so the two functions are interchangeable. The output column is named avg(...) even when you call mean.
import pyspark.sql.functions as sf
df = spark.createDataFrame([(1982, None), (1990, 2), (2000, 4)], ["birth", "age"])
df.select(sf.mean("age")).show()
+--------+
|avg(age)|
+--------+
| 3.0|
+--------+Checkpoint 4 of 6· Check yourself
You run df.select(sf.mean("age")). What is the result column called?
mean is an alias of avg, and the documented output header is avg(age).
“An alias of avg.”Source: docs.databricks.com
Sources6
5.summary() and describe(): statistics for a whole DataFrame
Sometimes you want a quick profile of every column rather than a single aggregate. describe() computes basic statistics for numeric and string columns: count, mean, stddev, min and max. summary() goes further. Its available statistics are count, mean, stddev, min, max and arbitrary approximate percentiles given as a percentage, such as 75%. Called with no arguments, it returns count, mean, stddev, min, the 25%, 50% and 75% percentiles, and max. You can also pass the names of the statistics you want.
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|
# +-------+---+------+------+| Aspect | describe() | summary() |
|---|---|---|
| Arguments | Column names to describe | Statistics to compute, e.g. "count", "25%" |
| Default statistics | count, mean, stddev, min, max | count, mean, stddev, min, 25%, 50%, 75%, max |
| Percentiles | No | Yes, approximate |
| Intended use | Exploratory data analysis | Exploratory data analysis |
Both pages carry the same warning: these functions are meant for exploratory data analysis, and the schema of the resulting DataFrame isn't guaranteed to stay backward compatible. Use them to inspect data. Don't build a production pipeline that parses their output; for that, write explicit agg expressions.
Checkpoint 5 of 6· Match them up
Match each call to what it gives you
Tap a term, then the definition that fits it.
describe gives a fixed set of basic statistics. summary adds percentiles and lets you choose which statistics to compute. mean is a single aggregate.
“Use summary for expanded statistics and control over which statistics to compute.”Source: docs.databricks.com
Checkpoint 6 of 6· Exam question
A data engineer needs to know the exact number of distinct customer IDs in a gold-layer sales table that will be used for a regulatory audit report, where the reported figure must match the true count precisely. Which function should the engineer use to compute this value?
Correct answer: B — `countDistinct("customer_id")`, which computes the exact number of unique values in the column by evaluating every row
- A. `approx_count_distinct` trades exactness for speed by using an approximation algorithm, so its result can differ slightly from the true distinct count and is unsuitable when an audit requires a precise figure.
- B. Correct — `countDistinct` returns the exact number of unique values in the specified column, which is the guarantee a regulatory audit figure requires.
- C. `count` on a column tallies non-null rows without regard to whether the same value repeats, so it would overcount whenever a customer places more than one order.
- D. `mean` computes a numeric average of the identifier values, which is not a meaningful count of unique customers and is the wrong aggregation entirely for this requirement.
Sources7
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.sf.count(col) counts every row in the group, including rows where col is null.Why is that wrong?
count on a specific column counts only its non-null values. To count all rows, use count(sf.expr("*")) or count("*").
Covered in Counting rows, non-null values and distinct values
2.approx_count_distinct returns the exact number of distinct values; the word 'approx' only refers to how it is computed.Why is that wrong?
It returns an estimate within a relative standard deviation (default 0.05). The documented run on 100,000 unique ids returns 95546.
Covered in approx_count_distinct: trading precision for speed
3.describe() and summary() return the same statistics, so it doesn't matter which one you use to get percentiles.Why is that wrong?
Only summary() supports approximate percentiles such as 25% and 75% and lets you choose the statistics. describe() returns count, mean, stddev, min and max.
Covered in summary() and describe(): statistics for a whole DataFrame
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“Built-in aggregation functions, such as avg, max, min, sum, count.”
↩︎ groupBy().agg(): the entry point for aggregation“Built-in aggregation functions and group aggregate pandas UDFs cannot be mixed in a single call to this function.”
↩︎ Checkpoint - 2.
“Returns the number of items in a group.”
↩︎ Counting rows, non-null values and distinct values“Example 2: Count non-null values in a specific column”
↩︎ Exam trap 1“Example 2: Count non-null values in a specific column”
↩︎ Prediction - 3.
“Returns a new Column for distinct count of col or cols.”
↩︎ Counting rows, non-null values and distinct values - 4.
“using approximation for aggregates can accelerate query processing and reduce costs when you don't require precise results.”
↩︎ approx_count_distinct: trading precision for speed - 5.
“If rsd < 0.01, it would be more efficient to use count_distinct.”
↩︎ approx_count_distinct: trading precision for speed“The maximum allowed relative standard deviation (default = 0.05).”
↩︎ Exam trap 2“Returns a new Column, which estimates the approximate distinct count of elements in a specified column or a group of columns.”
↩︎ Prediction - 6.
“Returns the average of the values in a group. An alias of avg.”
↩︎ mean: an alias of avg that skips nulls“An alias of avg.”
↩︎ Checkpoint - 7.
“This function is meant for exploratory data analysis, as we make no guarantee about the backward compatibility of the schema of the resulting DataFrame.”
↩︎ summary() and describe(): statistics for a whole DataFrame“Available statistics are: count, mean, stddev, min, max, arbitrary approximate percentiles specified as a percentage (e.g., 75%).”
↩︎ summary() and describe(): statistics for a whole DataFrame
Also cited
“Use summary for expanded statistics and control over which statistics to compute.”
↩︎ Exam trap 3“Use summary for expanded statistics and control over which statistics to compute.”
↩︎ Checkpoint