# -*- coding: utf-8 -*- """ Created on Mon Apr 8 15:01:43 2024 @author: LDDN """ import math import pandas as pd import numpy as np import matplotlib.pyplot as plt from matplotlib.pyplot import MultipleLocator#设定固定刻度 scada_10min = pd.read_csv(r'E:\BaiduNetdiskDownload\test\min_scada_LuoTuoGou\72\82.csv',encoding="utf-8") #.value是将单元格 turbine_info = pd.read_csv(r'E:\BaiduNetdiskDownload\test\min_scada_LuoTuoGou\72\info.csv') #.value是将单元格 PRated = turbine_info.loc[:,["额定功率"]] #2000 PRated = PRated.values VCutOut = turbine_info.loc[:,["切出风速"]] #25 VCutOut = VCutOut.values VCutIn = turbine_info.loc[:,["切入风速"]] #3 VCutIn = VCutIn.values VRated = turbine_info.loc[:,["额定风速"]] #10 VRated = VRated.values time_stamp = scada_10min.loc[:,['时间']] #dataframe active_power = scada_10min.loc[:,['变频器电网侧有功功率']] wind_speed = scada_10min.loc[:,['风速']] LM = pd.concat([time_stamp,active_power,wind_speed],axis=1) #dataframe Labeled_March809 = LM APower = Labeled_March809["变频器电网侧有功功率"] #series读入有功功率 WSpeed = Labeled_March809["风速"] #读入风速 maxP=np.max(APower) intervalP=25 #ceil(PRated*0.01)#功率分区间隔为额定功率的1% intervalwindspeed=0.25 #风速分区间隔0.25m/s #初始化 PNum = 0 TopP = 0 # 根据条件计算PNum和TopP if maxP >= PRated: PNum = math.floor(maxP / intervalP) + 1 TopP = math.floor((maxP - PRated) / intervalP) + 1 else: PNum = math.floor(PRated / intervalP) TopP = 0 VNum = math.ceil(VCutOut / intervalwindspeed) SM1 = Labeled_March809.shape AA1 = SM1[0] lab = [[0] for _ in range(AA1)] lab = pd.DataFrame(lab,columns=['lab']) Labeled_March809 = pd.concat([Labeled_March809,lab],axis=1) #在tpv后加一列标签列 Labeled_March809 = Labeled_March809.values SM = Labeled_March809.shape #(52561,4) AA = SM[0] #存储功率大于0的运行数据 #标识功率为0的点,标识-1 DzMarch809_0 = np.zeros((AA, 3)) # 初始化数组来存储功率大于零的运行数据 nCounter1 = 0 Point_line = np.zeros(AA, dtype=int) #考虑到很多功率小于10的数据存在,将<10的功率视为0 for i in range(AA): if (APower[i] > 10) & (WSpeed[i] > 0): nCounter1 += 1 #共有nCounter1个功率大于0的正常数据 DzMarch809_0[nCounter1-1, 0] = WSpeed[i] DzMarch809_0[nCounter1-1, 1] = APower[i] Point_line[nCounter1-1] = i+1 # 记录nCounter1记下的数据在原始数据中的位置 if APower[i] <= 10: Labeled_March809[i,SM[1]-1] = -1 # 功率为0标识为-1 array类型 # 截取DzMarch809_0中实际存储的数据 其他全为0 DzMarch809 = DzMarch809_0[:nCounter1, :] #统计各网格落入的散点个数 XBoxNumber = np.ones((PNum, VNum),dtype=int) #(86 100) nWhichP = 0 nWhichV = 0 # 循环遍历DzMarch809中的有效数据 for i in range(nCounter1): # 查找功率所在的区间 for m in range(1, PNum + 1): # 注意Python的range是左闭右开的,所以需要+1 if (DzMarch809[i,1] > (m - 1) * intervalP) and (DzMarch809[i,1] <= m * intervalP): nWhichP = m break # 查找风速所在的区间 for n in range(1, VNum + 1): # 同样需要+1 if (DzMarch809[i, 0] > (n - 1)*intervalwindspeed) and (DzMarch809[i, 0] <= n*intervalwindspeed): nWhichV = n break # 如果功率和风速都在有效区间内,增加对应网格的计数 if (nWhichP > 0) and (nWhichV > 0): XBoxNumber[nWhichP - 1, nWhichV - 1] += 1 # 注意Python的索引是从0开始的,所以需要减1 # XBoxNumber现在包含了每个网格的计数[PNum行, VNum列] for m in range(1,PNum+1): for n in range(1,VNum+1): XBoxNumber[m-1,n-1] = XBoxNumber[m-1,n-1] - 1 #在功率方向将网格内散点绝对个数转换为相对百分比,备用 PBoxPercent = np.zeros((PNum, VNum),dtype = float) #(86 100) #计算后会出现浮点型,所以不能定义int类型 PBinSum = np.zeros((PNum,1),dtype=int) for i in range(1,PNum+1): for m in range(1,VNum+1): PBinSum[i-1] = PBinSum[i-1] + XBoxNumber[i-1,m-1] for m in range(1,VNum+1): if PBinSum[i-1]>0: PBoxPercent[i-1,m-1] = (XBoxNumber[i-1,m-1] / PBinSum[i-1])*100 #在风速方向将网格内散点绝对个数转换为相对百分比,备用 VBoxPercent = np.zeros((PNum, VNum)) #(86 100) #计算后会出现浮点型,所以不能定义int类型 VBinSum = np.zeros((VNum,1),dtype=int) for i in range(1,VNum+1): for m in range(1,PNum+1): VBinSum[i-1] = VBinSum[i-1] + XBoxNumber[m-1,i-1] for m in range(1,PNum+1): if VBinSum[i-1]>0: VBoxPercent[m-1,i-1] = (XBoxNumber[m-1,i-1] / VBinSum[i-1])*100 # VBoxPercent PBoxPercent 左上-右下 # 将数据颠倒一下 左下-右上 第一行换为倒数第一行 方便可视化 InvXBoxNumber = np.zeros((PNum,VNum),dtype = int) InvPBoxPercent = np.zeros((PNum,VNum),dtype = float) InvVBoxPercent = np.zeros((PNum,VNum),dtype = float) for m in range(1,PNum+1): for n in range(1,VNum+1): InvXBoxNumber[m-1,n-1] = XBoxNumber[PNum-(m-1)-1,n-1] InvPBoxPercent[m-1,n-1] = PBoxPercent[PNum-(m-1)-1,n-1] InvVBoxPercent[m-1,n-1] = VBoxPercent[PNum-(m-1)-1,n-1] #以水平功率带方向为准,分析每个水平功率带中,功率主带中心,即找百分比最大的网格位置。 PBoxMaxIndex = np.zeros((PNum,1),dtype = int) #水平功率带最大网格位置索引 PBoxMaxP = np.zeros((PNum,1),dtype = float) #水平功率带最大网格百分比 for m in range(1,PNum+1): PBoxMaxIndex[m-1] = np.argmax(PBoxPercent[m-1, :]) #argmax返回最大值的索引 PBoxMaxP[m-1] = np.max(PBoxPercent[m-1, :]) #以垂直风速方向为准,分析每个垂直风速带中,功率主带中心,即找百分比最大的网格位置。 VBoxMaxIndex = np.zeros((VNum,1),dtype = int) VBoxMaxV = np.zeros((VNum,1),dtype = float) for m in range(1,VNum+1): VBoxMaxIndex[m-1] = np.argmax(VBoxPercent[:, m-1]) VBoxMaxV[m-1] = np.max(VBoxPercent[:, m-1]) #切入风速特殊处理,如果切入风速过于偏右,向左拉回 if PBoxMaxIndex[0]>14: #第一个值对应的是风速最小处 即切入风速 PBoxMaxIndex[0] = 9 #以水平功率带方向为基准,进行分析 DotDense = np.zeros(PNum) #每一水平功率带的功率主带包含的网格数 DotDenseLeftRight = np.zeros((PNum,2)) #存储每一水平功率带的功率主带以最大网格为中心,向左,向右扩展的网格数 DotValve = 90 #从中心向左右对称扩展网格的散点百分比和的阈值。 PDotDenseSum = 0 for i in range(PNum - TopP): # 从最下层水平功率带开始,向上分析到特定的功率带 PDotDenseSum = PBoxMaxP[i] # 以中心最大水平功率带为基准,向左向右对称扩展网格,累加各网格散点百分比 iSpreadRight = 1 iSpreadLeft = 1 while PDotDenseSum < DotValve: if (PBoxMaxIndex[i] + iSpreadRight) < VNum-1-1: PDotDenseSum += PBoxPercent[i, PBoxMaxIndex[i] + iSpreadRight] # 向右侧扩展 iSpreadRight += 1 else: break if (PBoxMaxIndex[i] - iSpreadLeft) > 0: PDotDenseSum += PBoxPercent[i, PBoxMaxIndex[i] - iSpreadLeft] # 向左侧扩展 iSpreadLeft += 1 else: break iSpreadRight = iSpreadRight-1 iSpreadLeft = iSpreadLeft-1 #向左右扩展完毕 DotDenseLeftRight[i, 0] = iSpreadLeft # 左 DotDenseLeftRight[i, 1] = iSpreadRight # 右 DotDense[i] = iSpreadLeft + iSpreadRight + 1 # 记录向左向右扩展的个数及每个功率仓内网格的个数 # 此时DotDense和DotDenseLeftRight数组已经包含了所需信息 #各行功率主带右侧宽度的中位数最具有代表性(因为先右后左) DotDenseWidthLeft = np.zeros((PNum-TopP)) for i in range(PNum-TopP): DotDenseWidthLeft[i] = DotDenseLeftRight[i,1] #DotDenseLeftRight[i,1]:向右延伸个数 MainBandRight = np.median(DotDenseWidthLeft) #计算中位数 # 初始化变量 PowerLimit = np.zeros(PNum, dtype=int) # 各水平功率带是否为限功率标识,1:是;0:不是 WidthAverage = 0 # 功率主带右侧平均宽度 WidthAverage_L = 0 # 功率主带左侧平均宽度 WidthVar = 0 # 功率主带方差(此变量在提供的代码中并未使用) PowerLimitValve = 6 # 限功率主带判别阈值 N_Pcount = 20 # 阈值 nCounterLimit = 0 # 限功率的个数 nCounter = 0 # 正常水平功率带的个数 # 循环遍历水平功率带,从第1个到第PNum-TopP个 for i in range(PNum - TopP): # 如果向右扩展网格数大于阈值,且该水平功率带点总数大于20,则标记为限功率带 if (DotDenseLeftRight[i, 1] > PowerLimitValve) and (PBinSum[i] > N_Pcount): PowerLimit[i] = 1 nCounterLimit += 1 #限功率的个数 # 如果向右扩展网格数小于等于阈值,则累加右侧宽度(左侧宽度在代码中似乎有误) if DotDenseLeftRight[i, 1] <= PowerLimitValve: WidthAverage += DotDenseLeftRight[i, 1] # 统计正常水平功率带右侧宽度 WidthAverage_L += DotDenseLeftRight[i,1] #统计正常水平功率带左侧宽度 nCounter += 1 # 计算平均宽度 WidthAverage /= nCounter if nCounter > 0 else 1 # 避免除以0的情况 WidthAverage_L /= nCounter if nCounter > 0 else 1 #计算正常(即非限功率)水平功率带的功率主带宽度的方差,以此来反映从下到上宽度是否一致 WidthVar = 0 # 功率主带宽度的方差 for i in range(PNum - TopP): # 如果向右扩展网格数小于等于阈值,则计算当前宽度与平均宽度的差值平方 if DotDenseLeftRight[i, 1] <= PowerLimitValve: WidthVar += (DotDenseLeftRight[i, 1] - WidthAverage) ** 2 # 计算方差(注意:除以nCounter-1是为了得到样本方差) WidthVar = np.sqrt(WidthVar / (nCounter - 1) if nCounter > 1 else 0) # 避免除以0的情况 #各水平功率带,功率主带的风速范围,右侧扩展网格数*2*0.25 PowerBandWidth = WidthAverage*intervalwindspeed+WidthAverage_L*intervalwindspeed # 对限负荷水平功率带的最大网格进行修正 for i in range(1, PNum - TopP+1): if (PowerLimit[i] == 1) and (abs(PBoxMaxIndex[i] - PBoxMaxIndex[i - 1]) > 5): PBoxMaxIndex[i] = PBoxMaxIndex[i - 1] + 1 # 输出各层功率主带的左右边界网格索引 DotDenseInverse = np.flipud(DotDenseLeftRight) # 上下翻转数组以得到反向顺序 # 计算功率主带的左右边界 CurveWidthR = np.ceil(WidthAverage) + 2 # 功率主带的右边界 + 2 CurveWidthL = np.ceil(WidthAverage_L) + 2 # 功率主带的左边界 + 2 # 网格是否为限功率网格的标识数组 BBoxLimit = np.zeros((PNum, VNum), dtype=int) # 标记限功率网格 for i in range(2, PNum - TopP): if PowerLimit[i] == 1: BBoxLimit[i, int(PBoxMaxIndex[i] + CurveWidthR + 1):VNum] = 1 # 初始化数据异常需要剔除的网格标识数组 BBoxRemove = np.zeros((PNum, VNum), dtype=int) # 标记需要剔除的网格 for m in range(PNum - TopP): for n in range(int(PBoxMaxIndex[m]) + int(CurveWidthR), VNum): # 注意Python中的索引从0开始,因此需要减去1 BBoxRemove[m, n] = 1 # 功率主带左侧的超发网格,从最大索引向左直到第一个网格 for n in range(int(PBoxMaxIndex[m]) - int(CurveWidthL)+1, 0, -1): # 使用range的步长参数来实现从右向左的迭代 BBoxRemove[m, n-1] = 2 # 注意Python中的索引从0开始,因此需要减去1 # 初始化变量 CurveTop = np.zeros((2, 1), dtype=int) CurveTopValve = 1 # 网格的百分比阈值 BTopFind = 0 mm = 0 #确定功率主带的左上拐点,即额定风速位置的网格索引 CurveTop = np.zeros((2, 1), dtype=int) CurveTopValve = 1 # 网格的百分比阈值 BTopFind = 0 mm = 0 for m in range(PNum - TopP, 0, -1): # 注意Python的range是左闭右开区间,所以这里从PNum-TopP开始到1(不包括0) for n in range(int(np.floor(int(VCutIn) / intervalwindspeed)), VNum - 1): # 使用floor函数来向下取整 if (VBoxPercent[m, n - 1] < VBoxPercent[m, n]) and (VBoxPercent[m, n] <= VBoxPercent[m, n + 1]) and (XBoxNumber[m, n] >= 3): CurveTop[0] = m CurveTop[1] = n #[第80个,第40个] BTopFind = 1 mm = m # mm是拐点所在功率仓,对应其index break # 找到后退出内层循环 if BTopFind == 1: break # 找到后退出外层循环 IsolateValve = 3 #功率主带右侧孤立点占比功率仓阈值 3% # 遍历功率仓和网格 for m in range(PNum - TopP): for n in range(int(PBoxMaxIndex[m]) + int(CurveWidthR), VNum): # 检查PBoxPercent是否小于阈值,如果是,则标记BBoxRemove为1 if PBoxPercent[m, n] < IsolateValve: BBoxRemove[m, n] = 1 #功率主带顶部宽度 CurveWidthT = np.floor((maxP - PRated) / intervalP) + 1 # 标记额定功率以上的超发点(PNum-PTop之间) for m in range(PNum - TopP, PNum): for n in range(VNum): BBoxRemove[m, n] = 3 # 标记功率主带拐点左侧的欠发网格 for m in range(mm-1, PNum - TopP): for n in range(int(CurveTop[1]) - 2): BBoxRemove[m, n] = 2 # BBoxRemove数组现在包含了根据条件标记的超发点和欠发网格的信息 #以网格的标识,决定该网格内数据的标识。 # DzMarch809Sel数组现在包含了每个数据点的标识 DzMarch809Sel = np.zeros(nCounter1, dtype=int) # 初始化标识数组 nWhichP = 0 nWhichV = 0 nBadA = 0 for i in range(nCounter1): for m in range( PNum ): if (DzMarch809[i, 1] > m * intervalP) and (DzMarch809[i, 1] <= (m+1) * intervalP): nWhichP = m #m记录的是index break for n in range( VNum ): # 注意Python的range是左闭右开区间,所以这里到VNum+1 if DzMarch809[i, 0] > ((n+1) * intervalwindspeed - intervalwindspeed/2) and DzMarch809[i, 0] <= ((n+1) * intervalwindspeed + intervalwindspeed / 2): nWhichV = n #index break if nWhichP >= 0 and nWhichV >= 0: if BBoxRemove[nWhichP, nWhichV] == 1: DzMarch809Sel[i] = 1 nBadA += 1 elif BBoxRemove[nWhichP, nWhichV] == 2: DzMarch809Sel[i] = 2 elif BBoxRemove[nWhichP , nWhichV] == 3: DzMarch809Sel[i] = 0 # 额定风速以上的超发功率点认为是正常点,不再标识 # DzMarch809Sel数组现在包含了每个数据点的标识 ##############################滑动窗口方法 # 存储限负荷数据 PVLimit = np.zeros((nCounter1, 3)) #存储限负荷数据 %第3列用于存储限电的点所在的行数 nLimitTotal = 0 nWindowLength = 6 #滑动窗口长度设置为6 LimitWindow = np.zeros(nWindowLength) #滑动窗口空列表 UpLimit = 0 #上限 LowLimit = 0 #下限 PowerStd = 30 # 功率波动方差 nWindowNum = np.floor(nCounter1/nWindowLength) #6587 PowerLimitUp = PRated - 100 PowerLimitLow = 100 # 循环遍历每个窗口 for i in range(int(nWindowNum)): start_idx = i * nWindowLength end_idx = start_idx + nWindowLength LimitWindow = DzMarch809[start_idx:end_idx, 1] # 提取当前窗口的数据 # 检查窗口内所有数据是否在功率范围内 bAllInAreas = np.all(LimitWindow >= PowerLimitLow) and np.all(LimitWindow <= PowerLimitUp) if not bAllInAreas: continue # 计算方差上下限 UpLimit = LimitWindow[0] + PowerStd LowLimit = LimitWindow[0] - PowerStd # 检查窗口内数据是否在方差范围内 bAllInUpLow = np.all(LimitWindow >= LowLimit) and np.all(LimitWindow <= UpLimit) if bAllInUpLow: # 标识窗口内的数据为限负荷数据 DzMarch809Sel[start_idx:end_idx] = 4 # 存储限负荷数据 for j in range(nWindowLength): PVLimit[nLimitTotal, :2] = DzMarch809[start_idx + j, :2] PVLimit[nLimitTotal, 2] = Point_line[start_idx + j] # 对数据进行标识 nLimitTotal += 1 # PVLimit现在包含了限负荷数据,nLimitTotal是限负荷数据的总数 #将功率滑动窗口主带平滑化 # 初始化锯齿平滑的计数器 nSmooth = 0 # 遍历除了最后 TopP+1 个元素之外的所有 PBoxMaxIndex 元素 for i in range(PNum - TopP - 1): PVLeftDown = np.zeros(2) PVRightUp = np.zeros(2) # 检查当前与下一个 PBoxMaxIndex 之间的距离是否大于等于1 if PBoxMaxIndex[i + 1] - PBoxMaxIndex[i] >= 1: # 计算左下和右上顶点的坐标 PVLeftDown[0] = (PBoxMaxIndex[i]+1 + CurveWidthR) * 0.25 - 0.125 PVLeftDown[1] = (i) * 25 PVRightUp[0] = (PBoxMaxIndex[i+1]+1 + CurveWidthR) * 0.25 - 0.125 PVRightUp[1] = (i+1) * 25 # 遍历 DzMarch809 数组 for m in range(nCounter1): # 检查当前点是否在锯齿区域内 if (DzMarch809[m, 0] > PVLeftDown[0]) and (DzMarch809[m, 0] < PVRightUp[0]) and (DzMarch809[m, 1] > PVLeftDown[1]) and (DzMarch809[m, 1] < PVRightUp[1]): # 检查斜率是否大于对角连线 if (DzMarch809[m, 1] - PVLeftDown[1]) / (DzMarch809[m, 0] - PVLeftDown[0]) > (PVRightUp[1] - PVLeftDown[1]) / (PVRightUp[0] - PVLeftDown[0]): # 如果在锯齿左上三角形中,则选中并增加锯齿平滑计数器 DzMarch809Sel[m] = 0 nSmooth += 1 # DzMarch809Sel 数组现在+包含了锯齿平滑的选择结果,nSmooth 是选中的点数 ###################################存储数据 # 存储好点 nCounterPV = 0 # 初始化计数器 PVDot = np.zeros((nCounter1, 3)) # 初始化存储好点的数组 nCounter1是p>0的数 for i in range(nCounter1): if DzMarch809Sel[i] == 0: nCounterPV += 1 PVDot[nCounterPV-1, :2] = DzMarch809[i, :2] PVDot[nCounterPV-1, 2] = Point_line[i] # 好点 Point_line记录nCounter1在原始数据中的位置 nCounterVP = nCounterPV # 对所有数据中的好点进行标注 for i in range(nCounterVP): Labeled_March809[int(PVDot[i, 2] - 1), (SM[1]-1)] = 1 # 注意Python的索引从0开始,并且需要转换为整数索引 # 存储坏点 nCounterBad = 0 # 初始化计数器 PVBad = np.zeros((nCounter1, 3)) # 初始化存储坏点的数组 for i in range(nCounter1): if DzMarch809Sel[i] in [1, 2, 3]: nCounterBad += 1 PVBad[nCounterBad-1, :2] = DzMarch809[i, :2] PVBad[nCounterBad-1, 2] = Point_line[i] # 对所有数据中的坏点进行标注 for i in range(nCounterBad): Labeled_March809[int(PVBad[i, 2] - 1),(SM[1]-1)] = 5 # 坏点标识 # 对所有数据中的限电点进行标注 for i in range(nLimitTotal): Labeled_March809[int(PVLimit[i, 2] - 1),(SM[1]-1)] = 4 # 限电点标识 # 对所有的数据点进行标注 # Labeled_March809是array,提取所第四列的值保存为dataframe A = Labeled_March809[:,3] A=pd.DataFrame(A,columns=['lab']) mergedTable = pd.concat([scada_10min,A],axis=1)#合并dataframe D = mergedTable[mergedTable['lab'] == 1]#选择为1的行 ws = D["风速"].values #array ap = D["变频器电网侧有功功率"] # fig=plt.figure(figsize=(10,6),dpi=500) #figsize是图形大小,dpi像素 fig=plt.figure() #figsize是图形大小,dpi像素 plt.scatter(ws,ap,s=1,c='black',marker='.') #'.'比'o'要更小 # plt.scatter(x2,y2,s=10,c='b',marker='.',label='5.8-6.5建模噪声点') x_major_locator=MultipleLocator(5) y_major_locator=MultipleLocator(500) ax=plt.gca() ax.xaxis.set_major_locator(x_major_locator) ax.yaxis.set_major_locator(y_major_locator) plt.xlim((0,30)) plt.ylim((0,2200)) plt.tick_params(labelsize=8) # plt.grid(c='dimgray',alpha=0.2) plt.xlabel("V/(m$·$s$^{-1}$)",fontsize=8) plt.ylabel("P/kW",fontsize=8) # plt.savefig(r'D:\赵雅丽\研究生学习资料\学习资料\劣化度健康度\spyder\大论文\图\风速-功率.jpg',bbox_inches='tight') plt.show()