Skip to content

Instantly share code, notes, and snippets.

@rdmtinez
Last active November 16, 2017 15:31
Show Gist options
  • Save rdmtinez/d6f031d8fb6d6c23e90ecdad977494a7 to your computer and use it in GitHub Desktop.
Save rdmtinez/d6f031d8fb6d6c23e90ecdad977494a7 to your computer and use it in GitHub Desktop.
# coding: utf-8
# In[343]:
##the following will combine the datatables from DWD in Stuttgart, as
##it is the only station which collects Solar Radiation data for BW
##for the first iteration only the data from Bosch sensors in BW will
import os
import pandas as pd
import numpy as np
import math
import matplotlib.pyplot as plt
from datetime import datetime as dt, timedelta
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity='all'
wkdir = 'C:/Users/MAR8RNG/BOSCH-PROJECTS/1-Humidity/python/condensed_CSVs/'
dwddir = wkdir+'dwddata/'
bscdir = wkdir+'basic/'
#directory where the dwd data is
os.chdir(dwddir)
#4928 refers to the fact that only solar radiation data from
#station 4928 - Stuttgart was used for these calculations
txt_list = [i for i in os.listdir(dwddir) if '4928' in i]
csv_list = [i for i in os.listdir(bscdir) if 'basic' in i]
for txt in txt_list:
#get pressure data - DWD provides the data in hPa, and should be converted to kPa for Penman-Monteith use
#also note that the airpressure should be adjusted for the Height at where the farm stands
if 'pressure' in txt:
with open(txt) as f:
"in Pressure"
df = pd.read_table(f, delimiter=';')
#convert the time into a pandas maneagable form
df['DateTime'] = pd.to_datetime(df.MESS_DATUM, format='%Y%m%d%H')
#drop irrelevant columns
df.drop(['STATIONS_ID', 'QN_8', 'eor', 'MESS_DATUM'], axis=1, inplace=True)
#take data only from 2017
df = df[df['DateTime'] > '2017']
#change units from hectoPascals into kiloPascals
df['P0'] = df['P0']/10
df['P'] = df['P']/10
#set DateTimeIndex for merging DataFrames
df.set_index('DateTime', inplace=True)
#Main Dataframe which will hold all the DWD data
main = df
#get solar data - DWD provides the data in J/cm^2, and should be converted to MJ/m^2 * (day or hour)
#Rs = given from the data (FG_LBERG)
if 'solar' in txt:
with open(txt) as f:
"in solar"
df = pd.read_table(f, delimiter=';')
df['DateTime'] = pd.to_datetime(df.MESS_DATUM, format='%Y%m%d%H:%M')
df.drop(['STATIONS_ID', 'MESS_DATUM', 'QN_592',
'eor', 'ATMO_LBERG', 'ZENIT',
'MESS_DATUM_WOZ'], axis=1, inplace=True)
df=df[df['DateTime'] > '2017']
#conversion of SunLight Minutes into hours
df['SD_LBERG'] = df['SD_LBERG'] / 60
#conversion of J/cm^2 units MJ/m^2 -> (1MJ/10^6 J)*(100cm/1m)^2 =
# = conversion factor (1/100) MJ/m^2 * hour
df['FD_LBERG'] = df['FD_LBERG'] / 100
df['FG_LBERG'] = df['FG_LBERG'] / 100
#original timestamp needed to be changed so that
#time indexing and merging works properly
df['DateTime'] = df['DateTime'].dt.round('H')
#DataFrames with DatetimeIndex are to be merged, not concatenated
df.set_index('DateTime', inplace=True)
main = main.merge(df, how='outer', left_index=True, right_index=True)
#get wind data - DWD provides the data as the mean wind speed in m/s
if 'wind' in txt:
with open(txt) as f:
"in Wind"
df = pd.read_table(f, delimiter=';')
df['DateTime'] = pd.to_datetime(df.MESS_DATUM, format='%Y%m%d%H')
df.drop(['STATIONS_ID', 'MESS_DATUM', 'QN_3', 'D', 'eor'], axis=1, inplace=True)
df = df[df['DateTime'] > '2017']
#conversion of windspeed measured at 10m
#to the windspeed at two meters (m/s)
df['F'] = df['F']*(4.87/math.log(67.8*10 - 5.42))
df.set_index('DateTime', inplace=True)
'Main df'
main = main.merge(df, how='outer', left_index=True, right_index=True)
main.rename(columns={'FD_LBERG':'Rso',
'FG_LBERG':'Rs', 'SD_LBERG':'SunHour', 'F':'U2'}, inplace=True)
main[3000:3005]
#temp, humidity can be obtained from own data - OWN data should be resampled PER HOUR first
# In[471]:
os.chdir(bscdir)
import math
def Delta(T):
"""Delta slope calculation"""
eT = lambda x: .6108*math.exp(17.27*x/(x+237.3))
top=4098*(T.apply(eT))
bot=(237.3+T).pow(2)
return top/bot
def Rn_minus_G(T, HM1, time, Rs, Lm, Lat):
"""Net Solar radiation minus Soil heatflux Calculation"""
h = 315 #the station-height of the solar measurement, this is the station-height for Stuttgart
#Net Shortwave and Longwave radiation
# Rns(1 - a)*Rs, .23 = reference crop albedo
Rns = (1-.23)*Rs
Ra = sol_Ra(Lm, Lat, time)
#This changes the negative values given by the calculations
#during nighttime hours, at night there is Zero (0) solar radiation
Ra=Ra.where(Ra.values > 0, 0)
Rso = sol_Rso(h, Ra)
#concatenate Rs Rso
Rnl = sol_Rnl(T,HM1,Rs,Rso)
#Net Radiation
Rn = Rns - Rnl
#Soil heatflux hourly calculation
G=Ghr(time, Rn)
print('Rs, Rns, Ra, Rso, Rnl, Rn, G')
RSC = pd.concat([Rs, Rns, Ra, Rso, Rnl, Rn, G], axis=1, verify_integrity=True)
print(RSC[1816:1817])
return Rn - G
def sol_Rso(h, Ra):
"""Rso: Rs on a cloudless day calculator"""
return (.75 + h*2*math.pow(10, -5))*Ra
def sol_Rnl(T,HM1,Rs,Rso):
"""Net Longwave Calculation"""
#sb, Stefan-Boltzmann hourly conversion (/24) MJ K^-4 * m^-2 * hr^-1
sb=2.043*math.pow(10, -10)
T4 = (273.16+T).pow(4) #Temp in Kelvin raised to the fourth power
#vapor pressure conversions
eT = lambda x: .6108*math.exp(17.27*x/(x+237.3))
ea = T.apply(eT)*(HM1/100)
Rsso=Rso
#correction of Rso values less than 0,
#since Rso is dependend on Ra and Ra is 0 at night
Rso = Rso.where(Rs.values < Rso.values, Rs.values)
Rso = Rso.where(Rso.values>0, 1)
print('Rso < Rs values to Rs, 0 to 1: Rs, RsO, Rso, Rs/Rso')
rso3 = pd.concat([Rs, Rsso, Rso, Rs/Rso], axis=1)
print(rso3[1816:1840])
return sb*T4*(.34 - .14*ea.apply(np.sqrt))*(1.35*(Rs/Rso) - .35)
def sol_Ra(Lm, Lat, time):
J = time.dt.dayofyear
#Lz--the Longitude of the Center of the TimeZone (i.e. CEST)
#where the measurements were taken in deegres West of Greenwich(DWG)
Lz = pd.Series(data= 360 - 15.35, index=Lm.index)
#Lm--the Longitude of the measurement site in DWG, Series
Lm = 360-Lm
#print('Lm')
#print(Lm)
#J = Transformation of the time Series into Julian day
#Gsc .0820 MJ * m^-2 * min-1
Gsc= .0820
#dr = inverse relative distance Earth-Sun
dr = 1+(.033*(J*2*math.pi/365).apply(math.cos))
#dr = 1+.033*math.cos(2*math.pi*J/365)
#solar time angles at end and beginning of period (Radian)
w2, w1, w0, Sc = omegas(time, J, Lz, Lm)
#print('Lm', 'Lat','Lz', 'w2', 'w1', 'w0', 'Sc')
#oms = pd.concat([Lm, Lat, Lz, w2, w1, w0, Sc], axis=1, names=['w2', 'w1', 'w0', 'Sc'])
#print(oms[:100])
#lat and long in Radians
#ld -- solar declination dependend only on the day of the year
#lp -- the latitude (Ln) of the measurements in Radians
ld = lild(J)
lp = lilp(Lat)
return ((12*60*Gsc*dr)*((w2-w1)*lp.apply(math.sin)*ld.apply(math.sin)+
lp.apply(math.cos)*ld.apply(math.cos)*
(w2.apply(math.sin) - w1.apply(math.sin))))/math.pi
def omegas(time, J, Lz, Lm):
#helper function
def time_to_decimal(t):
"helper function timestamp to decimal time conversion"
h, m = [int(i) for i in t.split(':')]
return h + m/60
#t -- the conversion of the DateTime series to hour (%H) format
#Since the time is the stamp of the end of data collection period, this calculation subtracts .5 hours
t0=time - timedelta(hours=.5) #midpioint of the hour (hour - .5 i.e. 13-to-14, => 14:00 - .5 = 13:30)
#variable holding the decimal converted timestamps
t0=t0.dt.strftime('%H:%M').apply(time_to_decimal)
t1=1 #one hour time step
#Seasonal correction factor Series
b= 2*math.pi*(J-81)/364
#seasonal correction factor (hourly) Series
Sc = .1645*((2*b).apply(math.sin)) - .1255*(b.apply(math.cos)) -.025*(b.apply(math.sin))
w0 = math.pi*((t0 + .06667*(Lz-Lm) + Sc) -12)/12
return w0+(math.pi*t1/24) , w0-(math.pi*t1/24), w0, Sc
def lild(J):
#little delta, or solar declination
#return .409*math.sin((J*2*math.pi/365)-1.39)
return .409*((J*2*math.pi/365)-1.39).apply(math.sin)
def lilp(Lat):
#little phi, conversion of latitude into Radians
return Lat*math.pi/180
def Ghr(time, Rn):
"""Soil Heat flux calculator"""
#sunrise -sunset hours from 1st of the month (sr,ss) to end of the month
#Jan-1st SR 8:17, Jan-31st SR 07:47
#Jan-1st SS 16:03, Jan-31st SS 16:52
daylight = {'Jan':('08:00', '16:25'),
'Feb':('07:20', '17:20'),
'Mar':('06:45', '18:40'),
'Apr':('06:05', '20:05'),
'May':('05:10', '20:55'),
'Jun':('04:50', '21:25'),
'Jul':('05:10', '21:15'),
'Aug':('05:50', '20:30'),
'Sep':('06:45', '19:25'),
'Oct':('07:00', '17:30'),
'Nov':('07:25', '16:15'),
'Dec':('08:05', '16:00')}
#this maps the month of 'time' as to the dictionary daylight
#then unzips the tuple to daystart and dayend lists which
#are then converted to pandas.Series objects for use in boolean mask
daystart, dayend = zip(*time.dt.strftime('%b').map(daylight))
#conversion to pandas series
daystart=pd.Series(pd.to_datetime(daystart).time)
dayend=pd.Series(pd.to_datetime(dayend).time)
t=pd.Series(time.dt.time)
light_mask = t.between(daystart, dayend)
#True values get multiplied by .1 (The inversion is because
#pd.Series.where() only changes the values which are negative)
Ghr = Rn.where(light_mask, Rn*.5)
#False values multiplied by *.5
Ghr = Ghr.where(np.invert(light_mask), Ghr*.1)
#sbs = light_mask.merge(Ghr, how='outer', left_index=True, right_index=True)
#print('Ghr')
#sbs = pd.concat([time, Rn, Ghr, light_mask], axis=1)
#print(sbs[:100])
return Ghr
def cd_get(time):
"Cd: short crop denominator constant dependend on night vs daylight"
Cd = pd.Series(data=.24, index=time.index)
daylight = {'Jan':('08:00', '16:25'),
'Feb':('07:20', '17:20'),
'Mar':('06:45', '18:40'),
'Apr':('06:05', '20:05'),
'May':('05:10', '20:55'),
'Jun':('04:50', '21:25'),
'Jul':('05:10', '21:15'),
'Aug':('05:50', '20:30'),
'Sep':('06:45', '19:25'),
'Oct':('07:00', '17:30'),
'Nov':('07:25', '16:15'),
'Dec':('08:05', '16:00')}
#this maps the month of 'time' as to the dictionary daylight
#then unzips the tuple to daystart and dayend lists which
#are then converted to pandas.Series objects for use in boolean mask
daystart, dayend = zip(*time.dt.strftime('%b').map(daylight))
#conversion to pandas series
daystart=pd.Series(pd.to_datetime(daystart).time)
dayend=pd.Series(pd.to_datetime(dayend).time)
t=pd.Series(time.dt.time)
daylight_mask = t.between(daystart, dayend)
return Cd.where(daylight_mask, .96)
def Gamma(P0):
"""Psychrometric Constant calculation"""
return .000665*P0
def es_minus_ea(T, HM1):
"""Vapor Pressure Calculation"""
#vapor pressure calculation
eT = lambda x: .6108*math.exp(17.27*x/(x+237.3))
es = T.apply(eT)
ea = es*(HM1/100)
return es - ea
ETo_Inst = []
for csv in csv_list:
imei = csv[-8:-4]
imei
with open(csv) as f:
df=pd.read_csv(f, parse_dates=['DateTime'])
#drop irrelevant data
df = df[df['Volts'] > -254]
df.drop(['IMEI', 'Volts'], inplace=True, axis=1)
#resample own data at hourly intervals to merge onto DWD data
df.set_index('DateTime', inplace=True)
df=df.resample('H').agg({'DigiTemp':'mean', 'HM1':'mean',
'T1':'mean', 'VWC':'mean', 'Lat':'mean', 'Lng':'mean'})
#create and fill change in VWC column at hourly interval
df.reset_index(inplace=True)
df['dVWC'] = None
df.loc[(df['DateTime'].shift(-1)-df['DateTime'])==timedelta(hours=1),
'dVWC'] = df['VWC'].shift(-1) - df['VWC']
df['dVWC'] = df['dVWC'].shift(+1)
df = df[1:] #drops first row as dVWC is NaN
#WaterLoss column for coparision with ETo
#df['WLoss'] = None
df.set_index('DateTime', inplace=True)
##MERGE WITH Main DataFrame
mdf = main.merge(df, how='outer', left_index=True, right_index=True)
mdf.reset_index(inplace=True)
mdf.dropna(axis=0, subset=['T1', 'Rs', 'Rso'], inplace=True)
mdf.reset_index(inplace=True)
mdf['ETo'] = None
#the vectorized individual parts of the Penman-Monteith Equation
#321\
delta = Delta(mdf['T1'])
rn_minus_g = Rn_minus_G(mdf['T1'], mdf['HM1'], mdf['DateTime'], mdf['Rs'], mdf['Lng'], mdf['Lat'])
gamma = Gamma(mdf['P0'])
es_less_ea = es_minus_ea(mdf['T1'], mdf['HM1'])
Cd = cd_get(mdf['DateTime'])
Tk = 37/(mdf['T1'] + 273.16)
U2 = mdf['U2']
mdf['ETo'] = ((.408 * delta * rn_minus_g) + (gamma *Tk*U2*es_less_ea)) / (delta + gamma*(1+(Cd*U2)))
ETo_Inst.append((imei,mdf))
# In[462]:
mains = ETo_Inst
for imei, df in mains:
df=df.drop('index', axis=1)
df=df[df['DateTime'] > 'June-21-2017']
df=df[df['DateTime' ]< 'June-22-2017']
df
# In[ ]:
# In[472]:
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import pyplot as plt, gridspec
from matplotlib.backends.backend_pdf import PdfPages
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
thisdir='C:/Users/MAR8RNG/BOSCH-PROJECTS/1-Humidity/python/condensed_CSVs/basic/'
thatdir='C:/Users/MAR8RNG/BOSCH-PROJECTS/1-Humidity/python/condensed_CSVs/'
os.chdir(thisdir)
dwd_ETo = []
txt_list = [i for i in os.listdir(thisdir) if 'dwd' in i]
for txt in txt_list:
imei = txt[-8:-4]
imei
with open(txt) as f:
df = pd.read_table(f, parse_dates=['Datum'],delimiter=';')
df = df.loc[:, 'Datum':'VGSL']
df.rename(columns={'VGSL':'DWD_ETo'}, inplace=True)
df['Datum'] = pd.to_datetime(df['Datum'], format='%Y%m%d%H')
df.set_index('Datum', inplace=True)
dwd_ETo.append(df)
merge_count = 0
for imei, df in ETo_Inst:
print(imei)
#convert the values that are less than Zero or NaN
#df.loc[(df['ETo'] < 0), 'ETo'] = 0
df=df.fillna(0)
#convert positive+ change in water into 0 to only account for daily water loss
df.loc[(df['dVWC'] > 0), 'dVWC'] = 0
#Renaming for later use as Legend names
df['VWCmax'] = df['VWC']
df['VWCmin'] = df['VWC']
#set index datetime for resampling purposes
df.set_index('DateTime', inplace=True)
df = df.resample('D').agg({'VWC':'mean', 'VWCmax':'max',
'VWCmin':'min', 'dVWC':'sum',
'ETo':'sum',})
#convert negative dVWC volumes into absolute (mm) WaterLoss values
df['mmWaterLoss'] = -df['dVWC'] * 1000
df['VWC'] = df['VWC'] * 1000
df.rename(columns={'ETo':'Own_ETo', 'VWC':'mmWater_mean'}, inplace=True)
###MergeWith DWD_ETo
df=df.merge(dwd_ETo[merge_count], how='outer', left_index=True, right_index=True)
merge_count+=1
##Drop Rows where there are NAs for OWN_ETo has NAs since
##after the merge some days will have this calculation due to unavailable data from
##the beginning of the year (see original data collection)
df.dropna(axis=0, subset=['Own_ETo'], inplace=True)
df.reset_index(inplace=True)
'resample daily min, max'
df['Own_ETo'].idxmin()
df['Own_ETo'].idxmax()
df.rename(columns={'index':'DateTime'}, inplace=True)
#df['DateTime'] = pd.to_datetime(df['DateTime'])
########################################################################
#df = df[df['mmWaterLoss'] < 20] ########################################
########################################################################
########################################################################
df = df[df['mmWaterLoss'] < 150] ########################################
########################################################################
########################################################################
#df = df[df['mmWaterLoss'] < 50] ########################################
########################################################################
with PdfPages(thatdir+'figures/'+'OWN_ETo_'+imei+'.pdf') as pdf:
fig = plt.figure(figsize=(16,36))
grid = gridspec.GridSpec(6,1)
"""p3d = fig.add_subplot(111, projection='3d')
p3d.scatter(xs=df['mmWaterLoss'], ys=df['Own_ETo'], zs=df['DateTime'], zdir='x')
plt.show()
#p3D = plt.subplot(grid[])
break"""
p1 = plt.subplot(grid[0,0]) #WaterLoss vs oETo
p2 = plt.subplot(grid[1,0]) #WaterLoss vs dETo
p3 = plt.subplot(grid[2,0]) #time vs. mean VWC, WaterLoss
p4 = plt.subplot(grid[3,0]) #oETo vs. dETo
p5 = plt.subplot(grid[4,0]) #time vs. oETo vs. dETo
fz= 20
#WaterLoss vs OWN_ETo
#sns.jointplot(x=df['mmWaterLoss'], y=df['Own_ETo'])
sns.regplot(x=df['mmWaterLoss'], y=df['Own_ETo'], ax=p1)
#df.plot.scatter(x='mmWaterLoss', y='Own_ETo', ax=p1, color='Green', fontsize=fz)
p1.set_title('WaterLoss vs Own_ETo', fontsize=fz)
p1.set_ylabel('Own ETo (mm/day) ' + '{}'.format(imei), fontsize=fz)
p1.set_xlabel('mm WaterLoss /day', fontsize=fz)
#WaterLoss vs DWD_ETo
sns.regplot(x=df['mmWaterLoss'], y=df['DWD_ETo'], ax=p2)
#df.plot.scatter(x='mmWaterLoss', y='DWD_ETo', color='Blue', ax=p2, fontsize=fz)
p2.set_title('WaterLoss vs. DWD_ETo', fontsize=fz)
p2.set_ylabel('DWD ETo (mm/day) ' + '{}'.format(imei), fontsize=fz)
p2.set_xlabel('mm WaterLoss /day', fontsize=fz)
#WaterLoss and Mean VWC(mm) vs Time
df.plot(x='DateTime', y=['mmWater_mean','mmWaterLoss'], ax=p3, fontsize=fz, color=['orange', 'blue'])
p3.set_title('Water Loss & Content vs Time', fontsize=fz)
p3.set_xlabel('DateTime', fontsize=fz)
p3.set_ylabel('Loss & Content /day '+ '{}'.format(imei), fontsize=fz)
p3.legend(fontsize=fz)
#own ETo vs DWD ETo
sns.regplot(x=df['Own_ETo'], y=df['DWD_ETo'], ax=p4)
#df.plot.scatter(x='Own_ETo', y='DWD_ETo', color='red', ax=p4, fontsize=fz)
p4.set_title('Calculated Own Data ETo vs Deutsche Wetterdienst ETo', fontsize=fz)
p4.set_xlabel('Own ETo (mm) ', fontsize=fz)
p4.set_ylabel('DWD ETo (mm) ' + '{}'.format(imei), fontsize=fz)
#time vs dETo vs. DWD ETo
df.plot(x='DateTime', y=['DWD_ETo', 'Own_ETo'], ax=p5, fontsize=fz, color=['blue', 'green'])
#style can be set but color must be removed, very confusing to read
p5.set_title('Time vs Own_ETo and DWD_ETo ')
p5.set_xlabel('DateTime', fontsize=fz)
p5.set_ylabel('ETo (mm) ' +'{}'.format(imei), fontsize=fz)
p5.legend(fontsize=fz)
fig.tight_layout()
plt.show()
pdf.savefig(fig)
plt.close('all')
###OMIT THE VALUES THAT ARE GREATER THAN 50 and compare....
###You have to consider that on certain days after an
#irrigation event the water loss will be greater given
#that percolation is by far a bigger quantity and happens at a
#faster rate than evaporation
#df.plot(x=df['DateTime'], y=['ETo'])
#plt.show()
# In[ ]:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment