diff --git a/ruins/apps/climate.py b/ruins/apps/climate.py new file mode 100644 index 0000000..dd93020 --- /dev/null +++ b/ruins/apps/climate.py @@ -0,0 +1,129 @@ +import streamlit as st +import numpy as np + +import xarray as xr # TODO this should be covered by the DataManager +import pandas as pd # TODO this should be covered by the DataManager + +from ruins.plotting import monthlyx +from ruins import components + +# TODO: Build this into DataManager +def load_data(sel='Weather',regagg=None): + #Read data from netcdf files and return xarray + if sel == 'Weather': + data = xr.load_dataset('data/weather.nc') + elif sel == 'CORDEX': + data = xr.load_dataset('data/cordex_coast.nc') + else: + if regagg == 'North Sea Coast': + data = xr.load_dataset('data/cordex_coast.nc') + data.filter_by_attrs(RCP=sel) + + return data + + +# TODO refactor the plots into the plotting module +def climate_explorer(w_topic: str): + cliprojs = ["Global", "Regional"] + cliproj = st.sidebar.radio("Climate Model Scaling:", options=cliprojs) + + # TODO: Re-implement this as a service + # expl_md = read_markdown_file('explainer/climatescale.md') + # st.sidebar.markdown(expl_md, unsafe_allow_html=True) + + if cliproj=='Regional': + regaggs = ['North Sea Coast', 'Krummhörn', 'Niedersachsen', 'Inland'] + regagg = st.sidebar.selectbox('Spatial aggregation:', regaggs) + + if regagg=='North Sea Coast': + climate = xr.load_dataset('data/cordex_coast.nc') + climate.filter_by_attrs(RCP='rcp45') + elif regagg=='Krummhörn': + climate = xr.load_dataset('data/cordex_krummh.nc') + + st.subheader('Climate Model Comparison') + crefs = ['Weather Data', 'all RCPs', 'RCP2.6', 'RCP4.5','RCP8.5'] + cref1 = st.sidebar.selectbox('Select first reference:', options=crefs) + + if cref1=='Weather Data': + data1 = load_data('Weather') + data1 = data1.coast + drngx1 = (1980,2000) + elif cref1 == 'all RCPs': + data1 = load_data('CORDEX') + drngx1 = (2050, 2070) + elif cref1 == 'RCP2.6': + data1 = load_data('rcp26','North Sea Coast') + drngx1 = (2050, 2070) + elif cref1 == 'RCP4.5': + data1 = load_data('rcp45', 'North Sea Coast') + drngx1 = (2050, 2070) + elif cref1 == 'RCP8.5': + data1 = load_data('rcp85','North Sea Coast') + drngx1 = (2050, 2070) + + drng1 = [pd.to_datetime(data1.isel(time=0, vars=1).time.values).year, pd.to_datetime(data1.isel(time=-1, vars=1).time.values).year] + datarng1 = st.sidebar.slider('Data range', drng1[0], drng1[1], drngx1, key='dr1') + + cref2 = st.sidebar.selectbox('Select second reference:', options=crefs) + if cref2 == 'Weather Data': + data2 = load_data('Weather') + drngx2 = (1980, 2000) + elif cref2 == 'all RCPs': + data2 = load_data('CORDEX') + drngx2 = (2050, 2070) + elif cref2 == 'RCP2.6': + data2 = load_data('rcp26','North Sea Coast') + drngx2 = (2050, 2070) + elif cref2 == 'RCP4.5': + data2 = load_data('rcp45', 'North Sea Coast') + drngx2 = (2050, 2070) + elif cref2 == 'RCP8.5': + data2 = load_data('rcp85','North Sea Coast') + drngx2 = (2050, 2070) + + drng2 = [pd.to_datetime(data2.isel(time=0, vars=1).time.values).year, pd.to_datetime(data2.isel(time=-1, vars=1).time.values).year] + datarng2 = st.sidebar.slider('Data range', drng2[0], drng2[1], drngx2, key='dr2') + + if w_topic == 'Warming': + navi_vars = ['Maximum Air Temperature', 'Mean Air Temperature', 'Minimum Air Temperature'] + navi_var = st.sidebar.radio("Select variable:", options=navi_vars) + if navi_var[:4] == 'Mini': + vari = 'Tmin' + afu = np.min + ag = 'min' + elif navi_var[:4] == 'Maxi': + vari = 'Tmax' + afu = np.max + ag = 'max' + else: + vari = 'T' + afu = np.mean + ag = 'mean' + + dyp = data1.sel(vars=vari).to_dataframe().resample('1M').apply(afu) + dyp = dyp.loc[(dyp.index.year>=datarng1[0]) & (dyp.index.year= datarng2[0]) & (dyp2.index.year < datarng2[1]),dyp2.columns[dyp2.columns!='vars']] + + monthlyx(dyp, dyp2, 'Temperature (°C)', 'Monthly '+ag+' in Year ('+cref1+')', 'Monthly '+ag+' in Year ('+cref2+')') + st.pyplot() + + +def main_app(): + st.header('Climate Projections Explorer') + st.markdown('''In this section we add climate model projections to the table. The same variables and climate indices are used to explore the projections of different climate models and downscaling models. It is also possible to compare projections under different scenarios about the CO2-concentration pathways to observed weather and between different model projections.''',unsafe_allow_html=True) + + + # TODO: refactor this + topics = ['Warming', 'Weather Indices', 'Drought/Flood', 'Agriculture', 'Extreme Events', 'Wind Energy'] + + # topic selector + topic = components.topic_selector(topic_list=topics, container=st.sidebar) + + # TODO refactor this + climate_explorer(topic) + + +if __name__ == '__main__': + main_app() diff --git a/ruins/apps/management.py b/ruins/apps/management.py new file mode 100644 index 0000000..342e46f --- /dev/null +++ b/ruins/apps/management.py @@ -0,0 +1,269 @@ +import streamlit as st +import numpy as np + +import pandas as pd # TODO this should be covered by the DataManager +import xarray as xr # TODO this should be covered by the DataManager +import matplotlib.pyplot as plt # TODO this should be moved into plotting submodule + + +from ruins import components + + +def load_alldata(): + weather = xr.load_dataset('data/weather.nc') + climate = xr.load_dataset('data/cordex_coast.nc') + + # WARNING - bug fix for now: + # 'HadGEM2-ES' model runs are problematic and will be removed for now + # The issue is with the timestamp and requires revision of the ESGF reading routines + kys = [s for s in list(climate.keys()) if 'HadGEM2-ES' not in s] #remove all entries of HadGEM2-ES (6 entries) + climate = climate[kys] + + return weather, climate + + +def applySDM(wdata, data, meth='rel', cdf_threshold=0.9999999, lower_limit=0.1): + '''apply structured distribution mapping to climate data and return unbiased version of dataset''' + from sdm import SDM + data_ub = data.copy() + + for k in data_ub.columns: + data_col = data_ub[k].dropna() + overlapx = pd.concat( + [wdata.loc[data_col.index[0]:wdata.index[-1]], data_col.loc[data_col.index[0]:wdata.index[-1]]], axis=1) + overlapx.columns = ['obs', 'cm'] + overlapx = overlapx.dropna() + try: + data_ub[k] = SDM(overlapx.obs, overlapx.cm, data_col, meth, cdf_threshold, lower_limit) + except: + data_ub[k] = data_ub[k] * np.nan + + data_ub[data_ub == 0.0000000] = np.nan + data_ub = data_ub.loc[data_ub.index[0]:pd.to_datetime('2099-12-31 23:59:59')] + + return data_ub + + + +def ub_climate(cdata, wdata, ub=True): + varis = ['T', 'Tmax', 'Tmin', 'aP', 'Prec', 'RH', 'Rs', 'u2', 'EToHG'] + firstitem = True + for vari in varis: + data = cdata.sel(vars=vari).to_dataframe() + data = data[data.columns[data.columns != 'vars']] + + if (vari == 'T') | (vari == 'Tmax') | (vari == 'Tmin'): + meth = 'abs' + else: + meth = 'rel' + + if ub: + wdatax = wdata.sel(vars=vari).to_dataframe().iloc[:, -1].dropna() + data_ub = applySDM(wdatax, data, meth=meth) + + else: + data_ub = data + + data_ubx = data_ub.mean(axis=1) + data_ubx.columns = [vari] + + if firstitem: + data_ubc = data_ubx + firstitem = False + else: + data_ubc = pd.concat([data_ubc, data_ubx], axis=1) + + data_ubc.columns = varis + return data_ubc + + +def get_turbine(pfi, plotit=False): + dummy = pd.read_csv(pfi) + v_start = dummy.loc[3].astype(np.float).values[0] + v_max = dummy.loc[2].astype(np.float).values[0] + P_v = dummy.loc[4:28].astype(np.float) + P_v.index = np.arange(int(np.ceil(v_max)) + 1)[1:] + if plotit: + P_v.plot() + return [P_v, v_start, v_max] + + +def P_wind(wind, pfi): + [P_v, v_start, v_max] = get_turbine(pfi, False) + + # stephours = (wind.index[2] - wind.index[1]).days * 24 + # stephours = 1. + + def interp(val): + if (val >= v_max) | (val < v_start): + return 0. + elif ~np.isnan(val): + if np.ceil(val) == np.floor(val): + return P_v.loc[int(np.floor(val))].values[0] + else: + x1 = P_v.loc[int(np.floor(val))].values[0] + x2 = P_v.loc[int(np.ceil(val))].values[0] + return ((val - np.floor(val)) * x2 + (1. - (val - np.floor(val))) * x1) + else: + return np.nan + + ip_vec = np.vectorize(interp) + + def get_windpower(u2): + val = np.fmax(-1. * np.cos(np.arange(0., 2 * np.pi, 0.27)) + u2, 0.) + return np.sum(ip_vec(val)) + + return wind.apply(get_windpower) + + +def cropmodel(data,crop='wheat',rcp='rcp85',name='croprunname'): + import get_climate_data.cli_crop as clc + #read co2 concentrations + if rcp == 'rcp85': + CO2 = pd.read_csv('data/RCP85_MIDYEAR_CONCENTRATIONS.DAT', skiprows=38, delim_whitespace=True, index_col=0).CO2EQ + elif rcp == 'rcp45': + CO2 = pd.read_csv('data/RCP45_MIDYEAR_CONCENTRATIONS.DAT', skiprows=38, delim_whitespace=True, index_col=0).CO2EQ + else: + CO2 = pd.read_csv('data/RCP45_MIDYEAR_CONCENTRATIONS.DAT', skiprows=38, delim_whitespace=True, index_col=0).CO2EQ * 0. + 400. + + if crop == 'maize': + x = np.array([9.375e+00, 3.198e+01, 1.973e+00, 8.700e+01, 1.144e+01, 3.630e+01, 7.260e-02, 1.237e+00, 2.180e+03, 1.501e-01, 5.230e-01, 5.678e+01, 6.970e+02]) + elif crop == 'meadow': + x = np.array([6.543e+00, 1.238e+01, 1.029e+00, 8.706e+01, 1.510e+01, 3.253e+01, 1.199e+00, 1.535e+03, 7.784e+03, 6.530e+03, 8.030e+03, 8.092e+03, 5.884e+03]) + elif crop == 'wheat': + x = np.array([1.257e+00, 1.276e+01, 1.101e+00, 1.010e+02, 2.578e+01, 2.769e+01, 3.416e-01, 4.940e-01, 1.906e+03, 1.921e-01, 4.595e-01, 6.066e+01, 5.360e+02]) + else: + print('ERROR: Crop not specified with parameters.') + + yields = clc.cli_SC(data, x, CO2, nme=name) + + return yields + + +def water_proj(): + #hsim = pd.read_csv('data/hsim.csv',index_col=0) + #hsim.index = pd.to_datetime(hsim.index) + hsim_collect = pd.read_csv('data/hsim_collect.csv', index_col=0) + hsim_collect.index = pd.to_datetime(hsim_collect.index) + all_vals = np.ravel(hsim_collect.values)[~np.isnan(np.ravel(hsim_collect.values))] + + perci = st.slider('Adjust percentile of extreme events:', min_value=90., max_value=99.9999, value=99., key='perci') + + firstitem = True + for i in hsim_collect.columns: + hc = hsim_collect.loc[hsim_collect[i] > np.percentile(all_vals, perci), i].resample('1Y').count() + hc.name = i + if firstitem: + h_count = hc + firstitem = False + else: + h_count = pd.concat([h_count, hc], axis=1) + + fig = plt.figure(constrained_layout=True,figsize=(10,2.5)) + gs = fig.add_gridspec(1, 5) + ax = fig.add_subplot(gs[0, :-1]) + ax1 = ax.twinx() + ax2 = fig.add_subplot(gs[0, -1]) + + all_vals = np.ravel(hsim_collect.values)[~np.isnan(np.ravel(hsim_collect.values))] + sns.distplot(all_vals, ax=ax2) + ax2.vlines(np.percentile(all_vals, perci), 0., 0.06, colors='red') + ax2.text(np.percentile(all_vals, perci), 0.065, str(perci) + '%', c='red', ha='center') + ax2.set_xlim(-3, np.percentile(all_vals, 99.98)) + + for i in hsim_collect.columns: + hsim_collect[i].plot(style='.', label='_nolegend_', c='gray', alpha=0.2, ax=ax) + try: + hsim_collect.loc[hsim_collect[i] > np.percentile(all_vals, perci), i].plot(style='.', label='_nolegend_', + c='red', alpha=0.2, ax=ax) + except: + pass + + (h_count.sum(axis=1) / len(h_count.columns)).rolling(7).mean().plot(ax=ax1, grid=False, ls='--', label='all') + cc = [x for x in h_count.columns if x.split('.')[-1] == 'rcp45'] + (h_count[cc].sum(axis=1) / len(cc)).rolling(7).mean().plot(ax=ax1, grid=False, label='rcp45') + + cc = [x for x in h_count.columns if x.split('.')[-1] == 'rcp85'] + (h_count[cc].sum(axis=1) / len(cc)).rolling(7).mean().plot(ax=ax1, grid=False, label='rcp85') + ax1.legend() + ax.set_ylabel('Q (mm/day)') + ax1.set_ylabel('days w/ Q>' + str(np.round(perci / 100., 3)) + ' percentile\n7 year rolling mean') + st.pyplot() + + + +def management_explorer(w_topic: str): + rcps = ['rcp45', 'rcp85'] # 'rcp26', + rcp = st.sidebar.selectbox('RCP:', rcps) + + weather, climate = load_alldata() + data = climate.filter_by_attrs(RCP=rcp) + + ub = st.sidebar.checkbox('Apply SDM bias correction', True) + + cdata = ub_climate(data, weather['krummhoern'], ub) + + if w_topic == 'Agriculture': + crops = ['wheat', 'maize', 'meadow'] + crop = st.sidebar.selectbox('Crop:', crops) + + yieldx = cropmodel(cdata,crop,rcp,name=crop+' @ '+rcp) + + yieldx.rolling(7).mean().plot() + st.pyplot() + + elif w_topic == 'Wind Energy': + storeddata = st.checkbox('Use pre-calculated data (calculation not yet optimized - very slow)', True) + if storeddata: + cwind_p = pd.read_csv('data/windpowerx.csv', index_col=0) + cwind_p.index = pd.to_datetime(cwind_p.index) + plt.figure(figsize=(10, 2.5)) + cwind_p[[x for x in cwind_p.columns if rcp in x][::3]].mean(axis=1).plot(label='Enercon 3MW') + cwind_p[[x for x in cwind_p.columns if rcp in x][1::3]].mean(axis=1).plot(label='9x Enercon 0.33MW') + cwind_p[[x for x in cwind_p.columns if rcp in x][2::3]].mean(axis=1).plot(label='0.4x Enercon 7.5MW') + else: + wpengine = ['Enercon 3MW', 'Vestas 3MW', '9x Enercon 0.33MW', '13x Vestas 0.23MW', '0.4x Enercon 7.5MW'] + #wpengine = ['Enercon 3MW', '9x Enercon 0.33MW', '0.4x Enercon 7.5MW'] + turbine = st.sidebar.selectbox('Select turbine:', wpengine) + + plt.figure(figsize=(10,2.5)) + if turbine == 'Enercon 3MW': + pfi = 'data/pow/Enercon E-115 3000kW (MG).pow' + P_wind(cdata.u2, pfi).resample('1Y').sum().rolling(7).mean().plot(label=turbine) + elif turbine == 'Vestas 3MW': + pfi = 'data/pow/Vestas 112m 3MW (MT).pow' + P_wind(cdata.u2, pfi).resample('1Y').sum().rolling(7).mean().plot(label=turbine) + elif turbine == '9x Enercon 0.33MW': + pfi = 'data/pow/Enercon E33_33.4m _330kw(MT).pow' + (P_wind(cdata.u2, pfi).resample('1Y').sum()*9.).rolling(7).mean().plot(label=turbine) + elif turbine == '13x Vestas 0.23MW': + pfi = 'data/pow/Vestas V29-225kw(MT).pow' + (P_wind(cdata.u2, pfi).resample('1Y').sum() * 13.).rolling(7).mean().plot(label=turbine) + elif turbine == '0.4x Vestas 7.5MW': + pfi = 'data/pow/Enercon E-126_127m_7500kW (MT).pow' + (P_wind(cdata.u2, pfi).resample('1Y').sum() * 0.4).rolling(7).mean().plot(label=turbine) + + plt.legend() + plt.ylabel('Wh/yr') + st.pyplot() + + elif w_topic == 'Extreme Events': + water_proj() + + +def main_app(): + st.header('Management Explorer') + st.markdown('''In this section we project the climate model outputs to ecosystem services through diffferent model approaches. The impact of potential management is tackeled at a rather broad level in terms of exemplary options.''', unsafe_allow_html=True) + + # TODO: refactor this + topics = ['Warming', 'Weather Indices', 'Drought/Flood', 'Agriculture', 'Extreme Events', 'Wind Energy'] + + # topic selector + topic = components.topic_selector(topic_list=topics, container=st.sidebar) + + # TODO refactor this + management_explorer(topic) + + +if __name__ == '__main__': + main_app() diff --git a/ruins/apps/uncertainty.py b/ruins/apps/uncertainty.py new file mode 100644 index 0000000..6742c24 --- /dev/null +++ b/ruins/apps/uncertainty.py @@ -0,0 +1,9 @@ +import streamlit as st + + +def main_app(): + st.header('Uncertainty Explorer') + + +if __name__ == '__main__': + main_app() diff --git a/ruins/apps/weather.py b/ruins/apps/weather.py new file mode 100644 index 0000000..2c0b9b9 --- /dev/null +++ b/ruins/apps/weather.py @@ -0,0 +1,321 @@ +import streamlit as st +import xarray as xr # TODO: these references should be moved to DataManager +import pandas as pd # TODO: these references should be moved to DataManager +import numpy as np +import matplotlib.pyplot as plt + +from ruins.plotting import plt_map, kde, yrplot_hm +from ruins import components + + + +#### +# OLD STUFF +# +# TODO: replace with DataManager +def load_alldata(): + weather = xr.load_dataset('data/weather.nc') + climate = xr.load_dataset('data/cordex_coast.nc') + + # WARNING - bug fix for now: + # 'HadGEM2-ES' model runs are problematic and will be removed for now + # The issue is with the timestamp and requires revision of the ESGF reading routines + kys = [s for s in list(climate.keys()) if 'HadGEM2-ES' not in s] #remove all entries of HadGEM2-ES (6 entries) + climate = climate[kys] + + return weather, climate + + +def applySDM(wdata, data, meth='rel', cdf_threshold=0.9999999, lower_limit=0.1): + '''apply structured distribution mapping to climate data and return unbiased version of dataset''' + from sdm import SDM + data_ub = data.copy() + + for k in data_ub.columns: + data_col = data_ub[k].dropna() + overlapx = pd.concat( + [wdata.loc[data_col.index[0]:wdata.index[-1]], data_col.loc[data_col.index[0]:wdata.index[-1]]], axis=1) + overlapx.columns = ['obs', 'cm'] + overlapx = overlapx.dropna() + try: + data_ub[k] = SDM(overlapx.obs, overlapx.cm, data_col, meth, cdf_threshold, lower_limit) + except: + data_ub[k] = data_ub[k] * np.nan + + data_ub[data_ub == 0.0000000] = np.nan + data_ub = data_ub.loc[data_ub.index[0]:pd.to_datetime('2099-12-31 23:59:59')] + + return data_ub + + +def climate_indi(ts, indi='Summer days (Tmax ≥ 25°C)'): + ''' + Calculate climate indicator days. + Input time series of meteorological data + ''' + if pd.infer_freq(ts.index) != 'D': + print('Please provide daily data.') + return + + if indi == 'Summer days (Tmax ≥ 25°C)': # summer days + return (ts.Tmax >= 25.).groupby(ts.index.year).sum() + elif indi == 'Ice days (Tmax < 0°C)': # ice days + return (ts.Tmax < 0.).groupby(ts.index.year).sum() + elif indi == 'Frost days (Tmin < 0°C)': # frost days + return (ts.Tmin < 0.).groupby(ts.index.year).sum() + elif indi == 'Hot days (Tmax ≥ 30°C)': # hot days + return (ts.Tmax >= 30.).groupby(ts.index.year).sum() + elif indi == 'Tropic nights (Tmin ≥ 20°C)': # tropic night + return (ts.Tmin >= 20.).groupby(ts.index.year).sum() + elif indi == 'Rainy days (Precip ≥ 1mm)': # rainy days + return (ts.Prec >= 1.).groupby(ts.index.year).sum() + else: + print('Nothing calculated.') + return + +# TODO: document + signature +# TODO: extract plotting +def climate_indices(weather: xr.Dataset, climate: xr.Dataset, stati='coast', cliproj=True): + cindi = ['Ice days (Tmax < 0°C)', 'Frost days (Tmin < 0°C)', 'Summer days (Tmax ≥ 25°C)', 'Hot days (Tmax ≥ 30°C)','Tropic nights (Tmin ≥ 20°C)', 'Rainy days (Precip ≥ 1mm)'] + ci_topic = st.selectbox('Select Index:', cindi) + + if ci_topic == 'Rainy days (Precip ≥ 1mm)': + vari = 'Prec' + meth = 'rel' + elif (ci_topic == 'Frost days (Tmin < 0°C)') | (ci_topic == 'Tropic nights (Tmin ≥ 20°C)'): + vari = 'Tmin' + meth = 'abs' + else: + vari = 'Tmax' + meth = 'abs' + + w1 = weather[stati].sel(vars=vari).to_dataframe().dropna() + w1.columns = ['bla', vari] + + plt.figure(figsize=(10,2.5)) + wi = climate_indi(w1, ci_topic).astype(int) + wi.plot(style='.', color='steelblue', label='Coast weather') + wi.rolling(10, center=True).mean().plot(color='steelblue', label='Rolling mean\n(10 years)') + + if cliproj: + c1 = climate.sel(vars=vari).to_dataframe() + c1 = c1[c1.columns[c1.columns != 'vars']] + c2 = applySDM(w1[vari], c1, meth=meth) + + firstitem = True + for j in c2.columns: + dummyt = pd.DataFrame(c2[j]) + dummyt.columns = [vari] + ci = pd.DataFrame(climate_indi(dummyt, ci_topic).astype(int)) + ci.columns = [j] + + if firstitem: + ci[j].plot(style='.', color='gray', alpha=0.1, label='all climate projections') + cid = ci + firstitem = False + else: + ci[j].plot(style='.', color='gray', alpha=0.1, label='_') + cid = pd.concat([cid, ci], axis=1) + + rcpx = np.array([x[-5:] for x in cid.columns.values]) + for n in np.unique(rcpx): + nx = n + if n == np.unique(rcpx)[-1]: + nx = n + '\n(Rolling of 5 years)' + cid[cid.columns[rcpx == n]].mean(axis=1).rolling(5, center=True).mean().plot(label='Mean of ' + nx) + + plt.legend(ncol=2) + plt.ylabel('Number of days') + plt.title(ci_topic) + st.pyplot() + + if ci_topic == 'Ice days (Tmax < 0°C)': + st.markdown('''Number of days in one year which persistently remain below 0°C air temperature.''') + elif ci_topic == 'Frost days (Tmin < 0°C)': + st.markdown('''Number of days in one year which reached below 0°C air temperature.''') + elif ci_topic == 'Summer days (Tmax ≥ 25°C)': + st.markdown('''Number of days in one year which reached or exceeded 25°C air temperature.''') + elif ci_topic == 'Hot days (Tmax ≥ 30°C)': + st.markdown('''Number of days in one year which reached or exceeded 30°C air temperature.''') + elif ci_topic == 'Tropic nights (Tmin ≥ 20°C)': + st.markdown('''Number of days in one year which persistently remained above 20°C air temperature.''') + elif ci_topic == 'Rainy days (Precip ≥ 1mm)': + st.markdown('''Number of days in one year which received at least 1 mm precipitation.''') + return + + +def weather_explorer(w_topic: str): + weather, climate = load_alldata() + #weather = load_data('Weather') + + #aspects = ['Annual', 'Monthly', 'Season'] + #w_aspect = st.sidebar.selectbox('Temporal aggegate:', aspects) + + #cliproj = st.sidebar.checkbox('add climate projections',False) + + statios = list(weather.keys()) + stat1 = st.selectbox('Select station/group (see map in sidebar for location):', statios) + + aspects = ['Annual', 'Monthly'] # , 'Season'] + w_aspect = st.selectbox('Select temporal aggegate:', aspects) + + cliproj = st.checkbox('add climate projections (for coastal region)',False) + if cliproj: + plt_map(stat1, 'CORDEX') + st.sidebar.markdown( + '''Map with available stations (blue dots) and selected reference station (magenta highlight). The climate model grid is given in orange with the selected references as filled dots.''', + unsafe_allow_html=True) + else: + plt_map(stat1) + st.sidebar.markdown( + '''Map with available stations (blue dots) and selected reference station (magenta highlight).''', + unsafe_allow_html=True) + + if w_topic == 'Warming': + + navi_vars = ['Maximum Air Temperature', 'Mean Air Temperature', 'Minimum Air Temperature'] + navi_var = st.sidebar.radio("Select variable:", options=navi_vars) + if navi_var[:4] == 'Mini': + vari = 'Tmin' + afu = np.min + ag = 'min' + elif navi_var[:4] == 'Maxi': + vari = 'Tmax' + afu = np.max + ag = 'max' + else: + vari = 'T' + afu = np.mean + ag = 'mean' + + if w_aspect == 'Annual': + wdata = weather[stat1].sel(vars=vari).resample(time='1Y').apply(afu).to_dataframe()[stat1] + wdata = wdata[~np.isnan(wdata)] + allw = weather.sel(vars=vari).resample(time='1Y').apply(afu).to_dataframe().iloc[:, 1:] + + dataLq = float(np.floor(allw.min().quantile(0.22))) + datamin = float(np.min([dataLq, np.round(allw.min().min(), 1)])) + if cliproj: + rcps = ['rcp26', 'rcp45', 'rcp85'] + rcp = st.selectbox( + 'RCP (Mean over all projections will be shown. For more details go to section "Climate Projections"):', + rcps) + + data = climate.filter_by_attrs(RCP=rcp).sel(vars=vari).resample(time='1Y').apply(afu).to_dataframe() + data = data[data.columns[data.columns != 'vars']] + data_ub = applySDM(wdata, data, meth='abs') + + dataUq = float(np.ceil(data_ub.max().quantile(0.76))) + datamax = float(np.max([dataUq, np.round(data_ub.max().max(), 1)])) + else: + dataUq = float(np.ceil(allw.max().quantile(0.76))) + datamax = float(np.max([dataUq,np.round(allw.max().max(), 1)])) + + datarng = st.slider('Adjust data range on x-axis of plot:', min_value=datamin, max_value=datamax, value=(dataLq, dataUq), step=0.1, key='drangew') + + if cliproj: + ax = kde(wdata, data_ub.mean(axis=1), split_ts=3) + else: + ax = kde(wdata, split_ts=3) + + ax.set_title(stat1 + ' Annual ' + navi_var) + ax.set_xlabel('T (°C)') + ax.set_xlim(datarng[0],datarng[1]) + st.pyplot() + + sndstat = st.checkbox('Show second station for comparison') + + if sndstat: + stat2 = st.selectbox('Select second station:', [x for x in statios if x != stat1]) + wdata2 = weather[stat2].sel(vars=vari).resample(time='1Y').apply(afu).to_dataframe()[stat2] + + ax2 = kde(wdata2, split_ts=3) + ax2.set_title(stat2 + ' Annual ' + navi_var) + ax2.set_xlabel('T (°C)') + ax2.set_xlim(datarng[0],datarng[1]) + st.pyplot() + + # Re-implement this as a application wide service + # expl_md = read_markdown_file('explainer/stripes.md') + # st.markdown(expl_md, unsafe_allow_html=True) + + elif w_aspect == 'Monthly': + wdata = weather[stat1].sel(vars=vari).resample(time='1M').apply(afu).to_dataframe()[stat1] + wdata = wdata[~np.isnan(wdata)] + ref_yr = st.slider('Reference period for anomaly calculation:', min_value=int(wdata.index.year.min()), max_value=2020,value=(max(1980, int(wdata.index.year.min())), 2000)) + + if cliproj: + rcps = ['rcp26', 'rcp45', 'rcp85'] + rcp = st.selectbox('RCP (Mean over all projections will be shown. For more details go to section "Climate Projections"):', rcps) + + data = climate.filter_by_attrs(RCP=rcp).sel(vars=vari).resample(time='1M').apply(afu).to_dataframe() + data = data[data.columns[data.columns != 'vars']] + + #ub = st.sidebar.checkbox('Apply SDM bias correction',True) + ub = True # simplify here and automatically apply bias correction + + if ub: + data_ub = applySDM(wdata, data, meth='abs') + yrplot_hm(pd.concat([wdata.loc[wdata.index[0]:data.index[0] - pd.Timedelta('1M')], data_ub.mean(axis=1)]),ref_yr, ag, li=2006) + else: + yrplot_hm(pd.concat([wdata.loc[wdata.index[0]:data.index[0] - pd.Timedelta('1M')], data.mean(axis=1)]), ref_yr, ag, li=2006) + + plt.title(stat1 + ' ' + navi_var + ' anomaly to ' + str(ref_yr[0]) + '-' + str(ref_yr[1])) + st.pyplot() + + + + else: + yrplot_hm(wdata,ref_yr,ag) + plt.title(stat1 + ' ' + navi_var + ' anomaly to ' + str(ref_yr[0]) + '-' + str(ref_yr[1])) + st.pyplot() + + sndstat = st.checkbox('Compare to a second station?') + + if sndstat: + stat2 = st.selectbox('Select second station:', [x for x in statios if x != stat1]) + data2 = weather[stat2].sel(vars=vari).resample(time='1M').apply(afu).to_dataframe()[stat2] + data2 = data2[~np.isnan(data2)] + + ref_yr2 = list(ref_yr) + if ref_yr2[1] str: + """ + Select a topic from a list of topics. The selected topic is returned and + additionally published to the session cache. + + Parameters + ---------- + topic_list : List[str] + List of topics to select from. + force_topic_select : bool + If False, the dropdown will not be shown if a topic was already + selected and is present in the streamlit session cache. + Default: True + container : streamlit.st.container + Container to use for the dropdown. Defaults to main streamlit. + **kwargs + These keyword arguments are only accepted to directly inject + :class:`Config ` objects. + """ + # check if a topic is already present + if kwargs.get('no_cache', False): + current_topic = kwargs.get('current_topic', None) + else: # pragma: no cover + current_topic = st.session_state.get('topic', kwargs.get('current_topic', None)) + if current_topic is not None and not force_topic_select: + return current_topic + + # otherwise print select + topic = st.selectbox( + 'Select a topic', + topic_list, + index=0 if current_topic is None else topic_list.index(current_topic) + ) + + # store topic in session cache + if current_topic != topic and not kwargs.get('no_cache', False): # pragma: no cover + st.session_state['topic'] = topic + + return topic diff --git a/ruins/plotting/__init__.py b/ruins/plotting/__init__.py new file mode 100644 index 0000000..39a2e15 --- /dev/null +++ b/ruins/plotting/__init__.py @@ -0,0 +1,3 @@ +from .maps import plt_map +from .kde import kde +from .weather_data import monthlyx, yrplot_hm diff --git a/ruins/plotting/kde.py b/ruins/plotting/kde.py new file mode 100644 index 0000000..18ad7fb --- /dev/null +++ b/ruins/plotting/kde.py @@ -0,0 +1,125 @@ +import numpy as np +import matplotlib.pyplot as plt + + +def kde(data, cmdata='none', split_ts=1, cplot=True, eq_period=True): + # plot of kde with stripes + from sklearn.neighbors import KernelDensity + cxx = ['#E69F00', '#009E73', '#0072B2', '#D55E00', '#CC79A7'] + cxx2 = ['#8c6bb1', '#810f7c'] + + data = data[~np.isnan(data)] + x_d = np.linspace(np.min(data) * 0.9, np.max(data) * 1.1, len(data)) + + # instantiate and fit the KDE model + kde = KernelDensity(bandwidth=1.0, kernel='gaussian') + kde.fit(data[:, None]) + + # score_samples returns the log of the probability density + logprob = kde.score_samples(x_d[:, None]) + + if cplot & (split_ts == 1): + plt.fill_between(x_d, np.exp(logprob), alpha=0.4, facecolor='grey') + + lp = np.exp(logprob) + xd = x_d + + if type(cmdata) != str: + fig, (ax, cax, cax2) = plt.subplots(ncols=3, figsize=(10.5, 2.3), gridspec_kw={"width_ratios": [1, 0.02, 0.02]}) + + x_d2 = np.linspace(np.min(cmdata) * 0.9, np.max(cmdata) * 1.1, len(cmdata)) + # instantiate and fit second KDE model + kde2 = KernelDensity(bandwidth=1.0, kernel='gaussian') + kde2.fit(cmdata[:, None]) + + # score_samples returns the log of the probability density + logprob2 = kde2.score_samples(x_d2[:, None]) + + if cplot & (split_ts == 1): + ax.fill_between(x_d2, np.exp(logprob2), alpha=0.4, facecolor='grey') + + lp2 = np.exp(logprob2) + xd2 = x_d2 + else: + fig, (ax, cax) = plt.subplots(ncols=2, figsize=(10.5, 2.3), gridspec_kw={"width_ratios": [1, 0.06]}) + + if split_ts > 1: + if eq_period: + spliti = [0, len(data) - 40, len(data) - 20, len(data)] + else: + spliti = np.linspace(0, len(data), split_ts + 1).astype(int) + + for i in np.arange(split_ts): + datax = data.iloc[spliti[i]:spliti[i + 1]] + x_d = np.linspace(np.min(datax) * 0.9, np.max(datax) * 1.1, len(datax)) + + try: + # instantiate and fit the KDE model + kde = KernelDensity(bandwidth=1.0, kernel='gaussian') + kde.fit(datax[:, None]) + + # score_samples returns the log of the probability density + logprob = kde.score_samples(x_d[:, None]) + + if cplot: + ax.fill_between(x_d, np.exp(logprob), alpha=0.4, facecolor=cxx[i], + label='-'.join([str(datax.index.year.min()), str(datax.index.year.max())])) + except: + pass + + if type(cmdata) != str: + # add climate model data + csplit_ts = [2040, 2080] + for i in np.arange(2): + datax = cmdata.loc[(cmdata.index.year >= csplit_ts[i]) & (cmdata.index.year < csplit_ts[i] + 20)] + x_d = np.linspace(np.min(datax) * 0.9, np.max(datax) * 1.1, len(datax)) + + try: + # instantiate and fit the KDE model + kde = KernelDensity(bandwidth=1.0, kernel='gaussian') + kde.fit(datax[:, None]) + + # score_samples returns the log of the probability density + logprob = kde.score_samples(x_d[:, None]) + + if cplot: + ax.fill_between(x_d, np.exp(logprob), alpha=0.4, facecolor=cxx2[i], + label='-'.join([str(datax.index.year.min()), str(datax.index.year.max())])) + + except: + pass + + ax.legend(loc=1, ncol=5) + + if cplot: + # cmap = plt.cm.get_cmap('cividis_r') + cmap = plt.cm.get_cmap('viridis_r') + colors = plt.cm.cividis_r(np.linspace(0, 1, len(data))) + colorsx = cmap(np.arange(cmap.N)) + + for i in np.arange(len(data)): + ax.plot([data.iloc[i], data.iloc[i]], [0, np.max(lp) * 0.9], c=colors[i]) + + labcb = 'Year' + if type(cmdata) != str: + cmap2 = plt.cm.get_cmap('plasma') + colors2 = plt.cm.plasma(np.linspace(0, 1, len(cmdata))) + colorsx2 = cmap2(np.arange(cmap2.N)) + + for i in np.arange(len(cmdata)): + ax.plot([cmdata.iloc[i], cmdata.iloc[i]], [0, np.max(lp) * 0.9], c=colors2[i]) + + cbar2 = plt.colorbar(plt.cm.ScalarMappable(cmap=cmap2), cax=cax2, label=labcb, + ticks=[0, 0.2, 0.4, 0.6, 0.8, 1], fraction=0.0027, anchor=(1.0, 0.1)) + cbar2.ax.set_yticklabels( + np.round(np.linspace(cmdata.index.year.min(), cmdata.index.year.max(), 6)).astype(int).astype(str)) + labcb = '' + + cbar = plt.colorbar(plt.cm.ScalarMappable(cmap=cmap), cax=cax, label=labcb, ticks=[0, 0.2, 0.4, 0.6, 0.8, 1]) + cbar.ax.set_yticklabels( + np.round(np.linspace(data.index.year.min(), data.index.year.max(), 6)).astype(int).astype( + str)) # vertically oriented colorbar + + ax.set_ylabel('Occurrence (KDE)') + + return ax diff --git a/ruins/plotting/maps.py b/ruins/plotting/maps.py new file mode 100644 index 0000000..cdbcd1a --- /dev/null +++ b/ruins/plotting/maps.py @@ -0,0 +1,153 @@ +import streamlit as st +import plotly.graph_objs as go +import plotly.express as px +import numpy as np + + +def plt_map(sel='all',cm='none'): + # TODO remove this part + import xarray as xr + import pandas as pd + dummy = xr.open_dataset('data/CORDEXgrid.nc') + dummy5 = xr.open_dataset('data/CMIP5grid.nc') + stats = pd.read_csv('data/stats.csv', index_col=0) + stats['ms'] = 15. + stats['color'] = 'gray' + + mapbox_access_token = 'pk.eyJ1IjoiY29qYWNrIiwiYSI6IkRTNjV1T2MifQ.EWzL4Qk-VvQoaeJBfE6VSA' + px.set_mapbox_access_token(mapbox_access_token) + + nodexy = pd.DataFrame([dummy.lon.values.ravel(), dummy.lat.values.ravel()]).T + nodexy.columns = ['lon', 'lat'] + nodexy['hov'] = 'CORDEX grid' + + def lin_grid(fig, dummy, chex='#d95f0e', opc=0.7): + x1, x2 = np.shape(dummy.lon) + lond = np.diff(dummy.lon.values) + latd = np.diff(dummy.lat.values) + lond1 = np.diff(dummy.lon.values, axis=0) + latd1 = np.diff(dummy.lat.values, axis=0) + + for i in np.arange(x2)[1:-1]: + fig.add_trace(go.Scattermapbox( + mode='lines', + lon=dummy.lon.values[:, i] - 0.5 * lond[:, i - 1], + lat=dummy.lat.values[:, i] - 0.5 * latd[:, i - 1], + line={'color': chex, 'width': 1}, + hoverinfo='skip', + opacity=opc)) + + fig.add_trace(go.Scattermapbox( + mode='lines', + lon=dummy.lon.values[:, i] + 0.5 * lond[:, i - 1], + lat=dummy.lat.values[:, i] + 0.5 * latd[:, i - 1], + line={'color': chex, 'width': 1}, + hoverinfo='skip', + opacity=opc)) + + for i in np.arange(x1)[1:-1]: + fig.add_trace(go.Scattermapbox( + mode='lines', + lon=dummy.lon.values[i, :] - 0.5 * lond1[i - 1, :], + lat=dummy.lat.values[i, :] - 0.5 * latd1[i - 1, :], + line={'color': chex, 'width': 1}, + hoverinfo='skip', + opacity=opc)) + + fig.add_trace(go.Scattermapbox( + mode='lines', + lon=dummy.lon.values[i, :] + 0.5 * lond1[i - 1, :], + lat=dummy.lat.values[i, :] + 0.5 * latd1[i - 1, :], + line={'color': chex, 'width': 1}, + hoverinfo='skip', + opacity=opc)) + + return fig + + def add_stats(sel='all'): + + + if sel == 'all': + ac = '#2c7fb8' + stats1 = stats + else: + ac = 'gray' + + fig.add_trace(go.Scattermapbox( + lat=stats.lat, + lon=stats.lon, + mode='markers', + marker=go.scattermapbox.Marker( + size=10, + color=ac, + opacity=0.8 + ), + text=stats['Station name'] + )) + + ac = '#df65b0' + if sel == 'krummhoern': + stats1 = stats.loc[stats.krummhoern == True] + elif sel == 'coast': + stats1 = stats.loc[stats.coast == True] + elif sel == 'niedersachsen': + stats1 = stats.loc[stats.niedersachsen == True] + elif sel == 'inland': + stats1 = stats.loc[stats.inland == True] + else: + try: + stats1 = pd.DataFrame(stats.loc[sel]).T + except: + ac = 'gray' + stats1 = pd.DataFrame(stats.loc['Norderney']).T + + fig.add_trace(go.Scattermapbox( + lat=stats1.lat, + lon=stats1.lon, + mode='markers', + marker=go.scattermapbox.Marker( + size=10, + color=ac, + opacity=0.8 + ), + text=stats1['Station name'] + )) + + return fig + + def add_cmpx(cm): + maskcordex_coast = [[10, 4], [11, 4], [11, 5], [11, 6], [11, 7], [11, 8], [10, 8], [9, 8], [8, 8], [8, 9], + [8, 10], [9, 10], [10, 10], [10, 11], [11, 12], [12, 12], [12, 13], [12, 14], [13, 14], + [14, 14], [15, 14], [16, 14], [17, 14], [18, 14], [19, 15], [20, 15], [21, 14], [22, 14], + [22, 13], + [9, 4], [10, 5], [10, 6], [10, 7], [10, 8], [9, 7], [8, 7], [7, 7], [7, 8], [7, 9], [9, 9], + [10, 12], [11, 13], [11, 14], [14, 15], [15, 15], [16, 15], [17, 15], [18, 15], [19, 16], + [20, 16], [21, 15], [22, 15], + [11, 4], [12, 4], [12, 5], [12, 6], [12, 7], [12, 8], [11, 10], [11, 11], [13, 12], + [13, 13], [14, 13], [15, 13], [16, 13], [17, 13], [18, 13], [19, 14], [20, 14], [21, 13]] + for cc in maskcordex_coast: + fig.add_trace(go.Scattermapbox( + lat=[dummy.lat.values[tuple(cc)]], + lon=[dummy.lon.values[tuple(cc)]], + mode='markers', + marker=go.scattermapbox.Marker( + size=8, + color='#fec44f', + opacity=0.8 + ), + hoverinfo='skip' + )) + return fig + + # fig = go.Scattermapbox(lon = nodexy.lon, lat= nodexy.lat, mode='markers', marker_symbol='square', marker_size=15) + #fig = px.scatter_mapbox(nodexy, lat='lat', lon='lon', center={'lat': 53.0, 'lon': 8.3}, zoom=5, opacity=0.1, hover_data=['hov']) + fig = px.scatter_mapbox(stats, lat='lat', lon='lon', center={'lat': 53.0, 'lon': 8.6}, zoom=5, size='ms', opacity=0.8, color='color', hover_data=['Station name', 'lat', 'lon'], size_max=10) + if cm != 'none': + fig = lin_grid(fig, dummy) + fig = lin_grid(fig, dummy5, '#2c7fb8') + fig = add_cmpx(cm) + fig = add_stats(sel) + fig.update_layout(showlegend=False,width=300, height=350,margin=dict(l=10, r=10, b=10, t=10)) # ,center={'lat':54.0,'lon':8.3}, zoom=7) + st.sidebar.plotly_chart(fig) + return + diff --git a/ruins/plotting/weather_data.py b/ruins/plotting/weather_data.py new file mode 100644 index 0000000..5a52116 --- /dev/null +++ b/ruins/plotting/weather_data.py @@ -0,0 +1,112 @@ +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt + + +def yrplot_hm(sr, ref=[1980, 2000], ag='sum', qa=0.95, cbar_title='Temperature anomaly (K)', cmx='coolwarm', cmxeq=True, li=False): + # plot of heatmap with monthyl and annual stripes + yrs = sr.index.year.unique() + dummy = np.zeros((len(yrs), 14)) * np.nan + dummy = pd.DataFrame(dummy) + dummy.index = yrs + dummy.columns = ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D', '', 'Year'] + + for i in sr.index: + dummy.iloc[i.year - yrs[0], i.month - 1] = sr.loc[i] + for i in yrs: + if ag == 'sum': + dummy.iloc[i - yrs[0], 13] = sr.loc[sr.index.year == i].sum() + cmx = 'coolwarm_r' + elif ag == 'min': + dummy.iloc[i - yrs[0], 13] = sr.loc[sr.index.year == i].min() + elif ag == 'max': + dummy.iloc[i - yrs[0], 13] = sr.loc[sr.index.year == i].max() + else: # ag == 'mean' + dummy.iloc[i - yrs[0], 13] = sr.loc[sr.index.year == i].mean() + + if ref == None: + pass + else: + refx = dummy.loc[ref[0]:ref[1]].mean(axis=0) + dummy = dummy - refx + + if cmxeq: + vxU = dummy.abs().quantile(qa).quantile(qa) + vxL = -1. * vxU + else: + vxU = dummy.quantile(qa).quantile(qa) + vxL = dummy.quantile(1. - qa).quantile(1. - qa) + + if ag == 'sum': + dummy.iloc[:, 13] = dummy.iloc[:, 13] / 12 + + plt.figure(figsize=(8,len(dummy)/15.)) + ax = sns.heatmap(dummy, cmap=cmx, vmin=vxL, vmax=vxU, cbar_kws={'label': cbar_title}) + + if ref == None: + pass + else: + ax.add_patch( + plt.Rectangle((0, ref[0] - yrs[0]), 12, ref[1] - ref[0], fill=True, edgecolor='red', facecolor='gray', lw=2, + alpha=0.3, clip_on=False)) + ax.add_patch( + plt.Rectangle((13, ref[0] - yrs[0]), 1, ref[1] - ref[0], fill=True, edgecolor='red', facecolor='gray', lw=2, + alpha=0.3, clip_on=False)) + ax.annotate('Reference period', (0.5, ref[1] - yrs[0] - 2), color='white', weight='bold', ha='left', + va='bottom', alpha=0.6) + + if type(li) == int: + ax.axhline(li - yrs[0], color='k', ls='--', lw=1, alpha=0.5) + ax.annotate(' >> observed', (12.5, li - yrs[0] - 0.5), color='k', ha='center', va='bottom', alpha=0.6, + rotation=90.) + ax.annotate('modelled << ', (12.5, li - yrs[0] + 0.5), color='k', ha='center', va='top', alpha=0.6, + rotation=90.) + # ax.add_patch(plt.Rectangle((0, li-yrs[0]), 12, 0, fill=False, edgecolor='k', ls='--', lw=1, alpha=0.5, clip_on=False)) + + ax.set_ylabel('Year') + ax.set_xlabel('Month ') + return + + +def monthlyx(dy, dyx=1, ylab='T (°C)', clab1='Monthly Mean in Year', clab2='Monthly Max in Year', pls='cividis_r'): + cmap = plt.cm.get_cmap(pls) + colors = cmap(np.linspace(0, 1, len(dy.index.year.unique())+1)) + colorsx = cmap(np.arange(cmap.N)) + + idx1 = dy.index.year - dy.index.year.min() + idx1m = dy.index.month + + if type(dyx) == int: + pass + else: + idx2 = (dyx.index.year - dyx.index.year.min()).astype(int) + idx2m = dyx.index.month + + cmap1 = plt.cm.get_cmap('gist_heat_r') + colors1 = plt.cm.gist_heat_r(np.linspace(0, 1, len(dyx.index.year.unique()) + 1)) + colorsx1 = cmap1(np.arange(cmap1.N)) + + for i in dy.columns: + plt.scatter(idx1m + (np.random.rand(len(dy)) - 1.5), dy[i].values.astype(np.float), c=colors[idx1], alpha=0.6, s=2) + + if type(dyx) == int: + pass + else: + for i in dyx.columns: + plt.scatter(idx2m + (np.random.rand(len(dyx)) - 1.5), dyx[i].values.astype(np.float), c=colors1[idx2], alpha=0.6, s=2) + + cbar = plt.colorbar(plt.cm.ScalarMappable(cmap=cmap), label=clab1, ticks=[0, 0.2, 0.4, 0.6, 0.8, 1]) + cbar.ax.set_yticklabels(np.round(np.linspace(dy.index.year.min(), dy.index.year.max(), 6)).astype(int).astype(str)) + + if type(dyx) == int: + pass + else: + cbar1 = plt.colorbar(plt.cm.ScalarMappable(cmap=cmap1), label=clab2, ticks=[0, 0.2, 0.4, 0.6, 0.8, 1]) + cbar1.ax.set_yticklabels( + np.round(np.linspace(dyx.index.year.min(), dyx.index.year.max(), 6)).astype(int).astype(str)) + + plt.xticks([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']) + plt.ylabel(ylab) + return + diff --git a/ruins/tests/test_topic_selector.py b/ruins/tests/test_topic_selector.py new file mode 100644 index 0000000..0a091c8 --- /dev/null +++ b/ruins/tests/test_topic_selector.py @@ -0,0 +1,23 @@ +from ruins import components + + +def test_default(): + """Test default behavior""" + assert components.topic_selector(['a', 'b', 'c'], no_cache=True) == 'a' + + +def test_current_preset(): + """Test with current topic set""" + topic = components.topic_selector(['a', 'b', 'c'], current_topic='b', no_cache=True) + + assert topic == 'b' + + +def test_no_force_render(): + """Test with rendering disabled""" + # default + assert components.topic_selector(['a', 'b', 'c'], force_topic_select=False, no_cache=True) == 'a' + + # with current topic set + assert components.topic_selector(['a', 'b', 'c'], current_topic='b', force_topic_select=False, no_cache=True) == 'b' + \ No newline at end of file diff --git a/ruins/tests/test_weather.py b/ruins/tests/test_weather.py new file mode 100644 index 0000000..64fe7c7 --- /dev/null +++ b/ruins/tests/test_weather.py @@ -0,0 +1,39 @@ +from ruins.apps import weather +from ruins.tests.util import get_test_config + +from ruins.core import DataManager + + +# TODO use the config and inject the dedub config here + +# TODO only run this test when the Value Error is solved +#def test_run_app(): +# """Make sure the appp runs without failing""" +# weather.main_app() + + +def test_climate_indices(): + """Test only climate indices """ + conf = get_test_config() + dm = DataManager(**conf) + + w = dm['weather'].read() + c = dm['cordex_coast'].read() + + # run + weather.climate_indices(w, c) + + +def test_climate_indi(): + """Test climate indi function""" + conf = get_test_config() + dm = DataManager(**conf) + + weath = dm['weather'].read() + + # make an arbitrary selection + w = weath['coast'].sel(vars='Tmax').to_dataframe().dropna() + w.columns = ['_', 'Tmax'] + + # run + weather.climate_indi(w, 'Ice days (Tmax < 0°C)') diff --git a/ruins/tests/testdata/cordex_coast.nc b/ruins/tests/testdata/cordex_coast.nc new file mode 100644 index 0000000..c1ffaf3 Binary files /dev/null and b/ruins/tests/testdata/cordex_coast.nc differ diff --git a/ruins/tests/testdata/weather.nc b/ruins/tests/testdata/weather.nc new file mode 100644 index 0000000..f1991bc Binary files /dev/null and b/ruins/tests/testdata/weather.nc differ diff --git a/ruins/tests/util.py b/ruins/tests/util.py new file mode 100644 index 0000000..067e93f --- /dev/null +++ b/ruins/tests/util.py @@ -0,0 +1,11 @@ +import os +from ruins.core import Config + +def get_test_config() -> Config: + """Get a test config""" + # overwrite some settings for unit tests + args = dict( + datapath=os.path.abspath(os.path.join(os.path.dirname(__file__), 'testdata')), + debug=True + ) + return Config(**args)