HDF5 is an excellent format for
storing large, multi-dimensional numerical arrays. h5lite
simplifies the process of reading and writing matrices and arrays by
handling the complex memory layout differences between R and HDF5
automatically.
This vignette covers writing matrices, preserving dimension names
(dimnames), and understanding how h5lite
manages dimension ordering.
In R, matrices are simply 2-dimensional arrays. You can write them
directly using h5_write(). h5lite preserves
the dimensions exactly as they appear in R.
The same logic applies to arrays with 3 or more dimensions.
R objects often carry metadata in the form of dimnames
(row names, column names, etc.). HDF5 does not have a native “row name”
concept for numerical arrays, but it supports Dimension
Scales.
h5lite automatically converts R dimnames
into HDF5 Dimension Scales. This allows your row and column names to
survive the round-trip to disk and back.
# Create a matrix with row and column names
data <- matrix(rnorm(6), nrow = 2)
rownames(data) <- c("Sample_A", "Sample_B")
colnames(data) <- c("Gene_1", "Gene_2", "Gene_3")
h5_write(data, file, "genetics/expression")
# Read back
data_in <- h5_read(file, "genetics/expression")
print(data_in)
#> Gene_1 Gene_2 Gene_3
#> Sample_A 0.5069539 0.4831928 -0.6186387
#> Sample_B 0.3764785 -0.5090470 -0.8438659Technical Note: In the HDF5 file, the names are stored as separate datasets (e.g.,
_rownames,_colnames) and linked to the main dataset using HDF5 Dimension Scale attributes.
One of the most confusing aspects of HDF5 for R users is dimension ordering.
To ensure that a 3x4 matrix in R looks like a
3x4 dataset in HDF5 tools (like h5dump or
HDFView), h5lite rearranges
the data during read/write operations.
h5lite converts R’s
column-major memory layout to HDF5’s row-major layout.h5lite converts the data back
to column-major for R.This ensures that indexing is preserved.
x[2, 1] in R refers to the exact same value after reading
it back from HDF5.
Because h5lite writes the data in C-order (Row-Major) to
match the HDF5 specification, files created with h5lite are
perfectly readable by Python (h5py or
pandas).
(3, 4)(3, 4)Note: Some other R packages create HDF5 files by swapping the
dimensions (writing a 3x4 matrix as 4x3) to avoid the cost of
transposing data. h5lite prioritizes correctness and
interoperability over raw write speed.
Matrices and arrays are compressed automatically. h5lite
splits each one into chunks (tiles of about 1 MB) and
compresses each chunk independently. By default, it uses gzip level 5.
Use the compress argument to choose another codec, and
h5_inspect() to see the chunk shape and the space
saved:
# A large, mostly-zero matrix (highly compressible)
sparse_mat <- matrix(0, nrow = 1000, ncol = 1000)
sparse_mat[1:10, 1:10] <- 1
# Default compression (gzip level 5)
h5_write(sparse_mat, file, "compressed/matrix")
# Zstandard: faster than gzip, with a similar or better ratio
h5_write(sparse_mat, file, "compressed/matrix_zstd", compress = "zstd-5")
h5_inspect(file, "compressed/matrix_zstd")
#> <HDF5 Dataset Properties>
#> Type: uint8 Size: 976.56 KB
#> Layout: chunked Disk: 103.00 B
#> Chunks: [1000 x 1000] Ratio: 9708.74x
#> Pipeline: zstdTo pick chunk shapes, h5lite repeatedly halves a
matrix’s largest dimension. For tall matrices, this keeps entire rows
together in each chunk, which makes reading a block of rows
efficient.
For codec choices, lossy compression of numeric arrays (such as
ZFP), and chunk-size tuning, see
vignette('compression').
You don’t need to read a whole matrix to get part of it. The
start and count arguments of
h5_read() read a block of rows directly from the file:
big <- matrix(1:50000, nrow = 10000, ncol = 5)
h5_write(big, file, "big_matrix")
# Read 3 rows, starting at row 5001
h5_read(file, "big_matrix", start = 5001, count = 3)
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 5001 15001 25001 35001 45001
#> [2,] 5002 15002 25002 35002 45002
#> [3,] 5003 15003 25003 35003 45003For arrays with 3 or more dimensions, a single start
value selects whole matrices along the last dimension. See
vignette('partial-reading') for the full rules, including
how to target individual columns and elements.