Skip to content

04 · Custom Primitives

View on GitHub · Download .ipynb

This notebook demonstrates how to extend Featurizer with custom aggregation and transformation primitives using a financial analytics scenario.

Note: Since the primitives expansion (commit 6b25339), several primitives demonstrated here — Median, P95, Range, Log, ZScore — are now built-in. This tutorial is retained to show the registration pattern for creating truly custom primitives.

1. Built-in Primitives That Cover Common Needs

Section titled “1. Built-in Primitives That Cover Common Needs”

Before writing custom primitives, check if your need is already covered. Featurizer now ships with 43 aggregations and 71 transformations.

import sys
from pathlib import Path
sys.path.insert(0, str(Path.cwd().parent.parent))
from featurizer.primitives.utils import list_aggregations, list_transformations
aggs = sorted(list_aggregations())
transforms = sorted(list_transformations())
print(f"Built-in aggregations ({len(aggs)}):")
print(f" {', '.join(aggs)}")
print(f"\nBuilt-in transformations ({len(transforms)}):")
print(f" {', '.join(transforms)}")
Built-in aggregations (69):
acf_1, age_in_system, all, any, bbox_area, burstiness, cosinor_amplitude_weekly, count, cross_type_latency, cv, distance_travelled, entropy, event_rate, first_passage_time, gap_cv, gap_max, gap_mean, gap_min, gap_stddev, geometric_mean, gini, harmonic_mean, hhi, inter_event_hazard_proxy, iqr, kl_drift, kurtosis, longest_streak, markov_conditional_entropy, max, max_transition_prob, mean, mean_deviation, median, median_absolute_deviation, min, min_max_scale, mode, ngram_2_freq, ngram_3_freq, nunique, p10, p25, p75, p90, p95, p99, radius_of_gyration, range, recency, recurrence_interval, rework_count, right_censoring_indicator, sequence_entropy, skewness, spatial_std, state_volatility, stddev, sum, tenure, theil, time_in_current_state, time_span, transition_matrix_summary, trimmed_mean_10, variance, variance_ratio, wasserstein_drift, z_score
Built-in transformations (83):
abs, avg_word_length, caps_ratio, cbrt, cdf, ceil, century, cross_entity_percentile, cross_entity_zscore, cum_count, cum_max, cum_mean, cum_min, cum_sum, cumprod, cusum, cyclic_day, cyclic_hour, cyclic_month, daily_bin, day, diff, diff2, diff3, digit_ratio, dom, dow, doy, ema_14, ema_7, exclamation_count, exp, first, floor, holt_winters_level_14, holt_winters_level_7, holt_winters_trend_14, holt_winters_trend_7, hour, hourly_bin, identity, in_array, is_null, lag_1, lag_3, lag_7, last, ln, log, mean_shift_ratio_14, mean_shift_ratio_7, month, ntile, num_chars, num_sentences, num_words, pct_change_1, pct_change_3, percent_rank, previous, punct_ratio, quarter, question_count, rolling_iqr_14, rolling_iqr_7, rolling_mean_14, rolling_mean_3, rolling_mean_7, rolling_median_5, rolling_median_7, rolling_std_14, rolling_std_3, rolling_std_7, sign, sqrt, time_since_previous, trunc, tz, tz_offset, unique_word_ratio, week, week_of_year, year

Built-in Equivalents of This Example’s Custom Primitives

Section titled “Built-in Equivalents of This Example’s Custom Primitives”
Custom (this example)Built-in nameType
Medianmedianaggregation
Percentile95p95aggregation
Rangerangeaggregation
Loglntransformation
ZScorecross_entity_zscoretransformation

To use built-in versions, no custom code is needed — just reference them by name.

When you need a truly custom primitive (e.g., domain-specific financial metrics), Featurizer provides a registration API. Let’s walk through the pattern.

with open("custom_primitives.py") as f:
print(f.read())
"""Custom aggregation and transformation primitives for financial analytics.
These demonstrate the current primitive API and emit PostgreSQL-valid SQL:
- An **aggregation** subclasses ``Aggregator`` (or a richer base such as
``OrderedSetAggregator``) and overrides ``_build_aggregate_expression`` to
return the SQL aggregate; the base wraps it into a ``Feature``. Interval
variants pass ``interval`` so the expression can add the ``daterange`` filter.
- A **transformation** subclasses ``Transformer`` and overrides
``_build_transformer_call`` to return a scalar SQL expression over the
current row; the base wraps it into a ``Feature``.
Register *instances* (not classes) via ``register_aggregation`` /
``register_transformer``, then select them by name in ``config.yaml``.
"""
from featurizer.primitives.aggregations import Aggregator, OrderedSetAggregator
from featurizer.primitives.transformations import Transformer
from featurizer.primitives.utils import register_aggregation, register_transformer
# ============================================================================
# Custom Aggregations
# ============================================================================
class Range(Aggregator):
"""Spread of a value: ``max(x) - min(x)``.
PostgreSQL's ``FILTER`` clause attaches to a single aggregate, so for an
interval window each of ``max`` and ``min`` is filtered independently.
"""
def __init__(self):
super().__init__(name="range")
def _agg(self, fn, feature, interval):
expr = f"{fn}({feature.name})"
if interval and feature.entity and feature.entity.temporal_ix:
event_date = feature.entity.temporal_ix.name
daterange = (
f"daterange((aod.as_of_date - interval '{interval}')::date, "
f"aod.as_of_date::date, '[]')"
)
expr += f" filter (where {daterange} @> {event_date}::date)"
return expr
def _build_aggregate_expression(self, feature, interval=None):
return f"({self._agg('max', feature, interval)} - {self._agg('min', feature, interval)})"
class Percentile95(OrderedSetAggregator):
"""95th percentile via the ordered-set base (handles WITHIN GROUP + interval)."""
def __init__(self):
super().__init__(name="p95", aggregate="percentile_cont", direct_argument=0.95)
# ============================================================================
# Custom Transformations
# ============================================================================
class Log1p(Transformer):
"""Natural log with a +1 offset: ``ln(x + 1)``.
Guarded with a CASE: the logarithm is undefined for ``x + 1 <= 0``, and this
data has negative values (withdrawals), so those map to NULL rather than
raising. (Without the guard PostgreSQL errors on the whole query.)
"""
def __init__(self):
super().__init__(name="log1p")
def _build_transformer_call(self, feature):
return f"case when {feature.name} + 1 > 0 then ln({feature.name} + 1) end"
class ZScore(Transformer):
"""Z-score standardization across the rows in scope (``OVER ()``)."""
def __init__(self):
super().__init__(name="zscore")
def _build_transformer_call(self, feature):
return (
f"(({feature.name} - avg({feature.name}) over ()) "
f"/ nullif(stddev({feature.name}) over (), 0))"
)
class BinCount(Transformer):
"""Discretize into 5 equal-width bins (0–4) across the rows in scope."""
def __init__(self):
super().__init__(name="bin", output_type="numeric")
def _build_transformer_call(self, feature):
f = feature.name
return (
f"case when {f} is null then null "
f"else cast(floor(5 * ({f} - min({f}) over ()) "
f"/ nullif(max({f}) over () - min({f}) over (), 0)) as integer) end"
)
# ============================================================================
# Registration
# ============================================================================
def register_all_custom_primitives():
"""Register all custom primitives with the feature system."""
register_aggregation("range", Range())
register_aggregation("p95", Percentile95())
register_transformer("log1p", Log1p())
register_transformer("zscore", ZScore())
register_transformer("bin", BinCount())
print("✓ Registered custom primitives:")
print(" Aggregations: range, p95")
print(" Transformations: log1p, zscore, bin")
  1. Subclass Aggregation or Transformation from featurizer.primitives.abstractions
  2. Implement to_sql(feature, alias) returning a SQL expression
  3. Register via register_aggregation() or register_transformation()
  4. Call register_all_custom_primitives() before creating the Featurizer

Important: Transformations must return new Feature instances (never mutate) to preserve hashing semantics.

import sys
from pathlib import Path
# This tutorial is database-free: it loads the config and inspects the
# synthesized features and the generated SQL — none of which touch a
# database. To execute the features against PostgreSQL, run
# `just example 04-custom-primitives` (create_data.py + run_example.py).
sys.path.insert(0, str(Path.cwd().parent.parent))
# Custom primitives must be registered BEFORE creating the Featurizer.
from custom_primitives import register_all_custom_primitives
register_all_custom_primitives()
✓ Registered custom primitives:
Aggregations: range, p95
Transformations: log1p, zscore, bin

4. Create Featurizer with Custom Primitives

