docs: def reduce_fit(interface, state, label, inp):
      """
      Function separates aggregation of continuous and discrete features.
      For continuous features it aggregates partially calculated means and variances and returns them. For discrete
      features it aggregates pairs and returns them. Pairs with label occurrences are used to calculate prior probabilities
      """
      from disco.util import kvgroup  # function for grouping values by key
      import numpy as np
  
      out = interface.output(0)  # all outputted pairs have the same output label
  
      # model of naive Bayes stores label names, sum of all label occurrences and pairs
      # (feature index, feature values) for discrete features which are needed to optimize predict phase.
      fit_model = {"y_labels": [], "y_sum": 0, "iv": set()}
      combiner = {}  # combiner maintains correct order of means and variances.
      means, variances = [], []
      k_prev = ""
  
      for key, value in kvgroup(inp):  # input pairs are sorted and grouped by key
          k_split = key.split(state["delimiter"])  # pair is split
  
          if len(k_split) == 3:  # discrete features
              # store pair (feature index, feature value)
              fit_model["iv"].add(tuple(k_split[1:]))
              # aggregate and output occurrences of a pair
              out.add(tuple(k_split), sum(value))
  
          elif len(k_split) == 2:  # continuous features
  
              # if label is different than previous.
              # This enables calculation of all variances and means for every feature for current label.
              if k_split[0] != k_prev and k_prev != "":
                  mean, var = zip(*[combiner[key] for key in sorted(combiner.keys())])
                  means.append(mean)
                  variances.append(var)
  
              # number of elements, partial mean, partial variance.
              n_a = mean_a = var_a = 0
              # code aggregates partially calculated means and variances
              for n_b, mean_b, var_b in value:
                  n_ab = n_a + n_b
                  var_a = ((n_a * var_a + n_b * var_b) / float(n_ab)) + (
                      n_a * n_b * ((mean_b - mean_a) / float(n_ab)) ** 2)
                  mean_a = (n_a * mean_a + n_b * mean_b) / float(n_ab)
                  n_a = n_ab
  
              # maintains correct order of statistics for every feature
              combiner[int(k_split[1])] = (mean_a, var_a + 1e-9)
              k_prev = k_split[0]
  
          else:  # aggregates label occurrences
              fit_model[key] = np.sum(value)
              fit_model["y_sum"] += fit_model[key]  # sum of all label occurrences
              fit_model["y_labels"].append(key)
  
      # if statistics for continuous features were not output in last iteration
      if len(means) > 0:
          mean, var = zip(*[combiner[key] for key in sorted(combiner.keys())])
          out.add("mean", np.array(means + [mean], dtype=np.float32))
          variances = np.array(variances + [var], dtype=np.float32)
          out.add("var", variances)
          out.add("var_log", np.log(np.pi * variances))
  
      # calculation of prior probabilities
      prior = [fit_model[y_label] / float(fit_model["y_sum"]) for y_label in fit_model["y_labels"]]
      out.add("prior", np.array(prior, dtype=np.float32))
      out.add("prior_log", np.log(prior))
      out.add("iv", list(fit_model["iv"]))
      out.add("y_labels", fit_model["y_labels"])