Flexible and powerful tensor operations for readable and reliable code.
Supports base R arrays (this includes matrices, a subtype of base arrays), and more types and frameworks such as torch in the future.
Installation
einops
is available on CRAN:
install.packages("einops")
For the latest semi-stable development version try:
devtools::install_github("Qile0317/einops@dev")
Usage
einops
has a minimalistic yet powerful API.
Three core operations provided (einops tutorial shows those cover stacking, reshape, transposition, squeeze/unsqueeze, repeat, tile, concatenate, view and numerous reductions)
library(einops)
# rearrange elements according to the pattern
output_tensor <- rearrange(input_tensor, 't b c -> b c t')
# combine rearrangement and reduction
output_tensor <- reduce(input_tensor, 'b c (h h2) (w w2) -> b h w c', 'mean', h2 = 2, w2 = 2)
# copy along a new axis (note: repeat is surrounded by ticks. einops.repeat() works too)
output_tensor <- `repeat`(input_tensor, 'h w -> h w c', c = 3)
A MAJOR CAVEAT is that R’s base::arrays
are column-major, while Python’s multidimensional arrays are row-major. This is reflected in their respective indexing operations. To fully replicate a python expression using a column-major array, each of the three core functions have a .row_major
optional argument which replicates the python row-major behaviour during einops operations, while ensuring the output is still column-major.
Vignettes
The vignette is the most convenient way to see einops
in action
- read it using
vignette("basics", package = "einops")
Kapil Sachdeva recorded a small intro to einops for the python version.
Naming
einops
stands for Einstein-Inspired Notation for operations (though “Einstein operations” is more attractive and easier to remember).
Notation was loosely inspired by Einstein summation (in particular by numpy.einsum
operation).
Why use einops
notation
Semantic information (being verbose in expectations)
While these two lines are doing the same job in some context, the second one provides information about the input and output. In other words, einops
focuses on interface: what is the input and output, not how the output is computed.
The next operation looks similar:
y <- rearrange(x, 'time c h w -> time (c h w)')
but it gives the reader a hint: this is not an independent batch of images we are processing, but rather a sequence (video).
Semantic information makes the code easier to read and maintain.
Convenient checks
Reconsider the same example:
y <- x; dim(y) <- c(dim(y)[1], prod(dim(y)[-1])) # x: (batch, 256, 19, 19)
y <- rearrange(x, 'b c h w -> b (c h w)')
The second line checks that the input has four dimensions, but you can also specify particular dimensions. That’s opposed to just writing comments about shapes since comments don’t prevent mistakes, not tested, and without code review tend to be outdated
Result is strictly determined
Below we have at least two ways to define the depth-to-space operation
# depth-to-space
rearrange(x, 'b c (h h2) (w w2) -> b (c h2 w2) h w', h2 = 2, w2 = 2)
rearrange(x, 'b c (h h2) (w w2) -> b (h2 w2 c) h w', h2 = 2, w2 = 2)
There are at least four more ways to do it. Which one is used by the underlying code?
These details are ignored, since usually it makes no difference, but it can make a big difference (e.g. if you use grouped convolutions in the next stage), and you’d like to specify this in your code.
Uniformity
reduce(x, 'b c (x dx) -> b c x', 'max', dx = 2)
reduce(x, 'b c (x dx) (y dy) -> b c x y', 'max', dx = 2, dy = 3)
reduce(x, 'b c (x dx) (y dy) (z dz) -> b c x y z', 'max', dx = 2, dy = 3, dz = 4)
These examples demonstrated that we don’t use separate operations for 1d/2d/3d pooling, those are all defined in a uniform way.
Space-to-depth and depth-to space are defined in many frameworks but how about width-to-height? Here you go:
rearrange(x, 'b c h (w w2) -> b c (h w2) w', w2 = 2)
Citing Einops (for R)
If using the R implementation of Einops in your research work, please cite the original ICLR paper, in addition to this package with these bibtex entries:
@Misc{yang2025einops,
title = {Einops for R},
author = {Qile Yang},
year = {2025},
url = {https://qile0317.github.io/einops/},
}
@inproceedings{rogozhnikov2022einops,
title={Einops: Clear and Reliable Tensor Manipulations with Einstein-like Notation},
author={Alex Rogozhnikov},
booktitle={International Conference on Learning Representations},
year={2022},
url={https://openreview.net/forum?id=oapKSVM2bcj}
}
Development
A Devcontainer and Dockerfile is provided in the sourcecode, this environment can be used locally, or on your server, or within github codespaces. To start with devcontainers in vs code, clone repo, and click ‘Reopen in Devcontainer’.
All building, installing, and documentation can be generated with the standard tidyverse devtools
pipeline, using functions such as devtools::document()
and devtools::test()
If you’d like to contribute, Github pull requests from forked branches are more than welcome as it is mostly a solo-project at the moment. For major changes, please open an issue first to discuss what you would like to change. Please also make sure to update tests as appropriate.