rgo

package module
v2.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 5, 2022 License: MIT Imports: 7 Imported by: 0

README

Rgo

Connecting R and Go

Rgo allows one to connect R to Go, exporting Go functions as callable C functions. This makes it easier to use the good parts of Go (performance, reading online data, and non-embarrassing parallelization) and the good parts of R (plotting and nice data analysis libraries) into one workflow.

This repository also contains an example of using rgo to export a library of simple Go functions that can be compiled and then called from R.

Using rgo

rgo helps translate data from R's internal object representation in C (a SEXP) to Go objects of standard types (floats, ints, strings, etc.) and back again. This allows for easy, RAM-only passage of data from R to Go and/or Go to R by calling Go functions in R. This can be desirable for a number of reasons, which I covered in a blogpost when I first started the project.

The intended use of the rgo package to provide an interface for writing Go functions that can be called from R without having to worry too much about R's C internals. This is especially valuable for programmers like me who are fluent in R and Go but don't have the C chops to mess around with R's internals directly.

Requirements

Rgo requires a working installation of R (at least version 4.0.0) and Go (at least version 1.18). Rgo uses cgo to call R's internal C functions, which means the Go installation must have cgo enabled and there must be a C compiler.

While rsexp contains its own header files which define the C functions called in the rsexp package, the location of the R shared libraries must also be included at compile time. This means the R libraries must be either in the default linker path, or be in one of the following directories that rsexp links automatically:

  • Linux: /usr/lib/
  • MacOS: /Library/Frameworks/R.framework/Libraries

Windows is neither well supported or tested in this package. Moreover, rgo does not look for a default Windows path to the R shared libraries.

If R's shared libraries are not in the default linker path or in the default locations which are included, the best solution is to either use environment variables as specified in this SO post or to modify the contents of conversion.go file to link to the appropriate path. If you are using the former, note that it will require using the replace directive in your go.mod file.

In addition to the requirements for getting rgo to compile, there are additional requirements to use the package. Because cgo does not allow for exported C types (see quoted text), the package which imports rgo must also include a link to R's internal definitions. Therefore, the file which uses the C.SEXP type must include a link to R's header files.

Cgo translates C types into equivalent unexported Go types. Because the translations are unexported, a Go package should not expose C types in its exported API: a C type used in one Go package is different from the same C type used in another.

In general, this will result in a code snippet like the following:

/*
#define USE_RINTERNALS // this is optional
#include <Rinternals.h>
// We need to include the shared R headers here
// One way to find this is via the rgo/rsexp directory
// Another way is to find them from your local R installation
// - Typical Linux: /usr/share/R/include/
// - Typical MacOS: /Library/Frameworks/R.framework/Headers/
// If all else fails, you can also find the required header files wherever rgo is located on your computer
// For example, on my computer all github packages are put in /Go/mod/pkg/github.com/...
#cgo CFLAGS: -I/Library/Frameworks/R.framework/Headers/
*/
import "C"

In order to have access to R's internal functions that are used in rgo like TYPEOF or XLENGTH, it's necessary to include a #define USE_RINTERNALS. Like in the rgo package itself, some functionality will also depend on linking the R shared libraries.

In order to avoid having to worry about the user's location of these header files, rgo keeps a copy in the repository. This means the headers are also available wherever go get saves the package files, typically in something like /Go/mod/pkg/.

Building Go functions that can be called from R

For a working example of how to use the rsexp package, refer to the demo package in the Rgo repository.

