You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Generated by src/feature_engineering.py · generate_features(df) appends all columns below to the input DataFrame.
Observation period: 90 days (observation_start_date → observation_end_date) Target: churn (1 = no live bet in the 30-day outcome window) — excluded from all features Leakage exclusions: live_bets_count_outcome_window, observation_start_date, observation_end_date
Base feature columns
Columns from the raw dataset that are safe model inputs (BASE_FEATURE_COLUMNS).
Column
Type
Description
tenure_days
int
Days from registration to observation end
live_bets_count
int
Number of live bets in the observation period
avg_bet_amount
float
Average stake per bet (EUR); NaN when no bets
total_turnover
float
Total live turnover (EUR)
total_payout
float
Total payout (EUR)
ggr
float
Gross gaming revenue = turnover − payout (can be negative)
ggr_margin
float
GGR / turnover; NaN when turnover = 0
deposit_count
int
Number of deposits
total_deposit_amount
float
Total deposited (EUR)
deposit_to_turnover_ratio
float
total_deposit_amount / total_turnover; NaN when turnover = 0
days_active_in_observation
int
Days with ≥ 1 bet in the observation period
bet_day_rate
float
days_active_in_observation / 90
days_since_last_bet
int
Days from last bet to observation end; NaN when no bets
Extra features (ExtraFeatures enum)
66 engineered features across 9 categories. All are in ALL_FEATURE_COLUMNS alongside the base columns above.
Log1p transforms
Applied to right-skewed count and amount columns. For no-bet players avg_bet_amount is filled with 0 before transformation; days_since_last_bet is filled with 90 (max staleness).
Feature
Formula
live_bets_count_log1p
log1p(live_bets_count)
avg_bet_amount_log1p
log1p(avg_bet_amount.fillna(0))
total_turnover_log1p
log1p(total_turnover)
total_payout_log1p
log1p(total_payout)
total_deposit_amount_log1p
log1p(total_deposit_amount)
avg_deposit_amount_log1p
log1p(total_deposit_amount / deposit_count)
deposit_count_log1p
log1p(deposit_count)
tenure_days_log1p
log1p(tenure_days)
days_since_last_bet_log1p
log1p(days_since_last_bet.fillna(90))
days_active_in_observation_log1p
log1p(days_active_in_observation)
days_to_first_bet_in_obs_log1p
log1p(days_to_first_bet_in_obs)
Sqrt transforms
Lighter compression than log1p; useful as an alternative encoding for tree ensembles.
Feature
Formula
live_bets_count_sqrt
sqrt(live_bets_count)
total_turnover_sqrt
sqrt(total_turnover)
avg_bet_amount_sqrt
sqrt(avg_bet_amount.fillna(0))
GGR transforms
GGR can be negative (player winning). Signed transforms preserve direction.
Feature
Formula
Interpretation
ggr_abs_log1p
log1p(|ggr|)
Magnitude of GGR (log-scaled)
ggr_signed_log1p
sign(ggr) × log1p(|ggr|)
Signed log; preserves positive/negative
ggr_positive
max(ggr, 0)
House-winning portion only
ggr_negative_abs
max(−ggr, 0)
Player-winning magnitude
Recency features
Key churn signal. No-bet players are treated as maximally stale (filled with 90).
Feature
Formula
Interpretation
days_since_last_bet_rate
days_since_last_bet / 90
Normalised staleness [0, 1]
last_bet_in_last_7_days
days_since_last_bet ≤ 7 → 1
Binary: very recent activity
last_bet_in_last_14_days
days_since_last_bet ≤ 14 → 1
Binary: recent activity
last_bet_in_last_30_days
days_since_last_bet ≤ 30 → 1
Binary: active in last month
Frequency / rate features
Feature
Formula
Interpretation
bets_per_active_day
live_bets_count / days_active_in_observation
Betting intensity on active days
bets_per_observation_day
live_bets_count / 90
Average daily bet count over full period
bets_per_tenure_day
live_bets_count / tenure_days
Lifetime betting rate
turnover_per_active_day
total_turnover / days_active_in_observation
Avg daily spend on active days
turnover_per_observation_day
total_turnover / 90
Avg daily spend over full period
payout_per_observation_day
total_payout / 90
Avg daily payout over full period
Per-bet metrics
Feature
Formula
Interpretation
payout_per_bet
total_payout / live_bets_count
Average return per bet
ggr_per_bet
ggr / live_bets_count
Average house margin per bet
ggr_per_active_day
ggr / days_active_in_observation
Daily value to the house
payout_rate
total_payout / total_turnover
Complement of ggr_margin; ≈ 1 − ggr_margin
Activity span
Derived from the gap between first_live_bet_date and last_live_bet_date within the observation period.
How far into the period they started (no-bet → 90)
Deposit features
Feature
Formula
Interpretation
avg_deposit_amount
total_deposit_amount / deposit_count
Average deposit size
deposit_frequency_rate
deposit_count / 90
Deposits per day
deposit_per_bet
deposit_count / live_bets_count
Deposit frequency relative to betting activity
avg_deposit_to_avg_bet_ratio
avg_deposit / avg_bet
Whether deposits are large relative to stakes
has_deposited
deposit_count > 0 → 1
Binary flag
deposit_coverage_rate
total_payout / total_deposit_amount
> 1 means player is net winning vs. deposits
ggr_to_deposit_ratio
ggr / total_deposit_amount
House take relative to player's investment
turnover_per_deposit
total_turnover / total_deposit_amount
Wagering multiplier (inverse of deposit_to_turnover_ratio)
Tenure & date features
Feature
Formula
Interpretation
active_to_tenure_ratio
days_active_in_observation / tenure_days
Engagement relative to account age
is_new_player
tenure_days < 90 → 1
Player registered during the observation period
tenure_years
tenure_days / 365
Account age in years
days_to_first_bet
(first_live_bet_date − registration_date).days
Days from registration to first bet in obs period
registration_day_of_week
registration_date.dayofweek
0 = Monday … 6 = Sunday
registration_month
registration_date.month
1–12
registration_year
registration_date.year
Calendar year
last_bet_day_of_week
last_live_bet_date.dayofweek
−1 sentinel for no-bet players
last_bet_month
last_live_bet_date.month
−1 sentinel for no-bet players
first_bet_day_of_week
first_live_bet_date.dayofweek
−1 sentinel for no-bet players
first_bet_month
first_live_bet_date.month
−1 sentinel for no-bet players
Interaction features
Multiplicative cross-terms to expose non-linear relationships between volume, frequency, and recency.
Feature
Formula
Rationale
turnover_x_bet_day_rate
total_turnover × bet_day_rate
High-value + high-frequency players
ggr_margin_x_bet_day_rate
ggr_margin × bet_day_rate
House margin weighted by activity rate
days_since_last_bet_x_bet_day_rate
days_since_last_bet × bet_day_rate
Recent lapse vs. historically frequent player
tenure_x_bet_day_rate
tenure_days × bet_day_rate
Veteran × engagement level
avg_bet_amount_x_bet_day_rate
avg_bet_amount × bet_day_rate
High-stakes × frequency signal
turnover_x_ggr_margin
total_turnover × ggr_margin
Absolute GGR (alternative to raw ggr)
active_days_x_avg_bet_amount
days_active_in_observation × avg_bet_amount
Total potential value proxy
Net position features
Feature
Formula
Interpretation
net_loss
total_deposit_amount − total_payout
Player's net cash lost (positive = lost money)
net_loss_signed_log1p
sign(net_loss) × log1p(|net_loss|)
Signed log; preserves winning/losing direction
net_loss_rate
net_loss / total_deposit_amount
Net loss as fraction of total deposits
NaN handling
Situation
Affected base column
Handling in extra features
No bets (live_bets_count = 0)
avg_bet_amount, days_since_last_bet
avg_bet_amount filled with 0; days_since_last_bet filled with 90; per-bet/per-active-day features → NaN
No deposits (deposit_count = 0)
total_deposit_amount (may be 0 or NaN)
Deposit-ratio features → NaN
Zero turnover
ggr_margin, deposit_to_turnover_ratio
Propagated as NaN
No-bet players, date fields
first_live_bet_date, last_live_bet_date
Bet date features → −1 sentinel; days_to_first_bet_in_obs filled with 90
Downstream models should apply appropriate imputation (e.g. median imputation or model-native NaN handling such as XGBoost's built-in missing value treatment).