Returns JSON formatted for a single mutant chart.

  Args:
    mutant_features: An iterable of `MutantFeatureValue`s representing the
      X-axis.
    inference_result_proto: A ClassificationResponse or RegressionResponse
      returned by Servo, representing the Y-axis.
      It contains one 'classification' or 'regression' for every Example that
      was sent for inference. The length of that field should be the same length
      of mutant_features.
    index_to_mutate: The index of the feature being mutated for this chart.

  Returns:
    A JSON-able dict for rendering a single mutant chart, parseable by
    `vz-line-chart` or `vz-bar-chart`.

def make_json_formatted_for_single_chart(mutant_features,
                                         inference_result_proto,
                                         index_to_mutate):
  """Returns JSON formatted for a single mutant chart.

  Args:
    mutant_features: An iterable of `MutantFeatureValue`s representing the
      X-axis.
    inference_result_proto: A ClassificationResponse or RegressionResponse
      returned by Servo, representing the Y-axis.
      It contains one 'classification' or 'regression' for every Example that
      was sent for inference. The length of that field should be the same length
      of mutant_features.
    index_to_mutate: The index of the feature being mutated for this chart.

  Returns:
    A JSON-able dict for rendering a single mutant chart, parseable by
    `vz-line-chart` or `vz-bar-chart`.
  """
  x_label = 'step'
  y_label = 'scalar'

  if isinstance(inference_result_proto,
                classification_pb2.ClassificationResponse):
    # classification_label -> [{x_label: y_label:}]
    series = {}

    # ClassificationResponse has a separate probability for each label
    for idx, classification in enumerate(
        inference_result_proto.result.classifications):
      # For each example to use for mutant inference, we create a copied example
      # with the feature in question changed to each possible mutant value. So
      # when we get the inferences back, we get num_examples*num_mutants
      # results. So, modding by len(mutant_features) allows us to correctly
      # lookup the mutant value for each inference.
      mutant_feature = mutant_features[idx % len(mutant_features)]
      for class_index, classification_class in enumerate(
        classification.classes):
        # Fill in class index when labels are missing
        if classification_class.label == '':
          classification_class.label = str(class_index)
        # Special case to not include the "0" class in binary classification.
        # Since that just results in a chart that is symmetric around 0.5.
        if len(
            classification.classes) == 2 and classification_class.label == '0':
          continue
        key = classification_class.label
        if index_to_mutate:
          key += ' (index %d)' % index_to_mutate
        if not key in series:
          series[key] = {}
        if not mutant_feature.mutant_value in series[key]:
          series[key][mutant_feature.mutant_value] = []
        series[key][mutant_feature.mutant_value].append(
          classification_class.score)

    # Post-process points to have separate list for each class
    return_series = collections.defaultdict(list)
    for key, mutant_values in iteritems(series):
      for value, y_list in iteritems(mutant_values):
        return_series[key].append({
          x_label: value,
          y_label: sum(y_list) / float(len(y_list))
        })
      return_series[key].sort(key=lambda p: p[x_label])
    return return_series

  elif isinstance(inference_result_proto, regression_pb2.RegressionResponse):
    points = {}

    for idx, regression in enumerate(inference_result_proto.result.regressions):
      # For each example to use for mutant inference, we create a copied example
      # with the feature in question changed to each possible mutant value. So
      # when we get the inferences back, we get num_examples*num_mutants
      # results. So, modding by len(mutant_features) allows us to correctly
      # lookup the mutant value for each inference.
      mutant_feature = mutant_features[idx % len(mutant_features)]
      if not mutant_feature.mutant_value in points:
        points[mutant_feature.mutant_value] = []
      points[mutant_feature.mutant_value].append(regression.value)
    key = 'value'
    if (index_to_mutate != 0):
      key += ' (index %d)' % index_to_mutate
    list_of_points = []
    for value, y_list in iteritems(points):
      list_of_points.append({
        x_label: value,
        y_label: sum(y_list) / float(len(y_list))
      })
    list_of_points.sort(key=lambda p: p[x_label])
    return {key: list_of_points}

  else:
    raise NotImplementedError('Only classification and regression implemented.')