Section titled “4. Create Featurizer with Custom Primitives”
from featurizer import Featurizer
featurizer = Featurizer("config.yaml")
print(f"Target entity: {featurizer.target.alias}")
print(f"Max depth: {featurizer.max_depth}")
print(f"Intervals: {featurizer.intervals}")
target_features = featurizer.features[featurizer.target.alias]
print(f"Generated features: {len(target_features)}")
2026-06-21 13:29:57.017 | DEBUG  | featurizer.planner:plan:230 - Starting feature build for target accounts
2026-06-21 13:29:57.018 | DEBUG  | featurizer.planner:_build_features:256 - build_features(accounts) depth=0
2026-06-21 13:29:57.018 | DEBUG  | featurizer.planner:_build_features:256 - build_features(transactions) depth=1
2026-06-21 13:29:57.018 | INFO  | featurizer.planner:_build_features:276 - Maximum recursion depth reached at depth 2; materializing transactions without traversing further.
2026-06-21 13:29:57.018 | DEBUG  | featurizer.planner:_build_aggregations:1014 - Processing backward relationship Entity(accounts).account_id -> Entity(transactions).account_id
2026-06-21 13:29:57.019 | WARNING  | featurizer.planner:_apply_direct_roles:1155 - Direct variable 'accounts.account_type' (type: categorical) will pass through as a raw string column and is likely to crash a downstream encoder. Set role: categorical (with a declared vocabulary or a PostgreSQL ENUM) to one-hot encode it, or role: identifier to exclude it.
Target entity: accounts
Max depth: 2
Intervals: ['P7D', 'P30D']
Generated features: 187

5. Examining Features from Custom Primitives

Section titled “5. Examining Features from Custom Primitives”

Let’s see which features use our custom primitives.

custom_names = ["median", "p95", "range", "log", "zscore", "bin"]
custom_features = [
f for f in target_features if any(prim in f.name.lower() for prim in custom_names)
]
print(f"Features using custom primitives: {len(custom_features)}")
for f in sorted(custom_features, key=lambda x: x.name)[:15]:
print(f" - {f.name}")
Features using custom primitives: 171
- "BIN(accounts.COUNT(transactions.transaction_date))"
- "BIN(accounts.COUNT(transactions.transaction_date|interval=P30D))"
- "BIN(accounts.COUNT(transactions.transaction_date|interval=P7D))"
- "BIN(accounts.COUNT(transactions.transaction_id))"
- "BIN(accounts.COUNT(transactions.transaction_id|interval=P30D))"
- "BIN(accounts.COUNT(transactions.transaction_id|interval=P7D))"
- "BIN(accounts.COUNT(transactions.transaction_type))"
- "BIN(accounts.COUNT(transactions.transaction_type|interval=P30D))"
- "BIN(accounts.COUNT(transactions.transaction_type|interval=P7D))"
- "BIN(accounts.MEAN(transactions.BIN(transactions.amount)))"
- "BIN(accounts.MEAN(transactions.BIN(transactions.amount)|interval=P30D))"
- "BIN(accounts.MEAN(transactions.BIN(transactions.amount)|interval=P7D))"
- "BIN(accounts.MEAN(transactions.LOG1P(transactions.amount)))"
- "BIN(accounts.MEAN(transactions.LOG1P(transactions.amount)|interval=P30D))"
- "BIN(accounts.MEAN(transactions.LOG1P(transactions.amount)|interval=P7D))"

Let’s compare the SQL generated by a custom primitive vs its built-in equivalent.

