ClusTorus is a package for clustering multivariate angular data, especially for protein structure data. ClusTorus provides various clustering algorithms designed with the conformal prediction framework, which can deal with the outliers. The package suggests various methods for fitting the algorithms, and some of them will be introduced soon. The package also provides some simple tools for handling angluar data, such as angular subtraction, computing angular distance, etc. Now, check how to use ClusTorus briefly.

Data Loading and Handling

ClusTorus provides two toy datasets, which are used in Jung, et.al.(2021), “Clustering on the Torus by Conformal Prediction”, Annals of Applied Statistics. The dataset we will use here is sampled from a mixture of \(K = 3\) clusters, where the first cluster is sampled from a spherical normal distribution with size \(n_1 = 100\), the second cluster of size \(n_2 = 350\) is from the uniform distribution on a large “L”-shaped region, and the third cluster of size 50 is sampled from the uniform distribution on the entire \(\mathbb{T}^2\).

library(ClusTorus)
library(tidyverse)

data <- toydata2
head(data)
#>        phi       psi label
#> 1 2.730154 0.2819140     1
#> 2 3.004941 0.7895926     1
#> 3 2.733773 0.9507966     1
#> 4 2.976103 0.6336779     1
#> 5 2.655409 0.6599409     1
#> 6 3.196350 0.6545109     1

data %>% ggplot(aes(x = phi, y = psi, color = label)) + geom_point() +
  scale_x_continuous(breaks = c(0,1,2,3,4)*pi/2, labels = c("0","pi/2","pi","3pi/2","2pi"), limits = c(0,2*pi))+
  scale_y_continuous(breaks = c(0,1,2,3,4)*pi/2, labels = c("0","pi/2","pi","3pi/2","2pi"), limits = c(0,2*pi))+
  ggtitle('Data set 2 with true labels')

ClusTorus provides the function on.torus, which converts the radian scale data to be on \([0, 2\pi)^d\), where \(d\) means the number of dimension. In this case, the provided dataset is already on \([0, 2\pi)^d\), and thus we don’t need to use on.torus.

Clustering with Various Options

Now, we are ready to implement clustering algorithms to the data. ClusTorus provides various options for clustering/constructing prediction set, but we will provide only the case for “kmeans - general”. We need to choose hyperparameters: the number of modes or ellipsoids \(J\) and the significance level \(\alpha\). Before choosing the hyperparameter, we will implement the model fitting function icp.torus.score with various hyperparameter options, first.

set.seed(2021)

Jvec <- 5:30
l <- icp.torus(as.matrix(data[, 1:2]),
                            model = "kmeans",
                            kmeansfitmethod = "general",
                            init = "hierarchical",
                            J = Jvec,
                            verbose = FALSE)

The list l contains the icp.torus objects, which consist of fitted parameters for generating clusters, by varying the hypterparameter \(J\). That is, these objects are optimally fitted ingredients for generating clusters for given \(J\). By specifying the significance level, we can get the clusters. But, how to generate optimal clusters/conformal prediction sets? One may think that the hyperparameter which generates the cluster/prediction set of the minimum volume/area will be the optimum for given significance level. The other may think that we can choose the number of mixture components \(J\) by using information criteria such as AIC or BIC. These approaches are implemented in the function hyperparam.torus; the main arguments of hyperparam.torus is data, icp.torus.objects, and option. Analogously, the argument data is analogously for the data. icp.torus.objects is for the list object whose elements are icp.torus objects, such as the list l generated above. The argument option, which is the most important argument, is for the hyperparameter selection criterion. If option = "elbow", then hyperparam.torus selects \(J\) and \(\alpha\) based on the volume based criterion as mentioned above. If option = "AIC" or option = "BIC", then hyperparam.torus selects \(J\) based on the designated information criterion, and selects the most stable \(\alpha\) in the sense of the number of generated clusters. If option = "risk", then it chooses \(J\) which minimizes the sum of the conformity scores and analogously choose the stable \(\alpha\). We will use option = "risk" in this case, and the following codes show the criterion results(\(J\) versus the evaluated criterion), \(\alpha\) results(\(\alpha\) versus the number of clusters), and chosen \(J\) and \(\alpha\).

