What you will be able to do
- Compute a column's mean and sample standard deviation with PySpark aggregate functions
- Build lower and upper outlier bounds as the mean plus or minus a multiple of the standard deviation
- Drop the rows outside those bounds with DataFrame.filter and Column.between
Key concept
Statistical bounds filter — Outlier removal on a Spark DataFrame works in two steps. First you turn a numeric column's statistics into a lower and an upper bound, then you keep only the rows whose value lies between them. The standard deviation method and the IQR method differ only in how they compute those two bounds.
1.The rule: average plus or minus k standard deviations
Every way of removing outliers from a Spark DataFrame follows the same pattern. You reduce a numeric column to a lower and an upper bound, then keep the rows that fall inside. The standard deviation method takes those bounds from two statistics of the column, its average and its standard deviation. It steps a fixed number of standard deviations to either side of the average.
The Databricks data-quality guidance documents this pattern and uses it to find outliers and anomalies. The bounds come from one aggregate query over the reference data:
bounds = spark.sql("""
SELECT
avg(amount) - 3 * stddev(amount) as lower_bound,
avg(amount) + 3 * stddev(amount) as upper_bound
FROM historical_stats
WHERE
date >= CURRENT_DATE() - INTERVAL 30 DAYS
""")
# Join with new data and apply bounds
return spark.read.table("new_data").crossJoin(bounds)Look at the two expressions. lower_bound is the average minus three standard deviations, and upper_bound is the average plus three. The multiplier 3 is a choice this example makes, not something Spark fixes. It is a parameter of the method, and a smaller multiplier pulls the bounds closer to the average, so more rows end up outside them.
The bounds also come from a different table than the one they are applied to. They are computed over historical_stats and applied to new_data through crossJoin. The aggregate has no grouping, so it returns a single row. Crossing that row with the data adds lower_bound and upper_bound to every row, ready to be compared.
Checkpoint 1 of 4· Check yourself
In the Databricks bounds query, which expression defines upper_bound?
The upper bound is the average shifted up by three standard deviations. The standard deviation alone is a spread, not a position, so it can't serve as a bound by itself.
“avg(amount) + 3 * stddev(amount) as upper_bound”Source: docs.databricks.com
Sources1
2.Computing the mean and standard deviation in PySpark
In the DataFrame API, both statistics come from pyspark.sql.functions. sf.stddev is an alias for stddev_samp. The SQL stddev function is documented the same way: it returns the sample standard deviation of the values in the group. sf.mean returns the average and is an alias of avg. Both names produce the same result, and the output header reads avg(...) either way.
import pyspark.sql.functions as sf
spark.range(6).select(sf.stddev("id")).show()
+------------------+
| stddev(id)|
+------------------+
|1.8708286933869...|
+------------------+Three behaviours matter when you use these functions in an outlier rule.
First, they return a Column, not a number. They are expressions, so they have to be evaluated inside select or agg. df.agg aggregates the entire DataFrame without groups. That gives you the shape you need: one row holding the column's mean and standard deviation, which you can cross-join onto the data as shown above.
Second, a standard deviation needs more than one value. If any group consists of only one row, stddev returns NULL for that group.
Third, the SQL function accepts DISTINCT, which computes over unique values only. On the values 1, 2, 3, 3, stddev(col) returns 0.957…, but stddev(DISTINCT col) returns 1.0. Adding DISTINCT therefore changes the bounds you get.
Checkpoint 2 of 4· Fill the gap
Which function completes this sample so it returns the standard deviation of id?
import pyspark.sql.functions as sf
spark.range(6).select(sf. ? ("id")).show()sf.stddev(col) returns the standard deviation of the column. mean and avg both return the average, and percentile_approx returns a percentile.
Source: docs.databricks.com| Call | Returns | Role in the method |
|---|---|---|
| sf.stddev(col) | Column | Spread of the column; alias for stddev_samp (sample standard deviation) |
| sf.mean(col) | Column | Centre of the column; alias of avg |
| df.agg(*exprs) | DataFrame | Evaluates the aggregates over the whole DataFrame without groups |
| between(lowerBound, upperBound) | Column (boolean) | Tests whether a value is inside the bounds, inclusive |
| df.filter(condition) | DataFrame | Keeps the rows that satisfy the condition |
3.Dropping the rows outside the bounds
Once you have the bounds, removing outliers is just a filter. df.filter takes a Column of BooleanType or a string of SQL expressions. It returns a new DataFrame containing the rows that satisfy the condition. Because the condition describes the rows you keep, not the ones you drop, you write it for the inliers. Several comparisons can be combined with &:
df.filter((df.age > 3) & (df.subject == "Physics")).show()
# +---+----+-------+
# |age|name|subject|
# +---+----+-------+
# | 5| Bob|Physics|
# +---+----+-------+You could write the range as two comparisons, but Column.between expresses it directly. It checks whether the value lies between the lower and upper bounds, inclusive. Spark even renders the test as (age >= 2) AND (age <= 4):
df = spark.createDataFrame([(2, "Alice"), (5, "Bob")], ["age", "name"])
df.select(df.name, df.age.between(2, 4)).show()
# +-----+---------------------------+
# | name|((age >= 2) AND (age <= 4))|
# +-----+---------------------------+
# |Alice| true|
# | Bob| false|
# +-----+---------------------------+Each bound can be a value or a Column. After the cross join, every row carries its own lower_bound and upper_bound columns, and between can compare against them directly. The Databricks example writes the same test in SQL as amount BETWEEN lower_bound AND upper_bound.
To see the outliers instead of the inliers, negate the test. NOT BETWEEN is a synonym for expr1 < expr2 or expr1 > expr3, so it lets you look at the rows before you throw them away. Watch out for a missing bound: 4 not between NULL and 5 evaluates to NULL, not true or false.
Checkpoint 3 of 4· Check yourself
A row's amount is exactly equal to upper_bound. What happens to it under df.filter(df.amount.between(df.lower_bound, df.upper_bound))?
between is inclusive at both ends (>= and <=), so a value equal to a bound passes the filter. Its bounds can be Columns as well as literal values.
“Check if the column value is between lower and upper bounds (inclusive).”Source: docs.databricks.com
Checkpoint 4 of 4· Exam question
A dataset of insurance claim amounts is heavily right-skewed, with a small number of extremely large claims sitting far above the much smaller majority. The team wants to drop only genuine outliers before training a regression model, without letting those extreme claims distort where the outlier boundary itself gets drawn. Which approach to removing outliers from the Spark DataFrame best fits this situation?
Correct answer: A — Use `approxQuantile` to compute the first and third quartiles, derive the interquartile range, and filter out rows outside `Q1 - 1.5*IQR` and `Q3 + 1.5*IQR`, since quartile-based fences stay anchored to the bulk of the distribution.
- A. Interquartile fences are built from the 25th and 75th percentiles, which sit inside the bulk of the distribution and barely move even when a handful of claims are extremely large, so the outlier boundary stays stable on skewed data.
- B. Extreme values in a right-skewed column inflate both the mean and the standard deviation, which pushes the mean-plus-three-standard-deviations boundary far to the right and lets many genuine outliers slip through unflagged.
- C. Dropping a fixed count from each end assumes the true outlier fraction is constant across every dataset, which has no connection to where this particular skewed distribution's genuine outliers actually begin.
- D. Capping values at a percentile changes the data through winsorization rather than removing the affected rows, and it does not use the standard-deviation or IQR fences the task calls for at all.
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.sf.stddev returns the population standard deviation.Why is that wrong?
sf.stddev is an alias for stddev_samp, so it returns the sample standard deviation.
Covered in Computing the mean and standard deviation in PySpark
2.A standard deviation over a single row is 0, so the bounds collapse onto that one value.Why is that wrong?
stddev returns NULL for any group that has only one row, so the bounds built from it are NULL as well.
Covered in Computing the mean and standard deviation in PySpark
3.Column.between excludes the bound values, so a row sitting exactly on a bound is removed.Why is that wrong?
between is inclusive at both ends and is equivalent to >= AND <=, so a row equal to a bound is kept.
Covered in Dropping the rows outside the bounds
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“avg(amount) - 3 * stddev(amount) as lower_bound,”
↩︎ The rule: average plus or minus k standard deviations“Join with new data and apply bounds”
↩︎ The rule: average plus or minus k standard deviations“amount BETWEEN lower_bound AND upper_bound”
↩︎ Dropping the rows outside the bounds“validate new data points against historical statistical ranges, helping identify outliers and anomalies in your data flow”
↩︎ Key concept“avg(amount) + 3 * stddev(amount) as upper_bound”
↩︎ Checkpoint - 2.
“Returns the sample standard deviation calculated from the values in the group.”
↩︎ Computing the mean and standard deviation in PySpark“If DISTINCT is specified the function operates only on a unique set of expr values.”
↩︎ Computing the mean and standard deviation in PySpark“If any group consists of only one row, the function returns NULL for that group.”
↩︎ Exam trap 2 - 3.
“Returns the average of the values in a group. An alias of avg.”
↩︎ Computing the mean and standard deviation in PySpark - 4.
“Aggregate on the entire DataFrame without groups (shorthand for df.groupBy().agg()).”
↩︎ Computing the mean and standard deviation in PySpark - 5.
“DataFrame: A new DataFrame with rows that satisfy the condition.”
↩︎ Dropping the rows outside the bounds - 6.
“If not is specified the function is a synonym for expr1 < expr2 or expr1 > expr3.”
↩︎ Dropping the rows outside the bounds
Also cited
“Alias for stddev_samp.”
↩︎ Exam trap 1“Alias for stddev_samp.”
↩︎ Prediction“Check if the column value is between lower and upper bounds (inclusive).”
↩︎ Exam trap 3“Check if the column value is between lower and upper bounds (inclusive).”
↩︎ Checkpoint