openxlsx format cells as number with comma and decimal places - r

How can one create an excel file using openxlsx where the number formatting includes both comma thousand separators and 2 decimal places? I have tried the code below, but no luck on the formatting.
# Create Customer Dataset
cust <- data.table(Customer = c("Sue", "Ben", "Jason", "Cody"), Sales =
c(5654.3456, 29384.4, 729, .4093))
# Start Workbook
wb <- createWorkbook()
# Set Sheet Name
sheet = "Customers Report"
# Initiate worksheet within workbook
addWorksheet(wb = wb, sheet = sheet)
# Add Formatting to Spreadsheet
addStyle(wb = wb, sheet = sheet, style = createStyle(numFmt = "NUMBER"), rows = 2:6, cols = 2)
addStyle(wb = wb, sheet = sheet, style = createStyle(numFmt = "COMMA"), rows = 2:6, cols = 2, stack = TRUE)
# Write Customer Dataset to Spreadsheet
writeData(wb = wb, sheet = sheet, x = cust, headerStyle =
createStyle(textDecoration = "bold"))
# Write Workbook to File
saveWorkbook(wb = wb, file = "~/Desktop/Customer_Report.xlsx", overwrite = TRUE)

You can set the default formatting for the 2 decimal cases prior to adding the thousands format.
wb = createWorkbook()
options("openxlsx.numFmt" = "0.00") # 2 decimal cases formating
styleT <- createStyle(numFmt = "#,##0.00") # create thousands format
addStyle(wb, sheetName, styleT,
rows = 'yourrows',cols = 'yourcols',
gridExpand = T, stack = T) # add thousands format to designated cols and rows
This will ensure that the thousands formatting happens on a value that already has only 2 decimal cases.

I found the answer as I was writing the question, but I figured I would go ahead and post it in the event that someone else has the same question down the road. I found this answer via github openxlsx issue #75. Please see the below code block:
# Add Formatting to Spreadsheet
addStyle(wb = wb, sheet = sheet, style = createStyle(numFmt = "#,##0.00"), rows = 2:6, cols = 2)
I have found that you use "0" when you want there to be a digit there no matter if it is 0 and that you use "#" to represent a placeholder for a potential digit. For example, if the number is .4093 as shown above, then it would be formatted to 0.41 and if the number is 29384.4 as shown above, then it would be formatted to 29,384.40.

Related

openxlsx: Copying the same style to a new column added to a worksheet

