'Map a function from str -> str element-wise over ``self``.
``f`` will be applied exactly once to each non-missing unique value in
``self``. Missing values will always map to ``self.missing_value``.'

def map(self, f):

    if (self.missing_value is None):
        allowed_outtypes = self.SUPPORTED_SCALAR_TYPES
    else:
        allowed_outtypes = self.SUPPORTED_NON_NONE_SCALAR_TYPES
    def f_to_use(x, missing_value=self.missing_value, otypes=allowed_outtypes):
        if (x == missing_value):
            return _sortable_sentinel
        ret = f(x)
        if (not isinstance(ret, otypes)):
            raise TypeError('LabelArray.map    expected    function    {f}    to    return    a    string    or    None,    but    got    {type}    instead.\nValue    was    {value}.'.format(f=f.__name__, type=type(ret).__name__, value=ret))
        if (ret == missing_value):
            return _sortable_sentinel
        return ret
    new_categories_with_duplicates = np.vectorize(f_to_use, otypes=[object])(self.categories)
    (new_categories, bloated_inverse_index) = np.unique(new_categories_with_duplicates, return_inverse=True)
    if (new_categories[0] is _sortable_sentinel):
        new_categories[0] = self.missing_value
    reverse_index = bloated_inverse_index.astype(smallest_uint_that_can_hold(len(new_categories)))
    new_codes = np.take(reverse_index, self.as_int_array())
    return self.from_codes_and_metadata(new_codes, new_categories, dict(zip(new_categories, range(len(new_categories)))), missing_value=self.missing_value)
