code: def as_index(keys, axis=semantics.axis_default, base=False, stable=True, lex_as_struct=False):
      
      if isinstance(keys, Index):
          if type(keys) is BaseIndex and base==False:
              keys = keys.keys    #need to upcast to an indirectly sorted index type
          else:
              return keys         #already done here
      if isinstance(keys, tuple):
          if lex_as_struct:
              keys = as_struct_array(*keys)
          else:
              return LexIndex(keys, stable)
  
      try:
          keys = np.asarray(keys)
      except:
          raise TypeError('Given object does not form a valid set of keys')
      if axis is None:
          keys = keys.flatten()
      if keys.ndim==1:
          if base:
              return BaseIndex(keys)
          else:
              return Index(keys, stable=stable)
      else:
          return ObjectIndex(keys, axis, stable=stable)
docs: casting rules for a keys object to an index object
  
      the preferred semantics is that keys is a sequence of key objects,
      except when keys is an instance of tuple,
      in which case the zipped elements of the tuple are the key objects
  
      the axis keyword specifies the axis which enumerates the keys
      if axis is None, the keys array is flattened
      if axis is 0, the first axis enumerates the keys
      which of these two is the default depends on whether backwards_compatible == True
  
      if base==True, the most basic index possible is constructed.
      this avoids an indirect sort; if it isnt required, this has better performance