Skip to main content
v2026.11,858 entries · CC-BY 4.0
LAC HealthMedical & Research SuppliesReagents, PPE & instruments — chain-of-custody documented.Fast, traceable sourcing built for regulated research environments, from bench consumables to instrumentation.Shop lac.us CodeCASRAIlac.us

ave() in R: Group Mean for Every Row, With Examples

ave() in R adds a group-level statistic to every row. Syntax, a tested NA-safe example, and how it differs from tapply() and dplyr.

Written and maintained by CASRAI Editorial Board

Last updated

ave() in R computes a statistic within groups and returns the result for every row, so the output has the same length as the input. By default the statistic is the mean, which makes ave(x, g) the shortest base-R way to add a “group mean” column to a data frame without collapsing the rows. It is a base function in the stats package.

This guide covers the syntax from the R manual, a worked example you can paste into a console (including the NA trap that catches most first-time users), and a table of how ave() differs from tapply() and grouped dplyr::mutate().

What ave() is used for in research

Research datasets are usually long: one row per participant, sample, plate well or visit, with a column naming the group (site, treatment arm, batch, subject). Many routine steps need a group-level number sitting next to each row:

  • Centering a measurement on its group mean, for example score - ave(score, group), so each row shows its deviation from its own group.
  • Comparing each row with its batch or site average when screening for batch effects.
  • Numbering rows within each group with FUN = seq_along (visit number, replicate number).
  • Inspecting how much a group mean would change if missing values were ignored. For choosing a defensible missing-data method, see MCAR, MAR and MNAR: diagnosing your missing data.

Because the result keeps one value per input row, you can assign it straight back into a column without a merge step.

Syntax and arguments

The R manual page for ave (package stats, R-devel manual, checked 2026-10-29) gives the usage as:

ave(x, ..., FUN = mean)
Argument What the manual says
x A vector, typically numeric.
... Grouping variables, typically factors, all of the same length as x. The groups are the interaction of these variables.
FUN The function applied to each combination of factor levels. If supplied, it must be named (FUN = ...).

Two rules from the Value section matter in practice. First, the result is a vector of the same length as x: element i is FUN applied to the values of x that share the group of row i. Second, if you give no grouping variables at all, every element equals FUN(x), so ave(1:3) returns 2 2 2.

Install or access

ave() is documented in the stats package of R itself, so no add-on package is needed. In a console, ?ave opens the manual page for your installed version.

Step-by-step: add a group-mean column

The example below uses a small made-up data frame with one missing score. Every line was run in R 4.6.1, and the printed values in the next section are that output.

  1. Create the data.
    d = data.frame(
      group = c("A","A","A","B","B","C"),
      score = c(4, 6, NA, 10, 20, 7),
      site  = c("x","y","x","y","x","y")
    )
  2. Add the default group mean.
    d$mean_naive = ave(d$score, d$group)
  3. Add a group mean that ignores NA. Pass a named function that sets na.rm:
    d$mean_narm = ave(d$score, d$group,
                      FUN = function(x) mean(x, na.rm = TRUE))
  4. Add a within-group row number. This is the pattern from the manual’s own examples:
    d$idx = ave(seq_along(d$group), d$group, FUN = seq_along)
  5. Compute each row’s deviation from its group mean.
    d$dev = d$score - d$mean_narm
  6. Group by more than one variable. Add a second grouping argument; the groups become the combinations of both:
    d$mean_by_group_site = ave(d$score, d$group, d$site,
                               FUN = function(x) mean(x, na.rm = TRUE))

The manual also shows a trimmed mean, ave(breaks, tension, FUN = function(x) mean(x, trim = 0.1)), on the built-in warpbreaks data. Any function that takes a vector and returns either one value or a vector of the same length works as FUN.

Output and how to read it

Printing d after steps 1 to 5 gave:

  group score site mean_naive mean_narm idx dev
1     A     4    x         NA         5   1  -1
2     A     6    y         NA         5   2   1
3     A    NA    x         NA         5   3  NA
4     B    10    y         15        15   1  -5
5     B    20    x         15        15   2   5
6     C     7    y          7         7   1   0
  • Every row of group A shows the same group value (5), because the group-level statistic is repeated for each member. That repetition is the point of ave().
  • mean_naive is NA for all of group A, not only for the row with the missing score: the default mean() returns NA when any value in the group is NA, and ave() hands that result to every row of the group. Groups B and C are unaffected.
  • mean_narm averages the two observed scores in group A (4 and 6) to get 5. The NA row itself still has a missing dev, because its own score is missing.
  • With site added as a second grouping variable (step 6), group A splits in two. The rows for A at site x (scores 4 and NA) get 4, the mean of the one observed score, and the row for A at site y gets 6. The six rows return 4, 6, 4, 10, 20, 7.

Troubleshooting

The result is NA for a whole group

One NA in a group makes the default mean NA for every row in that group (see the output above). Supply FUN = function(x) mean(x, na.rm = TRUE). Decide first whether ignoring missing values is appropriate for your analysis; the missing-data guide linked above explains when it is not.

Error: “unique() applies only to vectors”

This appears when the function is passed without its name. Everything after x is read as a grouping variable, so ave(d$score, d$group, function(x) mean(x)) treats the function as a grouping variable and fails; in R 4.6.1 the error message is exactly unique() applies only to vectors. The manual’s rule that FUN must be named, when supplied, is the fix: write FUN = function(x) mean(x).

Grouping variables of the wrong length

The manual states the grouping variables must have the same length as x. Build them from the same data frame (d$group) rather than from a separate vector, so the lengths cannot drift apart.

You wanted one row per group, not one per input row

ave() never collapses rows. If you want a summary table with one value per group, use tapply() or a grouped summary instead; see the table below.

ave() vs tapply() vs dplyr: free alternatives compared

All three tools are free. They differ in the shape of what they return, which is usually the deciding factor. The statements below come from the R manual pages for ave and tapply and the dplyr mutate() reference (all checked 2026-10-29).

Tool What it returns Rows kept? Group syntax
ave(x, g, FUN = mean) (base R, stats) A vector the same length as x, with each group’s value repeated per row Yes, one value per input row Grouping variables listed after x; FUN named
tapply(X, INDEX, FUN) (base R) “Apply a function to each cell of a ragged array”: with a single value per cell and simplify = TRUE it returns an array with one entry per group, and NA for empty cells No, one value per group INDEX argument
group_by(g) |> mutate(m = mean(x)) (dplyr) The data frame with a new column computed within groups Yes group_by(), or the per-operation .by argument of mutate()

For the same data, tapply(d$score, d$group, function(x) mean(x, na.rm = TRUE)) returned one named value per group (A 5, B 15, C 7), whereas ave() returned six values.

The same column in dplyr

The dplyr reference describes .by as “a selection of columns to group by for just this operation, functioning as an alternative to group_by()”, and its documented grouped-mutate() example computes an aggregate within group: starwars |> select(name, mass, species) |> group_by(species) |> mutate(mass_norm = mass / mean(mass, na.rm = TRUE)). Following that pattern, the equivalent of step 3 is:

library(dplyr)
d |> group_by(group) |> mutate(mean_narm = mean(score, na.rm = TRUE))

The same reference shows what grouping changes: Luke Skywalker’s normalised mass is 0.947 when grouped by species and 0.791 ungrouped. Choose ave() when you want a dependency-free base-R line; choose dplyr when the rest of your pipeline is already in the tidyverse. For other by-group statistics in base R, see Standard Deviation in R: sd(), na.rm, and by-group calculation.

FAQ

What does ave() do in R?

It applies a function (the mean by default) to x within each group defined by one or more grouping variables, and returns a vector the same length as x in which each element is that group’s result.

Why does ave() need FUN to be named?

Everything after x is treated as a grouping variable, so an unnamed function is read as a grouping variable and the call fails. The manual says FUN, if supplied, must be named.

How do I ignore missing values in ave()?

Pass a named function that handles them: FUN = function(x) mean(x, na.rm = TRUE).

Can ave() group by more than one variable?

Yes. List several grouping variables after x; the groups are the interaction of those variables, so each combination of levels gets its own result.

What is the difference between ave() and tapply()?

ave() returns one value per input row; tapply() returns one value per group. Use ave() to add a column and keep your rows, and tapply() for a per-group summary. Related reading: Descriptive statistics and Z-score (standard score), which standardises a value against a mean and spread.

Sources

Follow CASRAI

Research-administration guidance, standards updates and independent tool reviews.

Ask CASRAI · free to try

Ask about ave() in R: Group Mean for Every Row, With Examples

Ask your first 2 questions free below. Subscribers get 150 a day for $29 a month.

An AI assistant specialized in research administration. It cites the sources behind every answer, labels web answers and says when it can't answer.

Answers draw on CASRAI's guides and dictionary plus the federal and funder documents we index: Federal Register, Grants.gov, Regulations.gov and UKRI.

Works on this site and inside Claude, Cursor and the AI tools you already use.

Everything CASRAI publishes — this page, the dictionary, the guides and the news — stays free to read, with no account and no card.

Referenced across the research world

University of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logoUniversity of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logo
  • University of Cambridge logo
  • Columbia University logo
  • Crossref logo
  • University of Edinburgh logo
  • Harvard University logo
  • University of Oxford logo
  • Princeton University logo
  • Stanford School of Medicine logo
  • University College London logo
  • ORCID logo

View CASRAI adoption →