MyNameIsTatiBond commited on
Commit
6697d65
·
1 Parent(s): ff34011

Refine SHAP explanations with value descriptors

Browse files
Files changed (2) hide show
  1. app.py +45 -21
  2. index.html +1 -1
app.py CHANGED
@@ -253,51 +253,75 @@ def get_readable_explanation(feature_name, shap_val, metadata=None):
253
 
254
  def get_nuanced_explanation(feature_name, shap_val, feature_val, metadata=None):
255
  """
256
- Generate explanation with relative-to-typical context.
257
  """
258
  baseline_direction = "UP" if shap_val > 0 else "DOWN"
259
 
260
- # Default Text
261
- direction, text = get_readable_explanation(feature_name, shap_val, metadata)
 
 
 
 
 
 
 
262
 
263
- # Trend Analysis
264
  if TREND_REGISTRY and feature_name in TREND_REGISTRY:
265
  try:
266
  entry = TREND_REGISTRY[feature_name]
267
  bins = entry["bins"]
268
  shaps = entry["shap_values"]
269
  ref_idx = entry.get("ref_idx", len(bins)//2)
 
270
 
271
  # Find current bin
272
- # simple nearest neighbor in bins
273
- # bins are sorted. np.searchsorted or just min dist
274
  curr_idx = (np.abs(np.array(bins) - feature_val)).argmin()
275
 
276
  curr_shap_med = shaps[curr_idx]
277
  ref_shap_med = shaps[ref_idx]
278
-
279
  typical_delta = curr_shap_med - ref_shap_med
280
 
281
- # Threshold for "significant" relative difference?
282
- # Let's say if abs delta > 0.0005 (small but real for SHAP probability)
 
 
 
 
 
 
 
 
283
  if typical_delta > 0.0005:
284
  # Riskier than typical
285
- if baseline_direction == "DOWN":
286
- text += ", but riskier than typical values"
287
- else:
288
- text += " (high risk factor)"
289
  elif typical_delta < -0.0005:
290
  # Safer than typical
291
- if baseline_direction == "UP":
292
- text += ", but safer than typical values"
293
- else:
294
- text += " (low risk factor)"
295
 
296
- except Exception as e:
297
- # Fallback to standard
298
  pass
299
-
300
- return baseline_direction, text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
302
  @app.get("/")
303
  async def root():
 
253
 
254
  def get_nuanced_explanation(feature_name, shap_val, feature_val, metadata=None):
255
  """
256
+ Generate explanation with relative-to-typical context and value descriptors.
257
  """
258
  baseline_direction = "UP" if shap_val > 0 else "DOWN"
259
 
260
+ # 1. Resolve Name
261
+ raw_feat = feature_name
262
+ if metadata and feature_name in metadata:
263
+ raw_feat = metadata[feature_name].get("raw_feature", feature_name)
264
+ user_label = FEATURE_MAP.get(raw_feat, raw_feat.replace("_", " ").title())
265
+
266
+ # 2. Trend Analysis
267
+ trend_text = ""
268
+ value_desc = ""
269
 
 
270
  if TREND_REGISTRY and feature_name in TREND_REGISTRY:
271
  try:
272
  entry = TREND_REGISTRY[feature_name]
273
  bins = entry["bins"]
274
  shaps = entry["shap_values"]
275
  ref_idx = entry.get("ref_idx", len(bins)//2)
276
+ min_val, max_val = entry.get("min_val", bins[0]), entry.get("max_val", bins[-1])
277
 
278
  # Find current bin
 
 
279
  curr_idx = (np.abs(np.array(bins) - feature_val)).argmin()
280
 
281
  curr_shap_med = shaps[curr_idx]
282
  ref_shap_med = shaps[ref_idx]
 
283
  typical_delta = curr_shap_med - ref_shap_med
284
 
285
+ # Value Descriptor (Low/High/Typical)
286
+ # Simple percentile check
287
+ rng = max_val - min_val
288
+ if rng > 0:
289
+ rel_pos = (feature_val - min_val) / rng
290
+ if rel_pos < 0.33: value_desc = "Low "
291
+ elif rel_pos > 0.66: value_desc = "High "
292
+ else: value_desc = "Typical "
293
+
294
+ # Threshold for "significant" relative difference
295
  if typical_delta > 0.0005:
296
  # Riskier than typical
297
+ trend_text = "associated with higher risk than average"
 
 
 
298
  elif typical_delta < -0.0005:
299
  # Safer than typical
300
+ trend_text = "associated with lower risk than average"
 
 
 
301
 
302
+ except Exception:
 
303
  pass
304
+
305
+ # 3. Construct Final Sentence
306
+ # Case A: Trend info available and significant
307
+ if trend_text:
308
+ # "Low Injury Cost Portion is associated with higher risk than average."
309
+ if value_desc == "Typical ": value_desc = "" # Omit "Typical" prefix usually
310
+ full_text = f"{value_desc}{user_label} is {trend_text}"
311
+
312
+ # Add baseline context if it contradicts?
313
+ # If baseline is DOWN but trend is RISKIER -> "Reduces risk overall, but Low X is associated with higher risk than average"
314
+ # User implies they just want the "causes it higher" part.
315
+ # "Low Injury Cost Portion is associated with higher risk than average" is very clear.
316
+ pass
317
+ else:
318
+ # Case B: Standard Baseline Fallback
319
+ if shap_val > 0:
320
+ full_text = f"{user_label} contributes to risk"
321
+ else:
322
+ full_text = f"{user_label} reduces risk estimate"
323
+
324
+ return baseline_direction, full_text
325
 
326
  @app.get("/")
327
  async def root():
index.html CHANGED
@@ -1283,7 +1283,7 @@
1283
  ${icon} ${directionText}
1284
  </div>
1285
  <div style="font-size: 12px; opacity: 0.6; font-style: italic;">
1286
- Associated with ${isUp ? 'higher' : 'lower'} risk patterns
1287
  </div>
1288
  `;
1289
  list.appendChild(row);
 
1283
  ${icon} ${directionText}
1284
  </div>
1285
  <div style="font-size: 12px; opacity: 0.6; font-style: italic;">
1286
+ ${item.text}
1287
  </div>
1288
  `;
1289
  list.appendChild(row);