Last updated: 2025-02-18
Checks: 7 0
Knit directory: Tutorials/
This reproducible R Markdown analysis was created with workflowr (version 1.7.1). The Checks tab describes the reproducibility checks that were applied when the results were created. The Past versions tab lists the development history.
Great! Since the R Markdown file has been committed to the Git repository, you know the exact version of the code that produced these results.
Great job! The global environment was empty. Objects defined in the global environment can affect the analysis in your R Markdown file in unknown ways. For reproduciblity it’s best to always run the code in an empty environment.
The command set.seed(20240905)
was run prior to running
the code in the R Markdown file. Setting a seed ensures that any results
that rely on randomness, e.g. subsampling or permutations, are
reproducible.
Great job! Recording the operating system, R version, and package versions is critical for reproducibility.
Nice! There were no cached chunks for this analysis, so you can be confident that you successfully produced the results during this run.
Great job! Using relative paths to the files within your workflowr project makes it easier to run your code on other machines.
Great! You are using Git for version control. Tracking code development and connecting the code version to the results is critical for reproducibility.
The results in this page were generated with repository version 397f9b2. See the Past versions tab to see a history of the changes made to the R Markdown and HTML files.
Note that you need to be careful to ensure that all relevant files for
the analysis have been committed to Git prior to generating the results
(you can use wflow_publish
or
wflow_git_commit
). workflowr only checks the R Markdown
file, but you know if there are other scripts or data files that it
depends on. Below is the status of the Git repository when the results
were generated:
Ignored files:
Ignored: .DS_Store
Ignored: .RData
Ignored: .Rhistory
Ignored: data/.DS_Store
Note that any generated files, e.g. HTML, png, CSS, etc., are not included in this status report because it is ok for generated content to have uncommitted changes.
These are the previous versions of the repository in which changes were
made to the R Markdown (analysis/Unsupervised_Learning.Rmd
)
and HTML (docs/Unsupervised_Learning.html
) files. If you’ve
configured a remote Git repository (see ?wflow_git_remote
),
click on the hyperlinks in the table below to view the files as they
were in that past version.
File | Version | Author | Date | Message |
---|---|---|---|---|
html | 5baa04e | tkcaccia | 2025-02-17 | Build site. |
html | f26aafb | tkcaccia | 2025-02-17 | Build site. |
html | 1ce3cb4 | tkcaccia | 2025-02-16 | Build site. |
html | 681ec51 | tkcaccia | 2025-02-16 | Build site. |
html | 9558051 | tkcaccia | 2024-09-18 | update |
html | a7f82c5 | tkcaccia | 2024-09-18 | Build site. |
html | 83f8d4e | tkcaccia | 2024-09-16 | Build site. |
html | 159190a | tkcaccia | 2024-09-16 | Build site. |
html | 6d23cdb | tkcaccia | 2024-09-16 | Build site. |
html | 6301d0a | tkcaccia | 2024-09-16 | Build site. |
html | 897778a | tkcaccia | 2024-09-16 | Build site. |
Rmd | 9a91f51 | tkcaccia | 2024-09-16 | Start my new project |
Unsupervised Learning (UL) is a technique that uncovers patterns in data without predefined labels or extensive human input. Unlike supervised learning, which relies on data with known outcomes, UL focuses on exploring relationships within the data itself.
A key approach in UL is clustering, when data points are grouped based on their similarities. Common methods include K-means clustering, Hierarchical clustering, Probabilistic clustering.
DBSCAN (Density-Based Spatial Clustering of Applications with Noise) is another powerful clustering method that identifies clusters based on data density and can handle noise effectively.
For instance, in mRNA expression analysis, clustering
can group genes with similar expression profiles, while
DBSCAN
might reveal clusters of genes with high expression
density and identify outliers.
Additionally, dimensionality reduction techniques like Principal Component Analysis (PCA) and t-Distributed Stochastic Neighbor Embedding (t-SNE) are used to simplify and visualize complex data. These methods help reveal hidden structures and insights, such as the genetic basis of various conditions.
# Load necessary libraries
library(dplyr)
library(cluster) # For clustering algorithms
library(factoextra) # For cluster visualization
library(ggplot2) # For data visualization
library(Rtsne) # For t-SNE visualization
library(stats) # For K-means
library(tidyr) # For handling missing data
library(dendextend) # For Hierarchical Clustering
library(ggfortify)
library(dbscan)
library(mclust) # For probabilistic clustering
library(caret) # For scaling
data <- iris
# Remove "Species" column as this is not needed for unsupervised learning.
clean_data <- data[,c(1:4)]
# Step 1: Remove rows with missing values
clean_data <- na.omit(clean_data)
# Step 2: Normalization/Scaling
clean_data <- scale(clean_data)
This step involves filtering out variables with low variance and any missing values to ensure our data is clean and robust for analysis.
The aim is to filter out variables that are unlikely to be informative for distinguishing between different samples.
# Step 1: Calculate the variance for each gene (row)
variances <- apply(clean_data, 1, var)
# Step 2: Set a threshold for filtering low variance genes
threshold <- quantile(variances, 0.25) # Lower 25% variance genes
# Step 3: Retain only the genes with variance above the threshold
filtered_data <- clean_data[variances > threshold, ]
# Step 4: Remove duplicate rows
filtered_data <- unique(filtered_data)
Determine the number of clusters and evaluation criteria to ensure meaningful clusters.
Use methods like the elbow method, silhouette score, or cross-validation to decide the optimal number of clusters.
# 1. Elbow method
fviz_nbclust(filtered_data, kmeans, method = "wss") +
labs(subtitle = "Elbow Method for Optimal K")
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
# 2. Silhouette method
fviz_nbclust(filtered_data, kmeans, method = "silhouette") +
labs(subtitle = "Silhouette Method for Optimal K")
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
Perform K-means clustering with k = 2
set.seed(123)
k <- 2
# Use Euclidean distance
kmeans_res_euclidean <- kmeans(filtered_data, centers = k, nstart = 25)
# For comparison, use Manhattan distance
kmeans_res_manhattan <- kmeans(dist(filtered_data, method = "manhattan"), centers = k, nstart = 25)
# Visualize the clusters
fviz_cluster(kmeans_res_euclidean, data = filtered_data) +
labs(title = "K-means Clustering with Euclidean Distance")
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
fviz_cluster(kmeans_res_manhattan, data = filtered_data) +
labs(title = "K-means Clustering with Manhattan Distance")
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
Simplify and visualize the dataset using PCA to understand the structure and separation of clusters.
Principal Component Analysis (PCA): Reduces dimensionality while preserving variance, helping to visualize data clusters.
# PCA Visualization
pca_res <- prcomp(filtered_data, scale. = TRUE)
fviz_pca_ind(pca_res,
geom.ind = "point",
col.ind = as.factor(kmeans_res_euclidean$cluster)) +
labs(title = "PCA Visualization")
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
Use t-SNE to visualize high-dimensional data in a lower-dimensional space.
Captures complex nonlinear relationships and helps visualize clusters in a 2D or 3D space.
# t-SNE Visualization
set.seed(123)
tsne_res <- Rtsne(as.matrix(filtered_data), dims = 2, perplexity = 30, verbose = TRUE)
Performing PCA
Read the 111 x 4 data matrix successfully!
Using no_dims = 2, perplexity = 30.000000, and theta = 0.500000
Computing input similarities...
Building tree...
Done in 0.00 seconds (sparsity = 0.917945)!
Learning embedding...
Iteration 50: error is 44.706463 (50 iterations in 0.00 seconds)
Iteration 100: error is 44.987762 (50 iterations in 0.01 seconds)
Iteration 150: error is 44.895181 (50 iterations in 0.01 seconds)
Iteration 200: error is 45.880938 (50 iterations in 0.00 seconds)
Iteration 250: error is 46.116539 (50 iterations in 0.00 seconds)
Iteration 300: error is 1.006513 (50 iterations in 0.00 seconds)
Iteration 350: error is 0.069938 (50 iterations in 0.00 seconds)
Iteration 400: error is 0.053021 (50 iterations in 0.00 seconds)
Iteration 450: error is 0.052543 (50 iterations in 0.00 seconds)
Iteration 500: error is 0.051592 (50 iterations in 0.00 seconds)
Iteration 550: error is 0.051015 (50 iterations in 0.00 seconds)
Iteration 600: error is 0.051990 (50 iterations in 0.00 seconds)
Iteration 650: error is 0.052931 (50 iterations in 0.00 seconds)
Iteration 700: error is 0.053287 (50 iterations in 0.00 seconds)
Iteration 750: error is 0.052523 (50 iterations in 0.00 seconds)
Iteration 800: error is 0.051714 (50 iterations in 0.00 seconds)
Iteration 850: error is 0.051292 (50 iterations in 0.00 seconds)
Iteration 900: error is 0.050894 (50 iterations in 0.00 seconds)
Iteration 950: error is 0.051519 (50 iterations in 0.00 seconds)
Iteration 1000: error is 0.050986 (50 iterations in 0.00 seconds)
Fitting performed in 0.09 seconds.
# Convert t-SNE results to a data frame for plotting
tsne_data <- as.data.frame(tsne_res$Y)
colnames(tsne_data) <- c("Dim1", "Dim2")
# Map the clusters from k-means result to the t-SNE data
tsne_data$Cluster <- as.factor(kmeans_res_euclidean$cluster)
# Plot t-SNE results
library(ggplot2)
ggplot(tsne_data, aes(x = Dim1, y = Dim2, color = Cluster)) +
geom_point() +
labs(title = "t-SNE Visualization", x = "Dimension 1", y = "Dimension 2")
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
One could adjust parameters such as perplexity
,
exaggeration
, and PCAComps
iteratively based
on the visualization results.
Evaluate if clusters are too separated or not well defined. Modify exaggeration_num and perplexity to refine cluster separation and representation.
Lets see what happens when we adjust some parameters?
# Set seed for reproducibility
set.seed(1)
# Example data: replace this with your actual data
expression <- filtered_data
expression_standardized <- scale(expression) # Standardize data
# Parameters
algorithm <- 'barnes_hut' # Not directly set in R, handled internally
distance_metric <- 'euclidean' # Rtsne does not support 'spearman' directly
exaggeration_num <- 4
PCAComps <- 0
pca_comps <- if (PCAComps == 0) NULL else PCAComps # Set PCA dimension reduction
perp_num <- 30
# Optionally apply PCA if PCAComps > 0
if (!is.null(pca_comps)) {
pca_res <- prcomp(expression_standardized, center = TRUE, scale. = TRUE)
expression_pca <- pca_res$x[, 1:pca_comps]
} else {
expression_pca <- expression_standardized
}
# Run t-SNE
tsne_res <- Rtsne(expression_pca, dims = 2, pca = PCAComps > 0, perplexity = perp_num,
check_duplicates = FALSE, verbose = TRUE)
Read the 111 x 4 data matrix successfully!
Using no_dims = 2, perplexity = 30.000000, and theta = 0.500000
Computing input similarities...
Building tree...
Done in 0.00 seconds (sparsity = 0.918757)!
Learning embedding...
Iteration 50: error is 44.751156 (50 iterations in 0.00 seconds)
Iteration 100: error is 45.308458 (50 iterations in 0.00 seconds)
Iteration 150: error is 44.348748 (50 iterations in 0.01 seconds)
Iteration 200: error is 45.104693 (50 iterations in 0.01 seconds)
Iteration 250: error is 45.680708 (50 iterations in 0.00 seconds)
Iteration 300: error is 0.734819 (50 iterations in 0.00 seconds)
Iteration 350: error is 0.059571 (50 iterations in 0.00 seconds)
Iteration 400: error is 0.054598 (50 iterations in 0.00 seconds)
Iteration 450: error is 0.050394 (50 iterations in 0.00 seconds)
Iteration 500: error is 0.053462 (50 iterations in 0.00 seconds)
Iteration 550: error is 0.056509 (50 iterations in 0.00 seconds)
Iteration 600: error is 0.055215 (50 iterations in 0.00 seconds)
Iteration 650: error is 0.054666 (50 iterations in 0.00 seconds)
Iteration 700: error is 0.053571 (50 iterations in 0.00 seconds)
Iteration 750: error is 0.054591 (50 iterations in 0.00 seconds)
Iteration 800: error is 0.056016 (50 iterations in 0.00 seconds)
Iteration 850: error is 0.054679 (50 iterations in 0.00 seconds)
Iteration 900: error is 0.055034 (50 iterations in 0.00 seconds)
Iteration 950: error is 0.055220 (50 iterations in 0.00 seconds)
Iteration 1000: error is 0.054738 (50 iterations in 0.00 seconds)
Fitting performed in 0.08 seconds.
# Convert t-SNE results to a data frame for plotting
tsne_data <- as.data.frame(tsne_res$Y)
colnames(tsne_data) <- c("Dim1", "Dim2")
# Here we use the cluster labels from k-means:
set.seed(1)
cluster_labels <- kmeans(expression_standardized, centers = 3)$cluster
tsne_data$Cluster <- as.factor(cluster_labels)
# Plotting t-SNE results
ggplot(tsne_data, aes(x = Dim1, y = Dim2, color = Cluster)) +
geom_point() +
labs(title = "t-SNE Visualization", x = "Dimension 1", y = "Dimension 2") +
theme_minimal()
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
Within-Cluster Sum of Squares (Inertia)
# Compute inertia for K-means clustering
inertia_euclidean <- kmeans_res_euclidean$tot.withinss
inertia_manhattan <- kmeans_res_manhattan$tot.withinss
# Print inertia values
cat("Inertia for Euclidean K-means:", inertia_euclidean, "\n")
Inertia for Euclidean K-means: 159.1761
cat("Inertia for Manhattan K-means:", inertia_manhattan, "\n")
Inertia for Manhattan K-means: 18284.96
We see that our Euclidean K-means model produced more compact clusters.
Silhouette Analysis
Silhouette scores measure how similar each point is to its own cluster compared to other clusters.
Scores close to 1 indicate good clustering.
library(cluster)
# Calculate silhouette scores for Euclidean K-means
silhouette_euclidean <- silhouette(kmeans_res_euclidean$cluster, dist(filtered_data))
plot(silhouette_euclidean, main = "Silhouette Plot for Euclidean K-means")
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
# Calculate silhouette scores for Manhattan K-means
silhouette_manhattan <- silhouette(kmeans_res_manhattan$cluster, dist(filtered_data, method = "manhattan"))
plot(silhouette_manhattan, main = "Silhouette Plot for Manhattan K-means")
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
Davies-Bouldin Index
The DB-index is a validation technique that is a metric for evaluating clustering models.
The metric measures the average similarity between each cluster and the cluster most similar to it.
Similarity is assessed as the ratio of how spread out the points are within a cluster to the distance between cluster centroids.
Low Davies-Bouldin Index values = better clustering, with well-separated clusters and lower dispersion being more favorable.
# install.packages("clusterSim")
library(clusterSim)
Loading required package: MASS
Attaching package: 'MASS'
The following object is masked from 'package:dplyr':
select
# Step 1: Prepare distance matrix for Euclidean distance clustering
dist_matrix <- dist(filtered_data)
### Prepare distance matrix for Manhattan distance clustering
dist_matrix_manhattan <- dist(filtered_data, method = "manhattan")
### Calculate Davies-Bouldin Index for Euclidean distance clustering:
db_index_euclidean <- index.DB(filtered_data, kmeans_res_euclidean$cluster, d =
dist_matrix, centrotypes = "centroids")
# Compute Davies-Bouldin Index for our Manhattan distance clustering:
db_index_manhattan <- index.DB(filtered_data, kmeans_res_manhattan$cluster, d = dist_matrix_manhattan, centrotypes = "medoids")
# Print Index for both distances:
print(paste("Davies-Bouldin Index (Euclidean):", db_index_euclidean$DB))
[1] "Davies-Bouldin Index (Euclidean): 0.640411431053664"
print(paste("Davies-Bouldin Index (Manhattan):", db_index_manhattan$DB))
[1] "Davies-Bouldin Index (Manhattan): 0.638178665950447"
Cluster Validation Metrics
Additional metrics can provide further validation of clustering results.
library(NbClust)
# Use NbClust to determine the optimal number of clusters
nb <- NbClust(filtered_data, min.nc = 2, max.nc = 10, method = "kmeans")
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
*** : The Hubert index is a graphical method of determining the number of clusters.
In the plot of Hubert index, we seek a significant knee that corresponds to a
significant increase of the value of the measure i.e the significant peak in Hubert
index second differences plot.
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
*** : The D index is a graphical method of determining the number of clusters.
In the plot of D index, we seek a significant knee (the significant peak in Dindex
second differences plot) that corresponds to a significant increase of the value of
the measure.
*******************************************************************
* Among all indices:
* 12 proposed 2 as the best number of clusters
* 2 proposed 3 as the best number of clusters
* 8 proposed 5 as the best number of clusters
* 2 proposed 10 as the best number of clusters
***** Conclusion *****
* According to the majority rule, the best number of clusters is 2
*******************************************************************
# Using our cleaned data: "filtered_data"
# Compute the distance matrix
dist_matrix <- dist(filtered_data, method = "euclidean")
# Perform hierarchical clustering
hc <- hclust(dist_matrix, method = "complete")
# Plot the dendrogram
plot(hc, main = "Dendrogram of Hierarchical Clustering", xlab = "Branches", ylab = "Euclidean Distance", labels = FALSE)
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
Visualising the output from our Hierarchical Clustering using PCA:
# Convert PCA results to a data frame for ggplot2
pca_df <- as.data.frame(pca_res$x)
# Perform Hierarchical Clustering
dist_matrix <- dist(filtered_data, method = "euclidean")
hc <- hclust(dist_matrix, method = "complete")
# Cut dendrogram to form clusters
clusters <- cutree(hc, k = 3) # Adjust k based on the desired number of clusters
# Add cluster assignments to the PCA data
pca_res$clusters <- as.factor(clusters)
# Visualize the clusters using PCA for dimensionality reduction
ggplot(pca_res, aes(x = PC1, y = PC2, color = clusters)) +
geom_point(size = 3) +
labs(title = "PCA of Hierarchical Clustering", x = "PC1", y = "PC2") +
theme_minimal()
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
# Data Prep:
data(iris)
data <- iris[, 1:4] # Use only numerical features
# Normalize/Scale the data
scaled_data <- scale(data)
# Fit the Gaussian Mixture Model
model <- Mclust(data)
The output from the model indicates that the GMM identified 2 clusters with the same shape and variance.
# View model summary
summary(model)
----------------------------------------------------
Gaussian finite mixture model fitted by EM algorithm
----------------------------------------------------
Mclust VEV (ellipsoidal, equal shape) model with 2 components:
log-likelihood n df BIC ICL
-215.726 150 26 -561.7285 -561.7289
Clustering table:
1 2
50 100
# Plot the clustering results
plot(model, what = "classification")
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
# Predict cluster memberships for new data
new_data <- data[1:5, ]
predictions <- predict(model, new_data)
print(predictions$classification)
[1] 1 1 1 1 1
DBSCAN identifies clusters based on the density and noise of data points. It is particularly useful for finding clusters of arbitrary shape and handling high-noise datasets.
# Load necessary libraries
library(dbscan)
library(ggplot2)
# Data Prep:
data("iris")
iris <- as.matrix(iris[, 1:4])
# Remove rows with missing values
clean_data <- na.omit(iris)
# Normalize/Scale the data
scaled_data <- scale(clean_data)
Determine suitable DBSCAN parameters:
eps
= size (radius) of the epsilon
neighborhood
Where there is a sudden spike (increase) in the kNN
distance = points to the right of this spike are most likely
outliers.
Choose this kNN distance as the ‘eps’
# Visualize the k-NN distance plot for eps parameter
kNNdistplot(scaled_data, minPts = 5)
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
Add a line where approximately the noise starts. We see at
~0.8
the noise begins:
kNNdistplot(scaled_data, minPts = 5)
abline(h = 0.8, col = "red", lty = 2)
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
Now we can perform DBSCAN clustering:
# Perform DBSCAN clustering
eps <- 0.8 # Set eps based on the k-NN distance plot
minPts <- 5 # minPts is set to the number of features + 1
dbscan_result <- dbscan(scaled_data, eps = eps, minPts = minPts)
dbscan_result
DBSCAN clustering for 150 objects.
Parameters: eps = 0.8, minPts = 5
Using euclidean distances and borderpoints = TRUE
The clustering contains 2 cluster(s) and 4 noise points.
0 1 2
4 49 97
Available fields: cluster, eps, minPts, metric, borderPoints
# Add cluster assignments to the original data
airquality_clustered <- cbind(clean_data, cluster = as.factor(dbscan_result$cluster))
# Visualize the clusters using PCA for dimensionality reduction
pca <- prcomp(scaled_data, scale. = TRUE)
pca_df <- as.data.frame(pca$x)
pca_df$cluster <- as.factor(dbscan_result$cluster)
ggplot(pca_df, aes(x = PC1, y = PC2, color = cluster)) +
geom_point(size = 3) +
labs(title = "PCA of DBSCAN Clustering", x = "PC1", y = "PC2") +
theme_minimal()
Version | Author | Date |
---|---|---|
897778a | tkcaccia | 2024-09-16 |
sessionInfo()
R version 4.3.3 (2024-02-29)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Sonoma 14.5
Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.11.0
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: Africa/Johannesburg
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] NbClust_3.0.1 clusterSim_0.51-5 MASS_7.3-60.0.1 caret_6.0-94
[5] lattice_0.22-6 mclust_6.1.1 dbscan_1.2-0 ggfortify_0.4.17
[9] dendextend_1.17.1 tidyr_1.3.1 Rtsne_0.17 factoextra_1.0.7
[13] ggplot2_3.5.1 cluster_2.1.6 dplyr_1.1.4 workflowr_1.7.1
loaded via a namespace (and not attached):
[1] pROC_1.18.5 gridExtra_2.3 rlang_1.1.5
[4] magrittr_2.0.3 ade4_1.7-22 git2r_0.33.0
[7] e1071_1.7-14 compiler_4.3.3 getPass_0.2-4
[10] callr_3.7.6 vctrs_0.6.5 reshape2_1.4.4
[13] stringr_1.5.1 pkgconfig_2.0.3 fastmap_1.2.0
[16] backports_1.5.0 labeling_0.4.3 utf8_1.2.4
[19] promises_1.3.0 rmarkdown_2.29 prodlim_2024.06.25
[22] ps_1.7.7 purrr_1.0.2 xfun_0.46
[25] cachem_1.1.0 jsonlite_1.8.9 recipes_1.0.10
[28] highr_0.11 later_1.3.2 broom_1.0.6
[31] parallel_4.3.3 R6_2.5.1 bslib_0.8.0
[34] stringi_1.8.4 car_3.1-2 parallelly_1.38.0
[37] rpart_4.1.23 lubridate_1.9.3 jquerylib_0.1.4
[40] Rcpp_1.0.14 iterators_1.0.14 knitr_1.48
[43] future.apply_1.11.2 httpuv_1.6.15 Matrix_1.6-5
[46] splines_4.3.3 nnet_7.3-19 timechange_0.3.0
[49] tidyselect_1.2.1 abind_1.4-5 rstudioapi_0.16.0
[52] yaml_2.3.10 viridis_0.6.5 timeDate_4032.109
[55] codetools_0.2-20 processx_3.8.4 listenv_0.9.1
[58] tibble_3.2.1 plyr_1.8.9 withr_3.0.2
[61] evaluate_0.24.0 future_1.34.0 survival_3.7-0
[64] proxy_0.4-27 pillar_1.9.0 ggpubr_0.6.0
[67] carData_3.0-5 whisker_0.4.1 foreach_1.5.2
[70] stats4_4.3.3 generics_0.1.3 rprojroot_2.0.4
[73] munsell_0.5.1 scales_1.3.0 globals_0.16.3
[76] class_7.3-22 glue_1.7.0 tools_4.3.3
[79] data.table_1.15.4 ModelMetrics_1.2.2.2 gower_1.0.1
[82] ggsignif_0.6.4 fs_1.6.4 grid_4.3.3
[85] ipred_0.9-15 colorspace_2.1-1 nlme_3.1-165
[88] cli_3.6.3 fansi_1.0.6 viridisLite_0.4.2
[91] lava_1.8.0 gtable_0.3.5 rstatix_0.7.2
[94] sass_0.4.9 digest_0.6.36 ggrepel_0.9.5
[97] farver_2.1.2 htmltools_0.5.8.1 lifecycle_1.0.4
[100] hardhat_1.3.1 httr_1.4.7