code: def _explore(
      data: pd.DataFrame,
      bounds: Dict[str, Tuple[float, float]],
      current: list,
      base: Trial,
      old: Trial,
      config: Dict[str, Tuple[float, float]],
  ) -> Tuple[Dict, pd.DataFrame]:
      """Returns next hyperparameter configuration to use.
  
      This function primarily processes the data from completed trials
      and then requests the next config from the select_config function.
      It then adds the new trial to the dataframe, so that the reward change
      can be computed using the new weights.
      It returns the new point and the dataframe with the new entry.
      """
  
      df = data.sort_values(by="Time").reset_index(drop=True)
  
      # Group by trial ID and hyperparams.
      # Compute change in timesteps and reward.
      df["y"] = df.groupby(["Trial"] + list(bounds.keys()))[
          "Reward"
      ].diff()
      df["t_change"] = df.groupby(["Trial"] + list(bounds.keys()))[
          "Time"
      ].diff()
  
      # Delete entries without positive change in t.
      df = df[df["t_change"] > 0].reset_index(drop=True)
      df["R_before"] = df.Reward - df.y
  
      # Normalize the reward change by the update size.
      # For example if trials took diff lengths of time.
      df["y"] = df.y / df.t_change
      df = df[~df.y.isna()].reset_index(drop=True)
      df = df.sort_values(by="Time").reset_index(drop=True)
  
      # Only use the last 1k datapoints, so the GP is not too slow.
      df = df.iloc[-1000:, :].reset_index(drop=True)
  
      # We need this to know the T and Reward for the weights.
      dfnewpoint = df[df["Trial"] == str(base)]
  
      if not dfnewpoint.empty:
          # N ow specify the dataset for the GP.
          y = np.array(df.y.values)
          # Meta data we keep -> episodes and reward.
          # (TODO: convert to curve)
          t_r = df[["Time", "R_before"]]
          hparams = df[bounds.keys()]
          X = pd.concat([t_r, hparams], axis=1).values
          newpoint = (
              df[df["Trial"] == str(base)]
              .iloc[-1, :][["Time", "R_before"]]
              .values
          )
          new = _select_config(
              X, y, current, newpoint, bounds, num_f=len(t_r.columns)
          )
  
          new_config = config.copy()
          values = []
          # Cast types for new hyperparameters.
          for i, col in enumerate(hparams.columns):
              # Use the type from the old config. Like this types
              # should be passed on from the first config downwards.
              type_ = type(config[col])
              new_config[col] = type_(new[i])
              values.append(type_(new[i]))
  
          new_T = df[df["Trial"] == str(base)].iloc[-1, :]["Time"]
          new_Reward = df[df["Trial"] == str(base)].iloc[-1, :].Reward
  
          lst = [[old] + [new_T] + values + [new_Reward]]
          cols = ["Trial", "Time"] + list(bounds) + ["Reward"]
          new_entry = pd.DataFrame(lst, columns=cols)
  
          # Create an entry for the new config, with the reward from the
          # copied agent.
          data = pd.concat([data, new_entry]).reset_index(drop=True)
  
      else:
          new_config = config.copy()
  
      return new_config, data
  
imports: from typing import Callable, Dict, Optional, Tuple, Union, TYPE_CHECKING
  from copy import deepcopy
  import logging
  import numpy
  import pandas
  from ray.tune import TuneError
  from ray.tune.experiment import Trial
  from ray.tune.schedulers import PopulationBasedTraining
  from ray.tune.schedulers.pbt import _PBTTrialState
  from ray.tune.utils.util import flatten_dict, unflatten_dict
  from ray.util.debug import log_once
label_desc: Use image node_id 4 for calling a global function with example usage: _explore(data, bounds, current, base, old, config) and returns: new_config,  data