Skip to content

extract_cmap_data_from_siginfo(landmark = FALSE) fails on LINCS2020 geneinfo: duplicate 'row.names' are not allowed #3

Description

@alexyfyf

extract_cmap_data_from_siginfo(landmark = FALSE) fails on LINCS2020 geneinfo: duplicate 'row.names' are not allowed

Summary

extract_cmap_data_from_siginfo() labels the rows of the extracted matrix with
geneinfo$gene_symbol. That column is unique within each feature_space, but not across the
full 12,328-feature space: MIA2 appears twice in the official geneinfo_beta.txt. Since the
symbols are assigned as data.frame row names, landmark = FALSE fails outright on the canonical
reference file:

Error in `.rowNamesDF<-`(x, value = value) : duplicate 'row.names' are not allowed

landmark = TRUE is unaffected, which is why the README quick-start and the Step 3 example both
work — they pass landmark = TRUE.

Reproduction

Self-contained and runs in seconds. It needs only geneinfo_beta.txt (1.1 MB); the GCTX is stubbed
out because the bug is in row labelling and never touches matrix values.

library(CONCERTDR)

gi_url  <- "https://s3.amazonaws.com/macchiato.clue.io/builds/LINCS2020/geneinfo_beta.txt"
gi_file <- file.path(tempdir(), "geneinfo_beta.txt")
if (!file.exists(gi_file)) download.file(gi_url, gi_file, quiet = TRUE)
gi <- read.delim(gi_file, stringsAsFactors = FALSE, quote = "", comment.char = "")

sum(duplicated(gi$gene_symbol))
#> 1
gi[gi$gene_symbol == "MIA2", c("gene_id", "ensembl_id", "gene_symbol", "feature_space")]
#>      gene_id      ensembl_id gene_symbol feature_space
#> 2154  117153 ENSG00000150526        MIA2      inferred
#> 5100    4253 ENSG00000150527        MIA2 best inferred

# Minimal stand-in for level5_beta_*.gctx: real gene_ids, four made-up signatures.
si <- data.frame(sig_id = paste0("S", 1:4), pert_id = paste0("BRD-X", 1:4),
                 cmap_name = paste0("d", 1:4), pert_type = "trt_cp", cell_iname = "THP1",
                 pert_idose = "10 uM", pert_itime = "24 h", is_hiq = 1L,
                 stringsAsFactors = FALSE)
g <- file.path(tempdir(), "mini.gctx"); unlink(g)
rhdf5::h5createFile(g)
for (p in c("/0","/0/DATA","/0/DATA/0","/0/META","/0/META/ROW","/0/META/COL"))
    rhdf5::h5createGroup(g, p)
rhdf5::h5write(matrix(0, nrow(gi), 4), g, "/0/DATA/0/matrix")
rhdf5::h5write(as.character(gi$gene_id), g, "/0/META/ROW/id")
rhdf5::h5write(si$sig_id, g, "/0/META/COL/id")
rhdf5::h5closeAll()

run <- function(landmark) tryCatch(
    sprintf("OK - %d genes", nrow(extract_cmap_data_from_siginfo(
        siginfo_file = si, geneinfo_file = gi, gctx_file = g,
        filter_quality = FALSE, landmark = landmark, verbose = FALSE))),
    error = function(e) paste("ERROR:", conditionMessage(e)))

run(TRUE)
#> [1] "OK - 978 genes"
run(FALSE)
#> [1] "ERROR: duplicate 'row.names' are not allowed"

The same failure occurs with the real level5_beta_trt_cp_n720216x12328.gctx; the stub only makes
it fast to demonstrate.

Cause

get_rid() returns gene_symbol as genenames for every row of geneinfo, and
extract_cmap_data_from_siginfo() then does:

expression_data <- as.data.frame(mat)
rownames(expression_data) <- genenames   # <- fails when genenames has a duplicate

MIA2 is attached to two distinct features — Entrez 117153 / ENSG00000150526 ("melanoma inhibitory
activity 2", inferred) and Entrez 4253 / ENSG00000150527 ("MIA SH3 domain ER export factor 2",
best inferred) — a leftover of the NCBI MIA2/CTAGE5 reorganisation.

Why it matters

landmark = FALSE is the only usable setting for a small query signature, so this blocks the
package for that whole class of analysis rather than being a corner case. For a 168-gene
differential-expression signature we measured:

reference space features query genes covered
landmark = TRUE 978 13 / 168
landmark = FALSE 12,328 113 / 168

13 genes is not enough signature to score, so the full feature space is required — and it is
exactly the path that errors.

Suggested fix

Make gene_symbol unique before it is used as row names, preferring the better-annotated feature
(landmark > best inferred > inferred) rather than dropping arbitrarily:

rank_space <- c(landmark = 1L, "best inferred" = 2L, inferred = 3L)
pref <- rank_space[geneinfo_df$feature_space]
pref[is.na(pref)] <- 4L
keep <- !duplicated(geneinfo_df$gene_symbol[order(pref)])[order(order(pref))]
geneinfo_df <- geneinfo_df[keep, ]

Verified against the reproduction above: landmark = FALSE then returns 12,327 genes. A warning
naming what was dropped would help, since users should know a feature disappeared.

make.unique(genenames) also stops the error but is worse: it renames the feature to MIA2.1,
where no query gene can ever match it, so the row is silently dead weight rather than absent.

Two smaller observations from the same code path

Happy to split these into their own issues if you'd prefer.

  1. The row order of geneinfo does not match GCTX row order, so the index passed to
    rhdf5::h5read() is unsorted — for the landmark subset as well as the full space. The first few
    geneinfo features sit at GCTX rows 9675, 8434, 9676, 11053, … Labelling is correct today,
    because this rhdf5 returns rows in the order requested rather than sorted order; I verified that
    against rows read one at a time from the real GCTX, for both landmark settings. But it is an
    unstated dependency on rhdf5's semantics, and if it ever changed the result would be a silently
    mislabelled matrix rather than an error. Sorting the requested index (and labelling from the
    same order) would remove the dependency.

  2. annotate_drug_results() keeps only the first compoundinfo row per pert_id
    (compinfo <- compinfo[!duplicated(compinfo$pert_id), , drop = FALSE]), so a multi-target
    compound loses its other targets. That may well be deliberate, but it does discard annotation
    that matters if the target column is used for target-level aggregation downstream.

Environment

CONCERTDR 0.99.2 (GithubSHA1 d27d37cc7cfff088069a80ddad5482a20923cc08)
R version 4.6.1 (2026-06-24), x86_64-conda-linux-gnu, Rocky Linux 9.8
rhdf5 and data.table from conda-forge
CMap files: LINCS2020 geneinfo_beta.txt (12,328 rows, dated 2020-12-17)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions