This script performs candidate detection using the LFMM method (Frichot et al. 2013). The workflow is based on the LFMM tutorial (Frichot and François) and the tutorial by Brenna R. Forester.
LFMM can be applied in a univariate or multivariate framework, allowing the association of SNPs with explanatory variables either individually or jointly. The method can account for population structure or run without correction. It is a linear method, as it assumes a linear relationship between loci and climatic variables.
LFMMs are regression models that combine fixed effects (climatic variables) with latent effects (hidden parameters capturing background population structure) (Caye et al. 2019). Latent factors are estimated using a factorization method similar to PCA, which explains why latent factor results often align with principal component axes (Frichot et al. 2013).
Model overview: Environmental variables (primary predictors) are tested against genetic variation while correcting for population structure. Populations located closer together in latent space are expected to share more genetic similarities, while those further apart are less similar, suggesting additional factors influencing genetic variation. (As an analogy: if weight is influenced by both height and sex, we can isolate the effect of height on weight while holding sex constant, and vice versa. LFMM applies a similar logic by removing confounding effects to assess the role of environmental variables more accurately). The effect of population structure is removed simultaneously with the estimation of the coefficients, so the confounding between structure and climate is reduced. This approach lowers the false negative rate.
There are two types of LFMM analysis:
- lfmm() a Bayesian method using a Markov Chain Monte Carlo (MCMC)
algorithm.
- lfmm2() a frequentist method using least squares estimation,
recommended for large datasets (1,000–10,000 SNPs) because it is faster
and converges to results similar to the Bayesian approach.
Since LFMM requires allele counts at the individual level (not in the new version), the analysis must be performed on individual genotypes rather than population-level allele frequencies. In this study, we will use multivariate LFMM analysis.
Genomic data must be available as individual-level allele counts. We imputed the data at the individual level (475 individuals, 8,616 SNPs) with MAC correction.
#climatic data at individual level
load("C:/Users/tfrancisco/Documents/Thesis/Data/Species/Taxus_baccata/Climatic_data/new_selection/climatic_data_indivdual_level_scaled_new_selection_df.Rdata")
climatic_data <- climatic_data_indivdual_level_scaled_new_selection_df
load("C:/Users/tfrancisco/Documents/Thesis/Data/Species/Taxus_baccata/genetic_new/Gen_matrix_imp_T_Adapcon_Gentree_475_8616.Rdata")
#genomic data in numeric
genomic_data <- Gen_matrix_imp_T_Adapcon_Gentree_475_8616 %>% mutate_all( function(x) as.numeric(as.character(x)))
#meta_data
meta_data_pop <- read.csv("C:/Users/tfrancisco/Documents/Thesis/Data/Species/Taxus_baccata/Populations/taxus_sample_29pop.csv",h=T,sep=";",dec=",")
#alphabetic order
meta_data_pop_order <- meta_data_pop[order(meta_data_pop$Population),]
meta_data_vcf <- read.csv("C:/Users/tfrancisco/Documents/Thesis/Data/Species/Taxus_baccata/samples/samples_taxus_baccata_adapcon_gentree.csv",h=T,sep=";",dec=",")
genomic_data$VCF_ID <- row.names(genomic_data)
df <- merge(meta_data_vcf,genomic_data,"VCF_ID")
We need to fit the climate data to the number of individuals in the genomic data.
climatic_data_475 <- climatic_data[climatic_data$VCF_ID %in% row.names(Gen_matrix_imp_T_Adapcon_Gentree_475_8616),]
We can either write an LFMM input file or use genomic_data directly as input to lfmm2. For write.geno, the input must be a dataset with individuals in rows, SNPs in columns, and missing values coded as 9.
#write.lfmm(genomic_data,"C:/Users/tfrancisco/Documents/Thesis/Data/Species/Taxus_baccata/genetic_new/GEA/genomic_data.lfmm")
climate_format <- climatic_data_475[,-c(1:3)]
#write.env(climate_format, "C:/Users/tfrancisco/Documents/Thesis/Data/Species/Taxus_baccata/genetic_new/GEA/climate_format.env")
#See if the lfmm is in the right format
visualization_writelfmm <- data.frame(read.lfmm("C:/Users/tfrancisco/Documents/Thesis/Data/Species/Taxus_baccata/genetic_new/GEA/genomic_data.lfmm"))
Based on the PCA of the genetic data, the first two PC axes appear sufficient to account for population structure, as they discriminate the three main gene pools identified by STRUCTURE. However, because latent factors do not behave identically to PCs, we will perform a latent factor analysis to determine how many latent factors are required to properly separate the three main gene pools.
We can perform the LFMM bayesian analysis (not used)
#project = NULL
#test <- lfmm("C:/Users/tfrancisco/Documents/Thesis/Data/Species/Taxus_baccata/genetic_new/GEA/genomic_data.lfmm",
# "C:/Users/tfrancisco/Documents/Thesis/Data/Species/Taxus_baccata/genetic_new/GEA/climate_format.env",
# K=2,
# repetitions=5,
# project="new")
Here we’ve chosen to use the LFMM2 algorithm because it’s faster with larger data sets.
Run_LFMM2 <- lfmm2(input = genomic_data[,-8617], env = climatic_data_475[,-c(1:3)], K = 2, effect.sizes = T)
# GEA significance test
# showing the K = 2 estimated factors
score_LFMM <- data.frame(Run_LFMM2@U)
score_Pca <- data.frame(score_LFMM, row.names (genomic_data))
colnames(score_Pca) <- c(paste0("PC1"), paste0("PC2"), "VCF_ID")
#add pop info on the score_pca
score_Pca_meta_data <- merge(climatic_data_475[,c(1:2)],score_Pca, "VCF_ID")
# add country, population information
PCa_df_score <- merge(score_Pca_meta_data, meta_data_pop_order, "Population")
# Latent factor analysis
latent_factor_LFMM2_candidates_selection <- ggplot() +
geom_point(data = PCa_df_score, aes(PC1, PC2, color = Country),size=2.6) +
scale_colour_manual(name = "Countries",
values = c("orangered3", "gold2", "darkorchid3", "navyblue", "turquoise2", "green3", "blue", "red", "black", "gray", "orange", "darkgreen")) +
xlab("Latent factor 1") +
ylab("Latent factor 2") +
#facet_wrap(~"Graphical representation of the 2 firsts latent factor from LFMM")
guides(color = guide_legend(title="Country",override.aes = list(size = 3.5)))+ # Increase legend point size
theme_bw(base_size = 12) +
theme(
panel.grid = element_blank(),
plot.background = element_blank(),
panel.background = element_blank(),
strip.text = element_text(size = 12),
legend.text = element_text(size = 13),
legend.title = element_text(size = 14),
axis.title = element_text(size = 13)
)
print(latent_factor_LFMM2_candidates_selection)
The genetic PCs and the latent factors with k = 2 appear to be very similar. Moreover, according to Pritchard et al. (2000), K can be replaced by estimates of population genetic structure obtained with clustering algorithms such as STRUCTURE. Based on this, and following discussions with Benjamin Dauphin (who suggested using k = number of groups – 1), we will retain k = 2 for our analysis, reflecting the three main gene pools identified in the STRUCTURE analysis and supported by the latent factor analysis.
#non correcting for GIF
pv_non_corrected <- lfmm2.test(object = Run_LFMM2,
input = genomic_data[,-8617],
env = climatic_data_475[,-c(1:3)],
full = T,
genomic.control = F)
#correcting for GIF
pv_corrected <- lfmm2.test(object = Run_LFMM2,
input = genomic_data[,-8617],
env = climatic_data_475[,-c(1:3)],
full = T,
genomic.control = T)
The next step is to visualize the p-value distribution with and without correction by the genomic inflation factor (GIF). The GIF expresses the deviation of the observed test statistic distribution compared to the expected distribution (Van den Berg et al. 2019).
To assess the most appropriate GIF for our data, we can examine the shape of the p-value distribution. Ideally, this distribution should show a strong peak near 0, followed by a smooth, uniform distribution from 0.1 to 1. We will compare the distributions obtained with and without GIF correction.
#non corrected for GIF
Histogram_of_non_calibrated_Pvalues_LFMM<- hist(pv_non_corrected$pvalues,
main= "Histogram of non-calibrated P-values",
xlab= "P-values")
#correcting for GIF
Histogram_of_calibrated_Pvalues_LFMM<-hist(pv_corrected$pvalues,
main= "Histogram of calibrated P-values",
xlab= "P-values")
#gif values
pv_corrected$gif
## [1] 2.423337
The distribution of non-calibrated p-values does not follow the expected pattern for well-corrected genomic data, suggesting residual population structure or other confounding factors. In contrast, the calibrated p-values more closely match the expected distribution, indicating that calibration improved the correction. Furthermore, the genomic inflation factor (GIF) value of 2.42, while not exactly 1, still reflects a reasonable level of correction.
Can we choose the GIF values ourselves? For the LFMM2 univariate
method (full = FALSE), yes — we can select a GIF value for each climate
variable. However, for the full multivariate model, I have not found a
way to manually set the GIF. Arguments for using univariate
vs. multivariate models:
- Univariate: simpler, less prone to assumption violations, and easier
to interpret.
- Multivariate: accounts for the combined effects and interactions of
climatic variables on genetic variation, and reduces the total number of
tests performed.
We tested the false discovery rate (FDR) threshold of 5% and a more relaxed threshold of 10%.
df_pvalues_calibrated <- data.frame(SNP=colnames(genomic_data[,-8617]),pvalues=pv_corrected$pvalues)
#FDR correction
candidates_FDR <- data.frame(snp_names=colnames(genomic_data[,-8617]) ,qvalues=qvalue(pv_corrected$pvalues)$qvalues)
#threshold 0.05
thres_FDR <- 0.05
candidates_T_adapcon_gentree_LFMM_5perc <- data.frame(SNP=candidates_FDR$snp_names[which(candidates_FDR$qvalues<thres_FDR)],qvalues = candidates_FDR$qvalues[which(candidates_FDR$qvalues<thres_FDR)])
length(which(candidates_FDR$qvalues < thres_FDR)) ## how many SNPs we have with an FDR < 5%?
## [1] 14
#FDR 10%
thres_FDR <- 0.1
candidates_T_adapcon_gentree_LFMM_10perc <- data.frame(SNP=candidates_FDR$snp_names[which(candidates_FDR$qvalues<thres_FDR)],qvalues = candidates_FDR$qvalues[which(candidates_FDR$qvalues<thres_FDR)])
length(which(candidates_FDR$qvalues < thres_FDR)) ## how many SNPs we have with an FDR < 10%?
## [1] 24
We plotted the candidates in a Manhattan plot with a false discovery rate (FDR) threshold of 5%.
#selection of the candidates from FDR 5%
df_pvalues_calibrated$type <- "Other SNPs"
df_pvalues_calibrated$type[df_pvalues_calibrated$SNP%in%candidates_T_adapcon_gentree_LFMM_5perc$SNP] <- "Candidate SNP FDR 5%"
df_pvalues_calibrated$type <- as.factor(df_pvalues_calibrated$type)
#Bonferroni threshold
threshold_bonferroni <- 0.05/nrow(df_pvalues_calibrated)
#plot
Manhattan_plot_LFMM_FDR_5perc <- ggplot(df_pvalues_calibrated) +
geom_point(aes(x=1:nrow(df_pvalues_calibrated), y= -log10(pvalues), col = type), size=1.4) +
scale_color_manual(values = c("orange","lightblue")) +
xlab("Loci") + ylab("-log10(p.values)") +
geom_hline(yintercept= -log10(threshold_bonferroni), linetype="dashed", color = "red", size=0.6) +
ggtitle("Manhattan plot LFMM, with FDR 5% threshold") +
guides(color=guide_legend(title="SNP type")) +
theme_bw(base_size = 11) +
theme(legend.position="right",
panel.grid = element_blank(),
plot.background = element_blank(),
plot.title = element_text(hjust = 0.5,color = "Black",face="italic"),
panel.background = element_blank(),
strip.text = element_text(size = 12),
legend.text = element_text(size = 13),
legend.title = element_text(size = 13),
axis.title = element_text(size = 13)
)
plot(Manhattan_plot_LFMM_FDR_5perc)
We plotted the candiate SNPs for a 10% threshold FDR
#selection of the candidates from FDR 5 and 10%
df_pvalues_calibrated$type <- "Other SNPs"
df_pvalues_calibrated$type[df_pvalues_calibrated$SNP%in%candidates_T_adapcon_gentree_LFMM_10perc$SNP] <- "Candidate SNPs FDR 10%"
df_pvalues_calibrated$type <- as.factor(df_pvalues_calibrated$type)
#Bonferroni threshold
threshold_bonferroni <- 0.05/nrow(df_pvalues_calibrated)
#plot
Manhattan_plot_LFMM_FDR_10perc <- ggplot(df_pvalues_calibrated) +
geom_point(aes(x=1:nrow(df_pvalues_calibrated), y=-log10(pvalues), col = type), size=2.5) +
scale_color_manual(values = c("orange","lightblue")) +
xlab("Loci") + ylab("-log10(p.values)") +
geom_hline(yintercept=-log10(threshold_bonferroni), linetype="dashed", color = "red", size=0.6) +
ggtitle("Manhattan plot LFMM, with FDR 10% threshold") +
guides(color = guide_legend(title="SNP type",override.aes = list(size = 3)))+ # Increase legend point size
theme_bw(base_size = 14) +
theme(legend.position="right",
panel.grid = element_blank(),
plot.background = element_blank(),
plot.title = element_text(hjust = 0.5,color = "Black",face="italic"),
panel.background = element_blank(),
strip.text = element_text(size = 14),
legend.text = element_text(size = 13),
legend.title = element_text(size = 14),
axis.title = element_text(size = 13)
)
plot(Manhattan_plot_LFMM_FDR_10perc)
We also We plotted the candidates in a Manhattan plot with a false discovery rate (FDR) threshold of 5% and 10%.
#selection of the candidates from FDR 5 and 10%
df_pvalues_calibrated$type <- "Other SNPs"
df_pvalues_calibrated$type[df_pvalues_calibrated$SNP%in%candidates_T_adapcon_gentree_LFMM_10perc$SNP] <- "Candidate SNPs FDR 10%"
df_pvalues_calibrated$type[df_pvalues_calibrated$SNP%in%candidates_T_adapcon_gentree_LFMM_5perc$SNP] <- "Candidate SNPs FDR 5%"
df_pvalues_calibrated$type <- as.factor(df_pvalues_calibrated$type)
#Bonferroni threshold
threshold_bonferroni <- 0.05/nrow(df_pvalues_calibrated)
#plot
Manhattan_plot_LFMM_FDR_5_10perc <- ggplot(df_pvalues_calibrated) +
geom_point(aes(x=1:nrow(df_pvalues_calibrated), y=-log10(pvalues), col = type), size=1.4) +
scale_color_manual(values = c("darkgreen","orange","lightblue")) +
xlab("Loci") + ylab("-log10(p.values)") +
geom_hline(yintercept=-log10(threshold_bonferroni), linetype="dashed", color = "red", size=0.6) +
ggtitle("Manhattan plot LFMM, with FDR 5 and 10% threshold") +
guides(color=guide_legend(title="SNP type")) +
theme_bw(base_size = 11) +
theme(legend.position="right",
panel.grid = element_blank(),
plot.background = element_blank(),
plot.title = element_text(hjust = 0.5,color = "Black",face="italic"),
panel.background = element_blank(),
strip.text = element_text(size = 12),
legend.text = element_text(size = 13),
legend.title = element_text(size = 13),
axis.title = element_text(size = 13)
)
plot(Manhattan_plot_LFMM_FDR_5_10perc)
We need to save the identified candidates for downstream analysis
#FDR 5%
write_xlsx(candidates_T_adapcon_gentree_LFMM_5perc,"C:/Users/tfrancisco/Documents/Thesis/Results/species/taxus/GEA_new_var/outliers/candidates_T_adapcon_gentree_LFMM_5perc.xlsx")
save(candidates_T_adapcon_gentree_LFMM_5perc, file="C:/Users/tfrancisco/Documents/Thesis/Results/species/taxus/GEA_new_var/outliers/candidates_T_adapcon_gentree_LFMM_5perc.Rdata")
#FDR 10%
save(candidates_T_adapcon_gentree_LFMM_10perc, file="C:/Users/tfrancisco/Documents/Thesis/Results/species/taxus/GEA_new_var/outliers/candidates_T_adapcon_gentree_LFMM_10perc.Rdata")
To perform outlier detection corrected for population structure using
Gradient Forest (GF), we need to use a genomic matrix that has already
been corrected for population structure, as GF cannot correct for it. To
compute this corrected matrix, we followed Archambeau et al. (2024) and
used the LFMM approach to obtain the corrected genotypic matrix.
Below are the explained LFMM2 models of Caye et al. (2019):
B, U and V are the effect sizes and the factor and loading matrices adjusted by the LFMM2 algorithm from the set of current environmental variables included in the matrix X. B is a matrix of dimension p × b where p is the number of genetic markers and b is the number of environmental variables. U is a matrix of dimension n × K where n is the number of individuals (i.e. genotypes) and K is the number of latent factors. V is a matrix of dimension p x K. X is a matrix of dimension n x b. Yfut is a matrix of dimension n x p.
We want a matrix of allele frequencies corrected for population
structure:
Ycorrected = Yfut - UVt = XBt
Below we have performed a matrix multiplication of the matrix X (dimension n x b) and the transposition of the matrix B (dimension b x p) to obtain the matrix Ycorrect (dimension n x p) as described in Archambeau et al 2024.
The matrix B is the matrix from the lfmm2 output
# matrix X where we x by the matrix B.
Genomic_matrix_corrected_from_LFMM_T_adapcon_gentree <- as.matrix(climatic_data_475[,-c(1:3)]) %*% t(Run_LFMM2@B) %>% set_rownames(row.names(genomic_data)) %>% set_colnames(colnames(genomic_data)) %>% as.data.frame()
The corrected matrix shows very similar values for individuals within the same population, as they are close together in the PCA space. In contrast, we observe more significant differences for individuals that are further apart in the PCA, as realised by the LFMM.
We save the corrected genotypic matrix for GF analysis
devtools::session_info()
## ─ Session info ───────────────────────────────────────────────────────────────
## setting value
## version R version 4.3.2 (2023-10-31 ucrt)
## os Windows 11 x64 (build 26100)
## system x86_64, mingw32
## ui RTerm
## language (EN)
## collate French_France.utf8
## ctype French_France.utf8
## tz Europe/Paris
## date 2025-09-22
## pandoc 3.1.1 @ C:/Program Files/RStudio/resources/app/bin/quarto/bin/tools/ (via rmarkdown)
##
## ─ Packages ───────────────────────────────────────────────────────────────────
## package * version date (UTC) lib source
## boot 1.3-30 2024-02-26 [2] CRAN (R 4.3.2)
## bslib 0.6.1 2023-11-28 [1] CRAN (R 4.3.2)
## cachem 1.0.8 2023-05-01 [1] CRAN (R 4.3.1)
## cellranger 1.1.0 2016-07-27 [1] CRAN (R 4.3.2)
## class 7.3-22 2023-05-03 [2] CRAN (R 4.3.2)
## cli 3.6.1 2023-03-23 [1] CRAN (R 4.3.1)
## codetools 0.2-19 2023-02-01 [2] CRAN (R 4.3.2)
## colorspace 2.1-0 2023-01-23 [1] CRAN (R 4.3.1)
## data.table 1.15.0 2024-01-30 [1] CRAN (R 4.3.2)
## DescTools * 0.99.54 2024-02-03 [1] CRAN (R 4.3.3)
## devtools 2.4.5 2022-10-11 [1] CRAN (R 4.3.3)
## digest 0.6.33 2023-07-07 [1] CRAN (R 4.3.1)
## dplyr * 1.1.4 2023-11-17 [1] CRAN (R 4.3.2)
## e1071 1.7-14 2023-12-06 [1] CRAN (R 4.3.2)
## ellipsis 0.3.2 2021-04-29 [1] CRAN (R 4.3.1)
## evaluate 0.23 2023-11-01 [1] CRAN (R 4.3.2)
## Exact 3.2 2022-09-25 [1] CRAN (R 4.3.1)
## expm 0.999-9 2024-01-11 [1] CRAN (R 4.3.3)
## farver 2.1.2 2024-05-13 [1] CRAN (R 4.3.3)
## fastmap 1.1.1 2023-02-24 [1] CRAN (R 4.3.1)
## foreach 1.5.2 2022-02-02 [1] CRAN (R 4.3.2)
## fs 1.6.3 2023-07-20 [1] CRAN (R 4.3.1)
## generics 0.1.4 2025-05-09 [1] CRAN (R 4.3.2)
## ggplot2 * 3.5.2 2025-04-09 [1] CRAN (R 4.3.2)
## gld 2.6.6 2022-10-23 [1] CRAN (R 4.3.3)
## glue 1.7.0 2024-01-09 [1] CRAN (R 4.3.2)
## gtable 0.3.6 2024-10-25 [1] CRAN (R 4.3.3)
## highr 0.10 2022-12-22 [1] CRAN (R 4.3.1)
## htmltools 0.5.7 2023-11-03 [1] CRAN (R 4.3.2)
## htmlwidgets 1.6.4 2023-12-06 [1] CRAN (R 4.3.2)
## httpuv 1.6.13 2023-12-06 [1] CRAN (R 4.3.2)
## httr 1.4.7 2023-08-15 [1] CRAN (R 4.3.2)
## iterators 1.0.14 2022-02-05 [1] CRAN (R 4.3.2)
## jquerylib 0.1.4 2021-04-26 [1] CRAN (R 4.3.1)
## jsonlite 1.8.8 2023-12-04 [1] CRAN (R 4.3.2)
## knitr 1.45 2023-10-30 [1] CRAN (R 4.3.2)
## labeling 0.4.3 2023-08-29 [1] CRAN (R 4.3.1)
## later 1.3.2 2023-12-06 [1] CRAN (R 4.3.2)
## lattice 0.22-5 2023-10-24 [2] CRAN (R 4.3.2)
## LEA * 3.14.0 2023-10-24 [1] Bioconductor
## lfmm * 1.1 2021-06-30 [1] CRAN (R 4.3.2)
## lifecycle 1.0.4 2023-11-07 [1] CRAN (R 4.3.2)
## lmom 3.0 2023-08-29 [1] CRAN (R 4.3.1)
## magrittr * 2.0.3 2022-03-30 [1] CRAN (R 4.3.1)
## MASS 7.3-60.0.1 2024-01-13 [2] CRAN (R 4.3.2)
## Matrix 1.6-5 2024-01-11 [1] CRAN (R 4.3.2)
## memoise 2.0.1 2021-11-26 [1] CRAN (R 4.3.1)
## mime 0.12 2021-09-28 [1] CRAN (R 4.3.1)
## miniUI 0.1.1.1 2018-05-18 [1] CRAN (R 4.3.2)
## munsell 0.5.1 2024-04-01 [1] CRAN (R 4.3.3)
## mvtnorm 1.3-1 2024-09-03 [1] CRAN (R 4.3.3)
## pillar 1.10.2 2025-04-05 [1] CRAN (R 4.3.3)
## pkgbuild 1.4.3 2023-12-10 [1] CRAN (R 4.3.2)
## pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.3.2)
## pkgload 1.3.4 2024-01-16 [1] CRAN (R 4.3.2)
## plyr 1.8.9 2023-10-02 [1] CRAN (R 4.3.2)
## profvis 0.3.8 2023-05-02 [1] CRAN (R 4.3.2)
## promises 1.2.1 2023-08-10 [1] CRAN (R 4.3.2)
## proxy 0.4-27 2022-06-09 [1] CRAN (R 4.3.2)
## purrr 1.0.2 2023-08-10 [1] CRAN (R 4.3.2)
## qvalue * 2.34.0 2023-10-24 [1] Bioconductor
## R6 2.6.1 2025-02-15 [1] CRAN (R 4.3.3)
## Rcpp 1.0.11 2023-07-06 [1] CRAN (R 4.3.1)
## readxl 1.4.3 2023-07-06 [1] CRAN (R 4.3.2)
## remotes 2.5.0 2024-03-17 [1] CRAN (R 4.3.3)
## reshape2 1.4.4 2020-04-09 [1] CRAN (R 4.3.2)
## rlang 1.1.3 2024-01-10 [1] CRAN (R 4.3.2)
## rmarkdown 2.25 2023-09-18 [1] CRAN (R 4.3.1)
## rootSolve 1.8.2.4 2023-09-21 [1] CRAN (R 4.3.1)
## rstudioapi 0.15.0 2023-07-07 [1] CRAN (R 4.3.2)
## sass 0.4.8 2023-12-06 [1] CRAN (R 4.3.2)
## scales 1.3.0 2023-11-28 [1] CRAN (R 4.3.2)
## sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.3.2)
## shiny 1.8.0 2023-11-17 [1] CRAN (R 4.3.2)
## stringi 1.8.3 2023-12-11 [1] CRAN (R 4.3.2)
## stringr 1.5.1 2023-11-14 [1] CRAN (R 4.3.2)
## tibble 3.2.1 2023-03-20 [1] CRAN (R 4.3.2)
## tidyselect 1.2.1 2024-03-11 [1] CRAN (R 4.3.3)
## urlchecker 1.0.1 2021-11-30 [1] CRAN (R 4.3.2)
## usethis 2.2.3 2024-02-19 [1] CRAN (R 4.3.2)
## vctrs 0.6.5 2023-12-01 [1] CRAN (R 4.3.2)
## withr 3.0.2 2024-10-28 [1] CRAN (R 4.3.3)
## writexl * 1.5.0 2024-02-09 [1] CRAN (R 4.3.2)
## xfun 0.41 2023-11-01 [1] CRAN (R 4.3.2)
## xtable 1.8-4 2019-04-21 [1] CRAN (R 4.3.2)
## yaml 2.3.8 2023-12-11 [1] CRAN (R 4.3.2)
##
## [1] C:/Users/tfrancisco/AppData/Local/R/win-library/4.3
## [2] C:/Users/tfrancisco/AppData/Local/Programs/R/R-4.3.2/library
##
## ──────────────────────────────────────────────────────────────────────────────