def normalize_dataframe(
        self,
        base_dataframe_name,
        new_dataframe_name,
        index,
        additional_columns=None,
        copy_columns=None,
        make_time_index=None,
        make_secondary_time_index=None,
        new_dataframe_time_index=None,
        new_dataframe_secondary_time_index=None,
    ):
        """Create a new dataframe and relationship from unique values of an existing column.

        Args:
            base_dataframe_name (str) : Dataframe name from which to split.

            new_dataframe_name (str): Name of the new dataframe.

            index (str): Column in old dataframe
                that will become index of new dataframe. Relationship
                will be created across this column.

            additional_columns (list[str]):
                List of column names to remove from
                base_dataframe and move to new dataframe.

            copy_columns (list[str]): List of
                column names to copy from old dataframe
                and move to new dataframe.

            make_time_index (bool or str, optional): Create time index for new dataframe based
                on time index in base_dataframe, optionally specifying which column in base_dataframe
                to use for time_index. If specified as True without a specific column name,
                uses the primary time index. Defaults to True if base dataframe has a time index.

            make_secondary_time_index (dict[str -> list[str]], optional): Create a secondary time index
                from key. Values of dictionary are the columns to associate with a secondary time index.
                Only one secondary time index is allowed. If None, only associate the time index.

            new_dataframe_time_index (str, optional): Rename new dataframe time index.

            new_dataframe_secondary_time_index (str, optional): Rename new dataframe secondary time index.

        """
        base_dataframe = self.dataframe_dict[base_dataframe_name]
        additional_columns = additional_columns or []
        copy_columns = copy_columns or []

        for list_name, col_list in {
            "copy_columns": copy_columns,
            "additional_columns": additional_columns,
        }.items():
            if not isinstance(col_list, list):
                raise TypeError(
                    "'{}' must be a list, but received type {}".format(
                        list_name,
                        type(col_list),
                    ),
                )
            if len(col_list) != len(set(col_list)):
                raise ValueError(
                    f"'{list_name}' contains duplicate columns. All columns must be unique.",
                )
            for col_name in col_list:
                if col_name == index:
                    raise ValueError(
                        "Not adding {} as both index and column in {}".format(
                            col_name,
                            list_name,
                        ),
                    )

        for col in additional_columns:
            if col == base_dataframe.ww.time_index:
                raise ValueError(
                    "Not moving {} as it is the base time index column. Perhaps, move the column to the copy_columns.".format(
                        col,
                    ),
                )

        if isinstance(make_time_index, str):
            if make_time_index not in base_dataframe.columns:
                raise ValueError(
                    "'make_time_index' must be a column in the base dataframe",
                )
            elif make_time_index not in additional_columns + copy_columns:
                raise ValueError(
                    "'make_time_index' must be specified in 'additional_columns' or 'copy_columns'",
                )
        if index == base_dataframe.ww.index:
            raise ValueError(
                "'index' must be different from the index column of the base dataframe",
            )

        transfer_types = {}
        # Types will be a tuple of (logical_type, semantic_tags, column_metadata, column_description)
        transfer_types[index] = (
            base_dataframe.ww.logical_types[index],
            base_dataframe.ww.semantic_tags[index],
            base_dataframe.ww.columns[index].metadata,
            base_dataframe.ww.columns[index].description,
        )
        for col_name in additional_columns + copy_columns:
            # Remove any existing time index tags
            transfer_types[col_name] = (
                base_dataframe.ww.logical_types[col_name],
                (base_dataframe.ww.semantic_tags[col_name] - {"time_index"}),
                base_dataframe.ww.columns[col_name].metadata,
                base_dataframe.ww.columns[col_name].description,
            )

        # create and add new dataframe
        new_dataframe = self[base_dataframe_name].copy()

        if make_time_index is None and base_dataframe.ww.time_index is not None:
            make_time_index = True

        if isinstance(make_time_index, str):
            # Set the new time index to make_time_index.
            base_time_index = make_time_index
            new_dataframe_time_index = make_time_index
            already_sorted = new_dataframe_time_index == base_dataframe.ww.time_index
        elif make_time_index:
            # Create a new time index based on the base dataframe time index.
            base_time_index = base_dataframe.ww.time_index
            if new_dataframe_time_index is None:
                new_dataframe_time_index = "first_%s_time" % (base_dataframe.ww.name)

            already_sorted = True

            assert (
                base_dataframe.ww.time_index is not None
            ), "Base dataframe doesn't have time_index defined"

            if base_time_index not in [col for col in copy_columns]:
                copy_columns.append(base_time_index)

                time_index_types = (
                    base_dataframe.ww.logical_types[base_dataframe.ww.time_index],
                    base_dataframe.ww.semantic_tags[base_dataframe.ww.time_index],
                    base_dataframe.ww.columns[base_dataframe.ww.time_index].metadata,
                    base_dataframe.ww.columns[base_dataframe.ww.time_index].description,
                )
            else:
                # If base_time_index is in copy_columns then we've already added the transfer types
                # but since we're changing the name, we have to remove it
                time_index_types = transfer_types[base_dataframe.ww.time_index]
                del transfer_types[base_dataframe.ww.time_index]

            transfer_types[new_dataframe_time_index] = time_index_types

        else:
            new_dataframe_time_index = None
            already_sorted = False

        if new_dataframe_time_index is not None and new_dataframe_time_index == index:
            raise ValueError(
                "time_index and index cannot be the same value, %s"
                % (new_dataframe_time_index),
            )

        selected_columns = (
            [index]
            + [col for col in additional_columns]
            + [col for col in copy_columns]
        )

        new_dataframe = new_dataframe.dropna(subset=[index])
        new_dataframe2 = new_dataframe.drop_duplicates(index, keep="first")[
            selected_columns
        ]

        if make_time_index:
            new_dataframe2 = new_dataframe2.rename(
                columns={base_time_index: new_dataframe_time_index},
            )
        if make_secondary_time_index:
            assert (
                len(make_secondary_time_index) == 1
            ), "Can only provide 1 secondary time index"
            secondary_time_index = list(make_secondary_time_index.keys())[0]

            secondary_columns = [index, secondary_time_index] + list(
                make_secondary_time_index.values(),
            )[0]
            secondary_df = new_dataframe.drop_duplicates(index, keep="last")[
                secondary_columns
            ]
            if new_dataframe_secondary_time_index:
                secondary_df = secondary_df.rename(
                    columns={secondary_time_index: new_dataframe_secondary_time_index},
                )
                secondary_time_index = new_dataframe_secondary_time_index
            else:
                new_dataframe_secondary_time_index = secondary_time_index
            secondary_df = secondary_df.set_index(index)
            new_dataframe = new_dataframe2.join(secondary_df, on=index)
        else:
            new_dataframe = new_dataframe2

        base_dataframe_index = index

        if make_secondary_time_index:
            old_ti_name = list(make_secondary_time_index.keys())[0]
            ti_cols = list(make_secondary_time_index.values())[0]
            ti_cols = [c if c != old_ti_name else secondary_time_index for c in ti_cols]
            make_secondary_time_index = {secondary_time_index: ti_cols}

        # will initialize Woodwork on this DataFrame
        logical_types = {}
        semantic_tags = {}
        column_metadata = {}
        column_descriptions = {}
        for col_name, (ltype, tags, metadata, description) in transfer_types.items():
            logical_types[col_name] = ltype
            semantic_tags[col_name] = tags - {"time_index"}
            column_metadata[col_name] = copy.deepcopy(metadata)
            column_descriptions[col_name] = description

        new_dataframe.ww.init(
            name=new_dataframe_name,
            index=index,
            already_sorted=already_sorted,
            time_index=new_dataframe_time_index,
            logical_types=logical_types,
            semantic_tags=semantic_tags,
            column_metadata=column_metadata,
            column_descriptions=column_descriptions,
        )

        self.add_dataframe(
            new_dataframe,
            secondary_time_index=make_secondary_time_index,
        )

        self.dataframe_dict[base_dataframe_name] = self.dataframe_dict[
            base_dataframe_name
        ].ww.drop(additional_columns)

        self.dataframe_dict[base_dataframe_name].ww.add_semantic_tags(
            {base_dataframe_index: "foreign_key"},
        )

        self.add_relationship(
            new_dataframe_name,
            index,
            base_dataframe_name,
            base_dataframe_index,
        )
        self.reset_data_description()
        return self
Create a new dataframe and relationship from unique values of an existing column.

        Args:
            base_dataframe_name (str) : Dataframe name from which to split.

            new_dataframe_name (str): Name of the new dataframe.

            index (str): Column in old dataframe
                that will become index of new dataframe. Relationship
                will be created across this column.

            additional_columns (list[str]):
                List of column names to remove from
                base_dataframe and move to new dataframe.

            copy_columns (list[str]): List of
                column names to copy from old dataframe
                and move to new dataframe.

            make_time_index (bool or str, optional): Create time index for new dataframe based
                on time index in base_dataframe, optionally specifying which column in base_dataframe
                to use for time_index. If specified as True without a specific column name,
                uses the primary time index. Defaults to True if base dataframe has a time index.

            make_secondary_time_index (dict[str -> list[str]], optional): Create a secondary time index
                from key. Values of dictionary are the columns to associate with a secondary time index.
                Only one secondary time index is allowed. If None, only associate the time index.

            new_dataframe_time_index (str, optional): Rename new dataframe time index.

            new_dataframe_secondary_time_index (str, optional): Rename new dataframe secondary time index.

        
featuretools/entityset/entityset.py