4. 数据质量控制(QC):野点剔除、时间一致性检查、相关性分析、卡塔与冰冻数据处理

各位好,我是老张。干风资源评估这行十几年了,要说哪个环节最磨人,我肯定投数据质量控制一票。你想想看,测风塔往那一立,风吹日晒雨淋,什么稀奇古怪的数据都能给你整出来。要是前期QC没做好,后面画出来的风图谱就是空中楼阁,根本没法用。

今天咱们就聊聊数据质量控制这四大块:野点剔除、时间一致性检查、相关性分析,还有卡塔和冰冻数据的处理。嗯,都是实打实的硬功夫。

数据质量控制(QC)四大核心 ① 野点剔除 Range Check 阈值过滤 + 物理极限 ② 时间一致性 Trend Check 突变检测 + 持续异常 ③ 相关性分析 相邻塔对比 R² > 0.8 为合格 ④ 卡塔与冰冻 Calm / Icing 特殊工况处理 目标:数据有效率 ≥ 90%,年数据完整率 ≥ 95% 为风资源图谱提供干净、可靠的基础数据

4.1 野点剔除(Range Check)—— 先把明显离谱的干掉

野点剔除,说白了就是把那些明显不符合物理常识的数据直接扔掉。风速出现负值?风速超过80m/s?温度显示零下80度?这些数据留着只会污染你的分析结果。

我个人习惯的做法是分两步走:

  • 硬阈值过滤:根据仪器量程和当地气候特征,设定绝对不可逾越的上下限。比如风速传感器量程是0-60m/s,那超过60m/s的直接标记为无效。
  • 软阈值过滤:结合当地气候统计特征,比如该地区历史最大风速从未超过35m/s,那突然冒出来一个50m/s的数据,大概率是传感器故障或雷击干扰。

核心原则:宁可错杀一千,不可放过一个。野点对平均风速、风功率密度的影响是灾难性的,尤其是对湍流强度的影响,一个野点就能把整年的湍流强度拉高好几个百分点。

我在内蒙古一个项目上遇到过,测风塔10m高度风速数据里,连续三天出现40m/s以上的记录。当时现场人员说是大风天气,我一看风向数据——完全没变化,而且相邻塔同高度风速只有8m/s。后来一查,是传感器轴承卡死了,风杯被吹得乱转。这就是典型的野点,必须剔除。

# Python 示例:野点剔除的简单实现
import pandas as pd
import numpy as np

def range_check(df, col='ws', lower=0, upper=60):
    """
    野点剔除函数
    lower: 风速下限(m/s)
    upper: 风速上限(m/s)
    """
    mask = (df[col] >= lower) & (df[col] <= upper)
    df['qc_flag'] = np.where(mask, 1, 0)
    return df

# 使用示例
data = pd.read_csv('wind_data.csv')
data = range_check(data, 'ws_80m', lower=0, upper=50)
print(f"有效数据占比: {data['qc_flag'].mean()*100:.1f}%")

4.2 时间一致性检查(Trend Check)—— 揪出那些"抽风"的数据

野点剔除解决的是"明显错误",但有些数据看着在合理范围内,实际上却有问题。比如风速从5m/s突然跳到25m/s,下一秒又回到6m/s。这种"跳变"数据,你想想看,自然界里风的变化再剧烈,也不可能在10分钟内完成这种幅度的变化。

时间一致性检查,就是通过相邻时间点的数据对比,找出那些变化速率异常的数据点。我常用的方法有:

  • 一阶差分法:计算相邻两个时间点的差值,超过设定阈值(比如3m/s/10min)则标记为可疑。
  • 滑动窗口法:以当前点为中心,取前后各2-3个点,计算局部均值和标准差,当前点偏离均值超过3倍标准差则标记。
  • 持续异常检测:如果连续多个时间点数据都异常(比如连续6小时风速为0),那可能是传感器结冰或停机了。

我的小技巧:阈值设定不要一刀切。强风天气下风速变化本来就大,弱风天气下变化幅度小。我一般会根据风速大小动态调整阈值——风速越大,允许的变化幅度也越大。

嗯,这里要注意一点:时间一致性检查不能做得太激进。我曾经在一个山地项目上,把变化超过2m/s/10min的数据全标记了,结果发现大量有效数据被误删。后来一分析,山地的阵风效应本来就强,10分钟内风速变化3-4m/s是正常现象。所以阈值一定要结合地形和气候特征来定。

# 时间一致性检查示例
def trend_check(df, col='ws', max_change=5):
    """
    max_change: 10分钟内允许的最大风速变化(m/s)
    """
    df['ws_diff'] = df[col].diff().abs()
    df['trend_flag'] = np.where(df['ws_diff'] <= max_change, 1, 0)
    return df

# 动态阈值版本
def trend_check_dynamic(df, col='ws', base_change=3, factor=0.2):
    """
    根据风速大小动态调整阈值
    """
    df['threshold'] = base_change + factor * df[col].shift(1)
    df['ws_diff'] = df[col].diff().abs()
    df['trend_flag'] = np.where(df['ws_diff'] <= df['threshold'], 1, 0)
    return df

4.3 相关性分析(相邻塔对比)—— 让数据互相"作证"

单塔的数据再干净,你也得有个参照物来验证。相关性分析就是利用相邻测风塔的数据,来交叉验证当前塔的数据质量。说白了,就是让数据互相"作证"。

我一般会这样做:

  1. 选择参考塔:距离目标塔10-30公里范围内,地形和气候条件相似的测风塔。
  2. 时间对齐:确保两座塔的数据时间戳完全对齐,10分钟平均数据要一一对应。
  3. 计算相关系数:用皮尔逊相关系数R来衡量两塔数据的线性相关程度。R² > 0.8通常认为数据质量合格。
  4. 残差分析:如果相关系数高但个别点偏差很大,这些点可能就是问题数据。

