Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Depends:
Imports:
broom,
cli,
cpp11bigwig (>= 0.3.0),
cpp11bigwig (>= 0.3.1),
dplyr (>= 1.0.0),
ggplot2 (>= 3.3.0),
lifecycle,
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export(tribble)
export(valr_example)
export(values)
export(values_unique)
import(cpp11bigwig)
import(dplyr)
import(ggplot2)
importFrom(broom,tidy)
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

## New features

* `bed12_to_exons()` now accepts BED12 data from any source with the standard column order, including `cpp11bigwig::read_bigbed()` output, which uses UCSC column names (e.g. `blockSizes`/`chromStarts`). Previously only `read_bed12()` output (with valr's column names) was accepted. It also accepts a path or URL to a BED12 bigBed (`.bb`) file directly, validating the file is BED12 (via its header's declared field count) before conversion (#461).

* `bed_glyph()` gained a `max_rows` argument to control the row limit on the
evaluated result (default `100`), and now reports the offending row count when
the limit is exceeded. It also validates `label` (an absent column is an error
Expand Down
40 changes: 39 additions & 1 deletion R/bed12_to_exons.r
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
#' number, with respect to strand (i.e., the first exon for `-` strand
#' genes will have larger start and end coordinates).
#'
#' @param x [ivl_df]
#' @param x [ivl_df], or a path or URL to a BED12 bigBed (`.bb`) file, in which
#' case the whole file is read and validated as BED12 (via its header's
#' declared field count) before conversion.
#'
#' @family utilities
#'
Expand All @@ -13,15 +15,51 @@
#'
#' bed12_to_exons(x)
#'
#' # BED12 from any source with the standard column order is accepted,
#' # including `cpp11bigwig::read_bigbed()`, which uses UCSC column names.
#' bb <- system.file("extdata", "test.bb", package = "cpp11bigwig")
#' bed12_to_exons(cpp11bigwig::read_bigbed(bb))
#'
#' # a .bb path can also be passed directly; it is read and validated as BED12
#' bed12_to_exons(bb)
#'
#' @export
bed12_to_exons <- function(x) {
check_required(x)

# accept a path/URL to a BED12 bigBed file directly, validating its schema
if (is_bigbed_path(x)) {
x <- read_bigbed12(x)
}

x <- check_interval(x)

if (!ncol(x) == 12) {
cli::cli_abort("expected 12 column input")
}

# The parser looks up `exon_sizes`/`exon_starts` by name. If they are absent,
# assume a foreign BED12 source in standard column order (e.g.
# cpp11bigwig::read_bigbed(), which uses UCSC names like blockSizes/chromStarts)
# and standardize names positionally. Input that already carries valr's names is
# left untouched, so reordered-but-named columns still resolve correctly.
if (!all(c("exon_sizes", "exon_starts") %in% names(x))) {
names(x) <- names(bed12_coltypes)
}

# The parser reads `exon_sizes`/`exon_starts` as comma-separated character
# values. Validate up front so non-BED12 input fails with a clear message
# instead of a cryptic error from the C++ backend.
not_chr <- c("exon_sizes", "exon_starts")[
!vapply(x[c("exon_sizes", "exon_starts")], is.character, logical(1))
]
if (length(not_chr) > 0) {
cli::cli_abort(c(
"{.arg x} does not look like BED12 input.",
"x" = "{.field {not_chr}} must be {.cls character} ({.val comma,separated,values})."
))
}

res <- bed12toexons_impl(x)
res <- tibble::as_tibble(res)
res <- res[, c("chrom", "start", "end", "name", "score", "strand")]
Expand Down
23 changes: 23 additions & 0 deletions R/bigwig.r
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,29 @@ is_bigwig_path <- function(y) {
bigwig_ext(y) %in% c("bw", "bigwig", "bb", "bigbed")
}

# Is `y` a single path/URL to a bigBed file?
#' @noRd
is_bigbed_path <- function(y) {
is_bigwig_path(y) && bigwig_ext(y) %in% c("bb", "bigbed")
}

# Read an entire bigBed file as a BED12 interval dataframe.
#
# Validates via the file header that it declares 12 standard BED fields before
# reading, so a non-BED12 bigBed (e.g. a bedN+ file with 12 total columns)
# errors up front instead of being silently misinterpreted as BED12.
#' @noRd
read_bigbed12 <- function(file) {
n_fields <- bigbed_info(file)[["defined_field_count"]]
if (!identical(as.integer(n_fields), 12L)) {
cli::cli_abort(c(
"{.file {file}} is not a BED12 bigBed file.",
"x" = "Its header declares {n_fields} standard BED field{?s}; BED12 requires 12."
))
}
read_bigbed(file)
}

# Read a bigWig/bigBed file scoped to the intervals in `x`.
#
# Returns a de-duplicated [ivl_df] of file entries overlapping `x`. Overlapping
Expand Down
2 changes: 0 additions & 2 deletions R/reexport-cpp11bigwig.r
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#' @importFrom cpp11bigwig read_bigwig
#' @export
cpp11bigwig::read_bigwig

#' @importFrom cpp11bigwig read_bigbed
#' @export
cpp11bigwig::read_bigbed
1 change: 1 addition & 0 deletions R/valr-package.r
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#' @importFrom lifecycle deprecated
#' @import ggplot2
#' @import dplyr
#' @import cpp11bigwig
#' @importFrom lifecycle deprecated
## usethis namespace: end

Expand Down
12 changes: 11 additions & 1 deletion man/bed12_to_exons.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 65 additions & 0 deletions tests/testthat/test_bed12_to_exons.r
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,73 @@ test_that("invalid ncol causes an error", {
expect_snapshot(bed12_to_exons(x), error = TRUE)
})

test_that("non-BED12 12-column input errors clearly", {
# 12 columns but the exon columns are not comma-separated character values
x <- tibble::as_tibble(
setNames(
c(
list(chrom = "chr1", start = 1L, end = 100L),
rep(list(0L), 9)
),
c("chrom", "start", "end", letters[1:9])
)
)
expect_error(bed12_to_exons(x), "does not look like BED12")
})

test_that("BED12 is parsed correctly", {
x <- read_bed12(valr_example("mm9.refGene.bed.gz"))
expect_equal(nrow(bed12_to_exons(x)), 1683)
expect_equal(ncol(bed12_to_exons(x)), 6)
})

test_that("BED12 with UCSC column names (e.g. read_bigbed) is accepted (#461)", {
bb <- system.file("extdata", "test.bb", package = "cpp11bigwig")
x <- cpp11bigwig::read_bigbed(bb)

res <- bed12_to_exons(x)
expect_named(res, c("chrom", "start", "end", "name", "score", "strand"))
expect_equal(ncol(res), 6)
expect_equal(nrow(res), sum(x$blockCount))

# positional rename matches valr-named input exactly
y <- x
names(y) <- c(
"chrom",
"start",
"end",
"name",
"score",
"strand",
"cds_start",
"cds_end",
"item_rgb",
"exon_count",
"exon_sizes",
"exon_starts"
)
expect_equal(res, bed12_to_exons(y))

# valr-named columns are looked up by name, so reordering them is preserved
# (positional renaming is only applied to foreign sources)
expect_equal(res, bed12_to_exons(y[, rev(names(y))]))
})

test_that("bed12_to_exons accepts a BED12 .bb file path directly (#461)", {
bb <- system.file("extdata", "test.bb", package = "cpp11bigwig")

# passing the path is equivalent to reading it first
expect_equal(
bed12_to_exons(bb),
bed12_to_exons(cpp11bigwig::read_bigbed(bb))
)
})

test_that("bed12_to_exons rejects a non-BED12 bigBed file", {
# a bigBed whose header declares fewer than 12 standard BED fields (e.g. bed6,
# or bed9+3) must error rather than be misread as BED12
testthat::local_mocked_bindings(
bigbed_info = function(file) list(defined_field_count = 6L)
)
expect_error(bed12_to_exons("not_really.bb"), "not a BED12")
})
Loading