--- layout: default title: "TimVar for Elevated PTS" parent: "Point Sources" grand_parent: TEDS Python nav_order: 2 date: last_modified_date: 2021-12-06 12:09:47 tags: CAMx ptse TEDS --- # 高空點源之時變係數 {: .no_toc }
Table of contents {: .text-delta } - TOC {:toc}
--- ## 背景 - 高空點源的**時變係數**骨幹是CEMS數據,然而同一工廠無數據、鄰近工業區其他廠無數據者,亦會參考CEMS設定其**時變係數**。 - 排放量整體處理原則參見[處理程序總綱](https://sinotec2.github.io/Focus-on-Air-Quality/EmisProc/#處理程序總綱)、針對[點源之處理](https://sinotec2.github.io/Focus-on-Air-Quality/EmisProc/ptse/)及[龐大`.dbf`檔案之讀取](https://sinotec2.github.io/Focus-on-Air-Quality/EmisProc/dbf2csv.py/),為此處之前處理。程式也會呼叫到[ptse_sub](https://sinotec2.github.io/Focus-on-Air-Quality/EmisProc/ptse/ptse_sub/)中的副程式 ## 程式說明 ### 程式執行 因排放物質類別與污染源製造程序的特徵有關,必須分開個別處理,此處則以個別污染項目執行`ptseE_ONS.py`,執行方式如下: ```bash for spe in CO NMHC NOX PM SOX;do python ptseE_ONS.py $spe;done ``` - 由於程式消耗記憶體非常大量,如要同時進行,需注意記憶體的使用情形。 - 污染源個數與排放高度限值的設定、地面PM排放條件之給定、以及數據年代等等都有關係,需配套紀錄。 ### 排放與CEMS資料檔之讀取及準備 - 引用模組 - 程式用到[ptse_sub](https://sinotec2.github.io/Focus-on-Air-Quality/EmisProc/ptse/ptse_sub/)中的副程式`CORRECT`, `add_PMS`, `check_nan`, `check_landsea`, `FillNan`, `WGS_TWD`, `Elev_YPM` ```python kuang@node03 /nas1/TEDS/teds11/ptse $ cat -n ptseE_ONS.py 1 2 #! crding = utf8 3 from pandas import * 4 import numpy as np 5 import os, sys, subprocess 6 import netCDF4 7 import twd97 8 import datetime 9 from calendar import monthrange 10 from scipy.io import FortranFile 11 12 from ptse_sub import CORRECT, add_PMS, check_nan, check_landsea, FillNan, WGS_TWD, Elev_YPM 13 ``` - 從工作目錄讀取teds版本與年代 ```python 14 #Main 15 P=subprocess.check_output('pwd',shell=True).decode('utf8').strip('\n')+'/' 16 teds=int(P.split('/')[3][-2:]) 17 yr=2016+(teds-10)*3 18 ndays=365 19 if yr%4==0:ndays=366 20 s365=set([i*24 for i in range(ndays)]) 21 nhrs=ndays*24 22 ``` - 檔案讀取與品質確認 - 此個案將所有點源資料庫的數據都以「高空」方式處理(cutting height of stacks`Hs=10`) ```python 23 Hs=10 #cutting height of stacks 24 #Input the TEDS csv file 25 try: 26 df = read_csv('point.csv', encoding='big5') 27 except: 28 df = read_csv('point.csv') 29 # check_NOPandSCC(0) 30 df = check_nan(df) 31 # check and correct the X coordinates for isolated islands 32 df = check_landsea(df) 33 df = WGS_TWD(df) 34 df = Elev_YPM(df) ``` - 使用`Hs`進行篩選「高空」點源 ```python 35 df=df.loc[(df.HEI>=Hs) & (df.NO_S.map(lambda x:x[0]=='P'))].reset_index(drop=True) ``` - 排除沒有SNCPV排放者 ```python 36 df['SUM']=[i+j+k+l+m for i,j,k,l,m in zip(df.SOX_EMI,df.NOX_EMI,df.CO_EMI,df.PM_EMI,df.NMHC_EMI)] 37 df=df.loc[df.SUM>0].reset_index(drop=True) 38 df['CP_NO'] = [i + j for i, j in zip(list(df['C_NO']), list(df['NO_S']))] 39 df['DY1']=[i*j for i,j in zip(df.DW1,df.WY1)] 40 df['HY1']=[i*j for i,j in zip(df.HD1,df.DY1)] 41 ``` ### 讀取並填滿CEMS資料檔 - 填滿資料表 - 程式運作需要每筆**管煙**(**管編**+**煙編**)、每個小時都要有數值。將DataFrame轉成矩陣,再轉回DataFrame即可。 ```python 42 #71 factories with CEMS will emit (at ground) when stacks are operating 43 fname=P+'point_cems.csv' 44 cems=read_csv(fname) 45 val='SOX PM NOX FLOW X_BLANK1 X_BLANK2'.split() 46 nval=len(val) ``` - **管煙**欄位為處理過後檔案的特徵 ```python 47 if 'CP_NO' not in cems.columns: #pre-process 48 cems=cems.drop(cems.loc[cems.C_NO=='C_NO'].index).reset_index(drop=True) ``` - 新增**管編**+**煙編**(**管煙**)之新標籤、新增PM(設為SN之平均值) ```python 49 cems['CP_NO'] = [i + j for i, j in zip(list(cems['C_NO']), list(cems['NO_S']))] 50 cems['PM']=[(i+j)/2 for i,j in zip(cems.SOX,cems.NOX)] ``` - 新增時間標籤`MDH`。有的年度提供的CEMS檔案小時標記為0000~2300,有的是0~23,因此需要判斷一下 ```python 51 if max(cems.HOUR)>100: 52 cems['MDH']=[int(i*10000+j*100+k/100) for i,j,k in zip(cems.MONTH,cems.DATE,cems.HOUR)] 53 else: 54 cems['MDH']=[int(i*10000+j*100+k) for i,j,k in zip(cems.MONTH,cems.DATE,cems.HOUR)] ``` - 一個(**管煙**,**時籤**)組合只應對應一組CEMS數據,以`pivot_table sum`進行整併 ```python 55 cems=pivot_table(cems,index=['CP_NO','MDH'],values=val,aggfunc=sum).reset_index() ``` - 維度標籤之計算。如果使用`標籤=序列.index(值)`指令將會非常耗時,直接使用`dict{值:標籤}`,會快很多。 ```python 56 #cems(df) convert to cemsM(matrix) 57 for MC in ['CP_NO','MDH']: 58 mc=MC.lower() 59 exec(mc+'=list(set(cems.'+MC+'))');exec(mc+'.sort()') 60 exec('n'+MC+'=len('+mc+')') 61 exec('d'+MC+'={'+mc+'[i]:i for i in range(n'+MC+')}') 62 exec('cems["i'+MC+'"]=[d'+MC+'[i] for i in cems.'+MC+']') 63 if len(mdh)!=ndays*24:sys.exit('mdh coverage not enough!') ``` - `DataFrame`轉成`Array`,因所有值預設為0,如此就補滿空缺值了。有數據的部分再填入`Array`。 ```python 64 cemsM=np.zeros(shape=(nMDH,nCP_NO,nval)) 65 for i in range(nval): 66 cemsM[cems.iMDH[:],cems.iCP_NO[:],i]=cems[val[i]] ``` - Array再轉回`DataFrame`。因後面CEMS數據使用邏輯太多樣了,`Array`形式不敷應用。 ```python 67 DD={} 68 for i in range(nval): 69 DD[val[i]]=cemsM[:,:,i].flatten() 70 DD['MDH'] =[i for i in mdh for j in cp_no] 71 DD['CP_NO']=[j for i in mdh for j in cp_no] 72 cems=DataFrame(DD) 73 cems['C_NO']=[i[:8] for i in cems.CP_NO] 74 cems['MD']=[i//100 for i in cems.MDH] 75 cems.set_index('CP_NO').to_csv(fname) ``` ### 整理CEMS各廠運作時間模式 - 讀取資料庫欄位(**管編**`C_NO`, **管煙**`CP_NO`, 月日時`MDH`, 月日`MD`,) ```python 76 for MC in ['CP_NO','MDH','MD','C_NO']: 77 mc=MC.lower() 78 exec(mc+'=list(set(cems.'+MC+'))');exec(mc+'.sort()') 79 exec('n'+MC+'=len('+mc+')') 80 ``` - 沒有CEMS之同家工廠其它煙道、或鄰近工廠,可能具有上下游連動關係,因此適用該CEMS的操作特性,此處先行備妥。 - 個別廠的日操作特性,分析全年SOX排放量之小時變化,按排放量排序,代表最有可能運作之小時。 - 先執行`pivot_table`加總 ```python 81 #Hour of Day pattern 82 cems['HR']=[i%100 for i in cems.MDH] 83 pv_cems1=pivot_table(cems,index=['C_NO','HR'],values='SOX',aggfunc=sum).reset_index() 84 ``` - 以**管編**為索引之新的`DataFrame`,其內容為小時之序位 ```python 85 cems_HROD=DataFrame({'C_NO':c_no}) 86 cems_HROD['SOX_HR_ODER']=0 87 for ic in cems_HROD.index: 88 pv1=pv_cems1.loc[pv_cems1.C_NO==c_no[ic]] 89 pv3=pv1.sort_values('SOX',ascending=False).reset_index(drop=True) 90 cems_HROD.loc[ic,'SOX_HR_ODER']=''.join(['{:d} '.format(i) for i in pv3.HR]) ``` - 同樣原理應用在全年的逐日變化,內容為序列`mdh`的標籤。排序依據全天的流量總計,值越高者表當天最可能運作。 ```python 91 #orders for DY1 92 pv_cems2=pivot_table(cems,index=['C_NO','MD'],values='FLOW',aggfunc=sum).reset_index() ``` - `MD`欄位由**月日**轉變成`mdh`的標籤,轉變需要2步驟,否則會出錯 ```python 93 #Indexing is an exhaustive process. 94 iMD=[mdh.index(i*100) for i in pv_cems2.MD] #change the MMDD into index sequence among MMDD00's 95 pv_cems2.MD=iMD ``` - 以**管編**為索引之新的`DataFrame`,其內容為**日期標籤之序位** ```python 96 cems_DAOD=DataFrame({'C_NO':c_no}) 97 cems_DAOD['FLOW_DA_ODER']=0 98 for ic in cems_DAOD.index: 99 pv1=pv_cems2.loc[pv_cems2.C_NO==c_no[ic]] 100 pv3=pv1.sort_values('FLOW',ascending=False).reset_index(drop=True) 101 cems_DAOD.loc[ic,'FLOW_DA_ODER']=''.join(['{:d} '.format(i) for i in pv3.MD]) 102 ``` ### 套用CEMS或運作時間模式 - 建立各**管編**座標值之資料表 ```python 103 dfxy=pivot_table(df,index='C_NO',values=['UTM_E','UTM_N'],aggfunc=np.mean).reset_index() 104 ``` - `BLS`為污染項目與布林值的對照表。以直接提取資料庫中符合布林值、要處理的筆數。 - `{'NMHC':'PM', 'NMHC':'PM'}`因為`NMHC`及`CO`沒有`CEMS`數據,假設與`PM`、`NOX`一樣。 ```python 105 #booleans for pollutant selection 106 c2v={'NMHC':'PM','SOX':'SOX','NOX':'NOX','PM':'PM','NMHC':'PM'} #point.csv vs cems.csv 107 BLS={c:df[c+'_EMI']>0 for c in c2v} 108 colT=['HD1','DY1','HY1'] 109 col=['C_NO','CP_NO','HD1','DY1','HY1']+[i for i in df.columns if 'EMI' in i] ``` - `s`為物質種類,須由引數讀取,且限定在`BLS`的索引範圍(`c2v`) ```python 110 for spe in [s for s in [sys.argv[1]] if s in BLS]: 111 dfV=df[col].loc[BLS[spe]].reset_index(drop=True) ``` - 對全廠加總並形成新的資料庫`dfV` ```python 112 dfV1=pivot_table(dfV,index='CP_NO',values=spe+'_EMI',aggfunc=sum).reset_index() 113 dfV2=pivot_table(dfV,index='CP_NO',values=colT,aggfunc=np.mean).reset_index() 114 dfV=merge(dfV1,dfV2,on='CP_NO') 115 dfV['C_NO']=[i[:8] for i in dfV.CP_NO] 116 for c in colT: 117 dfV[c]=np.array(dfV[c],dtype=int) ``` - 對照資料庫(`a`)與cems(`b`)的**管編**`cp` - `ab`為二者交集 - `b1`為`b-a`,有CEMS數據卻不在資料庫中、沒有座標 - `c1`為`ab-b1`(就等於`ab`) - 列出所有`c1`的座標序列 ```python 118 a,b=list(set(dfV.C_NO)),list(set(cems.C_NO));a.sort();b.sort() 119 ab=[i for i in a if i in b] 120 cp=list(set(dfV.CP_NO)) 121 cp.sort() 122 ons=np.zeros(shape=(len(cp),nMDH))#,dtype=int) 123 #other fatories without CEMS, take the nearest one 124 b1=set(b)-set(dfxy.C_NO) #cems factory but without UTM location 125 c1=[c for c in b if c not in b1 and c in a] #cems plant with X,Y 126 cemsX=np.array([list(dfxy.loc[dfxy.C_NO==c,'UTM_E'])[0] for c in c1]) 127 cemsY=np.array([list(dfxy.loc[dfxy.C_NO==c,'UTM_N'])[0] for c in c1]) ``` - 逐一**管編**進行迴圈 - 如果工廠沒有CEMS(`c not in ab`),則以最近的一家廠的日變化特徵為其**時變係數**,令`c_cems`為該廠**管編** - 如果該廠**日期標籤之序位**不足全年日數(ndays),也將補足缺漏日之標籤`list(s365-set(pv2MD))` ```python 128 #loop for every factories 129 for c in [i for i in a if i not in b1]: 130 c_cems=c 131 if c not in ab: 132 x0,y0=list(dfxy.loc[dfxy.C_NO==c,'UTM_E'])[0],list(dfxy.loc[dfxy.C_NO==c,'UTM_N'])[0] 133 dist=(cemsX-x0)**2+(cemsY-y0)**2 134 idx=list(dist).index(min(dist)) 135 c_cems=c1[idx] 136 pv2MD=np.array(list(cems_DAOD.loc[cems_DAOD.C_NO==c_cems,'FLOW_DA_ODER'])[0].split(),dtype=int) 137 if len(pv2MD)