The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

3. Clustering histogram data: k-means and fuzzy c-means

library(HistDAWass)

The clustering methods of HistDAWass group the individuals (rows) of a MatH object using the L2 Wasserstein distance between histograms. The prototype of each cluster is itself a histogram for each variable (the Wasserstein mean of the histograms of the cluster).

We use the OzoneFull data: 78 stations in the USA described by the histograms of four variables.

get.MatH.main.info(OzoneFull)$varnames
#> [1] "Ozone.Conc.ppb"         "Temperature.C"          "Solar.Radiation.WattM2"
#> [4] "Wind.Speed.mSec"

Two options are common to all the methods:

The algorithms start from random partitions, so we fix the seed and repeat each algorithm rep times, keeping the best solution.

K-means

set.seed(1)
km <- WH_kmeans(OzoneFull, k = 3, rep = 5,
                simplify = TRUE, qua = 20, standardize = TRUE)
names(km$solution)
#> [1] "IDX"         "cardinality" "centers"     "Crit"
km$solution$cardinality
#> IDX
#> Cl.1 Cl.2 Cl.3 
#>   31   13   34
km$quality   # proportion of the total sum of squares explained by the partition
#> [1] 0.4845037
head(km$solution$IDX)
#> I1 I2 I3 I4 I5 I6 
#>  1  1  3  3  1  3

The prototypes are a MatH object, so they can be plotted:

plot(km$solution$centers)

Choosing the number of clusters

The quality (between / total sum of squares) increases with k; a common choice is where the gain begins to flatten (elbow):

set.seed(1)
qual <- sapply(2:7, function(k)
  WH_kmeans(OzoneFull, k = k, rep = 5, simplify = TRUE, qua = 20,
            standardize = TRUE)$quality)
round(qual, 3)
#> [1] 0.345 0.485 0.571 0.639 0.682 0.713
plot(2:7, qual, type = "b", xlab = "k", ylab = "quality")

K-means with adaptive distances

WH_adaptive.kmeans() also estimates a weight for each variable, so that the variables that better separate the clusters weigh more. With schema = 2 there is a weight for the position (mean) and one for the variability (centred distribution) of each variable; with schema = 3 or 4 the weights also change with the cluster.

set.seed(1)
akm <- WH_adaptive.kmeans(OzoneFull, k = 3, schema = 2, rep = 5,
                          simplify = TRUE, qua = 20, standardize = TRUE)
round(akm$weights[, 1], 3)   # the same for all the clusters with schema = 2
#>         Ozone.Conc.ppb(Cen.)         Ozone.Conc.ppb(Var.) 
#>                        0.990                        1.100 
#>          Temperature.C(Cen.)          Temperature.C(Var.) 
#>                        0.502                        1.699 
#> Solar.Radiation.WattM2(Cen.) Solar.Radiation.WattM2(Var.) 
#>                        2.172                        0.834 
#>        Wind.Speed.mSec(Cen.)        Wind.Speed.mSec(Var.) 
#>                        0.925                        0.642
akm$cardinality
#> IDX
#> Cl.1 Cl.2 Cl.3 
#>   28   13   37
akm$quality
#> [1] 0.5701985

By default the product of the weights is one (weight.sys = "PROD"); with weight.sys = "SUM" they sum to one.

Fuzzy c-means

WH_fcmeans() assigns to each individual a degree of membership to each cluster; m (greater than 1) controls the fuzziness.

set.seed(1)
fcm <- WH_fcmeans(OzoneFull, k = 3, m = 1.6, rep = 5,
                  simplify = TRUE, qua = 20, standardize = TRUE)
round(head(fcm$solution$membership), 3)
#>     [,1]  [,2]  [,3]
#> I1 0.955 0.002 0.043
#> I2 0.599 0.017 0.383
#> I3 0.185 0.119 0.696
#> I4 0.010 0.004 0.986
#> I5 0.920 0.002 0.077
#> I6 0.074 0.018 0.908
fcm$solution$cardinality   # crisp assignment to the cluster of maximum membership
#> IDX
#> Cl.1 Cl.2 Cl.3 
#>   33   13   32
fcm$quality
#> [1] 0.4470096

Individuals with no clearly dominant membership lie between clusters:

maxmemb <- apply(fcm$solution$membership, 1, max)
sum(maxmemb < 0.6)
#> [1] 12

Fuzzy c-means with adaptive distances

set.seed(1)
afcm <- WH_adaptive_fcmeans(OzoneFull, k = 3, schema = 2, m = 1.6, rep = 3,
                            simplify = TRUE, qua = 20, standardize = TRUE)
round(afcm$solution$var.weights[, 1], 3)
#>         Ozone.Conc.ppb(Cen.)         Ozone.Conc.ppb(Var.) 
#>                        0.944                        1.110 
#>          Temperature.C(Cen.)          Temperature.C(Var.) 
#>                        0.599                        1.776 
#> Solar.Radiation.WattM2(Cen.) Solar.Radiation.WattM2(Var.) 
#>                        1.776                        0.794 
#>        Wind.Speed.mSec(Cen.)        Wind.Speed.mSec(Var.) 
#>                        0.995                        0.638
afcm$quality
#> [1] 0.5286997

Comparing partitions

Hierarchical clustering (WH_hclust(), a wrapper of stats::hclust()) gives a dendrogram that can be cut at any number of clusters:

hc <- WH_hclust(OzoneFull, simplify = TRUE, qua = 20, standardize = TRUE,
                method = "ward.D2")
plot(hc, labels = FALSE, main = "OzoneFull, Ward method")

table(hclust = cutree(hc, k = 3), kmeans = km$solution$IDX)
#>       kmeans
#> hclust  1  2  3
#>      1 12  0 34
#>      2 19  0  0
#>      3  0 13  0

Self-organising maps (WH_2d_Kohonen_maps() and WH_2d_Adaptive_Kohonen_maps()) are also available.

References

Irpino, A., Verde, R., Lechevallier, Y. (2006) Dynamic clustering of histograms using Wasserstein metric. In: COMPSTAT 2006, Physica-Verlag, 869-876.

Irpino, A., Verde, R., De Carvalho, F.A.T. (2014) Dynamic clustering of histogram data based on adaptive squared Wasserstein distances. Expert Systems with Applications, 41, 3351-3366. https://doi.org/10.1016/j.eswa.2013.12.001

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.