docs: public PrecisionRecallCurve getPrecisionRecallCurve() {
  
          if (prCurve != null) {
              return prCurve;
          }
  
          double[] thresholdOut;
          double[] precisionOut;
          double[] recallOut;
          int[] tpCountOut;
          int[] fpCountOut;
          int[] fnCountOut;
  
          if (isExact) {
              INDArray pl = getProbAndLabelUsed();
              INDArray sorted = Nd4j.sortRows(pl, 0, false);
              INDArray isPositive = sorted.getColumn(1,true);
  
              INDArray cumSumPos = isPositive.cumsum(-1);
              val length = sorted.size(0);
  
              /*
              Sort descending. As we iterate: decrease probability threshold T... all values <= T are predicted
              as class 0, all others are predicted as class 1
  
              Precision:  sum(TP) / sum(predicted pos at threshold)
              Recall:     sum(TP) / total actual positives
  
              predicted positive at threshold: # values <= threshold, i.e., just i
               */
  
              INDArray t = Nd4j.create(DataType.DOUBLE, length + 2, 1);
              t.put(new INDArrayIndex[]{interval(1, length + 1), all()}, sorted.getColumn(0,true));
  
              INDArray linspace = Nd4j.linspace(1, length, length, DataType.DOUBLE);
              INDArray precision = cumSumPos.castTo(DataType.DOUBLE).div(linspace.reshape(cumSumPos.shape()));
              INDArray prec = Nd4j.create(DataType.DOUBLE, length + 2, 1);
              prec.put(new INDArrayIndex[]{interval(1, length + 1), all()}, precision);
  
              //Recall/TPR
              INDArray rec = Nd4j.create(DataType.DOUBLE, length + 2, 1);
              rec.put(new INDArrayIndex[]{interval(1, length + 1), all()},
                      cumSumPos.div(countActualPositive));
  
              //Edge cases
              t.putScalar(0, 0, 1.0);
              prec.putScalar(0, 0, 1.0);
              rec.putScalar(0, 0, 0.0);
              prec.putScalar(length + 1, 0, cumSumPos.getDouble(cumSumPos.length() - 1) / length);
              rec.putScalar(length + 1, 0, 1.0);
  
              thresholdOut = t.data().asDouble();
              precisionOut = prec.data().asDouble();
              recallOut = rec.data().asDouble();
  
              //Counts. Note the edge cases
              tpCountOut = new int[thresholdOut.length];
              fpCountOut = new int[thresholdOut.length];
              fnCountOut = new int[thresholdOut.length];
  
              for (int i = 1; i < tpCountOut.length - 1; i++) {
                  tpCountOut[i] = cumSumPos.getInt(i - 1);
                  fpCountOut[i] = i - tpCountOut[i]; //predicted positive - true positive
                  fnCountOut[i] = (int) countActualPositive - tpCountOut[i];
              }
  
              //Edge cases: last idx -> threshold of 0.0, all predicted positive
              tpCountOut[tpCountOut.length - 1] = (int) countActualPositive;
              fpCountOut[tpCountOut.length - 1] = (int) (exampleCount - countActualPositive);
              fnCountOut[tpCountOut.length - 1] = 0;
              //Edge case: first idx -> threshold of 1.0, all predictions negative
              tpCountOut[0] = 0;
              fpCountOut[0] = 0; //(int)(exampleCount - countActualPositive);  //All negatives are predicted positive
              fnCountOut[0] = (int) countActualPositive;
  
              //Finally: 2 things to do
              //(a) Reverse order: lowest to highest threshold
              //(b) remove unnecessary/rendundant points (doesn't affect graph or AUPRC)
  
              ArrayUtils.reverse(thresholdOut);
              ArrayUtils.reverse(precisionOut);
              ArrayUtils.reverse(recallOut);
              ArrayUtils.reverse(tpCountOut);
              ArrayUtils.reverse(fpCountOut);
              ArrayUtils.reverse(fnCountOut);
  
              if (rocRemoveRedundantPts) {
                  Pair<double[][], int[][]> pair = removeRedundant(thresholdOut, precisionOut, recallOut, tpCountOut,
                          fpCountOut, fnCountOut);
                  double[][] temp = pair.getFirst();
                  int[][] temp2 = pair.getSecond();
                  thresholdOut = temp[0];
                  precisionOut = temp[1];
                  recallOut = temp[2];
                  tpCountOut = temp2[0];
                  fpCountOut = temp2[1];
                  fnCountOut = temp2[2];
              }
          } else {
              thresholdOut = new double[counts.size()];
              precisionOut = new double[counts.size()];
              recallOut = new double[counts.size()];
              tpCountOut = new int[counts.size()];
              fpCountOut = new int[counts.size()];
              fnCountOut = new int[counts.size()];
  
              int i = 0;
              for (Map.Entry<Double, CountsForThreshold> entry : counts.entrySet()) {
                  double t = entry.getKey();
                  CountsForThreshold c = entry.getValue();
                  long tpCount = c.getCountTruePositive();
                  long fpCount = c.getCountFalsePositive();
                  //For edge cases: http://stats.stackexchange.com/questions/1773/what-are-correct-values-for-precision-and-recall-in-edge-cases
                  //precision == 1 when FP = 0 -> no incorrect positive predictions
                  //recall == 1 when no dataset positives are present (got all 0 of 0 positives)
                  double precision;
                  if (tpCount == 0 && fpCount == 0) {
                      //At this threshold: no predicted positive cases
                      precision = 1.0;
                  } else {
                      precision = tpCount / (double) (tpCount + fpCount);
                  }
  
                  double recall;
                  if (countActualPositive == 0) {
                      recall = 1.0;
                  } else {
                      recall = tpCount / ((double) countActualPositive);
                  }
  
                  thresholdOut[i] = c.getThreshold();
                  precisionOut[i] = precision;
                  recallOut[i] = recall;
  
                  tpCountOut[i] = (int) tpCount;
                  fpCountOut[i] = (int) fpCount;
                  fnCountOut[i] = (int) (countActualPositive - tpCount);
                  i++;
              }
          }
  
          prCurve = new PrecisionRecallCurve(thresholdOut, precisionOut, recallOut, tpCountOut, fpCountOut, fnCountOut,
                  exampleCount);
          return prCurve;
      }