I’ll first load some R packages.
library(data.table)
library(broman)
library(devtools)
I’ll now load the GeneSeek annotation files for the MegaMUGA and GigaMUGA arrays. There are a few rows above the header, and then some control rows at the bottom, that need to be clipped out.
gm <- data.table::fread("../GeneSeek/gigamuga_geneseek.csv",
skip=7, data.table=FALSE)
wh <- which(gm[,1]=="[Controls]")
gm <- gm[1:(wh-1),]
rownames(gm) <- gm$Name
mm <- data.table::fread("../GeneSeek/megamuga_geneseek.csv",
skip=7, data.table=FALSE)
wh <- which(mm[,1]=="[Controls]")
mm <- mm[1:(wh-1),]
rownames(mm) <- mm$Name
I’ll also load the file with the names that are in common between the two arrays.
common <- data.table::fread("../GeneSeek/common_markers.csv", data.table=FALSE)
common_markers <- common[-1,1]
The GigaMUGA array contains 143,446 markers, which the MegaMUGA has 77,808 markers. The marker names in each file are all distinct. There are 66,992 markers in common between the two arrays.
The Excel file that I received from GeneSeek has three worksheets; the second and third worksheets contain annotation information, including probe sequences, for the GigaMUGA and MegaMUGA files, respectively. The first worksheet indicates which markers are in common between the GigaMUGA and MegaMUGA arrays, but it’s a bit odd. It has counts of markers on each array, which are correct, but there’s a count that says “On Both
”, but it’s 67,015, which is different that what you get in a direct check of the marker names. Also it has a list of names, presumably the ones that are in common, and it does have 67,015 markers, but only there are only 67,000 distinct names, and only 66,992 are present in the GigaMUGA array (The same, number of markers are in the MegaMUGA array.) The other 23 markers contain all the duplicates and look like control stuff and not real marker names: Extension, Hybridization, Non-Polymorphic, Non-Specific Binding, Restoration, Staining, Stringency, and Target Removal. It seems like those came from the [Control]
section of the other two worksheets.
The key summaries here:
The GeneSeek annotation files contain four sets of sequences. AlleleA_ProbeSeq
, AlleleB_ProbeSeq
, SourceSeq
, and TopGenomicSeq
.
The AlleleA_ProbeSeq
sequences are all length 50.
The AlleleB_ProbeSeq
sequences are mostly missing. In the GigaMUGA file there are 469 that are not missing; in the MegaMUGA file there are 193 that are not missing.
The SourceSeq
and TopGenomicSeq
sequences are the same information. They’re identical when the IlmnStrand
column is TOP
. The latter is the reverse complement of the former when the IlmnStrand
column is BOT
.
The SourceSeq
all seem to contain text like [A/C]
that indicates the SNP. This is absent from the AlleleA_ProbeSeq
which are all strictly ACGT characters.
There probe sequences are not all distinct. On the GigaMUGA array, there are 2,711 that appear twice, 49 that appear three times, 2 that appear four times.
On the MegaMUGA array, there are 50 that appear twice, 0 that appear three times, 1 that appears four times.
Of the 66,992 markers that are in common between the two arrays, there are 29 markers that have different probe sequences.
For the small number of cases where there’s an AlleleB_ProbeSeq
, in addition to AlleleA_ProbeSeq
, I think the last base on the two is the SNP. They should match at the other 49 bases. Here’s a quick check of that:
# cases with AlleleB probe sequence
wh_gm <- which(gm$AlleleB_ProbeSeq != "")
# grab the two sequences
probeA <- gm$AlleleA_ProbeSeq[wh_gm]
probeB <- gm$AlleleB_ProbeSeq[wh_gm]
# first 49 characters are the same
stopifnot( all(substr(probeA, 1, 49) == substr(probeB, 1, 49)) )
# grab the last base from each, which should be the alleles
alleleA <- substr(probeA, 50, 50)
alleleB <- substr(probeB, 50, 50)
# the alleles are different
stopifnot( all( alleleA != alleleB ) )
# the alleles match the SNP column
stopifnot( all( gm$SNP[wh_gm] == paste0("[", alleleA, "/", alleleB, "]") ) )
# now all that for the MegaMUGA array...
# cases with AlleleB probe sequence
wh_mm <- which(mm$AlleleB_ProbeSeq != "")
# grab the two sequences
probeA <- mm$AlleleA_ProbeSeq[wh_mm]
probeB <- mm$AlleleB_ProbeSeq[wh_mm]
# first 49 characters are the same
stopifnot( all(substr(probeA, 1, 49) == substr(probeB, 1, 49)) )
# grab the last base from each, which should be the alleles
alleleA <- substr(probeA, 50, 50)
alleleB <- substr(probeB, 50, 50)
# the alleles are different
stopifnot( all( alleleA != alleleB ) )
# the alleles match the SNP column
stopifnot( all( mm$SNP[wh_mm] == paste0("[", alleleA, "/", alleleB, "]") ) )
The code aboved checked that for the cases where there’s an AlleleB probe sequence, the first 49 bases are the same, between the two alleles, while the 50th base is the SNP
For the remaining probes, I believe that the SNPs is just after the probe sequence. We’ll check that by comparing it to the source sequences, next.
I’m presuming that the probe sequences match the part of the source sequence from just before the [A/T]
SNP part, but I want to check.
I’ll first check that the SourceSeq
column matches the TopGenomicSeq
column, using the reverse complement when SourceStrand
is "BOT"
.
source("revcomp.R") # reverse complement
# reverse complement of source sequence
# ...a few cases of lower case letters in the latter
# ...and it's tricky handling the SNP alleles, b/c they shouldn't be swapped
gm_source_rev <- revcomp(gm$SourceSeq)
# when SourceStrand=="TOP", the SourceSeq and TopGenomicSeq match
# when SourceStrand=="BOT", the TopGenomicSeq is the reverse complement
stopifnot( all((gm$SourceSeq == gm$TopGenomicSeq & gm$SourceStrand=="TOP") |
(gm_source_rev == gm$TopGenomicSeq & gm$SourceStrand=="BOT")) )
# now same thing with MegaMUGA
# reverse complement of source sequence
# ...a few cases of lower case letters in the latter
# ...and it's tricky handling the SNP alleles, b/c they shouldn't be swapped
mm_source_rev <- revcomp(mm$SourceSeq)
# when SourceStrand=="TOP", the SourceSeq and TopGenomicSeq match
# when SourceStrand=="BOT", the TopGenomicSeq is the reverse complement
stopifnot( all((mm$SourceSeq == mm$TopGenomicSeq & mm$SourceStrand=="TOP") |
(mm_source_rev == mm$TopGenomicSeq & mm$SourceStrand=="BOT")) )
If we made it here, than that was true and the SourceSeq
and TopGenomicSeq
columns have the same information, so we can just focus on the SourceSeq
column.
I now want to compare the SNP information in the source sequence to the SNP column.
# grab SNP info from SourceSeq, with alleles in both orders
gm_source_snp <- sub("^.*\\[", "[", sub("\\].*$", "]", gm$SourceSeq))
gm_source_snp_comp <- justcomp(gm_source_snp)
# check that SNP column is one or the other
stopifnot( all(gm_source_snp == gm$SNP | gm_source_snp_comp == gm$SNP) )
# BUT NOTE: I thought whether it was one or the other depended on whether
# IlmnStrand == SourceStrand or not, but there are some differences.
gm_snp_strand_problem <- ((gm_source_snp == gm$SNP & gm$IlmnStrand != gm$SourceStrand) |
(gm_source_snp_comp == gm$SNP & gm$IlmnStrand == gm$SourceStrand))
# but these are all A/T or C/G cases, so just about defining which allele is A vs B
stopifnot( all( gm_source_snp[gm_snp_strand_problem] %in% c("[A/T]", "[C/G]") ) )
mm_source_snp <- sub("^.*\\[", "[", sub("\\].*$", "]", mm$SourceSeq))
mm_source_snp_comp <- justcomp(mm_source_snp)
# check that SNP column is one or the other
stopifnot( all(mm_source_snp == mm$SNP | mm_source_snp_comp == mm$SNP) )
# mismatches between direct/complement match and whether strands are the same
mm_snp_strand_problem <- ((mm_source_snp == mm$SNP & mm$IlmnStrand != mm$SourceStrand) |
(mm_source_snp_comp == mm$SNP & mm$IlmnStrand == mm$SourceStrand))
# but these are all A/T or C/G cases, so just about defining which allele is A vs B
stopifnot( all( mm_source_snp[mm_snp_strand_problem] %in% c("[A/T]", "[C/G]") ) )
From this, we find that the SNP
column is the same as the SNP in the SourceSeq
, or it is the complement of that. I thought whether it was a direct match or a match to the complement would be according to whether IlmnStrand == SourceStrand
or not, but that there are 237 mismatches on the GigaMUGA and 99 mismatches on the MegaMUGA. But these are all cases of A/T or C/G SNPs, so it’s just a matter of the definition of the A vs B alleles.
Finally (for this section), I’ll now turn to whether the SNP sequences match the source sequences.
I’ll first split the source sequences into the bits before and after the snp. I’ll also compare the SNP part of the source sequence to the SNP column. Then I’ll compare the parts of the source sequence to AlleleA probes.
# grab the sequence before and after the SNP
gm_source_spl <- strsplit(gm$SourceSeq, "\\[[ACGT]/[ACGT]\\]")
gm_before_snp <- sapply(gm_source_spl, "[", 1) # a bunch of these have length 0
gm_after_snp <- sapply(gm_source_spl, "[", 2)
gm_after_snp[is.na(gm_after_snp)] <- "" # turn NAs into emptys
# for the SNPs that have both A and B probe sequences, drop the last character from the A sequence
gm_probe <- gm$AlleleA_ProbeSeq
gm_probe[gm$AlleleB_ProbeSeq != ""] <- substr(gm_probe[gm$AlleleB_ProbeSeq != ""], 1, 49)
gm_probe_rev <- revcomp(gm_probe)
# subset the before and after sequences to no longer than the probe
nc <- nchar(gm_before_snp)
start <- nc - nchar(gm_probe)+1
start[start < 1] <- 1
gm_before_sub <- gm_before_snp
gm_before_sub[nc>0] <- substr(gm_before_snp[nc>0], start[nc>0], nc[nc>0])
nc <- nchar(gm_after_snp)
end <- nchar(gm_probe)
end[end > nc] <- nc[end > nc]
gm_after_sub <- gm_after_snp
gm_after_sub[nc>0] <- substr(gm_after_snp[nc>0], 1, end[nc>0])
# There were 6 cases where probe doesn't match sequence, but due to lower case letters
# so, convert to upper case
gm_before_sub <- toupper(gm_before_sub)
gm_after_sub <- toupper(gm_after_sub)
stopifnot( all(gm_after_sub == gm_probe_rev | gm_before_sub == gm_probe) )
# also check the strand
stopifnot( all((gm_after_sub == gm_probe_rev & gm$IlmnStrand != gm$SourceStrand) |
(gm_before_sub == gm_probe & gm$IlmnStrand == gm$SourceStrand)) )
# Now the MegaMUGA
# grab the sequence before and after the SNP
mm_source_spl <- strsplit(mm$SourceSeq, "\\[[ACGT]/[ACGT]\\]")
mm_before_snp <- sapply(mm_source_spl, "[", 1) # a bunch of these have length 0
mm_after_snp <- sapply(mm_source_spl, "[", 2)
mm_after_snp[is.na(mm_after_snp)] <- "" # turn NAs into emptys
# for the SNPs that have both A and B probe sequences, drop the last character from the A sequence
mm_probe <- mm$AlleleA_ProbeSeq
mm_probe[mm$AlleleB_ProbeSeq != ""] <- substr(mm_probe[mm$AlleleB_ProbeSeq != ""], 1, 49)
mm_probe_rev <- revcomp(mm_probe)
# subset the before and after sequences to no longer than the probe
nc <- nchar(mm_before_snp)
start <- nc - nchar(mm_probe)+1
start[start < 1] <- 1
mm_before_sub <- mm_before_snp
mm_before_sub[nc>0] <- substr(mm_before_snp[nc>0], start[nc>0], nc[nc>0])
nc <- nchar(mm_after_snp)
end <- nchar(mm_probe)
end[end > nc] <- nc[end > nc]
mm_after_sub <- mm_after_snp
mm_after_sub[nc>0] <- substr(mm_after_snp[nc>0], 1, end[nc>0])
# There were 9 cases where probe doesn't match sequence, but due to lower case letters
# so, convert to upper case
mm_before_sub <- toupper(mm_before_sub)
mm_after_sub <- toupper(mm_after_sub)
stopifnot( all(mm_after_sub == mm_probe_rev | mm_before_sub == mm_probe) )
# also check the strand
stopifnot( all((mm_after_sub == mm_probe_rev & mm$IlmnStrand != mm$SourceStrand) |
(mm_before_sub == mm_probe & mm$IlmnStrand == mm$SourceStrand)) )
Having gotten here, the probes match the source sequences exactly. SNP is just after the sequence when IlmnStrand
is the same as SourceStrand
, and is just before the reverse-complement of the sequence when the strands are different.
We should do a quick check of whether the probe sequences match those in the UNC files.
load("../UNC/snps.gigamuga.Rdata")
gm_unc <- snps
load("../UNC/snps.megamuga.Rdata")
mm_unc <- snps
rm(snps)
The UNC files for the GigaMUGA and MegaMUGA arrays have 143,259 and 77,808 rows, respectively. So the MegaMUGA file has the same number of rows as the file from GeneSeek, but the GigaMUGA file seems to be missing 187 rows. The missing markers are all have names that start CTRL
or CTRL2
. So control probes, I suppose.
stopifnot( all(gm_unc$marker %in% gm$Name) )
stopifnot( all(sort(mm_unc$marker) == sort(mm$Name)) )
Otherwise, all of the marker names are the same.
Let’s look to see if the sequences are the same.
# sequences in MM files match?
stopifnot( all(mm_unc[mm$Name, "seq.A"] == mm$AlleleA_ProbeSeq) )
# subset GeneSeek GM file to those in the UNC file
gm_sub <- gm[gm$Name %in% gm_unc$marker,]
# sequences in GM files match?
stopifnot( all(gm_unc[gm_sub$Name, "seq.A"] == gm_sub$AlleleA_ProbeSeq) )
Yes, the sequences all match exactly.
I should also compare the chromosome assignments between the GeneSeek and UNC files.
For the MegaMUGA array, they are the same except some of Y in GeneSeek are P in UNC, and most of the P and M in UNC are 0 in GeneSeek.
Here’s a table just of the markers where they differ. The rows are the UNC assignments; the columns are the GeneSeek ones.
chr_lev <- c(0:19,"X","Y","P","M")
mm_unc_chr <- factor(sub("chr", "", mm_unc[mm$Name, "chr"]), chr_lev)
mm_gs_chr <- factor(mm$Chr, chr_lev)
mm_chr_mismatch <- mm_unc_chr != mm_gs_chr
table(mm_unc_chr[mm_chr_mismatch], mm_gs_chr[mm_chr_mismatch])
##
## 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 X Y P M
## 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 7 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 13 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 14 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 16 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 17 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 18 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 19 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## X 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## Y 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## P 14 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 69 0 0
## M 46 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
For GM, on the other hand, there are a whole bunch of markers with disagreement. Also note that the UNC file for the GigaMUGA also has a bunch of markers with NA
in the chromosome column. I think they should be “P”, so I’ll call them that for now.
gm_unc_chr <- factor(sub("chr", "", gm_unc[gm_sub$Name, "chr"]), chr_lev)
gm_unc_chr[is.na(gm_unc_chr)] <- "P"
gm_gs_chr <- factor(gm_sub$Chr, chr_lev)
gm_chr_mismatch <- gm_unc_chr != gm_gs_chr
table(gm_unc_chr[gm_chr_mismatch], gm_gs_chr[gm_chr_mismatch])
##
## 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 X Y P M
## 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## 1 8 0 11 9 13 3 3 6 1 1 1 5 2 2 5 4 0 4 4 1 1 0 0 0
## 2 14 5 0 6 11 19 6 7 4 3 2 6 2 5 5 5 1 5 0 3 3 0 0 0
## 3 0 8 2 0 3 6 6 6 5 5 6 3 2 1 4 2 1 4 2 3 5 0 0 0
## 4 0 5 2 5 0 4 4 2 2 3 2 8 4 8 10 9 3 4 1 2 7 0 0 0
## 5 0 7 3 2 1 0 4 3 5 1 6 5 0 6 3 7 8 8 3 1 4 1 0 0
## 6 0 2 2 5 6 5 0 4 3 3 0 1 2 3 4 1 3 1 4 4 16 0 0 0
## 7 1 8 7 1 7 2 2 0 4 4 5 0 2 4 2 0 3 2 1 2 2 0 0 0
## 8 0 3 4 3 1 4 1 3 0 3 5 1 3 1 0 0 1 3 3 3 1 0 0 0
## 9 0 6 8 6 2 4 6 4 4 0 1 2 2 3 1 2 2 4 1 0 1 0 0 0
## 10 0 4 4 7 3 1 3 3 1 1 0 2 2 2 1 4 1 3 1 4 0 0 0 0
## 11 1 4 5 2 3 2 0 2 4 1 6 0 3 0 2 5 2 3 0 3 2 0 0 0
## 12 1 1 2 7 2 3 0 2 3 2 2 0 0 3 1 1 0 1 2 3 3 0 0 0
## 13 2 6 3 4 2 5 1 1 1 2 4 2 1 0 4 2 2 6 4 4 7 0 0 0
## 14 0 5 7 4 2 3 3 5 2 2 1 1 2 1 0 5 0 2 0 1 1 0 0 0
## 15 0 5 4 3 3 2 1 4 3 1 2 1 4 0 3 0 1 3 2 2 3 0 0 0
## 16 2 1 1 3 5 3 2 2 1 2 0 1 2 5 1 2 0 3 1 2 1 0 0 0
## 17 3 3 5 0 0 2 1 3 0 1 2 4 3 0 2 1 2 0 2 2 1 0 0 0
## 18 0 4 2 3 3 1 2 4 1 1 1 0 2 2 3 2 3 1 0 5 1 0 0 0
## 19 0 1 3 1 2 1 1 2 1 0 1 0 1 0 0 2 2 2 2 0 2 0 0 0
## X 0 6 10 5 6 2 6 5 1 2 6 6 1 4 6 5 0 3 4 4 0 0 0 0
## Y 0 1 0 2 2 1 1 2 1 1 0 1 1 0 1 2 0 0 0 0 33 0 0 0
## P 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 0 0 64 0 0
## M 32 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Here are the versions of R and R packages that I am using.
devtools::session_info()
## ─ Session info ─────────────────────────────────────────────────────────────────────────────────────────────
## setting value
## version R version 4.0.3 (2020-10-10)
## os Pop!_OS 20.10
## system x86_64, linux-gnu
## ui X11
## language en_US:en
## collate en_US.UTF-8
## ctype en_US.UTF-8
## tz America/Chicago
## date 2020-11-26
##
## ─ Packages ─────────────────────────────────────────────────────────────────────────────────────────────────
## package * version date lib source
## assertthat 0.2.1 2019-03-21 [1] CRAN (R 4.0.0)
## broman * 0.71-6 2020-11-24 [1] CRAN (R 4.0.3)
## callr 3.5.1 2020-10-13 [1] CRAN (R 4.0.3)
## cli 2.2.0 2020-11-20 [1] CRAN (R 4.0.3)
## crayon 1.3.4 2017-09-16 [1] CRAN (R 4.0.0)
## data.table * 1.13.2 2020-10-19 [1] CRAN (R 4.0.3)
## desc 1.2.0 2018-05-01 [1] CRAN (R 4.0.0)
## devtools * 2.3.2 2020-09-18 [1] CRAN (R 4.0.2)
## digest 0.6.27 2020-10-24 [1] CRAN (R 4.0.3)
## ellipsis 0.3.1 2020-05-15 [1] CRAN (R 4.0.0)
## evaluate 0.14 2019-05-28 [1] CRAN (R 4.0.0)
## fansi 0.4.1 2020-01-08 [1] CRAN (R 4.0.0)
## fs 1.5.0 2020-07-31 [1] CRAN (R 4.0.2)
## glue 1.4.2 2020-08-27 [1] CRAN (R 4.0.2)
## htmltools 0.5.0 2020-06-16 [1] CRAN (R 4.0.1)
## knitr 1.30 2020-09-22 [1] CRAN (R 4.0.2)
## magrittr 2.0.1 2020-11-17 [1] CRAN (R 4.0.3)
## memoise 1.1.0 2017-04-21 [1] CRAN (R 4.0.0)
## pkgbuild 1.1.0 2020-07-13 [1] CRAN (R 4.0.2)
## pkgload 1.1.0 2020-05-29 [1] CRAN (R 4.0.0)
## prettyunits 1.1.1 2020-01-24 [1] CRAN (R 4.0.0)
## processx 3.4.4 2020-09-03 [1] CRAN (R 4.0.2)
## ps 1.4.0 2020-10-07 [1] CRAN (R 4.0.2)
## R6 2.5.0 2020-10-28 [1] CRAN (R 4.0.3)
## remotes 2.2.0 2020-07-21 [1] CRAN (R 4.0.3)
## rlang 0.4.8 2020-10-08 [1] CRAN (R 4.0.2)
## rmarkdown 2.5 2020-10-21 [1] CRAN (R 4.0.3)
## rprojroot 2.0.2 2020-11-15 [1] CRAN (R 4.0.3)
## sessioninfo 1.1.1 2018-11-05 [1] CRAN (R 4.0.0)
## stringi 1.5.3 2020-09-09 [1] CRAN (R 4.0.2)
## stringr 1.4.0 2019-02-10 [1] CRAN (R 4.0.0)
## testthat 3.0.0 2020-10-31 [1] CRAN (R 4.0.3)
## usethis * 1.6.3 2020-09-17 [1] CRAN (R 4.0.2)
## withr 2.3.0 2020-09-22 [1] CRAN (R 4.0.2)
## xfun 0.19 2020-10-30 [1] CRAN (R 4.0.3)
## yaml 2.2.1 2020-02-01 [1] CRAN (R 4.0.0)
##
## [1] /home/kbroman/Rlibs
## [2] /usr/local/lib/R/site-library
## [3] /usr/lib/R/site-library
## [4] /usr/lib/R/library