def add_last_time_indexes(self, updated_dataframes=None):
        """
        Calculates the last time index values for each dataframe (the last time
        an instance or children of that instance were observed).  Used when
        calculating features using training windows. Adds the last time index as
        a series named _ft_last_time on the dataframe.

        Args:
            updated_dataframes (list[str]): List of dataframe names to update last_time_index for
                (will update all parents of those dataframes as well)
        """
        # Generate graph of dataframes to find leaf dataframes
        children = defaultdict(list)  # parent --> child mapping
        child_cols = defaultdict(dict)
        for r in self.relationships:
            children[r._parent_dataframe_name].append(r.child_dataframe)
            child_cols[r._parent_dataframe_name][r._child_dataframe_name] = (
                r.child_column
            )

        updated_dataframes = updated_dataframes or []
        if updated_dataframes:
            # find parents of updated_dataframes
            parent_queue = updated_dataframes[:]
            parents = set()
            while len(parent_queue):
                df_name = parent_queue.pop(0)
                if df_name in parents:
                    continue
                parents.add(df_name)

                for parent_name, _ in self.get_forward_dataframes(df_name):
                    parent_queue.append(parent_name)

            queue = [self[p] for p in parents]
            to_explore = parents
        else:
            to_explore = set(self.dataframe_dict.keys())
            queue = self.dataframes[:]

        explored = set()
        # Store the last time indexes for the entire entityset in a dictionary to update
        es_lti_dict = {}
        for df in self.dataframes:
            lti_col = df.ww.metadata.get("last_time_index")
            if lti_col is not None:
                lti_col = df[lti_col]
            es_lti_dict[df.ww.name] = lti_col

        for df in queue:
            es_lti_dict[df.ww.name] = None

        # We will explore children of dataframes on the queue,
        # which may not be in the to_explore set. Therefore,
        # we check whether all elements of to_explore are in
        # explored, rather than just comparing length
        while not to_explore.issubset(explored):
            dataframe = queue.pop(0)

            if es_lti_dict[dataframe.ww.name] is None:
                if dataframe.ww.time_index is not None:
                    lti = dataframe[dataframe.ww.time_index].copy()
                else:
                    lti = dataframe.ww[dataframe.ww.index].copy()
                    # Cannot have a category dtype with nans when calculating last time index
                    lti = lti.astype("object")
                    lti[:] = None

                es_lti_dict[dataframe.ww.name] = lti

            if dataframe.ww.name in children:
                child_dataframes = children[dataframe.ww.name]

                # if all children not explored, skip for now
                if not set([df.ww.name for df in child_dataframes]).issubset(explored):
                    # Now there is a possibility that a child dataframe
                    # was not explicitly provided in updated_dataframes,
                    # and never made it onto the queue. If updated_dataframes
                    # is None then we just load all dataframes onto the queue
                    # so we didn't need this logic
                    for df in child_dataframes:
                        if df.ww.name not in explored and df.ww.name not in [
                            q.ww.name for q in queue
                        ]:
                            # must also reset last time index here
                            es_lti_dict[df.ww.name] = None
                            queue.append(df)
                    queue.append(dataframe)
                    continue

                # updated last time from all children
                for child_df in child_dataframes:
                    if es_lti_dict[child_df.ww.name] is None:
                        continue
                    link_col = child_cols[dataframe.ww.name][child_df.ww.name].name

                    lti_df = pd.DataFrame(
                        {
                            "last_time": es_lti_dict[child_df.ww.name],
                            dataframe.ww.index: child_df[link_col],
                        },
                    )

                    # sort by time and keep only the most recent
                    lti_df.sort_values(
                        ["last_time", dataframe.ww.index],
                        kind="mergesort",
                        inplace=True,
                    )

                    lti_df.drop_duplicates(
                        dataframe.ww.index,
                        keep="last",
                        inplace=True,
                    )

                    lti_df.set_index(dataframe.ww.index, inplace=True)
                    lti_df = lti_df.reindex(es_lti_dict[dataframe.ww.name].index)
                    lti_df["last_time_old"] = es_lti_dict[dataframe.ww.name]
                    if lti_df.empty:
                        # Pandas errors out if it tries to do fillna and then max on an empty dataframe
                        lti_df = pd.Series([], dtype="object")
                    else:
                        lti_df["last_time"] = lti_df["last_time"].astype(
                            "datetime64[ns]",
                        )
                        lti_df["last_time_old"] = lti_df["last_time_old"].astype(
                            "datetime64[ns]",
                        )
                        lti_df = lti_df.fillna(
                            pd.to_datetime("1800-01-01 00:00"),
                        ).max(axis=1)
                        lti_df = lti_df.replace(
                            pd.to_datetime("1800-01-01 00:00"),
                            pd.NaT,
                        )

                    es_lti_dict[dataframe.ww.name] = lti_df
                    es_lti_dict[dataframe.ww.name].name = "last_time"

            explored.add(dataframe.ww.name)

        # Store the last time index on the DataFrames
        dfs_to_update = {}
        for df in self.dataframes:
            lti = es_lti_dict[df.ww.name]
            if lti is not None:
                if self.time_type == "numeric":
                    if lti.dtype == "datetime64[ns]":
                        # Woodwork cannot convert from datetime to numeric
                        lti = lti.apply(lambda x: x.value)
                    lti = init_series(lti, logical_type="Double")
                else:
                    lti = init_series(lti, logical_type="Datetime")

                lti.name = LTI_COLUMN_NAME

                if LTI_COLUMN_NAME in df.columns:
                    if "last_time_index" in df.ww.semantic_tags[LTI_COLUMN_NAME]:
                        # Remove any previous last time index placed by featuretools
                        df.ww.pop(LTI_COLUMN_NAME)
                    else:
                        raise ValueError(
                            "Cannot add a last time index on DataFrame with an existing "
                            f"'{LTI_COLUMN_NAME}' column. Please rename '{LTI_COLUMN_NAME}'.",
                        )

                # Add the new column to the DataFrame
                df.ww[LTI_COLUMN_NAME] = lti
                if "last_time_index" not in df.ww.semantic_tags[LTI_COLUMN_NAME]:
                    df.ww.add_semantic_tags({LTI_COLUMN_NAME: "last_time_index"})
                df.ww.metadata["last_time_index"] = LTI_COLUMN_NAME

        for df in dfs_to_update.values():
            df.ww.add_semantic_tags({LTI_COLUMN_NAME: "last_time_index"})
            df.ww.metadata["last_time_index"] = LTI_COLUMN_NAME
            self.dataframe_dict[df.ww.name] = df

        self.reset_data_description()
        for df in self.dataframes:
            self._add_references_to_metadata(df)

        Calculates the last time index values for each dataframe (the last time
        an instance or children of that instance were observed).  Used when
        calculating features using training windows. Adds the last time index as
        a series named _ft_last_time on the dataframe.

        Args:
            updated_dataframes (list[str]): List of dataframe names to update last_time_index for
                (will update all parents of those dataframes as well)
        
featuretools/entityset/entityset.py