output <- hyperparam.torus(l, option = "risk")
#> ..........................
#> .......
output
#> Type of conformity score: kmeans general 
#> Optimizing method: risk 
#> -------------
#> Optimally chosen parameters. Number of components =  5 , alpha =  0.084 
#> Results based on criterion risk :
#>     J criterion
#> 1   5  2495.134
#> 2   6  2499.865
#> 3   7  2501.007
#> 4   8  2510.881
#> 5   9  2536.576
#> 6  10  2555.744
#> 7  11  2552.515
#> 8  12  2549.668
#> 9  13  2568.499
#> 10 14  2585.362
#> 11 15  2595.043
#> 12 16  2608.337
#> 13 17  2604.204
#> 14 18  2598.733
#> 15 19  2612.264
#> 16 20  2616.506
#> 17 21  2638.969
#> 18 22  2639.597
#> 19 23  2654.307
#> 20 24  2656.181
#> 21 25  2659.685
#> 22 26  2668.536
#> 23 27  2668.536
#> 24 28  2737.958
#> 25 29  2759.033
#> 26 30  2754.286
#> 
#> Clustering results by varying alpha on [0.0020.15] :
#>    alpha ncluster
#> 1  0.002        1
#> 2  0.004        1
#> 3  0.006        1
#> 4  0.008        1
#> 5  0.010        1
#> 6  0.012        1
#> 7  0.014        1
#> 8  0.016        1
#> 9  0.018        1
#> 10 0.020        2
#> 11 0.022        2
#> 12 0.024        2
#> 13 0.026        2
#> 14 0.028        3
#> 15 0.030        3
#> 16 0.032        4
#> 17 0.034        4
#> 18 0.036        4
#> 19 0.038        4
#> 20 0.040        4
#> 21 0.042        4
#> 22 0.044        4
#> 23 0.046        4
#> 24 0.048        4
#> 25 0.050        4
#> 26 0.052        4
#> 27 0.054        4
#> 28 0.056        4
#> 29 0.058        2
#> 30 0.060        2
#> 31 0.062        2
#> 32 0.064        2
#> 33 0.066        2
#> 34 0.068        2
#> 35 0.070        2
#> 36 0.072        2
#> 37 0.074        2
#> 38 0.076        2
#> 39 0.078        2
#> 40 0.080        2
#> 41 0.082        2
#> 42 0.084        2
#> 43 0.086        2
#> 44 0.088        2
#> 45 0.090        2
#> 46 0.092        2
#> 47 0.094        2
#> 48 0.096        2
#> 49 0.098        2
#> 50 0.100        2
#> 51 0.102        2
#> 52 0.104        2
#> 53 0.106        2
#> 54 0.108        2
#> 55 0.110        2
#> 56 0.112        3
#> 57 0.114        3
#> 58 0.116        3
#> 59 0.118        3
#> 60 0.120        3
#> 61 0.122        3
#> 62 0.124        3
#> 63 0.126        3
#> 64 0.128        3
#> 65 0.130        3
#> 66 0.132        3
#> 67 0.134        3
#> 68 0.136        3
#> 69 0.138        3
#> 70 0.140        3
#> 71 0.142        3
#> 72 0.144        3
#> 73 0.146        3
#> 74 0.148        3
#> 75 0.150        3
#> 
#> Available components:
#> [1] "model"         "option"        "IC.results"    "alpha.results"
#> [5] "icp.torus"     "Jhat"          "alphahat"
plot(output)

hyperparam.torus also provides the selected model; that is, since icp.torus.score is the function for the model fitting, hyperparam.torus selects the model of the chosen \(J\). output$optim$icp.torus is the selected model, and we will generate the resulting clusters with it.

icp.torus.kmeans <- output$icp.torus
alphahat <- output$alphahat

Generating Clusters and Visualization of Clustering Results

With cluster.assign.torus, we can generate the cluster for option mixture and kmeans, and for each data point, the label of cluster is assigned. Moreover, for the case of kmeans, we can check how the cluster is generated directly as below:

c_kmeans <- cluster.assign.torus(icp.torus.kmeans, level = alphahat)
plot(icp.torus.kmeans, level = alphahat)

Also, with the assigned cluster labels, we can visualize the clusters with colored data points;

c_kmeans
#> Number of clusters: 2 
#> -------------
#> Clustering results by log density:
#>  [1] 1 1 1 1 1 1 1 1 1 1
#> cluster sizes: 209 791 
#> 
#> Clustering results by posterior:
#>  [1] 1 1 1 1 1 1 1 1 1 1
#> cluster sizes: 245 755 
#> 
#> Clustering results by representing outliers:
#>  [1] 1 1 1 1 1 1 1 3 1 1
#> cluster sizes: 196 704 100 
#> 
#>  Note: cluster id = 3 represents outliers.
#> 
#> Clustering results by Mahalanobis distance:
#>  [1] 1 1 1 1 1 1 1 1 1 1
#> cluster sizes: 209 791 
#> 
#> 990 clustering results are omitted.
plot(c_kmeans)

It seems that the resulting clusters perform satisfactory compared to the original cluster assignments.

If you clearly follow the examples, it will be relatively easy to use the other options for the argument method = "kde" or method = "mixture" of the model fitting function icp.torus.score, and for the argument option = "AIC", option = "BIC", option = "elbow" of the hyperparameter selecting function hyperparam.torus. You may need the detailed manual. In the package manual, concrete examples and descriptions for the functions and their arguments are listed. Please check the package manual and see the details.