From e7e92b1b867e56cb961bdc7f9fafcfebf20e12ae Mon Sep 17 00:00:00 2001 From: venom1204 Date: Mon, 15 Jun 2026 21:24:28 +0000 Subject: [PATCH 1/8] added changes --- R/onLoad.R | 1 + R/print.data.table.R | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/R/onLoad.R b/R/onLoad.R index 46a9826fe4..ec71252b75 100644 --- a/R/onLoad.R +++ b/R/onLoad.R @@ -90,6 +90,7 @@ datatable.print.keys=TRUE, # for print.data.table datatable.print.trunc.cols=FALSE, # for print.data.table datatable.show.indices=FALSE, # for print.data.table + datatable.show.ncols=FALSE, # for print.data.table datatable.allow.cartesian=FALSE, # datatable. datatable.join.many=TRUE, # mergelist, [.data.table #4383 #914 datatable.dfdispatchwarn=TRUE, # not a function argument diff --git a/R/print.data.table.R b/R/print.data.table.R index 88ff4ea505..5a025638d3 100644 --- a/R/print.data.table.R +++ b/R/print.data.table.R @@ -8,6 +8,7 @@ print.data.table = function(x, topn=getOption("datatable.print.topn"), print.keys=getOption("datatable.print.keys"), trunc.cols=getOption("datatable.print.trunc.cols"), show.indices=getOption("datatable.show.indices"), + show.ncols=getOption("datatable.show.ncols", FALSE), quote=FALSE, na.print=NULL, timezone=FALSE, ...) { @@ -52,6 +53,9 @@ print.data.table = function(x, topn=getOption("datatable.print.topn"), paste0("<", ixs, ">", collapse = ", ") )) } + if (show.ncols && !isTRUE(trunc.cols) && !any(dim(x)==0L)) { + catf("Number of columns: %d\n", ncol(x)) + } if (any(dim(x)==0L)) { x_class = if (is.data.table(x)) "data.table" else "data.frame" # a data.frame could be passed to print.data.table() directly, #3363 if (all(dim(x)==0L)) { @@ -116,6 +120,20 @@ print.data.table = function(x, topn=getOption("datatable.print.topn"), cons_width = getOption("width") cols_to_print = widths < cons_width not_printed = colnames(toprint)[!cols_to_print] + + if (show.ncols) { + n_not_printed = length(not_printed) + if (n_not_printed > 0L) { + if (class && col.names != "none") classes = paste0(" ", tail(abbs, n_not_printed)) else classes = "" + catf("Number of columns: %d, of which %d %s not shown: %s\n", + ncol(x), n_not_printed, ngettext(n_not_printed, "is", "are"), + brackify(paste0(not_printed, classes))) + trunc.cols = FALSE # message already printed in header + } else { + catf("Number of columns: %d\n", ncol(x)) + } + } + if (!any(cols_to_print)) { trunc_cols_message(not_printed, abbs, class, col.names) return(invisible(x)) @@ -123,7 +141,7 @@ print.data.table = function(x, topn=getOption("datatable.print.topn"), # When nrow(toprint) = 1, attributes get lost in the subset, # function below adds those back when necessary toprint = toprint_subset(toprint, cols_to_print) - trunc.cols = length(not_printed) > 0L + if (!show.ncols) trunc.cols = length(not_printed) > 0L else trunc.cols = FALSE } print_default = function(x) { if (col.names != "none") cut_colnames = identity From 021d6e21e560180cb7418bedf1dcf83d711e1935 Mon Sep 17 00:00:00 2001 From: venom1204 Date: Tue, 16 Jun 2026 15:08:01 +0000 Subject: [PATCH 2/8] .. --- R/print.data.table.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/print.data.table.R b/R/print.data.table.R index 5a025638d3..ace6debe2e 100644 --- a/R/print.data.table.R +++ b/R/print.data.table.R @@ -135,13 +135,13 @@ print.data.table = function(x, topn=getOption("datatable.print.topn"), } if (!any(cols_to_print)) { - trunc_cols_message(not_printed, abbs, class, col.names) + if (!show.ncols) trunc_cols_message(not_printed, abbs, class, col.names) return(invisible(x)) } # When nrow(toprint) = 1, attributes get lost in the subset, # function below adds those back when necessary toprint = toprint_subset(toprint, cols_to_print) - if (!show.ncols) trunc.cols = length(not_printed) > 0L else trunc.cols = FALSE + trunc.cols = !show.ncols && length(not_printed) > 0L } print_default = function(x) { if (col.names != "none") cut_colnames = identity From 864954e9fce71e18eb5883586675e791ede44400 Mon Sep 17 00:00:00 2001 From: venom1204 Date: Tue, 16 Jun 2026 15:28:43 +0000 Subject: [PATCH 3/8] added test --- inst/tests/tests.Rraw | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 687bf929ed..c030cac706 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -21660,3 +21660,10 @@ test(2374.08, key(DT[, .(a, a)]), NULL) test(2374.09, key(subset(DT, select=c(a, a))), NULL) DT = data.table(a=1:2, a.1=3:4, val=10:11) test(2374.10, key(DT[, .(a.1, sum(val)), keyby=.(a, a)]), NULL) + +#6663 Option to print the number of columns +test(2375.1, {options(datatable.show.ncols=TRUE); DT=as.data.table(iris); capture.output(print(DT))[1L]}, "Number of columns: 5") +test(2375.2, {options(datatable.show.ncols=TRUE); DT=as.data.table(iris); setkey(DT, Sepal.Length); any(grepl("^Number of columns: 5$", capture.output(print(DT))))}, TRUE) +test(2375.3, {options(width=20); options(datatable.show.ncols=TRUE); DT=data.table(a=1:3,b=1:3,c=1:3,d=1:3,e=1:3); any(grepl("^5 variables not shown", capture.output(print(DT, trunc.cols=TRUE))))}, FALSE) +test(2375.4, {options(width=10); options(datatable.show.ncols=TRUE); DT=as.data.table(iris); capture.output(print(DT, trunc.cols=TRUE))[1L]}, "Number of columns: 5, of which 5 are not shown: [Sepal.Length, Sepal.Width, Petal.Length, Petal.Width, Species]") +test(2375.5, {options(datatable.show.ncols=FALSE); DT=as.data.table(iris); any(grepl("^Number of columns:", capture.output(print(DT))))}, FALSE) From 1850b3920d8fba0bebb5b1752830f5c26ed48f27 Mon Sep 17 00:00:00 2001 From: venom1204 Date: Tue, 16 Jun 2026 15:34:19 +0000 Subject: [PATCH 4/8] added doc --- R/print.data.table.R | 2 +- man/data.table-options.Rd | 1 + man/print.data.table.Rd | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/R/print.data.table.R b/R/print.data.table.R index ace6debe2e..1dc0e6aed5 100644 --- a/R/print.data.table.R +++ b/R/print.data.table.R @@ -128,7 +128,7 @@ print.data.table = function(x, topn=getOption("datatable.print.topn"), catf("Number of columns: %d, of which %d %s not shown: %s\n", ncol(x), n_not_printed, ngettext(n_not_printed, "is", "are"), brackify(paste0(not_printed, classes))) - trunc.cols = FALSE # message already printed in header + trunc.cols = FALSE } else { catf("Number of columns: %d\n", ncol(x)) } diff --git a/man/data.table-options.Rd b/man/data.table-options.Rd index 439e88ef2f..90ed4e5f04 100644 --- a/man/data.table-options.Rd +++ b/man/data.table-options.Rd @@ -33,6 +33,7 @@ \item{\code{datatable.print.keys}}{A logical, default \code{FALSE}. If \code{TRUE}, the table's keys are printed above the data.} \item{\code{datatable.show.indices}}{A logical, default \code{TRUE}. A synonym for \code{datatable.print.keys} for historical reasons.} + \item{\code{datatable.show.ncols}}{A logical, default \code{FALSE}. If \code{TRUE}, the number of columns is printed in the header.} \item{\code{datatable.print.trunc.cols}}{A logical, default \code{FALSE}. If \code{TRUE} and a table has more columns than fit on the screen, it truncates the middle columns.} \item{\code{datatable.prettyprint.char}}{An integer, default \code{100L}. The maximum number of diff --git a/man/print.data.table.Rd b/man/print.data.table.Rd index 7d51a55fc1..4c21a25db0 100644 --- a/man/print.data.table.Rd +++ b/man/print.data.table.Rd @@ -25,6 +25,7 @@ print.keys=getOption("datatable.print.keys"), # default: TRUE trunc.cols=getOption("datatable.print.trunc.cols"), # default: FALSE show.indices=getOption("datatable.show.indices"), # default: FALSE + show.ncols=getOption("datatable.show.ncols"), # default: FALSE quote=FALSE, na.print=NULL, timezone=FALSE, \dots) @@ -43,6 +44,7 @@ \item{nrows}{ The number of rows which will be printed before truncation is enforced. } \item{class}{ If \code{TRUE}, the resulting output will include above each column its storage class (or a self-evident abbreviation thereof). When combined with \code{col.names="auto"} and tables >20 rows, classes will also appear at the bottom.} \item{row.names}{ If \code{TRUE}, row indices will be printed alongside \code{x}. } + \item{show.ncols}{ If \code{TRUE}, the number of columns is printed in the header. } \item{col.names}{ One of three flavours for controlling the display of column names in output. \code{"auto"} includes column names above the data, as well as below the table if \code{nrow(x) > 20} (when \code{class=TRUE}, column classes will also appear at the bottom). \code{"top"} excludes this lower register when applicable, and \code{"none"} suppresses column names altogether (as well as column classes if \code{class = TRUE}. } \item{print.keys}{ If \code{TRUE}, any \code{\link{key}} and/or \code{\link[=indices]{index}} currently assigned to \code{x} will be printed prior to the preview of the data. } \item{trunc.cols}{ If \code{TRUE}, only the columns that can be printed in the console without wrapping the columns to new lines will be printed (similar to \code{tibbles}). } From 78621ebd84482cb1a8ea9ae51629d19e502eb184 Mon Sep 17 00:00:00 2001 From: venom1204 Date: Tue, 16 Jun 2026 15:49:03 +0000 Subject: [PATCH 5/8] .. --- man/print.data.table.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/print.data.table.Rd b/man/print.data.table.Rd index 4c21a25db0..28334ab64f 100644 --- a/man/print.data.table.Rd +++ b/man/print.data.table.Rd @@ -25,7 +25,7 @@ print.keys=getOption("datatable.print.keys"), # default: TRUE trunc.cols=getOption("datatable.print.trunc.cols"), # default: FALSE show.indices=getOption("datatable.show.indices"), # default: FALSE - show.ncols=getOption("datatable.show.ncols"), # default: FALSE + show.ncols=getOption("datatable.show.ncols", FALSE), # default: FALSE quote=FALSE, na.print=NULL, timezone=FALSE, \dots) From 2aec2a5f42dd7488460e12909b1e9bf5f8c12a5d Mon Sep 17 00:00:00 2001 From: venom1204 Date: Tue, 16 Jun 2026 16:00:43 +0000 Subject: [PATCH 6/8] .. --- inst/tests/tests.Rraw | 1 + 1 file changed, 1 insertion(+) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index c030cac706..a31f44c0dc 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -21667,3 +21667,4 @@ test(2375.2, {options(datatable.show.ncols=TRUE); DT=as.data.table(iris); setkey test(2375.3, {options(width=20); options(datatable.show.ncols=TRUE); DT=data.table(a=1:3,b=1:3,c=1:3,d=1:3,e=1:3); any(grepl("^5 variables not shown", capture.output(print(DT, trunc.cols=TRUE))))}, FALSE) test(2375.4, {options(width=10); options(datatable.show.ncols=TRUE); DT=as.data.table(iris); capture.output(print(DT, trunc.cols=TRUE))[1L]}, "Number of columns: 5, of which 5 are not shown: [Sepal.Length, Sepal.Width, Petal.Length, Petal.Width, Species]") test(2375.5, {options(datatable.show.ncols=FALSE); DT=as.data.table(iris); any(grepl("^Number of columns:", capture.output(print(DT))))}, FALSE) +test(2375.6, {options(width=10); options(datatable.show.ncols=TRUE); DT=as.data.table(iris); capture.output(print(DT, trunc.cols=TRUE, class=TRUE))[1L]}, "Number of columns: 5, of which 5 are not shown: [Sepal.Length , Sepal.Width , Petal.Length , Petal.Width , Species ]") From 3e58a320d2a9ee98489ab3e8b65a92f7ad85830a Mon Sep 17 00:00:00 2001 From: venom1204 Date: Tue, 16 Jun 2026 18:55:55 +0000 Subject: [PATCH 7/8] removed empty lines --- R/print.data.table.R | 2 -- 1 file changed, 2 deletions(-) diff --git a/R/print.data.table.R b/R/print.data.table.R index 1dc0e6aed5..beac4cfac9 100644 --- a/R/print.data.table.R +++ b/R/print.data.table.R @@ -120,7 +120,6 @@ print.data.table = function(x, topn=getOption("datatable.print.topn"), cons_width = getOption("width") cols_to_print = widths < cons_width not_printed = colnames(toprint)[!cols_to_print] - if (show.ncols) { n_not_printed = length(not_printed) if (n_not_printed > 0L) { @@ -133,7 +132,6 @@ print.data.table = function(x, topn=getOption("datatable.print.topn"), catf("Number of columns: %d\n", ncol(x)) } } - if (!any(cols_to_print)) { if (!show.ncols) trunc_cols_message(not_printed, abbs, class, col.names) return(invisible(x)) From 43dddf29d6ac03973be9d5d5e2bf59a1491aac09 Mon Sep 17 00:00:00 2001 From: venom1204 Date: Tue, 16 Jun 2026 19:00:10 +0000 Subject: [PATCH 8/8] added news --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 45a36dcf0c..10d3bc41c3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -32,6 +32,8 @@ 6. `yearqtr()` and `yearmon()` now gain an optional format specifier [#7694](https://github.com/Rdatatable/data.table/issues/7694). 'numeric' is the default, which preserves the original behavior, but 'character' formats `yearqtr()` as YYYYQ# (e.g. 2025Q2) and `yearmon()` as YYYYM## (e.g. 2025M02, 2025M10). Thanks to @jan-swissre for the report and @LunaticSage218 for the implementation. +7. `print.data.table()` gains a `show.ncols` argument and `datatable.show.ncols` option to print the number of columns (labeled as `ncol:`) in the header, [#6663](https://github.com/Rdatatable/data.table/issues/6663). When `trunc.cols=TRUE`, this information is merged into the truncation message at the top, and the redundant footer message is suppressed. Thanks to @KyleHaynes for the suggestion and @venom1204 for the implementation. + ### BUG FIXES 1. `fread()` with `skip=0` and `(header=TRUE|FALSE)` no longer skips the first row when it has fewer fields than subsequent rows, [#7463](https://github.com/Rdatatable/data.table/issues/7463). Thanks @emayerhofer for the report and @ben-schwen for the fix.