The dbscan package implements two clustering algorithms based on shared nearest neighbors:
jpclust() implements Jarvis–Patrick clustering;
andsNNclust() implements shared nearest neighbor (SNN)
clustering with a DBSCAN-style density step.Both algorithms replace the original distances between observations with a local similarity: two observations are more similar when their lists of nearest neighbors have a large overlap. This can be useful when Euclidean distance alone does not describe local cluster structure well, especially in data with irregular shapes or differing local densities.
We use the DS3 data supplied with the package. It
contains six irregularly-shaped groups together with noise and a
sinusoidal structure that intersects the groups.
For numeric matrices and data frames, both functions use Euclidean distance and fast kd-tree nearest-neighbor search. Variable scales therefore matter. Standardize variables when their units are not comparable and scaling is appropriate for the application.
Jarvis–Patrick clustering links two observations when both of the following conditions hold:
k-nearest-neighbor
list.kt neighbors.Connected observations form clusters. Here we use neighborhoods of 20 points and require 12 shared neighbors.
jp <- jpclust(x, k = 20, kt = 12)
c(clusters = ncluster(jp), noise = nnoise(jp))
#> clusters noise
#> 43 0Cluster assignments are stored in jp$cluster in the same
order as the rows of x. The result also records the
algorithm name, distance metric, and parameters.
names(jp)
#> [1] "cluster" "type" "metric" "param"
jp$param
#> $k
#> [1] 20
#>
#> $kt
#> [1] 12
head(jp$cluster)
#> [1] 1 1 1 1 2 1The convenience function clplot() marks observations by
cluster. It uses the first two columns when the data have more than two
dimensions.
clplot(x, jp, cex = 0.25, main = "Jarvis-Patrick clustering")
#> Warning in hullplot(x, cl = cl, col = col, pch = pch, cex = cex, main = main, :
#> Not enough colors. Some colors will be reused.Jarvis–Patrick clustering has no explicit concept of noise.
Observations not connected to a larger group become singleton or other
small clusters rather than receiving label 0. Conversely, a
chain of qualifying links can join larger structures. In this example,
the sinusoidal points can connect groups that otherwise appear
separate.
Increasing kt makes the link rule stricter. This removes
edges from the shared-neighbor graph and can split clusters or create
more small components. Decreasing kt adds edges and can
merge clusters through chaining.
jp_settings <- c(10, 12, 14)
jp_fits <- lapply(
jp_settings,
function(threshold) jpclust(x, k = 20, kt = threshold)
)
data.frame(
kt = jp_settings,
clusters = vapply(jp_fits, ncluster, integer(1)),
singleton_clusters = vapply(
jp_fits,
function(fit) sum(table(fit$cluster) == 1L),
integer(1)
)
)
#> kt clusters singleton_clusters
#> 1 10 24 18
#> 2 12 43 33
#> 3 14 189 118The required range is 1 <= kt <= k. A useful
setting should preserve stable, meaningful components without
fragmenting the data into many tiny clusters.
Nearest-neighbor search can be computed once and reused. This is
especially helpful when comparing algorithms or parameter settings on a
large data set. Compute at least the largest k that any
later fit will need.
nn <- kNN(x, k = 30)
jp_from_nn <- jpclust(nn, k = 20, kt = 12)
snn_from_nn <- sNNclust(nn, k = 20, eps = 7, minPts = 16)
c(
jp_same = identical(jp$cluster, jp_from_nn$cluster),
snn_same = identical(snn$cluster, snn_from_nn$cluster)
)
#> jp_same snn_same
#> TRUE TRUEWhen the full stored neighborhood should be used, k may
be omitted from jpclust() because it can be recovered from
the kNN object. Keep k explicit when using
only the first part of a larger precomputed neighborhood.
Both clustering functions also accept a dist object.
This permits other distance measures, at the cost of storing all
pairwise distances and giving up the fast kd-tree search.
d <- dist(my_data, method = "manhattan")
jp_manhattan <- jpclust(d, k = 20, kt = 12)
snn_manhattan <- sNNclust(d, k = 20, eps = 7, minPts = 16)The distance matrix must be finite. Choose a distance measure, transformations, and scaling based on the meaning of the variables rather than solely on the resulting number of clusters.
Use jpclust() when a simple connected-components
interpretation of mutual shared-neighbor links is appropriate and small
components are meaningful. Use sNNclust() when the analysis
needs an explicit density requirement, noise labels, or control over
core and border points. For either method, k sets the
neighborhood scale and should reflect the smallest local structure that
needs to be retained.
Jarvis, R. A. and Patrick, E. A. (1973). Clustering Using a Similarity Measure Based on Shared Near Neighbors. IEEE Transactions on Computers, 22(11), 1025–1034. doi:10.1109/T-C.1973.223640.
Ertoz, L., Steinbach, M., and Kumar, V. (2003). Finding Clusters of Different Sizes, Shapes, and Densities in Noisy, High Dimensional Data. Proceedings of the 2003 SIAM International Conference on Data Mining, 47–58. doi:10.1137/1.9781611972733.5.