Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge 1.0.0a2 #58

Merged
merged 22 commits into from
Sep 19, 2023
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
13 changes: 5 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
# LIANA+: an all-in-one framework for cell-cell communication <img src="https://raw.githubusercontent.com/saezlab/liana-py/dev/docs/source/_static/logo.png?raw=true" align="right" height="125">
# LIANA+: an all-in-one cell-cell communication framework <img src="https://raw.githubusercontent.com/saezlab/liana-py/dev/docs/source/_static/logo.png?raw=true" align="right" height="125">

<!-- badges: start -->
[![main](https://github.com/saezlab/liana-py/actions/workflows/main.yml/badge.svg)](https://github.com/saezlab/liana-py/actions)
[![GitHub issues](https://img.shields.io/github/issues/saezlab/liana-py.svg)](https://github.com/saezlab/liana-py/issues/)
[![Documentation Status](https://readthedocs.org/projects/liana-py/badge/?version=latest)](https://liana-py.readthedocs.io/en/latest/?badge=latest)
[![codecov](https://codecov.io/gh/saezlab/liana-py/branch/main/graph/badge.svg?token=TM0P29KKN5)](https://codecov.io/gh/saezlab/liana-py)
[![Downloads](https://pepy.tech/badge/liana)](https://pepy.tech/project/liana)
[![Downloads](https://static.pepy.tech/badge/liana)](https://pepy.tech/project/liana)
<!-- badges: end -->

LIANA+ is an efficient framework that integrates and extends existing methods and knowledge to study cell-cell communication in single-cell, spatially-resolved, and multi-modal omics data. It works in conjunction with the [scverse ecosystem](https://github.com/scverse), and it relies on [AnnData](https://github.com/scverse/anndata) & [MuData](https://github.com/scverse/mudata) objects as input.
LIANA+ is a scalable framework that integrates and extends existing methods and knowledge to study cell-cell communication in single-cell, spatially-resolved, and multi-modal omics data. It works in conjunction with the [scverse ecosystem](https://github.com/scverse), and it relies on [AnnData](https://github.com/scverse/anndata) & [MuData](https://github.com/scverse/mudata) objects as input.

<img src="https://raw.githubusercontent.com/saezlab/liana-py/main/docs/source/_static/abstract.png" width="700" align="center">


## Development & Contributions

LIANA+ is still under development. Nevertheless, the API visible to the user should be stable, specifically `liana.method.sc`.

We welcome suggestions, ideas, and contributions! Please use do not hesitate to contact us, or use the issues or the [LIANA+ Development project](https://github.com/orgs/saezlab/projects/16) to make suggestions.

## Tutorials
Expand All @@ -37,9 +34,9 @@ We welcome suggestions, ideas, and contributions! Please use do not hesitate to

- [Multicellular programmes with MOFA](https://liana-py.readthedocs.io/en/latest/notebooks/mofacellular.html). Using MOFA to obtain coordinates gene expression programmes across samples and conditions, as done in [Ramirez et al., 2023](https://europepmc.org/article/ppr/ppr620471).

- [LIANA with MOFA](https://liana-py.readthedocs.io/en/latest/notebooks/mofatalk.html). Using MOFA to infer intercellular communication programmes across samples and conditions, as initially proposed by cell2cell-Tensor.
- [LIANA with MOFA](https://liana-py.readthedocs.io/en/latest/notebooks/mofatalk.html). Using MOFA to infer intercellular communication programmes across samples and conditions, as initially proposed by Tensor-cell2cell.

- [LIANA with cell2cell-Tensor](https://liana-py.readthedocs.io/en/latest/notebooks/liana_c2c.html) to extract intercellular communication programmes across samples and conditions. Extensive tutorials combining LIANA & [cell2cell-Tensor](https://www.nature.com/articles/s41467-022-31369-2) are available [here](https://ccc-protocols.readthedocs.io/en/latest/index.html).
- [LIANA with Tensor-cell2cell](https://liana-py.readthedocs.io/en/latest/notebooks/liana_c2c.html) to extract intercellular communication programmes across samples and conditions. Extensive tutorials combining LIANA & [Tensor-cell2cell](https://www.nature.com/articles/s41467-022-31369-2) are available [here](https://ccc-protocols.readthedocs.io/en/latest/index.html).


### Others
Expand Down
5 changes: 0 additions & 5 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@ spatially-resolved, and multi-modal omics data.

notebooks/basic_usage

.. toctree::
:maxdepth: 1
:hidden:
:caption: Multi-Sample Vignettes

notebooks/liana_c2c

notebooks/mofacellular
Expand Down
119 changes: 69 additions & 50 deletions docs/source/notebooks/basic_usage.ipynb

Large diffs are not rendered by default.

81 changes: 34 additions & 47 deletions docs/source/notebooks/bivariate.ipynb

Large diffs are not rendered by default.

Binary file modified docs/source/notebooks/causal-networks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 27 additions & 6 deletions docs/source/notebooks/liana_c2c.ipynb

Large diffs are not rendered by default.

89 changes: 49 additions & 40 deletions docs/source/notebooks/misty.ipynb

Large diffs are not rendered by default.

523 changes: 380 additions & 143 deletions docs/source/notebooks/mofacellular.ipynb

Large diffs are not rendered by default.

71 changes: 46 additions & 25 deletions docs/source/notebooks/mofatalk.ipynb

Large diffs are not rendered by default.

485 changes: 275 additions & 210 deletions docs/source/notebooks/targeted.ipynb

Large diffs are not rendered by default.

27 changes: 26 additions & 1 deletion docs/source/release_notes.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
Release notes
=============

1.0.0a1 (30.07.2023)
1.0.0a2 (19.09.2023)

- Interactions names in `tileplot` and `dotplot` will now be sorted according to `orderby` when used; related to #55

- Added `filter_view_markers` function to filter view markers considered background in MOFAcellular tutorial

- Added `keep_stats` parameter to `adata_to_views` to enable pseudobulk stats to be kept.

- Replace `intra_groupby` and `extra_groupby` with `maskby` in misty.
The spots will now only be filtered according to `maskby`, such that both intra and extra both contain the same spots.
The extra views are multiplied by the spatial connectivities prior to masking and the model being fit

- Merge MOFAcell improvements; related to #42 and #29

- Targets with zero variance will no longer be modeled by misty.

- Resolve #46 - refactored misty's pipeline

- Resolved logging and package import verbosity issues related to #43

- Iternal .obs['label'] placeholder renamed to the less generic .obs['@label']; related to #53

- Minor Readme & tutorial text improvements.


1.0.0a1 Biorxiv (30.07.2023)
---------------------------------------------------------

- `positive_only` in bivariate metrics was renamed to `mask_negatives` will now mask only negative-negative/low-low interactions, and not negative-positive interactions.
Expand Down
2 changes: 1 addition & 1 deletion liana/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import importlib.metadata
__version__ = importlib.metadata.version("liana")

from liana import method as mt, plotting as pl, resource as rs, multi as mu, utils as ut, testing
from liana import _logging, method as mt, plotting as pl, resource as rs, multi as mu, utils as ut, testing

# done after everything has been imported (adapted from scanpy)
import sys
Expand Down
29 changes: 29 additions & 0 deletions liana/_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import logging
from types import ModuleType

logging.basicConfig(level=logging.INFO, format='%(message)s')

def _logg(message, level='info', verbose=False):
"""
Log a message with a specified logging level.

Args:
message (str): The message to log.
level (int or None): The logging level for the message (default is None).
If None or False, no logging is performed.
"""
if verbose:
if level == "warn":
logging.warning(message)
elif level == "info":
logging.info(message)

def _check_if_installed(package_name: str, custom_error_message: str = None) -> ModuleType:
try:
imported_module = __import__(package_name)
return imported_module
except ImportError:
if custom_error_message:
raise ImportError(custom_error_message)
else:
raise ImportError(f'{package_name} is not installed. Please install it with: pip install {package_name}')
6 changes: 3 additions & 3 deletions liana/method/_pipe_utils/_get_mean_perms.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ def _get_means_perms(adata: anndata.AnnData,
adata.X /= norm_factor

# define labels and masks
labels = adata.obs.label.cat.categories
labels = adata.obs['@label'].cat.categories
labels_mask = np.zeros((adata.shape[0], labels.shape[0]), dtype=bool)

# populate masks shape(genes, labels)
for ct_idx, label in enumerate(labels):
labels_mask[:, ct_idx] = adata.obs.label == label
labels_mask[:, ct_idx] = adata.obs['@label'] == label

# Perm should be a cube /w dims: n_perms x idents x n_genes
perms = _generate_perms_cube(adata.X, n_perms, labels_mask, seed, agg_fun, verbose)
Expand Down Expand Up @@ -79,7 +79,7 @@ def _generate_perms_cube(X, n_perms, labels_mask, seed, agg_fun, verbose):


def _get_positions(adata, lr_res):
labels = adata.obs['label'].cat.categories
labels = adata.obs['@label'].cat.categories

# get positions of each entity in the matrix
ligand_pos = {entity: np.where(adata.var_names == entity)[0][0] for entity
Expand Down
41 changes: 16 additions & 25 deletions liana/method/_pipe_utils/_pre.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from pandas import DataFrame, Index
import scanpy as sc
from scipy.sparse import csr_matrix, isspmatrix_csr

from liana._logging import _logg

def assert_covered(
subset,
Expand Down Expand Up @@ -56,8 +56,7 @@ def assert_covered(
)
raise ValueError(msg + f" [{x_missing}] missing from {superset_name}")

if verbose & (prop_missing > 0):
print(f"{prop_missing:.2f} of entities in the resource are missing from the data.")
_logg(f"{prop_missing:.2f} of entities in the resource are missing from the data.", verbose=verbose & (prop_missing > 0))


def prep_check_adata(adata: AnnData,
Expand Down Expand Up @@ -114,33 +113,29 @@ def prep_check_adata(adata: AnnData,
msk_features = np.sum(adata.X, axis=0).A1 == 0
n_empty_features = np.sum(msk_features)
if n_empty_features > 0:
if verbose:
print(f"{n_empty_features} features of mat are empty, they will be removed.")
_logg(f"{n_empty_features} features of mat are empty, they will be removed.", level='warn', verbose=verbose)
adata = adata[:, ~msk_features]

# Check for empty samples
msk_samples = adata.X.sum(axis=1).A1 == 0
n_empty_samples = np.sum(msk_samples)
if n_empty_samples > 0:
if verbose:
print(f"{n_empty_samples} samples of mat are empty, please remove them.")
_logg(f"{n_empty_samples} samples of mat are empty, they will be removed.", level='warn', verbose=verbose)

# Check if log-norm
_sum = np.sum(adata.X.data[0:100])
if _sum == np.floor(_sum):
if verbose:
print("Make sure that normalized counts are passed!")
_logg("Make sure that normalized counts are passed!", level='warn', verbose=verbose)

# Check for non-finite values
if np.any(~np.isfinite(adata.X.data)):
raise ValueError(
"mat contains non finite values (nan or inf), please set them to 0 or remove them.")
raise ValueError("mat contains non finite values (nan or inf), please set them to 0 or remove them.")

# Define idents col name
if groupby is not None:
if groupby not in adata.obs.columns:
raise AssertionError(f"`{groupby}` not found in `adata.obs.columns`.")
adata.obs.loc[:, 'label'] = adata.obs[groupby]
adata.obs.loc[:, '@label'] = adata.obs[groupby]

# Remove any cell types below X number of cells per cell type
count_cells = adata.obs.groupby(groupby)[groupby].size().reset_index(name='count').copy()
Expand All @@ -151,9 +146,9 @@ def prep_check_adata(adata: AnnData,
# remove lowly abundant identities
msk = ~np.isin(adata.obs[[groupby]], lowly_abundant_idents)
adata = adata[msk]
if verbose:
print("The following cell identities were excluded: {0}".format(
", ".join(lowly_abundant_idents)))
_logg("The following cell identities were excluded: {0}".format(", ".join(lowly_abundant_idents)),
level='warn', verbose=verbose)


check_vars(adata.var_names,
complex_sep=complex_sep,
Expand All @@ -178,8 +173,8 @@ def check_vars(var_names, complex_sep, verbose=False) -> list:
else:
pass

if verbose & (len(var_issues) > 0):
print(f"Warning: {var_issues} contain `{complex_sep}`. Consider replacing those!")
_logg(f"{var_issues} contain `{complex_sep}`. Consider replacing those!",
verbose=verbose & (len(var_issues) > 0), level='warn')



Expand Down Expand Up @@ -243,24 +238,20 @@ def _choose_mtx_rep(adata, use_raw=False, layer=None, verbose=False) -> csr_matr
if is_layer & use_raw:
raise ValueError("Cannot specify `layer` and have `use_raw=True`.")
if is_layer:
if verbose:
print(f"Using the `{layer}` layer!")
_logg(f"Using the `{layer}` layer!", verbose=verbose)
X = adata.layers[layer]
elif use_raw:
if adata.raw is None:
raise ValueError("`.raw` is not initialized!")
if verbose:
print("Using `.raw`!")
_logg("Using `.raw`!", verbose=verbose)
X = adata.raw.X
else:
if verbose:
print("Using `.X`!")
_logg("Using `.X`!", verbose=verbose)
X = adata.X

# convert to sparse csr matrix
if not isspmatrix_csr(X):
if verbose:
print("Converting mat to CSR format")
_logg("Converting to sparse csr matrix!", verbose=verbose)
X = csr_matrix(X)

return X
10 changes: 5 additions & 5 deletions liana/method/_pipe_utils/_reassemble_complexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
from __future__ import annotations

import pandas as pd
import numpy as np

from liana._logging import _logg

def filter_reassemble_complexes(lr_res,
_key_cols,
Expand Down Expand Up @@ -73,9 +72,10 @@ def filter_reassemble_complexes(lr_res,
if duplicate_mask.any():
# check if there are any non-equal subunit values
if not lr_res[duplicate_mask].groupby(_key_cols)[complex_cols].transform(lambda x: x.duplicated(keep=False)).all().all():
print('Warning: there were duplicated subunits in the complexes. ' +
'The subunits were reduced to only the minimum expression subunit. ' +
'However, there were subunits that were not the same within a complex. ')
_logg('There were duplicated subunits in the complexes. ' +
'The subunits were reduced to only the minimum expression subunit. ' +
'However, there were subunits that were not the same within a complex. ',
level='warn')
lr_res = lr_res.drop_duplicates(subset=_key_cols, keep='first')

return lr_res
Expand Down
Loading
Loading