code: def strings_to_categoricals(self, df: Optional[pd.DataFrame] = None):
          
          dont_modify = False  # only necessary for backed views
          if df is None:
              dfs = [self.obs, self.var]
              if self.isview:
                  if not self.isbacked:
                      self._init_as_actual(self.copy())
                  else:
                      dont_modify = True
          else:
              dfs = [df]
          for df in dfs:
              string_cols = [
                  key for key in df.columns
                  if is_string_dtype(df[key])
                  and not is_categorical(df[key])
              ]
              for key in string_cols:
                  # make sure we only have strings (could be that there are
                  # np.nans (float), -666, '-666', for instance)
                  c = df[key].astype('U')
                  # make a categorical
                  c = pd.Categorical(c, categories=natsorted(np.unique(c)))
                  if len(c.categories) < len(c):
                      if dont_modify:
                          raise RuntimeError(
                              'Please call `.strings_to_categoricals()` on full AnnData, not on this view. '
                              'You might encounter this error message while copying or writing to disk.'
                          )
                      df[key] = c
                      logger.info('... storing {!r} as categorical'.format(key))
docs: Transform string annotations to categoricals.
  
          Only affects string annotations that lead to less categories than the
          total number of observations.
  
          Params
          ------
          df
              If ``df`` is ``None``, modifies both :attr:`obs` and :attr:`.var`,
              otherwise modifies ``df`` inplace.
  
          Notes
          -----
          Turns the view of an :class:`~anndata.AnnData` into an actual
          :class:`~anndata.AnnData`.