The Go functions are made in the main package. They must use the C.SEXP type as both the input and output (remember that R doesn't allow multiple returns, which means the output must be only one C.SEXP), and have an export comment before their signature that looks like this:

//export MYFUNC
func MYFUNC(C.SEXP) C.SEXP {

These functions should be sure to parse the C.SEXP to Go objects and then format its desired output back to a C.SEXP for R, but otherwise can be written like normal Go.

In order for a package to be compiled into a C library correctly, the main function needs to be defined, but it shouldn't do anything:

func main() {}

Finally, the C library can be compiled like so:

go build -o <package>.so -buildmode=c-shared <package>

Supported Types

Rgo uses Go's generics to support as many types as possible. However, it only uses types that are easily translated and understood in both R's and Go's type systems. Therefore, it contains two types of type constraints which match R's notion of the numeric and character types.

type RCharacter interface {
    ~string | ~[]byte
}
type RNumeric interface {
    ~float64 | ~float32
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

All functions to create, modify, or extract data from an RSEXP use these constraints as type parameters.

The only supported types that can be used as inputs to a Go function are R's numeric and character vectors and matrices. Because Go functions can have any number of inputs, supporting a more complex type like lists or data frames doesn't seem necessary.

However, the types that can be created in Go and sent to R are much more diverse, because R functions can only have a single output. Rgo contains functions to create matrices, lists, named lists, and data frames, as well as more common vectors.

Writing Go code using Rgo

Because the C.SEXP type used in rgo is different from the C.SEXP type that is used elsewhere, the only way to pass the type around is to use a combination of unsafe pointers, reflection, and type parameters. The workhorse object of the rgo package is an RSEXP:

type RSEXP C.SEXP

While Go considers a C.SEXP in one package to be different from the C.SEXP from another, the underlying data is the same. Therefore, the data can be converted within the rgo package using a combination of reflection and unsafe pointers. The two most important functions in rgo are therefore those that convert between a users C.SEXP and rgo's. They are called NewRSEXP and ExportRSEXP, respectively.

func NewRSEXP(in any) (RSEXP, error)
func ExportRSEXP[t any](*RSEXP) (t, error)

Both functions use the any type parameter as its input, which means techincally anything can be provided by the caller. The rgo package doesn't have knowledge of a user's C.SEXP type a priori, so there's no way around allowing users to provide anything. Both functions, however, perform basic type checking and will return an error if the provided variable's underlying data isn't either a C.SEXP or a *C.SEXP.

Rgo contains functions to create, modify, and extract data from R's internal SEXP type. These functions wrap R's internal C functions, and use the constraints of supported types to help users avoid panics, runtime errors, or segmentatin faults.

Writing R Code Using Rgo

Because the Go code is compiled to be executable in C, all we need to do in R is load the shared library using dyn.load and then call it using R's .Call interface:

dyn.load("MYLIB.so")
outputR = .Call("MYFUNC", inputR)

It is important to be careful to only load the library once per R session, as loading it multiple times can result in instability. Likewise, loading a library in R, changing it in Go and then recompiling, and then loading it again in the same R session will most likely crash R.

The Matrix Type

Because lots of R code focuses on matrices, data frames, and data.tables, rsexp contains an implementation of the matrix type which mirrors the R matrix implementation. This allows for easier matrix operations in Go and provides a Go type which will return an identical matrix back to R.

Specifically, a matrix is specified as a single vector with metadata describing the dimensions. While matrices in R can be of any numeric type, in Rgo they are always the float64 type, matching the way R will handle operations of just about any matrix.

type Matrix struct {
	Nrow, Ncol int
	Data       []float64
}

Consistent with R's implmentation, the Data vector is a single concatenation of all the data, with each column serving as a vector itself. For example:

// this matrix
Matrix{Nrow: 2, Ncol: 2, Data: []float64{1.1,2.2,3.3,4.4}}
// looks like this:
// [1.1 3.3
//  2.2 4.4]

In addition to providing the Matrix type, the rsexp package provides many functions and methods to get and set subsets of data within a matrix and do simple linear algebra operations.

In order to ensure matrix data quality, all matrix operation functions which can return an error first check the input matrix for internal consistency (such as the length of the data vector matching the Nrow and Ncol metadata).

The Matrix struct is exported in order to allow users to be as flexible as possible in using it, but that comes with responsibility. Sloppy handling of matrices will likely result in compiler issues and/or panics at runtime. Sticking to the methods and functions provided in the package is much safer, although somewhat restricting.

How Rgo Works

The workhorse file of rgo is conversion.go. It defines the C code used as the go-between (get it?) between R and Go and defines the functions that convert an RSEXP to a useful Go type and a useful Go type back to an RSEXP.

Extracting data from R

Rgo is based on the C interface for R's internals. More about R's internals can be found here, and Hadley Wickham's book R's C Interface is also a good resource on the topic.

Everything in R is a SEXP, which is always a pointer to a SEXPREC, which in turn contains some header information and a pointer to the data itself. A SEXP can point to a SEXPREC of up to a couple dozen types. The rsexp package only concerns itself with 5 of them:

  1. REALSXP, akin to a Go slice of float64s
  2. INTSXP, akin to a Go slice of ints
  3. CHARSXP, akin to a a Go string
  4. STRSXP, akin to a Go slice of strings
  5. VECSXP, which is an R list and contains no parallel in Go

In C, the type of data a SEXP points to can be found using the TYPEOF function. It returns an integer, which can be matched to the relevant types based on the rsexp's constants. When using one of the functions to convert an RSEXP to a Go object, they first check to make sure the type of the SEXP matches the type list allowed by the function. If the type doesn't match, they return an error.

The SEXPREC type, which points to the underlying data of the R object itself, does not explicitly point to a vector. Instead, it points to the beginning of the vector and the rest can be found using pointer arithmetic. Go doesn't support pointer arithmetic. Therefore, Rgo uses C functions to extract a single element of a vector based on the original pointer location and an index. The functions that extract data from a SEXP determine the length of the underlying data (via XLENGTH), make a slice to hold it, and fill the slice one index at a time using these extractor functions.

Sending data to R

Rgo only accepts relatively simple types from R but sends a much more varied list of types back, including named lists and data frames. This is because Go functions can support any number of inputs, but R functions can only return one, requiring more complex types to be useful.

Creating R output objects in Go is, intuitively, the same process as extracting data in reverse. First, Rgo determines the underlying R type to create, allocates the size of the vector, and fills it in element by element. Each Rgo generic type has a function to create corresponding SEXP objects.

There are Go functions to facilitate the creation of more complex types as well, which is all done by setting class attributes of the underlying SEXP.

CRAN

Right now, there is no Rgo CRAN package, nor is there a way, using Rgo, to ship a CRAN package which runs Go under the hood rather than C or R itself.

In principle, it should be doable to create a CRAN package which simply wraps .Call to run C functions built using Go. The tricky part with this is compilation. I don't think CRAN can check for a Go installation before downloading and building a package. It should be possible to bundle the package using pre-compiled versions for each operating system, but I don't make my own R packages so I'm not sure how easy that is to implement.

If someone who knows more about CRAN than I do would like to contribute, please do.

Documentation

Overview

Package rgo provides a translation between R and Go using cgo.

Rgo helps translate data from R's internal representation (a SEXP) in C to o objects of standard types (floats, ints, strings, etc.). It contains C functions that help create, modify, and extract data from a SEXP. In order to take advantage of these functions, the workhorse type of rgo wraps its internal notion of a C.SEXP.

type RSEXP C.SEXP

Whenever Rgo wants to read data that comes from R, or format data to send back to R, it uses the RSEXP type. Because C types cannot be exported, the notion of a C.SEXP in the user's package is different from that of Rgo's. Therefore, the best way to create an RSEXP is to use the NewRSEXP function.

Sending data from R to Go

Rgo uses R's internal functions to extract data from a SEXP and into a Go typed object. More about these objects can be found in R's documentation at https://cran.r-project.org/doc/manuals/r-release/R-ints.html#SEXPs. In short, everything in R is a SEXP, which is a pointer to a SEXPREC, which in turn contains some header information, attributes, and a pointer to the data itself. A SEXP can point to a SEXPREC of up to a couple dozen types which map to R's types. Rgo only concerns itself with 5 of them:

  1. REALSXP, akin to a Go slice of float64s and, when containing the dimension attributes, a matrix.
  2. INTSXP, akin to a Go slice of integers
  3. CHARSXP, akin to a a Go string
  4. STRSXP, akin to a Go slice of strings
  5. VECSXP, which is an R list and, when containing the correct attributes, data frame

In C, the type of data a SEXP points to can be found using the ”TYPEOF” function. It returns an integer, which can be matched to the relevant types based on the constant enumerations declared in this package. As a convenience, Rgo's TYPEOF function wraps R's TYPEOF function. Rgo's LENGTH function also wraps R's LENGTH (also called LENGTH) function.

Rgo contains generic function which can be used to extract data from a SEXP as a desired type in Go. There are three functions, which relate to R's numeric type, character type, and matrix type. The numeric and character functions use a type parameter as an input so that the Go slice can be made to be the supported type which is most convenient for the caller, within reason. These functions are:

  1. func AsNumeric[t RNumeric](r RSEXP) ([]t, error)
  2. func AsCharacter[t RCharacter](r RSEXP) ([]t, error)
  3. func AsMatrix(r RSEXP) (Matrix, error)

Each of these functions checks the SEXPTYPE of the underlying SEXP and will return an error if it doesn't match the function that was called.

Sending data from Go to R

Sending data from Go to R is done by creating an RSEXP (which will always point to a newly created C.SEXP) from one of the supported Go types:

  1. func NumericToRSEXP[t RNumeric](in []t) *RSEXP
  2. func CharacterToRSEXP[t RCharacter](in []t) *RSEXP
  3. func MatrixToRSEXP(in Matrix) *RSEXP

This SEXPTYPE of the output SEXP from these functions will match the R internal type which makes the most sense.

Because R does not allow functions to have multiple returns, the preferred way to return multiple pieces of data from a function is a list. Therefore, Rgo contains functions to create three types of lists: a generic list, a named list, and a data frame.

  1. func MakeList(in ...*RSEXP) *RSEXP
  2. func MakeNamedList(names []string, data ...*RSEXP) (*RSEXP, error)
  3. func MakeDataFrame(rowNames, colNames []string, dataColumns ...*RSEXP) (*RSEXP, error)

The functions to create named lists and data frames enforce data quality so that valid R objects can be created. This includes enforcing the number of names and objects provided, checking the lengths of all the columns provided in a data frame, and making sure no nested objects (like lists or data frames themselves) are provided as columns for data frames.

In order to send data back to R, it must be a C.SEXP that matches the user's notion of a SEXP, not Rgo's. Therefore, Rgo provides a generic ExportRSEXP function which is used to create a user's C.SEXP that has the same data as the RSEXP that has been made using the rgo library. Callers provide a type parameter, which should always be their C.SEXP:

mySEXP, err := [C.SEXP](rgoRSEXP)

If their provided type is not a C.SEXP (or a *C.SEXP which is also acceptable) an error is returned.

Building Your Package and Calling Functions in R

In order for a package of Go functions to be callable from R, they must take any number of C.SEXP objects as input and return a single C.SEXP object. They also need to be marked to be exported, by including an export statement immediately above the function signature. Note that if there is a space between the comment slashes and the export mark, Go will parse it as a vanilla comment and the function won't be exported.

//export DoubleVector
func DoubleVector(C.SEXP) C.SEXP {}

The Go package must then be compiled to a C shared library:

go build -o <libName>.so -buildmode=c-shared <package>

Finally, the Go functions can be called in R using the .Call function:

output = .Call("DoubleVector", input)

For a more complete demonstration, see the example below, or the demo package at https://github.com/EMurray16/rgo/demo.

Example

The code below contains a functional example of a Go function that can be called from R:

    package main

    // #include <Rinternals.h>
    // We need to include the shared R headers here
    // One way to find this is via the rgo directory
    // Another way is to find them from your local R installation
    // - Typical Linux: /usr/share/R/include/
    // - Typical MacOS: /Library/Frameworks/R.framework/Headers/
    // If all else fails, you can also find the required header files wherever rgo is located on your computer
    // For example, on my computer all github packages are put in /Go/mod/pkg/github.com/...
    // #cgo CFLAGS: -I/Go/mod/pkg/github.com/EMurray16/rgo/Rheader/
    import "C"
    import(
        "github.com/EMurray16/rgo"
    )

    //export DoubleVector
    func DoubleVector(input C.SEXP) C.SEXP {
        // cast the incoming SEXP as a GoSEXP
        r, err := rgo.NewRSEXP(&input)
        if err != nil {
            fmt.Println(err)
            return nil
        }

        // create a slice from the SEXPs data
        floats, err := rgo.AsNumeric[float64](r)
        if err != nil {
            fmt.Println(err)
            return nil
        }

        // double each element of the slice
        for i, _ := range floats {
            floats[i] *= 2
        }

        // create a SEXP and GoSEXP from the new data
        outputRSEXP := rgo.NumericToSEXP(floats)

		mySEXP, err := rgo.ExportRSEXP[C.SEXP](outputSEXP)
		if err != nil {
            fmt.Println(err)
            return nil
        }

        return mySEXP
    }

Once it is compiled to a shared library, the function can be called using R's .Call() interface:

input = c(0, 2.71, 3.14)
output = .Call("DoubleVector", input)
print(output)

The result would look like this:

[0, 5.52, 6.28]

Index

Constants

This section is empty.

Variables

View Source
var (
	ImpossibleMatrix = errors.New("matrix size and underlying data length are not compatible")
	SizeMismatch     = errors.New("operation is not possible with given input dimensions")
	InvalidIndex     = errors.New("given index is impossible (ie < 0)")
	IndexOutOfBounds = errors.New("index is out of bounds (ie too large)")
	LengthMismatch   = errors.New("lengths of provided inputs are not the same")
)

All matrix and data frame operations check inputs for validity and will return errors where applicable.

View Source
var NotASEXP = errors.New("non-SEXP object provided to a function that needs a SEXP")

NotASEXP is returned by NewRSEXP or ExportRSEXP when it cannot coerce the input object into a *C.SEXP.

View Source
var TypeMismatch = errors.New("input SEXP type does not match desired output type")

TypeMismatch is most often returned from an AsX method when the caller tries to extract the incorrect type from a SEXP, or when they try to create a SEXP of the wrong type using a Go slice.

View Source
var UnsupportedType = errors.New("type provided is not currently supported in Rgo for this operation")

UnsupportedType is returned when a function input is not of a type that Rgo supports, such as when reading data from R that isn't of the simpler types used by Rgo.

Functions

func AreMatricesEqual

func AreMatricesEqual(A, B Matrix) bool

AreMatricesEqual returns true if the input matrices are of the same dimension and have identical data vectors. It's important to note that this function uses strict equality - even if elements of two matrices differ by floating point error, it will return false.

func AreMatricesEqualTol

func AreMatricesEqualTol(A, B Matrix, tolerance float64) bool

AreMatricesEqualTol is the same as AreMatricesEqual, except the data vectors are checked in relation to the input tolerance allowed. If any elements differ by more than the tolerance, this function will return false.

func AsCharacter

func AsCharacter[t RCharacter](r RSEXP) (out []t, err error)

AsCharacter extracts the data from the input RSEXP and returns it as a slice of the given type parameter. The resulting slice that contains the same data as the contents of the RSEXP, but a new copy that can be modified independently. If the underlying data connot be coerced into string data, the TypeMismatch error is returned.

func AsNumeric

func AsNumeric[t RNumeric](r RSEXP) (out []t, err error)

AsNumeric extracts data from the input RSEXP and returns it as a slice of the given type parameter. The data is the same data that is contained in the RSEXP, but a new copy that can be modified independently. If the underlying data cannot be coerced into numeric data, the TypeMismatch error is returned.

func ExportRSEXP

func ExportRSEXP[t any](r *RSEXP) (out t, err error)

ExportRSEXP converts an input RSEXP object into the caller's provided C.SEXP type. It is used as a final function to prepare data to be sent back to R. The input type is any, because the rsexp package cannot anticipate the strict C.SEXP type used by the caller. Like NewRSEXP, ExportRSEXP checks the type parameter provided using reflection and returns a NotASEXP error if it is not a C.SEXP.

The intent of ExportRSEXP is that it is always called with the user providing their C.SEXP as the type parameter, like so:

mySEXP, err := [C.SEXP](rgoRSEXP)

func LENGTH

func LENGTH(r RSEXP) int

LENGTH is Rgo's convenience wrapper of R's internal LENGTH/XLENGTH function. It returns the length of an RSEXP.

Types

type Matrix

type Matrix struct {
	// The Matrix header - two integers which specify its dimension
	Nrow, Ncol int

	// The data in a matrix is represented as a single slice of data
	Data []float64
}

Matrix is a representation of a matrix in Go that mirrors how matrices are represented in R. The Matrix contains a vector of all the data, and a header of two integers that contain the dimensions of the matrix. The Data vector is organized so that column indices are together, but row indices are not. In other words, the data can be thought of as a concatenation of several vectors, each of which contains the data for one column.

For example, the following Matrix:

Matrix{Nrow: 3, Ncol: 2, Data: []float64{1.1,2.2,3.3,4.4,5.5,6.6}}

will look like this:

[1.1, 4.4
 2.2, 5.5
 3.3, 6.6]

Matrix data is accessed using 0-based indexing, which is natural in Go but differs from R. For example, the 0th row in the example matrix is [1.1, 4.4], while the "1st" row is [2.2, 5.5].

func AsMatrix

func AsMatrix(r RSEXP) (out Matrix, err error)

AsMatrix returns a matrix based on the input RSEXP. All matrices must contain doubles/float64s with a dimension attribute. The data returned by this function is a copy of the data contained in the RSEXP that can be modified independently. If the data in the RSEXP cannot be coerced into a matrix, the TypeMismatch error is returned.

func CopyMatrix

func CopyMatrix(in Matrix) (out Matrix)

CopyMatrix creates an exact copy of an existing matrix. The copies are independent, so that the output matrix can be changed without changing the input matrix and vice versa.

func CreateIdentity

func CreateIdentity(size int) (*Matrix, error)

CreateIdentity creates an identity matrix, which is always square by definition, of the input dimension. An identity matrix is a matrix will all 0s, except for having a 1 in each element of the diagonal. If the given size is impossible, it will return an InvalidIndex error.

func CreateZeros

func CreateZeros(Nrow, Ncol int) (*Matrix, error)

CreateZeros creates a matrix of the given dimensions in which every element is 0. If the given dimensions are nonsensical (negative, for example) it will return an InvalidIndex error.

func MatrixAdd

func MatrixAdd(A, B *Matrix) (C *Matrix, err error)

MatrixAdd adds two matrices. Matrix addition is done by adding each element of the two matrices together, so they must be of identical size. If they are not, a SizeMismatch error will be returned.

func MatrixMultiply

func MatrixMultiply(A, B *Matrix) (C *Matrix, err error)

MatrixMultiply performs a matrix multiplication of two matrices. This is not an element-wise multiplication, but a true multiplication as defined in elementary linear algebra. In matrix multiplication, order matters. Two matrices A and B can only be multiplied if A has the same number of rows as B has number of columns. If the dimensions of the input matrices do not allow for a multiplication, a SizeMismatch error is returned.

func NewMatrix

func NewMatrix(Nrow, Ncol int, data []float64) (*Matrix, error)

NewMatrix creates a new matrix given a vector of data. The number of rows and columns must be provided, and it assumes the data is already in the order a Matrix should be, with column indexes adjacent. In other words, the data vector should be a concatenation of several vectors, one for each column. NewMatrix makes a copy of the input slice, so that changing the slice later will not affect the data in the matrix. If the provided dimensions don't match the length of the provided data, an ImpossibleMatrix error will be returned.

func (*Matrix) AddConstant

func (m *Matrix) AddConstant(c float64)

AddConstant adds a constant to every element of a matrix. There is no SubtractConstant method. To subtract a constant N from a matrix, add its negative, -N.

func (*Matrix) AppendCol

func (m *Matrix) AppendCol(data []float64) error

AppendCol appends a column onto an existing matrix and updates the dimension metadata accordingly. If the provided data column is not equal to the number of rows in the matrix, it will return a SizeMismatch error.

func (*Matrix) AppendRow

func (m *Matrix) AppendRow(data []float64) error

AppendRow appends a row onto an existing matrix and updates the dimension metadata accordingly. If the length of the provided row is not equal to the number of columns in the matrix, it will return a SizeMismatch error.

func (*Matrix) CreateTranspose

func (m *Matrix) CreateTranspose() *Matrix

CreateTranspose creates a new matrix which is a transpose of the input matrix. The output matrix is created from a copy of the input matrix such that they can be altered independently.

func (*Matrix) GetCol

func (m *Matrix) GetCol(ind int) ([]float64, error)

GetCol gets the column of the matrix specified by the provided index, using 0-based indexing. The first column of a matrix is index 0, even though it may be more intuitive that it should be 1. If the input index is too big, it will return a IndexOutOfBounds error. If you get this error, there's a good chance it's just an off-by-one error. The resulting slice does not point to the matrix itself, so it can be edited without altering the matrix.

func (*Matrix) GetInd

func (m *Matrix) GetInd(row, col int) (float64, error)

This method returns the value in the element of the matrix defined by the inputs.

func (*Matrix) GetRow

func (m *Matrix) GetRow(ind int) ([]float64, error)

GetRow gets the row of the matrix specified by the provided index, using 0-based indexing. The first row of a matrix is index 0, even though it may be more intuitive that it should be 1. If the input index is too big, it will return a IndexOutOfBounds error. If you get this error, there's a good chance it's just an off-by-one error. The resulting slice does not point to the matrix itself, so it can be edited without altering the matrix.

func (*Matrix) MultiplyConstant

func (m *Matrix) MultiplyConstant(c float64)

MultiplyConstant multiplies each element of a matrix by a constant. There is no DivideConstant method. To divide a matrix by a constant N, multiply it by its reciprocal, 1/N.

func (*Matrix) SetCol

func (m *Matrix) SetCol(ind int, data []float64) error

SetCol sets the column of the matrix, specified by the input index, to match the data provided. If the length of the provided column is not of the same as the number of rows in the matrix, it will return a SizeMismatch error.

func (*Matrix) SetInd

func (m *Matrix) SetInd(row, col int, data float64) error

This method alters the value in the element of the matrix defined by the inputs to the input value.

func (*Matrix) SetRow

func (m *Matrix) SetRow(ind int, data []float64) error

SetRow sets the row of the matrix, specified by the input index, to match the data provided. If the provided data is not of the same length as the number of columns in the matrix, it will return a SizeMismatch error.

type RCharacter

type RCharacter interface {
	~string | ~[]byte
}

RCharacter is a type parameter of Go types that map well onto R's character type, which is a string and a byte slice.

type RNumeric

type RNumeric interface {
	~float64 | ~float32 |
		~int | ~int8 | ~int16 | ~int32 | ~int64
}

RNumeric is a type parameter of Go types that map well onto R's numeric types, including both doubles and integers. It includes both float types and all int types, but does not contain unsigned integers because R has no equivalent type.

type RSEXP

type RSEXP C.SEXP

RSEXP is the workhorse type of the rsexp package. It is an identical to R's SEXP implementation in C. It is used for any operation that deals with sending, receiving, or modifying data that moves from R to Go or from Go back to R.

The RSEXP type exists because Cgo does not allow packages to export C types. Therefore, the C.SEXP that is defined in the rsexp package is different from a C.SEXP in the caller's main package. The RSEXP type acts as a go-between, allowing the use of functions in this package to extract, modify, or create SEXP objects without needing to write additional C code.

func CharacterToRSEXP

func CharacterToRSEXP[t RCharacter](in []t) *RSEXP

CharacterToRSEXP converts a slice of strings (or byte slices) into a C.SEXP, represented by the returned RSEXP data. The R representation will have the same data as the input slice and be the STRSXP type (aka the character type in R).

func MakeDataFrame

func MakeDataFrame(rowNames, colNames []string, dataColumns ...*RSEXP) (*RSEXP, error)

MakeDataFrame creates an R data frame from the provided inputs and returns its representing RSEXP object.

It creates a data frame based on the provided data columns (as RSEXP objects) and column names. Users may directly provide row names or an empty slice. If an empty slice is provided, then row names are automatically generated as the row indexes, starting at 1 instead of 0 to be consistent with R.

MakeDataFrame is strict about making sure the provided inputs can create a valid R data frame. The number of column names and data columns provided must match. Likewise, the lengths of all the provided data columns must match and, if row names are provided, match the number of provided row names. If any of these conditions are false, then a LengthMismatch error will be returned with more detail about which condition failed.

MakeDataFrame also checks that the types of all the provided data columns are valid types according to Rgo and that they can be used to create a column in a data frame. If these conditions are not met, an UnsupportedType error will be returned. Right now, this list includes integer vectors, real vectors, and string vectors only. Examples of invalid types include lists, data frames, or other nested SEXP objects.

func MakeList

func MakeList(in ...*RSEXP) *RSEXP

MakeList creates an R list from the provided inputs and returns its representing RSEXP object. Unlike MakeDataFrame and MakeNamedList, there are no restrictions on the data that is provided.

func MakeNamedList

func MakeNamedList(names []string, data ...*RSEXP) (*RSEXP, error)

MakeNamedList creates a named list based on the provided input names and data and returns its representing RSEXP object. Users must provide the names and each element of the list. If number of names and elements provided do not match, a LengthMismatch error is returned. Elements of a named list may be of any valid SEXP type.

func MatrixToRSEXP

func MatrixToRSEXP(in Matrix) *RSEXP

MatrixToRSEXP converts a Matrix a C.SEXP, represented by the returned RSEXP data. The R representation will have the same data and dimensions as the input Matrix and be of the REALSXP type (aka a double in R).

func NewRSEXP

func NewRSEXP(in any) (r RSEXP, err error)

NewRSEXP creates an RSEXP object from the function input. It attempts to create an RSEXP object from any input, but only succeeds if the provided type is a C.SEXP or a *C.SEXP.

It would be ideal for this function to have a more limited set of input types (like only those that can be coerced to a C.SEXP), but checking the "coercibility" of an input (without knowing the universe of input types a priori) is impossible at compile time in Go.

Generally speaking, a failed type cast in Go results in a panic. NewRSEXP returns useful errors to the fullest extent possible, but guaranteeing no runtime failures or panics is impossible.

NewRSEXP uses a combination of reflection and the unsafe package to coerce the input into Rgo's C.SEXP type. First, it creates an unsafe pointer to the underlying data. Then, it uses reflection to verify that the input type is either a C.SEXP or a *C.SEXP. Then, it performs the coercion. If this fails at any point, it returns a NotASEXP error. If the input is a SEXP, but not one of the types supported by the Rgo package, then it returns an UnsupportedType error.

func NumericToRSEXP

func NumericToRSEXP[t RNumeric](in []t) *RSEXP

NumericToRSEXP converts a slice of numeric data into a C.SEXP, represented by the returned RSEXP data. The R representation will have the same data as the input slice and be the REALSXP type (aka a double in R). Because the intent of this function is to prepare data to be sent back to R, which largely treats doubles and integers the same, this function cannot return an RSEXP of type INTSXP.

type RSEXPTYPE

type RSEXPTYPE int

RSEXPTYPE is the Go equivalent of R's type enumerations for SEXP types. Package constants match the enumerations used by R and have the same names.

const (
	CHARSXP RSEXPTYPE = 9
	INTSXP  RSEXPTYPE = 13
	REALSXP RSEXPTYPE = 14

	// A STRSXP is a vector of strings, where each element points to a CHARSXP.
	STRSXP RSEXPTYPE = 16

	// VECSXP is a list, which is not obvious from the name. Each element of a VECSXP is a SEXP and can be of any type.
	VECSXP RSEXPTYPE = 19
)

These constants are enumerations of the SEXPTYPEs that are part of R's internals. There are about 2 dozen in all, Rgo only supports 5 of them.

func TYPEOF

func TYPEOF(r RSEXP) RSEXPTYPE

TYPEOF is Rgo's convenience wrapper of R's internal TYPEOF function. It returns the type of an RSEXP, matching the enumerations used by R itself.

Directories

Path Synopsis
This tests the rgo package
This tests the rgo package

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL