Full Pipeline Outputs
Legacy low-level tutorial: this page documents the pre-clusterer intermediate pipeline. For the current public contract use class-guided regions, regions_, region_metrics_, labels_, and assign_regions().
This tutorial documents the intermediate artifacts worth saving when you run InsideForest with get_detail=True. Each stage records dimensions, key columns, and up to 10 sample rows so the analysis can be audited later.
Optional dependencies
pip install InsideForest==0.3.9
pip install MetaCraft==2025.10.6
import pandas as pd
pd.set_option("display.max_columns", None)
def audit_df(name, df, n=10):
print(f"{name}: shape={df.shape}")
print("columns:", df.columns.tolist())
return df.head(n) if len(df) >= n else df
Output Map
| Stage | Object | What it confirms | Recommended sample |
|---|---|---|---|
| Prepared data | df, X, y | Feature names, target column, and preprocessing. | df.head(10) |
| Training | in_f.labels_, pred_labels | Learned and predicted cluster labels. | Label counts and 10 rows aligned to the target. |
| Regions | in_f.df_reres_ | Prioritized ranges from the forest. | First DataFrame in the list; show all rows if fewer than 10. |
| Description | in_f.df_clusters_description_ | Readable rules, weight, effectiveness, and cluster size. | head(10) |
| Relative expansion | df_datos_explain or in_f.df_datos_explain_ | Rules expressed as variable percentiles. | head(10) |
| Frontiers | frontiers or in_f.frontiers_ | Comparable cluster pairs, similarity, delta, and score. | head(10) sorted by score. |
| Text | descrip_generales, descrip_gpt | Original conditions and generated narratives. | First 10 descriptions. |
| Metadata | metadata.df, df_meta_sub | Variable catalog and the rule-linked metadata subset. | head(10) with identity, type, domain, and actionability columns. |
| Experiments | mis_df2s, top_experiments, df_exper | Prioritized comparisons between clusters. | head(10), or all rows for two-cluster experiments. |
| Hypothesis | generate_hypothesis(...) | Actionable narrative using rules, metrics, and metadata. | Local text plus optional GPT output. |
Iris: fit, labels, and regions
from InsideForest import InsideForest
from sklearn.datasets import load_iris
iris = load_iris()
df = pd.DataFrame(iris.data, columns=["petal_length", "petal_width", "sepal_length", "sepal_width"])
df["species"] = iris.target.astype(float)
in_f = InsideForest(
rf_params={"random_state": 15},
tree_params={"lang": "py", "n_sample_multiplier": 0.05, "ef_sample_multiplier": 10},
leaf_percentile=95,
low_leaf_fraction=0.25,
get_detail=True,
var_obj="species",
)
in_f.fit(df)
pred_labels = in_f.predict(df.drop(columns=["species"]))
training_labels = in_f.labels_
Prepared data output: shape=(150, 5). Keep at least 10 rows to review units and target encoding.
| row | petal_length | petal_width | sepal_length | sepal_width | species |
|---|---|---|---|---|---|
| 0 | 5.1 | 3.5 | 1.4 | 0.2 | 0.0 |
| 1 | 4.9 | 3.0 | 1.4 | 0.2 | 0.0 |
| 2 | 4.7 | 3.2 | 1.3 | 0.2 | 0.0 |
| 3 | 4.6 | 3.1 | 1.5 | 0.2 | 0.0 |
| 4 | 5.0 | 3.6 | 1.4 | 0.2 | 0.0 |
| 5 | 5.4 | 3.9 | 1.7 | 0.4 | 0.0 |
| 6 | 4.6 | 3.4 | 1.4 | 0.3 | 0.0 |
| 7 | 5.0 | 3.4 | 1.5 | 0.2 | 0.0 |
| 8 | 4.4 | 2.9 | 1.4 | 0.2 | 0.0 |
| 9 | 4.9 | 3.1 | 1.5 | 0.1 | 0.0 |
Labels and regions
df_plot = pd.DataFrame({"species": iris.target, "training_labels": training_labels})
audit_df("df_plot", df_plot)
audit_df("in_f.df_reres_[0]", in_f.df_reres_[0])
The label table stays aligned to the original target. Records that do not match any rule can receive -1.
| row | species | training_labels |
|---|---|---|
| 0 | 0 | 11 |
| 1 | 0 | 11 |
| 2 | 0 | 11 |
| 3 | 0 | 11 |
| 4 | 0 | 11 |
| 5 | 0 | 12 |
| 6 | 0 | 11 |
| 7 | 0 | 11 |
| 8 | 0 | 11 |
| 9 | 0 | 11 |
df_reres_ is a list of DataFrames. Each table combines lower bounds, upper bounds, and region metrics. If the first table has fewer than 10 rows, show it entirely.
| row | linf.petal_width | linf.sepal_length | linf.sepal_width | lsup.petal_width | lsup.sepal_length | lsup.sepal_width | metrics.ef_sample | metrics.n_sample | metrics.ponderador | metrics.count |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2.45 | 1.00 | 0.10 | 4.80 | 1.90 | 0.60 | 0.00 | 50 | 0.91 | 8 |
| 1 | 2.45 | 4.45 | 1.20 | 7.00 | 5.10 | 1.80 | 1.00 | 48 | 0.86 | 7 |
| 2 | 2.45 | 4.80 | 1.70 | 7.90 | 6.90 | 2.50 | 2.00 | 46 | 0.79 | 5 |
| 3 | 2.20 | 3.00 | 1.00 | 6.40 | 5.00 | 1.70 | 1.00 | 31 | 0.63 | 3 |
| 4 | 2.50 | 4.90 | 1.80 | 7.90 | 6.90 | 2.50 | 2.00 | 29 | 0.58 | 2 |
Descriptions, explanation, and frontiers
from InsideForest.descrip import get_frontiers, generate_descriptions
df_datos_explain, frontiers = get_frontiers(in_f.df_clusters_description_, df, divide=5)
audit_df("in_f.df_clusters_description_", in_f.df_clusters_description_)
audit_df("df_datos_explain", df_datos_explain)
audit_df("frontiers", frontiers.sort_values("score", ascending=False))
df_clusters_description_ contains one row per candidate cluster, with the original readable rule and its metrics.
| cluster | cluster_description | cluster_weight | cluster_ef_sample | cluster_n_sample | cluster_count |
|---|---|---|---|---|---|
| 0 | 1.00 <= sepal_length <= 1.90 AND 0.10 <= sepal_width <= 0.60 | 0.91 | 0.00 | 50 | 8 |
| 1 | 4.45 <= sepal_length <= 5.10 AND 1.20 <= sepal_width <= 1.80 | 0.86 | 1.00 | 48 | 7 |
| 2 | 4.80 <= sepal_length <= 6.90 AND 1.70 <= sepal_width <= 2.50 | 0.79 | 2.00 | 46 | 5 |
| 3 | 3.00 <= sepal_length <= 5.00 AND 1.00 <= sepal_width <= 1.70 | 0.63 | 1.00 | 31 | 3 |
| 4 | 4.90 <= sepal_length <= 6.90 AND 1.80 <= sepal_width <= 2.50 | 0.58 | 2.00 | 29 | 2 |
| 5 | 2.30 <= petal_width <= 3.20 AND 1.00 <= sepal_length <= 1.70 | 0.47 | 0.00 | 25 | 2 |
| 6 | 2.70 <= petal_width <= 3.40 AND 4.00 <= sepal_length <= 5.00 | 0.44 | 1.00 | 24 | 2 |
| 7 | 2.90 <= petal_width <= 3.80 AND 5.10 <= sepal_length <= 6.70 | 0.40 | 2.00 | 21 | 2 |
| 8 | 4.60 <= petal_length <= 6.30 AND 1.50 <= sepal_width <= 2.40 | 0.36 | 2.00 | 19 | 1 |
| 9 | 3.30 <= petal_length <= 4.90 AND 1.00 <= sepal_width <= 1.60 | 0.32 | 1.00 | 18 | 1 |
df_datos_explain adds relative percentile columns for each variable used in the rules.
| cluster | cluster_ef_sample | cluster_n_sample | cluster_desc_relative | petal_width | sepal_length | sepal_width | petal_length |
|---|---|---|---|---|---|---|---|
| 0 | 0.00 | 50 | sepal_length = Percentile 20, sepal_width = Percentile 20 | PERCENTILE 20 | PERCENTILE 20 | ||
| 1 | 1.00 | 48 | sepal_length = Percentile 60, sepal_width = Percentile 60 | PERCENTILE 60 | PERCENTILE 60 | ||
| 2 | 2.00 | 46 | sepal_length = Percentile 90, sepal_width = Percentile 90 | PERCENTILE 90 | PERCENTILE 90 | ||
| 3 | 1.00 | 31 | sepal_length = Percentile 50, sepal_width = Percentile 50 | PERCENTILE 50 | PERCENTILE 50 | ||
| 4 | 2.00 | 29 | sepal_length = Percentile 90, sepal_width = Percentile 90 | PERCENTILE 90 | PERCENTILE 90 | ||
| 5 | 0.00 | 25 | petal_width = Percentile 40, sepal_length = Percentile 20 | PERCENTILE 40 | PERCENTILE 20 | ||
| 6 | 1.00 | 24 | petal_width = Percentile 60, sepal_length = Percentile 60 | PERCENTILE 60 | PERCENTILE 60 | ||
| 7 | 2.00 | 21 | petal_width = Percentile 80, sepal_length = Percentile 90 | PERCENTILE 80 | PERCENTILE 90 | ||
| 8 | 2.00 | 19 | petal_length = Percentile 80, sepal_width = Percentile 80 | PERCENTILE 80 | PERCENTILE 80 | ||
| 9 | 1.00 | 18 | petal_length = Percentile 60, sepal_width = Percentile 60 | PERCENTILE 60 | PERCENTILE 60 |
frontiers ranks cluster pairs. The score favors similar pairs with larger metric deltas.
| cluster_1 | cluster_2 | similarity | delta_cluster_ef_sample | score |
|---|---|---|---|---|
| 1 | 2 | 0.82 | 1.00 | 0.74 |
| 3 | 4 | 0.76 | 1.00 | 0.68 |
| 6 | 7 | 0.71 | 1.00 | 0.61 |
| 0 | 1 | 0.65 | 1.00 | 0.57 |
| 5 | 6 | 0.63 | 1.00 | 0.55 |
| 8 | 9 | 0.60 | 1.00 | 0.52 |
| 0 | 2 | 0.54 | 2.00 | 0.51 |
| 5 | 7 | 0.48 | 2.00 | 0.46 |
| 1 | 4 | 0.44 | 1.00 | 0.39 |
| 6 | 8 | 0.42 | 1.00 | 0.37 |
Generated descriptions
descrip_generales = [
x for x in in_f.df_clusters_description_["cluster_description"].unique().tolist()
if isinstance(x, str)
]
# Optional when an API key is available.
descrip_gpt = generate_descriptions(descrip_generales[:10], "english", api_k)
audit_df("descriptions", pd.DataFrame({"cluster_description": descrip_generales[:10]}))
Titanic: metadata and experiments
import seaborn as sns
from metacraft import Metadata
from InsideForest.metadata import MetaExtractor, run_experiments
df = sns.load_dataset("titanic")
var_obj = "survived"
metadata = Metadata()
titanic_meta = "https://raw.githubusercontent.com/jcval94/Metadata_files/refs/heads/main/titanic.yaml"
metadata.update(df, titanic_meta, inplace=False, output="titanic.yaml")
metadata.validate(df)
mx = MetaExtractor(metadata.df, var_obj)
audit_df("metadata.df", metadata.df)
metadata.df.head(10): this is the minimum 10-row metadata review. It includes identity, logical type, domain, and actionability.
| metadata_row | identity.label_i18n.en | identity.description_i18n.en | type.logical_type | domain.categorical.codes | increase_difficulty | decrease_difficulty | side_effects |
|---|---|---|---|---|---|---|---|
| survived | Survived | Passenger survival indicator. | categorical | 0=no; 1=yes | 10 | 10 | Target variable; do not intervene directly. |
| pclass | Ticket class | Socioeconomic ticket class. | ordinal | 1=first; 2=second; 3=third | 8 | 8 | Socioeconomic proxy; possible bias. |
| sex | Sex | Registered passenger sex. | categorical | male; female | 10 | 10 | Sensitive attribute; not actionable. |
| age | Age | Recorded or estimated age. | numeric | 10 | 10 | Demographic attribute; not actionable. | |
| sibsp | Siblings/spouses | Number of siblings or spouses aboard. | numeric | 7 | 7 | May represent family structure. | |
| parch | Parents/children | Number of parents or children aboard. | numeric | 7 | 7 | May represent dependents. | |
| fare | Fare | Ticket fare paid. | numeric | 6 | 6 | Economic access proxy. | |
| embarked | Embarkation port | Port where the passenger boarded. | categorical | C=Cherbourg; Q=Queenstown; S=Southampton | 9 | 9 | May introduce geographic bias. |
| class | Class label | Passenger class as text. | categorical | First; Second; Third | 8 | 8 | Redundant with pclass. |
| who | Person group | Derived group: man, woman, or child. | categorical | man; woman; child | 10 | 10 | Derived from age and sex; review bias. |
Experiment construction
percentile_75 = frontiers["score"].quantile(0.75)
frontiers_flt = frontiers[frontiers["score"] > percentile_75]
mis_df2s = {}
for vals in range(len(frontiers_flt) - 1):
quick_wins = frontiers_flt[["cluster_1", "cluster_2"]].values[vals]
df_QW_ = df_datos_explain[df_datos_explain["cluster"].isin(quick_wins)]
mis_df2s[f"experiment_{vals}"] = df_QW_
top_experiments = run_experiments(mx, mis_df2s)
audit_df("top_experiments", top_experiments)
mis_df2s["experiment_0"] usually contains two rows because each experiment compares two clusters.
| cluster | cluster_ef_sample | cluster_n_sample | cluster_description | sex | pclass | fare | age |
|---|---|---|---|---|---|---|---|
| 12 | 0.72 | 84 | cat__sex_female > 0.50 AND num__fare > 0.15 | PERCENTILE 80 | PERCENTILE 75 | ||
| 27 | 0.28 | 91 | cat__sex_male > 0.50 AND num__pclass > 0.65 | PERCENTILE 20 | PERCENTILE 80 |
top_experiments.head(10) ranks contrasts by effectiveness delta, size, intersection, and action difficulty.
| dataset | cluster_a | cluster_b | cluster_ef_a | cluster_ef_b | delta_ef | variables_a | variables_b | variables_intersection | difficulty_a | difficulty_b | score |
|---|---|---|---|---|---|---|---|---|---|---|---|
| experiment_0 | 27 | 12 | 0.28 | 0.72 | 0.44 | cat__sex_male, num__pclass | cat__sex_female, num__fare | 10 | 10 | 0.46 | |
| experiment_1 | 31 | 18 | 0.34 | 0.69 | 0.35 | num__pclass | num__fare | cat__alone_0 | 8 | 6 | 0.42 |
| experiment_2 | 44 | 7 | 0.21 | 0.55 | 0.34 | cat__adult_male_1 | cat__who_child | 10 | 10 | 0.31 | |
| experiment_3 | 22 | 9 | 0.41 | 0.67 | 0.26 | cat__embarked_S | cat__embarked_C | num__fare | 9 | 9 | 0.28 |
| experiment_4 | 35 | 16 | 0.18 | 0.43 | 0.25 | num__age | cat__who_woman | num__pclass | 10 | 10 | 0.22 |
| experiment_5 | 29 | 4 | 0.36 | 0.59 | 0.23 | cat__deck_nan | cat__deck_B | num__fare | 9 | 9 | 0.20 |
| experiment_6 | 41 | 11 | 0.24 | 0.45 | 0.21 | cat__alone_1 | cat__alone_0 | cat__sex_female | 7 | 7 | 0.18 |
| experiment_7 | 19 | 3 | 0.47 | 0.64 | 0.17 | num__sibsp | num__fare | cat__class_First | 7 | 6 | 0.15 |
| experiment_8 | 50 | 23 | 0.29 | 0.44 | 0.15 | cat__embark_town_Southampton | cat__embark_town_Cherbourg | 9 | 9 | 0.11 | |
| experiment_9 | 14 | 6 | 0.52 | 0.66 | 0.14 | num__parch | cat__who_child | cat__alone_0 | 7 | 10 | 0.10 |
df_exper, df_meta_sub, and hypothesis
n_experimento = 0
experimento_ = top_experiments.loc[n_experimento, "dataset"]
cols_act = [
"cluster_ef_a", "cluster_ef_b", "delta_ef", "variables_intersection",
"variables_a", "variables_b", "intersection", "only_cluster_a",
"only_cluster_b", "score",
]
df_exper = top_experiments[top_experiments["dataset"] == experimento_][cols_act]
cols_mtd = [
"metadata_row", "rule_token", "identity.label_i18n.en",
"identity.description_i18n.en", "type.logical_type",
"domain.categorical.codes", "actionability.increase_difficulty",
"actionability.decrease_difficulty", "actionability.side_effects",
]
df_meta_sub = mx.extract(mis_df2s[experimento_])[cols_mtd]
| metadata_row | rule_token | identity.label_i18n.en | identity.description_i18n.en | type.logical_type | domain.categorical.codes | increase_difficulty | decrease_difficulty | side_effects |
|---|---|---|---|---|---|---|---|---|
| sex | cat__sex_male | Sex | Registered passenger sex. | categorical | male; female | 10 | 10 | Sensitive attribute; not actionable. |
| sex | cat__sex_female | Sex | Registered passenger sex. | categorical | male; female | 10 | 10 | Sensitive attribute; not actionable. |
| pclass | num__pclass | Ticket class | Socioeconomic ticket class. | ordinal | 1=first; 2=second; 3=third | 8 | 8 | Socioeconomic proxy; possible bias. |
| fare | num__fare | Fare | Ticket fare paid. | numeric | 6 | 6 | Economic access proxy. | |
| survived | survived | Survived | Passenger survival indicator. | categorical | 0=no; 1=yes | 10 | 10 | Target variable; do not intervene directly. |
from InsideForest.descrip import generate_hypothesis
hypothesis = generate_hypothesis(df_meta_sub, df_exper, target="survived", lang="en")
print(hypothesis)
Example local output: Group A has an effectiveness of 0.28 versus 0.72 in group B, a difference of 0.44 for Survived. The lower-effectiveness group is distinguished by Sex and Ticket class; the higher-effectiveness group is distinguished by Sex and Fare. Treat this as a descriptive hypothesis, not a direct recommendation, because it includes sensitive attributes and socioeconomic proxies.
For reproducible reports, store df_clusters_description_, df_datos_explain, frontiers, metadata.df, top_experiments, df_exper, and df_meta_sub next to the notebook.