|
8 | 8 | import pandas as pd |
9 | 9 | import numpy as np |
10 | 10 | from scipy.optimize import Bounds, LinearConstraint, NonlinearConstraint |
| 11 | +import matplotlib.pyplot as plt |
11 | 12 | import seaborn as sns |
12 | 13 |
|
13 | 14 | from climada.hazard import Hazard |
14 | 15 | from climada.entity import Exposures, ImpactFuncSet |
15 | 16 | from climada.engine import Impact, ImpactCalc |
16 | 17 | import climada.util.coordinates as u_coord |
| 18 | +# from .bayesian_optimizer import BayesianOptimizerOutput |
17 | 19 |
|
18 | 20 | ConstraintType = Union[LinearConstraint, NonlinearConstraint, Mapping] |
19 | 21 |
|
@@ -158,6 +160,131 @@ def plot_impf_set(self, **plot_kwargs): |
158 | 160 | """ |
159 | 161 | return self.impf_set.plot(**plot_kwargs) |
160 | 162 |
|
| 163 | + def plot_impf_variability( |
| 164 | + self, |
| 165 | + cost_func_diff: float = 0.1, |
| 166 | + p_space_df: Optional[pd.DataFrame] = None, |
| 167 | + plot_haz: bool = True, |
| 168 | + plot_impf_kws: Optional[dict] = None, |
| 169 | + plot_hist_kws: Optional[dict] = None, |
| 170 | + ): |
| 171 | + |
| 172 | + """Plot impact function variability with parameter combinations of |
| 173 | + almost equal cost function values |
| 174 | +
|
| 175 | + Args: |
| 176 | + cost_func_diff (float, optional): Max deviation from optimal cost |
| 177 | + function value (as fraction). Defaults to 0.1 (i.e. 10%). |
| 178 | + p_space_df (pd.DataFrame, optional): parameter space. Defaults to None. |
| 179 | + plot_haz (bool, optional): Whether or not to plot hazard intensity |
| 180 | + distibution. Defaults to False. |
| 181 | + plot_impf_kws (dict, optional): Keyword arguments for impact |
| 182 | + function plot. Defaults to None. |
| 183 | + plot_hist_kws (dict, optional): Keyword arguments for hazard |
| 184 | + intensity distribution plot. Defaults to None. |
| 185 | + """ |
| 186 | + |
| 187 | + # Initialize plot keyword arguments |
| 188 | + if plot_impf_kws is None: |
| 189 | + plot_impf_kws = {} |
| 190 | + if plot_hist_kws is None: |
| 191 | + plot_hist_kws = {} |
| 192 | + |
| 193 | + # Retrieve hazard type and parameter space |
| 194 | + haz_type = self.input.hazard.haz_type |
| 195 | + if p_space_df is None: |
| 196 | + # Assert that self.output has the p_space_to_dataframe() method, |
| 197 | + # which is defined for the BayesianOptimizerOutput class |
| 198 | + if not hasattr(self.output,"p_space_to_dataframe"): |
| 199 | + raise TypeError( |
| 200 | + "To derive the full impact function parameter space, " |
| 201 | + "plot_impf_variability() requires BayesianOptimizerOutput " |
| 202 | + "as OutputEvaluator.output attribute, which provides the " |
| 203 | + "method p_space_to_dataframe()." |
| 204 | + ) |
| 205 | + p_space_df = self.output.p_space_to_dataframe() |
| 206 | + |
| 207 | + # Retrieve list of parameters required for creating impact functions |
| 208 | + # and remove the dimension 'Cost Function'. |
| 209 | + params = p_space_df.columns.tolist() |
| 210 | + try: |
| 211 | + params.remove('Cost Function') |
| 212 | + except ValueError: |
| 213 | + pass |
| 214 | + |
| 215 | + # Retrieve parameters of impact functions with cost function values |
| 216 | + # within 'cost_func_diff' % of the best estimate |
| 217 | + params_within_range = p_space_df[params] |
| 218 | + plot_space_label = 'Parameter space' |
| 219 | + if cost_func_diff is not None: |
| 220 | + max_cost_func_val = (p_space_df['Cost Function'].min()* |
| 221 | + (1+cost_func_diff)) |
| 222 | + params_within_range = p_space_df.loc[ |
| 223 | + p_space_df['Cost Function'] <=max_cost_func_val,params |
| 224 | + ] |
| 225 | + plot_space_label = (f"within {int(cost_func_diff*100)} percent " |
| 226 | + f"of best fit") |
| 227 | + |
| 228 | + # Set plot defaults |
| 229 | + color = plot_impf_kws.pop('color','tab:blue') |
| 230 | + lw = plot_impf_kws.pop('lw',2) |
| 231 | + zorder = plot_impf_kws.pop('zorder',3) |
| 232 | + label = plot_impf_kws.pop('label','best fit') |
| 233 | + |
| 234 | + #get number of impact functions and create a plot for each |
| 235 | + n_impf = len(self.impf_set.get_func(haz_type=haz_type)) |
| 236 | + axes=[] |
| 237 | + |
| 238 | + for impf_idx in range(n_impf): |
| 239 | + |
| 240 | + _,ax = plt.subplots() |
| 241 | + |
| 242 | + #Plot best-fit impact function |
| 243 | + best_impf = self.impf_set.get_func(haz_type=haz_type)[impf_idx] |
| 244 | + ax.plot(best_impf.intensity,best_impf.mdd*best_impf.paa*100, |
| 245 | + color=color,lw=lw,zorder=zorder,label=label,**plot_impf_kws) |
| 246 | + |
| 247 | + #Plot all impact functions within 'cost_func_diff' % of best estimate |
| 248 | + for row in range(params_within_range.shape[0]): |
| 249 | + label_temp = plot_space_label if row == 0 else None |
| 250 | + |
| 251 | + sel_params = params_within_range.iloc[row,:].to_dict() |
| 252 | + temp_impf_set = self.input.impact_func_creator(**sel_params) |
| 253 | + temp_impf = temp_impf_set.get_func(haz_type=haz_type)[impf_idx] |
| 254 | + |
| 255 | + ax.plot(temp_impf.intensity,temp_impf.mdd*temp_impf.paa*100, |
| 256 | + color='grey',alpha=0.4,label=label_temp) |
| 257 | + |
| 258 | + # Plot hazard intensity value distributions |
| 259 | + if plot_haz: |
| 260 | + haz_vals = self.input.hazard.intensity[ |
| 261 | + :, self.input.exposure.gdf[f"centr_{haz_type}"] |
| 262 | + ] |
| 263 | + |
| 264 | + #Plot defaults |
| 265 | + color_hist = plot_hist_kws.pop('color','tab:orange') |
| 266 | + alpha_hist = plot_hist_kws.pop('alpha',0.3) |
| 267 | + |
| 268 | + ax2 = ax.twinx() |
| 269 | + ax2.hist(haz_vals.data,bins=40,color=color_hist, |
| 270 | + alpha=alpha_hist,label='Hazard intensity\noccurence') |
| 271 | + ax2.set(ylabel='Hazard intensity occurence (#Exposure points)') |
| 272 | + ax.axvline(x=haz_vals.max(),label='Maximum hazard value', |
| 273 | + color='tab:orange') |
| 274 | + ax2.legend(loc='lower right') |
| 275 | + |
| 276 | + ax.set(xlabel=f"Intensity ({self.input.hazard.units})", |
| 277 | + ylabel="Mean Damage Ratio (MDR) in %", |
| 278 | + xlim=(min(best_impf.intensity),max(best_impf.intensity))) |
| 279 | + ax.legend() |
| 280 | + axes.append(ax) |
| 281 | + |
| 282 | + if n_impf > 1: |
| 283 | + return axes |
| 284 | + |
| 285 | + return ax |
| 286 | + |
| 287 | + |
161 | 288 | def plot_at_event( |
162 | 289 | self, |
163 | 290 | data_transf: Callable[[pd.DataFrame], pd.DataFrame] = lambda x: x, |
|
0 commit comments