From 2d3dcc92d69bd73b5c4ae9dfd83bddc4e775756f Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Sun, 5 May 2024 12:20:49 -0700 Subject: [PATCH 1/5] support non mapping types for uns/metadata when importing from h5ad/anndata --- .../SingleCellExperiment.py | 118 +++++------------- tests/test_sce_io.py | 8 ++ 2 files changed, 42 insertions(+), 84 deletions(-) diff --git a/src/singlecellexperiment/SingleCellExperiment.py b/src/singlecellexperiment/SingleCellExperiment.py index 0df4ff9..9b3e708 100644 --- a/src/singlecellexperiment/SingleCellExperiment.py +++ b/src/singlecellexperiment/SingleCellExperiment.py @@ -30,9 +30,7 @@ def _validate_reduced_dims(reduced_dims, shape): if reduced_dims is None: - raise ValueError( - "'reduced_dims' cannot be `None`, must be assigned to an empty dictionary." - ) + raise ValueError("'reduced_dims' cannot be `None`, must be assigned to an empty dictionary.") if not isinstance(reduced_dims, dict): raise TypeError("'reduced_dims' is not a dictionary.") @@ -40,21 +38,16 @@ def _validate_reduced_dims(reduced_dims, shape): for rdname, mat in reduced_dims.items(): if not hasattr(mat, "shape"): raise TypeError( - f"Reduced dimension: '{rdname}' must be a matrix-like object." - "Does not contain a `shape` property." + f"Reduced dimension: '{rdname}' must be a matrix-like object." "Does not contain a `shape` property." ) if shape[1] != mat.shape[0]: - raise ValueError( - f"Reduced dimension: '{rdname}' does not contain embeddings for all cells." - ) + raise ValueError(f"Reduced dimension: '{rdname}' does not contain embeddings for all cells.") def _validate_alternative_experiments(alternative_experiments, shape): if alternative_experiments is None: - raise ValueError( - "'alternative_experiments' cannot be `None`, must be assigned to an empty dictionary." - ) + raise ValueError("'alternative_experiments' cannot be `None`, must be assigned to an empty dictionary.") if not isinstance(alternative_experiments, dict): raise TypeError("'alternative_experiments' is not a dictionary.") @@ -67,10 +60,7 @@ def _validate_alternative_experiments(alternative_experiments, shape): ) if shape[1] != alternative_experiment.shape[1]: - raise ValueError( - f"Alternative experiment: '{alt_name}' does not contain same number of" - " cells." - ) + raise ValueError(f"Alternative experiment: '{alt_name}' does not contain same number of" " cells.") def _validate_pairs(pairs): @@ -204,18 +194,14 @@ def __init__( self._reduced_dims = reduced_dims if reduced_dims is not None else {} - self._alternative_experiments = ( - alternative_experiments if alternative_experiments is not None else {} - ) + self._alternative_experiments = alternative_experiments if alternative_experiments is not None else {} self._row_pairs = row_pairs if row_pairs is not None else {} self._column_pairs = column_pairs if column_pairs is not None else {} if validate: _validate_reduced_dims(self._reduced_dims, self._shape) - _validate_alternative_experiments( - self._alternative_experiments, self._shape - ) + _validate_alternative_experiments(self._alternative_experiments, self._shape) _validate_pairs(self._row_pairs) _validate_pairs(self._column_pairs) @@ -309,14 +295,10 @@ def __repr__(self) -> str: output += ", row_ranges=" + self._row_ranges.__repr__() if self._alternative_experiments is not None: - output += ", alternative_experiments=" + ut.print_truncated_list( - self.alternative_experiment_names - ) + output += ", alternative_experiments=" + ut.print_truncated_list(self.alternative_experiment_names) if self._reduced_dims is not None: - output += ", reduced_dims=" + ut.print_truncated_list( - self.reduced_dim_names - ) + output += ", reduced_dims=" + ut.print_truncated_list(self.reduced_dim_names) if self._main_experiment_name is not None: output += ", main_experiment_name=" + self._main_experiment_name @@ -344,10 +326,14 @@ def __str__(self) -> str: output += f"assays({len(self.assay_names)}): {ut.print_truncated_list(self.assay_names)}\n" - output += f"row_data columns({len(self._rows.column_names)}): {ut.print_truncated_list(self._rows.column_names)}\n" + output += ( + f"row_data columns({len(self._rows.column_names)}): {ut.print_truncated_list(self._rows.column_names)}\n" + ) output += f"row_names({0 if self._row_names is None else len(self._row_names)}): {' ' if self._row_names is None else ut.print_truncated_list(self._row_names)}\n" - output += f"column_data columns({len(self._cols.column_names)}): {ut.print_truncated_list(self._cols.column_names)}\n" + output += ( + f"column_data columns({len(self._cols.column_names)}): {ut.print_truncated_list(self._cols.column_names)}\n" + ) output += f"column_names({0 if self._column_names is None else len(self._column_names)}): {' ' if self._column_names is None else ut.print_truncated_list(self._column_names)}\n" output += f"main_experiment_name: {' ' if self._main_experiment_name is None else self._main_experiment_name}\n" @@ -373,9 +359,7 @@ def get_reduced_dims(self) -> Dict[str, Any]: """ return self._reduced_dims - def set_reduced_dims( - self, reduced_dims: Dict[str, Any], in_place: bool = False - ) -> "SingleCellExperiment": + def set_reduced_dims(self, reduced_dims: Dict[str, Any], in_place: bool = False) -> "SingleCellExperiment": """Set new reduced dimensions. Args: @@ -421,9 +405,7 @@ def get_reduced_dim_names(self) -> List[str]: """ return list(self._reduced_dims.keys()) - def set_reduced_dim_names( - self, names: List[str], in_place: bool = False - ) -> "SingleCellExperiment": + def set_reduced_dim_names(self, names: List[str], in_place: bool = False) -> "SingleCellExperiment": """Replace :py:attr:`~.reduced_dims`'s names. Args: @@ -439,9 +421,7 @@ def set_reduced_dim_names( """ current_names = self.get_reduced_dim_names() if len(names) != len(current_names): - raise ValueError( - "Length of 'names' does not match the number of `reduced_dims`." - ) + raise ValueError("Length of 'names' does not match the number of `reduced_dims`.") new_reduced_dims = OrderedDict() for idx in range(len(names)): @@ -499,9 +479,7 @@ def reduced_dim(self, dimension: Union[str, int]) -> Any: return self._reduced_dims[dimension] - raise TypeError( - f"'dimension' must be a string or integer, provided '{type(dimension)}'." - ) + raise TypeError(f"'dimension' must be a string or integer, provided '{type(dimension)}'.") ################################ ######>> main_expt_name <<###### @@ -515,9 +493,7 @@ def get_main_experiment_name(self) -> Optional[str]: """ return self._main_experiment_name - def set_main_experiment_name( - self, name: Optional[str], in_place: bool = False - ) -> "SingleCellExperiment": + def set_main_experiment_name(self, name: Optional[str], in_place: bool = False) -> "SingleCellExperiment": """Set new experiment data (assays). Args: @@ -609,9 +585,7 @@ def get_alternative_experiment_names(self) -> List[str]: """ return list(self._alternative_experiments.keys()) - def set_alternative_experiment_names( - self, names: List[str], in_place: bool = False - ) -> "SingleCellExperiment": + def set_alternative_experiment_names(self, names: List[str], in_place: bool = False) -> "SingleCellExperiment": """Replace :py:attr:`~.alternative_experiment`'s names. Args: @@ -627,15 +601,11 @@ def set_alternative_experiment_names( """ current_names = self.get_alternative_experiment_names() if len(names) != len(current_names): - raise ValueError( - "Length of 'names' does not match the number of `alternative_experiments`." - ) + raise ValueError("Length of 'names' does not match the number of `alternative_experiments`.") new_alt_expts = OrderedDict() for idx in range(len(names)): - new_alt_expts[names[idx]] = self._alternative_experiments.pop( - current_names[idx] - ) + new_alt_expts[names[idx]] = self._alternative_experiments.pop(current_names[idx]) output = self._define_output(in_place) output._alternative_experiments = new_alt_expts @@ -680,13 +650,9 @@ def alternative_experiment(self, name: Union[str, int]) -> Any: raise IndexError("Index cannot be negative.") if name > len(self.alternative_experiment_names): - raise IndexError( - "Index greater than the number of alternative experiments." - ) + raise IndexError("Index greater than the number of alternative experiments.") - return self._alternative_experiments[ - self.alternative_experiment_names[name] - ] + return self._alternative_experiments[self.alternative_experiment_names[name]] elif isinstance(name, str): if name not in self._alternative_experiments: raise AttributeError(f"Alternative experiment: {name} does not exist.") @@ -707,9 +673,7 @@ def get_row_pairs(self) -> Dict[str, Any]: """ return self._row_pairs - def set_row_pairs( - self, pairs: Dict[str, Any], in_place: bool = False - ) -> "SingleCellExperiment": + def set_row_pairs(self, pairs: Dict[str, Any], in_place: bool = False) -> "SingleCellExperiment": """Replace :py:attr:`~.row_pairs`'s names. Args: @@ -755,9 +719,7 @@ def get_row_pair_names(self) -> List[str]: """ return list(self._row_pairs.keys()) - def set_row_pair_names( - self, names: List[str], in_place: bool = False - ) -> "SingleCellExperiment": + def set_row_pair_names(self, names: List[str], in_place: bool = False) -> "SingleCellExperiment": """Replace :py:attr:`~.row_pair`'s names. Args: @@ -773,9 +735,7 @@ def set_row_pair_names( """ current_names = self.get_row_pair_names() if len(names) != len(current_names): - raise ValueError( - "Length of 'names' does not match the number of `row_pairs`." - ) + raise ValueError("Length of 'names' does not match the number of `row_pairs`.") new_row_pairs = OrderedDict() for idx in range(len(names)): @@ -811,9 +771,7 @@ def get_column_pairs(self) -> Dict[str, Any]: """ return self._column_pairs - def set_column_pairs( - self, pairs: Dict[str, Any], in_place: bool = False - ) -> "SingleCellExperiment": + def set_column_pairs(self, pairs: Dict[str, Any], in_place: bool = False) -> "SingleCellExperiment": """Replace :py:attr:`~.column_pairs`'s names. Args: @@ -859,9 +817,7 @@ def get_column_pair_names(self) -> List[str]: """ return list(self._column_pairs.keys()) - def set_column_pair_names( - self, names: List[str], in_place: bool = False - ) -> "SingleCellExperiment": + def set_column_pair_names(self, names: List[str], in_place: bool = False) -> "SingleCellExperiment": """Replace :py:attr:`~.column_pair`'s names. Args: @@ -877,9 +833,7 @@ def set_column_pair_names( """ current_names = self.get_column_pair_names() if len(names) != len(current_names): - raise ValueError( - "Length of 'names' does not match the number of `column_pairs`." - ) + raise ValueError("Length of 'names' does not match the number of `column_pairs`.") new_column_pairs = OrderedDict() for idx in range(len(names)): @@ -1033,7 +987,7 @@ def from_anndata(cls, input: "anndata.AnnData") -> "SingleCellExperiment": assays=layers, row_data=biocframe.BiocFrame.from_pandas(input.var), column_data=biocframe.BiocFrame.from_pandas(input.obs), - metadata=input.uns, + metadata={"uns": input.uns}, reduced_dims=obsm, row_pairs=varp, column_pairs=obsp, @@ -1249,9 +1203,7 @@ def relaxed_combine_columns( _new_rdim = None try: - _new_rdim = relaxed_merge_numpy_generic( - x, by="row", attr="reduced_dims", names_attr="reduced_dim_names" - ) + _new_rdim = relaxed_merge_numpy_generic(x, by="row", attr="reduced_dims", names_attr="reduced_dim_names") except Exception as e: warn( f"Cannot combine 'reduced_dimensions' across experiments, {str(e)}", @@ -1260,9 +1212,7 @@ def relaxed_combine_columns( _new_alt_expt = None try: - _new_alt_expt = relaxed_merge_generic( - x, by="column", attr="alternative_experiments" - ) + _new_alt_expt = relaxed_merge_generic(x, by="column", attr="alternative_experiments") except Exception as e: warn( f"Cannot combine 'alternative_experiments' across experiments, {str(e)}", diff --git a/tests/test_sce_io.py b/tests/test_sce_io.py index 75bd505..79dce44 100644 --- a/tests/test_sce_io.py +++ b/tests/test_sce_io.py @@ -129,6 +129,14 @@ def test_SCE_randomAnnData(): assert tse is not None assert isinstance(tse, SingleCellExperiment) + # to avoid unknown mapping types; + # ran into an issue with anndata.compat._overloaded_dict.OverloadedDict when loading a h5ad + adata.uns = {".internal": [f"obs_{i+1}" for i in range(n)]} + tse = singlecellexperiment.SingleCellExperiment.from_anndata(adata) + + assert tse is not None + assert isinstance(tse, SingleCellExperiment) + def test_SCE_to_mudata(): tse = SingleCellExperiment( From 4787173ff1137aef500fef23ea82bce4c27f0739 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 May 2024 19:21:54 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../SingleCellExperiment.py | 116 +++++++++++++----- tests/test_sce_io.py | 2 +- 2 files changed, 84 insertions(+), 34 deletions(-) diff --git a/src/singlecellexperiment/SingleCellExperiment.py b/src/singlecellexperiment/SingleCellExperiment.py index 9b3e708..b04ad31 100644 --- a/src/singlecellexperiment/SingleCellExperiment.py +++ b/src/singlecellexperiment/SingleCellExperiment.py @@ -30,7 +30,9 @@ def _validate_reduced_dims(reduced_dims, shape): if reduced_dims is None: - raise ValueError("'reduced_dims' cannot be `None`, must be assigned to an empty dictionary.") + raise ValueError( + "'reduced_dims' cannot be `None`, must be assigned to an empty dictionary." + ) if not isinstance(reduced_dims, dict): raise TypeError("'reduced_dims' is not a dictionary.") @@ -38,16 +40,21 @@ def _validate_reduced_dims(reduced_dims, shape): for rdname, mat in reduced_dims.items(): if not hasattr(mat, "shape"): raise TypeError( - f"Reduced dimension: '{rdname}' must be a matrix-like object." "Does not contain a `shape` property." + f"Reduced dimension: '{rdname}' must be a matrix-like object." + "Does not contain a `shape` property." ) if shape[1] != mat.shape[0]: - raise ValueError(f"Reduced dimension: '{rdname}' does not contain embeddings for all cells.") + raise ValueError( + f"Reduced dimension: '{rdname}' does not contain embeddings for all cells." + ) def _validate_alternative_experiments(alternative_experiments, shape): if alternative_experiments is None: - raise ValueError("'alternative_experiments' cannot be `None`, must be assigned to an empty dictionary.") + raise ValueError( + "'alternative_experiments' cannot be `None`, must be assigned to an empty dictionary." + ) if not isinstance(alternative_experiments, dict): raise TypeError("'alternative_experiments' is not a dictionary.") @@ -60,7 +67,10 @@ def _validate_alternative_experiments(alternative_experiments, shape): ) if shape[1] != alternative_experiment.shape[1]: - raise ValueError(f"Alternative experiment: '{alt_name}' does not contain same number of" " cells.") + raise ValueError( + f"Alternative experiment: '{alt_name}' does not contain same number of" + " cells." + ) def _validate_pairs(pairs): @@ -194,14 +204,18 @@ def __init__( self._reduced_dims = reduced_dims if reduced_dims is not None else {} - self._alternative_experiments = alternative_experiments if alternative_experiments is not None else {} + self._alternative_experiments = ( + alternative_experiments if alternative_experiments is not None else {} + ) self._row_pairs = row_pairs if row_pairs is not None else {} self._column_pairs = column_pairs if column_pairs is not None else {} if validate: _validate_reduced_dims(self._reduced_dims, self._shape) - _validate_alternative_experiments(self._alternative_experiments, self._shape) + _validate_alternative_experiments( + self._alternative_experiments, self._shape + ) _validate_pairs(self._row_pairs) _validate_pairs(self._column_pairs) @@ -295,10 +309,14 @@ def __repr__(self) -> str: output += ", row_ranges=" + self._row_ranges.__repr__() if self._alternative_experiments is not None: - output += ", alternative_experiments=" + ut.print_truncated_list(self.alternative_experiment_names) + output += ", alternative_experiments=" + ut.print_truncated_list( + self.alternative_experiment_names + ) if self._reduced_dims is not None: - output += ", reduced_dims=" + ut.print_truncated_list(self.reduced_dim_names) + output += ", reduced_dims=" + ut.print_truncated_list( + self.reduced_dim_names + ) if self._main_experiment_name is not None: output += ", main_experiment_name=" + self._main_experiment_name @@ -326,14 +344,10 @@ def __str__(self) -> str: output += f"assays({len(self.assay_names)}): {ut.print_truncated_list(self.assay_names)}\n" - output += ( - f"row_data columns({len(self._rows.column_names)}): {ut.print_truncated_list(self._rows.column_names)}\n" - ) + output += f"row_data columns({len(self._rows.column_names)}): {ut.print_truncated_list(self._rows.column_names)}\n" output += f"row_names({0 if self._row_names is None else len(self._row_names)}): {' ' if self._row_names is None else ut.print_truncated_list(self._row_names)}\n" - output += ( - f"column_data columns({len(self._cols.column_names)}): {ut.print_truncated_list(self._cols.column_names)}\n" - ) + output += f"column_data columns({len(self._cols.column_names)}): {ut.print_truncated_list(self._cols.column_names)}\n" output += f"column_names({0 if self._column_names is None else len(self._column_names)}): {' ' if self._column_names is None else ut.print_truncated_list(self._column_names)}\n" output += f"main_experiment_name: {' ' if self._main_experiment_name is None else self._main_experiment_name}\n" @@ -359,7 +373,9 @@ def get_reduced_dims(self) -> Dict[str, Any]: """ return self._reduced_dims - def set_reduced_dims(self, reduced_dims: Dict[str, Any], in_place: bool = False) -> "SingleCellExperiment": + def set_reduced_dims( + self, reduced_dims: Dict[str, Any], in_place: bool = False + ) -> "SingleCellExperiment": """Set new reduced dimensions. Args: @@ -405,7 +421,9 @@ def get_reduced_dim_names(self) -> List[str]: """ return list(self._reduced_dims.keys()) - def set_reduced_dim_names(self, names: List[str], in_place: bool = False) -> "SingleCellExperiment": + def set_reduced_dim_names( + self, names: List[str], in_place: bool = False + ) -> "SingleCellExperiment": """Replace :py:attr:`~.reduced_dims`'s names. Args: @@ -421,7 +439,9 @@ def set_reduced_dim_names(self, names: List[str], in_place: bool = False) -> "Si """ current_names = self.get_reduced_dim_names() if len(names) != len(current_names): - raise ValueError("Length of 'names' does not match the number of `reduced_dims`.") + raise ValueError( + "Length of 'names' does not match the number of `reduced_dims`." + ) new_reduced_dims = OrderedDict() for idx in range(len(names)): @@ -479,7 +499,9 @@ def reduced_dim(self, dimension: Union[str, int]) -> Any: return self._reduced_dims[dimension] - raise TypeError(f"'dimension' must be a string or integer, provided '{type(dimension)}'.") + raise TypeError( + f"'dimension' must be a string or integer, provided '{type(dimension)}'." + ) ################################ ######>> main_expt_name <<###### @@ -493,7 +515,9 @@ def get_main_experiment_name(self) -> Optional[str]: """ return self._main_experiment_name - def set_main_experiment_name(self, name: Optional[str], in_place: bool = False) -> "SingleCellExperiment": + def set_main_experiment_name( + self, name: Optional[str], in_place: bool = False + ) -> "SingleCellExperiment": """Set new experiment data (assays). Args: @@ -585,7 +609,9 @@ def get_alternative_experiment_names(self) -> List[str]: """ return list(self._alternative_experiments.keys()) - def set_alternative_experiment_names(self, names: List[str], in_place: bool = False) -> "SingleCellExperiment": + def set_alternative_experiment_names( + self, names: List[str], in_place: bool = False + ) -> "SingleCellExperiment": """Replace :py:attr:`~.alternative_experiment`'s names. Args: @@ -601,11 +627,15 @@ def set_alternative_experiment_names(self, names: List[str], in_place: bool = Fa """ current_names = self.get_alternative_experiment_names() if len(names) != len(current_names): - raise ValueError("Length of 'names' does not match the number of `alternative_experiments`.") + raise ValueError( + "Length of 'names' does not match the number of `alternative_experiments`." + ) new_alt_expts = OrderedDict() for idx in range(len(names)): - new_alt_expts[names[idx]] = self._alternative_experiments.pop(current_names[idx]) + new_alt_expts[names[idx]] = self._alternative_experiments.pop( + current_names[idx] + ) output = self._define_output(in_place) output._alternative_experiments = new_alt_expts @@ -650,9 +680,13 @@ def alternative_experiment(self, name: Union[str, int]) -> Any: raise IndexError("Index cannot be negative.") if name > len(self.alternative_experiment_names): - raise IndexError("Index greater than the number of alternative experiments.") + raise IndexError( + "Index greater than the number of alternative experiments." + ) - return self._alternative_experiments[self.alternative_experiment_names[name]] + return self._alternative_experiments[ + self.alternative_experiment_names[name] + ] elif isinstance(name, str): if name not in self._alternative_experiments: raise AttributeError(f"Alternative experiment: {name} does not exist.") @@ -673,7 +707,9 @@ def get_row_pairs(self) -> Dict[str, Any]: """ return self._row_pairs - def set_row_pairs(self, pairs: Dict[str, Any], in_place: bool = False) -> "SingleCellExperiment": + def set_row_pairs( + self, pairs: Dict[str, Any], in_place: bool = False + ) -> "SingleCellExperiment": """Replace :py:attr:`~.row_pairs`'s names. Args: @@ -719,7 +755,9 @@ def get_row_pair_names(self) -> List[str]: """ return list(self._row_pairs.keys()) - def set_row_pair_names(self, names: List[str], in_place: bool = False) -> "SingleCellExperiment": + def set_row_pair_names( + self, names: List[str], in_place: bool = False + ) -> "SingleCellExperiment": """Replace :py:attr:`~.row_pair`'s names. Args: @@ -735,7 +773,9 @@ def set_row_pair_names(self, names: List[str], in_place: bool = False) -> "Singl """ current_names = self.get_row_pair_names() if len(names) != len(current_names): - raise ValueError("Length of 'names' does not match the number of `row_pairs`.") + raise ValueError( + "Length of 'names' does not match the number of `row_pairs`." + ) new_row_pairs = OrderedDict() for idx in range(len(names)): @@ -771,7 +811,9 @@ def get_column_pairs(self) -> Dict[str, Any]: """ return self._column_pairs - def set_column_pairs(self, pairs: Dict[str, Any], in_place: bool = False) -> "SingleCellExperiment": + def set_column_pairs( + self, pairs: Dict[str, Any], in_place: bool = False + ) -> "SingleCellExperiment": """Replace :py:attr:`~.column_pairs`'s names. Args: @@ -817,7 +859,9 @@ def get_column_pair_names(self) -> List[str]: """ return list(self._column_pairs.keys()) - def set_column_pair_names(self, names: List[str], in_place: bool = False) -> "SingleCellExperiment": + def set_column_pair_names( + self, names: List[str], in_place: bool = False + ) -> "SingleCellExperiment": """Replace :py:attr:`~.column_pair`'s names. Args: @@ -833,7 +877,9 @@ def set_column_pair_names(self, names: List[str], in_place: bool = False) -> "Si """ current_names = self.get_column_pair_names() if len(names) != len(current_names): - raise ValueError("Length of 'names' does not match the number of `column_pairs`.") + raise ValueError( + "Length of 'names' does not match the number of `column_pairs`." + ) new_column_pairs = OrderedDict() for idx in range(len(names)): @@ -1203,7 +1249,9 @@ def relaxed_combine_columns( _new_rdim = None try: - _new_rdim = relaxed_merge_numpy_generic(x, by="row", attr="reduced_dims", names_attr="reduced_dim_names") + _new_rdim = relaxed_merge_numpy_generic( + x, by="row", attr="reduced_dims", names_attr="reduced_dim_names" + ) except Exception as e: warn( f"Cannot combine 'reduced_dimensions' across experiments, {str(e)}", @@ -1212,7 +1260,9 @@ def relaxed_combine_columns( _new_alt_expt = None try: - _new_alt_expt = relaxed_merge_generic(x, by="column", attr="alternative_experiments") + _new_alt_expt = relaxed_merge_generic( + x, by="column", attr="alternative_experiments" + ) except Exception as e: warn( f"Cannot combine 'alternative_experiments' across experiments, {str(e)}", diff --git a/tests/test_sce_io.py b/tests/test_sce_io.py index 79dce44..a744e55 100644 --- a/tests/test_sce_io.py +++ b/tests/test_sce_io.py @@ -129,7 +129,7 @@ def test_SCE_randomAnnData(): assert tse is not None assert isinstance(tse, SingleCellExperiment) - # to avoid unknown mapping types; + # to avoid unknown mapping types; # ran into an issue with anndata.compat._overloaded_dict.OverloadedDict when loading a h5ad adata.uns = {".internal": [f"obs_{i+1}" for i in range(n)]} tse = singlecellexperiment.SingleCellExperiment.from_anndata(adata) From ec896f7cb75b8a6aad5b09bb3635762349e4659a Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Sun, 5 May 2024 12:26:16 -0700 Subject: [PATCH 3/5] Update docstring --- src/singlecellexperiment/SingleCellExperiment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/singlecellexperiment/SingleCellExperiment.py b/src/singlecellexperiment/SingleCellExperiment.py index b04ad31..aedb656 100644 --- a/src/singlecellexperiment/SingleCellExperiment.py +++ b/src/singlecellexperiment/SingleCellExperiment.py @@ -1015,7 +1015,9 @@ def from_anndata(cls, input: "anndata.AnnData") -> "SingleCellExperiment": Input data. Returns: - A ``SingleCellExperiment`` object. + A ``SingleCellExperiment`` object. If the input contains any data + in the ``uns`` attribute, the `metadata` slot of the ``SingleCellExperiment`` + will contain a key ``uns``. """ layers = OrderedDict() From 988402e2e425dc8d7c4a8456f4dc3a5b9af2830c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 May 2024 19:26:25 +0000 Subject: [PATCH 4/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/singlecellexperiment/SingleCellExperiment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/singlecellexperiment/SingleCellExperiment.py b/src/singlecellexperiment/SingleCellExperiment.py index aedb656..8d41854 100644 --- a/src/singlecellexperiment/SingleCellExperiment.py +++ b/src/singlecellexperiment/SingleCellExperiment.py @@ -1015,8 +1015,8 @@ def from_anndata(cls, input: "anndata.AnnData") -> "SingleCellExperiment": Input data. Returns: - A ``SingleCellExperiment`` object. If the input contains any data - in the ``uns`` attribute, the `metadata` slot of the ``SingleCellExperiment`` + A ``SingleCellExperiment`` object. If the input contains any data + in the ``uns`` attribute, the `metadata` slot of the ``SingleCellExperiment`` will contain a key ``uns``. """ From d3ae1e3393321fc774523b43377e396899da3bcc Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Sun, 5 May 2024 12:28:25 -0700 Subject: [PATCH 5/5] slightly better --- src/singlecellexperiment/SingleCellExperiment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/singlecellexperiment/SingleCellExperiment.py b/src/singlecellexperiment/SingleCellExperiment.py index 8d41854..47df2ee 100644 --- a/src/singlecellexperiment/SingleCellExperiment.py +++ b/src/singlecellexperiment/SingleCellExperiment.py @@ -1030,12 +1030,13 @@ def from_anndata(cls, input: "anndata.AnnData") -> "SingleCellExperiment": obsm = _to_normal_dict(input.obsm) varp = _to_normal_dict(input.varp) obsp = _to_normal_dict(input.obsp) + _metadata = {"uns": input.uns} if input.uns is not None else None return cls( assays=layers, row_data=biocframe.BiocFrame.from_pandas(input.var), column_data=biocframe.BiocFrame.from_pandas(input.obs), - metadata={"uns": input.uns}, + metadata=_metadata, reduced_dims=obsm, row_pairs=varp, column_pairs=obsp,