class_docstr: Calculates the values of a set of features for given instance ids.
code: def _calculate_agg_features(
      self, features, frame, df_trie, progress_callback
  ):
      test_feature = features[0]
      child_dataframe = test_feature.base_features[0].dataframe
      base_frame = df_trie.get_node(
          test_feature.relationship_path
      ).value
      parent_merge_col = test_feature.relationship_path[0][
          1
      ]._parent_column_name
      # Sometimes approximate features get computed in a previous filter frame
      # and put in the current one dynamically,
      # so there may be existing features here
      fl = []
      for f in features:
          for ind in f.get_feature_names():
              if ind not in frame.columns:
                  fl.append(f)
                  break
      features = fl
      if not len(features):
          progress_callback(len(features) / float(self.num_features))
          return frame
  
      # handle where
      base_frame_empty = (
          base_frame.empty
          if isinstance(base_frame, pd.DataFrame)
          else False
      )
      where = test_feature.where
      if where is not None and not base_frame_empty:
          base_frame = base_frame.loc[base_frame[where.get_name()]]
  
      # when no child data, just add all the features to frame with nan
      base_frame_empty = (
          base_frame.empty
          if isinstance(base_frame, pd.DataFrame)
          else False
      )
      if base_frame_empty:
          feature_values = []
          for f in features:
              feature_values.append(
                  (f, np.full(f.number_output_features, np.nan))
              )
              progress_callback(1 / float(self.num_features))
          frame = update_feature_columns(feature_values, frame)
      else:
          relationship_path = test_feature.relationship_path
  
          groupby_col = get_relationship_column_id(relationship_path)
  
          # if the use_previous property exists on this feature, include only the
          # instances from the child dataframe included in that Timedelta
          use_previous = test_feature.use_previous
          if use_previous:
              # Filter by use_previous values
              time_last = self.time_last
              if use_previous.has_no_observations():
                  time_first = time_last - use_previous
                  ti = child_dataframe.ww.time_index
                  if ti is not None:
                      base_frame = base_frame[
                          base_frame[ti] >= time_first
                      ]
              else:
                  n = use_previous.get_value("o")
  
                  def last_n(df):
                      return df.iloc[-n:]
  
                  base_frame = base_frame.groupby(
                      groupby_col,
                      observed=True,
                      sort=False,
                      group_keys=False,
                  ).apply(last_n)
  
          to_agg = {}
          agg_rename = {}
          to_apply = set()
          # apply multi-column and time-dependent features as we find them, and
          # save aggregable features for later
          for f in features:
              if _can_agg(f):
                  column_id = f.base_features[0].get_name()
                  if column_id not in to_agg:
                      to_agg[column_id] = []
                  if is_instance(base_frame, dd, "DataFrame"):
                      func = f.get_function(agg_type=Library.DASK)
                  elif is_instance(base_frame, ps, "DataFrame"):
                      func = f.get_function(agg_type=Library.SPARK)
                  else:
                      func = f.get_function()
  
                  # for some reason, using the string count is significantly
                  # faster than any method a primitive can return
                  # https://stackoverflow.com/questions/55731149/use-a-function-instead-of-string-in-pandas-groupby-agg
                  if func == pd.Series.count:
                      func = "count"
  
                  funcname = func
                  if callable(func):
                      # if the same function is being applied to the same
                      # column twice, wrap it in a partial to avoid
                      # duplicate functions
                      funcname = str(id(func))
                      if (
                          "{}-{}".format(column_id, funcname)
                          in agg_rename
                      ):
                          func = partial(func)
                          funcname = str(id(func))
  
                      func.__name__ = funcname
  
                  if dd and isinstance(func, dd.Aggregation):
                      # TODO: handle aggregation being applied to same column twice
                      # (see above partial wrapping of functions)
                      funcname = func.__name__
  
                  to_agg[column_id].append(func)
                  # this is used below to rename columns that pandas names for us
                  agg_rename[
                      "{}-{}".format(column_id, funcname)
                  ] = f.get_name()
                  continue
  
              to_apply.add(f)
  
          # Apply the non-aggregable functions generate a new dataframe, and merge
          # it with the existing one
          if len(to_apply):
              wrap = agg_wrapper(to_apply, self.time_last)
              # groupby_col can be both the name of the index and a column,
              # to silence pandas warning about ambiguity we explicitly pass
              # the column (in actuality grouping by both index and group would
              # work)
              to_merge = base_frame.groupby(
                  base_frame[groupby_col],
                  observed=True,
                  sort=False,
                  group_keys=False,
              ).apply(wrap)
              frame = pd.merge(
                  left=frame,
                  right=to_merge,
                  left_index=True,
                  right_index=True,
                  how="left",
              )
  
              progress_callback(
                  len(to_apply) / float(self.num_features)
              )
  
          # Apply the aggregate functions to generate a new dataframe, and merge
          # it with the existing one
          if len(to_agg):
              # groupby_col can be both the name of the index and a column,
              # to silence pandas warning about ambiguity we explicitly pass
              # the column (in actuality grouping by both index and group would
              # work)
              if is_instance(base_frame, (dd, ps), "DataFrame"):
                  to_merge = base_frame.groupby(groupby_col).agg(to_agg)
  
              else:
                  to_merge = base_frame.groupby(
                      base_frame[groupby_col],
                      observed=True,
                      sort=False,
                  ).agg(to_agg)
              # rename columns to the correct feature names
              to_merge.columns = [
                  agg_rename["-".join(x)] for x in to_merge.columns
              ]
              to_merge = to_merge[list(agg_rename.values())]
  
              # Workaround for pandas bug where categories are in the wrong order
              # see: https://github.com/pandas-dev/pandas/issues/22501
              #
              # Pandas claims that bug is fixed but it still shows up in some
              # cases.  More investigation needed.
              if pdtypes.is_categorical_dtype(frame.index):
                  categories = pdtypes.CategoricalDtype(
                      categories=frame.index.categories,
                  )
                  to_merge.index = to_merge.index.astype(object).astype(
                      categories
                  )
  
              if is_instance(frame, (dd, ps), "DataFrame"):
                  frame = frame.merge(
                      to_merge,
                      left_on=parent_merge_col,
                      right_index=True,
                      how="left",
                  )
              else:
                  frame = pd.merge(
                      left=frame,
                      right=to_merge,
                      left_index=True,
                      right_index=True,
                      how="left",
                  )
  
              # determine number of features that were just merged
              progress_callback(
                  len(to_merge.columns) / float(self.num_features)
              )
  
      # Handle default values
      fillna_dict = {}
      for f in features:
          feature_defaults = {
              name: f.default_value for name in f.get_feature_names()
          }
          fillna_dict.update(feature_defaults)
  
      frame = frame.fillna(fillna_dict)
  
      return frame
  
imports: from datetime import datetime
  from functools import partial
  import numpy
  import pandas
  import pandas.api.types
  from featuretools.entityset.relationship import RelationshipPath
  from featuretools.exceptions import UnknownFeature
  from featuretools.feature_base import AggregationFeature, DirectFeature, GroupByTransformFeature, IdentityFeature, TransformFeature
  from featuretools.utils import Trie
  from featuretools.utils.gen_utils import Library, get_relationship_column_id, import_or_none, is_instance
label_desc: Use image node_id 12 for calling the FeatureSetCalculator obj's underlying member method code with example usage: obj._calculate_agg_features(features, frame, df_trie, progress_callback) and returns: frame, frame, df