| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 |
- """
- 独立打标模块:基于预先生成的机型阈值 Excel 对风机数据打标签。
- 功能:
- apply_labels(df, model_name, threshold_file=None) -> DataFrame
- 输入原始数据 DataFrame 和机型名称,返回打标后的 DataFrame,只包含原始列和 label 列、sensor_tag列。
- 输出新增列:
- - label : 最终业务状态(运行/停机/限功率/传感器异常-xxx)
- 状态优先级(高→低):传感器异常 > 停机 > 限功率 > 运行
- """
- from typing import Dict, Optional
- import numpy as np
- import pandas as pd
- # ==================== 打标参数 ====================
- # 功率越界倍数(相对额定功率)
- STATUS_POWER_UPPER_RATIO = 1.25 # 功率上限倍数
- STATUS_POWER_LOWER_RATIO = -0.10 # 功率下限倍数
- STATUS_SHUTDOWN_RATIO = 0.005 # 停机功率阈值(额定功率的比例),最小取 50kW
- STATUS_CURTAIL_LOW_RATIO = 0.02 # 限功率下限(额定功率比例)
- STATUS_CURTAIL_HIGH_RATIO = 0.95 # 限功率上限(额定功率比例)
- STATUS_CURTAIL_PITCH_OFFSET = 3.0 # 限功率桨距角偏移量(基准桨距角 + 此值)
- # 风速越界阈值(m/s)
- STATUS_WIND_MAX = 75.0
- STATUS_WIND_MIN = -2.0
- # 变桨越界阈值(°)
- STATUS_PITCH_MAX = 105.0
- STATUS_PITCH_MIN = -10.0
- # 转速/扭矩越界倍数
- STATUS_SPD_UPPER_RATIO = 1.25
- STATUS_TORQUE_UPPER_RATIO = 1.25
- STATUS_TORQUE_LOWER_ABS = -2000.0
- # 逻辑悖论阈值
- STATUS_LOGIC_WIND_MIN = 0.1 # 风速低于此值但功率大则为逻辑异常
- STATUS_LOGIC_POWER_MIN = 100.0 # 配合上面风速阈值的功率下限
- # 机型特殊规则(可扩展)
- STATUS_MODEL_SPECIAL_RULES = {
- "EN156-3300": [(2.7, 450)], # 示例:风速 < 2.7 且功率 > 450 时追加逻辑异常
- }
- # 传感器异常列与对应可读标签(内部使用,不输出)
- _SENSOR_COL_LABEL: Dict[str, str] = {
- "d_val_power": "功率值异常",
- "d_val_wind": "风速值异常",
- "d_val_pitch": "变桨值异常",
- "d_val_spd": "转速值异常",
- "d_val_torque": "扭矩值异常",
- "d_logic_wind_pwr": "风速功率逻辑异常",
- "d_logic_torque_pwr": "转速扭矩逻辑异常",
- }
- # ==================== 阈值加载(静态数据) ====================
- # 静态阈值数据(从机型参数阈值表.xlsx转换)
- _THRESHOLD_CACHE: Dict[str, dict] = {
- "CCWE1500-82.DF": {
- "p_max_observed": 1554.0,
- "torque_limit": 17924.0,
- "spd_limit": 1861.59,
- "baseline_pitch": 0.04
- },
- "CCWE3000-122.HD": {
- "p_max_observed": 3047.0,
- "torque_limit": None,
- "spd_limit": 362.3,
- "baseline_pitch": 0.19
- },
- "DEW-D10000-185": {
- "p_max_observed": 10135.3,
- "torque_limit": 10841040.0,
- "spd_limit": 10.45,
- "baseline_pitch": -0.5
- },
- "DEW-D3800-148": {
- "p_max_observed": 3508.8,
- "torque_limit": 3320535.25,
- "spd_limit": 11.42,
- "baseline_pitch": 0.0
- },
- "DEW-G3600-165": {
- "p_max_observed": 3730.5,
- "torque_limit": 21010.0,
- "spd_limit": 1838.41,
- "baseline_pitch": 0.15
- },
- "DEW-G5500-183": {
- "p_max_observed": 5548.6,
- "torque_limit": 32636.0,
- "spd_limit": 1746.57,
- "baseline_pitch": 2.85
- },
- "DEW-G6250-186": {
- "p_max_observed": 6314.0,
- "torque_limit": 92110.56,
- "spd_limit": 710.82,
- "baseline_pitch": 0.1
- },
- "DEW-H8350-A-212": {
- "p_max_observed": 7085.1,
- "torque_limit": 125065.52,
- "spd_limit": 8.82,
- "baseline_pitch": 1.89
- },
- "DF103-2000": {
- "p_max_observed": 2024.2,
- "torque_limit": 1264956.0,
- "spd_limit": 16.37,
- "baseline_pitch": -0.5
- },
- "DF131-2500": {
- "p_max_observed": 2527.9,
- "torque_limit": 2058254.75,
- "spd_limit": 13.25,
- "baseline_pitch": 0.0
- },
- "EN141-2650": {
- "p_max_observed": 2744.6,
- "torque_limit": 15705.3,
- "spd_limit": 1778.21,
- "baseline_pitch": -0.79
- },
- "EN156-3300": {
- "p_max_observed": 3359.5,
- "torque_limit": 18575.35,
- "spd_limit": 1835.56,
- "baseline_pitch": -1.45
- },
- "EN171-4500": {
- "p_max_observed": 4559.4,
- "torque_limit": 25390.95,
- "spd_limit": 1795.83,
- "baseline_pitch": -0.85
- },
- "EN182-6250": {
- "p_max_observed": 6326.0,
- "torque_limit": 34601.38,
- "spd_limit": 1802.05,
- "baseline_pitch": -0.25
- },
- "FD77-1500": {
- "p_max_observed": 1534.2,
- "torque_limit": 8058.38,
- "spd_limit": 1846.84,
- "baseline_pitch": 0.0
- },
- "G52-850": {
- "p_max_observed": 851.5,
- "torque_limit": None,
- "spd_limit": 1653.3,
- "baseline_pitch": -2.2
- },
- "G58-850": {
- "p_max_observed": 813.5,
- "torque_limit": None,
- "spd_limit": 1670.4,
- "baseline_pitch": 0.48
- },
- "GW140-2500": {
- "p_max_observed": 2575.0,
- "torque_limit": None,
- "spd_limit": 12.31,
- "baseline_pitch": 3.63
- },
- "GW140-3300": {
- "p_max_observed": 3486.0,
- "torque_limit": None,
- "spd_limit": None,
- "baseline_pitch": 4.25
- },
- "GW191-5000": {
- "p_max_observed": 5004.0,
- "torque_limit": None,
- "spd_limit": 705.6,
- "baseline_pitch": 0.0
- },
- "GW191-6250": {
- "p_max_observed": 6258.6,
- "torque_limit": None,
- "spd_limit": 686.2,
- "baseline_pitch": 2.2
- },
- "GW82-1500": {
- "p_max_observed": 1570.0,
- "torque_limit": None,
- "spd_limit": 18.12,
- "baseline_pitch": 0.2
- },
- "GWH191-5270": {
- "p_max_observed": 4916.6,
- "torque_limit": None,
- "spd_limit": 709.99,
- "baseline_pitch": 0.29
- },
- "GWH191-6700": {
- "p_max_observed": 5905.7,
- "torque_limit": None,
- "spd_limit": 685.9,
- "baseline_pitch": 0.08
- },
- "MY2.0se-121": {
- "p_max_observed": 2079.0,
- "torque_limit": None,
- "spd_limit": 1798.0,
- "baseline_pitch": 0.03
- },
- "MY3.0se-145": {
- "p_max_observed": 3212.0,
- "torque_limit": None,
- "spd_limit": 316.0,
- "baseline_pitch": 0.07
- },
- "MY4.0se-166": {
- "p_max_observed": 4160.0,
- "torque_limit": None,
- "spd_limit": 1095.0,
- "baseline_pitch": -0.45
- },
- "MY5.0se-155": {
- "p_max_observed": 5551.0,
- "torque_limit": None,
- "spd_limit": 284.6,
- "baseline_pitch": 0.0
- },
- "MY5.0se-166": {
- "p_max_observed": 5162.0,
- "torque_limit": 102.4,
- "spd_limit": 1031.1,
- "baseline_pitch": -0.49
- },
- "MY5.2se-166": {
- "p_max_observed": 5242.0,
- "torque_limit": None,
- "spd_limit": None,
- "baseline_pitch": -0.37
- },
- "MY6.25se-172": {
- "p_max_observed": 6346.0,
- "torque_limit": None,
- "spd_limit": 228.6,
- "baseline_pitch": -0.49
- },
- "S48-750": {
- "p_max_observed": 780.0,
- "torque_limit": None,
- "spd_limit": 1524.0,
- "baseline_pitch": 0.0
- },
- "SL1500-82": {
- "p_max_observed": 1544.3,
- "torque_limit": None,
- "spd_limit": 1841.5,
- "baseline_pitch": 0.01
- },
- "SL3000-113": {
- "p_max_observed": 3055.7,
- "torque_limit": None,
- "spd_limit": 1229.26,
- "baseline_pitch": 0.02
- },
- "SWT7000-154": {
- "p_max_observed": 7163.4,
- "torque_limit": None,
- "spd_limit": 10.8,
- "baseline_pitch": -1.69
- },
- "UP1500-82": {
- "p_max_observed": 1502.4,
- "torque_limit": 103.87,
- "spd_limit": 1777.1,
- "baseline_pitch": 0.03
- },
- "UP1500-86": {
- "p_max_observed": 1558.4,
- "torque_limit": 103.53,
- "spd_limit": 1773.6,
- "baseline_pitch": 0.03
- },
- "V100-2000": {
- "p_max_observed": 1999.8,
- "torque_limit": None,
- "spd_limit": 1734.83,
- "baseline_pitch": -2.7
- },
- "V110-2200": {
- "p_max_observed": 2204.1,
- "torque_limit": None,
- "spd_limit": 1757.7,
- "baseline_pitch": 0.9
- },
- "V52-850": {
- "p_max_observed": 854.8,
- "torque_limit": None,
- "spd_limit": 1693.8,
- "baseline_pitch": -1.97
- },
- "V60-850": {
- "p_max_observed": 853.2,
- "torque_limit": None,
- "spd_limit": 1673.9,
- "baseline_pitch": -3.06
- },
- "V80-2000": {
- "p_max_observed": 2043.9,
- "torque_limit": None,
- "spd_limit": 1748.7,
- "baseline_pitch": -1.74
- },
- "V90-2000": {
- "p_max_observed": 2005.7,
- "torque_limit": None,
- "spd_limit": 1730.4,
- "baseline_pitch": -2.16
- },
- "W2000-111": {
- "p_max_observed": 2111.0,
- "torque_limit": 100.04,
- "spd_limit": 1872.0,
- "baseline_pitch": 0.18
- },
- "W2500-135": {
- "p_max_observed": 2566.0,
- "torque_limit": None,
- "spd_limit": 1504.6,
- "baseline_pitch": 0.0
- },
- "W3450-146": {
- "p_max_observed": 3533.0,
- "torque_limit": None,
- "spd_limit": 1604.8,
- "baseline_pitch": 0.2
- },
- "XE122-2500": {
- "p_max_observed": 2568.0,
- "torque_limit": None,
- "spd_limit": 14.32,
- "baseline_pitch": 0.5
- },
- "XE72-2000": {
- "p_max_observed": 2084.0,
- "torque_limit": None,
- "spd_limit": 23.58,
- "baseline_pitch": -0.27
- }
- }
- def get_model_stats(model_name: str) -> dict:
- """根据机型名称获取对应的阈值字典"""
- thresh = _THRESHOLD_CACHE
- if model_name not in thresh:
- raise ValueError(f"未找到机型 {model_name} 的阈值配置")
- return thresh[model_name]
- # ==================== 核心打标函数 ====================
- def _label_dataframe(
- df_input: pd.DataFrame,
- stats: Optional[dict] = None,
- model_name: Optional[str] = None
- ) -> pd.DataFrame:
- """
- 内部打标函数,返回包含所有中间列(d_val_* 等)的完整 DataFrame。
- 参数含义同 apply_labels。
- """
- if stats is None:
- if model_name is None:
- raise ValueError("必须提供 stats 或 model_name")
- stats = get_model_stats(model_name)
- df = df_input.copy()
- P_MAX = stats["p_max_observed"]
- PITCH_BASE = stats["baseline_pitch"]
- # ── 1. 传感器异常列 ────────────────────────────────────────────────────────
- df["d_val_power"] = (
- (df["p_active"] > P_MAX * STATUS_POWER_UPPER_RATIO) |
- (df["p_active"] < P_MAX * STATUS_POWER_LOWER_RATIO)
- )
- if "wind_spd" in df.columns:
- df["d_val_wind"] = (df["wind_spd"] > STATUS_WIND_MAX) | (df["wind_spd"] < STATUS_WIND_MIN)
- else:
- df["d_val_wind"] = False
- pitch_cols = [c for c in df.columns if "pitch_ang_act" in c]
- df["d_val_pitch"] = False
- for col in pitch_cols:
- df["d_val_pitch"] |= (df[col] > STATUS_PITCH_MAX) | (df[col] < STATUS_PITCH_MIN)
- df["d_val_spd"] = False
- if stats["spd_limit"] and "gen_spd" in df.columns:
- df["d_val_spd"] = (
- (df["gen_spd"] > stats["spd_limit"] * STATUS_SPD_UPPER_RATIO) |
- (df["gen_spd"] < -200)
- )
- df["d_val_torque"] = False
- if stats["torque_limit"] and "actual_torque" in df.columns:
- df["d_val_torque"] = (
- (df["actual_torque"] > stats["torque_limit"] * STATUS_TORQUE_UPPER_RATIO) |
- (df["actual_torque"] < STATUS_TORQUE_LOWER_ABS)
- )
- # ── 2. 逻辑悖论列 ──────────────────────────────────────────────────────────
- if "wind_spd" in df.columns:
- df["d_logic_wind_pwr"] = (
- (df["wind_spd"] < STATUS_LOGIC_WIND_MIN) &
- (df["p_active"] > STATUS_LOGIC_POWER_MIN)
- )
- # 风速冻结检测:滑动窗口内风速几乎不变但功率明显不为0(传感器卡死)
- wind_std = df["wind_spd"].rolling(window=60, min_periods=2).std()
- df["d_logic_wind_pwr"] |= (
- (wind_std < 0.05) &
- (df["p_active"] > STATUS_LOGIC_POWER_MIN)
- )
- # 机型特殊规则
- if model_name is not None:
- for wind_thresh, pwr_thresh in STATUS_MODEL_SPECIAL_RULES.get(model_name, []):
- df["d_logic_wind_pwr"] |= (
- (df["wind_spd"] < wind_thresh) & (df["p_active"] > pwr_thresh)
- )
- else:
- df["d_logic_wind_pwr"] = False
- df["d_logic_torque_pwr"] = False
- if stats["torque_limit"] and "actual_torque" in df.columns:
- df["d_logic_torque_pwr"] = (
- (df["p_active"] > P_MAX * 0.1) &
- (df["actual_torque"].abs() < stats["torque_limit"] * 0.01)
- )
- # ── 3. 汇总传感器异常标签 ───────────────────────────
- all_sensor_cols = list(_SENSOR_COL_LABEL.keys())
- existing = [c for c in all_sensor_cols if c in df.columns]
- if existing:
- tag_matrix = np.where(
- df[existing].values,
- np.array([_SENSOR_COL_LABEL[c] for c in existing]),
- "",
- )
- df["sensor_anomaly_tags"] = pd.Series(
- [",".join(t for t in row if t) for row in tag_matrix],
- index=df.index,
- )
- else:
- df["sensor_anomaly_tags"] = ""
- # ── 4. 业务状态打标 ────────────────────────────────────────────────────────
- any_sensor_anomaly = df[[c for c in existing if c in df.columns]].any(axis=1)
- df["label"] = "运行"
- shutdown_thresh = max(50.0, P_MAX * STATUS_SHUTDOWN_RATIO)
- mask_shutdown = (~df["d_val_power"]) & (df["p_active"] <= shutdown_thresh)
- df.loc[mask_shutdown, "label"] = "停机"
- if "pitch_ang_act_1" in df.columns:
- mask_curtail = (
- (df["label"] != "停机") &
- (~df["d_val_power"]) &
- (~df["d_val_pitch"]) &
- (df["p_active"] > P_MAX * STATUS_CURTAIL_LOW_RATIO) &
- (df["p_active"] < P_MAX * STATUS_CURTAIL_HIGH_RATIO) &
- (df["pitch_ang_act_1"] > (PITCH_BASE + STATUS_CURTAIL_PITCH_OFFSET))
- )
- df.loc[mask_curtail, "label"] = "限功率"
- # 传感器异常覆盖
- mask_sensor = any_sensor_anomaly
- df.loc[mask_sensor, "label"] = "传感器异常"
- # 新增 sensor_tag 列:7位整数,每位1=正常,2=异常
- # 7位整数从左至右 分别为功率值异常(最高位)、风速值异常、变桨值异常、转速值异常、扭矩值异常、风速功率逻辑异常、转速扭矩逻辑异常(最低位)
- sensor_order = [
- 'd_val_power', 'd_val_wind', 'd_val_pitch', 'd_val_spd', 'd_val_torque', 'd_logic_wind_pwr',
- 'd_logic_torque_pwr'
- ]
- # 确保这些列都存在
- tag_str_series = df[sensor_order].apply(
- lambda row: ''.join('2' if x else '1' for x in row), axis=1
- )
- df['sensor_tag'] = tag_str_series.astype(int)
- return df
- def apply_labels(
- df: pd.DataFrame,
- model_name: str
- ) -> pd.DataFrame:
- """
- 便捷打标函数:传入原始 DataFrame 和机型名称,返回打标后的 DataFrame。
- 返回的 DataFrame 包含原始数据的所有列,以及新增的 `label` 列(最终业务状态)。
- 参数
- ----------
- df : pd.DataFrame
- 原始数据,必须包含 p_active 列,可选 wind_spd, pitch_ang_act_1, gen_spd, actual_torque 等。
- model_name : str
- 机型名称(需与静态数据中的机型名称一致)。
- 返回
- -------
- pd.DataFrame
- 包含原始数据列 + label 列的 DataFrame。
- """
- # 调用内部打标函数,获取完整结果(包含中间列)
- full_labeled = _label_dataframe(df, model_name=model_name)
- # 只保留原始列和 label 列
- result_cols = list(df.columns) + ['label', 'sensor_tag']
- return full_labeled[result_cols]
|