yardstick precision() in R: Score Positive Predictive Value
The yardstick precision() function in R returns the share of predicted positives that were correct, accepting a tibble of truth and prediction columns and returning a tidy one-row summary you can group, average across classes, or feed into a metric set.
precision(df, truth, estimate) # basic two-class call precision(df, truth = obs, estimate = pred) # named arguments precision(df, class, .pred_class) # default parsnip output df |> group_by(fold) |> precision(class, .pred_class) # by resample precision(df, class, .pred_class, estimator = "macro") # multiclass macro average precision(df, class, .pred_class, event_level = "second") # flip positive class precision_vec(truth_vec, pred_vec) # vector interface
Need explanation? Read on for examples and pitfalls.
What precision() measures
precision() answers a single question: when the model predicts positive, how often is it right? You pass a data frame with observed labels and predicted labels, and the function returns a one-row tibble with .metric, .estimator, and .estimate. The estimate is true positives over the sum of true positives and false positives, a number between 0 and 1.
Precision is the headline metric whenever false positives are expensive. Spam filters, fraud flags, and medical screening referrals all share the shape where a noisy positive wastes time, money, or trust. Accuracy hides that asymmetry; precision exposes it.
Precision is not symmetric with recall. A model that predicts positive once can score 1.0 while missing every other positive case. Always pair it with recall() or f_meas().
binary, macro, macro_weighted, or micro) changes the number, so always state it.precision() syntax and arguments
The signature matches the rest of the yardstick class-metric family. The same call shape works for logistic regression, random forest, xgboost, or any classifier whose predictions land in a tibble.
| Argument | Description |
|---|---|
data |
A data frame with truth and estimate columns. |
truth |
Unquoted column name of the observed class labels (a factor). |
estimate |
Unquoted column name of the predicted class labels (a factor with matching levels). |
estimator |
Averaging mode for multiclass: "binary", "macro", "macro_weighted", or "micro". Defaults to "binary" for two-class data and "macro" for multiclass. |
na_rm |
If TRUE, drop rows where either column is missing before scoring. |
event_level |
"first" or "second"; controls which factor level counts as the positive class. |
Both truth and estimate must be factors with identical levels. The most common mistake is passing class probabilities to estimate; precision needs class labels, so feed the .pred_class column from parsnip::augment().
Score classifiers: four worked examples
The examples below build a two-class frame, then a multiclass frame, so you can run precision() against both shapes. First, load yardstick and create a small two-class prediction tibble.
Example 1 calls precision() with positional arguments. Because "spam" is the first factor level, yardstick treats it as the positive class.
A 0.286 estimate means about 29 percent of rows the model labeled spam were actually spam; the rest were false alarms, exactly what a spam filter needs to push down.
Example 2 flips the positive class with event_level. Setting event_level = "second" makes "ham" the positive class, useful when the rare event is the second factor level.
Precision shifts dramatically between the two classes. Report both, or pick the one that matches the cost you care about.
Example 3 groups scoring by resample fold. Cross-validated predictions in a single tibble pair with group_by() to give one score per group.
Example 4 uses the vector interface for ad-hoc checks. precision_vec() accepts two factors and returns a plain numeric scalar instead of a tibble.
metric_set(precision, recall, f_meas) builds a reusable function you pipe predictions into to get all three scores at once.Multiclass averaging: macro, weighted, and micro
precision() handles multiclass data, but the answer depends on the averaging rule. Different rules suit different reporting goals.
The same tibble scored under each averaging mode gives a useful comparison:
| Estimator | What it does | When to use |
|---|---|---|
macro |
Unweighted mean of per-class precision | Treat every class as equally important, regardless of size |
macro_weighted |
Mean weighted by class prevalence in truth | Reflect the class mix in the real population |
micro |
Pool all predictions, then compute one ratio | Match the global hit rate; equals accuracy for symmetric metrics |
binary |
Score one positive class only | Two-class problems, set by default |
Multiclass papers usually report macro precision because small classes count as much as large ones. Production dashboards prefer macro_weighted because it tracks real traffic prevalence.
precision compared with related metrics
Precision is one of three numbers you usually need to see together. The table below summarizes when to reach for each yardstick metric.
| Metric | Best use case | Limitation |
|---|---|---|
precision() |
Costly false positives, e.g. spam, fraud flags | Says nothing about missed positives |
recall() |
Costly false negatives, e.g. cancer screening | Says nothing about wasted positives |
f_meas() |
Need a single imbalance-aware score | Equal precision-recall weighting may not match your costs |
accuracy() |
Balanced classes, equal error costs | Misleading on imbalanced data |
pr_auc() |
Threshold-free ranking under heavy imbalance | Requires probability predictions, not class labels |
A practical workflow reports precision, recall, and either f_meas() or pr_auc() together. Precision alone is easy to game.
yardstick::precision() is the equivalent of sklearn.metrics.precision_score(). The estimator argument maps directly to scikit-learn's average parameter: "macro", "micro", and "macro_weighted" line up with average="macro", "micro", and "weighted".Common pitfalls
Three small mistakes account for most precision() errors.
The first is feeding probabilities instead of class labels. yardstick errors on numeric input, but a probability column cast to a factor on the fly produces a meaningless score. Always pull .pred_class from augment(), not .pred_<level>.
The second pitfall is forgetting to set event_level when the rare class is the second factor level. Precision swings massively when you flip the positive class; never compare scores without confirming both runs used the same level.
The third pitfall is divide-by-zero on multiclass macro averaging. If the model never predicts a class, that class contributes NaN and the macro average drops to NaN. Switch to estimator = "macro_weighted" so empty classes drop out by weight.
recall() or check sum(estimate == positive_class) before celebrating a high score.Try it yourself
Try it: Use the built-in two_class_example data from yardstick. Compute the precision score, then compute it again with the positive class flipped via event_level = "second". Save the second result to ex_precision_alt.
Click to reveal solution
Explanation: event_level = "second" treats Class2 as the positive class, so precision now measures how many of the model's Class2 predictions were correct. The default "first" would have scored Class1 predictions instead.
Related yardstick metrics
precision() is one entry in the yardstick class-metric family. Reach for these neighbors when precision alone is not enough:
recall()for the symmetric partner: actual-positive coveragef_meas()for a single harmonic mean of precision and recallbal_accuracy()averages per-class recall, robust to imbalancekap()andmcc()for chance-corrected agreement scorespr_auc()androc_auc()for threshold-free probability rankingsconf_mat()to see the full confusion matrix behind every number
For the full set, see the yardstick reference index.
FAQ
Why does precision() return a tibble instead of a number?
The tidy-data return shape is the defining yardstick convention. Every metric, from precision() to roc_auc(), returns the same three columns: .metric, .estimator, and .estimate. That uniformity lets you bind_rows() multiple metric calls or chain group_by() |> metric() without writing reshape code. When you want the scalar, call precision_vec() instead, which returns a plain number.
Does precision() handle multiclass classification?
Yes, with one extra decision: how to average the per-class scores. yardstick defaults to estimator = "macro" for multiclass data, which computes precision for each class against the rest and takes an unweighted mean. Pass estimator = "macro_weighted" to weight by class prevalence, or "micro" to pool predictions globally before scoring. Each mode is documented in the syntax table above.
What is the difference between precision() and accuracy()?
accuracy() asks how many of all predictions were correct. precision() asks how many of the predicted-positive predictions were correct, ignoring the negative class entirely. On imbalanced data the two diverge sharply: a model that always predicts the majority can hit high accuracy while scoring near-zero precision on the minority class. Report both whenever class costs are asymmetric.
Why does precision return NaN on my multiclass data?
A NaN precision means your model never predicted at least one of the classes, so the per-class precision divides by zero. Switch to estimator = "macro_weighted" to drop empty classes by weight, or inspect conf_mat() to confirm which class the model is ignoring.