added all info

This commit is contained in:
Beau 2026-06-24 12:31:29 +02:00
commit e0c40da5d0
94 changed files with 5197859 additions and 0 deletions

129
evaluate_ruleset.py Normal file
View file

@ -0,0 +1,129 @@
"""
Evaluate a learned RIPPER ruleset against a dataset.
Ruleset format: {class_label: [[cond1, cond2, ...], [cond3, ...], ...], ...}
Conditions: 'col_name' → col == True
'!col_name' → col == False (negated condition)
The last entry in sorted_values is the default class — predicted when no rule fires.
"""
import pandas as pd
# Sentinel returned by runtime ontology guidance when no ontologically-valid action exists
# for the row's asset. It is never a true label, so it always counts as WRONG in accuracy/F1,
# and it is a plain string (not None) so sklearn's f1_score never chokes on mixed None/str
# labels. Tracked separately via the abstain_rate metric in run_all_experiments.
ABSTAIN = "Geen advies mogelijk."
def _row_matches_condition(row: pd.Series, cond: str) -> bool:
"""Return True if the row satisfies one condition."""
if cond.startswith('!'):
return row[cond[1:]] == False
return row[cond] == True
def _row_matches_rule(row: pd.Series, rule: list) -> bool:
"""Return True if the row satisfies ALL conditions in a rule (AND logic)."""
return all(_row_matches_condition(row, cond) for cond in rule)
def _predict_one(row: pd.Series, rule_set: dict, sorted_values: list) -> str:
"""
Predict the class for a single row.
Checks each class in sorted_values order (rarest first, same as training).
The first class whose rules fire wins. If nothing fires, return the default
class (sorted_values[-1], the most common one).
"""
for class_label in sorted_values[:-1]:
for rule in rule_set.get(class_label, []):
if rule and _row_matches_rule(row, rule):
return class_label
return sorted_values[-1] # default class: no rule fired
from ontology_guidance.get_onto_guidance_actions_per_asset import get_ontology_info_actions_per_asset
def _row_asset(row: pd.Series) -> str | None:
"""Return the asset value of a one-hot row: the assetType_* column that is True.
Mirrors the column convention used in build_invalid_row_mask (assetType_<value>).
Returns None if no assetType column is set.
"""
for col in row.index:
if col.startswith('assetType_') and row[col] == True:
return col.replace('assetType_', '')
return None
def _predict_one_runtime_onto_guidance(row: pd.Series, rule_set: dict, sorted_values: list) -> str:
"""
Predict the class for a single row, with inference-time ontology guidance.
Checks each class in sorted_values order (rarest first, same as training).
The first class whose rules fire wins. Then it checks whether that action is
ontologically valid for the row's asset. If not, it falls back to the most
common action that IS valid for the asset.
"""
asset = _row_asset(row)
valid_actions = get_ontology_info_actions_per_asset(asset) if asset is not None else []
valid_norm = {a.strip().lower() for a in valid_actions}
def _most_common_valid() -> str:
# walk most-common-first; return the first action valid for this asset
for item in reversed(sorted_values):
if item.strip().lower() in valid_norm:
return item
return ABSTAIN
for class_label in sorted_values[:-1]:
for rule in rule_set.get(class_label, []):
if rule and _row_matches_rule(row, rule):
if valid_norm and class_label.strip().lower() not in valid_norm:
return _most_common_valid()
return class_label
# no rule fired -> default class, but prefer one valid for the asset
if valid_norm:
return _most_common_valid()
return ABSTAIN
def evaluate_ruleset(
rule_set: dict,
data: pd.DataFrame,
sorted_values: list,
runtime_ontology_guidance_bool: bool
) -> tuple[pd.DataFrame, float]:
"""
Evaluate a ruleset against labelled data.
Args:
rule_set: {class_label: [[cond, ...], ...], ...}
data: DataFrame with feature columns plus a 'y' column (true labels)
sorted_values: class labels sorted by frequency ascending;
sorted_values[-1] is the default (most common) class
runtime_ontology_guidance_bool: if true will evaluate using inference time ontology guidance.
Returns:
result_df: copy of data with an extra 'y_pred' column
accuracy: fraction of rows where y_pred == y (0.0 – 1.0)
"""
result_df = data.copy()
if not runtime_ontology_guidance_bool:
result_df['y_pred'] = result_df.apply(
lambda row: _predict_one(row, rule_set, sorted_values),
axis=1,
)
else:
result_df['y_pred'] = result_df.apply(
lambda row: _predict_one_runtime_onto_guidance(row, rule_set, sorted_values),
axis=1,
)
accuracy = (result_df['y_pred'] == result_df['y']).mean()
return result_df, accuracy