From d08b70d05ca6d8c122a4f1e6c99aea1a39a0558c Mon Sep 17 00:00:00 2001
From: Mukul Kumar <mukulrao6480@gmail.com>
Date: Fri, 21 Mar 2025 23:20:04 +0530
Subject: [PATCH 01/10] add jj usage description as add working examples

---
 NEWS.md             | 2 ++
 man/groupingsets.Rd | 9 +++++++++
 2 files changed, 11 insertions(+)

diff --git a/NEWS.md b/NEWS.md
index 85e27c8704..e8a07e5b14 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -27,6 +27,8 @@
    + Argument `in.place` to `droplevels` has been removed.
    + It's now an error to set `datatable.nomatch`, which has been warning since 1.15.0.
 
+3. The groupingsets() documentation now explicitly addresses scoping issues when using variables from outer environments (e.g., function parameters) in aggregation expressions. Previously, using such variables in j could lead to "object not found" errors due to evaluation environment mismatches. This is resolved by using jj = substitute(expr) instead, which captures both the expression and its environment per R’s scoping rules (§6). Three new examples demonstrate proper usage, including function parameter references (e.g., sum(value > threshold)) and multi-aggregation patterns.
+
 # data.table [v1.17.0](https://github.com/Rdatatable/data.table/milestone/34)  (20 Feb 2025)
 
 ## POTENTIALLY BREAKING CHANGES
diff --git a/man/groupingsets.Rd b/man/groupingsets.Rd
index 5d8a309548..2e8137a6d5 100644
--- a/man/groupingsets.Rd
+++ b/man/groupingsets.Rd
@@ -34,6 +34,8 @@ groupingsets(x, \dots)
     The \code{label} argument can be a named list of scalars, or a scalar, or \code{NULL}. When \code{label} is a list, each element name must be (1) a variable name in \code{by}, or (2) the first element of the class in the data.table \code{x} of a variable in \code{by}, or (3) one of 'character', 'integer', 'numeric', 'factor', 'Date', 'IDate'. The order of the list elements is not important. A label specified by variable name will apply only to that variable, while a label specified by first element of a class will apply to all variables in \code{by} for which the first element of the class of the variable in \code{x} matches the \code{label} element name, except for variables that have a label specified by variable name (that is, specification by variable name takes precedence over specification by class). For \code{label} elements with name in \code{by}, the class of the label value must be the same as the class of the variable in \code{x}. For \code{label} elements with name not in \code{by}, the first element of the class of the label value must be the same as the \code{label} element name. For example, \code{label = list(integer = 999, IDate = as.Date("3000-01-01"))} would produce an error because \code{class(999)[1]} is not \code{"integer"} and \code{class(as.Date("3000-01-01"))[1]} is not \code{"IDate"}. A corrected specification would be \code{label = list(integer = 999L, IDate = as.IDate("3000-01-01"))}.
 
     The \code{label = <scalar>} option provides a shorter alternative in the case where only one class of grouping variable requires a label. For example, \code{label = list(character = "Total")} can be shortened to \code{label = "Total"}. When this option is used, the label will be applied to all variables in \code{by} for which the first element of the class of the variable in \code{x} matches the first element of the class of the scalar.
