@@ -80,6 +80,10 @@ def get_best_raw_objective_point_with_trial_index(
80
80
"""Given an experiment, identifies the arm that had the best raw objective,
81
81
based on the data fetched from the experiment.
82
82
83
+ Note: This function will error with an invalid configuration. If you would
84
+ prefer for error logs rather than exceptions, use
85
+ `get_best_by_raw_objective_with_trial_index`.
86
+
83
87
Args:
84
88
experiment: Experiment, on which to identify best raw objective arm.
85
89
optimization_config: Optimization config to use in place of the one stored
@@ -108,15 +112,10 @@ def get_best_raw_objective_point_with_trial_index(
108
112
if dat .df .empty :
109
113
raise ValueError ("Cannot identify best point if experiment contains no data." )
110
114
if any (oc .relative for oc in optimization_config .all_constraints ):
111
- if experiment .status_quo is not None :
112
- optimization_config = derelativize_opt_config (
113
- optimization_config = optimization_config ,
114
- experiment = experiment ,
115
- )
116
- else :
117
- logger .warning (
118
- "No status quo provided; relative constraints will be ignored."
119
- )
115
+ optimization_config = derelativize_opt_config (
116
+ optimization_config = optimization_config ,
117
+ experiment = experiment ,
118
+ )
120
119
121
120
# Only COMPLETED trials should be considered when identifying the best point
122
121
completed_indices = {
@@ -146,15 +145,22 @@ def get_best_raw_objective_point_with_trial_index(
146
145
raise ValueError ("No feasible points are in the search space." )
147
146
148
147
in_design_df = feasible_df .loc [is_in_design ]
148
+ value_by_arm_pull = get_trace_by_arm_pull_from_data (
149
+ df = in_design_df ,
150
+ optimization_config = optimization_config ,
151
+ use_cumulative_best = False ,
152
+ )
149
153
150
- objective = optimization_config .objective
151
- best_row_helper = (
152
- _get_best_row_for_scalarized_objective
153
- if isinstance (objective , ScalarizedObjective )
154
- else _get_best_row_for_single_objective
154
+ maximize = isinstance (optimization_config .objective , MultiObjective ) or (
155
+ not optimization_config .objective .minimize
156
+ )
157
+ best_row_idx = (
158
+ value_by_arm_pull ["value" ].idxmax ()
159
+ if maximize
160
+ else value_by_arm_pull ["value" ].idxmin ()
155
161
)
156
- # pyre-ignore Incompatible parameter type [6 ]
157
- best_row = best_row_helper ( df = in_design_df , objective = objective )
162
+ best_row = value_by_arm_pull . loc [ best_row_idx ]
163
+
158
164
best_arm = experiment .arms_by_name [best_row ["arm_name" ]]
159
165
best_trial_index = int (best_row ["trial_index" ])
160
166
objective_rows = dat .df .loc [
@@ -321,6 +327,9 @@ def get_best_by_raw_objective_with_trial_index(
321
327
TModelPredictArm is of the form:
322
328
({metric_name: mean}, {metric_name_1: {metric_name_2: cov_1_2}})
323
329
330
+ This is a version of `get_best_raw_objective_point_with_trial_index` that
331
+ logs errors rather than letting exceptions be raised.
332
+
324
333
Args:
325
334
experiment: Experiment, on which to identify best raw objective arm.
326
335
optimization_config: Optimization config to use in place of the one stored
@@ -468,51 +477,6 @@ def get_pareto_optimal_parameters(
468
477
return res
469
478
470
479
471
- # NOTE: This function will be removed in the next PR.
472
- def _get_best_row_for_scalarized_objective (
473
- df : pd .DataFrame ,
474
- objective : ScalarizedObjective ,
475
- ) -> pd .Series :
476
- df = df .copy ()
477
- # First, add a weight column, setting 0.0 if the metric is not part
478
- # of the objective
479
- metric_to_weight = {
480
- m .name : objective .weights [i ] for i , m in enumerate (objective .metrics )
481
- }
482
- df ["weight" ] = df ["metric_name" ].apply (lambda x : metric_to_weight .get (x ) or 0.0 )
483
- # Now, calculate the weighted linear combination via groupby,
484
- # filtering out NaN for missing data
485
- df ["weighted_mean" ] = df ["mean" ] * df ["weight" ]
486
- groupby_df = (
487
- df [["arm_name" , "trial_index" , "weighted_mean" ]]
488
- .groupby (["arm_name" , "trial_index" ], as_index = False )
489
- .sum (min_count = 1 )
490
- .dropna ()
491
- )
492
- if groupby_df .empty :
493
- raise ValueError ("No data has been logged for scalarized objective." )
494
- return (
495
- groupby_df .loc [groupby_df ["weighted_mean" ].idxmin ()]
496
- if objective .minimize
497
- else groupby_df .loc [groupby_df ["weighted_mean" ].idxmax ()]
498
- )
499
-
500
-
501
- # NOTE: This function will be removed in the next PR.
502
- def _get_best_row_for_single_objective (
503
- df : pd .DataFrame , objective : Objective
504
- ) -> pd .Series :
505
- objective_name = objective .metric .name
506
- objective_rows = df .loc [df ["metric_name" ] == objective_name ]
507
- if objective_rows .empty :
508
- raise ValueError (f'No data has been logged for objective "{ objective_name } ".' )
509
- return (
510
- objective_rows .loc [objective_rows ["mean" ].idxmin ()]
511
- if objective .minimize
512
- else objective_rows .loc [objective_rows ["mean" ].idxmax ()]
513
- )
514
-
515
-
516
480
def _is_row_feasible (
517
481
df : pd .DataFrame ,
518
482
optimization_config : OptimizationConfig ,
@@ -779,6 +743,11 @@ def get_trace_by_arm_pull_from_data(
779
743
"`Derelativize` the optimization config, or use `get_trace`."
780
744
)
781
745
746
+ empty_result = pd .DataFrame (columns = ["trial_index" , "arm_name" , "value" ])
747
+
748
+ if len (df ) == 0 :
749
+ return empty_result
750
+
782
751
# reshape data to wide, using only the metrics in the optimization config
783
752
metrics = list (optimization_config .metrics .keys ())
784
753
@@ -793,6 +762,16 @@ def get_trace_by_arm_pull_from_data(
793
762
.set_index (["trial_index" , "arm_name" , "metric_name" ])["mean" ]
794
763
.unstack (level = "metric_name" )
795
764
)
765
+ missing_metrics = [
766
+ m for m in metrics if m not in df_wide .columns or df_wide [m ].isnull ().any ()
767
+ ]
768
+ if len (missing_metrics ) > 0 :
769
+ raise ValueError (
770
+ "Some metrics are not present for all trials and arms. The "
771
+ f"following are missing: { missing_metrics } ."
772
+ )
773
+ if len (df_wide ) == 0 :
774
+ return empty_result
796
775
df_wide ["feasible" ] = df .groupby (["trial_index" , "arm_name" ])["row_feasible" ].all ()
797
776
df_wide .reset_index (inplace = True )
798
777
0 commit comments