def _build_transform_features(self, all_features, entity, max_depth=0, require_direct_input=False): """Creates trans_features for all the variables in an entity Args: all_features (dict[:class:`.Entity`.id:dict->[str->:class:`BaseFeature`]]): Dict containing a dict for each entity. Each nested dict has features as values with their ids as keys entity (Entity): Entity to calculate features for. """ new_max_depth = None if max_depth is not None: new_max_depth = max_depth - 1 for trans_prim in self.trans_primitives: current_options = self.primitive_options[trans_prim.name] if ignore_entity_for_primitive(current_options, entity): continue # if multiple input_types, only use first one for DFS input_types = trans_prim.input_types if type(input_types[0]) == list: input_types = input_types[0] matching_inputs = self._get_matching_inputs( all_features, entity, new_max_depth, input_types, trans_prim, current_options, require_direct_input=require_direct_input) for matching_input in matching_inputs: if all(bf.number_output_features == 1 for bf in matching_input): new_f = TransformFeature(matching_input, primitive=trans_prim) self._handle_new_feature(all_features=all_features, new_feature=new_f) for groupby_prim in self.groupby_trans_primitives: current_options = self.primitive_options[groupby_prim.name] if ignore_entity_for_primitive(current_options, entity, groupby=True): continue input_types = groupby_prim.input_types[:] # if multiple input_types, only use first one for DFS if type(input_types[0]) == list: input_types = input_types[0] matching_inputs = self._get_matching_inputs( all_features, entity, new_max_depth, input_types, groupby_prim, current_options) # get columns to use as groupbys, use IDs as default unless other groupbys specified if any([ 'include_groupby_variables' in option and entity.id in option['include_groupby_variables'] for option in current_options ]): default_type = variable_types.PandasTypes._all else: default_type = set([Id]) groupby_matches = self._features_by_type( all_features=all_features, entity=entity, max_depth=new_max_depth, variable_type=default_type) groupby_matches = filter_groupby_matches_by_options( groupby_matches, current_options) # If require_direct_input, require a DirectFeature in input or as a # groupby, and don't create features of inputs/groupbys which are # all direct features with the same relationship path for matching_input in matching_inputs: if all(bf.number_output_features == 1 for bf in matching_input): for groupby in groupby_matches: if require_direct_input and ( _all_direct_and_same_path(matching_input + (groupby, )) or not any([ isinstance(feature, DirectFeature) for feature in (matching_input + (groupby, )) ])): continue new_f = GroupByTransformFeature(list(matching_input), groupby=groupby[0], primitive=groupby_prim) self._handle_new_feature(all_features=all_features, new_feature=new_f)
def _build_transform_features(self, all_features, dataframe, max_depth=0, require_direct_input=False): """Creates trans_features for all the columns in a dataframe Args: all_features (dict[dataframe name: dict->[str->:class:`BaseFeature`]]): Dict containing a dict for each dataframe. Each nested dict has features as values with their ids as keys dataframe (DataFrame): DataFrame to calculate features for. """ new_max_depth = None if max_depth is not None: new_max_depth = max_depth - 1 # Keep track of features to add until the end to avoid applying # transform primitives to features that were also built by transform primitives features_to_add = [] for trans_prim in self.trans_primitives: current_options = self.primitive_options.get( trans_prim, self.primitive_options.get(trans_prim.name)) if ignore_dataframe_for_primitive(current_options, dataframe): continue input_types = trans_prim.input_types matching_inputs = self._get_matching_inputs( all_features, dataframe, new_max_depth, input_types, trans_prim, current_options, require_direct_input=require_direct_input, ) for matching_input in matching_inputs: if all(bf.number_output_features == 1 for bf in matching_input) and check_transform_stacking( matching_input): new_f = TransformFeature(matching_input, primitive=trans_prim) features_to_add.append(new_f) for groupby_prim in self.groupby_trans_primitives: current_options = self.primitive_options.get( groupby_prim, self.primitive_options.get(groupby_prim.name)) if ignore_dataframe_for_primitive(current_options, dataframe, groupby=True): continue input_types = groupby_prim.input_types[:] matching_inputs = self._get_matching_inputs( all_features, dataframe, new_max_depth, input_types, groupby_prim, current_options, ) # get columns to use as groupbys, use IDs as default unless other groupbys specified if any([ "include_groupby_columns" in option and dataframe.ww.name in option["include_groupby_columns"] for option in current_options ]): column_schemas = "all" else: column_schemas = [ColumnSchema(semantic_tags=["foreign_key"])] groupby_matches = self._features_by_type( all_features=all_features, dataframe=dataframe, max_depth=new_max_depth, column_schemas=column_schemas, ) groupby_matches = filter_groupby_matches_by_options( groupby_matches, current_options) # If require_direct_input, require a DirectFeature in input or as a # groupby, and don't create features of inputs/groupbys which are # all direct features with the same relationship path for matching_input in matching_inputs: if all(bf.number_output_features == 1 for bf in matching_input) and check_transform_stacking( matching_input): for groupby in groupby_matches: if require_direct_input and ( _all_direct_and_same_path(matching_input + (groupby, )) or not any([ isinstance(feature, DirectFeature) for feature in (matching_input + (groupby, )) ])): continue new_f = GroupByTransformFeature( list(matching_input), groupby=groupby[0], primitive=groupby_prim, ) features_to_add.append(new_f) for new_f in features_to_add: self._handle_new_feature(all_features=all_features, new_feature=new_f)