Titanic Survival Prediction using Iguanas#
This notebook demonstrates a complete end-to-end example of using Iguanas for rule-based classification on the Kaggle Titanic dataset.
The workflow includes:
Generating candidate rules using XGBoost
Filtering and selecting high-quality rules
Combining rules using different strategies
Analyzing the best ruleset
Rule explanation, fairness auditing, and registry
[1]:
import numpy as np
import polars as pl
from xgboost import XGBClassifier
from iguanas.metrics import compute_metrics
from iguanas.rule_analysis import generate_rule_performance_report
from iguanas.rule_combination import (
combine_rules_beam_search,
combine_rules_cumulative,
combine_rules_greedy,
)
from iguanas.rule_evaluation import apply_rules
from iguanas.rule_explanation import compute_counterfactual, compute_coverage_overlap, verbalize_rule
from iguanas.rule_fairness import compute_subgroup_metrics
from iguanas.rule_generation import rule_grid_search
from iguanas.rule_registry import RuleRegistry, filter_rule_pairs_by_overlap
from iguanas.rule_selection import filter_correlated_rules
Load and Prepare Data#
Load the Titanic training data and separate features from the target variable (Survived).
[2]:
train = pl.read_csv("../../../../../kaggle/titanic/train.csv").drop("PassengerId")
X_train = train.drop("Survived")
y_train = train["Survived"]
Extract numeric columns for rule generation:
[3]:
num_columns = [col for col, dtype in X_train.schema.items() if dtype in [pl.Int64, pl.Float64]]
num_columns
[3]:
['Pclass', 'Age', 'SibSp', 'Parch', 'Fare']
1. Generate Candidate Rules#
Use XGBoost-based grid search to generate candidate rules. The rule_grid_search function trains models with different scale_pos_weight values and extracts rules from the decision trees.
[4]:
estimator = XGBClassifier(n_estimators=10, max_depth=4, eval_metric="logloss", random_state=0)
rules = rule_grid_search(
estimator,
X_train[num_columns].to_pandas(),
y_train.to_pandas(),
scale_pos_weights=np.logspace(-3, 3, 50),
)
rules_df = rules.unique("rule")
[5]:
rules_df.head(3)
[5]:
| rule | tree | transformation | scale_pos_weight |
|---|---|---|---|
| str | i64 | str | f64 |
| "(X["Fare"] >= 52.5542) & (X["F… | 1 | "Baseline" | 0.068665 |
| "(X["Fare"] >= 10.5) & (X["Age"… | 7 | "Baseline" | 0.159986 |
| "(X["Pclass"] < 3.0) & (X["Fare… | 2 | "Baseline" | 0.09103 |
[6]:
rules = rules_df.select("rule").to_series().to_list()
print(f"Number of unique rules: {len(rules)}")
Number of unique rules: 175
2. Filter High-Quality Rules#
Apply the generated rules to the training data, compute performance metrics, and filter based on:
Minimum precision (> 0.15)
Minimum recall (> 0.15)
Maximum correlation between rules (< 0.8)
This ensures we keep only the most useful and diverse rules.
[7]:
R = apply_rules(X_train[num_columns], rules)
M = compute_metrics(R, y_train)
M = M.filter((pl.col("precision") > 0.15) & (pl.col("recall") > 0.15)).sort(
"accuracy", descending=True
)
importance = dict(zip(M["rule"], M["f0.5"], strict=False))
uncorrelated_rules = filter_correlated_rules(
R[M["rule"].to_list()], importance=importance, max_corr=0.8
)
[8]:
R_fixed = R[uncorrelated_rules].fill_null(False)
# Disjoint pairs: max_overlap=0.0
disjoint = filter_rule_pairs_by_overlap(R_fixed, max_overlap=0.0)
# Near-redundant pairs (high overlap): min_overlap=0.9
redundant = filter_rule_pairs_by_overlap(R_fixed, min_overlap=0.9)
print(f"Total pairs: {len(uncorrelated_rules) * (len(uncorrelated_rules) - 1) // 2}")
print(f"Disjoint (jaccard=0): {len(disjoint)}")
print(f"Redundant (jaccard≥0.9): {len(redundant)}")
disjoint.head(3)
Total pairs: 820
Disjoint (jaccard=0): 14
Redundant (jaccard≥0.9): 24
[8]:
| rule_a | rule_b | jaccard | flagged_by_both | flagged_by_either |
|---|---|---|---|---|
| str | str | f64 | i64 | i64 |
| "(X["Age"] >= 16.0) & (X["Age"]… | "(X["Age"] < 16.0) & (X["SibSp"… | 0.0 | 0 | 161 |
| "(X["Age"] >= 18.0) & (X["Age"]… | "(X["Fare"] >= 11.1333) & (X["S… | 0.0 | 0 | 160 |
| "(X["Age"] >= 18.0) & (X["Age"]… | "(X["Fare"] >= 10.5) & (X["SibS… | 0.0 | 0 | 162 |
3. Combine Rules#
Test different rule combination strategies to find the best performing ruleset.
3.1 Cumulative Combination#
Combines rules cumulatively (rule1 OR rule2 OR … OR ruleN):
[9]:
num_rules = R.shape[1]
[10]:
num_rules = len(uncorrelated_rules)
R_combined = combine_rules_cumulative(
R[uncorrelated_rules], output_names=[f"combined_rule_{i}" for i in range(1, num_rules + 1)]
)
M_combined = compute_metrics(R_combined, y_train).sort("accuracy", descending=True)
M_combined.head(3)
[10]:
| rule | TP | FP | TN | FN | precision | recall | accuracy | flagged(%) | good_flagged(%) | f0.25 | f0.5 | f1 | f1.5 | f2 | num_rules |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| str | i64 | i64 | i64 | i64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | u32 |
| "combined_rule_1" | 174 | 87 | 447 | 152 | 0.666667 | 0.533742 | 0.722093 | 30.348837 | 16.292135 | 0.657041 | 0.635036 | 0.592845 | 0.568627 | 0.555911 | 1 |
| "combined_rule_2" | 176 | 91 | 443 | 150 | 0.659176 | 0.539877 | 0.719767 | 31.046512 | 17.041199 | 0.650718 | 0.631277 | 0.593592 | 0.571714 | 0.560153 | 1 |
| "combined_rule_3" | 176 | 91 | 443 | 150 | 0.659176 | 0.539877 | 0.719767 | 31.046512 | 17.041199 | 0.650718 | 0.631277 | 0.593592 | 0.571714 | 0.560153 | 1 |
3.2 Beam Search with f1 optimization#
Uses beam search to explore rule combinations up to a maximum number of rules.
[11]:
R_f1 = combine_rules_beam_search(R[uncorrelated_rules], y_train, metric="f1", max_rules=12)
M_f1 = compute_metrics(R_f1, y_train)
M_f1.head(3)
[11]:
| rule | TP | FP | TN | FN | precision | recall | accuracy | flagged(%) | good_flagged(%) | f0.25 | f0.5 | f1 | f1.5 | f2 | num_rules |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| str | i64 | i64 | i64 | i64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | u32 |
| "((X["Pclass"] < 3.0) & (X["Far… | 241 | 156 | 282 | 60 | 0.607053 | 0.800664 | 0.707713 | 53.721245 | 35.616438 | 0.615812 | 0.637904 | 0.690544 | 0.729113 | 0.752655 | 5 |
| "((X["Pclass"] < 3.0) & (X["Far… | 241 | 156 | 282 | 60 | 0.607053 | 0.800664 | 0.707713 | 53.721245 | 35.616438 | 0.615812 | 0.637904 | 0.690544 | 0.729113 | 0.752655 | 6 |
| "((X["Pclass"] < 3.0) & (X["Far… | 241 | 156 | 282 | 60 | 0.607053 | 0.800664 | 0.707713 | 53.721245 | 35.616438 | 0.615812 | 0.637904 | 0.690544 | 0.729113 | 0.752655 | 6 |
3.2 Beam Search with f1 accuracy#
Uses beam search to explore rule combinations up to a maximum number of rules.
[12]:
R_beam = combine_rules_beam_search(R[uncorrelated_rules], y_train, metric="accuracy", max_rules=12)
M_beam = compute_metrics(R_beam, y_train)
M_beam.head(3)
[12]:
| rule | TP | FP | TN | FN | precision | recall | accuracy | flagged(%) | good_flagged(%) | f0.25 | f0.5 | f1 | f1.5 | f2 | num_rules |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| str | i64 | i64 | i64 | i64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | u32 |
| "((X["Pclass"] < 3.0) & (X["Far… | 205 | 100 | 417 | 111 | 0.672131 | 0.648734 | 0.746699 | 36.614646 | 19.34236 | 0.670708 | 0.667318 | 0.660225 | 0.655758 | 0.653282 | 4 |
| "((X["Pclass"] < 3.0) & (X["Far… | 205 | 100 | 417 | 111 | 0.672131 | 0.648734 | 0.746699 | 36.614646 | 19.34236 | 0.670708 | 0.667318 | 0.660225 | 0.655758 | 0.653282 | 4 |
| "((X["Pclass"] < 3.0) & (X["Far… | 205 | 100 | 417 | 111 | 0.672131 | 0.648734 | 0.746699 | 36.614646 | 19.34236 | 0.670708 | 0.667318 | 0.660225 | 0.655758 | 0.653282 | 4 |
4. Analyze the Best Ruleset#
Generate a detailed report for the best performing ruleset from brute force combination:
[13]:
for r in M_beam["rule"][0].split(" | "):
print(r)
((X["Pclass"] < 3.0) & (X["Fare"] >= 13.7917) & (X["Age"] < 61.0))
((X["Fare"] >= 11.1333) & (X["SibSp"] < 3.0) & (X["Age"] < 18.0))
((X["Fare"] >= 52.5542) & (X["SibSp"] < 2.0) & (X["Age"] < 63.0))
((X["Fare"] >= 75.25))
[14]:
ruleset = M_beam["rule"][0]
report = generate_rule_performance_report(ruleset, X_train, y_train)
report.head(5)
[14]:
| rule_index | rule | TP | FP | TN | FN | precision | recall | accuracy | flagged(%) | good_flagged(%) | f0.25 | f0.5 | f1 | f1.5 | f2 | num_rules |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| str | str | i64 | i64 | i64 | i64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | u32 |
| "0" | "((X["Pclass"] < 3.0) & (X["Far… | 205 | 100 | 417 | 111 | 0.672131 | 0.648734 | 0.746699 | 36.614646 | 19.34236 | 0.670708 | 0.667318 | 0.660225 | 0.655758 | 0.653282 | 4 |
| "0.0" | "(X['Pclass'] < 3.0) & (X['Fare… | 174 | 87 | 447 | 152 | 0.666667 | 0.533742 | 0.722093 | 30.348837 | 16.292135 | 0.657041 | 0.635036 | 0.592845 | 0.568627 | 0.555911 | 1 |
| "0.1" | "(X['Fare'] >= 11.1333) & (X['S… | 50 | 11 | 504 | 260 | 0.819672 | 0.16129 | 0.671515 | 7.393939 | 2.135922 | 0.660964 | 0.451264 | 0.269542 | 0.214239 | 0.19216 | 1 |
| "0.2" | "(X['Fare'] >= 52.5542) & (X['S… | 89 | 27 | 519 | 244 | 0.767241 | 0.267267 | 0.691695 | 13.196815 | 4.945055 | 0.691183 | 0.558344 | 0.396437 | 0.334296 | 0.30732 | 1 |
| "0.3" | "(X['Fare'] >= 75.25)" | 74 | 23 | 526 | 268 | 0.762887 | 0.216374 | 0.673401 | 10.886644 | 4.189435 | 0.664203 | 0.506849 | 0.33713 | 0.277553 | 0.25256 | 1 |
5. Rule Explanation#
Three tools to understand individual rules and their predictions.
5.1 Verbalize Rules#
Convert rule expressions into plain-English sentences for stakeholder reporting:
[15]:
# Verbalize the rules by accuracy
selected_rules = M_beam["rule"][0][1:-1].split(") | (")
for rule in selected_rules:
print(verbalize_rule(rule))
print()
Pclass is less than 3.0 AND Fare is at least 13.7917 AND Age is less than 61.0
Fare is at least 11.1333 AND SibSp is less than 3.0 AND Age is less than 18.0
Fare is at least 52.5542 AND SibSp is less than 2.0 AND Age is less than 63.0
Fare is at least 75.25
5.2 Coverage Overlap#
Pairwise Jaccard similarity between the top uncorrelated rules. A high score means two rules flag nearly the same passengers — useful for spotting redundancy that correlation filtering may have missed:
[16]:
overlap = compute_coverage_overlap(R[selected_rules])
overlap.head(8)
[16]:
| rule_a | rule_b | jaccard | flagged_by_both | flagged_by_either |
|---|---|---|---|---|
| str | str | f64 | i64 | i64 |
| "(X["Fare"] >= 52.5542) & (X["S… | "(X["Fare"] >= 75.25)" | 0.601504 | 80 | 133 |
| "(X["Pclass"] < 3.0) & (X["Fare… | "(X["Fare"] >= 52.5542) & (X["S… | 0.417293 | 111 | 266 |
| "(X["Pclass"] < 3.0) & (X["Fare… | "(X["Fare"] >= 75.25)" | 0.316176 | 86 | 272 |
| "(X["Pclass"] < 3.0) & (X["Fare… | "(X["Fare"] >= 11.1333) & (X["S… | 0.110345 | 32 | 290 |
| "(X["Fare"] >= 11.1333) & (X["S… | "(X["Fare"] >= 52.5542) & (X["S… | 0.066265 | 11 | 166 |
| "(X["Fare"] >= 11.1333) & (X["S… | "(X["Fare"] >= 75.25)" | 0.060403 | 9 | 149 |
5.3 Counterfactual Explanation#
For a passenger flagged by the best individual rule, find the minimal feature changes that would un-flag them:
[17]:
X_num = X_train[num_columns]
best_rule = M.sort("accuracy", descending=True)["rule"][0]
# Pick the first passenger flagged by the best rule
flagged_idx = apply_rules(X_num, [best_rule])[best_rule].arg_true().to_list()
sample = X_num[flagged_idx[0]]
print(f"Rule: {best_rule}")
print(f"\nPassenger features: {dict(zip(sample.columns, sample.row(0)))}")
print("\nMinimal changes to un-flag this passenger:")
for cf in compute_counterfactual(best_rule, sample):
print(f" {cf['feature']:10s} {cf['current_value']:>10.4f} → {cf['suggested_value']:.6f} (Δ {cf['abs_change']:.6f})")
Rule: (X["Pclass"] < 3.0) & (X["Fare"] >= 13.7917) & (X["Age"] < 61.0)
Passenger features: {'Pclass': 1, 'Age': 38.0, 'SibSp': 1, 'Parch': 0, 'Fare': 71.2833}
Minimal changes to un-flag this passenger:
Pclass 1.0000 → 3.000001 (Δ 2.000001)
Age 38.0000 → 61.000001 (Δ 23.000001)
Fare 71.2833 → 13.791699 (Δ 57.491601)
6. Fairness — Metrics by Passenger Class#
compute_subgroup_metrics breaks down each rule’s precision, recall, and F1 per subgroup. Here we audit across the three passenger classes to detect disparate impact:
[18]:
# Use the top 3 uncorrelated rules and audit across Pclass (1st / 2nd / 3rd class)
group = X_train["Pclass"].cast(pl.String).rename("Pclass")
subgroup_df = compute_subgroup_metrics(
R[uncorrelated_rules[:3]],
y_train,
group_col=group,
)
subgroup_df.select(["group", "group_size", "rule", "precision", "recall", "f1"])
[18]:
| group | group_size | rule | precision | recall | f1 |
|---|---|---|---|---|---|
| str | i32 | str | f64 | f64 | f64 |
| "1" | 216 | "(X["Pclass"] < 3.0) & (X["Fare… | 0.708333 | 0.97541 | 0.82069 |
| "1" | 216 | "(X["Pclass"] < 3.0) & (X["Fare… | 0.695402 | 0.991803 | 0.817568 |
| "1" | 216 | "(X["Pclass"] < 3.0) & (X["Fare… | 0.802326 | 0.560976 | 0.660287 |
| "2" | 184 | "(X["Pclass"] < 3.0) & (X["Fare… | 0.591398 | 0.647059 | 0.617978 |
| "2" | 184 | "(X["Pclass"] < 3.0) & (X["Fare… | 0.591398 | 0.647059 | 0.617978 |
| "2" | 184 | "(X["Pclass"] < 3.0) & (X["Fare… | 0.632911 | 0.588235 | 0.609756 |
| "3" | 491 | "(X["Pclass"] < 3.0) & (X["Fare… | NaN | 0.0 | NaN |
| "3" | 491 | "(X["Pclass"] < 3.0) & (X["Fare… | NaN | 0.0 | NaN |
| "3" | 491 | "(X["Pclass"] < 3.0) & (X["Fare… | NaN | 0.0 | NaN |
7. Rule Registry#
RuleRegistry lets you version-control rulesets across experiments and compare their metrics side by side.
7.1 Save Snapshots#
[19]:
registry = RuleRegistry()
# Save beam-search with f1 opt and beam-search with acc opt rulesets with their metrics
registry.save(
"beam_f1",
rules=M_f1["rule"].to_list(),
metrics=M_f1,
metadata={"strategy": "beam_search", "metric": "f1"},
)
registry.save(
"beam_acc",
rules=M_beam["rule"].to_list(),
metrics=M_beam,
metadata={"strategy": "beam_search", "max_rules": 12, "metric": "accuracy"},
)
print("Saved snapshots:", registry.list())
print("\nBeam snapshot metadata:", registry.load("beam_acc")["metadata"])
Saved snapshots: ['beam_acc', 'beam_f1']
Beam snapshot metadata: {'strategy': 'beam_search', 'max_rules': 12, 'metric': 'accuracy'}
7.2 Compare Snapshots#
compare joins both snapshots on rule name and suffixes each metric column with the snapshot name — making it easy to spot regressions or improvements:
[20]:
# registry.compare() joins on the rule column — it works when the same rules
# exist in both snapshots (e.g. train vs test evaluation of the same ruleset).
# Here beam_f1 and beam_acc produce completely different combined expressions,
# so a join would return all nulls. Build a direct head-to-head summary instead.
metrics_cols = ["precision", "recall", "accuracy", "f1"]
comparison = pl.concat([
registry.load(name)["metrics"]
.sort("f1", descending=True)
.head(1)
.select(metrics_cols)
.with_columns(pl.lit(name).alias("strategy"))
for name in registry.list()
]).select(["strategy"] + metrics_cols)
comparison
[20]:
| strategy | precision | recall | accuracy | f1 |
|---|---|---|---|---|
| str | f64 | f64 | f64 | f64 |
| "beam_acc" | 0.67101 | 0.651899 | 0.746699 | 0.661316 |
| "beam_f1" | 0.607053 | 0.800664 | 0.707713 | 0.690544 |
7.3 Filter Rule Pairs by Overlap#
filter_rule_pairs_by_overlap returns pairs whose Jaccard similarity falls within [min_overlap, max_overlap]. Both bounds are inclusive, making it easy to query any region of the overlap spectrum:
Call |
Returns |
|---|---|
|
All pairs (defaults: 0.0 – 1.0) |
|
Only disjoint pairs (never co-fire) |
|
Only near-redundant pairs |
|
Mid-range overlap only |
[21]:
# The Titanic dataset has ~177 missing Age values.
# Rules that reference Age evaluate to null for those rows.
# null -> NaN after cast, and np.corrcoef propagates NaN for any pair
# where either rule has a missing value.
# Fix: fill_null(False) — a rule that cannot be evaluated does not fire.
arr = R[uncorrelated_rules].fill_null(False).cast(pl.Float64).to_numpy() # (891, 41)
corr_matrix = np.corrcoef(arr.T) # (41, 41)
labels = [f"r{i}" for i in range(len(uncorrelated_rules))]
pl.DataFrame(corr_matrix, schema=labels).with_row_index("rule").head(10)
[21]:
| rule | r0 | r1 | r2 | r3 | r4 | r5 | r6 | r7 | r8 | r9 | r10 | r11 | r12 | r13 | r14 | r15 | r16 | r17 | r18 | r19 | r20 | r21 | r22 | r23 | r24 | r25 | r26 | r27 | r28 | r29 | r30 | r31 | r32 | r33 | r34 | r35 | r36 | r37 | r38 | r39 | r40 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| u32 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 |
| 0 | 1.0 | 0.983981 | 0.740668 | 0.684959 | 0.598092 | 0.580043 | 0.58609 | 0.617553 | 0.564432 | 0.595141 | 0.595141 | 0.538504 | 0.580981 | 0.56785 | 0.477839 | 0.549294 | 0.501451 | 0.72815 | 0.602654 | 0.501451 | 0.621759 | 0.621759 | 0.491304 | 0.5336 | 0.5336 | 0.455939 | 0.137996 | 0.130315 | 0.605406 | 0.515425 | 0.520781 | 0.421188 | 0.647272 | 0.647272 | 0.424882 | 0.428554 | 0.376764 | 0.553224 | 0.343668 | 0.087346 | 0.511071 |
| 1 | 0.983981 | 1.0 | 0.728803 | 0.673987 | 0.588511 | 0.570751 | 0.576701 | 0.604068 | 0.562324 | 0.592304 | 0.592304 | 0.542024 | 0.5714 | 0.558753 | 0.46987 | 0.540495 | 0.493114 | 0.743058 | 0.624687 | 0.493114 | 0.611799 | 0.611799 | 0.483433 | 0.524904 | 0.524904 | 0.463543 | 0.133106 | 0.125406 | 0.601683 | 0.506731 | 0.512438 | 0.414441 | 0.65781 | 0.65781 | 0.418076 | 0.421689 | 0.369155 | 0.562684 | 0.337122 | 0.082967 | 0.502885 |
| 2 | 0.740668 | 0.728803 | 1.0 | 0.729102 | 0.557592 | 0.571512 | 0.572574 | 0.74268 | 0.339303 | 0.337202 | 0.337202 | 0.299943 | 0.543268 | 0.569528 | 0.436937 | 0.53936 | 0.438219 | 0.638376 | 0.310132 | 0.438219 | 0.839457 | 0.839457 | 0.472368 | 0.410649 | 0.410649 | 0.176592 | 0.202548 | 0.195373 | 0.507847 | 0.396923 | 0.549714 | 0.534336 | 0.479414 | 0.479414 | 0.380609 | 0.285545 | 0.42705 | 0.418649 | 0.386987 | 0.138451 | 0.339599 |
| 3 | 0.684959 | 0.673987 | 0.729102 | 1.0 | 0.836905 | 0.837549 | 0.855657 | 0.686819 | 0.561296 | 0.597677 | 0.597677 | 0.550278 | 0.819029 | 0.829027 | 0.684675 | 0.801937 | 0.729481 | 0.450835 | 0.538136 | 0.729481 | 0.511085 | 0.511085 | 0.717275 | 0.424618 | 0.424618 | 0.451134 | 0.157396 | 0.151204 | 0.403634 | 0.411157 | 0.588781 | 0.61491 | 0.443355 | 0.443355 | 0.608354 | 0.613802 | 0.421848 | 0.374174 | 0.391097 | 0.098805 | 0.377378 |
| 4 | 0.598092 | 0.588511 | 0.557592 | 0.836905 | 1.0 | 0.969822 | 0.979932 | 0.570262 | 0.646873 | 0.669958 | 0.669958 | 0.621865 | 0.980513 | 0.949435 | 0.775561 | 0.918411 | 0.783137 | 0.354895 | 0.680523 | 0.783137 | 0.321451 | 0.321451 | 0.821452 | 0.258007 | 0.258007 | 0.553278 | 0.054703 | 0.050521 | 0.260305 | 0.248184 | 0.593021 | 0.704219 | 0.387129 | 0.387129 | 0.670928 | 0.716534 | 0.284594 | 0.322538 | 0.2484 | -0.016936 | 0.47107 |
| 5 | 0.580043 | 0.570751 | 0.571512 | 0.837549 | 0.969822 | 1.0 | 0.97931 | 0.574083 | 0.629121 | 0.644696 | 0.644696 | 0.598283 | 0.950922 | 0.978979 | 0.754162 | 0.946989 | 0.774919 | 0.349901 | 0.659986 | 0.774919 | 0.337135 | 0.337135 | 0.81127 | 0.227665 | 0.227665 | 0.529313 | 0.020855 | 0.017276 | 0.247337 | 0.218524 | 0.602305 | 0.71257 | 0.375446 | 0.375446 | 0.692118 | 0.698745 | 0.265391 | 0.311725 | 0.221974 | -0.054346 | 0.465684 |
| 6 | 0.58609 | 0.576701 | 0.572574 | 0.855657 | 0.979932 | 0.97931 | 1.0 | 0.587682 | 0.641795 | 0.656429 | 0.656429 | 0.609305 | 0.960836 | 0.968878 | 0.768958 | 0.937218 | 0.788682 | 0.349191 | 0.666867 | 0.788682 | 0.331792 | 0.331792 | 0.838274 | 0.245119 | 0.245119 | 0.544642 | 0.059222 | 0.055041 | 0.260958 | 0.235625 | 0.606619 | 0.71864 | 0.37936 | 0.37936 | 0.671518 | 0.71795 | 0.293684 | 0.315351 | 0.256156 | -0.013673 | 0.459825 |
| 7 | 0.617553 | 0.604068 | 0.74268 | 0.686819 | 0.570262 | 0.574083 | 0.587682 | 1.0 | 0.345925 | 0.370083 | 0.370083 | 0.323717 | 0.582605 | 0.569392 | 0.462555 | 0.550786 | 0.494827 | 0.725664 | 0.299397 | 0.494827 | 0.623448 | 0.623448 | 0.492638 | 0.393668 | 0.393668 | 0.26708 | 0.148597 | 0.160037 | 0.405387 | 0.377914 | 0.522195 | 0.422332 | 0.625768 | 0.625768 | 0.416333 | 0.420086 | 0.544507 | 0.531765 | 0.454889 | 0.088084 | 0.213032 |
| 8 | 0.564432 | 0.562324 | 0.339303 | 0.561296 | 0.646873 | 0.629121 | 0.641795 | 0.345925 | 1.0 | 0.953287 | 0.953287 | 0.892276 | 0.632448 | 0.60337 | 0.850263 | 0.574254 | 0.800825 | 0.344143 | 0.621657 | 0.800825 | 0.057882 | 0.057882 | 0.527339 | 0.308942 | 0.308942 | 0.721403 | 0.040391 | 0.036405 | 0.366923 | 0.298219 | 0.335164 | 0.700725 | 0.389059 | 0.389059 | 0.654441 | 0.647924 | 0.207427 | 0.344467 | 0.184533 | -0.017737 | 0.243521 |
| 9 | 0.595141 | 0.592304 | 0.337202 | 0.597677 | 0.669958 | 0.644696 | 0.656429 | 0.370083 | 0.953287 | 1.0 | 1.0 | 0.936 | 0.65495 | 0.62088 | 0.810545 | 0.594442 | 0.846214 | 0.341894 | 0.634755 | 0.846214 | 0.043009 | 0.043009 | 0.555112 | 0.338217 | 0.338217 | 0.757823 | 0.030275 | 0.026273 | 0.428021 | 0.326727 | 0.312887 | 0.667992 | 0.408124 | 0.408124 | 0.673851 | 0.679674 | 0.261452 | 0.341891 | 0.216693 | -0.025471 | 0.224585 |
5. Generate Predictions on Test Data#
Apply the best ruleset to the test data and create a submission file:
[22]:
X_test = pl.read_csv("../../../../../kaggle/titanic/test.csv")
y_pred = eval(ruleset.replace("X", "X_test"))
[23]:
# Create submission file (Kaggle leaderboard score: 0.61004)
# pl.DataFrame({"PassengerId": X_test["PassengerId"], "Survived": y_pred}).with_columns(
# pl.col("Survived").cast(pl.Int64)
# ).write_csv("submission_titanic.csv")