code: def _stack_multi_columns(frame, level_num=-1, dropna=True):
      def _convert_level_number(level_num, columns):
          """
          Logic for converting the level number to something we can safely pass
          to swaplevel:
  
          We generally want to convert the level number into a level name, except
          when columns do not have names, in which case we must leave as a level
          number
          """
          if level_num in columns.names:
              return columns.names[level_num]
          else:
              if columns.names[level_num] is None:
                  return level_num
              else:
                  return columns.names[level_num]
  
      this = frame.copy()
  
      # this makes life much simpler
      if level_num != frame.columns.nlevels - 1:
          # roll levels to put selected level at end
          roll_columns = this.columns
          for i in range(level_num, frame.columns.nlevels - 1):
              # Need to check if the ints conflict with level names
              lev1 = _convert_level_number(i, roll_columns)
              lev2 = _convert_level_number(i + 1, roll_columns)
              roll_columns = roll_columns.swaplevel(lev1, lev2)
          this.columns = roll_columns
  
      if not this.columns.is_lexsorted():
          # Workaround the edge case where 0 is one of the column names,
          # which interferes with trying to sort based on the first
          # level
          level_to_sort = _convert_level_number(0, this.columns)
          this = this.sort_index(level=level_to_sort, axis=1)
  
      # tuple list excluding level for grouping columns
      if len(frame.columns.levels) > 2:
          tuples = list(
              zip(
                  *[
                      lev.take(level_codes)
                      for lev, level_codes in zip(
                          this.columns.levels[:-1],
                          this.columns.codes[:-1],
                      )
                  ]
              )
          )
          unique_groups = [key for key, _ in itertools.groupby(tuples)]
          new_names = this.columns.names[:-1]
          new_columns = MultiIndex.from_tuples(
              unique_groups, names=new_names
          )
      else:
          new_columns = unique_groups = this.columns.levels[0]
  
      # time to ravel the values
      new_data = {}
      level_vals = this.columns.levels[-1]
      level_codes = sorted(set(this.columns.codes[-1]))
      level_vals_used = level_vals[level_codes]
      levsize = len(level_codes)
      drop_cols = []
      for key in unique_groups:
          try:
              loc = this.columns.get_loc(key)
          except KeyError:
              drop_cols.append(key)
              continue
  
          # can make more efficient?
          # we almost always return a slice
          # but if unsorted can get a boolean
          # indexer
          if not isinstance(loc, slice):
              slice_len = len(loc)
          else:
              slice_len = loc.stop - loc.start
  
          if slice_len != levsize:
              chunk = this.loc[:, this.columns[loc]]
              chunk.columns = level_vals.take(chunk.columns.codes[-1])
              value_slice = chunk.reindex(
                  columns=level_vals_used
              ).values
          else:
              if (
                  frame._is_homogeneous_type
                  and is_extension_array_dtype(frame.dtypes.iloc[0])
              ):
                  dtype = this[this.columns[loc]].dtypes.iloc[0]
                  subset = this[this.columns[loc]]
  
                  value_slice = (
                      dtype.construct_array_type()._concat_same_type(
                          [x._values for _, x in subset.iteritems()]
                      )
                  )
                  N, K = this.shape
                  idx = np.arange(N * K).reshape(K, N).T.ravel()
                  value_slice = value_slice.take(idx)
  
              elif frame._is_mixed_type:
                  value_slice = this[this.columns[loc]].values
              else:
                  value_slice = this.values[:, loc]
  
          if value_slice.ndim > 1:
              # i.e. not extension
              value_slice = value_slice.ravel()
  
          new_data[key] = value_slice
  
      if len(drop_cols) > 0:
          new_columns = new_columns.difference(drop_cols)
  
      N = len(this)
  
      if isinstance(this.index, MultiIndex):
          new_levels = list(this.index.levels)
          new_names = list(this.index.names)
          new_codes = [lab.repeat(levsize) for lab in this.index.codes]
      else:
          new_levels = [this.index]
          new_codes = [np.arange(N).repeat(levsize)]
          new_names = [this.index.name]  # something better?
  
      new_levels.append(level_vals)
      new_codes.append(np.tile(level_codes, N))
      new_names.append(frame.columns.names[level_num])
  
      new_index = MultiIndex(
          levels=new_levels,
          codes=new_codes,
          names=new_names,
          verify_integrity=False,
      )
  
      result = frame._constructor(
          new_data, index=new_index, columns=new_columns
      )
  
      # more efficient way to go about this? can do the whole masking biz but
      # will only save a small amount of time...
      if dropna:
          result = result.dropna(axis=0, how="all")
  
      return result
  
imports: from functools import partial
  import itertools
  import numpy
  from pandas._libs import algos, reshape
  from pandas._libs.sparse import IntIndex
  from pandas.compat import PY2, range, text_type, u, zip
  from pandas.core.dtypes.cast import maybe_promote
  from pandas.core.dtypes.common import ensure_platform_int, is_bool_dtype, is_extension_array_dtype, is_integer_dtype, is_list_like, is_object_dtype, needs_i8_conversion
  from pandas.core.dtypes.missing import notna
  from pandas import compat
  import pandas.core.algorithms
  from pandas.core.arrays import SparseArray
  from pandas.core.arrays.categorical import _factorize_from_iterable
  from pandas.core.frame import DataFrame
  from pandas.core.index import Index, MultiIndex
  from pandas.core.internals.arrays import extract_array
  from pandas.core.series import Series
  from pandas.core.sorting import compress_group_index, decons_obs_group_ids, get_compressed_ids, get_group_index
label_desc: Use image node_id 7 for calling a global function with example usage: _stack_multi_columns(frame, level_num, dropna) and returns: result, columns, level_num, columns