review.py 3.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. """输出小时标签的排查指标,便于人工判断标签是否合理。"""
  2. from __future__ import annotations
  3. import numpy as np
  4. import pandas as pd
  5. from . import config
  6. def _slope(values: pd.Series, times: pd.Series) -> float:
  7. if len(values) < 2:
  8. return 0.0
  9. x = (pd.to_datetime(times) - pd.to_datetime(times).iloc[0]).dt.total_seconds() / 3600.0
  10. x = np.asarray(x, dtype=float)
  11. y = np.asarray(values, dtype=float)
  12. if np.ptp(x) <= 0 or not np.isfinite(y).any():
  13. return 0.0
  14. return float(np.polyfit(x, y, 1)[0])
  15. def build_review(hourly_labels: pd.DataFrame, frame: pd.DataFrame) -> pd.DataFrame:
  16. if hourly_labels.empty:
  17. return pd.DataFrame()
  18. frame = frame.copy()
  19. frame["sample_time"] = pd.to_datetime(frame["sample_time"])
  20. rows = []
  21. for item in hourly_labels.itertuples(index=False):
  22. group = frame[frame["cylinder"] == item.cylinder]
  23. part = group[(group["sample_time"] >= item.start_hour) & (group["sample_time"] < item.end_hour)]
  24. pre = group[(group["sample_time"] >= item.start_hour - pd.Timedelta(hours=6)) & (group["sample_time"] < item.start_hour)]
  25. if part.empty:
  26. continue
  27. score_max = float(part["score"].max())
  28. score_mean = float(part["score"].mean())
  29. rms_max = float(part["rms_ratio"].max())
  30. rms_mean = float(part["rms_ratio"].mean())
  31. kurt_max = float(part["kurt"].max())
  32. kurt_ratio_max = float(part["kurt_ratio"].max())
  33. kurt_ratio_mean = float(part["kurt_ratio"].mean())
  34. active_rms = float((part["rms_ratio"] >= 1.10).mean())
  35. active_kurt = float((part["kurt_ratio"] >= 1.50).mean())
  36. transition_ratio = float(part["transition"].mean()) if "transition" in part else 0.0
  37. baseline_ok_ratio = float(part["baseline_ok"].mean()) if "baseline_ok" in part else 1.0
  38. flags = []
  39. if not bool(item.confirmed):
  40. flags.append("未确认")
  41. if rms_max < 1.10 and kurt_ratio_max >= 1.50:
  42. flags.append("低幅高冲击")
  43. if score_max >= 4.50 and rms_max < 1.10 and kurt_ratio_max < 1.50:
  44. flags.append("分数驱动不明")
  45. if active_kurt < 0.30 and active_rms < 0.30:
  46. flags.append("持续证据弱")
  47. if transition_ratio >= 0.25:
  48. flags.append("启动/过渡")
  49. if baseline_ok_ratio < 0.50:
  50. flags.append("局部基线不足")
  51. rows.append(
  52. {
  53. "cylinder": item.cylinder,
  54. "start_hour": item.start_hour,
  55. "end_hour": item.end_hour,
  56. "hour_count": item.hour_count,
  57. "score_mean": round(score_mean, 3),
  58. "score_max": round(score_max, 3),
  59. "rms_ratio_mean": round(rms_mean, 3),
  60. "rms_ratio_max": round(rms_max, 3),
  61. "kurt_ratio_mean": round(kurt_ratio_mean, 3),
  62. "kurt_ratio_max": round(kurt_ratio_max, 3),
  63. "kurt_max": round(kurt_max, 3),
  64. "active_rms": round(active_rms, 2),
  65. "active_kurt": round(active_kurt, 2),
  66. "transition_ratio": round(transition_ratio, 2),
  67. "baseline_ok_ratio": round(baseline_ok_ratio, 2),
  68. "pre6h_score_slope": round(_slope(pre["score"], pre["sample_time"]), 4),
  69. "pre6h_kurt_slope": round(_slope(pre["kurt_ratio"], pre["sample_time"]), 4),
  70. "direction": item.direction,
  71. "level": item.level,
  72. "confirmed": bool(item.confirmed),
  73. "review_flags": " / ".join(flags),
  74. }
  75. )
  76. return pd.DataFrame(rows)