narrashap
Turn SHAP explanations into plain-language, fidelity-checked narratives in one line of code.
If you have ever built a machine learning model and been asked "okay, but why did it say that?", this library is for that moment. narrashap takes the output of SHAP and turns it into a few sentences that a non-technical reader can follow.
- Works offline. The built-in template mode needs no API key and costs nothing.
- Optional LLM mode. Plug in Groq (free tier available) or Anthropic for more natural phrasing.
- Fidelity scoring. In LLM mode, the generated text is scored against the underlying SHAP values.
- Domain-aware wording. Neutral by default, with healthcare and fraud wording built in.
- Safety guardrails. In LLM mode, causal wording such as "causes", "proven" or "guaranteed" is blocked, with one automatic rewrite attempt.
Table of contents
- What problem does this solve?
- Install
- Quickstart
- Domains and audiences
- Optional: richer explanations with an LLM
- Using it with a real model
- Jupyter notebook cell
- What you get back
- Safety guardrails
- Troubleshooting
- Development
- Project structure
- Current limitations
- Contributing
- License
What problem does this solve?
Machine learning models, such as one that predicts a health risk or flags a fraudulent transaction, do not explain themselves. SHAP can tell you which factors influenced a prediction and by how much, but its raw output looks like this:
Age: +0.40
Smoker: +0.90
That is useful to a data scientist. It means little to a patient, a manager, or anyone without a machine learning background.
narrashap turns it into this (real output from the Quickstart below):
"Based on the information provided, the estimate is 72.0%. This falls in the high risk range. Here is what influenced that result the most. The biggest factor was smoker, which raised the estimate. Another important factor was age, which raised the estimate. This is higher than about 67 out of 100 cases in our data. This is only a computer estimate based on patterns in data. It does not mean any one factor by itself causes the outcome. Please treat it as one input to a decision, not as a final answer."
Install
You need Python 3.10 or newer. Check with python --version.
pip install narrashap
For LLM-generated narratives, install the extra for your provider:
pip install "narrashap[groq]" # Groq
pip install "narrashap[anthropic]" # Anthropic
pip install "narrashap[all]" # both
Using a virtual environment is recommended:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install narrashap
Check the install:
python -c "from narrashap import narrate; print('It works!')"
Quickstart
This example uses tiny fake data, so you can see the library work before connecting it to a real model. Save it as quick_test.py (or paste it into a notebook cell):
import pandas as pd
from narrashap import narrate
# Pretend this is the data your model was trained on
training_data = pd.DataFrame({
"Age": [25, 30, 45, 60, 35, 50],
"Smoker": [0, 0, 1, 1, 0, 1],
})
# SHAP values for one person: Age contributed +0.4, Smoker contributed +0.9
shap_values = [0.4, 0.9]
# The actual feature values for that person
instance = pd.Series({"Age": 45, "Smoker": 1})
result = narrate(
shap_values=shap_values,
instance=instance,
training_data=training_data,
feature_names=["Age", "Smoker"],
base_value=0.1, # the model's starting point before considering this person
risk_percentage=72.0, # optional: the real percentage you would show a user
risk_level="HIGH RISK", # optional: a label to go with it
)
print(result)
Run it:
python quick_test.py
You get the paragraph shown at the top of this page. This is the template mode: fixed sentence templates, no AI service, no API key, no cost. The wording is domain-neutral unless you choose a domain, as described next.
Domains and audiences
By default the wording is neutral ("the estimate", "this case"). Two optional arguments change it:
narrate(..., domain="healthcare") # "generic" (default), "healthcare" or "fraud"
narrate(..., audience="clinician") # "general", "patient", "clinician" or "executive"
domain applies to both template mode and LLM mode. audience sets the reading level and decides which closing note is used: "general" and "patient" get a plain-language note, while "clinician" and "executive" get a shorter, more technical one. If you leave audience out, healthcare uses "patient" and every other domain uses "general".
In template mode you can also fine-tune two words, used in the closing note and the percentile sentence:
narrate(
...,
domain="healthcare",
audience="patient",
condition="fibroids", # "...does not mean any one factor by itself causes fibroids"
population="women", # "...higher than about 67 out of 100 women in our data"
)
Without them, healthcare falls back to "the condition" and "people". An unknown domain or audience raises a ValueError straight away.
Optional: richer explanations with an LLM
Template sentences follow a fairly repetitive pattern. If you want explanations that read more naturally, you can pass an LLM client. narrashap supports Groq and Anthropic. The steps below use Groq, which has a free tier and does not need a credit card to start.
1. Get a Groq API key
- Go to console.groq.com and sign up.
- Open the API Keys section and create a key.
- Copy it right away (it looks like
gsk_abc123...); it is only shown once.
2. Install the extra
pip install "narrashap[groq]"
3. Make the key available to your code
Never type your key into a file that might be shared or pushed to GitHub. Set it as an environment variable instead.
Mac/Linux:
export GROQ_API_KEY="gsk_your-actual-key-here"
Windows (PowerShell):
$env:GROQ_API_KEY = "gsk_your-actual-key-here"
This lasts only for the current terminal window. To make it permanent on Windows, run setx GROQ_API_KEY "gsk_your-actual-key-here" and then open a brand new terminal.
Check that it is set:
echo $GROQ_API_KEY # Mac/Linux
echo $env:GROQ_API_KEY # Windows PowerShell
4. Pass the client to narrate()
from narrashap import narrate
from narrashap.core.llm_client import GroqClient
result = narrate(
shap_values=shap_values,
instance=instance,
training_data=training_data,
feature_names=["Age", "Smoker"],
base_value=0.1,
risk_percentage=72.0,
risk_level="HIGH RISK",
llm_client=GroqClient(), # switches on the LLM-generated version
)
print(result)
To use Anthropic instead, install narrashap[anthropic], set ANTHROPIC_API_KEY, and pass AnthropicClient() from the same module.
If you see a "model not found" error, the default model has probably been retired. See Troubleshooting.
Using it with a real model
narrashap works on top of a trained model; it does not build one for you. The general shape is:
import shap
import pandas as pd
from narrashap import narrate
# 1. Your trained model, the data it learned from, and the one row to explain
model = ... # your trained model
X_train = ... # the training data (a pandas DataFrame)
X_instance = ... # a one-row DataFrame: the person or case to explain
# 2. Compute SHAP values for that one instance
explainer = shap.Explainer(model, X_train)
shap_values = explainer(X_instance)[0]
# 3. Get the narrative
result = narrate(
shap_values=shap_values,
instance=X_instance.iloc[0],
training_data=X_train,
risk_percentage=65.6, # your model's predicted probability, as a percentage
risk_level="MODERATE RISK",
)
print(result)
A complete working example, including a scikit-learn Pipeline with a preprocessing step and a logistic regression classifier, lives in the uf-risk-model repository, in fibroids_hospital_app/app.py.
Jupyter notebook cell
This cell assumes your notebook already has a fitted scikit-learn Pipeline called model (with steps named preprocessor and classifier, the classifier being a logistic regression) and the training DataFrame X_train. It does not train or fit anything.
import pandas as pd
from narrashap import narrate
# Choose which row to explain
patient_idx = 0
X_instance = X_train.iloc[[patient_idx]]
# Reuse the already-fitted pipeline steps
preprocessor = model.named_steps["preprocessor"]
classifier = model.named_steps["classifier"]
X_trans = preprocessor.transform(X_instance)
feature_names = list(preprocessor.get_feature_names_out())
# Clean names like "num__Age" into "Age" so the narrative reads naturally
def clean_name(name):
return name.replace("num__", "").replace("cat__", "").replace("_", " ").title()
clean_feature_names = [clean_name(fn) for fn in feature_names]
# SHAP values for a logistic regression: coefficient * feature value
# (replace this line if your model is not logistic regression)
shap_vals = classifier.coef_[0] * X_trans[0]
# Training data transformed the same way, used for percentile context
X_train_trans = preprocessor.transform(X_train)
training_df = pd.DataFrame(X_train_trans, columns=clean_feature_names)
# The model's real predicted probability, as a percentage
proba = classifier.predict_proba(X_trans)[0][1]
risk_pct = round(proba * 100, 1)
risk_level = (
"HIGH RISK" if risk_pct >= 70
else "MODERATE RISK" if risk_pct >= 40
else "LOW RISK"
)
narrated_shap = narrate(
shap_values=shap_vals,
instance=pd.Series(X_trans[0], index=clean_feature_names),
training_data=training_df,
feature_names=clean_feature_names,
base_value=classifier.intercept_[0],
risk_percentage=risk_pct,
risk_level=risk_level,
domain="healthcare", # this cell is a health-risk example
audience="patient",
)
print(narrated_shap)
For the LLM version, add llm_client=GroqClient() to the narrate() call, with from narrashap.core.llm_client import GroqClient and GROQ_API_KEY set.
What you get back
By default, narrate() returns a plain string: the explanation text, ready to print or display.
For more detail, such as a score for how well the text matches the real numbers, use return_details=True:
result = narrate(
shap_values=shap_values,
instance=instance,
training_data=training_data,
llm_client=GroqClient(),
return_details=True,
)
print(result.text) # the explanation
print(result.fidelity_score) # 0 to 1: how well the text matches the SHAP values
fidelity_score is only calculated when an LLM backend is used. In template mode it is None.
Safety guardrails
narrashap is deliberately careful not to imply that a factor causes an outcome. It describes factors as "associated with" or "among the strongest factors in" a prediction. SHAP shows what influenced a model's prediction; it cannot prove that anything causes an outcome in the real world, and that distinction matters a great deal in areas like healthcare.
Template mode uses fixed sentences that were written with this in mind, so nothing needs checking at run time.
LLM mode checks every generated explanation for risky phrasing (words like "causes", "proven", "guaranteed", plus extra phrases for each domain). If a banned phrase appears, the library asks the model to rewrite the text once. If the retry still contains banned wording, it raises a RuntimeError instead of showing something misleading. Treat that error as the system doing its job.
The check ignores a banned word when a negation such as "not" or "never" appears earlier in the same sentence, so safe disclaimers like "this does not mean smoking causes the condition" pass. The trade-off is that an unrelated negation earlier in a long sentence can occasionally let a real violation through, so keep reading the output in sensitive settings. LLM prompts also ask the model to avoid statistical jargon, but that part is a request, not a hard check.
Troubleshooting
ModuleNotFoundError: No module named 'narrashap'
You probably installed it into a different Python environment than the one running your code. Activate your virtual or conda environment first, then install, and activate it again whenever you return to the project.
"narrashap is not installed in this environment" inside a Jupyter notebook Notebooks sometimes use a different Python than your terminal. Check which one with:
import sys
print(sys.executable)
Then install with that exact Python, for example in a notebook cell: !pip install narrashap.
error: externally-managed-environment when installing
This appears on some Linux and Homebrew Python setups. Use a virtual environment (see Install) rather than forcing the install.
ValueError: Unknown domain or Unknown audience
Use one of the values listed in the message. Domains are "generic", "healthcare" and "fraud"; audiences are "general", "patient", "clinician" and "executive".
"Groq API key required" even though I set it
Environment variables reset when you open a new terminal window (unless you used setx on Windows and reopened the terminal). Set the key again in the same window you run your code from.
"The model ... does not exist or you do not have access to it" (from Groq)
The default model name is out of date. Open narrashap/core/llm_client.py, find the GroqClient class, and update the default model= value to a current name from console.groq.com/docs/models.
The LLM explanation sounds too technical or mentions raw numbers
Pass risk_percentage and risk_level to narrate(). Without them, the explanation falls back to the model's raw internal numbers, which are not meant for a general audience.
"Generated narrative still contains banned phrases after retry" The LLM used risky causal language twice in a row and the safety check correctly blocked it. This is rare and usually goes away if you try again, since LLM responses vary. If it happens constantly, please open an issue.
Development
To work on narrashap itself:
git clone https://github.com/daviewisdm/narrashap.git
cd narrashap
pip install -e ".[dev]"
python -m pytest -q
The -e flag installs in editable mode, so code changes take effect without reinstalling. If any test fails, check your Python version and dependencies before assuming the library is at fault.
Project structure
narrashap/
├── narrashap/
│ ├── convenience.py # the narrate() one-liner; start here
│ ├── core/
│ │ ├── extractor.py # turns raw SHAP output into a clean structure
│ │ ├── llm_client.py # the backends: Groq, Anthropic, or none
│ │ ├── narrator.py # builds the explanation and checks it for safety
│ │ └── scorer.py # scores how accurate a generated explanation is
│ ├── domains/ # per-industry terminology and tone (healthcare, fraud)
│ └── templates/ # template-mode wording, audience levels, causal-language checks
└── tests/ # automated tests
Current limitations
- Thoroughly tested against a logistic regression healthcare model. Tree-based models and neural networks should work in principle but have not been confirmed.
- Only the healthcare and fraud domains have terminology and tone configured. Finance and insurance are planned.
- It explains one prediction at a time, not a batch.
Contributing
Issues and pull requests are welcome. If you plan a larger change, please open an issue first to discuss it. Run the test suite (python -m pytest -q) before submitting.
License
Released under the MIT License.
Metadata
Release files for narrashap 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| narrashap-0.1.0.tar.gz | 34.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| narrashap-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 63.2 kB
Release files / narrashap-0.1.0.tar.gz
| Download URL | narrashap-0.1.0.tar.gz |
|---|---|
| Size | 34.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
933999057d6a9e54c5ded51865c09ff87598909106174c71ebe544acb7c586ff
|
|
BLAKE2b-256 checksum How to use checksums |
935efaeb057dcb2a6fdfdad7c21dff0ed1efa32bc850a463f43ad40ef629cf77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.8
|
Release files / narrashap-0.1.0-py3-none-any.whl
| Download URL | narrashap-0.1.0-py3-none-any.whl |
|---|---|
| Size | 29.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
54a48a6c46d1b42c41b8ed640fb4f3c75fb1e01e0cdc9dbfd78f1d8a42adc95d
|
|
BLAKE2b-256 checksum How to use checksums |
c3152467864759aafc5e56309163dbbb92a96e2fec265ca87400d697b017b447
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.8
|
Security for narrashap 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.