Skip to content

Commit ab6ea27

Browse files
committed
igblastr 1.3.1: Fix representation of missing values in custom annotations
We cannot use the string NA to represent missing values in the custom "ndm" or ".aux" files that we create, because this crashes IgBLAST! So now we use the following special values instead: o "N/A" for missing character values; o -1 for missing integer values. See long comment at the top of R/igdata-IO.R for more information.
1 parent ec5f0bf commit ab6ea27

18 files changed

Lines changed: 508 additions & 172 deletions

‎DESCRIPTION‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Description: The igblastr package provides functions to conveniently install
1717
biocViews: Immunology, Immunogenetics, ImmunoOncology, CellBiology
1818
URL: https://bioconductor.org/packages/igblastr
1919
BugReports: https://github.com/HyrienLab/igblastr/issues
20-
Version: 1.3.0
20+
Version: 1.3.1
2121
License: Artistic-2.0
2222
Encoding: UTF-8
2323
Authors@R: c(
@@ -42,7 +42,7 @@ Collate:
4242
utils.R
4343
internet-utils.R
4444
long_to_wide_airr.R
45-
read_broken_table.R
45+
igdata-IO.R
4646
translate_codons.R
4747
allele2gene.R
4848
parse_imgt_fasta_headers.R

‎R/OGRDB-utils.R‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ OGRDB_URL <- "https://ogrdb.airr-community.org/"
1515
encode_OGRDB_URL_component <- function(component)
1616
{
1717
stopifnot(isSingleString(component))
18-
gsub("/", "%25252f", URLencode(component))
18+
gsub("/", "%25252f", URLencode(component), fixed=TRUE)
1919
}
2020

2121
.OGRDB_organism_url <- function(organism, for.download=FALSE)
@@ -280,7 +280,7 @@ download_OGRDB_germline_set <-
280280
.encode_OGRDB_path_component <- function(path_component)
281281
{
282282
stopifnot(isSingleNonWhiteString(path_component))
283-
gsub("/", ".slash.", chartr(" ", "_", path_component))
283+
gsub("/", ".slash.", chartr(" ", "_", path_component), fixed=TRUE)
284284
}
285285

286286
.get_path_to_stored_OGRDB_germline_set <-

‎R/auxdata-IO.R‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,12 @@ check_auxdata_col2class <- function(auxdata, what="'auxdata'")
4848

4949
read_auxdata <- function(filepath)
5050
{
51-
read_broken_table(filepath, AUXDATA_COL2CLASS)
51+
read_igdata(filepath, AUXDATA_COL2CLASS)
5252
}
5353

5454
write_auxdata <- function(auxdata, file="")
5555
{
5656
check_auxdata_col2class(auxdata)
57-
header <- paste0("#", paste(colnames(auxdata), collapse=", "))
58-
cat(header, "\n", sep="", file=file)
59-
write.table(auxdata, file, append=TRUE, quote=FALSE,
60-
sep="\t", row.names=FALSE, col.names=FALSE)
57+
write_igdata(auxdata, file=file)
6158
}
6259

‎R/clean_allele_set.R‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ checkarg_with.intdata <- function(with.intdata, gapped)
317317

318318
## Annotate the J alleles.
319319
auxdata <- compute_auxdata(allele_set, codon_starts=codon_starts,
320-
no.warning=TRUE)
320+
no.warnings=TRUE)
321321

322322
## Sanity checks.
323323
stopifnot(identical(auxdata[ , "allele_name"], allele_names))

‎R/compute_auxdata.R‎

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,16 +177,33 @@ J_alleles_with_missing_coding_frame_start <- function(auxdata)
177177
bad_alleles <- J_alleles_with_missing_coding_frame_start(auxdata)
178178
if (length(bad_alleles) != 0L) {
179179
in1string <- paste(bad_alleles, collapse=", ")
180-
warning(wmsg("the \"coding frame start\" could not be determined ",
181-
"for J allele(s): ", in1string),
180+
warning(wmsg("The \"coding frame start\" could not be determined ",
181+
"for J allele(s): ", in1string, "."),
182182
"\n ",
183183
wmsg("--> coding_frame_start, cdr3_end, and extra_bps ",
184184
"were set to NA for these alleles"))
185185
}
186186
}
187187

