Source code for denia.insights
"""Insights: what drives the target?
``drivers()`` answers the question every experimentalist asks right after
"what should I run next?": *which variables actually control my outcome?*
A random forest is fit on the campaign's featurized history and its impurity
importances are reported at two levels:
* **feature level** - individual species and settings
(``Cation::Mn``, ``Preparation::Impregnation``, ``Temperature, K``), and
* **column level** - importances summed back to the original spreadsheet
columns/groups, so the answer is phrased in the user's own vocabulary.
These are descriptive, not causal: they say what the model relies on given
the (possibly biased) history, which is exactly the right caveat to show a
lab before they reorganize their program around it.
"""
from __future__ import annotations
from typing import Optional, Tuple
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from .features import Featurizer
[docs]
def drivers(
history: pd.DataFrame,
schema,
target: Optional[str] = None,
n_estimators: int = 300,
random_state: int = 0,
) -> Tuple[pd.DataFrame, pd.DataFrame]:
"""Return (feature_level, column_level) importance tables for a target.
Both tables have columns ``name`` and ``importance`` and sum to 1.
"""
target = target or schema.targets[0]
feat = Featurizer(schema).fit(history)
X = feat.transform(history)
y = feat.target_vector(history, target)
mask = np.isfinite(y)
rf = RandomForestRegressor(
n_estimators=n_estimators, random_state=random_state, n_jobs=-1, min_samples_leaf=2
).fit(X[mask], y[mask])
names = feat.feature_names_
imp = rf.feature_importances_
feature_level = (
pd.DataFrame({"name": names, "importance": imp})
.sort_values("importance", ascending=False)
.reset_index(drop=True)
)
def group_of(n: str) -> str:
return n.split("::", 1)[0] if "::" in n else n
column_level = (
feature_level.assign(name=feature_level["name"].map(group_of))
.groupby("name", as_index=False)["importance"].sum()
.sort_values("importance", ascending=False)
.reset_index(drop=True)
)
return feature_level, column_level