GROUPBY function
PRO
The GROUPBY function in Jspreadsheet Formulas Pro summarizes a range by grouping the rows that share the same value in one or more fields and aggregating the related values. It works like a lightweight pivot table written as a single formula: you point it to the field column, the values column and an aggregation function such as SUM or AVERAGE, and it spills a summary table with one row per group and an optional total row. Because the result is a dynamic array, the summary updates automatically whenever the source data changes.
Documentation
Groups the rows of a range by one or more fields and aggregates the associated values, returning a summary table.
Category
Lookup and reference
Syntax
GROUPBY(row_fields, values, function, [field_headers], [total_depth], [sort_order], [filter_array], [field_relationship])
| Parameter | Description |
|---|---|
row_fields |
A column-oriented range or array with the values to group by. Adjacent columns create nested groups. |
values |
A column-oriented range or array with the values to aggregate. It must have the same number of rows as row_fields. Each additional column produces one more result column. |
function |
The aggregation applied to each group, such as SUM, AVERAGE, COUNT, MAX, MEDIAN, PERCENTOF or a LAMBDA. Use HSTACK(SUM, AVERAGE) to apply several aggregations side by side. |
[field_headers] |
Optional. When omitted, a header row is detected automatically. 0 = the ranges have no headers, 1 = the first row holds headers but they are not shown, 2 = no headers, generate them, 3 = the first row holds headers and they are shown. |
[total_depth] |
Optional. 0 = no totals, 1 = grand total (default), 2 = grand total and one subtotal per first-level group. Negative values (-1, -2) place the totals at the top of the result. |
[sort_order] |
Optional. The result column to sort by, counting the row fields first and then the value columns, or an array of key columns such as {2,1}. Use a negative number for descending order. |
[filter_array] |
Optional. A column of TRUE/FALSE values with one entry per row, for example C2:C6>4, that selects the rows to include. |
[field_relationship] |
Optional. 0 = hierarchy (default), 1 = table. Only meaningful when more than one row field is provided. |
Behavior
The GROUPBY function returns a dynamic array with one row per unique combination of the row fields, sorted in ascending order by default, followed by a Total row.
- When
field_headersis omitted, a header row in the ranges is detected automatically. Setting it to 0 explicitly treats the header text as data, which adds a group whose sum is 0. - With two or more row fields and
total_depthequal to 2, the result contains a subtotal row for each first-level group and a finalGrand Totalrow. - When several aggregations are passed with
HSTACK, the result gains a header row with the function names, for exampleSUMandAVERAGE. - A
LAMBDAreceives the values of each group as an array, so any custom aggregation can be written, such asLAMBDA(x, MAX(x)). ALAMBDAwith two parameters also receives the values of all groups, for exampleLAMBDA(x, y, SUM(x) / SUM(y))for the share of each group. - Using
PERCENTOFas the function returns the share of each group relative to the total of all groups. - The
filter_arrayis applied before grouping, so the totals reflect only the rows that pass the filter. - The result spills into the cells below and to the right of the formula. Its size changes with the number of distinct groups.
Common Errors
| Error | Description |
|---|---|
| #VALUE! | Returned when row_fields, values and filter_array do not have the same number of rows, when function is not a function (for example the text "SUM"), or when field_headers, total_depth or sort_order receive a value outside the accepted set. |
Best practices
- Set
field_headersto 3 when the ranges include a header row and you want the labels shown in the result. Avoid setting it to 0 in that case, because the header text becomes a group of its own.- Keep the cells below and to the right of the formula empty, because the spilled range grows and shrinks with the number of groups.
- Use
HSTACK(SUM, AVERAGE)as the function when you need more than one aggregation of the same data, instead of separate formulas.- Use
filter_arraywith a comparison such asC2:C6>4instead of copying the filtered rows to another range.- Use
sort_order2 or -2 to rank the groups by their aggregated value rather than by name.
Usage
A few examples using the GROUPBY function.
// Data: A2:A6 = North, South, North, South, North
// B2:B6 = Pen, Pen, Book, Book, Pen
// C2:C6 = 10, 5, 2, 8, 3
GROUPBY(A2:A6, C2:C6, SUM)
// { "North", 15; "South", 13; "Total", 28 }
GROUPBY(A1:A6, C1:C6, SUM, 3)
// Shows the header row: { "Region", "Qty"; "North", 15; "South", 13; "Total", 28 }
GROUPBY(A2:B6, C2:C6, SUM, 0, 2)
// Two row fields with a subtotal per region and a "Grand Total" row
GROUPBY(A2:A6, C2:C6, HSTACK(SUM, AVERAGE))
// { "", "SUM", "AVERAGE"; "North", 15, 5; "South", 13, 6.5; "Total", 28, 5.6 }
GROUPBY(A2:A6, C2:C6, LAMBDA(x, MAX(x)), 0, 0, -2)
// Custom aggregation, no total row, sorted by value descending: { "North", 10; "South", 8 }
GROUPBY(A2:A6, C2:C6, SUM, 0, 1, 1, C2:C6>4)
// Only rows with Qty above 4: { "North", 10; "South", 13; "Total", 23 }
Interactive Spreadsheet Demo
<html>
<script src="https://jsuites.net/v6/jsuites.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspreadsheet@13/dist/index.min.js"></script>
<link rel="stylesheet" href="https://jsuites.net/v6/jsuites.css" type="text/css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jspreadsheet@13/dist/jspreadsheet.min.css" type="text/css" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" />
<script src="https://cdn.jsdelivr.net/npm/@jspreadsheet/formula-pro/dist/index.min.js"></script>
<div id="spreadsheet"></div>
<script>
// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('NTAzZTEyYWUwMzg5ZWJmMmYxMmFhOGUwMjgxNjcyMzRiOWQ1MjJmYzhmNTVkM2EwYzI4NzNkMzE2YTNiYWE4NWYwMDI4ZDllOTdmMWMwN2Q4NDI4MjYxNTEwOTdmOWZhNjE3NTA1ODU1MDk1YzlhZjNhMmExZGNhZGZjYWVhMDgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpreE1ESTRNekF5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');
// Set the extensions
jspreadsheet.setExtensions({ formula });
// Create a new spreadsheet
jspreadsheet(document.getElementById('spreadsheet'), {
worksheets: [{
data: [
[
"Region",
"Product",
"Qty",
"Price",
"",
"=GROUPBY(A1:A6, C1:C6, SUM, 3)"
],
[
"North",
"Pen",
10,
2.5
],
[
"South",
"Pen",
5,
2.5
],
[
"North",
"Book",
2,
12
],
[
"South",
"Book",
8,
12
],
[
"North",
"Pen",
3,
2.5
]
]
}]
});
</script>
</html>
import React, { useRef } from "react";
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/react";
import formula from "@jspreadsheet/formula-pro";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";
// Set license
jspreadsheet.setLicense('NTAzZTEyYWUwMzg5ZWJmMmYxMmFhOGUwMjgxNjcyMzRiOWQ1MjJmYzhmNTVkM2EwYzI4NzNkMzE2YTNiYWE4NWYwMDI4ZDllOTdmMWMwN2Q4NDI4MjYxNTEwOTdmOWZhNjE3NTA1ODU1MDk1YzlhZjNhMmExZGNhZGZjYWVhMDgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpreE1ESTRNekF5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');
// Set the extensions
jspreadsheet.setExtensions({ formula });
export default function App() {
// Spreadsheet array of worksheets
const spreadsheet = useRef();
// Worksheet data
const data = [
[
"Region",
"Product",
"Qty",
"Price",
"",
"=GROUPBY(A1:A6, C1:C6, SUM, 3)"
],
[
"North",
"Pen",
10,
2.5
],
[
"South",
"Pen",
5,
2.5
],
[
"North",
"Book",
2,
12
],
[
"South",
"Book",
8,
12
],
[
"North",
"Pen",
3,
2.5
]
];
// Render component
return (
<Spreadsheet ref={spreadsheet}>
<Worksheet data={data} />
</Spreadsheet>
);
}
<template>
<Spreadsheet ref="spreadsheet">
<Worksheet :data="data" />
</Spreadsheet>
</template>
<script>
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/vue";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";
import formula from "@jspreadsheet/formula-pro";
// Set license
jspreadsheet.setLicense('NTAzZTEyYWUwMzg5ZWJmMmYxMmFhOGUwMjgxNjcyMzRiOWQ1MjJmYzhmNTVkM2EwYzI4NzNkMzE2YTNiYWE4NWYwMDI4ZDllOTdmMWMwN2Q4NDI4MjYxNTEwOTdmOWZhNjE3NTA1ODU1MDk1YzlhZjNhMmExZGNhZGZjYWVhMDgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpreE1ESTRNekF5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');
// Set the extensions
jspreadsheet.setExtensions({ formula });
export default {
components: {
Spreadsheet,
Worksheet,
},
data() {
// Worksheet data
const data = [
[
"Region",
"Product",
"Qty",
"Price",
"",
"=GROUPBY(A1:A6, C1:C6, SUM, 3)"
],
[
"North",
"Pen",
10,
2.5
],
[
"South",
"Pen",
5,
2.5
],
[
"North",
"Book",
2,
12
],
[
"South",
"Book",
8,
12
],
[
"North",
"Pen",
3,
2.5
]
]
return {
data
};
}
}
</script>
import { Component, ViewChild, ElementRef } from "@angular/core";
import jspreadsheet from "jspreadsheet";
import formula from "@jspreadsheet/formula-pro";
// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('NTAzZTEyYWUwMzg5ZWJmMmYxMmFhOGUwMjgxNjcyMzRiOWQ1MjJmYzhmNTVkM2EwYzI4NzNkMzE2YTNiYWE4NWYwMDI4ZDllOTdmMWMwN2Q4NDI4MjYxNTEwOTdmOWZhNjE3NTA1ODU1MDk1YzlhZjNhMmExZGNhZGZjYWVhMDgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpreE1ESTRNekF5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');
// Set the extensions
jspreadsheet.setExtensions({ formula });
@Component({
standalone: true,
selector: "app-root",
template: `<div #spreadsheet></div>`
})
export class AppComponent {
@ViewChild("spreadsheet") spreadsheet: ElementRef;
// Worksheets
worksheets: jspreadsheet.worksheetInstance[];
// Create a new data grid
ngAfterViewInit() {
// Create spreadsheet
this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, {
worksheets: [{
data: [
[
"Region",
"Product",
"Qty",
"Price",
"",
"=GROUPBY(A1:A6, C1:C6, SUM, 3)"
],
[
"North",
"Pen",
10,
2.5
],
[
"South",
"Pen",
5,
2.5
],
[
"North",
"Book",
2,
12
],
[
"South",
"Book",
8,
12
],
[
"North",
"Pen",
3,
2.5
]
]
}]
});
}
}