# Overview

The frab package allows one to “add” tables in a natural way. It also furnishes an alternative interpretation of named vectors wherein addition is defined using the (unique) names as the primary key. Support for multi-dimensional tables is included. The underlying mathematical object is the Free Abelian group. To cite in publications please use R. K. S. Hankin 2023. “The free Abelian group in R: the frab package”, arXiv, https://arxiv.org/abs/2307.13184.

The package has two S4 classes: frab and sparsetable. Class frab is for one-dimensional tables and is an alternative implementation of named vectors; class sparsetable handles multi-way tables in a natural way.

# The package in use

## One-dimensional tables: class frab

Primary construction function frab() takes a named vector and returns a frab object:

suppressMessages(library("frab"))
p <- c(x=1,b=2,a=2,b=3,c=7,x=-1)
frab(p)
#> A frab object with entries
#> a b c
#> 2 5 7

Above, we see from the return value that function frab() has reordered the labels of its argument, calculated the value for entry b [as ], determined that the entry for x has vanished [the values cancelling out], and printed the result using a bespoke show method. It is useful to think of the input argument as a semi-constructed and generalized “table” of observations. Thus

p
#>  x  b  a  b  c  x
#>  1  2  2  3  7 -1

Above we see p might correspond to a story: “look, we have one x, two bs, two as, another three bs, seven cs…oh hang on that x was a mistake I had better subtract one now”. However, the package’s most useful feature is the overloaded definition of addition:

(x <- rfrab())
#> A frab object with entries
#> a b c d g i
#> 3 6 1 5 7 5
(y <- rfrab())
#> A frab object with entries
#> a b c d e f i
#> 4 4 1 1 8 5 2
x+y
#> A frab object with entries
#>  a  b  c  d  e  f  g  i
#>  7 10  2  6  8  5  7  7

Above we see function rfrab() used to generate a random frab object, corresponding to a table. It is possible to add x and y directly:

xn <- as.namedvector(x)
yn <- as.namedvector(y)
table(c(rep(names(xn),times=xn),rep(names(yn),times=yn)))
#>
#>  a  b  c  d  e  f  g  i
#>  7 10  2  6  8  5  7  7

but this is extremely inefficient and cannot deal with fractional (or indeed negative) entries.

# Multi-way tables

Class sparsetable deals with multi-way tables. Taking three-way tables as an example:

(x3 <- rspar())
#>  Jan Feb Mar     val
#>    a   a   a  =   10
#>    a   c   b  =   15
#>    b   a   a  =   11
#>    b   a   b  =    9
#>    b   a   c  =   12
#>    b   b   a  =    6
#>    b   b   b  =    3
#>    b   b   c  =   14
#>    b   c   a  =    9
#>    b   c   c  =   21
#>    c   c   a  =   10

Function rspar() returns a random sparsetable object. We see that, of the possible entries, only 11 are non-zero. We may coerce to a regular table:

as.array(x3)
#> , , Mar = a
#>
#>    Feb
#> Jan  a b  c
#>   a 10 0  0
#>   b 11 6  9
#>   c  0 0 10
#>
#> , , Mar = b
#>
#>    Feb
#> Jan a b  c
#>   a 0 0 15
#>   b 9 3  0
#>   c 0 0  0
#>
#> , , Mar = c
#>
#>    Feb
#> Jan  a  b  c
#>   a  0  0  0
#>   b 12 14 21
#>   c  0  0  0

In this case it is hardly worth taking advantage of the sparse representation (which is largely inherited from the spray package) but a larger example might be

rspar(n=4,l=10,d=12)
#>  Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec     val
#>    b   c   j   e   f   j   f   a   g   i   a   d  =    1
#>    g   a   j   e   c   f   e   c   a   f   g   c  =    4
#>    j   b   j   g   h   c   d   c   c   b   b   i  =    2
#>    j   j   h   h   a   a   i   f   c   h   g   h  =    3

The random sparsetable object shown above would require floating point numbers in full array form, of which only 4 are nonzero. Multi-way tables may be added in the same way as frab objects:

y3 <- rspar()
x3+y3
#>  Jan Feb Mar     val
#>    a   a   a  =   10
#>    a   a   b  =   14
#>    a   b   a  =    4
#>    a   c   a  =   14
#>    a   c   b  =   15
#>    b   a   a  =   11
#>    b   a   b  =   23
#>    b   a   c  =   12
#>    b   b   a  =   17
#>    b   b   b  =   13
#>    b   b   c  =   23
#>    b   c   a  =    9
#>    b   c   b  =    7
#>    b   c   c  =   24
#>    c   a   a  =   15
#>    c   c   a  =   15
#>    c   c   c  =   14

## Two-way tables

Two-way tables are something of a special case, having their own print method. By default, two-dimensional sparsetable objects are coerced to a matrix before printing, but otherwise operate in the same way as the multi-dimensional case discussed above:

(x2 <- rspar2())
#>    bar
#> foo A  B  D  E  F
#>   a 3 20  0  0  9
#>   b 0  0 15  0  0
#>   c 0  0  0  4  0
#>   d 0  0  0  5 22
#>   e 0  2  0 11 29
(y2 <- rspar2())
#>    bar
#> foo A C  D  E  F
#>   a 9 0 25  6 10
#>   b 7 0  0  0  1
#>   c 0 0  0 11  0
#>   d 8 5  0  4  0
#>   e 0 3  2  0  0
#>   f 0 0 14  0 15
x2+y2
#>    bar
#> foo  A  B C  D  E  F
#>   a 12 20 0 25  6 19
#>   b  7  0 0 15  0  1
#>   c  0  0 0  0 15  0
#>   d  8  0 5  0  9 22
#>   e  0  2 3  2 11 29
#>   f  0  0 0 14  0 15

Above, note how the sizes of the coerced matrices are different ( for x2, for y2) but the addition method copes, using a bespoke sparse matrix representation. Also note that the sum has six columns (corresponding to six distinct column headings) even though x2 and y2 have only five.

# Further information

For more detail, see the package vignette

vignette("frab")