Lookup and reference functions

GROUPBY

Matches all 428 recorded answers

Recorded: 428 workbook cells. How we measure this · See the Rust code

Local names in 40 languages
LanguageName in Excel
ArabicGROUPBY
BulgarianGROUPBY
CzechSESKUPITPODLE
DanishGRUPPER.EFTER
GermanGROUPBY
GreekGROUPBY
English (Australia)GROUPBY
English (United Kingdom)GROUPBY
English (United States)GROUPBY
SpanishAGRUPARPOR
EstonianGROUPBY
FinnishJÄRJESTÄ.ARVOJEN.MUKAAN
FrenchGROUPER.PAR
HebrewGROUPBY
CroatianGROUPBY
HungarianCSOPORTOSÍTÁS.ALAP.SZERINT
IndonesianGROUPBY
ItalianGROUPBY
JapaneseGROUPBY
KoreanGROUPBY
LithuanianGROUPBY
LatvianGROUPBY
NorwegianGROUPBY
DutchGROEPEREN.PER
PolishGRUPUJ.WEDŁUG
Portuguese (Brazil)GROUPBY
Portuguese (Portugal)AGRUPARPOR
RomanianGROUPBY
RussianГРУПППО
SlovakGROUPBY
SlovenianGROUPBY
Serbian (Latin)GROUPBY
SwedishGRUPPERAEFTER
ThaiGROUPBY
TurkishGRUPLAÖLÇÜT
UkrainianGROUPBY
VietnameseGROUPBY
Chinese (Simplified)GROUPBY
Chinese (Hong Kong)GROUPBY
Chinese (Traditional)GROUPBY

Names from Microsoft's support site.

What it does

GROUPBY(row_fields, values, function, [field_headers], [total_depth], [sort_order], [filter_array], [field_relationship])

GROUPBY(row_fields,values,function,[field_headers],[total_depth],[sort_order],[filter_array],[field_relationship]) aggregates source rows by one or more key columns and spills a grouped table. The callback can be SUM, another supported function value, a LAMBDA or a vector of callbacks.

The key and value arguments are column-oriented tables with the same number of records. One key column groups by one label; several key columns describe levels such as region and product. Several value columns or a vector of callbacks can produce several aggregations.

Try it

Input cells (optional)

Example: {"B2": 3, "B3": 4}. A1 holds the formula. Other cells need values before a formula can read them.

Enter a formula, then calculate.

Runs locally in your browser with OneCalc. Its answer may differ from Excel. Use English function names and commas between arguments.

Put east,west,east,west in B2:B5 and 3,5,7,11 in C2:C5. =GROUPBY(B2:B5,C2:C5,LAMBDA(v,SUM(v)),0,0) returns the two group rows east,10 and west,16. Explicit zero options mean no input headers and no totals.

To put the largest amount first, add -2 as sort_order: =GROUPBY(B2:B5,C2:C5,LAMBDA(v,SUM(v)),0,0,-2). The first output column is the group label and the second the aggregated amount, so this returns west before east. A negative sort index means descending.

Quirks

Automatic header inference can remove a first row when values begin with text then a number; specify field_headers when that ambiguity matters. Header modes zero through three distinguish no headers, hidden existing headers, generated headers and shown existing headers. Totals, hierarchy/table relationships, sorting and filtering affect output shape. Current recorded text keys are case-insensitive and retain the first spelling.

OptionMeaning
field_headers0 treats every row as data; 1 consumes a header; 2 generates labels; 3 consumes and displays a header. Omitted means infer.
total_depth0 omits totals, 1 requests a grand total, deeper levels add subtotals. Negative values place totals before details.
filter_arrayOne Boolean per source row, applied before aggregation.
field_relationship0 keeps hierarchical key ordering; 1 treats keys as a table and excludes subtotals.

Subtotals need enough key columns. In the recorded current route, a filter admitting no rows gives #VALUE!; a blank output is not the universal empty-filter result.

How to implement

Validate matching row counts, callable arguments and any Boolean filter. Resolve headers and remove filtered rows before grouping. Group rows by typed key tuples, preserve the represented key label, sort under the requested relationship and sort keys, then pass each group’s value column to the callback. Build requested subtotals/grand totals by aggregating their underlying rows, not by summing already aggregated cells. A two-argument callback also receives its comparison set. Recorded array callback results remain boxed within a result cell; do not silently flatten them. In the example east receives three and seven, west five and eleven.

For an average callback, a grand total must use source observations with their original counts. If east contains 3,7 and west contains only 11, their averages are five and eleven, but the grand average is (3+7+11)/3=7. Averaging the two group results would incorrectly give eight. The same rule matters for COUNT, percent callbacks and custom lambdas.

Rust code

Show the CalcFunc source of this function

Quoted from the public repository DnaCalc/DnaCalc, crates calc-func and calc-fml, at commit 9e1884369a67. Comments are the source's own. A kernel shared with other functions appears once, on its module page.

