Custom Validation with specially()

Create bespoke validations using specially() to implement domain-specific business rules.

Pointblank Validation
2025-06-22|01:26:31
Polars
STEP COLUMNS VALUES TBL EVAL UNITS PASS FAIL W E C EXT
#4CA64C66 1
specially
specially()

All values in column 'a' should be within 2 std devs of mean

EXPR 2000 1947
0.97
53
0.03
#4CA64C 2
specially
specially()

All values in column 'c' should be within 3 std devs of mean

EXPR 2000 2000
1.00
0
0.00
2025-06-22 01:26:31 UTC< 1 s2025-06-22 01:26:31 UTC
import pointblank as pb
import polars as pl

def within_std_deviations(df, column, n_std=2):
    """Check if all values are within n standard deviations of the mean"""
    mean_val = df[column].mean()
    std_val = df[column].std()

    lower_bound = mean_val - (n_std * std_val)
    upper_bound = mean_val + (n_std * std_val)

    # Add a boolean column and return the modified DataFrame
    return df.with_columns(
        pl.col(column).is_between(lower_bound, upper_bound, closed="both").alias("validation_result")
    )

validation = (
    pb.Validate(
        data=pb.load_dataset(dataset="game_revenue", tbl_type="polars")
    )
    .specially(
        expr=lambda df: within_std_deviations(df, column="session_duration", n_std=2),
        brief="All values in column 'a' should be within 2 std devs of mean"
    )
    .specially(
        expr=lambda df: within_std_deviations(df, column="session_duration", n_std=3),
        brief="All values in column 'c' should be within 3 std devs of mean"
    )
    .interrogate()
)

validation
Preview of Input Table
PolarsRows2,000Columns11
player_id
String
session_id
String
session_start
Datetime
time
Datetime
item_type
String
item_name
String
item_revenue
Float64
session_duration
Float64
start_day
Date
acquisition
String
country
String
1 ECPANOIXLZHF896 ECPANOIXLZHF896-eol2j8bs 2015-01-01 01:31:03+00:00 2015-01-01 01:31:27+00:00 iap offer2 8.99 16.3 2015-01-01 google Germany
2 ECPANOIXLZHF896 ECPANOIXLZHF896-eol2j8bs 2015-01-01 01:31:03+00:00 2015-01-01 01:36:57+00:00 iap gems3 22.49 16.3 2015-01-01 google Germany
3 ECPANOIXLZHF896 ECPANOIXLZHF896-eol2j8bs 2015-01-01 01:31:03+00:00 2015-01-01 01:37:45+00:00 iap gold7 107.99 16.3 2015-01-01 google Germany
4 ECPANOIXLZHF896 ECPANOIXLZHF896-eol2j8bs 2015-01-01 01:31:03+00:00 2015-01-01 01:42:33+00:00 ad ad_20sec 0.76 16.3 2015-01-01 google Germany
5 ECPANOIXLZHF896 ECPANOIXLZHF896-hdu9jkls 2015-01-01 11:50:02+00:00 2015-01-01 11:55:20+00:00 ad ad_5sec 0.03 35.2 2015-01-01 google Germany
1996 NAOJRDMCSEBI281 NAOJRDMCSEBI281-j2vs9ilp 2015-01-21 01:57:50+00:00 2015-01-21 02:02:50+00:00 ad ad_survey 1.332 25.8 2015-01-11 organic Norway
1997 NAOJRDMCSEBI281 NAOJRDMCSEBI281-j2vs9ilp 2015-01-21 01:57:50+00:00 2015-01-21 02:22:14+00:00 ad ad_survey 1.35 25.8 2015-01-11 organic Norway
1998 RMOSWHJGELCI675 RMOSWHJGELCI675-vbhcsmtr 2015-01-21 02:39:48+00:00 2015-01-21 02:40:00+00:00 ad ad_5sec 0.03 8.4 2015-01-10 other_campaign France
1999 RMOSWHJGELCI675 RMOSWHJGELCI675-vbhcsmtr 2015-01-21 02:39:48+00:00 2015-01-21 02:47:12+00:00 iap offer5 26.09 8.4 2015-01-10 other_campaign France
2000 GJCXNTWEBIPQ369 GJCXNTWEBIPQ369-9elq67md 2015-01-21 03:59:23+00:00 2015-01-21 04:06:29+00:00 ad ad_5sec 0.12 18.5 2015-01-14 organic United States