From 8c55bacbb3e1ecfca3dad48d7e4df2b366464040 Mon Sep 17 00:00:00 2001 From: Danila Bredikhin Date: Sun, 14 Mar 2021 21:49:57 +0100 Subject: [PATCH] Add utility functions for rich MuData repr Closes #22. --- muon/_core/repr.py | 239 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 muon/_core/repr.py diff --git a/muon/_core/repr.py b/muon/_core/repr.py new file mode 100644 index 0000000..9929864 --- /dev/null +++ b/muon/_core/repr.py @@ -0,0 +1,239 @@ +# +# Utility functions for MuData._repr_html_() +# + +from typing import Tuple, Iterable +import pandas as pd + + +def maybe_module_class(obj, sep=".", builtins=False) -> Tuple[str, str]: + m, cl = "", obj.__class__.__name__ + try: + m += obj.__class__.__module__ + if m == "builtins" and not builtins: + m = "" + else: + m += sep + except: + m += "" + return (m, cl) + + +def format_values(x): + s = "" + if not isinstance(x, Iterable): + s += f"{x}" + elif isinstance(x, pd.DataFrame): + s += "DataFrame ({} x {})".format(*x.shape) + elif hasattr(x, "keys") and hasattr(x, "values") and not hasattr(x, "shape"): + ks = ",".join(x.keys()) + if "," not in ks: # only 1 element + vs = ",".join(map(format_values, x.values())) + s += ks + ": " + vs + else: + s += ks + elif isinstance(x, str): + s += x + else: + x = x[: min(100, len(x))] + if isinstance(x[0], float): + s += ",".join([f"{i:.2f}" for i in x]) + else: + s += ",".join([f"{i}" for i in x]) + s = s[:50] + while s[-1] != ",": + s = s[:-1] + s += "..." + return s + + +def block_matrix(data, attr, name): + obj = getattr(data, attr) + s = "" + s += "
{}
.{}".format(name, attr) + s += "
" + s += """ + {}    {}{} + """.format( + obj.dtype, *maybe_module_class(obj) + ) + s += "
" + return s + + +def details_block_table(data, attr, name, expand=0, dims=True, square=False): + obj = getattr(data, attr) + s = "" + # DataFrame + if isinstance(obj, pd.DataFrame): + s += "".format(" open" if expand else "") + s += "
{}
.{}{} element{}
".format( + name, attr, obj.shape[1], "s" if obj.shape[1] != 1 else "" + ) + s += "
" + s += "\n".join( + [ + """ + + """.format( + attr_key, obj[attr_key].dtype, format_values(obj[attr_key]) + ) + for attr_key in obj.columns + ] + ) + s += "
{} {} {}
" + s += "" + # Dict-like object + elif hasattr(obj, "keys") and hasattr(obj, "values") and name != "Unstructured": + s += "".format(" open" if expand else "") + s += "
{}
.{}{} element{}
".format( + name, attr, len(obj), "s" if len(obj) != 1 else "" + ) + if len(obj) > 0: + s += "
" + if square: # e.g. distance matrices in .obsp + s += "\n".join( + [ + """ + + """.format( + attr_key, obj[attr_key].dtype, *maybe_module_class(obj[attr_key]) + ) + for attr_key in obj.keys() + ] + ) + else: # e.g. embeddings in .obsm + s += "\n".join( + [ + """ + + """.format( + attr_key, + obj[attr_key].dtype, + *maybe_module_class(obj[attr_key]), + f"{obj[attr_key].shape[1]} dims" + if len(obj[attr_key].shape) > 1 and dims + else "", + ) + for attr_key in obj.keys() + ] + ) + + s += "
{} {} {}{}
{} {} {}{} {}
" + else: + s += f"No {name.lower()}" + s += "" + elif hasattr(obj, "file"): # HDF5 dataset + s += "".format(" open" if expand else "") + s += "
{}
.{}{} elements
".format( + name, attr, len(obj) + ) + s += "
" + s += """ + + """.format( + obj.dtype, *maybe_module_class(obj) + ) + s += "
{} {}{}
" + s += "" + else: # Unstructured + s += "".format(" open" if expand else "") + s += "
{}
.{}{} elements
".format( + name, attr, len(obj) + ) + s += "
" + s += "\n".join( + [ + """ + + """.format( + attr_key, + *maybe_module_class(obj[attr_key]), + len(obj[attr_key]), + "s" if len(obj[attr_key]) != 1 else "", + format_values(obj[attr_key]), + ) + for attr_key in obj.keys() + ] + ) + s += "
{} {}{} {} element{} {}
" + s += "" + return s + + +MUDATA_CSS = """"""