jay.sf
jay.sf

Reputation: 72683

How to indent output of multiline-outputs such as summary, using print or cat, and keep columns aligned?

The question probably generalizes to printing any type of multiline output, and keeping the columns aligned. My actual issue concerns the output of summary.default.

I'm trying to add indentation of a multiple of two to cat output of the summary.default function. I've made some attempts using cat and print, also consulting some related answers, but they all fail so far.

f <- function(x) {
  s <- summary.default(x)
  liner <- Reduce(paste0, rep("-", 70))
  cat("\n", liner, "\n")  ## ATT 1. -------------------------------
  cat("\n", "foo first attempt", "\n")
  cat("\n--|\n")
  
  print(round(s, 3))  #
  
  cat("\n----|\n")
  cat("\n    *additional information\n")
  cat("\n", liner, "\n")  ## ATT 2. -------------------------------
  cat("\n", "foo second attempt", "\n")
  cat("\n--|\n")
  
  print(unname(as.data.frame(cbind("   ", t(attr(s, "names"))))), row.names=F)  #
  print(unname(as.data.frame(cbind(" ", t(round(unclass(s), 3))))), row.names=F)  #
  
  cat("\n----|\n")
  cat("\n    *additional information\n")
  cat("\n", liner, "\n")  ## ATT 3. -------------------------------
  cat("\n", "foo third attempt", "\n")
  cat("\n--|\n")
  cat("\n  ", attr(s, "names"), "\n")
  
  cat("\n  ", round(s, 3), "\n")  #
  
  cat("\n----|\n")
  cat("\n    *additional information\n")
}

> x <- rnorm(100)
> f(x)

---------------------------------------------------------------------- 

 foo first attempt 

--|
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 -2.069  -0.654  -0.092  -0.075   0.696   1.997 

----|

    *additional information

 ---------------------------------------------------------------------- 

 foo second attempt 

--|
                                          
     Min. 1st Qu. Median Mean 3rd Qu. Max.
                                          
   -2.069 -0.654 -0.092 -0.075 0.696 1.997

----|

    *additional information

 ---------------------------------------------------------------------- 

 foo third attempt 

--|

   Min. 1st Qu. Median Mean 3rd Qu. Max. 

   -2.069 -0.654 -0.092 -0.075 0.696 1.997 

----|

    *additional information

The ---| symbolize the required indentation. First attempt does not have any indentation. Second attempt is better, but there's additional space between the lines, and does not generalize when rounding to different digits. At attempt 3 the columns are no longer aligned.

How can I get the desired output using features that come with R?

Desired output:

---------------------------------------------------------------------- 
  
foo some text 

    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  -2.069  -0.654  -0.092  -0.075   0.696   1.997 
      
    *additional information

Upvotes: 3

Views: 693

Answers (2)

thomas
thomas

Reputation: 449

For this task you can use two function of base r consecutively. There is the function capture.output which captures the output of a command like print. Using paste to combine the desired number of spaces, it can be written to stdout with writelines, which preserves the line structure of the dataframe printout. To do the above a combined all your information given in the printout as an example to a data.frame (df).

df = data.frame("Min."=-2.069, "First Qu."= -0.654,  "Median"= -0.092   , "Mean"  =-0.075, "Third Qu."=  0.696,    "Max."=  1.997 )
writeLines(paste0("  ", capture.output(print(df, row.names = F))))

I added roe.names = F, to suppress printing the row numbers of the dataframe

Upvotes: 2

dario
dario

Reputation: 6485

The only thing I could come up with was to manually re-assemble the 'summary' output to account for the different 'cell' widths:

add_indent_and_right_align <- function(summry, indent=2, minDist= 2, rounding = 3) {
  # add an indent and then right-align summary output table
  #
  # summry - summary object
  # indent - integer Number of spaces to add before summary output
  # minDist . integer Minimal number of spaces between two columns
  # rounding - integer Number of decimals to round summary to

  header <- attr(summry, "names")
  value <- round(summry, 3)     
  r = list()
  for (i in seq_along(header)) {
    # find out how wide the column has to be
    # to fit header and value and indent (if first column) or minDist
    #
    max_len <- max(nchar(header[i]), nchar(value[i]))       
    if (i == 1) {
      cell_width <- max_len + indent
      r[1] <- paste0(paste0(rep(" ", cell_width - nchar(header[i])), collapse=""), header[i])
      r[2] <- paste0(paste0(rep(" ", cell_width - nchar(value[i])), collapse=""), value[i])
    } else {
      cell_width <- max_len + minDist
      r[1] <- paste0(r[1], paste0(rep(" ", cell_width - nchar(header[i])), collapse=""), header[i])
      r[2] <- paste0(r[2], paste0(rep(" ", cell_width - nchar(value[i])), collapse=""), value[i])
    }
  }
  return(paste0(r, collapse="\n"))
}

f <- function(x) {
  indent <- 4 # how many spaces shoud the whole summary output be indented?
  s <- summary.default(x)

  cat(paste0(rep("-", 70), collapse=""), "\n")
  cat("\n")
  cat("foo some text", "\n")
  cat("\n")
  cat(add_indent_and_right_align(summry=s, indent=4, minDist= 1, rounding = 3), "\n")
  cat("\n")
  cat(paste0(paste0(rep(" ", indent), collapse=""), "*additional information"), "\n")
  cat("\n")
}

set.seed(1)
x <- rnorm(100)
f(x)

Returns:

---------------------------------------------------------------------- 

foo some text

    Min. 1st Qu. Median  Mean 3rd Qu.  Max.
  -2.215  -0.494  0.114 0.109   0.692 2.402

  *additional information

Upvotes: 2

Related Questions