188+
### Very rare but can happen (we've only seen this for rhesus monkey
189+
### allele IGHJ0-ZXTW*01 from OGRDB so far).
190+
warn_if_negative_cdr3_end <- function(auxdata, what)
191+
{
192+
bad_idx <- which(auxdata[ , "cdr3_end"] < 0L)
193+
if (length(bad_idx) != 0L) {
194+
in1string <- paste(auxdata[bad_idx , "allele_name"], collapse=", ")
195+
warning(wmsg("The ", what, " is negative for J allele(s): ",
196+
in1string, "."),
197+
"\n ",
198+
wmsg("Note that you won't be able to save the returned ",
199+
"data.frame with write_auxdata() unless you replace ",
200+
"the negative \"cdr3_end\" value(s) with NA(s) or drop ",
201+
"the row(s) with a negative \"cdr3_end\" value."))
202+
}
203+
}
204+
188205
### Returns a data.frame with 1 row per sequence in 'J_alleles'.
189-
compute_auxdata <- function(J_alleles, codon_starts=NULL, no.warning=FALSE)
206+
compute_auxdata <- function(J_alleles, codon_starts=NULL, no.warnings=FALSE)
190207
{
191208
if (!is(J_alleles, "DNAStringSet"))
192209
stop(wmsg("'J_alleles' must be DNAStringSet object"))
@@ -202,8 +219,8 @@ compute_auxdata <- function(J_alleles, codon_starts=NULL, no.warning=FALSE)
202219
if (!is.null(codon_starts))
203220
codon_starts <- .normarg_codon_starts(codon_starts, allele_names)
204221

205-
if (!isTRUEorFALSE(no.warning))
206-
stop(wmsg("'no.warning' must be TRUE or FALSE"))
222+
if (!isTRUEorFALSE(no.warnings))
223+
stop(wmsg("'no.warnings' must be TRUE or FALSE"))
207224

208225
JH_alleles <- J_alleles[allele_groups == "IGHJ"]
209226
JK_alleles <- J_alleles[allele_groups == "IGKJ"]
@@ -223,8 +240,11 @@ compute_auxdata <- function(J_alleles, codon_starts=NULL, no.warning=FALSE)
223240
## Add "extra_bps" column.
224241
ans$extra_bps <- (width(J_alleles) - ans[ , "coding_frame_start"]) %% 3L
225242

226-
if (!no.warning)
243+
if (!no.warnings) {
227244
.warn_if_missing_coding_frame_starts(ans)
245+
what <- "computed CDR3 end position"
246+
warn_if_negative_cdr3_end(ans, what)
247+
}
228248

229249
ans
230250
}

