Skip to content
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions R/onLoad.R
Original file line number Diff line number Diff line change
Expand Up @@ -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.<argument name>
datatable.join.many=TRUE, # mergelist, [.data.table #4383 #914
datatable.dfdispatchwarn=TRUE, # not a function argument
Expand Down
42 changes: 33 additions & 9 deletions R/print.data.table.R
Original file line number Diff line number Diff line change
Expand Up @@ -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, ...) {
Expand Down Expand Up @@ -43,6 +44,7 @@ print.data.table = function(x, topn=getOption("datatable.print.topn"),
if (!is.numeric(topn)) topn = 5L
topnmiss = missing(topn)
topn = max(as.integer(topn),1L)
show_trunc_message = isTRUE(trunc.cols)
if (print.keys) {
if (!is.null(ky <- key(x)))
catf("Key: <%s>\n", toString(ky))
Expand All @@ -52,6 +54,9 @@ print.data.table = function(x, topn=getOption("datatable.print.topn"),
paste0("<", ixs, ">", collapse = ", ")
))
}
if (show.ncols && !isTRUE(trunc.cols) && !any(dim(x)==0L)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move this below the any(dim(x)==0L) branch which comes next and drop the !any(dim(x)==0L) guard?

trunc_cols_message(character(0), NULL, FALSE, "none", ncol=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)) {
Expand Down Expand Up @@ -121,8 +126,13 @@ 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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The column count seems to be wrong when using show.indices=TRUE

DT = data.table(a=1:3, b=1:3, c=1:3, d=1:3, e=1:3)
setindex(DT, a)
options(width=20, datatable.show.ncols=TRUE)
print(DT, trunc.cols=TRUE, show.indices=TRUE)
# Index: <a>
# Number of columns: 5, of which 4 are not shown: [c <int>, d <int>, e <int>, index:a <int>]
#        a     b
#    <int> <int>
# 1:     1     1
# 2:     2     2
# 3:     3     3

trunc_cols_message(not_printed, abbs, class, col.names, ncol=ncol(x))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be even as simple to exchange ncol(x) with ncol(toprint)

show_trunc_message = FALSE
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}

wrong indentation


if (!any(cols_to_print)) {
trunc_cols_message(not_printed, abbs, class, col.names)
if (show_trunc_message) trunc_cols_message(not_printed, abbs, class, col.names)
return(invisible(x))
}
# When nrow(toprint) = 1, attributes get lost in the subset,
Expand All @@ -134,7 +144,7 @@ print.data.table = function(x, topn=getOption("datatable.print.topn"),
if (col.names != "none") cut_colnames = identity
cut_colnames(print(x, right=TRUE, quote=quote, na.print=na.print))
# prints names of variables not shown in the print
if (trunc.cols) trunc_cols_message(not_printed, abbs, class, col.names)
if (show_trunc_message) trunc_cols_message(not_printed, abbs, class, col.names)
}
if (printdots) {
if (isFALSE(row.names)) {
Expand Down Expand Up @@ -291,14 +301,28 @@ toprint_subset = function(x, cols_to_print) {
}
}
# message for when trunc.cols=TRUE and some columns are not printed
trunc_cols_message = function(not_printed, abbs, class, col.names){
trunc_cols_message = function(not_printed, abbs, class, col.names, ncol=NULL){
n = length(not_printed)
if (class && col.names != "none") classes = paste0(" ", tail(abbs, n)) else classes = ""
catf(
ngettext(n, "%d variable not shown: %s\n", "%d variables not shown: %s\n"),
n, brackify(paste0(not_printed, classes)),
domain=NA
)
if (is.null(ncol)) {
if (n == 0L) return()
if (class && col.names != "none") classes = paste0(" ", tail(abbs, n)) else classes = ""
catf(
ngettext(n, "%d variable not shown: %s\n", "%d variables not shown: %s\n"),
n, brackify(paste0(not_printed, classes)),
domain=NA
)
} else {
if (n > 0L) {
if (class && col.names != "none") classes = paste0(" ", tail(abbs, n)) else classes = ""
catf(
ngettext(n, "Number of columns: %d, of which %d is not shown: %s\n", "Number of columns: %d, of which %d are not shown: %s\n"),
ncol, n, brackify(paste0(not_printed, classes)),
domain=NA
)
} else {
catf("Number of columns: %d\n", ncol)
}
}
}

# Maybe add a method for repr::repr_text. See https://github.com/Rdatatable/data.table/issues/933#issuecomment-220237965
Expand Down
10 changes: 10 additions & 0 deletions inst/tests/tests.Rraw
Original file line number Diff line number Diff line change
Expand Up @@ -21775,3 +21775,13 @@ test(2377.44, copy(dt)[!(a < 5 & b != "d"), .ROW := NULL], dt[1:3])
dt = data.table(a=1:3)
test(2377.91, truelength(dt$a), 0L)
test(2377.92, {setallocrow(dt); truelength(dt$a)}, 3L)

#6663 Option to print the number of columns
test(2378.1, capture.output(print(as.data.table(iris)))[1L], "Number of columns: 5", options=list(datatable.show.ncols=TRUE))
test(2378.2, any(grepl("^Number of columns: 5$", capture.output(print(setkey(as.data.table(iris), Sepal.Length))))), TRUE, options=list(datatable.show.ncols=TRUE))
test(2378.3, any(grepl("^[0-9]+ variables? not shown", capture.output(print(data.table(a=1:3, b=1:3, c=1:3, d=1:3, e=1:3), trunc.cols=TRUE)))), FALSE, options=list(width=20, datatable.show.ncols=TRUE))
test(2378.4, capture.output(print(as.data.table(iris), trunc.cols=TRUE))[1L], "Number of columns: 5, of which 5 are not shown: [Sepal.Length, Sepal.Width, Petal.Length, Petal.Width, Species]", options=list(width=10, datatable.show.ncols=TRUE))
test(2378.5, any(grepl("^Number of columns:", capture.output(print(as.data.table(iris))))), FALSE, options=list(datatable.show.ncols=FALSE))
test(2378.6, capture.output(print(as.data.table(iris), trunc.cols=TRUE, class=TRUE))[1L], "Number of columns: 5, of which 5 are not shown: [Sepal.Length <num>, Sepal.Width <num>, Petal.Length <num>, Petal.Width <num>, Species <fctr>]", options=list(width=10, datatable.show.ncols=TRUE))
test(2378.7, { dt = data.table(matrix(1:200, nrow=25)); out = capture.output(print(dt, trunc.cols=TRUE, class=TRUE)); grepl("<int>", out[length(out)]) }, TRUE, options=list(width=30, datatable.show.ncols=TRUE))
test(2378.8, any(grepl("variable", capture.output(print(data.table(a=1), trunc.cols=TRUE)))), FALSE, options=list(width=80, datatable.show.ncols=FALSE))
1 change: 1 addition & 0 deletions man/data.table-options.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions man/print.data.table.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -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", FALSE), # default: FALSE
quote=FALSE,
na.print=NULL,
timezone=FALSE, \dots)
Expand All @@ -44,6 +45,7 @@
\item{trunc.char}{The number of characters at which character columns and list-column summaries are truncated. If \code{NULL} (the default), it is dynamically calculated based on \code{getOption("width")}.}
\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}). }
Expand Down
Loading