% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/cluster_data.R
\name{build_clusters}
\alias{build_clusters}
\title{Cluster Sequences by Dissimilarity}
\usage{
build_clusters(
  data,
  k,
  dissimilarity = "hamming",
  method = "pam",
  na_syms = c("*", "\%"),
  weighted = FALSE,
  lambda = 1,
  seed = NULL,
  q = 2L,
  p = 0.1,
  covariates = NULL,
  estimator = c("auto", "firth", "multinom", "chisq"),
  ...
)
}
\arguments{
\item{data}{Input data. Accepts multiple formats:
\describe{
\item{data.frame / matrix}{Wide-format sequences (rows = sequences,
columns = time points, values = state names).}
\item{netobject}{A network object from \code{\link{build_network}}.
Extracts the stored sequence data. Only valid for sequence-based
methods (relative, frequency, co_occurrence, attention).}
\item{tna}{A tna model from the tna package. Decodes the
integer-encoded sequence data using stored labels.}
\item{cograph_network}{A cograph network object. Extracts the
stored sequence data.}
}}

\item{k}{Integer. Number of clusters (must be between 2 and
\code{nrow(data) - 1}).}

\item{dissimilarity}{Character. Distance metric. One of \code{"hamming"},
\code{"osa"} (optimal string alignment), \code{"lv"} (Levenshtein),
\code{"dl"} (Damerau-Levenshtein), \code{"lcs"} (longest common
subsequence), \code{"qgram"}, \code{"cosine"}, \code{"jaccard"},
\code{"jw"} (Jaro-Winkler). Default: \code{"hamming"}.}

\item{method}{Character. Clustering method. \code{"pam"} for Partitioning
Around Medoids, or a hierarchical method: \code{"ward.D2"},
\code{"ward.D"}, \code{"complete"}, \code{"average"}, \code{"single"},
\code{"mcquitty"}, \code{"median"}, \code{"centroid"}.
Default: \code{"pam"}.}

\item{na_syms}{Character vector. Symbols treated as missing values.
Default: \code{c("*", "\%")}.

\strong{Missing-value distance rule:} after symbols are converted to
\code{NA}, missing values are encoded as a single comparable sentinel
state -- \emph{not} pairwise-deleted. Two missing values in the same
position match (distance contribution 0); a missing value paired with
any observed state mismatches (distance contribution 1 for Hamming,
etc.). This is the conventional behaviour for aligned sequence
matrices because pairwise deletion would change the effective length
of every pair and break the metric. If you want pairwise deletion or
a different missing-value semantic, drop or recode the missing cells
before passing the data in.}

\item{weighted}{Logical. Apply exponential decay weighting to Hamming
distance positions? Only valid when \code{dissimilarity = "hamming"}.
Default: \code{FALSE}.}

\item{lambda}{Numeric. Non-negative decay rate for weighted Hamming.
Higher values weight earlier positions more strongly. Default: 1.}

\item{seed}{Integer or NULL. Random seed for reproducibility. Default:
\code{NULL}.}

\item{q}{Integer. Size of q-grams for \code{"qgram"}, \code{"cosine"},
and \code{"jaccard"} distances. Default: \code{2L}.}

\item{p}{Numeric. Winkler prefix penalty for Jaro-Winkler distance.
Must be between 0 and 0.25. Default: \code{0.1}.}

\item{covariates}{Optional. Post-hoc covariate analysis of cluster
membership. Accepts:
\describe{
\item{string}{Single column name, e.g. \code{"Age"}. Resolved
against \code{x$metadata} (and \code{x$data}) for
\code{netobject} or \code{cograph_network} input.}
\item{character vector}{\code{c("Age", "Gender")}, same lookup.}
\item{formula}{\code{~ Age + Gender}, same lookup; supports
\code{"Age + Gender"} string form too.}
\item{data.frame}{All columns used as covariates verbatim;
must have one row per sequence.}
\item{NULL}{No covariate analysis (default).}
}
For \code{netobject} or \code{cograph_network} input, names are
resolved against \code{$metadata} first and then non-state columns
of \code{$data}, so a typical call looks like
\code{build_clusters(net, k = 3, covariates = "session_label")}
without pre-extracting a data.frame. \code{tna} input requires the
data.frame form. Results are stored in \code{$covariates}.}

\item{estimator}{Multinomial logit fitter for the covariate analysis.
\code{"auto"} (default) inspects the cluster x covariate cross-tab
and falls back to \code{"firth"} only when any cell has fewer than
5 observations (quasi-complete separation risk); otherwise uses
the much faster \code{"multinom"}. \code{"firth"} forces Firth's
penalised likelihood via \code{brglm2::brmultinom} -- bias-reduced
and finite under separation, but ~200x slower than multinom on
well-conditioned data. \code{"multinom"} forces classical ML via
\code{nnet::multinom}; warns because rare-cell separation produces
astronomical ORs with degenerate CIs (silent failure).
\code{"chisq"} runs WeightedCluster-style descriptive tests
(chi-square + Cramer's V + standardized adjusted residuals for
factors; Kruskal-Wallis + eta-squared for numerics).}

\item{...}{Unsupported. Supplying unused arguments raises an error.}
}
\value{
An object of class \code{"net_clustering"} containing:
\describe{
\item{data}{The original input data.}
\item{k}{Number of clusters.}
\item{assignments}{Named integer vector of cluster assignments.}
\item{silhouette}{Overall average silhouette width.}
\item{sizes}{Named integer vector of cluster sizes.}
\item{method}{Clustering method used.}
\item{dissimilarity}{Distance metric used.}
\item{distance}{The computed dissimilarity matrix (\code{dist} object).}
\item{medoids}{Integer vector of medoid row indices (PAM only; NULL
for hierarchical methods).}
\item{seed}{Seed used (or NULL).}
\item{weighted}{Logical, whether weighted Hamming was used.}
\item{lambda}{Lambda value used (0 if not weighted).}
}
}
\description{
Clusters wide-format sequences using pairwise string dissimilarity and
either PAM (Partitioning Around Medoids) or hierarchical clustering.
Supports 9 distance metrics including temporal weighting for Hamming
distance. When the \pkg{stringdist} package is available, uses C-level
distance computation for 100-1000x speedup on edit distances.
}
\examples{
seqs <- data.frame(V1 = c("A","B","C","A","B"), V2 = c("B","C","A","B","A"),
                   V3 = c("C","A","B","C","B"))
cl <- build_clusters(seqs, k = 2)
cl
\donttest{
seqs <- data.frame(
  V1 = sample(LETTERS[1:3], 20, TRUE), V2 = sample(LETTERS[1:3], 20, TRUE),
  V3 = sample(LETTERS[1:3], 20, TRUE), V4 = sample(LETTERS[1:3], 20, TRUE)
)
cl <- build_clusters(seqs, k = 2)
print(cl)
summary(cl)
}

}
