Portable, light-weight data frame to xlsx exporter based on libxlsxwriter. No Java or Excel required.
Wraps the libxlsxwriter library to create files in Microsoft Excel ‘xlsx’ format.
Installation
install.packages("writexl")Getting started
# A tibble: 150 × 5
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
<dbl> <dbl> <dbl> <dbl> <chr>
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
4 4.6 3.1 1.5 0.2 setosa
5 5 3.6 1.4 0.2 setosa
# ℹ 145 more rows
A named list writes one sheet per element:
write_xlsx(list(Flowers = iris, Cars = mtcars))That is the whole of the common case. Everything below is optional.
What else it can do
Each of these has a vignette; none is needed to write a workbook.
| Formatting cells | Fonts, fills, borders, number formats and alignment, built from group constructors that combine with +. The same format object styles a cell, a column, a chart series or a conditional rule. Conditional formatting, colour scales, data bars and icon sets. |
| Worksheets and workbooks | Column widths and row heights in characters or pixels, frozen and split panes, tab colours and visibility, outlines, protection, page setup and printing, document properties, and the row-streaming mode for large workbooks. |
| Charts and images | All 22 chart types Excel offers, with axes, markers, data labels, trendlines, error bars, legends and data tables; chartsheets; and pictures placed on a sheet, inside a cell, in a header or tiled behind the grid. |
| Formulas, tables and the rest | Formulas including array and dynamic array formulas, hyperlinks, comments, rich strings, mixed-type columns, data validation, autofilters that actually hide the rows, worksheet tables and merged cells. |
| Everything at once | One runnable script that exercises the whole package into two workbooks — the quickest way to see what it does without reading the other four. |
A taste of how they fit together:
sheet <- xl_sheet(
sales,
cols = xl_col_spec("revenue", format = xl_num_format("$#,##0")),
freeze = "A2",
chart = xl_chart("column", xl_chart_series(values = list(cols = "revenue")))
)
write_xlsx(list(Sales = sheet), "report.xlsx")Round-tripping
Every column type survives a trip through readxl unchanged, with one exception that is deliberate:
library(nycflights13)
out <- readxl::read_xlsx(writexl::write_xlsx(flights))
keep <- setdiff(names(flights), "time_hour")
all.equal(out[keep], flights[keep])
## TRUEtime_hour is a POSIXct in America/New_York, and Excel has no concept of a time zone. Rather than silently converting everything to UTC, writexl decides once per workbook: when every datetime shares a zone it writes the local wall-clock reading and drops the ” UTC” suffix from the default datetime format, so nothing is mislabelled; when the zones differ it converts to UTC and warns. Either way the zone itself is not in the file for readxl to hand back.
Performance
Writing nycflights13::flights — 336,776 rows by 19 columns — is faster than the openxlsx2 implementation, for files of the same size:
library(nycflights13)
bench <- function(f) median(replicate(2, {
p <- tempfile(fileext = ".xlsx")
on.exit(unlink(p))
system.time(f(p))[["elapsed"]]
}))
bench(function(p) writexl::write_xlsx(flights, p))
## 12.3
bench(function(p) openxlsx2::write_xlsx(flights, p))
## 19.6The output files are within a rounding error of each other:
file.size(writexl::write_xlsx(flights, tempfile(fileext = ".xlsx")))
## 29132216 (27.8 MB)
p <- tempfile(fileext = ".xlsx"); openxlsx2::write_xlsx(flights, p); file.size(p)
## 29297097 (27.9 MB)Measured on R 4.6.1, writexl 2.0.0, openxlsx2 1.28.
Memory
constant_memory = TRUE streams each row to disk instead of holding the whole sheet, which is the difference between a workbook that fits in memory and one that does not. Peak resident memory of the whole R process, writing the same 336,776 rows:
| peak memory | file | time | |
|---|---|---|---|
constant_memory = FALSE |
894 MB | 26.8 MB | 7.4 s |
constant_memory = TRUE |
208 MB | 27.8 MB | 7.7 s |
A quarter of the memory, for a file 3.5% larger and about the same time. Streaming produces slightly bigger files because a shared-string table cannot be built for rows already flushed to disk.
Left at its default, writexl chooses per workbook: streaming is switched on only when the estimated saving is worth it, and switched off automatically for the features that cannot be written while streaming — worksheet tables, multi-cell array formulas, merged ranges and embedded images. See Worksheets and workbooks.
