Extending fcaR: Equivalence Rules for Implications

Introduction

In this vignette, we present a way to extend the functionalities provided in the fcaR package: define new operations on an ImplicationSet.

First, we load the fcaR package by:

library(fcaR)

Let us use the planets dataset included in the package:

fc <- FormalContext$new(planets)
fc$find_implications()

The Registry

In fcaR, we have decided to use a registry from the registry package to store the operations that can be performed on an ImplicationSet. Currently, its purpose is to store equivalence rules, that is, methods that obtain equivalent ImplicationSets from one given.

This registry is called equivalencesRegistry and one can inspect its contents by:

equivalencesRegistry$get_entry_names()
#> [1] "Composition"          "Generalization"       "Reduction"           
#> [4] "Simplification"       "Right Simplification" "Reorder"

These names correspond to the methods that are added to the registry by default, and are used to index those methods. Every method is accompanied by a description, so we can see its definition:

equivalencesRegistry$get_entry("Composition")
#>      method Composition
#>         fun <<function>>
#> description A -> B and A -> C equivalent to A -> BC

We can even use abbreviated names to refer to the method:

equivalencesRegistry$get_entry("comp")
#>      method Composition
#>         fun <<function>>
#> description A -> B and A -> C equivalent to A -> BC

Use of the Rules

As explained in the vignette corresponding to ImplicationSets, we can use any of these methods by using the apply_rules() method in the ImplicationSet:

fc$implications$apply_rules(c("comp", "simp"))

Definition of New Equivalence Rules

The way to extend the functionality in fcaR is to define new equivalence operators and include them in the registry.

In order to add a new method, we use:

equivalencesRegistry$set_entry(method = "Method name",
                               fun = method_function,
                               description = "Method description")

where method_function() must be a function with the following scheme:

method_function <- function(LHS, RHS, attributes) {
  
  # LHS and RHS are the sparse matrices of the left-hand and
  # right-hand sides of the implications
  # attributes is the vector of attribute names
  # The three arguments are mandatory
  
  # Perform operations on LHS and RHS
  # ...
  
  # Must return a list with two components: lhs and rhs
  return(list(lhs = LHS,
              rhs = RHS))
  
}

The method_function() function must be defined before adding the method to the registry. Once the method is added, it can be executed by using the corresponding call to apply_rules().

An Example

Let us define an operator which randomly reorders the implications. Evidently, this operation provides an equivalent ImplicationSet.

In this case, we begin by defining the method function:

random_reorder <- function(LHS, RHS, attributes) {
  
  # Remember: attributes are in rows, implications are
  # in columns.
  # Random order for columns:
  o <- sample(ncol(LHS), ncol(LHS))
  
  # Return the reordered implications
  return(list(lhs = LHS[, o],
              rhs = RHS[, o]))
  
}

Once we have defined the function, we add the method to the registry:

equivalencesRegistry$set_entry(method = "Randomize",
                               fun = random_reorder,
                               description = "Randomize the order of the implications.")

If we inspect the registry, we obtain the list of the methods, including the one we have just inserted:

equivalencesRegistry$get_entry_names()
#> [1] "Composition"          "Generalization"       "Reduction"           
#> [4] "Simplification"       "Right Simplification" "Reorder"             
#> [7] "Randomize"

We can apply the new method:

# Original implications
fc$implications
#> Implication set with 10 implications.
#> Rule 1: {no_moon} -> {small, near}
#> Rule 2: {far} -> {moon}
#> Rule 3: {near} -> {small}
#> Rule 4: {large} -> {far, moon}
#> Rule 5: {medium} -> {far, moon}
#> Rule 6: {medium, large, far, moon} -> {small, near, no_moon}
#> Rule 7: {small, near, moon, no_moon} -> {medium, large, far}
#> Rule 8: {small, near, far, moon} -> {medium, large, no_moon}
#> Rule 9: {small, large, far, moon} -> {medium, near, no_moon}
#> Rule 10: {small, medium, far, moon} -> {large, near, no_moon}
# Apply the randomize method
fc$implications$apply_rules("randomize")
#> Processing batch
#> --> Randomize: from 10 to 10 in 0 secs.
#> Batch took 0 secs.
# Reordered implications
fc$implications
#> Implication set with 10 implications.
#> Rule 1: {small, near, far, moon} -> {medium, large, no_moon}
#> Rule 2: {far} -> {moon}
#> Rule 3: {small, near, moon, no_moon} -> {medium, large, far}
#> Rule 4: {no_moon} -> {small, near}
#> Rule 5: {near} -> {small}
#> Rule 6: {small, large, far, moon} -> {medium, near, no_moon}
#> Rule 7: {large} -> {far, moon}
#> Rule 8: {small, medium, far, moon} -> {large, near, no_moon}
#> Rule 9: {medium, large, far, moon} -> {small, near, no_moon}
#> Rule 10: {medium} -> {far, moon}