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,
                feature_filter=not_a_transform_input,
            )

            for matching_input in matching_inputs:
                if not can_stack_primitive_on_inputs(trans_prim, matching_input):
                    continue
                if not any(
                    True for bf in matching_input if bf.number_output_features != 1
                ):
                    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,
                feature_filter=not_a_transform_input,
            )

            # get columns to use as groupbys, use IDs as default unless other groupbys specified
            if any(
                True
                for option in current_options
                if dataframe.ww.name in option.get("include_groupby_columns", [])
            ):
                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,
            )

            for matching_input in matching_inputs:
                if not can_stack_primitive_on_inputs(groupby_prim, matching_input):
                    continue
                if any(True for bf in matching_input if bf.number_output_features != 1):
                    continue
                if require_direct_input:
                    if any_direct_in_matching_input := any(
                        isinstance(bf, DirectFeature) for bf in matching_input
                    ):
                        all_direct_and_same_path_in_matching_input = (
                            _all_direct_and_same_path(matching_input)
                        )
                for groupby in groupby_matches:
                    if require_direct_input:
                        # 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
                        #
                        # If we require_direct_input, we skip Feature generation
                        # in the following two cases:
                        # (1) --> There are no DirectFeatures in the matching input,
                        #         and groupby is not a DirectFeature
                        # (2) --> All of the matching input and groupby are DirectFeatures
                        #         with the same relationship path
                        groupby_is_direct = isinstance(groupby[0], DirectFeature)
                        # Checks case (1)
                        if not any_direct_in_matching_input:
                            if not groupby_is_direct:
                                continue
                        elif all_direct_and_same_path_in_matching_input:
                            # Checks case (2)
                            if (
                                groupby_is_direct
                                and groupby[0].relationship_path
                                == matching_input[0].relationship_path
                            ):
                                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)
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.
        
featuretools/synthesis/deep_feature_synthesis.py