Code for Lazy People: function to align equal signs
Published:
In this Code for Lazy People blog series, I share coding tools that make my daily work more efficient — because, honestly, I prefer to work smarter, not harder. Like many researchers, I often deal with repetitive tasks, and instead of doing them manually, I use code to automate them. Over time, I have built a collection of such tools that help me save time and effort. That’s one of the things I love about coding: once you create a solution, you can recycle it for future projects instead of reinventing the wheel every time.
List of values, parameters or function inputs
It is common to write a long list of values to add labels to variables or to write input parameters for a function.
Example 1: list of values to add labels
library(labelled)
library(gtsummary)
# add labels to variables in the built-in 'Titanic' data
data_titanic <- data.frame(datasets::Titanic)
## before labels
gtsummary::tbl_summary(data_titanic)
Characteristic | N = 321 |
---|---|
Class | |
1st | 8 (25%) |
2nd | 8 (25%) |
3rd | 8 (25%) |
Crew | 8 (25%) |
Sex | |
Male | 16 (50%) |
Female | 16 (50%) |
Age | |
Child | 16 (50%) |
Adult | 16 (50%) |
Survived | 16 (50%) |
Freq | 14 (1, 78) |
1 n (%); Median (Q1, Q3) |
labelled::var_label(data_titanic) <-
list(
Class="Passenger class", Sex="Sex of passenger",
Age="Age group", Survived="Survival status", Freq="Passenger count"
)
## after labels
gtsummary::tbl_summary(data_titanic)
Characteristic | N = 321 |
---|---|
Passenger class | |
1st | 8 (25%) |
2nd | 8 (25%) |
3rd | 8 (25%) |
Crew | 8 (25%) |
Sex of passenger | |
Male | 16 (50%) |
Female | 16 (50%) |
Age group | |
Child | 16 (50%) |
Adult | 16 (50%) |
Survival status | 16 (50%) |
Passenger count | 14 (1, 78) |
1 n (%); Median (Q1, Q3) |
In the example above, there are only a few variables. However, such list can get messy quickly when there are 10, 20 or even more variables. Accordingly, I think it is nice to have a ‘vertical’ list to add labels such as:
# vertical list for labels
labelled::var_label(data_titanic) <-
list(
Class="Passenger class",
Sex="Sex of passenger",
Age="Age group",
Survived="Survival status",
Freq="Passenger count"
)
And, of course, it would be even nicer to have a vertical list with all equal signs =
nicely aligned, such as:
# vertical list for labels
labelled::var_label(data_titanic) <-
list(
Class = "Passenger class",
Sex = "Sex of passenger",
Age = "Age group",
Survived = "Survival status",
Freq = "Passenger count"
)
Unfortunately, the process of manually adding the right number of spaces to line up every equal sign of every variable names is tedious.
Example 2: list of input parameters for a function
In a similar fashion, I find that functions are easier to read and verify when their input parameters are aligned vertically.
# Hypothetical function with many input parameters
run_model_analysis <- function(y, Xmat, method = "glm", tol = 1e-6, penalization = "none",
lambda = NULL, family = "gaussian", verbose = TRUE) {return(NULL)}
# Might be harder to read:
output <-
run_model_analysis(y,Xmat, method = "glm", tol = 1e-6, penalization = "none",
lambda = NULL, family = "gaussian", verbose = TRUE)
# Might be easier to read:
output <-
run_model_analysis(
y,
Xmat,
method = "glm",
tol = 1e-6,
penalization = "none",
lambda = NULL,
family = "gaussian",
verbose = TRUE
)
Again, would it be nice if all equal signs =
could be nicely aligned automatically?
Well, I made a function that does exactly that:
- generate a vertical output with aligned equal signs;
- allows to specify the name;
- automatically copy the output so you can
CTRL+V
it directly in your script after execution.
make_ftext
R function
You can copy and paste the code below, save it in a .R
script, and run it before you wish to use it. You can also download the script here.
The key input to the make_ftext
function is parm_list
which accepts a character string of values separated by comma such as: c("value1", "value2", "...", "valueV")
(best) or c("value1, value2, ..., valueV")
(also works).
#' @title Make formatted text
#'
#' @description Generate vertically-aligned equal sign for input parameters or values
#'
#' @param parm_list A character string containing the function parameters or values in a comma-separated format.
#' @param name The name of the function (default is `list`)
#'
#' @return This function does not return a value directly, but it prints and copies the formatted parameter call.
#'
#' @export
#'
#' @importFrom clipr write_clip
make_ftext<- function(parm_list, name="list"){
# print first row of function call (i.e., its name)
header_row <- paste0(name,"(")
# generate symbol to add after each
symbol <- c(" = ,")
# generate list of parameters
parm_list <- gsub("[=]","",parm_list)
parms <- unlist(strsplit(parm_list, ","))
nparms <- length(parms)
# create data and derive values
data <- data.frame(parms = parms)
data$nchar <- nchar(data$parms) # get length
data$max <- max(data$nchar) # derive maximum field length (i.e., max nchar )
data$blanks <- data$max - data$nchar # calculate number of blank needed
# correction for first, if uneven, in single-string
if( (length(parm_list)==1) & (max(data$nchar) %% 2 ==0)){
data[1,"blanks"] <- data[1,"blanks"]-1
}
## create particular symbol for last parameter or otherwise use <symbol>
data$symbol <- NA
data[nparms,]$symbol <- " = )"
data$symbol <- ifelse(is.na(data$symbol),symbol,data$symbol)
# generate each line of text, according to <nparms>
generate_row_text <- function(index){
paste0("\t",data[index,"parms"],
paste0(replicate(data[index,"blanks"]," "),collapse=""),
data[index,"symbol"], collapse="")
}
## loop through each parameter
l <- list()
for (i in 1:nparms){
l[[i]]<-generate_row_text(index = i)
}
parms_fmt <- do.call("rbind",l)
# output list
to_copy <- c(header_row, parms_fmt)
cat(to_copy,sep="\n")
# copy to clipboard
clipr::write_clip(to_copy,breaks="\n")
}
The most “advanced” feature - to automatically transfer the resulting heading to copy/paste - is performed using the
clipr
package.
Using the function with list of values
With the make_ftext
function, it is quick and easy to create formatted list of variable names to create labels.
# Create vector of variable names in the data
my_variable <- names(data_titanic)
# Use the function to generate the list of variables with aligned equal sign
make_ftext(
parm_list = my_variable,
name = "list"
)
list(
Class = ,
Sex = ,
Age = ,
Survived = ,
Freq = )
The output is printed to the console, but we can also paste it directly with CTRL+V
# Once pasted with CTRL+V, the output is:
list(
Class = ,
Sex = ,
Age = ,
Survived = ,
Freq = )
Finally, we can add the remaining code to generate labels:
labelled::var_label(data_titanic) <-
list(
Class = "Passenger class",
Sex = "Sex of passenger",
Age = "Age group",
Survived = "Survival status",
Freq = "Passenger count")
Of note, you could also use the make_ftext
function for other purposes than labeling; labeling is simply one of the most common use for me.
Sorting variable names alphabetically
Another helpful scenario when dealing with many variables is to sort the list alphabetically. Sorting alphabetically can be done with the base R function sort()
.
# Create vector of variable names:
my_variable <- names(data_titanic)
print(my_variable)
[1] "Class" "Sex" "Age" "Survived" "Freq"
# Sort vector alphabetically
print(sort(my_variable))
[1] "Age" "Class" "Freq" "Sex" "Survived"
# Use function to generate list of variables with aligned equal sign, sorted alphabetically
make_ftext(
parm_list = sort(my_variable),
name = "list"
)
list(
Age = ,
Class = ,
Freq = ,
Sex = ,
Survived = )
Using the function for input parameters
We can also create a nicely aligned list of parameters for the hypothetical run_model_analysis
function.
# Hypothetical function with many input parameters
run_model_analysis <- function(y, Xmat, method = "glm", tol = 1e-6, penalization = "none",
lambda = NULL, family = "gaussian", verbose = TRUE) {return(NULL)}
# Create quoted list of parameters with `formals` and `names`
my_parameters <- names(formals(run_model_analysis))
make_ftext(
parm_list = my_parameters,
name = "run_model_analysis"
)
run_model_analysis(
y = ,
Xmat = ,
method = ,
tol = ,
penalization = ,
lambda = ,
family = ,
verbose = )
Similarly, the output is printed to the console and we can paste it directly with CTRL+V
.
Conclusion
Is it necessary to always align everything vertically? No, of course. Is it always better to read vertically? Also no… But, if you need to do it, at least you can do it quickly now.