docs: def convert_wide_to_long(wide_data,
                           ind_vars,
                           alt_specific_vars,
                           availability_vars,
                           obs_id_col,
                           choice_col,
                           new_alt_id_name=None):
      """
      Will convert a cross-sectional dataframe of discrete choice data from wide
      format to long format.
  
      Parameters
      ----------
      wide_data : pandas dataframe.
          Contains one row for each observation. Should have the specified
          `[obs_id_col, choice_col] + availability_vars.values()` columns.
      ind_vars : list of strings.
          Each element should be a column heading in `wide_data` that denotes a
          variable that varies across observations but not across alternatives.
      alt_specific_vars : dict.
          Each key should be a string that will be a column heading of the
          returned, long format dataframe. Each value should be a dictionary
          where the inner key is the alternative id and the value is the column
          heading in wide data that specifies the value of the outer key for the
          associated alternative. The variables denoted by the outer key should
          vary across individuals and across some or all alternatives.
      availability_vars : dict.
          There should be one key value pair for each alternative that is
          observed in the dataset. Each key should be the alternative id for the
          alternative, and the value should be the column heading in `wide_data`
          that denotes (using ones and zeros) whether an alternative is
          available/unavailable, respectively, for a given observation.
          Alternative id's, i.e. the keys, must be integers.
      obs_id_col : str.
          Denotes the column in `wide_data` that contains the observation ID
          values for each row.
      choice_col : str.
          Denotes the column in `wide_data` that contains a one if the
          alternative pertaining to the given row was the observed outcome for
          the observation pertaining to the given row and a zero otherwise.
      new_alt_id_name : str, optional.
          If not None, should be a string. This string will be used as the column
          heading for the alternative id column in the returned 'long' format
          dataframe. If not passed, this column will be called `'alt_id'`.
          Default == None.
  
      Returns
      -------
      final_long_df : pandas dataframe.
          Will contain one row for each available alternative for each
          observation. Will contain an observation id column of the same name as
          `obs_id_col`. Will also contain a choice column of the same name as
          `choice_col`. Will also contain an alternative id column called
          `alt_id` if `new_alt_id_col == None`, or `new_alt_id` otherwise. Will
          contain one column per variable in `ind_vars`. Will contain one column
          per key in `alt_specific_vars`.
      """
      ##########
      # Check that all columns of wide_data are being
      # used in the conversion to long format
      ##########
      all_alt_specific_cols = []
      for var_dict in alt_specific_vars.values():
          all_alt_specific_cols.extend(var_dict.values())
  
      vars_accounted_for = set(ind_vars +
                               # converto list explicitly to support
                               # both python 2 and 3
                               list(availability_vars.values()) +
                               [obs_id_col, choice_col] +
                               all_alt_specific_cols)
      num_vars_accounted_for = len(vars_accounted_for)
  
      ensure_all_columns_are_used(num_vars_accounted_for,
                                  wide_data,
                                  data_title='wide_data')
  
      ##########
      # Check that all columns one wishes to use are actually in wide_data
      ##########
      ensure_columns_are_in_dataframe(ind_vars,
                                      wide_data,
                                      col_title='ind_vars',
                                      data_title='wide_data')
  
      ensure_columns_are_in_dataframe(availability_vars.values(),
                                      wide_data,
                                      col_title='availability_vars',
                                      data_title='wide_data')
  
      for new_column in alt_specific_vars:
          for alt_id in alt_specific_vars[new_column]:
              old_column = alt_specific_vars[new_column][alt_id]
              ensure_columns_are_in_dataframe([old_column],
                                              wide_data,
                                              col_title="alt_specific_vars",
                                              data_title='wide_data')
  
      ensure_columns_are_in_dataframe([choice_col, obs_id_col],
                                      wide_data,
                                      col_title='[choice_col, obs_id_col]',
                                      data_title='wide_data')
  
      ##########
      # Check the integrity of the various columns present in wide_data
      ##########
      # Make sure the observation id's are unique (i.e. one per row)
      ensure_unique_obs_ids_in_wide_data(obs_id_col, wide_data)
  
      # Make sure there are no blank values in the choice column
      check_wide_data_for_blank_choices(choice_col, wide_data)
  
      ##########
      # Check that the user-provided alternative ids are observed
      # in the realized choices.
      ##########
      ensure_all_wide_alt_ids_are_chosen(choice_col,
                                         alt_specific_vars,
                                         availability_vars,
                                         wide_data)
  
      ##########
      # Check that the realized choices are all in the
      # user-provided alternative ids
      ##########
      ensure_chosen_alternatives_are_in_user_alt_ids(choice_col,
                                                     wide_data,
                                                     availability_vars)
  
      ##########
      # Make sure each observation chose a personally available alternative.
      ##########
      ensure_each_wide_obs_chose_an_available_alternative(obs_id_col,
                                                          choice_col,
                                                          availability_vars,
                                                          wide_data)
  
      ##########
      # Figure out how many rows/columns should be in the long format dataframe
      ##########
      # Note that the number of rows in long format is the
      # number of available alternatives across all observations
      sorted_alt_ids = np.sort(wide_data[choice_col].unique())
      sorted_availability_cols = [availability_vars[x] for x in sorted_alt_ids]
      num_rows = wide_data[sorted_availability_cols].sum(axis=0).sum()
  
      #####
      # Calculate the needed number of colums
      #####
      # For each observation, there is at least one column-- the observation id,
      num_cols = 1
      # We should also have one alternative id column
      num_cols += 1
      # We should also have one column to record the choice of each observation
      num_cols += 1
      # We should also have one column for each individual specific variable
      num_cols += len(ind_vars)
      # We should also have one column for each alternative specific variable,
      num_cols += len(alt_specific_vars.keys())
  
      ##########
      # Create the columns of the new dataframe
      ##########
      #####
      # Create the observation id column,
      #####
      # Determine the various availability values for each observation
      wide_availability_values = wide_data[list(
          availability_vars.values())].values
      new_obs_id_col = (wide_availability_values *
                        wide_data[obs_id_col].values[:, None]).ravel()
      # Make sure the observation id column has an integer data type
      new_obs_id_col = new_obs_id_col.astype(int)
  
      #####
      # Create the independent variable columns. Store them in a list.
      #####
      new_ind_var_cols = []
      for var in ind_vars:
          new_ind_var_cols.append((wide_availability_values *
                                   wide_data[var].values[:, None]).ravel())
  
      #####
      # Create the choice column in the long data format
      #####
      wide_choice_data = (wide_data[choice_col].values[:, None] ==
                          sorted_alt_ids[None, :])
      new_choice_col = wide_choice_data.ravel()
      # Make sure the choice column has an integer data type
      new_choice_col = new_choice_col.astype(int)
  
      #####
      # Create the alternative id column
      #####
      new_alt_id_col = (wide_availability_values *
                        sorted_alt_ids[None, :]).ravel().astype(int)
      # Make sure the alternative id column has an integer data type
      new_alt_id_col = new_alt_id_col.astype(int)
  
      #####
      # Create the alternative specific and subset
      # alternative specific variable columns
      #####
      # For each alternative specific variable, create a wide format array.
      # Then unravel that array to have the long format column for the
      # alternative specific variable. Store all the long format columns
      # in a list
      new_alt_specific_cols = []
      for new_col in alt_specific_vars:
          new_wide_alt_specific_cols = []
          for alt_id in sorted_alt_ids:
              # This will extract the correct values for the alternatives over
              # which the alternative specific variables vary
              if alt_id in alt_specific_vars[new_col]:
                  rel_wide_column = alt_specific_vars[new_col][alt_id]
                  new_col_vals = wide_data[rel_wide_column].values[:, None]
                  new_wide_alt_specific_cols.append(new_col_vals)
              # This will create placeholder zeros for the alternatives that
              # the alternative specific variables do not vary over
              else:
                  new_wide_alt_specific_cols.append(np.zeros(
                                                      (wide_data.shape[0], 1)))
          concatenated_long_column = np.concatenate(new_wide_alt_specific_cols,
                                                    axis=1).ravel()
          new_alt_specific_cols.append(concatenated_long_column)
  
      ##########
      # Construct the final wide format dataframe to be returned
      ##########
      # Identify rows that correspond to unavailable alternatives
      availability_condition = wide_availability_values.ravel() != 0
  
      # Figure out the names of all of the columns in the final
      # dataframe
      alt_id_column_name = ("alt_id" if new_alt_id_name is None
                            else new_alt_id_name)
      final_long_columns = ([obs_id_col,
                             alt_id_column_name,
                             choice_col] +
                            ind_vars +
                            list(alt_specific_vars.keys()))
  
      # Create a 'record array' of the final dataframe's columns
      # Note that record arrays are constructed from a list of 1D
      # arrays hence the array unpacking performed below for
      # new_ind_var_cols and new_alt_specific_cols
      all_arrays = ([new_obs_id_col,
                     new_alt_id_col,
                     new_choice_col] +
                    new_ind_var_cols +
                    new_alt_specific_cols)
  
      # Be sure to remove rows corresponding to unavailable alternatives
      # When creating the record array.
      df_recs = np.rec.fromarrays([all_arrays[pos][availability_condition]
                                   for pos in range(len(all_arrays))],
                                  names=final_long_columns)
  
      # Create the final dataframe
      final_long_df = pd.DataFrame.from_records(df_recs)
  
      ##########
      # Make sure one has the correct number of rows and columns in
      # the final dataframe
      ##########
      try:
          assert final_long_df.shape == (num_rows, num_cols)
      except AssertionError:
          msg_1 = "There is an error with the dataframe that will be returned."
          msg_2 = "The shape of the dataframe should be {}".format((num_rows,
                                                                    num_cols))
          msg_3 = "Instead, the returned dataframe will have shape: {}"
          total_msg = "\n".join([msg_1, msg_2, msg_3])
          warnings.warn(total_msg.format(final_long_df.shape))
  
      # Return the wide format dataframe
      return final_long_df