I'm working with the openxlsx package and am cloning a worksheet in a workbook object. I then add new data to this cloned worksheet.
How can I apply the same styles to this added data as currently exists on this sheet?
E.g. assume that the old worksheet consists of data in columns 1:3 that was formatted as percentages, has some underlinings, bolding, font sizes etc. I now just want to make sure that the same format/style from column 3 is applied to column 4 (that's where I add my new data).
I found the getStyle, creatStyle and replaceStyle functions in openxlsx, but don't see any option to grab and apply a certain style (because getStyle returns a list, but it doesn't tell which list element belongs to which worksheet or column).
My current workflow (although probably not relevant/helpful):
old_wb <- loadWorkbook(file.choose())
new_wb <- old_wb
cloneWorksheet(new_wb, "new", "old worksheet")
writeData(new_wb,
sheet = "new",
x = c(1:3),
startRow = 3,
startCol = 4)
saveWorkbook(new_wb, file = "test.xlsx", overwrite = TRUE)
You can use this function which combines the capabilities of the openxlsx and tidyxl packages. It will give you the style objects associated with all cells in a given excel, as well as all other content like numeric, character and formulas. You can then directly apply the style to other cells or in a different workbook.
read_excel_template <- function(excel_path) {
cells <- tidyxl::xlsx_cells(excel_path)
template <- openxlsx::loadWorkbook(excel_path)
map_df(.x = enframe(template$styleObjects)$value,
.f = ~ tibble(
style = c(.x$style),
sheet = .x$sheet,
row = .x$rows,
col = .x$cols
)) %>%
full_join(cells, by=c("sheet", "row", "col")) %>%
relocate(style, .after = address)
}
The getStyles() function returns just the list of style objects present in the workbook, not location of where styles are used. It also orders the styles and can be viewed using getStyles(wb) %>% View()
If you would want to apply an existing style after writing the new column you would have to add the style to that column with addStyle(). You could use the getStyle() though to easily pull over styles and layer them as well.
Below I have 4 styles. Note the the big_purple_text will ultimately be the fourth style listed once the workbook is loaded. I apply these 4 styles to the columns in the iris data set. Save it. And then load it as iris_wb.
library(openxlsx)
wb <- createWorkbook()
bold_blue_text <- createStyle(textDecoration = "bold", fontColour = "blue")
red_italic_text <- createStyle(textDecoration = "italic", fontColour = "red")
big_purple_text <- createStyle(fontSize = 18, fontColour = "purple")
underline_text <- createStyle(textDecoration = "underline")
addWorksheet(wb, "Iris")
writeData(wb, "Iris", iris)
addStyle(wb, "Iris", bold_blue_text, cols = 1, rows = 1:dim(iris)[1],gridExpand = TRUE)
addStyle(wb, "Iris", red_italic_text, cols = 2, rows = 1:dim(iris)[1],gridExpand = TRUE)
addStyle(wb, "Iris", big_purple_text, cols = 3:4, rows = 1:dim(iris)[1],gridExpand = TRUE)
addStyle(wb, "Iris", underline_text, cols = 5, rows = 1:dim(iris)[1],gridExpand = TRUE)
saveWorkbook(wb, file = "C:\\Users\\Desktop\\test.xlsx", overwrite = TRUE)
openXL(wb)
iris_wb <- loadWorkbook("C:\\Users\\Desktop\\test.xlsx")
This will show only the 4 styles loaded, using View you can explore more how the ordering of the styles are listed
getStyles(iris_wb)
getStyles(iris_wb) %>% View() #Here you can view the ascending order of the style types, note that the fontdecoration is above fontsize. This is why big_purple_text is listed 4th
#Write new data to existing sheet
writeData(iris_wb, "Iris", mtcars, startCol = 6)
#add styles from existing styles in loaded workbook
addStyle(iris_wb, "Iris", getStyles(iris_wb)[[1]], cols = 6, rows = 1:10)
addStyle(iris_wb, "Iris", getStyles(iris_wb)[[3]], cols = 7:8, rows = 1:20, gridExpand = TRUE)
addStyle(iris_wb, "Iris", getStyles(iris_wb)[[4]], cols = 8:10, rows = 10:20, gridExpand = TRUE, stack = TRUE) # Should retain underline
openXL(iris_wb)
From studying #Ljupcho Naumov's great answer, I learned that if you simply want the cell locations of the styles from openxlsx::getStyle then you can use
old_wb$styleObjects
which returns a list of the styles as well as the sheet/rows/columns. You can check that the style objects are the same with
all.equal(purrr::map(old_wb$styleObjects, "style"), getStyles(old_wb))
This is one of the many things improved in openxlsx2. In the upcoming release 0.3 you can simply run the following code. This will load a workbook, get the styles from a range of cells. Clone the worksheet, apply the style to a range of cells, add data and open the sheet.
library(openxlsx2)
wb <- wb_load(system.file("extdata", "oxlsx2_sheet.xlsx", package = "openxlsx2"))
styles <- wb_get_cell_style(wb = wb, dims = "A12:G15")
wb <- wb %>%
wb_clone_worksheet(new = "Clone1") %>%
wb_set_cell_style(dims = "A22:G25", style = styles) %>%
wb_add_data(x = matrix(1, 1, 5), dims = "B22:G22", colNames = FALSE) %>%
wb_open() # or wb_save("file.xlsx")
In openxlsx2 there are no style objects. Therefore the styles of imported xlsx workbook are untouched when modifying the workbook. While you asked to clone a sheet, that's improved as well. It's now even possible to clone sheets with charts and pivot tables (lacking slider support).

Formatting numbers with openxlsx package in R