‎R/create_region_db.R‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,12 +280,11 @@ create_V_region_db <- function(fasta_files, destdir,
280280
### create_J_region_db()
281281
###
282282

283-
### Not exported!
284283
### Adds the auxdata to the db ONLY if its "coding_frame_start" column
285284
### contains no NAs.
286285
### Returns TRUE if that's actually the case (and therefore the auxdata
287286
### was added), and FALSE otherwise.
288-
add_computed_auxdata_to_db <- function(auxdata, destdir, verbose=FALSE)
287+
.add_computed_auxdata_to_db <- function(auxdata, destdir, verbose=FALSE)
289288
{
290289
bad_alleles <- J_alleles_with_missing_coding_frame_start(auxdata)
291290
if (length(bad_alleles) != 0L) {
@@ -313,6 +312,11 @@ add_computed_auxdata_to_db <- function(auxdata, destdir, verbose=FALSE)
313312
auxdata_dir <- dirname(auxdata_path)
314313
stopifnot(!dir.exists(auxdata_dir))
315314
dir.create(auxdata_dir)
315+
316+
## write_auxdata() will reject a data.frame with negative values in
317+
## the "cdr3_end" column, so we replace them with NAs.
318+
bad_idx <- which(auxdata[ , "cdr3_end"] < 0L)
319+
auxdata[bad_idx, "cdr3_end"] <- NA_integer_
316320
write_auxdata(auxdata, auxdata_path)
317321

318322
if (verbose)
@@ -363,7 +367,7 @@ create_J_region_db <- function(fasta_files, destdir,
363367
auxdata <- mcols(allele_set, use.names=FALSE)
364368
stopifnot(identical(allele_names, auxdata[ , "allele_name"]))
365369
auxdata <- as.data.frame(auxdata)
366-
add_computed_auxdata_to_db(auxdata, destdir, verbose=verbose)
370+
.add_computed_auxdata_to_db(auxdata, destdir, verbose=verbose)
367371
}
368372

369373
allele_set

‎R/download_OGRDB_germline_json.R‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ download_OGRDB_germline_json <- function(organism, germline_sets,
139139
setNames(as.character(allele_description[fields]), fields)
140140
}
141141

142+
### Returns a character matrix with colnames on it.
142143
.make_matrix_from_allele_data_list <- function(data, colnames, what)
143144
{
144145
stopifnot(is.list(data), is.character(colnames))
@@ -163,6 +164,7 @@ download_OGRDB_germline_json <- function(organism, germline_sets,
163164
### extract_intdata_from_ogrdb_json()
164165
###
165166

167+
### Returns a named character vector.
166168
.extract_IMGT_v_gene_delineation <-
167169
function(allele_description, extra_fields=NULL)
168170
{
@@ -229,7 +231,7 @@ extract_intdata_from_ogrdb_json <- function(json_path, extra_fields=NULL)
229231
df <- matrix2df(m[ , names(col2class)], col2class)
230232
df <- cbind(df, coding_frame_start=integer(nrow(df)))
231233
if (!is.null(extra_fields))
232-
df <- cbind(df, m[ , extra_fields])
234+
df <- cbind(df, m[ , extra_fields, drop=FALSE])
233235
df
234236
}
235237

@@ -362,6 +364,8 @@ validate_OGRDB_intdata <- function(organism, germline_set, source_set=FALSE,
362364
### first 6 amino acids (AEYFQH) are the last 6 amino acids of the CDR3,
363365
### confirming that the 18th nucleotide in the coding sequence is where the
364366
### CDR3 ends.
367+
###
368+
### Returns a named character vector.
365369
.extract_j_annotation <-
366370
function(allele_description, extra_fields=NULL)
367371
{
@@ -408,8 +412,10 @@ extract_auxdata_from_ogrdb_json <- function(json_path, extra_fields=NULL)
408412
what <- c("J allele descriptions found in JSON file: ", json_path)
409413
m <- .make_matrix_from_allele_data_list(data, expected_names, what)
410414
df <- matrix2df(m[ , names(AUXDATA_COL2CLASS)], AUXDATA_COL2CLASS)
415+
what <- "CDR3 end position reported in the JSON file"
416+
warn_if_negative_cdr3_end(df, what)
411417
if (!is.null(extra_fields))
412-
df <- cbind(df, m[ , extra_fields])
418+
df <- cbind(df, m[ , extra_fields, drop=FALSE])
413419
df
414420
}
415421

‎R/igblastn.R‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,18 @@ print.igblastn_raw_output <- function(x, ...) cat(x, sep="\n")
180180
stderr_lines <- readLines(stderr_file)
181181
if (length(stderr_lines) == 0L && status == 0L)
182182
return()
183-
## When the 'igblastn' executable crashes, we typically get a 0 status
184-
## and an empty out file.
185-
if (status == 0L && isTRUE(out_is_empty))
183+
## When the 'igblastn' executable crashes, we typically get an empty
184+
## out file. But is an empty out file always indicating a crash? We're
185+
## not sure. So we add a few more conditions before concluding that we
186+
## got a crash:
187+
## - In our experience an empty out file and a 0 status seem to always
188+
## be associated with a crash.
189+
## - An empty out file, a non-0 status, and a single stderr line
190+
## (e.g. "Segmentation fault (core dumped)") also seem to always
191+
## be associated with a crash.
192+
crash <- isTRUE(out_is_empty) &&
193+
(status == 0L || length(stderr_lines) == 1L)
194+
if (crash)
186195
.stop_on_igblastn_exe_errors(stderr_lines, crash=TRUE)
187196
warn_msgs <- .parse_errors_or_warnings(stderr_lines, "warning:")
188197
for (msg in warn_msgs)

0 commit comments

Comments
 (0)