docs: def merge_metadata(preprocess_output_dir, transforms_file):
    """Merge schema, analysis, and transforms files into one python object.
  
    Args:
      preprocess_output_dir: the output folder of preprocessing. Should contain
          the schema, and the numerical and categorical
          analysis files.
      transforms_file: the training transforms file.
  
    Returns:
      A dict in the form
      {
        csv_header: [name1, name2, ...],
        csv_defaults: {name1: value, name2: value},
        key_column: name,
        target_column: name,
        categorical_columns: []
        numerical_columns: []
        transforms: { name1: {transform: scale, value: 2},
                      name2: {transform: embedding, dim: 50}, ...
                    }
        vocab_stats: { name3: {n_classes: 23, labels: ['1', '2', ..., '23']},
                       name4: {n_classes: 102, labels: ['red', 'blue', ...]}}
      }
  
    Raises:
      ValueError: if one of the input metadata files is wrong.
    """
    numerical_anlysis_file = os.path.join(preprocess_output_dir,
                                          NUMERICAL_ANALYSIS)
    schema_file = os.path.join(preprocess_output_dir, SCHEMA_FILE)
  
    numerical_anlysis = json.loads(
        python_portable_string(
            file_io.read_file_to_string(numerical_anlysis_file)))
    schema = json.loads(
        python_portable_string(file_io.read_file_to_string(schema_file)))
    transforms = json.loads(
        python_portable_string(file_io.read_file_to_string(transforms_file)))
  
    result_dict = {}
    result_dict['csv_header'] = [col_schema['name'] for col_schema in schema]
    result_dict['key_column'] = None
    result_dict['target_column'] = None
    result_dict['categorical_columns'] = []
    result_dict['numerical_columns'] = []
    result_dict['transforms'] = {}
    result_dict['csv_defaults'] = {}
    result_dict['vocab_stats'] = {}
  
    # get key column.
    for name, trans_config in six.iteritems(transforms):
      if trans_config.get('transform', None) == 'key':
        result_dict['key_column'] = name
        break
    if result_dict['key_column'] is None:
      raise ValueError('Key transform missing form transfroms file.')
  
    # get target column.
    result_dict['target_column'] = schema[0]['name']
    for name, trans_config in six.iteritems(transforms):
      if trans_config.get('transform', None) == 'target':
        result_dict['target_column'] = name
        break
    if result_dict['target_column'] is None:
      raise ValueError('Target transform missing from transforms file.')
  
    # Get the numerical/categorical columns.
    for col_schema in schema:
      col_name = col_schema['name']
      col_type = col_schema['type'].lower()
      if col_name == result_dict['key_column']:
        continue
  
      if col_type == 'string':
        result_dict['categorical_columns'].append(col_name)
      elif col_type == 'integer' or col_type == 'float':
        result_dict['numerical_columns'].append(col_name)
      else:
        raise ValueError('Unsupported schema type %s' % col_type)
  
    # Get the transforms.
    for name, trans_config in six.iteritems(transforms):
      if name != result_dict['target_column'] and name != result_dict['key_column']:
        result_dict['transforms'][name] = trans_config
  
    # Get the vocab_stats
    for name in result_dict['categorical_columns']:
      if name == result_dict['key_column']:
        continue
  
      label_values = get_vocabulary(preprocess_output_dir, name)
      if name != result_dict['target_column'] and '' not in label_values:
        label_values.append('')  # append a 'missing' label.
      n_classes = len(label_values)
      result_dict['vocab_stats'][name] = {'n_classes': n_classes,
                                          'labels': label_values}
  
    # Get the csv_defaults
    for col_schema in schema:
      name = col_schema['name']
      col_type = col_schema['type'].lower()
      default = transforms.get(name, {}).get('default', None)
  
      if name == result_dict['target_column']:
        if name in result_dict['numerical_columns']:
          default = float(default or 0.0)
        else:
          default = default or ''
      elif name == result_dict['key_column']:
        if col_type == 'string':
          default = str(default or '')
        elif col_type == 'float':
          default = float(default or 0.0)
        else:
          default = int(default or 0)
      else:
        if col_type == 'string':
          default = str(default or '')
          if default not in result_dict['vocab_stats'][name]['labels']:
            raise ValueError('Default %s is not in the vocab for %s' %
                             (default, name))
        else:
          default = float(default or numerical_anlysis[name]['mean'])
  
      result_dict['csv_defaults'][name] = default
  
    validate_metadata(result_dict)
    return result_dict