I am trying to format certain columns using openxlsx to write an r data frame to an excel file.
Here is a snippet of the R data frame:
The square bracket part in the "seed" column is used to superscript the excel output.
Here is the code I used to write the file:
openxlsx::addWorksheet(wb, sheetName = 'data') # add sheet
openxlsx::writeData(wb, sheet ='data',
x=df, xy=c(1, 1),withFilter = T) # write data on workbook
# make quality codes superscript
for(i in grep("\\_\\[([A-z0-9\\s]*)\\]", wb$sharedStrings)){
# if empty string in superscript notation, then just remove the superscript notation
if(grepl("\\_\\[\\]", wb$sharedStrings[[i]])){
wb$sharedStrings[[i]] <- gsub("\\_\\[\\]", "", wb$sharedStrings[[i]])
next # skip to next iteration
}
# insert additional formatting in shared string
wb$sharedStrings[[i]] <- gsub("<si>", "<si><r>", gsub("</si>", "</r></si>", wb$sharedStrings[[i]]))
# find the "_[...]" pattern, remove brackets and udnerline and enclose the text with superscript format
wb$sharedStrings[[i]] <- gsub("\\_\\[([A-z0-9\\s]*)\\]",
"</t></r><r><rPr><vertAlign val=\"superscript\"/></rPr><t xml:space=\"preserve\">\\1</t></r><r><t xml:space=\"preserve\">",
wb$sharedStrings[[i]])
}
openxlsx::modifyBaseFont(wb, fontSize = 10, fontName = 'Arial')
# right-justify data
openxlsx::addStyle(wb, sheet = 'data',
style = openxlsx::createStyle(halign = "right"), rows = 1:nrow(df)+1, cols = 3:12, gridExpand = TRUE)
#apply to rows with "All" in column B
openxlsx::conditionalFormatting(wb,sheet = 'data',
cols = 1:ncol(df),
rows = 1:nrow(df)+1,
rule = 'LEFT($B2,3)="ALL"',
style = openxlsx::createStyle(textDecoration = 'bold', bgFill = '#dad9d9'))
# format numbers
openxlsx::addStyle(wb = wb, sheet = 'data',
style = openxlsx::createStyle(numFmt = "#,###.0"),
rows = 1:nrow(df)+1, cols = c(5:7,10:12),gridExpand = T)
# write excel file
openxlsx::saveWorkbook(wb, file="file.xlsx",
overwrite = TRUE)
The output looks like this:
I am trying to conditionally format the numbers in the seed/harv/yield/prod columns to be numbers, but the "F" values are creating a mixed-class vector at best. (I need these Fs!)
Any ideas?
Thanks!

How to allow filtering by year, month, date in XLSX generated by openxlsx

I've started to generate XLSX files instead of CSV through openxlsx. However, I'm experiencing a different behavior in what concerns Date filtering.
I've generated the following dummy code:
library(openxlsx)
df <- data.frame(ID=c(1,2,3,4,5,6), Date=c("1900-01-12","2010-12-29","1934-03-17", "1989-09-19","1978-11-27","2010-01-13"))
write.csv(df, "dateTestCSV.csv")
# Create the workbook
wb = createWorkbook()
hs <- createStyle(fontColour = "#ffffff", fgFill = "#4F80BD",
halign = "center", valign = "center", textDecoration = "bold",
border = "TopBottomLeftRight")
addWorksheet(wb=wb, sheetName = "Test", gridLines=T, zoom=70)
writeData(
wb,
sheet = "Test",
x = df,
withFilter=T,
borders="all",
borderStyle="thin",
headerStyle=hs
)
setColWidths(wb, sheet = "Test", cols=1:ncol(df), widths = "auto")
openxlsx::saveWorkbook(wb, "dateTestXLSX.xlsx", overwrite=T)
The first file generated, dateTestCSV.csv, is comma separated values file. And it looks like it follows:
If I add a filter to the Date column, it will look like follows:
However, when I create the XSLX file with filters, such filter looks like it follows:
It can be seen that Excel is filtering by absolute values, and doesn't group dates by year, month and/or day.
What am I dooing wrong?
You need to make 2 changes to your code as below, and the filters should work fine:
df <- data.frame(ID=c(1,2,3,4,5,6),
Date=c("1900-01-12","2010-12-29","1934-03-17", "1989-09-19","1978-11-27","2010-01-13"),
stringsAsFactors = F) # Ensure your dates are initially strings and not factors
# Actually convert the character dates to Date before writing them to excel
df$Date <- as.Date(df$Date)

Extract Links from Excel Sheet using R-package 'openxlsx'

