key_factors.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. """不良关键因子发现。"""
  2. import numpy as np
  3. import pandas as pd
  4. from defect_analysis.schemas import normalize_defect_schema
  5. DEFAULT_FACTOR_DIMENSIONS = [
  6. "equipment_id",
  7. "seat_id",
  8. "lam_equipment_id",
  9. "lam_seat_id",
  10. "lam_fixture_id",
  11. "lam_jig_id",
  12. "lam_nozzle_id",
  13. "material_lot_oca",
  14. "material_lot_glass",
  15. "material_lot_polarizer",
  16. "clean_equipment_id",
  17. "clean_slot_id",
  18. "bond_equipment_id",
  19. "bond_head_id",
  20. "recipe_id",
  21. "shift",
  22. "defect_geometry_type",
  23. ]
  24. def _build_target_mask(df, target_defect_type=None, target_severity=None):
  25. if target_defect_type:
  26. return df["defect_type"] == target_defect_type
  27. if target_severity:
  28. return df["severity"] == target_severity
  29. return df["severity"] == "严重"
  30. def find_key_factors(
  31. df,
  32. *,
  33. target_defect_type=None,
  34. target_severity=None,
  35. dimensions=None,
  36. min_count=3,
  37. min_lift=1.1,
  38. top_n=20,
  39. ):
  40. """查找与目标不良显著相关的关键因子。
  41. 当前实现是可解释统计排序:按每个维度取值计算目标占比、异常倍数和支持度,
  42. 用综合得分排序。它适合生产早期作为 ML 特征与根因候选的基线。
  43. """
  44. normalized = normalize_defect_schema(df)
  45. if normalized.empty:
  46. return pd.DataFrame()
  47. dimensions = DEFAULT_FACTOR_DIMENSIONS if dimensions is None else dimensions
  48. target_mask = _build_target_mask(normalized, target_defect_type, target_severity)
  49. baseline_rate = float(target_mask.mean())
  50. if baseline_rate <= 0:
  51. return pd.DataFrame(
  52. columns=["维度", "因子值", "样本数", "目标数", "目标占比", "基线占比", "异常倍数", "支持度", "关键因子得分"]
  53. )
  54. rows = []
  55. total = len(normalized)
  56. for dimension in dimensions:
  57. if dimension not in normalized.columns:
  58. continue
  59. values = normalized[dimension].fillna("").astype(str)
  60. valid = normalized[values != ""].copy()
  61. if valid.empty:
  62. continue
  63. valid_target = target_mask.loc[valid.index]
  64. grouped = valid.assign(_target=valid_target.astype(int)).groupby(dimension)
  65. for value, group in grouped:
  66. count = len(group)
  67. if count < min_count:
  68. continue
  69. target_count = int(group["_target"].sum())
  70. if target_count == 0:
  71. continue
  72. target_rate = target_count / count
  73. lift = target_rate / baseline_rate
  74. if lift < min_lift:
  75. continue
  76. support = count / total
  77. score = (max(lift - 1, 0) * 45) + (target_rate * 30) + (np.sqrt(target_count) * 8) + (support * 17)
  78. rows.append(
  79. {
  80. "维度": dimension,
  81. "因子值": str(value),
  82. "样本数": int(count),
  83. "目标数": target_count,
  84. "目标占比": round(float(target_rate), 4),
  85. "基线占比": round(float(baseline_rate), 4),
  86. "异常倍数": round(float(lift), 2),
  87. "支持度": round(float(support), 4),
  88. "关键因子得分": round(float(score), 2),
  89. }
  90. )
  91. if not rows:
  92. return pd.DataFrame(
  93. columns=["维度", "因子值", "样本数", "目标数", "目标占比", "基线占比", "异常倍数", "支持度", "关键因子得分"]
  94. )
  95. result = pd.DataFrame(rows)
  96. return (
  97. result.sort_values(["关键因子得分", "目标数", "异常倍数"], ascending=False)
  98. .head(top_n)
  99. .reset_index(drop=True)
  100. )