+
+    The \code{jj} argument provides a mechanism to handle scoping issues when working with variables from outer environments. When using \code{j} directly with variables not defined in the columns of the data.table, R's evaluation rules may not find these variables, resulting in "object not found" errors. The \code{jj} argument accepts a quoted expression created with \code{substitute()}, which captures both the expression and its environment. This approach is particularly useful in functions where parameters need to be referenced within aggregation expressions. When \code{jj} is provided, the function will ignore any value specified in the \code{j} argument.
 }
 \value{
     A data.table with various aggregates.
@@ -81,5 +83,12 @@ cube(DT, j = c(list(count=.N), lapply(.SD, sum)), by = c("color","year","status"
 # groupingsets
 groupingsets(DT, j = c(list(count=.N), lapply(.SD, sum)), by = c("color","year","status"),
              sets = list("color", c("year","status"), character()), id=TRUE)
+# Using jj argument(enables access to variables from outer environments)
+groupingsets(DT, jj = substitute(c(list(count=.N), lapply(.SD, sum))), by = c("color","year","status"),  # Basic jj usage with substitute()
+             sets = list("color", c("year","status"), character()), id = TRUE, .SDcols = "value") 
+groupingsets(DT, jj = substitute(lapply(.SD, sum)), by = c("color","year","status"),   # Simple aggregation with jj
+             sets = list("color", c("year","status"), character()), id = TRUE, .SDcols = "value")
+groupingsets(DT, jj = substitute(list(count = .N, total = sum(value))), by = c("color","year","status"), # jj with direct column references         
+             sets = list("color", c("year","status"), character()), id = TRUE)      
 }
 \keyword{ data }

From 4407f1c463a15525cf277f2ca96b979d0ef1fae9 Mon Sep 17 00:00:00 2001
From: Mukul Kumar <mukulrao6480@gmail.com>
Date: Fri, 21 Mar 2025 23:32:36 +0530
Subject: [PATCH 02/10] \n exceeding characters

---
 man/groupingsets.Rd | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/man/groupingsets.Rd b/man/groupingsets.Rd
index 2e8137a6d5..da46336748 100644
--- a/man/groupingsets.Rd
+++ b/man/groupingsets.Rd
@@ -84,11 +84,14 @@ cube(DT, j = c(list(count=.N), lapply(.SD, sum)), by = c("color","year","status"
 groupingsets(DT, j = c(list(count=.N), lapply(.SD, sum)), by = c("color","year","status"),
              sets = list("color", c("year","status"), character()), id=TRUE)
 # Using jj argument(enables access to variables from outer environments)
-groupingsets(DT, jj = substitute(c(list(count=.N), lapply(.SD, sum))), by = c("color","year","status"),  # Basic jj usage with substitute()
+groupingsets(DT, jj = substitute(c(list(count=.N), lapply(.SD, sum))), 
+             by = c("color","year","status"),  # Basic jj usage with substitute()
              sets = list("color", c("year","status"), character()), id = TRUE, .SDcols = "value") 
-groupingsets(DT, jj = substitute(lapply(.SD, sum)), by = c("color","year","status"),   # Simple aggregation with jj
+groupingsets(DT, jj = substitute(lapply(.SD, sum)),
+             by = c("color","year","status"),   # Simple aggregation with jj
              sets = list("color", c("year","status"), character()), id = TRUE, .SDcols = "value")
-groupingsets(DT, jj = substitute(list(count = .N, total = sum(value))), by = c("color","year","status"), # jj with direct column references         
+groupingsets(DT, jj = substitute(list(count = .N, total = sum(value))),
+             by = c("color","year","status"), # jj with direct column references         
              sets = list("color", c("year","status"), character()), id = TRUE)      
 }
 \keyword{ data }

From edaf377e7644b6452c298849fac01ab9729b7180 Mon Sep 17 00:00:00 2001
From: Mukul Kumar <mukulrao6480@gmail.com>
Date: Sat, 22 Mar 2025 22:27:47 +0530
Subject: [PATCH 03/10] specify use of quote and substitute with example

---
 NEWS.md             |  2 +-
 man/groupingsets.Rd | 19 +++++++++++++++----
 2 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/NEWS.md b/NEWS.md
index e8a07e5b14..d34745dca7 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -27,7 +27,7 @@
    + Argument `in.place` to `droplevels` has been removed.
    + It's now an error to set `datatable.nomatch`, which has been warning since 1.15.0.
 
-3. The groupingsets() documentation now explicitly addresses scoping issues when using variables from outer environments (e.g., function parameters) in aggregation expressions. Previously, using such variables in j could lead to "object not found" errors due to evaluation environment mismatches. This is resolved by using jj = substitute(expr) instead, which captures both the expression and its environment per R’s scoping rules (§6). Three new examples demonstrate proper usage, including function parameter references (e.g., sum(value > threshold)) and multi-aggregation patterns.
+3. The groupingsets() documentation now explicitly addresses scoping issues when using variables from outer environments (e.g., function parameters) in aggregation expressions. Previously, using such variables in j could lead to "object not found" errors due to evaluation environment mismatches. This is resolved by using jj = substitute(expr) instead, which captures both the expression and its environment per R’s scoping rules (Chapter 6 of the R Language Definition manual). Three new examples demonstrate proper usage, including function parameter references (e.g., sum(value > threshold)) and multi-aggregation patterns.
 
 # data.table [v1.17.0](https://github.com/Rdatatable/data.table/milestone/34)  (20 Feb 2025)
 
diff --git a/man/groupingsets.Rd b/man/groupingsets.Rd
index da46336748..78a64f0abc 100644
--- a/man/groupingsets.Rd
+++ b/man/groupingsets.Rd
@@ -35,7 +35,7 @@ groupingsets(x, \dots)
 
     The \code{label = <scalar>} option provides a shorter alternative in the case where only one class of grouping variable requires a label. For example, \code{label = list(character = "Total")} can be shortened to \code{label = "Total"}. When this option is used, the label will be applied to all variables in \code{by} for which the first element of the class of the variable in \code{x} matches the first element of the class of the scalar.
 
-    The \code{jj} argument provides a mechanism to handle scoping issues when working with variables from outer environments. When using \code{j} directly with variables not defined in the columns of the data.table, R's evaluation rules may not find these variables, resulting in "object not found" errors. The \code{jj} argument accepts a quoted expression created with \code{substitute()}, which captures both the expression and its environment. This approach is particularly useful in functions where parameters need to be referenced within aggregation expressions. When \code{jj} is provided, the function will ignore any value specified in the \code{j} argument.
+    The \code{jj} argument provides a mechanism to handle scoping issues when working with variables from outer environments. When using \code{j} directly with variables not defined in the columns of the data.table, R's evaluation rules may not find these variables, resulting in "object not found" errors. The \code{jj} argument accepts a quoted expression which can be created with either \code{quote()} (for direct calls referencing only columns and special symbols) or \code{substitute()} (when inside functions that need to capture parameters from the outer environment). This approach is particularly useful in functions where parameters need to be referenced within aggregation expressions. When \code{jj} is provided, the function will ignore any value specified in the \code{j} argument.    
 }
 \value{
     A data.table with various aggregates.
@@ -83,15 +83,26 @@ cube(DT, j = c(list(count=.N), lapply(.SD, sum)), by = c("color","year","status"
 # groupingsets
 groupingsets(DT, j = c(list(count=.N), lapply(.SD, sum)), by = c("color","year","status"),
              sets = list("color", c("year","status"), character()), id=TRUE)
+
 # Using jj argument(enables access to variables from outer environments)
-groupingsets(DT, jj = substitute(c(list(count=.N), lapply(.SD, sum))), 
+groupingsets(DT, jj = quote(c(list(count=.N), lapply(.SD, sum))), 
              by = c("color","year","status"),  # Basic jj usage with substitute()
              sets = list("color", c("year","status"), character()), id = TRUE, .SDcols = "value") 
-groupingsets(DT, jj = substitute(lapply(.SD, sum)),
+groupingsets(DT, jj = quote(lapply(.SD, sum)),
              by = c("color","year","status"),   # Simple aggregation with jj
              sets = list("color", c("year","status"), character()), id = TRUE, .SDcols = "value")
-groupingsets(DT, jj = substitute(list(count = .N, total = sum(value))),
+groupingsets(DT, jj = quote(list(count = .N, total = sum(value))),
              by = c("color","year","status"), # jj with direct column references         
              sets = list("color", c("year","status"), character()), id = TRUE)      
+
+# Example showing when substitute() is necessary - in a wrapper function
+custom_grouping <- function(dt, multiplier = 1) {
+  # Here substitute() is needed to capture 'multiplier' from function environment
+  groupingsets(dt, jj = substitute(list(count = .N, total = sum(value) * multiplier)),
+               by = c("color", "year", "status"),sets = list("color", c("year", "status"), character()),
+               id = TRUE)
+}
+# Using the wrapper function
+custom_grouping(DT, multiplier = 2)
 }
 \keyword{ data }

From 5e6b246c007feac9939dfe5cd6445c59852084d6 Mon Sep 17 00:00:00 2001
From: Mukul Kumar <mukulrao6480@gmail.com>
Date: Sat, 22 Mar 2025 23:12:50 +0530
Subject: [PATCH 04/10] remove small error

---
 man/groupingsets.Rd | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/man/groupingsets.Rd b/man/groupingsets.Rd
index 78a64f0abc..a6db5cc4af 100644
--- a/man/groupingsets.Rd
+++ b/man/groupingsets.Rd
@@ -96,11 +96,11 @@ groupingsets(DT, jj = quote(list(count = .N, total = sum(value))),
              sets = list("color", c("year","status"), character()), id = TRUE)      
 
 # Example showing when substitute() is necessary - in a wrapper function
-custom_grouping <- function(dt, multiplier = 1) {
+custom_grouping <- function(DT, multiplier = 1) {
   # Here substitute() is needed to capture 'multiplier' from function environment
-  groupingsets(dt, jj = substitute(list(count = .N, total = sum(value) * multiplier)),
-               by = c("color", "year", "status"),sets = list("color", c("year", "status"), character()),
-               id = TRUE)
+  groupingsets(DT, jj = substitute(list(count = .N, total = sum(value) * multiplier)),
+               by = c("color", "year", "status"),
+               sets = list("color", c("year", "status"), character()), id = TRUE)
 }
 # Using the wrapper function
 custom_grouping(DT, multiplier = 2)

From 05d166e317723a5716c616454a20dcd676bd2e58 Mon Sep 17 00:00:00 2001
From: Mukul Kumar <mukulrao6480@gmail.com>
Date: Sat, 22 Mar 2025 23:15:41 +0530
Subject: [PATCH 05/10] correct wrong comment

---
 man/groupingsets.Rd | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/man/groupingsets.Rd b/man/groupingsets.Rd
index a6db5cc4af..9c3b5b76fb 100644
--- a/man/groupingsets.Rd
+++ b/man/groupingsets.Rd
@@ -84,9 +84,9 @@ cube(DT, j = c(list(count=.N), lapply(.SD, sum)), by = c("color","year","status"
 groupingsets(DT, j = c(list(count=.N), lapply(.SD, sum)), by = c("color","year","status"),
              sets = list("color", c("year","status"), character()), id=TRUE)
 
-# Using jj argument(enables access to variables from outer environments)
+# Using jj argument with quote() for direct calls
 groupingsets(DT, jj = quote(c(list(count=.N), lapply(.SD, sum))), 
-             by = c("color","year","status"),  # Basic jj usage with substitute()
+             by = c("color","year","status"),  # Basic jj usage with quote()
              sets = list("color", c("year","status"), character()), id = TRUE, .SDcols = "value") 
 groupingsets(DT, jj = quote(lapply(.SD, sum)),
              by = c("color","year","status"),   # Simple aggregation with jj

From 57f5baf3ae188d4026df3f570d7f4be911063928 Mon Sep 17 00:00:00 2001
From: Mukul Kumar <mukulrao6480@gmail.com>
Date: Sun, 23 Mar 2025 01:53:52 +0530
Subject: [PATCH 06/10] pass a list of param equal value in substitute

---
 NEWS.md             | 2 +-
 man/groupingsets.Rd | 8 +++++---
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/NEWS.md b/NEWS.md
index d34745dca7..bc8a59e038 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -27,7 +27,7 @@
    + Argument `in.place` to `droplevels` has been removed.
    + It's now an error to set `datatable.nomatch`, which has been warning since 1.15.0.
 
-3. The groupingsets() documentation now explicitly addresses scoping issues when using variables from outer environments (e.g., function parameters) in aggregation expressions. Previously, using such variables in j could lead to "object not found" errors due to evaluation environment mismatches. This is resolved by using jj = substitute(expr) instead, which captures both the expression and its environment per R’s scoping rules (Chapter 6 of the R Language Definition manual). Three new examples demonstrate proper usage, including function parameter references (e.g., sum(value > threshold)) and multi-aggregation patterns.
+3. The groupingsets() documentation now explicitly addresses scoping issues when using variables from outer environments (e.g., function parameters) in aggregation expressions. Previously, using such variables in j could lead to "object not found" errors due to evaluation environment mismatches. This is resolved by using jj = substitute(expr, list(param=value)) instead, which creates an expression with values directly substituted at call time, following R's evaluation rules (Chapter 6 of the R Language Definition manual). Three new examples demonstrate proper usage, including function parameter references (e.g., sum(value > threshold)) and multi-aggregation patterns.
 
 # data.table [v1.17.0](https://github.com/Rdatatable/data.table/milestone/34)  (20 Feb 2025)
 
diff --git a/man/groupingsets.Rd b/man/groupingsets.Rd
index 9c3b5b76fb..e7492f8909 100644
--- a/man/groupingsets.Rd
+++ b/man/groupingsets.Rd
@@ -35,7 +35,7 @@ groupingsets(x, \dots)
 
     The \code{label = <scalar>} option provides a shorter alternative in the case where only one class of grouping variable requires a label. For example, \code{label = list(character = "Total")} can be shortened to \code{label = "Total"}. When this option is used, the label will be applied to all variables in \code{by} for which the first element of the class of the variable in \code{x} matches the first element of the class of the scalar.
 
-    The \code{jj} argument provides a mechanism to handle scoping issues when working with variables from outer environments. When using \code{j} directly with variables not defined in the columns of the data.table, R's evaluation rules may not find these variables, resulting in "object not found" errors. The \code{jj} argument accepts a quoted expression which can be created with either \code{quote()} (for direct calls referencing only columns and special symbols) or \code{substitute()} (when inside functions that need to capture parameters from the outer environment). This approach is particularly useful in functions where parameters need to be referenced within aggregation expressions. When \code{jj} is provided, the function will ignore any value specified in the \code{j} argument.    
+    The \code{jj} argument provides a mechanism to handle scoping issues when working with variables from outer environments. When using \code{j} directly with variables not defined in the columns of the data.table, R's evaluation rules may not find these variables, resulting in "object not found" errors. The \code{jj} argument accepts a quoted expression which can be created with either \code{quote()} (for direct calls referencing only columns and special symbols) or \code{substitute()} (when inside functions that need to capture parameters from the outer environment). For reliable behavior with function parameters, especially in nested environments, use the two-argument form \code{substitute(expr, list(param=value))} which binds values rather than symbols. This approach is particularly useful in functions where parameters need to be referenced within aggregation expressions. When \code{jj} is provided, the function will ignore any value specified in the \code{j} argument.
 }
 \value{
     A data.table with various aggregates.
@@ -96,9 +96,11 @@ groupingsets(DT, jj = quote(list(count = .N, total = sum(value))),
              sets = list("color", c("year","status"), character()), id = TRUE)      
 
 # Example showing when substitute() is necessary - in a wrapper function
+# Note: Use substitute(expr, list(param=value)) to bind values, not symbols
 custom_grouping <- function(DT, multiplier = 1) {
-  # Here substitute() is needed to capture 'multiplier' from function environment
-  groupingsets(DT, jj = substitute(list(count = .N, total = sum(value) * multiplier)),
+  # use substitute() with environment to ensure variable are properly evaluated
+  groupingsets(DT, jj = substitute(list(count = .N, total = sum(value) * multiplier),
+               list(multiplier = multiplier)),
                by = c("color", "year", "status"),
                sets = list("color", c("year", "status"), character()), id = TRUE)
 }

From 33b60cc4370e3fdfa5182d1889180dc008568a6f Mon Sep 17 00:00:00 2001
From: Mukul Kumar <mukulrao6480@gmail.com>
Date: Tue, 25 Mar 2025 08:47:09 +0530
Subject: [PATCH 07/10] correct last example by using symbol

---
 man/groupingsets.Rd | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/man/groupingsets.Rd b/man/groupingsets.Rd
index e7492f8909..6b1d80f9f9 100644
--- a/man/groupingsets.Rd
+++ b/man/groupingsets.Rd
@@ -97,14 +97,14 @@ groupingsets(DT, jj = quote(list(count = .N, total = sum(value))),
 
 # Example showing when substitute() is necessary - in a wrapper function
 # Note: Use substitute(expr, list(param=value)) to bind values, not symbols
-custom_grouping <- function(DT, multiplier = 1) {
+custom_grouping <- function(DT, multiplier) {
   # use substitute() with environment to ensure variable are properly evaluated
-  groupingsets(DT, jj = substitute(list(count = .N, total = sum(value) * multiplier),
-               list(multiplier = multiplier)),
+  groupingsets(DT, jj = substitute(list(count = .N, total = sum(value) * m),
+               list(m = multiplier)),
                by = c("color", "year", "status"),
                sets = list("color", c("year", "status"), character()), id = TRUE)
 }
 # Using the wrapper function
-custom_grouping(DT, multiplier = 2)
+local({ foo <- 3; custom_grouping(DT, multiplier = foo) })
 }
 \keyword{ data }

From d68b7fb22150458e2a186dc619c6e3027c5084c9 Mon Sep 17 00:00:00 2001
From: Mukul <145585624+Mukulyadav2004@users.noreply.github.com>
Date: Fri, 25 Apr 2025 23:07:16 +0530
Subject: [PATCH 08/10] Update groupingsets.Rd

---
 man/groupingsets.Rd | 15 +++++----------
 1 file changed, 5 insertions(+), 10 deletions(-)

diff --git a/man/groupingsets.Rd b/man/groupingsets.Rd
index 6b1d80f9f9..db915c3195 100644
--- a/man/groupingsets.Rd
+++ b/man/groupingsets.Rd
@@ -34,8 +34,6 @@ groupingsets(x, \dots)
     The \code{label} argument can be a named list of scalars, or a scalar, or \code{NULL}. When \code{label} is a list, each element name must be (1) a variable name in \code{by}, or (2) the first element of the class in the data.table \code{x} of a variable in \code{by}, or (3) one of 'character', 'integer', 'numeric', 'factor', 'Date', 'IDate'. The order of the list elements is not important. A label specified by variable name will apply only to that variable, while a label specified by first element of a class will apply to all variables in \code{by} for which the first element of the class of the variable in \code{x} matches the \code{label} element name, except for variables that have a label specified by variable name (that is, specification by variable name takes precedence over specification by class). For \code{label} elements with name in \code{by}, the class of the label value must be the same as the class of the variable in \code{x}. For \code{label} elements with name not in \code{by}, the first element of the class of the label value must be the same as the \code{label} element name. For example, \code{label = list(integer = 999, IDate = as.Date("3000-01-01"))} would produce an error because \code{class(999)[1]} is not \code{"integer"} and \code{class(as.Date("3000-01-01"))[1]} is not \code{"IDate"}. A corrected specification would be \code{label = list(integer = 999L, IDate = as.IDate("3000-01-01"))}.
 
     The \code{label = <scalar>} option provides a shorter alternative in the case where only one class of grouping variable requires a label. For example, \code{label = list(character = "Total")} can be shortened to \code{label = "Total"}. When this option is used, the label will be applied to all variables in \code{by} for which the first element of the class of the variable in \code{x} matches the first element of the class of the scalar.
-
-    The \code{jj} argument provides a mechanism to handle scoping issues when working with variables from outer environments. When using \code{j} directly with variables not defined in the columns of the data.table, R's evaluation rules may not find these variables, resulting in "object not found" errors. The \code{jj} argument accepts a quoted expression which can be created with either \code{quote()} (for direct calls referencing only columns and special symbols) or \code{substitute()} (when inside functions that need to capture parameters from the outer environment). For reliable behavior with function parameters, especially in nested environments, use the two-argument form \code{substitute(expr, list(param=value))} which binds values rather than symbols. This approach is particularly useful in functions where parameters need to be referenced within aggregation expressions. When \code{jj} is provided, the function will ignore any value specified in the \code{j} argument.
 }
 \value{
     A data.table with various aggregates.
@@ -95,16 +93,13 @@ groupingsets(DT, jj = quote(list(count = .N, total = sum(value))),
              by = c("color","year","status"), # jj with direct column references         
              sets = list("color", c("year","status"), character()), id = TRUE)      
 
-# Example showing when substitute() is necessary - in a wrapper function
-# Note: Use substitute(expr, list(param=value)) to bind values, not symbols
-custom_grouping <- function(DT, multiplier) {
-  # use substitute() with environment to ensure variable are properly evaluated
-  groupingsets(DT, jj = substitute(list(count = .N, total = sum(value) * m),
-               list(m = multiplier)),
+# Wrapper around groupingsets demonstrating NSE with user-defined j expression
+mygrp = function(x, j) {
+  groupingsets(x, jj = substitute(j),
                by = c("color", "year", "status"),
                sets = list("color", c("year", "status"), character()), id = TRUE)
 }
-# Using the wrapper function
-local({ foo <- 3; custom_grouping(DT, multiplier = foo) })
+multiplier = 2
+mygrp(DT, .(count = .N, total = sum(value) * multiplier))
 }
 \keyword{ data }

From 6b0905f4b2af0e2a97b6429028b69d544b0f91ff Mon Sep 17 00:00:00 2001
From: Mukul <145585624+Mukulyadav2004@users.noreply.github.com>
Date: Thu, 1 May 2025 13:51:56 +0530
Subject: [PATCH 09/10] Update groupingsets.Rd

---
 man/groupingsets.Rd | 13 +------------
 1 file changed, 1 insertion(+), 12 deletions(-)

diff --git a/man/groupingsets.Rd b/man/groupingsets.Rd
index db915c3195..1165dd27bb 100644
--- a/man/groupingsets.Rd
+++ b/man/groupingsets.Rd
@@ -82,18 +82,7 @@ cube(DT, j = c(list(count=.N), lapply(.SD, sum)), by = c("color","year","status"
 groupingsets(DT, j = c(list(count=.N), lapply(.SD, sum)), by = c("color","year","status"),
              sets = list("color", c("year","status"), character()), id=TRUE)
 
-# Using jj argument with quote() for direct calls
-groupingsets(DT, jj = quote(c(list(count=.N), lapply(.SD, sum))), 
-             by = c("color","year","status"),  # Basic jj usage with quote()
-             sets = list("color", c("year","status"), character()), id = TRUE, .SDcols = "value") 
-groupingsets(DT, jj = quote(lapply(.SD, sum)),
-             by = c("color","year","status"),   # Simple aggregation with jj
-             sets = list("color", c("year","status"), character()), id = TRUE, .SDcols = "value")
-groupingsets(DT, jj = quote(list(count = .N, total = sum(value))),
-             by = c("color","year","status"), # jj with direct column references         
-             sets = list("color", c("year","status"), character()), id = TRUE)      
-
-# Wrapper around groupingsets demonstrating NSE with user-defined j expression
+# Using jj in a wrapper to handle outer variables
 mygrp = function(x, j) {
   groupingsets(x, jj = substitute(j),
                by = c("color", "year", "status"),

From 893b76a38c9d0aedea06f50e6dcbc02404722629 Mon Sep 17 00:00:00 2001
From: Mukul <145585624+Mukulyadav2004@users.noreply.github.com>
Date: Thu, 1 May 2025 13:53:05 +0530
Subject: [PATCH 10/10] Update NEWS.md

---
 NEWS.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/NEWS.md b/NEWS.md
index bc8a59e038..60aed036d8 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -27,7 +27,7 @@
    + Argument `in.place` to `droplevels` has been removed.
    + It's now an error to set `datatable.nomatch`, which has been warning since 1.15.0.
 
-3. The groupingsets() documentation now explicitly addresses scoping issues when using variables from outer environments (e.g., function parameters) in aggregation expressions. Previously, using such variables in j could lead to "object not found" errors due to evaluation environment mismatches. This is resolved by using jj = substitute(expr, list(param=value)) instead, which creates an expression with values directly substituted at call time, following R's evaluation rules (Chapter 6 of the R Language Definition manual). Three new examples demonstrate proper usage, including function parameter references (e.g., sum(value > threshold)) and multi-aggregation patterns.
+3. The `groupingsets()` documentation clarifies how to handle scoping issues when using variables from outer environments (e.g., function parameters) in aggregation expressions, [#5560](https://github.com/Rdatatable/data.table/issues/5560). Previously, using such variables in `j` could lead to "object not found" errors. This is resolved by creating a wrapper function that accepts a `j` argument and forwards it using `jj = substitute(j)`, which correctly captures the expression and its environment. A new example demonstrates this proper usage pattern.
 
 # data.table [v1.17.0](https://github.com/Rdatatable/data.table/milestone/34)  (20 Feb 2025)
 