注意:相关性分析不能替代前两步的QC。如果参考塔本身数据就有问题,那对比结果也会误导你。我习惯先对参考塔也做一遍野点剔除和时间一致性检查,再用干净的数据来做相关性分析。

我记得在新疆一个项目上,两座塔直线距离只有8公里,但相关系数只有0.3。一开始我以为是数据有问题,后来实地考察才发现,两座塔中间隔着一道山梁,气流被完全阻断了。所以相关性低不一定就是数据质量差,也可能是地形导致的局地气候差异。

# 相关性分析示例
from scipy import stats

def correlation_check(df_target, df_ref, col='ws_80m'):
    """
    计算目标塔与参考塔的相关性
    """
    merged = pd.merge(df_target, df_ref, on='timestamp', suffixes=('_target', '_ref'))
    r, p_value = stats.pearsonr(merged[f'{col}_target'], merged[f'{col}_ref'])
    return r, p_value

# 残差分析:找出偏差大的点
def residual_analysis(df_target, df_ref, col='ws_80m', threshold=3):
    merged = pd.merge(df_target, df_ref, on='timestamp', suffixes=('_target', '_ref'))
    merged['residual'] = abs(merged[f'{col}_target'] - merged[f'{col}_ref'])
    mean_res = merged['residual'].mean()
    std_res = merged['residual'].std()
    merged['outlier'] = merged['residual'] > (mean_res + threshold * std_res)
    return merged

4.4 卡塔(Calm)与冰冻数据处理—— 特殊工况不能一刀切

卡塔和冰冻,是风资源评估里两个让人头疼的问题。处理不好,要么低估了风资源,要么高估了发电量。

4.4.1 卡塔数据(Calm)

卡塔指的是风速低于传感器启动风速(通常0.5-1.0m/s)的情况。这时候风速计不转了,记录的是0或者一个很小的值。但实际风并没有完全停止,只是传感器测不到了。

我的处理原则是:

  • 保留卡塔数据:不要直接删除,因为卡塔时间占比虽然小(通常1-5%),但对年平均风速有影响。
  • 标记但不剔除:在数据标记中注明"calm",后续计算时按0风速处理。
  • 检查卡塔比例:如果某个高度卡塔比例超过10%,那就要怀疑传感器是不是出问题了。

4.4.2 冰冻数据(Icing)

冰冻比卡塔麻烦得多。传感器结冰后,风杯转不动,记录的数据要么是0,要么是异常低值。而且冰冻往往持续几个小时甚至几天,对数据完整性的影响很大。

我处理冰冻数据的经验是:

  1. 识别冰冻事件:结合温度数据(低于0°C)、风速骤降、风向锁定等特征来判断。
  2. 标记冰冻时段:将冰冻期间的数据标记为"icing",不参与正常统计。
  3. 插值或替代:如果冰冻时间短(< 6小时),可以用前后数据线性插值;如果时间长,需要用相邻塔或再分析数据来替代。
  4. 记录冰冻损失:在报告中明确说明冰冻导致的数据缺失比例,这对后续发电量评估很重要。

避坑指南:我曾经在东北一个项目上,冬天连续20天的数据都是0风速。当时以为是传感器坏了,后来才发现是结冰导致的。但因为处理不及时,这20天的数据被直接删除了,导致冬季平均风速被严重低估,发电量预测偏差了15%以上。所以,冰冻数据一定要单独处理,不能简单删除。

# 冰冻数据识别与处理示例
def icing_detection(df, temp_col='temp', ws_col='ws_80m', temp_threshold=0):
    """
    基于温度识别冰冻事件
    """
    df['icing'] = (df[temp_col] < temp_threshold) & (df[ws_col] < 0.5)
    # 连续冰冻标记:如果连续3个点都满足条件,认为是冰冻事件
    df['icing_event'] = df['icing'].rolling(window=3, min_periods=1).sum() >= 3
    return df

def icing_imputation(df, ws_col='ws_80m', max_gap=6):
    """
    冰冻数据插值:仅对短时间冰冻进行插值
    """
    df[ws_col + '_imputed'] = df[ws_col].copy()
    # 对冰冻事件内的数据进行插值
    df.loc[df['icing_event'], ws_col + '_imputed'] = np.nan
    # 线性插值,限制最大插值长度
    df[ws_col + '_imputed'] = df[ws_col + '_imputed'].interpolate(
        method='linear', limit=max_gap
    )
    return df

4.5 数据质量报告—— 让QC结果可追溯

QC做完了,不能拍拍屁股走人。你得把整个过程记录下来,形成数据质量报告。我一般会包含以下内容:

QC项目 剔除/标记数量 占比 备注
原始数据总量 52560 100% 10分钟数据,全年
野点剔除 128 0.24% 风速>50m/s或<0
时间一致性 256 0.49% 变化>5m/s/10min
卡塔数据 1051 2.00% 风速<0.5m/s
冰冻数据 2102 4.00% 温度<0°C且风速<0.5m/s
有效数据 49023 93.27% 满足QC要求

数据质量报告不仅是给别人看的,更是给自己留个底。万一后续分析结果有问题,你可以回溯到QC环节,看看是不是哪个阈值设得太严或太松了。

好了,数据质量控制这部分就聊到这儿。记住一句话:垃圾进,垃圾出。QC花的时间越多,后面的分析就越省心。别嫌麻烦,这是风资源评估的根基。


专注资料整理