--- title: "Getting started with dbscan" author: "Michael Hahsler" output: rmarkdown::html_vignette: toc: true vignette: > %\VignetteIndexEntry{Getting started with dbscan} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 4 ) library(dbscan) ``` The **dbscan** package provides fast implementations of density-based clustering algorithms. These algorithms can find clusters with irregular shapes and identify observations in sparse regions as noise. This vignette introduces the usual workflow with DBSCAN and briefly shows when HDBSCAN or OPTICS may be a better choice. ## Installation Install the released version from CRAN: ```{r install, eval=FALSE} install.packages("dbscan") ``` Load the package in each R session where you want to use it: ```{r load-package} library(dbscan) ``` ## A first clustering We use the two-dimensional `moons` data included in the package. Each row is an observation and each column is a numeric feature. ```{r data} data("moons") x <- as.matrix(moons) plot(x, pch = 19, asp = 1, main = "Moons data") ``` DBSCAN needs two parameters: * `eps` is the radius of a point's neighborhood. * `minPts` is the minimum number of points, including the point itself, needed to form a dense region. ```{r fit-dbscan} cl <- dbscan(x, eps = 0.45, minPts = 5) cl ``` The cluster assignment for each observation is stored in `cl$cluster`. Positive integers are cluster labels and `0` denotes noise. Cluster numbers are identifiers only; their numeric order has no meaning. ```{r inspect-dbscan} table(cl$cluster) head(cl$cluster) ``` The labels can be added to the original data or used directly for plotting. ```{r add-labels} clustered <- transform(moons, cluster = factor(cl$cluster)) head(clustered) ``` ```{r plot-dbscan} plot( x, col = cl$cluster + 1L, pch = ifelse(cl$cluster == 0, 4, 19), asp = 1, xlab = "X", ylab = "Y", main = "DBSCAN clustering" ) ``` With the default R palette, noise is black because its cluster label is zero. ## Prepare your own data For the fast default search, supply a numeric matrix or data frame without missing or infinite values. DBSCAN uses Euclidean distance, so the scale of the variables matters. A variable measured in large units can otherwise dominate the distance calculation. Standardizing is often appropriate when features use different units: ```{r scaling, eval=FALSE} x <- scale(my_data) ``` Whether scaling is appropriate depends on the meaning of the variables. Do not include identifiers, labels, or unordered factors as numeric features. For a non-Euclidean distance, calculate a `dist` object first and pass it to `dbscan()`; this does not use the fast kd-tree search. ## Choose `minPts` and `eps` There is no single best parameter setting for every data set. A useful starting point is: 1. Choose `minPts` based on the smallest dense group that should count as a cluster. For low-dimensional data, the number of dimensions plus one is a common lower bound; larger values produce smoother, more conservative results. 2. Inspect the sorted distance to each point's `minPts - 1` nearest neighbor. 3. Choose `eps` near a visible bend where the distances begin to increase rapidly. `kNNdistplot()` performs the second step. Supplying `minPts` automatically uses `k = minPts - 1` because a DBSCAN neighborhood also counts the point itself. ```{r knn-distance} kNNdistplot(x, minPts = 5) abline(h = 0.45, col = 2, lty = 2) ``` The bend is a guide rather than an automatic rule. Refit the model with a few nearby values and check whether the important structure is stable: ```{r sensitivity} settings <- c(0.40, 0.45, 0.50) fits <- lapply( settings, function(e) dbscan(x, eps = e, minPts = 5) ) data.frame( eps = settings, clusters = vapply(fits, function(fit) max(fit$cluster), integer(1)), noise = vapply(fits, function(fit) sum(fit$cluster == 0), integer(1)) ) ``` Increasing `eps` tends to merge clusters and label fewer points as noise. Increasing `minPts` makes the density requirement stricter. Domain knowledge and the intended use of the clusters should guide the final choice. ## Assign new observations Although DBSCAN does not learn a conventional prediction model, the package can assign a new observation to the cluster of its nearest non-noise training point within `eps`. If no such point exists, it is assigned to noise. ```{r predict} new_points <- rbind( c(0.0, 0.0), c(1.0, 2.0), c(4.0, 4.0) ) predict(cl, newdata = new_points, data = x) ``` When training data were transformed, apply the same transformation to new observations before prediction. Also keep the original training matrix: it is required by `predict()`. ## When DBSCAN is not the best starting point The package contains several related algorithms: | Algorithm | Useful when | |:--|:--| | `dbscan()` | Clusters have roughly similar density and one meaningful neighborhood radius can be chosen. | | `hdbscan()` | Clusters may have different densities or you want to avoid choosing a global `eps`. | | `optics()` | You want to explore clustering structure over a range of neighborhood radii. | HDBSCAN requires only `minPts` for a basic analysis and extracts stable clusters from a density hierarchy: ```{r hdbscan} hdb <- hdbscan(x, minPts = 5) hdb plot( x, col = hdb$cluster + 1L, pch = ifelse(hdb$cluster == 0, 4, 19), asp = 1, main = "HDBSCAN clustering" ) ``` The HDBSCAN result also contains membership strengths, outlier scores, and a cluster hierarchy. See `vignette("hdbscan", package = "dbscan")` for a more detailed introduction. OPTICS creates an ordering whose reachability plot exposes clustering structure. Valleys in the plot correspond to dense groups. A DBSCAN-like clustering can then be extracted at different thresholds without rerunning OPTICS. ```{r optics} opt <- optics(x, minPts = 5) plot(opt) opt_cl <- extractDBSCAN(opt, eps_cl = 0.45) table(opt_cl$cluster) ``` ## Next steps Useful help pages include: * `?dbscan` for DBSCAN options, core-point detection, and more examples; * `?hdbscan` and `?optics` for hierarchical and multi-scale clustering; * `?kNN` and `?frNN` for fast nearest-neighbor search; * `?lof` and `?glosh` for outlier scoring; and * `?dbcv` for density-based cluster validation. For a citable description of the algorithms and implementation, use `citation("dbscan")`.