-- predict_glm() MVP stage 3: factor predictors (treatment contrasts), -- rebuilt from metadata.xlevels / contrasts without R. -- Reference values in scripts/parity_reference.R. CREATE TEMP TABLE t_train_factor ( y double precision, gender text ); INSERT INTO t_train_factor VALUES (1.0, 'F'), (2.0, 'M'), (1.5, 'F'), (2.5, 'M'), (3.0, 'Other'), (2.8, 'Other'); CREATE TEMP TABLE t_new_factor ( id integer, gender text ); -- Row 4: NULL factor must predict NULL. Row 5: level unseen at fit time. INSERT INTO t_new_factor VALUES (1, 'F'), (2, 'M'), (3, 'Other'), (4, NULL), (5, 'Unknown'); CREATE TEMP TABLE t_model_f AS SELECT * FROM fbsql.fit_glm( relation => $$ SELECT y, gender FROM t_train_factor $$, formula => 'y ~ gender', family => 'gaussian'); -- Default on_new_levels => 'error': the novel level aborts the prediction. SELECT id, gender, round(y_predicted::numeric, 4) AS y_predicted FROM fbsql.predict_glm( relation => $$ SELECT id, gender FROM t_new_factor $$, model => $$ SELECT * FROM t_model_f $$ ) AS p(id integer, gender text, y_predicted double precision) ORDER BY id; -- on_new_levels => 'na': only the novel-level row predicts NULL. SELECT id, gender, round(y_predicted::numeric, 4) AS y_predicted FROM fbsql.predict_glm( relation => $$ SELECT id, gender FROM t_new_factor $$, model => $$ SELECT * FROM t_model_f $$, on_new_levels => 'na' ) AS p(id integer, gender text, y_predicted double precision) ORDER BY id; -- Without the novel row the default policy predicts normally. SELECT id, gender, round(y_predicted::numeric, 4) AS y_predicted FROM fbsql.predict_glm( relation => $$ SELECT id, gender FROM t_new_factor WHERE id <= 4 $$, model => $$ SELECT * FROM t_model_f $$ ) AS p(id integer, gender text, y_predicted double precision) ORDER BY id; -- Invalid on_new_levels value. SELECT * FROM fbsql.predict_glm( relation => $$ SELECT id, gender FROM t_new_factor $$, model => $$ SELECT * FROM t_model_f $$, on_new_levels => 'ignore' ) AS p(id integer, gender text, y_predicted double precision); -- Mixed numeric + factor predictors (gaussian). CREATE TEMP TABLE t_train_mix ( y double precision, x1 double precision, gender text ); INSERT INTO t_train_mix VALUES (1.2, 0, 'F'), (2.3, 1, 'M'), (3.1, 2, 'Other'), (1.8, 3, 'F'), (2.9, 4, 'M'), (3.7, 5, 'Other'), (2.1, 6, 'F'), (3.3, 7, 'M'), (4.2, 8, 'Other'); CREATE TEMP TABLE t_new_mix ( id integer, x1 double precision, gender text ); INSERT INTO t_new_mix VALUES (1, 2.0, 'F'), (2, 5.0, 'Other'), (3, 3.0, NULL), (4, NULL, 'M'); CREATE TEMP TABLE t_model_mix AS SELECT * FROM fbsql.fit_glm( relation => $$ SELECT y, x1, gender FROM t_train_mix $$, formula => 'y ~ x1 + gender', family => 'gaussian'); SELECT id, x1, gender, round(y_predicted::numeric, 4) AS y_predicted FROM fbsql.predict_glm( relation => $$ SELECT id, x1, gender FROM t_new_mix $$, model => $$ SELECT * FROM t_model_mix $$ ) AS p(id integer, x1 double precision, gender text, y_predicted double precision) ORDER BY id; -- Binomial + factor: probabilities through the inverse logit. CREATE TEMP TABLE t_train_bf ( y integer, gender text ); INSERT INTO t_train_bf VALUES (0, 'F'), (1, 'F'), (0, 'F'), (1, 'M'), (0, 'M'), (1, 'M'); CREATE TEMP TABLE t_model_bf AS SELECT * FROM fbsql.fit_glm( relation => $$ SELECT y, gender FROM t_train_bf $$, formula => 'y ~ gender', family => 'binomial'); SELECT id, gender, round(y_predicted::numeric, 4) AS y_predicted FROM fbsql.predict_glm( relation => $$ SELECT id, gender FROM t_new_factor WHERE id IN (1, 2) $$, model => $$ SELECT * FROM t_model_bf $$ ) AS p(id integer, gender text, y_predicted double precision) ORDER BY id;