You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Conversion to dgCMatrix Causes Segmentation Fault #129
are there more than 2^31 - 1 nonzero elements in the counts? Can the dgCMatrix be constructed without the involvement of DelayedArray? Would SparseArray provide a more useful representation?
Thanks for the additional details. It's very useful to know that your big 109578x2389155 SummarizedExperiment was obtained by combining 19 smaller ones with combineCols().
A major problem here is that combineCols() delays its operations by default. For example, with 2 small artificial SummarizedExperiment objects:
Because all operations are delayed it's VERY fast, but, unfortunately, everything you'll try to do after that is going to be HUGELY inefficient (as you've experienced).
A much better way to go is to NOT take the delayed route by using combineCols(SE1, SE2, delayed=FALSE) instead. Unfortunately this wasn't working so far on SummarizedExperiment objects that have SparseMatrix assays. But I just repaired that so it should work now with the latest version of the SparseArray package (1.12.3 in release and 1.13.3 in devel):
SEb<- combineCols(SE1, SE2, delayed=FALSE)
assay(SEb)
# <4 x 7 SparseMatrix> of type "integer" [nzcount=17 (61%)]:# [,1] [,2] [,3] [,4] [,5] [,6] [,7]# gene1 1 0 0 1 NA NA NA# gene2 0 0 2 0 3 0 0# gene3 0 1 1 0 NA NA NA# gene4 NA NA NA NA 0 0 1
This approach has 3 main advantages over the delayed route:
No need to coerce the assay of the combined object to SparseMatrix because it's already a SparseMatrix.
It should be much faster than doing delayed combineCols + coercion.
It should work even if the combined assay has more than 2^31 nonzero values.
Let's check performance on 4 artificial medium size objects:
counts1<- poissonSparseMatrix(2800, 24000, density=0.03, dimnames=list(sprintf("GENE%03d", 1:2800), NULL))
counts2<- poissonSparseMatrix(3500, 15000, density=0.03, dimnames=list(sprintf("GENE%03d", 1:3500), NULL))
counts3<- poissonSparseMatrix(2000, 22000, density=0.03, dimnames=list(sample(rownames(counts2), 2000), NULL))
counts4<- poissonSparseMatrix(4500, 35000, density=0.03, dimnames=list(sprintf("GENE%03d", 1001:5500), NULL))
SE1<- SummarizedExperiment(list(counts=counts1))
SE2<- SummarizedExperiment(list(counts=counts2))
SE3<- SummarizedExperiment(list(counts=counts3))
SE4<- SummarizedExperiment(list(counts=counts4))
## Delayed combineCols + coercion:
system.time(combined_counts_A<- as(assay(combineCols(SE1, SE2, SE3, SE4)), "SparseMatrix"))
# user system elapsed # 12.795 5.518 18.272 ## Immediate combineCols (i.e. effectively combine the assays):
system.time(combined_counts_B<- assay(combineCols(SE1, SE2, SE3, SE4, delayed=FALSE)))
# user system elapsed # 0.692 0.394 1.085 ## 'combined_counts_A' and 'combined_counts_B' contain the same values but their classes## are different flavors of the SparseArray virtual class:
class(combined_counts_A)
# [1] "COO_SparseMatrix"# attr(,"package")# [1] "SparseArray"
class(combined_counts_B)
# [1] "SVT_SparseMatrix"# attr(,"package")# [1] "SparseArray"## Sanity check:
identical(as(combined_counts_A, class(combined_counts_B)), combined_counts_B)
# [1] TRUE
As you can see, the speedup for this artificial example is about 17x but it might be even more with your 19 objects.
We don't know what versions of SparseArray or S4Arrays you were using before. Without this information we cannot deduce that the changes that happened between SparseArray 1.12.2 and 1.12.3 or between S4Arrays 1.12.0 and 1.12.1 introduced this new problem. I highly doubt it because these changes didn't touch the internal representation of SVT_SparseMatrix objects.
BTW we still don't know what version of Bioconductor you're using (you still haven't communicated your sessionInfo()). Note that SparseArray 1.12.3 and S4Arrays 1.12.1 belong to BioC 3.23 (the current release) so make sure that you're indeed using BioC 3.23 and that all your packages are up to date.
In particular make sure that you have the latest version of DropletUtils, which is version 1.32.0.
Also, as always, a reproducible example is needed.
Once we have your sessionInfo() and a reproducible example, we can start working on a solution.
I can also reproduce. This seems like a long standing bug in read10xCounts() where the function returns an SVT_SparseMatrix object with invalid SVT leaves. The bug is present since the mtx.class argument was introduced in BioC 3.22 because I can reproduce the problem with all versions of BioC since 3.22. Therefore I'm not sure how this could have ever worked for you.
counts(dataset)@SVT<- lapply(counts(dataset)@SVT, function(x) {
if (length(x[[1]]) >0) {
x
} else {
NULL
}
})
I presume that SVT_SparseMatrix expects all non-NULL leaves to be non-empty, which is alluded to in ?"SVT_SparseMatrix-class":
Each list element is an SVT of depth 0 (as described above), or a NULL if the corresponding column is empty (i.e. has no nonzero data).
Somehow, I had interpreted this as a suggestion that empty columns could be replaced with NULLs for efficiency, rather than an explicit requirement for correctness. So read10xCounts() wasn't going out of its way to detect empty columns and set the corresponding leaves to NULL. But no matter, this is easy enough to fix, see MarioniLab/DropletUtils#133.
@hpages I'd request adding a line to the description of leaf vectors in ?"SVT_SparseMatrix-class" that mentions that a leaf vector must have a non-empty nzoffs and nzvals, otherwise the leaf should be NULL. I semi-regularly consult this page as a reference of what the SVT_SparseMatrix is meant to be, given that I sometimes need to construct them manually without going through dgCMatrix intermediates.
Note that the specs in src/leaf_utils.h also cover the lacunar leaf topic which my fix_SVT_leaves() utility above handles. In Dario's case, this contributes to a 20% additional memory footprint reduction after the 70% reduction obtained by replacing empty leaves with NULLs.
@DarioS Does the combineCols(..., delayed=FALSE) suggestion work for you? Note that the fill argument is set to NA by default. However, depending on what you're going to do downstream with the combined object, you might want to consider setting fill to 0L. It should speed up things even more and further reduce the memory footprint of the combined object.
## IMPORTANT: Make sure to use 0L and not 0 so the combined object is still of type "integer". Using## 0 would change its type to "double", which would significantly (and unnecessarily) increase its## memory footprint.
system.time(combined_counts_B0<- assay(combineCols(SE1, SE2, SE3, SE4, fill=0L, delayed=FALSE)))
# user system elapsed # 0.393 0.009 0.401
type(combined_counts_B0)
# [1] "integer"
object.size(combined_counts_B0)
# 84833400 bytes
So in this case using fill=0L reduces memory footprint by 20x (YMMV).
But again, it all depends on what you're going to do downstream.
Activity
vjcitn commentedon Sep 11, 2026
are there more than 2^31 - 1 nonzero elements in the counts? Can the dgCMatrix be constructed without the involvement of DelayedArray? Would SparseArray provide a more useful representation?
hpages commentedon Sep 11, 2026
As always, we need a repreoducible example. In particular how was
databaseobtained and what's yoursessionInfo()?As suggested by Vince, have you tried to replace
as(assay(database, "counts"), "dgCMatrix")withas(assay(database, "counts"), "SparseMatrix")?hpages commentedon Sep 11, 2026
I'd also need to see the output of:
and
and
Thanks!
DarioS commentedon Sep 12, 2026
Yes, although "invalid permissions" seems like something that needs administrator privileges.
SparseMatrixconversion also fails after some hours with an even less informative error message.The object looks like
Strange that it is
double. The nineteen source objects used to build it bycombineColsare unexpectedly a mix.Looking at one of them, D007 started as Seurat object before
as.SingleCellExperiment. It was double all along.D011 is a set of H5AD files downloaded from Zenodo.
So, this Bioconductor package imports counts matrices not as integers but as doubles. But, not the problem.
The conversion of this small matrix worked fine. The
databasetree isTo create an example, I considered
mockSCEbut it only ever creates a densematrixwhich is not suitable for this size.hpages commentedon Sep 22, 2026
Thanks for the additional details. It's very useful to know that your big 109578x2389155 SummarizedExperiment was obtained by combining 19 smaller ones with
combineCols().A major problem here is that
combineCols()delays its operations by default. For example, with 2 small artificial SummarizedExperiment objects:Because all operations are delayed it's VERY fast, but, unfortunately, everything you'll try to do after that is going to be HUGELY inefficient (as you've experienced).
A much better way to go is to NOT take the delayed route by using
combineCols(SE1, SE2, delayed=FALSE)instead. Unfortunately this wasn't working so far on SummarizedExperiment objects that have SparseMatrix assays. But I just repaired that so it should work now with the latest version of the SparseArray package (1.12.3 in release and 1.13.3 in devel):This approach has 3 main advantages over the delayed route:
combineCols+ coercion.Let's check performance on 4 artificial medium size objects:
As you can see, the speedup for this artificial example is about 17x but it might be even more with your 19 objects.
Hope this helps,
H.
DarioS commentedon Sep 23, 2026
Nice but upgrading the packages has created a new issue. I processed GSM8370247 (seems lots of empty droplets were uploaded).
The crude empty droplet filtering worked before I installed SparseArray 1.12.3 and S4Arrays 1.12.1 and restarted R.
hpages commentedon Sep 23, 2026
We don't know what versions of SparseArray or S4Arrays you were using before. Without this information we cannot deduce that the changes that happened between SparseArray 1.12.2 and 1.12.3 or between S4Arrays 1.12.0 and 1.12.1 introduced this new problem. I highly doubt it because these changes didn't touch the internal representation of SVT_SparseMatrix objects.
BTW we still don't know what version of Bioconductor you're using (you still haven't communicated your
sessionInfo()). Note that SparseArray 1.12.3 and S4Arrays 1.12.1 belong to BioC 3.23 (the current release) so make sure that you're indeed using BioC 3.23 and that all your packages are up to date.In particular make sure that you have the latest version of DropletUtils, which is version 1.32.0.
Also, as always, a reproducible example is needed.
Once we have your
sessionInfo()and a reproducible example, we can start working on a solution.DarioS commentedon Sep 23, 2026
Ah, was trying to avoid overwhelming details. A (hopefully reproducible) example:
getGEOSuppFiles("GSM8370247"), or simply internet browser. Then,The environment is:
vjcitn commentedon Sep 23, 2026
Yes, I reproduced it on mac. What was annoying is that the session hangs. I do not get to the "recover on error" R traceback.
hpages commentedon Sep 23, 2026
I can also reproduce. This seems like a long standing bug in
read10xCounts()where the function returns an SVT_SparseMatrix object with invalid SVT leaves. The bug is present since themtx.classargument was introduced in BioC 3.22 because I can reproduce the problem with all versions of BioC since 3.22. Therefore I'm not sure how this could have ever worked for you.I suggest that you open an issue at https://github.com/MarioniLab/DropletUtils to report the problem.
In the mean time a workaround is to "manually" fix the invalid SVT leaves of the SVT_SparseMatrix object:
Then:
Whoa, this is incredibly sparse!
hpages commentedon Sep 23, 2026
FWIW another fix is to not specify
mtx.class="SVT_SparseMatrix"to get a dgCMatrix object and to coerce to SVT_SparseMatrix:This is easier/faster than the
fix_SVT_leavesworkaround but it only works if the number of nonzero values is < 2^31.LTLA commentedon Sep 24, 2026
Basically the fix distills down to:
I presume that
SVT_SparseMatrixexpects all non-NULLleaves to be non-empty, which is alluded to in?"SVT_SparseMatrix-class":Somehow, I had interpreted this as a suggestion that empty columns could be replaced with
NULLs for efficiency, rather than an explicit requirement for correctness. Soread10xCounts()wasn't going out of its way to detect empty columns and set the corresponding leaves toNULL. But no matter, this is easy enough to fix, see MarioniLab/DropletUtils#133.@hpages I'd request adding a line to the description of leaf vectors in
?"SVT_SparseMatrix-class"that mentions that a leaf vector must have a non-emptynzoffsandnzvals, otherwise the leaf should beNULL. I semi-regularly consult this page as a reference of what theSVT_SparseMatrixis meant to be, given that I sometimes need to construct them manually without going throughdgCMatrixintermediates.hpages commentedon Sep 24, 2026
@LTLA The full specs for SVT leaves are in
src/leaf_utils.h:https://github.com/Bioconductor/SparseArray/blob/9a0e93056f05879a15057e3d597d0102068f09bb/src/leaf_utils.h#L10-L31
I'm now referring the developer to this file in the man page: https://github.com/Bioconductor/SparseArray/blob/9a0e93056f05879a15057e3d597d0102068f09bb/man/SVT_SparseArray-class.Rd#L212-L213
Note that the specs in
src/leaf_utils.halso cover the lacunar leaf topic which myfix_SVT_leaves()utility above handles. In Dario's case, this contributes to a 20% additional memory footprint reduction after the 70% reduction obtained by replacing empty leaves with NULLs.hpages commentedon Sep 24, 2026
Back to the original issue.
@DarioS Does the
combineCols(..., delayed=FALSE)suggestion work for you? Note that thefillargument is set toNAby default. However, depending on what you're going to do downstream with the combined object, you might want to consider settingfillto0L. It should speed up things even more and further reduce the memory footprint of the combined object.Using
fill=NA(the default):Using
fill=0L:So in this case using
fill=0Lreduces memory footprint by 20x (YMMV).But again, it all depends on what you're going to do downstream.
DarioS commentedon Sep 24, 2026
It works.
hpages commentedon Sep 24, 2026
you're welcome