function computeHistogram(data, bins) {
  return tf.tidy(() => {
    const dt = tf.tensor(data);
    if (dt.dtype === 'string') {
      return computeHistogramCat(data, bins);
    }
    const minVal = tf.min(dt).dataSync()[0];
    const maxVal = tf.max(dt).dataSync()[0];

    // parse the overloaded bins argument
    let nBins, binEdges, firstEdge, lastEdge;

    if (Number.isInteger(bins)) {
      nBins = bins;
      binEdges = tf.linspace(minVal, maxVal, nBins + 1);
      firstEdge = minVal;
      lastEdge = maxVal;
    } else if (bins.length) {
      nBins = bins.length - 1;
      binEdges = tf.tensor1d(bins);
      firstEdge = bins[0];
      lastEdge = bins[bins.length - 1];
    } else {
      throw '`bins` must be a number or an array';
    }

    // histogram scaling factor
    const norm = nBins / (lastEdge - firstEdge);

    const indicesRaw = tf
      .mul(tf.sub(dt, tf.scalar(firstEdge)), tf.scalar(norm))
      .toInt();

    // for values that lie exactly on lastEdge we need to subtract one
    const indices = tf.sub(
      indicesRaw,
      tf.equal(indicesRaw, tf.scalar(nBins).toInt()).toInt()
    );

    const binIds = tf.linspace(0, nBins - 1, nBins).toInt();
    const hist = binCount(indices, binIds);

    return [Array.from(hist.dataSync()), Array.from(binEdges.dataSync())];
  });
}
@param {Array} data - 1-D array of data points
@param {Number|Array} bins - number of bins, or bin edges
@return [{Array}] an array of 2 arrays:
0: hist, values of the histogram;
1: binEdges, an array of edge values (X values) of bins, length = hist.length + 1.