I'm working with an Excel sheet in which some columns contain hyperlinks that are represented as text that is completely different from the actual address the hyperlinks point to. I want to use some R code to modify and subset the Excel sheet but keep the hyperlinks. I think I can do this by extracting those hyperlinks as an indexed character vector then re-introducing them into a new Excel document using the makeHyperlinkString() and writeFormula() functions. But I cannot figure out how to get a vector of the links themselves.
In case it matters, my intention is to do all the modifying and subsetting on a data.frame version of the Excel sheet rather than a workbook object.
Oh, now I think I got your problem. I thought there were only normal hyperlinks not Excel-Hyperlinks.
I think this may help you to get a vector of the hyperlinks, although its a bit messy.
library(openxlsx)
pathtofile = "path to .xlsx file"
df1 <- read.xlsx(xlsxFile = pathtofile,
sheet = 1, skipEmptyRows = FALSE,
colNames = F, rowNames = F,
startRow = 1)
## Sheet or Tabelle
Sheet = "Sheet" ## Or "Tabelle"
## Get Names of rows from Hyperlink column
rowIndex <- sub(x = df1[,1], pattern = paste0("(#'",Sheet,"\\d'!)"), replacement = "")
## Get the Sheet, where Hyperlinks are saved
SheetName <- regmatches(df1[,1], regexpr(text = df1[,1], pattern = paste0("(",Sheet,"\\d)")))
## Extract only the Sheet number
SheetIndex <- as.numeric(sub(x = SheetName, pattern = Sheet, replacement = ""))
## Get the row Indexes as numeric
RowIndexNum <- as.numeric(regmatches(rowIndex, regexpr(text = rowIndex, pattern = "\\d")))
## Get the column name as character
RowIndexName <- sub(x = rowIndex, pattern = "\\d", "")
## Create uppercase Letters
myLetters <- toupper(letters[1:26])
## Convert Row Name (character) to numeric (based on alphabetical order)
RowIndexNameNum <- match(RowIndexName, myLetters)
## If Hyperlinks only in 1 Sheet or several sheets
if (length(unique(SheetIndex)) == 1) {
dfLinks <- read.xlsx(xlsxFile = pathtofile,
sheet = unique(SheetIndex),
skipEmptyRows = FALSE,
colNames = F, rowNames = F,
rows = RowIndexNum[1]:tail(RowIndexNum,1),
cols = unique(RowIndexNameNum),
startRow = 1
);
} else {
dfLinks <- data.frame()
for (i in unique(SheetIndex)){
dfTmp <- read.xlsx(xlsxFile = pathtofile,
sheet = i,
skipEmptyRows = FALSE,
colNames = F, rowNames = F,
rows = RowIndexNum[1]:tail(RowIndexNum,1),
cols = unique(RowIndexNameNum),
startRow = 1)
dfLinks <- rbind(dfLinks, dfTmp)
}
}
dfLinks
This is how my Excel File looks like:

Copy an existing worksheet to another workbook using xlsx package

I have a default workbook with my company logo and formating already in place, in order to quickly generate workbooks to my clients, without having to reformat everything all the time.
I manage to do it for a single sheet. I would like to do it for as many sheets as I need, in a single workbook.
I do it, now, as follows
wb = loadWorkbook("O:/R/handle_wb.xlsx") # loads default workbook
sheets = getSheets(wb) # get pre-formatted sheet
# change styles
cs = CellStyle(wb) + ...
# add df of data to excel with the chosen styles
addDataFrame(data, sheets$Sheet1, startRow = 6, startColumn = 1,
colnamesStyle = cs, row.names = F)
Then, I would like to generate another sheet in the same wb, but using the preformatted sheet I already have, by writing a function like
add.sheet <- funtion(newdata, original.wb, default.wb){
wb = loadWorkbook(default.wb) # loads default workbook
sheets = getSheets(wb) # get pre-formatted sheet
ob = loadWorkbook(original.wb) # loads orginal workbook
# change styles
cs = CellStyle(wb) + ...
# add df of data to excel with the chosen styles
addDataFrame(newdata, sheets$Sheet1, startRow = 6, startColumn = 1, colnamesStyle = cs, row.names = F)
# create a new sheet in the original workbook to receive the newsheet created above
createSheet(ob, "sheet2")
she <- getSheets(ob)
she$sheet2 <- sheets$Sheet1 # designate to sheet2 the created default sheet
saveWorkbook(ob, original.wb)
}
The question is, how do I replace the blank sheet I created in the original workbook with the one in my default workbook, i.e., she$sheet2 <- sheets$Sheet1 ?

Resources