sql = featurizer.query
print("Generated SQL (with custom primitives):")
print("=" * 80)
print(sql)
print("=" * 80)
2026-06-21 13:29:57.026 | DEBUG  | featurizer.sql:render:40 - Rendered SQL for target 'accounts': 5 CTEs, 60572 chars
Generated SQL (with custom primitives):
================================================================================
select aod.as_of_date, t.*
from as_of_dates as aod
cross join lateral (
with
-- sythetize aggregations and direct features for transactions
transactions_synth as (
select
transactions.transaction_id, transactions.transaction_date, transactions.account_id, amount, transaction_type
from transactions
)
,
-- transform transactions
transactions_transform as (
select
transaction_id, transaction_date, account_id, case when amount is null then null else cast(floor(5 * (amount - min(amount) over ()) / nullif(max(amount) over () - min(amount) over (), 0)) as integer) end as "BIN(transactions.amount)" , case when amount + 1 > 0 then ln(amount + 1) end as "LOG1P(transactions.amount)" , ((amount - avg(amount) over ()) / nullif(stddev(amount) over (), 0)) as "ZSCORE(transactions.amount)" , amount as amount, transaction_type as transaction_type
from transactions_synth _ego
)
,
-- Aggregate for accounts
transactions_aggs_for_accounts as (
select
transactions_transform.account_id,
count( transaction_date ) as "COUNT(transactions.transaction_date)" ,count( transaction_date ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "COUNT(transactions.transaction_date|interval=P30D)" ,count( transaction_date ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "COUNT(transactions.transaction_date|interval=P7D)" ,count( transaction_id ) as "COUNT(transactions.transaction_id)" ,count( transaction_id ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "COUNT(transactions.transaction_id|interval=P30D)" ,count( transaction_id ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "COUNT(transactions.transaction_id|interval=P7D)" ,count( transaction_type ) as "COUNT(transactions.transaction_type)" ,count( transaction_type ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "COUNT(transactions.transaction_type|interval=P30D)" ,count( transaction_type ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "COUNT(transactions.transaction_type|interval=P7D)" ,avg( "BIN(transactions.amount)" ) as "MEAN(transactions.BIN(transactions.amount))" ,avg( "BIN(transactions.amount)" ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "MEAN(transactions.BIN(transactions.amount)|interval=P30D)" ,avg( "BIN(transactions.amount)" ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "MEAN(transactions.BIN(transactions.amount)|interval=P7D)" ,avg( "LOG1P(transactions.amount)" ) as "MEAN(transactions.LOG1P(transactions.amount))" ,avg( "LOG1P(transactions.amount)" ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)" ,avg( "LOG1P(transactions.amount)" ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)" ,avg( "ZSCORE(transactions.amount)" ) as "MEAN(transactions.ZSCORE(transactions.amount))" ,avg( "ZSCORE(transactions.amount)" ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)" ,avg( "ZSCORE(transactions.amount)" ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)" ,avg( amount ) as "MEAN(transactions.amount)" ,avg( amount ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "MEAN(transactions.amount|interval=P30D)" ,avg( amount ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "MEAN(transactions.amount|interval=P7D)" ,percentile_cont(0.95) within group(order by "BIN(transactions.amount)") as "P95(transactions.BIN(transactions.amount))" ,percentile_cont(0.95) within group(order by "BIN(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "P95(transactions.BIN(transactions.amount)|interval=P30D)" ,percentile_cont(0.95) within group(order by "BIN(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "P95(transactions.BIN(transactions.amount)|interval=P7D)" ,percentile_cont(0.95) within group(order by "LOG1P(transactions.amount)") as "P95(transactions.LOG1P(transactions.amount))" ,percentile_cont(0.95) within group(order by "LOG1P(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "P95(transactions.LOG1P(transactions.amount)|interval=P30D)" ,percentile_cont(0.95) within group(order by "LOG1P(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "P95(transactions.LOG1P(transactions.amount)|interval=P7D)" ,percentile_cont(0.95) within group(order by "ZSCORE(transactions.amount)") as "P95(transactions.ZSCORE(transactions.amount))" ,percentile_cont(0.95) within group(order by "ZSCORE(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "P95(transactions.ZSCORE(transactions.amount)|interval=P30D)" ,percentile_cont(0.95) within group(order by "ZSCORE(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "P95(transactions.ZSCORE(transactions.amount)|interval=P7D)" ,percentile_cont(0.95) within group(order by amount) as "P95(transactions.amount)" ,percentile_cont(0.95) within group(order by amount) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "P95(transactions.amount|interval=P30D)" ,percentile_cont(0.95) within group(order by amount) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) as "P95(transactions.amount|interval=P7D)" ,(max("BIN(transactions.amount)") - min("BIN(transactions.amount)")) as "RANGE(transactions.BIN(transactions.amount))" ,(max("BIN(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) - min("BIN(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date)) as "RANGE(transactions.BIN(transactions.amount)|interval=P30D)" ,(max("BIN(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) - min("BIN(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date)) as "RANGE(transactions.BIN(transactions.amount)|interval=P7D)" ,(max("LOG1P(transactions.amount)") - min("LOG1P(transactions.amount)")) as "RANGE(transactions.LOG1P(transactions.amount))" ,(max("LOG1P(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) - min("LOG1P(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date)) as "RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)" ,(max("LOG1P(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) - min("LOG1P(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date)) as "RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)" ,(max("ZSCORE(transactions.amount)") - min("ZSCORE(transactions.amount)")) as "RANGE(transactions.ZSCORE(transactions.amount))" ,(max("ZSCORE(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) - min("ZSCORE(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date)) as "RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)" ,(max("ZSCORE(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) - min("ZSCORE(transactions.amount)") filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date)) as "RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)" ,(max(amount) - min(amount)) as "RANGE(transactions.amount)" ,(max(amount) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) - min(amount) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> transaction_date::date)) as "RANGE(transactions.amount|interval=P30D)" ,(max(amount) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date) - min(amount) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> transaction_date::date)) as "RANGE(transactions.amount|interval=P7D)"
from transactions_transform
where transaction_date <= aod.as_of_date
group by account_id
)
,
-- sythetize aggregations and direct features for accounts
accounts_synth as (
select
accounts.account_id, accounts.open_date, "COUNT(transactions.transaction_date)", "COUNT(transactions.transaction_date|interval=P30D)", "COUNT(transactions.transaction_date|interval=P7D)", "COUNT(transactions.transaction_id)", "COUNT(transactions.transaction_id|interval=P30D)", "COUNT(transactions.transaction_id|interval=P7D)", "COUNT(transactions.transaction_type)", "COUNT(transactions.transaction_type|interval=P30D)", "COUNT(transactions.transaction_type|interval=P7D)", "MEAN(transactions.BIN(transactions.amount))", "MEAN(transactions.BIN(transactions.amount)|interval=P30D)", "MEAN(transactions.BIN(transactions.amount)|interval=P7D)", "MEAN(transactions.LOG1P(transactions.amount))", "MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)", "MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)", "MEAN(transactions.ZSCORE(transactions.amount))", "MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)", "MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)", "MEAN(transactions.amount)", "MEAN(transactions.amount|interval=P30D)", "MEAN(transactions.amount|interval=P7D)", "P95(transactions.BIN(transactions.amount))", "P95(transactions.BIN(transactions.amount)|interval=P30D)", "P95(transactions.BIN(transactions.amount)|interval=P7D)", "P95(transactions.LOG1P(transactions.amount))", "P95(transactions.LOG1P(transactions.amount)|interval=P30D)", "P95(transactions.LOG1P(transactions.amount)|interval=P7D)", "P95(transactions.ZSCORE(transactions.amount))", "P95(transactions.ZSCORE(transactions.amount)|interval=P30D)", "P95(transactions.ZSCORE(transactions.amount)|interval=P7D)", "P95(transactions.amount)", "P95(transactions.amount|interval=P30D)", "P95(transactions.amount|interval=P7D)", "RANGE(transactions.BIN(transactions.amount))", "RANGE(transactions.BIN(transactions.amount)|interval=P30D)", "RANGE(transactions.BIN(transactions.amount)|interval=P7D)", "RANGE(transactions.LOG1P(transactions.amount))", "RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)", "RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)", "RANGE(transactions.ZSCORE(transactions.amount))", "RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)", "RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)", "RANGE(transactions.amount)", "RANGE(transactions.amount|interval=P30D)", "RANGE(transactions.amount|interval=P7D)", account_type, credit_limit
from accounts
left join
transactions_aggs_for_accounts on transactions_aggs_for_accounts.account_id = accounts.account_id
)
,
-- transform accounts
accounts_transform as (
select
account_id, open_date, case when "COUNT(transactions.transaction_date)" is null then null else cast(floor(5 * ("COUNT(transactions.transaction_date)" - min("COUNT(transactions.transaction_date)") over ()) / nullif(max("COUNT(transactions.transaction_date)") over () - min("COUNT(transactions.transaction_date)") over (), 0)) as integer) end as "BIN(accounts.COUNT(transactions.transaction_date))" , case when "COUNT(transactions.transaction_date|interval=P30D)" is null then null else cast(floor(5 * ("COUNT(transactions.transaction_date|interval=P30D)" - min("COUNT(transactions.transaction_date|interval=P30D)") over ()) / nullif(max("COUNT(transactions.transaction_date|interval=P30D)") over () - min("COUNT(transactions.transaction_date|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.COUNT(transactions.transaction_date|interval=P30D))" , case when "COUNT(transactions.transaction_date|interval=P7D)" is null then null else cast(floor(5 * ("COUNT(transactions.transaction_date|interval=P7D)" - min("COUNT(transactions.transaction_date|interval=P7D)") over ()) / nullif(max("COUNT(transactions.transaction_date|interval=P7D)") over () - min("COUNT(transactions.transaction_date|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.COUNT(transactions.transaction_date|interval=P7D))" , case when "COUNT(transactions.transaction_id)" is null then null else cast(floor(5 * ("COUNT(transactions.transaction_id)" - min("COUNT(transactions.transaction_id)") over ()) / nullif(max("COUNT(transactions.transaction_id)") over () - min("COUNT(transactions.transaction_id)") over (), 0)) as integer) end as "BIN(accounts.COUNT(transactions.transaction_id))" , case when "COUNT(transactions.transaction_id|interval=P30D)" is null then null else cast(floor(5 * ("COUNT(transactions.transaction_id|interval=P30D)" - min("COUNT(transactions.transaction_id|interval=P30D)") over ()) / nullif(max("COUNT(transactions.transaction_id|interval=P30D)") over () - min("COUNT(transactions.transaction_id|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.COUNT(transactions.transaction_id|interval=P30D))" , case when "COUNT(transactions.transaction_id|interval=P7D)" is null then null else cast(floor(5 * ("COUNT(transactions.transaction_id|interval=P7D)" - min("COUNT(transactions.transaction_id|interval=P7D)") over ()) / nullif(max("COUNT(transactions.transaction_id|interval=P7D)") over () - min("COUNT(transactions.transaction_id|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.COUNT(transactions.transaction_id|interval=P7D))" , case when "COUNT(transactions.transaction_type)" is null then null else cast(floor(5 * ("COUNT(transactions.transaction_type)" - min("COUNT(transactions.transaction_type)") over ()) / nullif(max("COUNT(transactions.transaction_type)") over () - min("COUNT(transactions.transaction_type)") over (), 0)) as integer) end as "BIN(accounts.COUNT(transactions.transaction_type))" , case when "COUNT(transactions.transaction_type|interval=P30D)" is null then null else cast(floor(5 * ("COUNT(transactions.transaction_type|interval=P30D)" - min("COUNT(transactions.transaction_type|interval=P30D)") over ()) / nullif(max("COUNT(transactions.transaction_type|interval=P30D)") over () - min("COUNT(transactions.transaction_type|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.COUNT(transactions.transaction_type|interval=P30D))" , case when "COUNT(transactions.transaction_type|interval=P7D)" is null then null else cast(floor(5 * ("COUNT(transactions.transaction_type|interval=P7D)" - min("COUNT(transactions.transaction_type|interval=P7D)") over ()) / nullif(max("COUNT(transactions.transaction_type|interval=P7D)") over () - min("COUNT(transactions.transaction_type|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.COUNT(transactions.transaction_type|interval=P7D))" , case when "MEAN(transactions.BIN(transactions.amount))" is null then null else cast(floor(5 * ("MEAN(transactions.BIN(transactions.amount))" - min("MEAN(transactions.BIN(transactions.amount))") over ()) / nullif(max("MEAN(transactions.BIN(transactions.amount))") over () - min("MEAN(transactions.BIN(transactions.amount))") over (), 0)) as integer) end as "BIN(accounts.MEAN(transactions.BIN(transactions.amount)))" , case when "MEAN(transactions.BIN(transactions.amount)|interval=P30D)" is null then null else cast(floor(5 * ("MEAN(transactions.BIN(transactions.amount)|interval=P30D)" - min("MEAN(transactions.BIN(transactions.amount)|interval=P30D)") over ()) / nullif(max("MEAN(transactions.BIN(transactions.amount)|interval=P30D)") over () - min("MEAN(transactions.BIN(transactions.amount)|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.MEAN(transactions.BIN(transactions.amount)|interval=P30D))" , case when "MEAN(transactions.BIN(transactions.amount)|interval=P7D)" is null then null else cast(floor(5 * ("MEAN(transactions.BIN(transactions.amount)|interval=P7D)" - min("MEAN(transactions.BIN(transactions.amount)|interval=P7D)") over ()) / nullif(max("MEAN(transactions.BIN(transactions.amount)|interval=P7D)") over () - min("MEAN(transactions.BIN(transactions.amount)|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.MEAN(transactions.BIN(transactions.amount)|interval=P7D))" , case when "MEAN(transactions.LOG1P(transactions.amount))" is null then null else cast(floor(5 * ("MEAN(transactions.LOG1P(transactions.amount))" - min("MEAN(transactions.LOG1P(transactions.amount))") over ()) / nullif(max("MEAN(transactions.LOG1P(transactions.amount))") over () - min("MEAN(transactions.LOG1P(transactions.amount))") over (), 0)) as integer) end as "BIN(accounts.MEAN(transactions.LOG1P(transactions.amount)))" , case when "MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)" is null then null else cast(floor(5 * ("MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)" - min("MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)") over ()) / nullif(max("MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)") over () - min("MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.MEAN(transactions.LOG1P(transactions.amount)|interval=P30D))" , case when "MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)" is null then null else cast(floor(5 * ("MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)" - min("MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)") over ()) / nullif(max("MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)") over () - min("MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.MEAN(transactions.LOG1P(transactions.amount)|interval=P7D))" , case when "MEAN(transactions.ZSCORE(transactions.amount))" is null then null else cast(floor(5 * ("MEAN(transactions.ZSCORE(transactions.amount))" - min("MEAN(transactions.ZSCORE(transactions.amount))") over ()) / nullif(max("MEAN(transactions.ZSCORE(transactions.amount))") over () - min("MEAN(transactions.ZSCORE(transactions.amount))") over (), 0)) as integer) end as "BIN(accounts.MEAN(transactions.ZSCORE(transactions.amount)))" , case when "MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)" is null then null else cast(floor(5 * ("MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)" - min("MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)") over ()) / nullif(max("MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)") over () - min("MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D))" , case when "MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)" is null then null else cast(floor(5 * ("MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)" - min("MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)") over ()) / nullif(max("MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)") over () - min("MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D))" , case when "MEAN(transactions.amount)" is null then null else cast(floor(5 * ("MEAN(transactions.amount)" - min("MEAN(transactions.amount)") over ()) / nullif(max("MEAN(transactions.amount)") over () - min("MEAN(transactions.amount)") over (), 0)) as integer) end as "BIN(accounts.MEAN(transactions.amount))" , case when "MEAN(transactions.amount|interval=P30D)" is null then null else cast(floor(5 * ("MEAN(transactions.amount|interval=P30D)" - min("MEAN(transactions.amount|interval=P30D)") over ()) / nullif(max("MEAN(transactions.amount|interval=P30D)") over () - min("MEAN(transactions.amount|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.MEAN(transactions.amount|interval=P30D))" , case when "MEAN(transactions.amount|interval=P7D)" is null then null else cast(floor(5 * ("MEAN(transactions.amount|interval=P7D)" - min("MEAN(transactions.amount|interval=P7D)") over ()) / nullif(max("MEAN(transactions.amount|interval=P7D)") over () - min("MEAN(transactions.amount|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.MEAN(transactions.amount|interval=P7D))" , case when "P95(transactions.BIN(transactions.amount))" is null then null else cast(floor(5 * ("P95(transactions.BIN(transactions.amount))" - min("P95(transactions.BIN(transactions.amount))") over ()) / nullif(max("P95(transactions.BIN(transactions.amount))") over () - min("P95(transactions.BIN(transactions.amount))") over (), 0)) as integer) end as "BIN(accounts.P95(transactions.BIN(transactions.amount)))" , case when "P95(transactions.BIN(transactions.amount)|interval=P30D)" is null then null else cast(floor(5 * ("P95(transactions.BIN(transactions.amount)|interval=P30D)" - min("P95(transactions.BIN(transactions.amount)|interval=P30D)") over ()) / nullif(max("P95(transactions.BIN(transactions.amount)|interval=P30D)") over () - min("P95(transactions.BIN(transactions.amount)|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.P95(transactions.BIN(transactions.amount)|interval=P30D))" , case when "P95(transactions.BIN(transactions.amount)|interval=P7D)" is null then null else cast(floor(5 * ("P95(transactions.BIN(transactions.amount)|interval=P7D)" - min("P95(transactions.BIN(transactions.amount)|interval=P7D)") over ()) / nullif(max("P95(transactions.BIN(transactions.amount)|interval=P7D)") over () - min("P95(transactions.BIN(transactions.amount)|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.P95(transactions.BIN(transactions.amount)|interval=P7D))" , case when "P95(transactions.LOG1P(transactions.amount))" is null then null else cast(floor(5 * ("P95(transactions.LOG1P(transactions.amount))" - min("P95(transactions.LOG1P(transactions.amount))") over ()) / nullif(max("P95(transactions.LOG1P(transactions.amount))") over () - min("P95(transactions.LOG1P(transactions.amount))") over (), 0)) as integer) end as "BIN(accounts.P95(transactions.LOG1P(transactions.amount)))" , case when "P95(transactions.LOG1P(transactions.amount)|interval=P30D)" is null then null else cast(floor(5 * ("P95(transactions.LOG1P(transactions.amount)|interval=P30D)" - min("P95(transactions.LOG1P(transactions.amount)|interval=P30D)") over ()) / nullif(max("P95(transactions.LOG1P(transactions.amount)|interval=P30D)") over () - min("P95(transactions.LOG1P(transactions.amount)|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.P95(transactions.LOG1P(transactions.amount)|interval=P30D))" , case when "P95(transactions.LOG1P(transactions.amount)|interval=P7D)" is null then null else cast(floor(5 * ("P95(transactions.LOG1P(transactions.amount)|interval=P7D)" - min("P95(transactions.LOG1P(transactions.amount)|interval=P7D)") over ()) / nullif(max("P95(transactions.LOG1P(transactions.amount)|interval=P7D)") over () - min("P95(transactions.LOG1P(transactions.amount)|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.P95(transactions.LOG1P(transactions.amount)|interval=P7D))" , case when "P95(transactions.ZSCORE(transactions.amount))" is null then null else cast(floor(5 * ("P95(transactions.ZSCORE(transactions.amount))" - min("P95(transactions.ZSCORE(transactions.amount))") over ()) / nullif(max("P95(transactions.ZSCORE(transactions.amount))") over () - min("P95(transactions.ZSCORE(transactions.amount))") over (), 0)) as integer) end as "BIN(accounts.P95(transactions.ZSCORE(transactions.amount)))" , case when "P95(transactions.ZSCORE(transactions.amount)|interval=P30D)" is null then null else cast(floor(5 * ("P95(transactions.ZSCORE(transactions.amount)|interval=P30D)" - min("P95(transactions.ZSCORE(transactions.amount)|interval=P30D)") over ()) / nullif(max("P95(transactions.ZSCORE(transactions.amount)|interval=P30D)") over () - min("P95(transactions.ZSCORE(transactions.amount)|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.P95(transactions.ZSCORE(transactions.amount)|interval=P30D))" , case when "P95(transactions.ZSCORE(transactions.amount)|interval=P7D)" is null then null else cast(floor(5 * ("P95(transactions.ZSCORE(transactions.amount)|interval=P7D)" - min("P95(transactions.ZSCORE(transactions.amount)|interval=P7D)") over ()) / nullif(max("P95(transactions.ZSCORE(transactions.amount)|interval=P7D)") over () - min("P95(transactions.ZSCORE(transactions.amount)|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.P95(transactions.ZSCORE(transactions.amount)|interval=P7D))" , case when "P95(transactions.amount)" is null then null else cast(floor(5 * ("P95(transactions.amount)" - min("P95(transactions.amount)") over ()) / nullif(max("P95(transactions.amount)") over () - min("P95(transactions.amount)") over (), 0)) as integer) end as "BIN(accounts.P95(transactions.amount))" , case when "P95(transactions.amount|interval=P30D)" is null then null else cast(floor(5 * ("P95(transactions.amount|interval=P30D)" - min("P95(transactions.amount|interval=P30D)") over ()) / nullif(max("P95(transactions.amount|interval=P30D)") over () - min("P95(transactions.amount|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.P95(transactions.amount|interval=P30D))" , case when "P95(transactions.amount|interval=P7D)" is null then null else cast(floor(5 * ("P95(transactions.amount|interval=P7D)" - min("P95(transactions.amount|interval=P7D)") over ()) / nullif(max("P95(transactions.amount|interval=P7D)") over () - min("P95(transactions.amount|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.P95(transactions.amount|interval=P7D))" , case when "RANGE(transactions.BIN(transactions.amount))" is null then null else cast(floor(5 * ("RANGE(transactions.BIN(transactions.amount))" - min("RANGE(transactions.BIN(transactions.amount))") over ()) / nullif(max("RANGE(transactions.BIN(transactions.amount))") over () - min("RANGE(transactions.BIN(transactions.amount))") over (), 0)) as integer) end as "BIN(accounts.RANGE(transactions.BIN(transactions.amount)))" , case when "RANGE(transactions.BIN(transactions.amount)|interval=P30D)" is null then null else cast(floor(5 * ("RANGE(transactions.BIN(transactions.amount)|interval=P30D)" - min("RANGE(transactions.BIN(transactions.amount)|interval=P30D)") over ()) / nullif(max("RANGE(transactions.BIN(transactions.amount)|interval=P30D)") over () - min("RANGE(transactions.BIN(transactions.amount)|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.RANGE(transactions.BIN(transactions.amount)|interval=P30D))" , case when "RANGE(transactions.BIN(transactions.amount)|interval=P7D)" is null then null else cast(floor(5 * ("RANGE(transactions.BIN(transactions.amount)|interval=P7D)" - min("RANGE(transactions.BIN(transactions.amount)|interval=P7D)") over ()) / nullif(max("RANGE(transactions.BIN(transactions.amount)|interval=P7D)") over () - min("RANGE(transactions.BIN(transactions.amount)|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.RANGE(transactions.BIN(transactions.amount)|interval=P7D))" , case when "RANGE(transactions.LOG1P(transactions.amount))" is null then null else cast(floor(5 * ("RANGE(transactions.LOG1P(transactions.amount))" - min("RANGE(transactions.LOG1P(transactions.amount))") over ()) / nullif(max("RANGE(transactions.LOG1P(transactions.amount))") over () - min("RANGE(transactions.LOG1P(transactions.amount))") over (), 0)) as integer) end as "BIN(accounts.RANGE(transactions.LOG1P(transactions.amount)))" , case when "RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)" is null then null else cast(floor(5 * ("RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)" - min("RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)") over ()) / nullif(max("RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)") over () - min("RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.RANGE(transactions.LOG1P(transactions.amount)|interval=P30D))" , case when "RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)" is null then null else cast(floor(5 * ("RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)" - min("RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)") over ()) / nullif(max("RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)") over () - min("RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.RANGE(transactions.LOG1P(transactions.amount)|interval=P7D))" , case when "RANGE(transactions.ZSCORE(transactions.amount))" is null then null else cast(floor(5 * ("RANGE(transactions.ZSCORE(transactions.amount))" - min("RANGE(transactions.ZSCORE(transactions.amount))") over ()) / nullif(max("RANGE(transactions.ZSCORE(transactions.amount))") over () - min("RANGE(transactions.ZSCORE(transactions.amount))") over (), 0)) as integer) end as "BIN(accounts.RANGE(transactions.ZSCORE(transactions.amount)))" , case when "RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)" is null then null else cast(floor(5 * ("RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)" - min("RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)") over ()) / nullif(max("RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)") over () - min("RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D))" , case when "RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)" is null then null else cast(floor(5 * ("RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)" - min("RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)") over ()) / nullif(max("RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)") over () - min("RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D))" , case when "RANGE(transactions.amount)" is null then null else cast(floor(5 * ("RANGE(transactions.amount)" - min("RANGE(transactions.amount)") over ()) / nullif(max("RANGE(transactions.amount)") over () - min("RANGE(transactions.amount)") over (), 0)) as integer) end as "BIN(accounts.RANGE(transactions.amount))" , case when "RANGE(transactions.amount|interval=P30D)" is null then null else cast(floor(5 * ("RANGE(transactions.amount|interval=P30D)" - min("RANGE(transactions.amount|interval=P30D)") over ()) / nullif(max("RANGE(transactions.amount|interval=P30D)") over () - min("RANGE(transactions.amount|interval=P30D)") over (), 0)) as integer) end as "BIN(accounts.RANGE(transactions.amount|interval=P30D))" , case when "RANGE(transactions.amount|interval=P7D)" is null then null else cast(floor(5 * ("RANGE(transactions.amount|interval=P7D)" - min("RANGE(transactions.amount|interval=P7D)") over ()) / nullif(max("RANGE(transactions.amount|interval=P7D)") over () - min("RANGE(transactions.amount|interval=P7D)") over (), 0)) as integer) end as "BIN(accounts.RANGE(transactions.amount|interval=P7D))" , case when credit_limit is null then null else cast(floor(5 * (credit_limit - min(credit_limit) over ()) / nullif(max(credit_limit) over () - min(credit_limit) over (), 0)) as integer) end as "BIN(accounts.credit_limit)" , "COUNT(transactions.transaction_date)" as "COUNT(transactions.transaction_date)", "COUNT(transactions.transaction_date|interval=P30D)" as "COUNT(transactions.transaction_date|interval=P30D)", "COUNT(transactions.transaction_date|interval=P7D)" as "COUNT(transactions.transaction_date|interval=P7D)", "COUNT(transactions.transaction_id)" as "COUNT(transactions.transaction_id)", "COUNT(transactions.transaction_id|interval=P30D)" as "COUNT(transactions.transaction_id|interval=P30D)", "COUNT(transactions.transaction_id|interval=P7D)" as "COUNT(transactions.transaction_id|interval=P7D)", "COUNT(transactions.transaction_type)" as "COUNT(transactions.transaction_type)", "COUNT(transactions.transaction_type|interval=P30D)" as "COUNT(transactions.transaction_type|interval=P30D)", "COUNT(transactions.transaction_type|interval=P7D)" as "COUNT(transactions.transaction_type|interval=P7D)", case when "COUNT(transactions.transaction_date)" + 1 > 0 then ln("COUNT(transactions.transaction_date)" + 1) end as "LOG1P(accounts.COUNT(transactions.transaction_date))" , case when "COUNT(transactions.transaction_date|interval=P30D)" + 1 > 0 then ln("COUNT(transactions.transaction_date|interval=P30D)" + 1) end as "LOG1P(accounts.COUNT(transactions.transaction_date|interval=P30D))" , case when "COUNT(transactions.transaction_date|interval=P7D)" + 1 > 0 then ln("COUNT(transactions.transaction_date|interval=P7D)" + 1) end as "LOG1P(accounts.COUNT(transactions.transaction_date|interval=P7D))" , case when "COUNT(transactions.transaction_id)" + 1 > 0 then ln("COUNT(transactions.transaction_id)" + 1) end as "LOG1P(accounts.COUNT(transactions.transaction_id))" , case when "COUNT(transactions.transaction_id|interval=P30D)" + 1 > 0 then ln("COUNT(transactions.transaction_id|interval=P30D)" + 1) end as "LOG1P(accounts.COUNT(transactions.transaction_id|interval=P30D))" , case when "COUNT(transactions.transaction_id|interval=P7D)" + 1 > 0 then ln("COUNT(transactions.transaction_id|interval=P7D)" + 1) end as "LOG1P(accounts.COUNT(transactions.transaction_id|interval=P7D))" , case when "COUNT(transactions.transaction_type)" + 1 > 0 then ln("COUNT(transactions.transaction_type)" + 1) end as "LOG1P(accounts.COUNT(transactions.transaction_type))" , case when "COUNT(transactions.transaction_type|interval=P30D)" + 1 > 0 then ln("COUNT(transactions.transaction_type|interval=P30D)" + 1) end as "LOG1P(accounts.COUNT(transactions.transaction_type|interval=P30D))" , case when "COUNT(transactions.transaction_type|interval=P7D)" + 1 > 0 then ln("COUNT(transactions.transaction_type|interval=P7D)" + 1) end as "LOG1P(accounts.COUNT(transactions.transaction_type|interval=P7D))" , case when "MEAN(transactions.BIN(transactions.amount))" + 1 > 0 then ln("MEAN(transactions.BIN(transactions.amount))" + 1) end as "LOG1P(accounts.MEAN(transactions.BIN(transactions.amount)))" , case when "MEAN(transactions.BIN(transactions.amount)|interval=P30D)" + 1 > 0 then ln("MEAN(transactions.BIN(transactions.amount)|interval=P30D)" + 1) end as "LOG1P(accounts.MEAN(transactions.BIN(transactions.amount)|interval=P30D))" , case when "MEAN(transactions.BIN(transactions.amount)|interval=P7D)" + 1 > 0 then ln("MEAN(transactions.BIN(transactions.amount)|interval=P7D)" + 1) end as "LOG1P(accounts.MEAN(transactions.BIN(transactions.amount)|interval=P7D))" , case when "MEAN(transactions.LOG1P(transactions.amount))" + 1 > 0 then ln("MEAN(transactions.LOG1P(transactions.amount))" + 1) end as "LOG1P(accounts.MEAN(transactions.LOG1P(transactions.amount)))" , case when "MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)" + 1 > 0 then ln("MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)" + 1) end as "LOG1P(accounts.MEAN(transactions.LOG1P(transactions.amount)|interval=P30D))" , case when "MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)" + 1 > 0 then ln("MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)" + 1) end as "LOG1P(accounts.MEAN(transactions.LOG1P(transactions.amount)|interval=P7D))" , case when "MEAN(transactions.ZSCORE(transactions.amount))" + 1 > 0 then ln("MEAN(transactions.ZSCORE(transactions.amount))" + 1) end as "LOG1P(accounts.MEAN(transactions.ZSCORE(transactions.amount)))" , case when "MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)" + 1 > 0 then ln("MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)" + 1) end as "LOG1P(accounts.MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D))" , case when "MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)" + 1 > 0 then ln("MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)" + 1) end as "LOG1P(accounts.MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D))" , case when "MEAN(transactions.amount)" + 1 > 0 then ln("MEAN(transactions.amount)" + 1) end as "LOG1P(accounts.MEAN(transactions.amount))" , case when "MEAN(transactions.amount|interval=P30D)" + 1 > 0 then ln("MEAN(transactions.amount|interval=P30D)" + 1) end as "LOG1P(accounts.MEAN(transactions.amount|interval=P30D))" , case when "MEAN(transactions.amount|interval=P7D)" + 1 > 0 then ln("MEAN(transactions.amount|interval=P7D)" + 1) end as "LOG1P(accounts.MEAN(transactions.amount|interval=P7D))" , case when "P95(transactions.BIN(transactions.amount))" + 1 > 0 then ln("P95(transactions.BIN(transactions.amount))" + 1) end as "LOG1P(accounts.P95(transactions.BIN(transactions.amount)))" , case when "P95(transactions.BIN(transactions.amount)|interval=P30D)" + 1 > 0 then ln("P95(transactions.BIN(transactions.amount)|interval=P30D)" + 1) end as "LOG1P(accounts.P95(transactions.BIN(transactions.amount)|interval=P30D))" , case when "P95(transactions.BIN(transactions.amount)|interval=P7D)" + 1 > 0 then ln("P95(transactions.BIN(transactions.amount)|interval=P7D)" + 1) end as "LOG1P(accounts.P95(transactions.BIN(transactions.amount)|interval=P7D))" , case when "P95(transactions.LOG1P(transactions.amount))" + 1 > 0 then ln("P95(transactions.LOG1P(transactions.amount))" + 1) end as "LOG1P(accounts.P95(transactions.LOG1P(transactions.amount)))" , case when "P95(transactions.LOG1P(transactions.amount)|interval=P30D)" + 1 > 0 then ln("P95(transactions.LOG1P(transactions.amount)|interval=P30D)" + 1) end as "LOG1P(accounts.P95(transactions.LOG1P(transactions.amount)|interval=P30D))" , case when "P95(transactions.LOG1P(transactions.amount)|interval=P7D)" + 1 > 0 then ln("P95(transactions.LOG1P(transactions.amount)|interval=P7D)" + 1) end as "LOG1P(accounts.P95(transactions.LOG1P(transactions.amount)|interval=P7D))" , case when "P95(transactions.ZSCORE(transactions.amount))" + 1 > 0 then ln("P95(transactions.ZSCORE(transactions.amount))" + 1) end as "LOG1P(accounts.P95(transactions.ZSCORE(transactions.amount)))" , case when "P95(transactions.ZSCORE(transactions.amount)|interval=P30D)" + 1 > 0 then ln("P95(transactions.ZSCORE(transactions.amount)|interval=P30D)" + 1) end as "LOG1P(accounts.P95(transactions.ZSCORE(transactions.amount)|interval=P30D))" , case when "P95(transactions.ZSCORE(transactions.amount)|interval=P7D)" + 1 > 0 then ln("P95(transactions.ZSCORE(transactions.amount)|interval=P7D)" + 1) end as "LOG1P(accounts.P95(transactions.ZSCORE(transactions.amount)|interval=P7D))" , case when "P95(transactions.amount)" + 1 > 0 then ln("P95(transactions.amount)" + 1) end as "LOG1P(accounts.P95(transactions.amount))" , case when "P95(transactions.amount|interval=P30D)" + 1 > 0 then ln("P95(transactions.amount|interval=P30D)" + 1) end as "LOG1P(accounts.P95(transactions.amount|interval=P30D))" , case when "P95(transactions.amount|interval=P7D)" + 1 > 0 then ln("P95(transactions.amount|interval=P7D)" + 1) end as "LOG1P(accounts.P95(transactions.amount|interval=P7D))" , case when "RANGE(transactions.BIN(transactions.amount))" + 1 > 0 then ln("RANGE(transactions.BIN(transactions.amount))" + 1) end as "LOG1P(accounts.RANGE(transactions.BIN(transactions.amount)))" , case when "RANGE(transactions.BIN(transactions.amount)|interval=P30D)" + 1 > 0 then ln("RANGE(transactions.BIN(transactions.amount)|interval=P30D)" + 1) end as "LOG1P(accounts.RANGE(transactions.BIN(transactions.amount)|interval=P30D))" , case when "RANGE(transactions.BIN(transactions.amount)|interval=P7D)" + 1 > 0 then ln("RANGE(transactions.BIN(transactions.amount)|interval=P7D)" + 1) end as "LOG1P(accounts.RANGE(transactions.BIN(transactions.amount)|interval=P7D))" , case when "RANGE(transactions.LOG1P(transactions.amount))" + 1 > 0 then ln("RANGE(transactions.LOG1P(transactions.amount))" + 1) end as "LOG1P(accounts.RANGE(transactions.LOG1P(transactions.amount)))" , case when "RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)" + 1 > 0 then ln("RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)" + 1) end as "LOG1P(accounts.RANGE(transactions.LOG1P(transactions.amount)|interval=P30D))" , case when "RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)" + 1 > 0 then ln("RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)" + 1) end as "LOG1P(accounts.RANGE(transactions.LOG1P(transactions.amount)|interval=P7D))" , case when "RANGE(transactions.ZSCORE(transactions.amount))" + 1 > 0 then ln("RANGE(transactions.ZSCORE(transactions.amount))" + 1) end as "LOG1P(accounts.RANGE(transactions.ZSCORE(transactions.amount)))" , case when "RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)" + 1 > 0 then ln("RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)" + 1) end as "LOG1P(accounts.RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D))" , case when "RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)" + 1 > 0 then ln("RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)" + 1) end as "LOG1P(accounts.RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D))" , case when "RANGE(transactions.amount)" + 1 > 0 then ln("RANGE(transactions.amount)" + 1) end as "LOG1P(accounts.RANGE(transactions.amount))" , case when "RANGE(transactions.amount|interval=P30D)" + 1 > 0 then ln("RANGE(transactions.amount|interval=P30D)" + 1) end as "LOG1P(accounts.RANGE(transactions.amount|interval=P30D))" , case when "RANGE(transactions.amount|interval=P7D)" + 1 > 0 then ln("RANGE(transactions.amount|interval=P7D)" + 1) end as "LOG1P(accounts.RANGE(transactions.amount|interval=P7D))" , case when credit_limit + 1 > 0 then ln(credit_limit + 1) end as "LOG1P(accounts.credit_limit)" , "MEAN(transactions.BIN(transactions.amount))" as "MEAN(transactions.BIN(transactions.amount))", "MEAN(transactions.BIN(transactions.amount)|interval=P30D)" as "MEAN(transactions.BIN(transactions.amount)|interval=P30D)", "MEAN(transactions.BIN(transactions.amount)|interval=P7D)" as "MEAN(transactions.BIN(transactions.amount)|interval=P7D)", "MEAN(transactions.LOG1P(transactions.amount))" as "MEAN(transactions.LOG1P(transactions.amount))", "MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)" as "MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)", "MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)" as "MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)", "MEAN(transactions.ZSCORE(transactions.amount))" as "MEAN(transactions.ZSCORE(transactions.amount))", "MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)" as "MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)", "MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)" as "MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)", "MEAN(transactions.amount)" as "MEAN(transactions.amount)", "MEAN(transactions.amount|interval=P30D)" as "MEAN(transactions.amount|interval=P30D)", "MEAN(transactions.amount|interval=P7D)" as "MEAN(transactions.amount|interval=P7D)", "P95(transactions.BIN(transactions.amount))" as "P95(transactions.BIN(transactions.amount))", "P95(transactions.BIN(transactions.amount)|interval=P30D)" as "P95(transactions.BIN(transactions.amount)|interval=P30D)", "P95(transactions.BIN(transactions.amount)|interval=P7D)" as "P95(transactions.BIN(transactions.amount)|interval=P7D)", "P95(transactions.LOG1P(transactions.amount))" as "P95(transactions.LOG1P(transactions.amount))", "P95(transactions.LOG1P(transactions.amount)|interval=P30D)" as "P95(transactions.LOG1P(transactions.amount)|interval=P30D)", "P95(transactions.LOG1P(transactions.amount)|interval=P7D)" as "P95(transactions.LOG1P(transactions.amount)|interval=P7D)", "P95(transactions.ZSCORE(transactions.amount))" as "P95(transactions.ZSCORE(transactions.amount))", "P95(transactions.ZSCORE(transactions.amount)|interval=P30D)" as "P95(transactions.ZSCORE(transactions.amount)|interval=P30D)", "P95(transactions.ZSCORE(transactions.amount)|interval=P7D)" as "P95(transactions.ZSCORE(transactions.amount)|interval=P7D)", "P95(transactions.amount)" as "P95(transactions.amount)", "P95(transactions.amount|interval=P30D)" as "P95(transactions.amount|interval=P30D)", "P95(transactions.amount|interval=P7D)" as "P95(transactions.amount|interval=P7D)", "RANGE(transactions.BIN(transactions.amount))" as "RANGE(transactions.BIN(transactions.amount))", "RANGE(transactions.BIN(transactions.amount)|interval=P30D)" as "RANGE(transactions.BIN(transactions.amount)|interval=P30D)", "RANGE(transactions.BIN(transactions.amount)|interval=P7D)" as "RANGE(transactions.BIN(transactions.amount)|interval=P7D)", "RANGE(transactions.LOG1P(transactions.amount))" as "RANGE(transactions.LOG1P(transactions.amount))", "RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)" as "RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)", "RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)" as "RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)", "RANGE(transactions.ZSCORE(transactions.amount))" as "RANGE(transactions.ZSCORE(transactions.amount))", "RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)" as "RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)", "RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)" as "RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)", "RANGE(transactions.amount)" as "RANGE(transactions.amount)", "RANGE(transactions.amount|interval=P30D)" as "RANGE(transactions.amount|interval=P30D)", "RANGE(transactions.amount|interval=P7D)" as "RANGE(transactions.amount|interval=P7D)", (("COUNT(transactions.transaction_date)" - avg("COUNT(transactions.transaction_date)") over ()) / nullif(stddev("COUNT(transactions.transaction_date)") over (), 0)) as "ZSCORE(accounts.COUNT(transactions.transaction_date))" , (("COUNT(transactions.transaction_date|interval=P30D)" - avg("COUNT(transactions.transaction_date|interval=P30D)") over ()) / nullif(stddev("COUNT(transactions.transaction_date|interval=P30D)") over (), 0)) as "ZSCORE(accounts.COUNT(transactions.transaction_date|interval=P30D))" , (("COUNT(transactions.transaction_date|interval=P7D)" - avg("COUNT(transactions.transaction_date|interval=P7D)") over ()) / nullif(stddev("COUNT(transactions.transaction_date|interval=P7D)") over (), 0)) as "ZSCORE(accounts.COUNT(transactions.transaction_date|interval=P7D))" , (("COUNT(transactions.transaction_id)" - avg("COUNT(transactions.transaction_id)") over ()) / nullif(stddev("COUNT(transactions.transaction_id)") over (), 0)) as "ZSCORE(accounts.COUNT(transactions.transaction_id))" , (("COUNT(transactions.transaction_id|interval=P30D)" - avg("COUNT(transactions.transaction_id|interval=P30D)") over ()) / nullif(stddev("COUNT(transactions.transaction_id|interval=P30D)") over (), 0)) as "ZSCORE(accounts.COUNT(transactions.transaction_id|interval=P30D))" , (("COUNT(transactions.transaction_id|interval=P7D)" - avg("COUNT(transactions.transaction_id|interval=P7D)") over ()) / nullif(stddev("COUNT(transactions.transaction_id|interval=P7D)") over (), 0)) as "ZSCORE(accounts.COUNT(transactions.transaction_id|interval=P7D))" , (("COUNT(transactions.transaction_type)" - avg("COUNT(transactions.transaction_type)") over ()) / nullif(stddev("COUNT(transactions.transaction_type)") over (), 0)) as "ZSCORE(accounts.COUNT(transactions.transaction_type))" , (("COUNT(transactions.transaction_type|interval=P30D)" - avg("COUNT(transactions.transaction_type|interval=P30D)") over ()) / nullif(stddev("COUNT(transactions.transaction_type|interval=P30D)") over (), 0)) as "ZSCORE(accounts.COUNT(transactions.transaction_type|interval=P30D))" , (("COUNT(transactions.transaction_type|interval=P7D)" - avg("COUNT(transactions.transaction_type|interval=P7D)") over ()) / nullif(stddev("COUNT(transactions.transaction_type|interval=P7D)") over (), 0)) as "ZSCORE(accounts.COUNT(transactions.transaction_type|interval=P7D))" , (("MEAN(transactions.BIN(transactions.amount))" - avg("MEAN(transactions.BIN(transactions.amount))") over ()) / nullif(stddev("MEAN(transactions.BIN(transactions.amount))") over (), 0)) as "ZSCORE(accounts.MEAN(transactions.BIN(transactions.amount)))" , (("MEAN(transactions.BIN(transactions.amount)|interval=P30D)" - avg("MEAN(transactions.BIN(transactions.amount)|interval=P30D)") over ()) / nullif(stddev("MEAN(transactions.BIN(transactions.amount)|interval=P30D)") over (), 0)) as "ZSCORE(accounts.MEAN(transactions.BIN(transactions.amount)|interval=P30D))" , (("MEAN(transactions.BIN(transactions.amount)|interval=P7D)" - avg("MEAN(transactions.BIN(transactions.amount)|interval=P7D)") over ()) / nullif(stddev("MEAN(transactions.BIN(transactions.amount)|interval=P7D)") over (), 0)) as "ZSCORE(accounts.MEAN(transactions.BIN(transactions.amount)|interval=P7D))" , (("MEAN(transactions.LOG1P(transactions.amount))" - avg("MEAN(transactions.LOG1P(transactions.amount))") over ()) / nullif(stddev("MEAN(transactions.LOG1P(transactions.amount))") over (), 0)) as "ZSCORE(accounts.MEAN(transactions.LOG1P(transactions.amount)))" , (("MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)" - avg("MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)") over ()) / nullif(stddev("MEAN(transactions.LOG1P(transactions.amount)|interval=P30D)") over (), 0)) as "ZSCORE(accounts.MEAN(transactions.LOG1P(transactions.amount)|interval=P30D))" , (("MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)" - avg("MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)") over ()) / nullif(stddev("MEAN(transactions.LOG1P(transactions.amount)|interval=P7D)") over (), 0)) as "ZSCORE(accounts.MEAN(transactions.LOG1P(transactions.amount)|interval=P7D))" , (("MEAN(transactions.ZSCORE(transactions.amount))" - avg("MEAN(transactions.ZSCORE(transactions.amount))") over ()) / nullif(stddev("MEAN(transactions.ZSCORE(transactions.amount))") over (), 0)) as "ZSCORE(accounts.MEAN(transactions.ZSCORE(transactions.amount)))" , (("MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)" - avg("MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)") over ()) / nullif(stddev("MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D)") over (), 0)) as "ZSCORE(accounts.MEAN(transactions.ZSCORE(transactions.amount)|interval=P30D))" , (("MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)" - avg("MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)") over ()) / nullif(stddev("MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D)") over (), 0)) as "ZSCORE(accounts.MEAN(transactions.ZSCORE(transactions.amount)|interval=P7D))" , (("MEAN(transactions.amount)" - avg("MEAN(transactions.amount)") over ()) / nullif(stddev("MEAN(transactions.amount)") over (), 0)) as "ZSCORE(accounts.MEAN(transactions.amount))" , (("MEAN(transactions.amount|interval=P30D)" - avg("MEAN(transactions.amount|interval=P30D)") over ()) / nullif(stddev("MEAN(transactions.amount|interval=P30D)") over (), 0)) as "ZSCORE(accounts.MEAN(transactions.amount|interval=P30D))" , (("MEAN(transactions.amount|interval=P7D)" - avg("MEAN(transactions.amount|interval=P7D)") over ()) / nullif(stddev("MEAN(transactions.amount|interval=P7D)") over (), 0)) as "ZSCORE(accounts.MEAN(transactions.amount|interval=P7D))" , (("P95(transactions.BIN(transactions.amount))" - avg("P95(transactions.BIN(transactions.amount))") over ()) / nullif(stddev("P95(transactions.BIN(transactions.amount))") over (), 0)) as "ZSCORE(accounts.P95(transactions.BIN(transactions.amount)))" , (("P95(transactions.BIN(transactions.amount)|interval=P30D)" - avg("P95(transactions.BIN(transactions.amount)|interval=P30D)") over ()) / nullif(stddev("P95(transactions.BIN(transactions.amount)|interval=P30D)") over (), 0)) as "ZSCORE(accounts.P95(transactions.BIN(transactions.amount)|interval=P30D))" , (("P95(transactions.BIN(transactions.amount)|interval=P7D)" - avg("P95(transactions.BIN(transactions.amount)|interval=P7D)") over ()) / nullif(stddev("P95(transactions.BIN(transactions.amount)|interval=P7D)") over (), 0)) as "ZSCORE(accounts.P95(transactions.BIN(transactions.amount)|interval=P7D))" , (("P95(transactions.LOG1P(transactions.amount))" - avg("P95(transactions.LOG1P(transactions.amount))") over ()) / nullif(stddev("P95(transactions.LOG1P(transactions.amount))") over (), 0)) as "ZSCORE(accounts.P95(transactions.LOG1P(transactions.amount)))" , (("P95(transactions.LOG1P(transactions.amount)|interval=P30D)" - avg("P95(transactions.LOG1P(transactions.amount)|interval=P30D)") over ()) / nullif(stddev("P95(transactions.LOG1P(transactions.amount)|interval=P30D)") over (), 0)) as "ZSCORE(accounts.P95(transactions.LOG1P(transactions.amount)|interval=P30D))" , (("P95(transactions.LOG1P(transactions.amount)|interval=P7D)" - avg("P95(transactions.LOG1P(transactions.amount)|interval=P7D)") over ()) / nullif(stddev("P95(transactions.LOG1P(transactions.amount)|interval=P7D)") over (), 0)) as "ZSCORE(accounts.P95(transactions.LOG1P(transactions.amount)|interval=P7D))" , (("P95(transactions.ZSCORE(transactions.amount))" - avg("P95(transactions.ZSCORE(transactions.amount))") over ()) / nullif(stddev("P95(transactions.ZSCORE(transactions.amount))") over (), 0)) as "ZSCORE(accounts.P95(transactions.ZSCORE(transactions.amount)))" , (("P95(transactions.ZSCORE(transactions.amount)|interval=P30D)" - avg("P95(transactions.ZSCORE(transactions.amount)|interval=P30D)") over ()) / nullif(stddev("P95(transactions.ZSCORE(transactions.amount)|interval=P30D)") over (), 0)) as "ZSCORE(accounts.P95(transactions.ZSCORE(transactions.amount)|interval=P30D))" , (("P95(transactions.ZSCORE(transactions.amount)|interval=P7D)" - avg("P95(transactions.ZSCORE(transactions.amount)|interval=P7D)") over ()) / nullif(stddev("P95(transactions.ZSCORE(transactions.amount)|interval=P7D)") over (), 0)) as "ZSCORE(accounts.P95(transactions.ZSCORE(transactions.amount)|interval=P7D))" , (("P95(transactions.amount)" - avg("P95(transactions.amount)") over ()) / nullif(stddev("P95(transactions.amount)") over (), 0)) as "ZSCORE(accounts.P95(transactions.amount))" , (("P95(transactions.amount|interval=P30D)" - avg("P95(transactions.amount|interval=P30D)") over ()) / nullif(stddev("P95(transactions.amount|interval=P30D)") over (), 0)) as "ZSCORE(accounts.P95(transactions.amount|interval=P30D))" , (("P95(transactions.amount|interval=P7D)" - avg("P95(transactions.amount|interval=P7D)") over ()) / nullif(stddev("P95(transactions.amount|interval=P7D)") over (), 0)) as "ZSCORE(accounts.P95(transactions.amount|interval=P7D))" , (("RANGE(transactions.BIN(transactions.amount))" - avg("RANGE(transactions.BIN(transactions.amount))") over ()) / nullif(stddev("RANGE(transactions.BIN(transactions.amount))") over (), 0)) as "ZSCORE(accounts.RANGE(transactions.BIN(transactions.amount)))" , (("RANGE(transactions.BIN(transactions.amount)|interval=P30D)" - avg("RANGE(transactions.BIN(transactions.amount)|interval=P30D)") over ()) / nullif(stddev("RANGE(transactions.BIN(transactions.amount)|interval=P30D)") over (), 0)) as "ZSCORE(accounts.RANGE(transactions.BIN(transactions.amount)|interval=P30D))" , (("RANGE(transactions.BIN(transactions.amount)|interval=P7D)" - avg("RANGE(transactions.BIN(transactions.amount)|interval=P7D)") over ()) / nullif(stddev("RANGE(transactions.BIN(transactions.amount)|interval=P7D)") over (), 0)) as "ZSCORE(accounts.RANGE(transactions.BIN(transactions.amount)|interval=P7D))" , (("RANGE(transactions.LOG1P(transactions.amount))" - avg("RANGE(transactions.LOG1P(transactions.amount))") over ()) / nullif(stddev("RANGE(transactions.LOG1P(transactions.amount))") over (), 0)) as "ZSCORE(accounts.RANGE(transactions.LOG1P(transactions.amount)))" , (("RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)" - avg("RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)") over ()) / nullif(stddev("RANGE(transactions.LOG1P(transactions.amount)|interval=P30D)") over (), 0)) as "ZSCORE(accounts.RANGE(transactions.LOG1P(transactions.amount)|interval=P30D))" , (("RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)" - avg("RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)") over ()) / nullif(stddev("RANGE(transactions.LOG1P(transactions.amount)|interval=P7D)") over (), 0)) as "ZSCORE(accounts.RANGE(transactions.LOG1P(transactions.amount)|interval=P7D))" , (("RANGE(transactions.ZSCORE(transactions.amount))" - avg("RANGE(transactions.ZSCORE(transactions.amount))") over ()) / nullif(stddev("RANGE(transactions.ZSCORE(transactions.amount))") over (), 0)) as "ZSCORE(accounts.RANGE(transactions.ZSCORE(transactions.amount)))" , (("RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)" - avg("RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)") over ()) / nullif(stddev("RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D)") over (), 0)) as "ZSCORE(accounts.RANGE(transactions.ZSCORE(transactions.amount)|interval=P30D))" , (("RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)" - avg("RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)") over ()) / nullif(stddev("RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D)") over (), 0)) as "ZSCORE(accounts.RANGE(transactions.ZSCORE(transactions.amount)|interval=P7D))" , (("RANGE(transactions.amount)" - avg("RANGE(transactions.amount)") over ()) / nullif(stddev("RANGE(transactions.amount)") over (), 0)) as "ZSCORE(accounts.RANGE(transactions.amount))" , (("RANGE(transactions.amount|interval=P30D)" - avg("RANGE(transactions.amount|interval=P30D)") over ()) / nullif(stddev("RANGE(transactions.amount|interval=P30D)") over (), 0)) as "ZSCORE(accounts.RANGE(transactions.amount|interval=P30D))" , (("RANGE(transactions.amount|interval=P7D)" - avg("RANGE(transactions.amount|interval=P7D)") over ()) / nullif(stddev("RANGE(transactions.amount|interval=P7D)") over (), 0)) as "ZSCORE(accounts.RANGE(transactions.amount|interval=P7D))" , ((credit_limit - avg(credit_limit) over ()) / nullif(stddev(credit_limit) over (), 0)) as "ZSCORE(accounts.credit_limit)" , account_type as account_type, credit_limit as credit_limit
from accounts_synth _ego
)
select * from accounts_transform
) as t
order by aod.as_of_date
================================================================================

In this tutorial, we learned:

  1. Check built-ins first: Featurizer now ships with 43 aggregations and 71 transformations covering most common needs
  2. Registration pattern: How to create custom primitives when built-ins aren’t enough
  3. Implementation: Subclass, implement to_sql(), register, use
  4. Best practices: Always return new Feature instances from transformers
  • Domain-specific metrics (e.g., financial ratios, medical scores)
  • Complex SQL expressions not covered by built-ins
  • Organization-specific feature definitions
print("Custom Primitives Summary")
print("=" * 40)
print(f"Built-in aggregations: {len(aggs)}")
print(f"Built-in transformations: {len(transforms)}")
print(f"Custom aggregations: 3 (median, p95, range)")
print(f"Custom transformations: 3 (log, zscore, bin)")
print(f"Total features generated: {len(target_features)}")
Custom Primitives Summary
========================================
Built-in aggregations: 69
Built-in transformations: 83
Custom aggregations: 3 (median, p95, range)
Custom transformations: 3 (log, zscore, bin)
Total features generated: 187