class_docstr: Stores all actual data and typing information for an entityset
  
  Attributes:
      id
      dataframe_dict
      relationships
      time_type
  
  Properties:
      metadata
class_docstr_tok: ["Stores","all","actual","data","and","typing","information","for","an","entityset","Attributes",":","id","dataframe_dict","relationships","time_type","Properties",":","metadata"]
docstr: 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)
docstr_tok: ["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",")"]
code: 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()
                  if is_instance(dataframe, dd, "DataFrame"):
                      # The current Dask implementation doesn't set the index of the dataframe
                      # to the dataframe's index, so we have to do it manually here
                      lti.index = dataframe[dataframe.ww.index].copy()
              else:
                  lti = dataframe.ww[dataframe.ww.index].copy()
                  if is_instance(dataframe, dd, "DataFrame"):
                      lti.index = dataframe[dataframe.ww.index].copy()
                      lti = lti.apply(lambda x: None)
                  elif is_instance(dataframe, ps, "DataFrame"):
                      lti = ps.Series(
                          pd.Series(index=lti.to_list(), name=lti.name)
                      )
                  else:
                      # 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:
                  # TODO: Figure out if Dask code related to indexes is important for Spark
                  if es_lti_dict[child_df.ww.name] is None:
                      continue
                  link_col = child_cols[dataframe.ww.name][
                      child_df.ww.name
                  ].name
  
                  lti_is_dask = is_instance(
                      es_lti_dict[child_df.ww.name],
                      dd,
                      "Series",
                  )
                  lti_is_spark = is_instance(
                      es_lti_dict[child_df.ww.name],
                      ps,
                      "Series",
                  )
  
                  if lti_is_dask or lti_is_spark:
                      to_join = child_df[link_col]
                      if lti_is_dask:
                          to_join.index = child_df[child_df.ww.index]
  
                      lti_df = (
                          es_lti_dict[child_df.ww.name]
                          .to_frame(name="last_time")
                          .join(
                              to_join.to_frame(name=dataframe.ww.index)
                          )
                      )
  
                      if lti_is_dask:
                          new_index = lti_df.index.copy()
                          new_index.name = None
                          lti_df.index = new_index
                      lti_df = lti_df.groupby(
                          lti_df[dataframe.ww.index]
                      ).agg("max")
  
                      lti_df = (
                          es_lti_dict[dataframe.ww.name]
                          .to_frame(name="last_time_old")
                          .join(lti_df)
                      )
  
                  else:
                      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 not (lti_is_dask or lti_is_spark) and 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:
                      if lti_is_spark:
                          lti_df["last_time"] = ps.to_datetime(
                              lti_df["last_time"]
                          )
                          lti_df["last_time_old"] = ps.to_datetime(
                              lti_df["last_time_old"],
                          )
                          # TODO: Figure out a workaround for fillna and replace
                          lti_df = lti_df.max(axis=1)
                      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:
              lti_ltype = 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")
                  lti_ltype = "Double"
              else:
                  lti = init_series(lti, logical_type="Datetime")
                  lti_ltype = "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
              if is_instance(df, dd, "DataFrame"):
                  new_df = df.merge(lti.reset_index(), on=df.ww.index)
                  new_df.ww.init_with_partial_schema(
                      schema=df.ww.schema,
                      logical_types={LTI_COLUMN_NAME: lti_ltype},
                  )
  
                  new_idx = new_df[new_df.ww.index]
                  new_idx.name = None
                  new_df.index = new_idx
                  dfs_to_update[df.ww.name] = new_df
              elif is_instance(df, ps, "DataFrame"):
                  new_df = df.merge(
                      lti, left_on=df.ww.index, right_index=True
                  )
                  new_df.ww.init_with_partial_schema(
                      schema=df.ww.schema,
                      logical_types={LTI_COLUMN_NAME: lti_ltype},
                  )
  
                  dfs_to_update[df.ww.name] = new_df
              else:
                  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)
  
imports: import copy
  import logging
  import warnings
  from collections import defaultdict
  import numpy
  import pandas
  from woodwork import init_series
  from woodwork.logical_types import Datetime, LatLong
  from featuretools.entityset import deserialize, serialize
  from featuretools.entityset.relationship import Relationship, RelationshipPath
  from featuretools.feature_base.feature_base import _ES_REF
  from featuretools.utils.gen_utils import Library, import_or_none, is_instance
  from featuretools.utils.plot_utils import check_graphviz, get_graphviz_format, save_graph
  from featuretools.utils.wrangle import _check_timedelta
label_desc: Use image node_id 33 for calling the EntitySet obj's underlying member method code with example usage: obj.add_last_time_indexes(updated_dataframes) without return types