GROUPBY · 1 lines · calc-func/src/catalogue/grouping.rs on GitHub
1 groupby_fn "GROUPBY" (3, 8) [R "row_fields", R "values", R "function", V "field_headers", V "total_depth", R "sort_order", R "filter_array", V "field_relationship"] |s, a, sv| { groupby(s, a, sv) }
groupby · 113 lines · calc-func/src/catalogue/grouping.rs on GitHub
fn groupby(site: &Site<'_>, a: &[CalcValue], sv: &mut EvalServices<'_>) -> Step<CalcValue> {
    let fields = table(site, a, 0, sv)?;
    let values = table(site, a, 1, sv)?;
    let functions = functions(a, 2)?;
    if fields.height() != values.height() {
        return fail(W::Value);
    }
    let (has_header, show) = headers(site, a, 3, &values)?;
    let depth = whole(site, a, 4)?.unwrap_or(1);
    let entries = sort_entries(site, a, 5)?;
    let tabular = match whole(site, a, 7)? {
        None | Some(0) => false,
        Some(1) => true,
        Some(_) => return fail(W::Value),
    };
    let k = fields.cols;
    if !(-2..=2).contains(&depth) || (depth.abs() == 2 && (k < 2 || tabular)) {
        return fail(W::Value);
    }
    let pairs = pairs(&functions, values.cols)?;
    if entries
        .iter()
        .any(|e| e.unsigned_abs() as usize > k + pairs.len())
    {
        return fail(W::Value);
    }
    let start = usize::from(has_header);
    if values.height() <= start {
        return fail(W::Value);
    }
    let count = values.height() - start;
    let keep = filter(site, a, 6, count, values.height(), sv)?;
    let rows: Vec<usize> = (start..values.height()).filter(|r| keep[r - start]).collect();
    let mut groups = Vec::new();
    let keys = canonical(site, &fields.rows, &rows)?;
    for (key, members) in group(site, &keys, &rows)? {
        let mut aggregates = Vec::with_capacity(pairs.len());
        for &(v, f) in &pairs {
            aggregates.push(aggregate(site, sv, &functions[f], &values, v, &members, &rows)?);
        }
        groups.push((key, members, aggregates));
    }
    sort_groups(site, &mut groups, k, &entries, tabular)?;
    let width = k + pairs.len();
    let mut out: Vec<Vec<CalcValue>> = Vec::new();
    if show {
        let mut header: Vec<CalcValue> = (0..k)
            .map(|c| if has_header { fields.rows[0][c].clone() } else { text(&format!("Row Field {}", c + 1)) })
            .collect();
        for &(v, f) in &pairs {
            header.push(if functions.len() > 1 {
                function_name(&functions[f])
            } else if has_header {
                values.rows[0][v].clone()
            } else {
                text(&format!("Value {}", v + 1))
            });
        }
        out.push(header);
    } else if functions.len() > 1 {
        let mut header = vec![text(""); k];
        header.extend(pairs.iter().map(|&(_, f)| function_name(&functions[f])));
        out.push(header);
    }
    let totals = |site: &Site<'_>, sv: &mut EvalServices<'_>, label: &str, prefix: Option<&CalcValue>, members: &[usize]| -> Step<Vec<CalcValue>> {
        let mut row = vec![text(""); width];
        match prefix {
            Some(key) => row[0] = key.clone(),
            None => row[0] = text(label),
        }
        for (at, &(v, f)) in pairs.iter().enumerate() {
            row[k + at] = aggregate(site, sv, &functions[f], &values, v, members, &rows)?;
        }
        Ok(row)
    };
    let grand_label = if depth.abs() == 2 { "Grand Total" } else { "Total" };
    if depth < 0 {
        out.push(totals(site, sv, grand_label, None, &rows)?);
    }
    let mut at = 0;
    while at < groups.len() {
        let mut end = at + 1;
        if depth.abs() == 2 {
            while end < groups.len() && same(site, &groups[end].0[0], &groups[at].0[0])? {
                end += 1;
            }
        }
        let members: Vec<usize> = groups[at..end].iter().flat_map(|g| g.1.iter().copied()).collect();
        if depth == -2 {
            out.push(totals(site, sv, "", Some(&groups[at].0[0]), &members)?);
        }
        for g in &groups[at..end] {
            let mut row = g.0.clone();
            row.extend(g.2.iter().cloned());
            out.push(row);
        }
        if depth == 2 {
            out.push(totals(site, sv, "", Some(&groups[at].0[0]), &members)?);
        }
        at = end;
    }
    if depth > 0 {
        out.push(totals(site, sv, grand_label, None, &rows)?);
    }
    for row in &mut out {
        for cell in row.iter_mut() {
            if matches!(cell.core_ref(), CoreRef::Missing) {
                *cell = text("");
            }
        }
    }
    result(out)
}

Kernels shared with other functions

Argument and result plumbing

No implementation has been published with a Handbook test suite for this function.

Version differences

The current source retains Excel 16.0 build 20527 COM observations from 30 October 2026: sixty grouping calls, plus separately described depth and multi-value controls. Those are one-build observations. No same-input difference against an earlier build is established here. Microsoft’s availability list is separate from these measurements.

Excel 16.0 build 20527

Windows through COM; bitness and CPU not stated in these comments

Implementation comments retain 60 COM grouping calls on 30 October 2026, including case-insensitive text keys, header modes, callable results and total ordering. This is one-build scope, with no earlier-build comparison.

Alternatives

PIVOTBY adds column groups; SUMIFS computes a chosen scalar group; PERCENTOF uses the second callback argument.

Named variants

No named variants are recorded yet.

Suggest a correction or contribute an implementation · For agents