diff --git a/src/genomicranges/GenomicRanges.py b/src/genomicranges/GenomicRanges.py index 17a1373..04a7898 100644 --- a/src/genomicranges/GenomicRanges.py +++ b/src/genomicranges/GenomicRanges.py @@ -1006,13 +1006,13 @@ def to_pandas(self): _rdf["seqnames"] = self.get_seqnames() _rdf["strand"] = self.get_strand(as_type="list") - if self._names is not None: - _rdf.index = self._names - if self._mcols is not None: if self._mcols.shape[1] > 0: _rdf = pd.concat([_rdf, self._mcols.to_pandas()], axis=1) + if self._names is not None: + _rdf.index = self._names + return _rdf @classmethod diff --git a/tests/test_gr_initialize_pandas.py b/tests/test_gr_initialize_pandas.py index 4adaa40..626dfd0 100644 --- a/tests/test_gr_initialize_pandas.py +++ b/tests/test_gr_initialize_pandas.py @@ -1,6 +1,7 @@ import pytest from genomicranges import GenomicRanges from biocframe import BiocFrame +from iranges import IRanges from random import random import pandas as pd @@ -41,3 +42,29 @@ def test_from_pandas_should_fail(): ) with pytest.raises(Exception): GenomicRanges.from_pandas(df_gr) + + +def test_to_pandas_with_names_and_mcols_is_positional(): + # Regression test for a bug where to_pandas() set the index to `names` + # before concatenating `mcols`, whose index was still the default + # positional RangeIndex. pandas.concat(axis=1) then outer-joined on the + # two disjoint indices instead of binding columns positionally, doubling + # the row count and NaN-splitting every row. + ranges = IRanges(start=[0, 10, 20], width=[5, 5, 5]) + mcols = BiocFrame({"gene_id": ["g1", "g2", "g3"], "gene_name": ["A", "B", "C"]}) + gr = GenomicRanges( + seqnames=["1", "1", "1"], + ranges=ranges, + strand=["+", "+", "-"], + names=["g1", "g2", "g3"], + mcols=mcols, + ) + + df = gr.to_pandas() + + assert df.shape == (3, 7) + assert list(df.index) == ["g1", "g2", "g3"] + assert df["gene_id"].notna().all() + assert df["seqnames"].notna().all() + assert list(df["gene_id"]) == ["g1", "g2", "g3"] + assert list(df["gene_name"]) == ["A", "B", "C"]