docs: def propagate_name_down(self, col_name, df_name, verbose=False):
          """
          Put the data for "col_name" into dataframe with df_name
          Used to add 'site_name' to specimen table, for example.
          """
          if df_name not in self.tables:
              table = self.add_magic_table(df_name)[1]
              if is_null(table):
                  return
          df = self.tables[df_name].df
          if col_name in df.columns:
              if all(df[col_name].apply(not_null)):
                  #print('{} already in {}'.format(col_name, df_name))
                  return df
  
          # otherwise, do necessary merges to get col_name into df
          # get names for each level
          grandparent_table_name = col_name.split('_')[0] + "s"
          grandparent_name = grandparent_table_name[:-1]
          ind = self.ancestry.index(grandparent_table_name) - 1
          #
          parent_table_name, parent_name = self.get_table_name(ind)
          child_table_name, child_name = self.get_table_name(ind - 1)
          bottom_table_name, bottom_name = self.get_table_name(ind - 2)
  
          # merge in bottom level
          if child_name not in df.columns:
              # add child table if missing
              if bottom_table_name not in self.tables:
                  result = self.add_magic_table(bottom_table_name)[1]
                  if not isinstance(result, MagicDataFrame):
                      if verbose:
                          print("-W- Couldn't read in {} data for data propagation".format(bottom_table_name))
                      return df
              # add child_name to df
              add_df = self.tables[bottom_table_name].df
              # drop duplicate names
              add_df = add_df.drop_duplicates(subset=bottom_name)
              if child_name not in df.columns:
                  if verbose:
                      print("-W- Cannot complete propagation, {} table is missing {} column".format(df_name, child_name))
              else:
                  add_df = stringify_col(add_df, child_name)
                  df = stringify_col(df, bottom_name)
                  df = df.merge(add_df[[child_name]],
                                left_on=[bottom_name],
                                right_index=True, how="left")
                  self.tables[df_name].df = df
  
          # merge in one level above
          if parent_name not in df.columns:
              # add parent_table if missing
              if child_table_name not in self.tables:
                  result = self.add_magic_table(child_table_name)[1]
                  if not isinstance(result, MagicDataFrame):
                      if verbose:
                          print("-W- Couldn't read in {} data".format(child_table_name))
                          print("-I- Make sure you've provided the correct file name")
                      return df
              # add parent_name to df
              add_df = self.tables[child_table_name].df
              # drop duplicate names
              add_df = add_df.drop_duplicates(subset=child_name)
              if parent_name not in add_df:
                  if verbose:
                      print('-W- could not finish propagating names: {} table is missing {} column'.format(child_table_name, parent_name))
              elif parent_name not in df:
                  if verbose:
                      print('-W- could not finish propagating names: {} table is missing {} column'.format(df_name, parent_name))
              else:
                  add_df = stringify_col(add_df, parent_name)
                  df = stringify_col(df, child_name)
                  df = df.merge(add_df[[parent_name]],
                                left_on=[child_name],
                                right_index=True, how="left")
                  self.tables[df_name].df = df
  
          # merge in two levels above
          if grandparent_name not in df.columns:
              # add grandparent table if it is missing
              if parent_table_name not in self.tables:
                  result = self.add_magic_table(parent_table_name)[1]
                  if not isinstance(result, MagicDataFrame):
                      if verbose:
                          print("-W- Couldn't read in {} data".format(parent_table_name))
                          print("-I- Make sure you've provided the correct file name")
                      return df
              # add grandparent name to df
              add_df = self.tables[parent_table_name].df
              # drop duplicate names
              add_df = add_df.drop_duplicates(subset=parent_name)
              if grandparent_name not in add_df.columns:
                  if verbose:
                      print('-W- could not finish propagating names: {} table is missing {} column'.format(parent_table_name, grandparent_name))
              elif parent_name not in df.columns:
                  if verbose:
                      print('-W- could not finish propagating names: {} table is missing {} column'.format(df_name, parent_name))
              else:
                  add_df = stringify_col(add_df, grandparent_name)
                  df = stringify_col(df, parent_name)
                  df = df.merge(add_df[[grandparent_name]],
                                left_on=[parent_name],
                                right_index=True, how="left")
                  df = stringify_col(df, grandparent_name)
          # update the Contribution
          self.tables[df_name].df = df
          return df