Skip to content

PhenopacketTable

This class creates a table with a summary of all phenopackets in a cohort of individuals Create Individual objects and transform them into phenopackets, or import GA4GH phenopackets and display them.

from IPython.display import HTML, display
phenopackets = [i.to_ga4gh_phenopacket(metadata=metadata) for i in individuals]
table = PhenopacketTable.from_phenopackets(phenopacket_list=phenopackets)
display(HTML(table.to_html()))

Alternatively,

table = PhenopacketTable.from_individuals(individual_list=individuals, metadata=metadata)
display(HTML(table.to_html()))
Source code in pyphetools/visualization/phenopacket_table.py
class PhenopacketTable:
    """
    This class creates a table with a summary of all phenopackets in a cohort of individuals
    Create Individual objects and transform them into phenopackets, or import GA4GH phenopackets and display them.

        from IPython.display import HTML, display
        phenopackets = [i.to_ga4gh_phenopacket(metadata=metadata) for i in individuals]
        table = PhenopacketTable.from_phenopackets(phenopacket_list=phenopackets)
        display(HTML(table.to_html()))

    Alternatively,

        table = PhenopacketTable.from_individuals(individual_list=individuals, metadata=metadata)
        display(HTML(table.to_html()))
    """
    def __init__(self, phenopacket_list:List[PPKt.Phenopacket]=None,
                        individual_list:List[Individual]=None,
                        metadata:MetaData=None) -> None:
        """
        :param phenopacket_list: List of GA4GH phenopackets to be displayed
        :type phenopacket_list: List[PPKt.Phenopacket]
        :param individual_list: List of Indidivual objects to be displayed
        :type individual_list: List[Individual]
        :param metadata: metadata about the individuals
        :type metadata: MetaData
        """
        if phenopacket_list is None and individual_list is not None and metadata is not None:
            self._phenopacket_list =  [i.to_ga4gh_phenopacket(metadata=metadata.to_ga4gh()) for i in individual_list]
        elif phenopacket_list is not None and individual_list is None:
            if metadata is not None:
                raise ValueError("metadata argument not needed for phenopacket list")
            self._phenopacket_list = phenopacket_list

    @staticmethod
    def from_phenopackets(phenopacket_list:List[PPKt.Phenopacket]):
        """Initialize PhenopacketTable from list of GA4GH Phenopackets

        :param phenopacket_list: list of GA4GH Phenopackets
        :type phenopacket_list: List[PPKt.Phenopacket]

        :returns: PhenopacketTable for displaying information about a cohort
        :rtype: PhenopacketTable
        """
        if not isinstance(phenopacket_list, list):
            raise ValueError(f"Expecting a list but got {type(phenopacket_list)}")
        if len(phenopacket_list) == 0:
            raise ValueError("phenopacket_list was empty")
        ppkt = phenopacket_list[0]
        if not isinstance(ppkt, PPKt.Phenopacket):
            raise ValueError(f"phenopacket_list argument must be list of Phenopacket objects but was {type(ppkt)}")
        return PhenopacketTable(phenopacket_list=phenopacket_list)

    @staticmethod
    def from_individuals(individual_list:List[Individual], metadata:MetaData):
        """Initialize PhenopacketTable from list of GA4GH Phenopackets

        :param phenopacket_list: list of GA4GH Phenopackets
        :type phenopacket_list: List[PPKt.Phenopacket]

        :returns: PhenopacketTable for displaying information about a cohort
        :rtype: PhenopacketTable
        """
        if not isinstance(individual_list, list):
            raise ValueError(f"Expecting a list but got {type(individual_list)}")
        if len(individual_list) == 0:
            raise ValueError("individual_list was empty")
        indi = individual_list[0]
        if not isinstance(indi, Individual):
            raise ValueError(f"individual_list argument must be list of Individual objects but was {type(indi)}")
        if not isinstance(metadata, MetaData):
            raise ValueError(f"metadata argument must be pyphetools MetaData object but was {type(metadata)}")
        return PhenopacketTable(individual_list=individual_list, metadata=MetaData)

    def _simple_patient_to_table_row(self, spat:SimplePatient) -> List[str]:
        """
        private method intended to create one table row that represents one individual
        :param spat: An object that represents one individual
        :type spat: SimplePatient
        """
        row_items = []
        # Patient information
        pat_info = spat.get_subject_id() + " (" + spat.get_sex() + "; " + spat.get_age() + ")"
        row_items.append( pat_info)
        row_items.append( spat.get_disease())
        # Variant information
        var_list = spat.get_variant_list()
        if len(var_list) == 0:
            row_items.append("n/a" )
        elif len(var_list) == 1:
            var = var_list[0]
            row_items.append( var.get_display() )
        else:
            cell_items = []
            cell_items.append("<ul>")
            for var in var_list:
                cell_items.append("<li>" + var.get_display() + "</li>")
            cell_items.append("</ul>")
            row_items.append( " ".join(cell_items) )
        # HPO information
        hpo_html = self.get_hpo_cell(spat.get_term_by_age_dict())
        row_items.append( hpo_html )
        return row_items


    def get_hpo_cell(self, term_by_age_dict:Dict[str,HpTerm]) -> str:
        """
        :param term_by_age_dict: A dictionary with key - ISO8601 string, value - list of HpTerm objects
        :type term_by_age_dict: Dict[str,HpTerm]
        :returns: HTML code for the HTML cell
        :rtype: str
        """
        lines = []
        age2day_list = PhenopacketTable.get_sorted_age2data_list(term_by_age_dict.keys())
        sorted_age = sorted(age2day_list, key=lambda x: x.days)
        for onset in sorted_age:
            hpo_list = term_by_age_dict.get(onset.key)
            hpos = "; ".join([hpo.__str__() for hpo in hpo_list])
            if onset.key == Constants.NOT_PROVIDED:
                lines.append(hpos)
            else:
                lines.append(f"<b>{onset.key}</b>: {hpos}")
        return "<br/>".join(lines)

    def to_html(self):
        """create an HTML table with patient ID, age, sex, genotypes, and PhenotypicFeatures
        """
        ppack_list = self._phenopacket_list
        spat_list = []
        pmid_count_d = defaultdict(int)
        no_pmid_found = 0
        pmid_found = 0
        for pp in ppack_list:
            spat = SimplePatient(ga4gh_phenopacket=pp)
            if spat.has_pmid():
                pmid_count_d[spat.get_pmid()] += 1
                pmid_found += 1
            else:
                no_pmid_found += 1
            spat_list.append(spat)
        # Create caption
        if pmid_found == 0:
            capt = f"{len(ppack_list)} phenopackets - no PMIDs (consider adding this information to the MetaData)"
        else:
            pmid_strings = []
            for k, v in pmid_count_d.items():
                pmid_strings.append(f"{k} (n={v})")
            pmid_str = "; ".join(pmid_strings)
            n_phenopackets = len(ppack_list)
            if n_phenopackets == 1:
                capt = f"{n_phenopackets} phenopacket - {pmid_str}"
            else:
                capt = f"{n_phenopackets} phenopackets - {pmid_str}"
        header_items = ["Individual", "Disease", "Genotype", "Phenotypic features"]
        rows = []
        for spat in spat_list:
            rows.append(self._simple_patient_to_table_row(spat))
        generator = HtmlTableGenerator(caption=capt, header_items=header_items, rows=rows)
        return generator.get_html()

    @staticmethod
    def iso_to_days(iso_age:str) -> int:
        """
        Transform the ISO8601 age strings (e.g., P3Y2M) into the corresponding number of days to facilitate sorting.

        Note that if age is not provided we want to sort it to the end of the list so we transform to a very high number of days.

        :param iso_age: ISO8601 age string (e.g., P3Y2M)
        :type iso_age: str
        :returns: number of days
        :rtype: int
        """
        if iso_age == Constants.NOT_PROVIDED:
            days = sys.maxsize
        elif not iso_age.startswith("P"):
            raise ValueError(f"Invlaid age string: {age}")
        else:
            days = 0
            age = iso_age[1:]
            N = len(age)
            y = age.find("Y")
            if y != -1:
                days = days + int(365.25*int(age[:y]))
                if age.endswith("Y"):
                    return days
                age = age[y+1:]
            m = age.find("M")
            if m is not None and m != -1:
                days = days + int(30.436875*int(age[:m]))
                if age.endswith("M"):
                    return days
                age = age[m+1:]
            d = age.find("D")
            if d != -1:
                days = days + int(age[:d])
        return days

    @staticmethod
    def get_sorted_age2data_list(ages:Set[str]) -> List[Age2Day]:
        """
        Create a sorted list of Age2Day objects that we use to display the age in the HTML output.

        :param ages: A set of ISO 8601 age strings
        :type ages: Set[str]
        :returns: A list of sorted Age2Day objects
        :rtype:  List[Age2Day]
        """
        age2day_list = list(Age2Day(age, PhenopacketTable.iso_to_days(age)) for age in ages)
        sorted_list = sorted(age2day_list, key=lambda x: x.days)
        return sorted_list

__init__(phenopacket_list=None, individual_list=None, metadata=None)

Parameters:

Name Type Description Default
phenopacket_list List[Phenopacket]

List of GA4GH phenopackets to be displayed

None
individual_list List[Individual]

List of Indidivual objects to be displayed

None
metadata MetaData

metadata about the individuals

None
Source code in pyphetools/visualization/phenopacket_table.py
def __init__(self, phenopacket_list:List[PPKt.Phenopacket]=None,
                    individual_list:List[Individual]=None,
                    metadata:MetaData=None) -> None:
    """
    :param phenopacket_list: List of GA4GH phenopackets to be displayed
    :type phenopacket_list: List[PPKt.Phenopacket]
    :param individual_list: List of Indidivual objects to be displayed
    :type individual_list: List[Individual]
    :param metadata: metadata about the individuals
    :type metadata: MetaData
    """
    if phenopacket_list is None and individual_list is not None and metadata is not None:
        self._phenopacket_list =  [i.to_ga4gh_phenopacket(metadata=metadata.to_ga4gh()) for i in individual_list]
    elif phenopacket_list is not None and individual_list is None:
        if metadata is not None:
            raise ValueError("metadata argument not needed for phenopacket list")
        self._phenopacket_list = phenopacket_list

from_individuals(individual_list, metadata) staticmethod

Initialize PhenopacketTable from list of GA4GH Phenopackets

Parameters:

Name Type Description Default
phenopacket_list List[PPKt.Phenopacket]

list of GA4GH Phenopackets

required

Returns:

Type Description
PhenopacketTable

PhenopacketTable for displaying information about a cohort

Source code in pyphetools/visualization/phenopacket_table.py
@staticmethod
def from_individuals(individual_list:List[Individual], metadata:MetaData):
    """Initialize PhenopacketTable from list of GA4GH Phenopackets

    :param phenopacket_list: list of GA4GH Phenopackets
    :type phenopacket_list: List[PPKt.Phenopacket]

    :returns: PhenopacketTable for displaying information about a cohort
    :rtype: PhenopacketTable
    """
    if not isinstance(individual_list, list):
        raise ValueError(f"Expecting a list but got {type(individual_list)}")
    if len(individual_list) == 0:
        raise ValueError("individual_list was empty")
    indi = individual_list[0]
    if not isinstance(indi, Individual):
        raise ValueError(f"individual_list argument must be list of Individual objects but was {type(indi)}")
    if not isinstance(metadata, MetaData):
        raise ValueError(f"metadata argument must be pyphetools MetaData object but was {type(metadata)}")
    return PhenopacketTable(individual_list=individual_list, metadata=MetaData)

from_phenopackets(phenopacket_list) staticmethod

Initialize PhenopacketTable from list of GA4GH Phenopackets

Parameters:

Name Type Description Default
phenopacket_list List[Phenopacket]

list of GA4GH Phenopackets

required

Returns:

Type Description
PhenopacketTable

PhenopacketTable for displaying information about a cohort

Source code in pyphetools/visualization/phenopacket_table.py
@staticmethod
def from_phenopackets(phenopacket_list:List[PPKt.Phenopacket]):
    """Initialize PhenopacketTable from list of GA4GH Phenopackets

    :param phenopacket_list: list of GA4GH Phenopackets
    :type phenopacket_list: List[PPKt.Phenopacket]

    :returns: PhenopacketTable for displaying information about a cohort
    :rtype: PhenopacketTable
    """
    if not isinstance(phenopacket_list, list):
        raise ValueError(f"Expecting a list but got {type(phenopacket_list)}")
    if len(phenopacket_list) == 0:
        raise ValueError("phenopacket_list was empty")
    ppkt = phenopacket_list[0]
    if not isinstance(ppkt, PPKt.Phenopacket):
        raise ValueError(f"phenopacket_list argument must be list of Phenopacket objects but was {type(ppkt)}")
    return PhenopacketTable(phenopacket_list=phenopacket_list)

get_hpo_cell(term_by_age_dict)

Parameters:

Name Type Description Default
term_by_age_dict Dict[str, HpTerm]

A dictionary with key - ISO8601 string, value - list of HpTerm objects

required

Returns:

Type Description
str

HTML code for the HTML cell

Source code in pyphetools/visualization/phenopacket_table.py
def get_hpo_cell(self, term_by_age_dict:Dict[str,HpTerm]) -> str:
    """
    :param term_by_age_dict: A dictionary with key - ISO8601 string, value - list of HpTerm objects
    :type term_by_age_dict: Dict[str,HpTerm]
    :returns: HTML code for the HTML cell
    :rtype: str
    """
    lines = []
    age2day_list = PhenopacketTable.get_sorted_age2data_list(term_by_age_dict.keys())
    sorted_age = sorted(age2day_list, key=lambda x: x.days)
    for onset in sorted_age:
        hpo_list = term_by_age_dict.get(onset.key)
        hpos = "; ".join([hpo.__str__() for hpo in hpo_list])
        if onset.key == Constants.NOT_PROVIDED:
            lines.append(hpos)
        else:
            lines.append(f"<b>{onset.key}</b>: {hpos}")
    return "<br/>".join(lines)

get_sorted_age2data_list(ages) staticmethod

Create a sorted list of Age2Day objects that we use to display the age in the HTML output.

Parameters:

Name Type Description Default
ages Set[str]

A set of ISO 8601 age strings

required

Returns:

Type Description
List[Age2Day]

A list of sorted Age2Day objects

Source code in pyphetools/visualization/phenopacket_table.py
@staticmethod
def get_sorted_age2data_list(ages:Set[str]) -> List[Age2Day]:
    """
    Create a sorted list of Age2Day objects that we use to display the age in the HTML output.

    :param ages: A set of ISO 8601 age strings
    :type ages: Set[str]
    :returns: A list of sorted Age2Day objects
    :rtype:  List[Age2Day]
    """
    age2day_list = list(Age2Day(age, PhenopacketTable.iso_to_days(age)) for age in ages)
    sorted_list = sorted(age2day_list, key=lambda x: x.days)
    return sorted_list

iso_to_days(iso_age) staticmethod

Transform the ISO8601 age strings (e.g., P3Y2M) into the corresponding number of days to facilitate sorting.

Note that if age is not provided we want to sort it to the end of the list so we transform to a very high number of days.

Parameters:

Name Type Description Default
iso_age str

ISO8601 age string (e.g., P3Y2M)

required

Returns:

Type Description
int

number of days

Source code in pyphetools/visualization/phenopacket_table.py
@staticmethod
def iso_to_days(iso_age:str) -> int:
    """
    Transform the ISO8601 age strings (e.g., P3Y2M) into the corresponding number of days to facilitate sorting.

    Note that if age is not provided we want to sort it to the end of the list so we transform to a very high number of days.

    :param iso_age: ISO8601 age string (e.g., P3Y2M)
    :type iso_age: str
    :returns: number of days
    :rtype: int
    """
    if iso_age == Constants.NOT_PROVIDED:
        days = sys.maxsize
    elif not iso_age.startswith("P"):
        raise ValueError(f"Invlaid age string: {age}")
    else:
        days = 0
        age = iso_age[1:]
        N = len(age)
        y = age.find("Y")
        if y != -1:
            days = days + int(365.25*int(age[:y]))
            if age.endswith("Y"):
                return days
            age = age[y+1:]
        m = age.find("M")
        if m is not None and m != -1:
            days = days + int(30.436875*int(age[:m]))
            if age.endswith("M"):
                return days
            age = age[m+1:]
        d = age.find("D")
        if d != -1:
            days = days + int(age[:d])
    return days

to_html()

create an HTML table with patient ID, age, sex, genotypes, and PhenotypicFeatures

Source code in pyphetools/visualization/phenopacket_table.py
def to_html(self):
    """create an HTML table with patient ID, age, sex, genotypes, and PhenotypicFeatures
    """
    ppack_list = self._phenopacket_list
    spat_list = []
    pmid_count_d = defaultdict(int)
    no_pmid_found = 0
    pmid_found = 0
    for pp in ppack_list:
        spat = SimplePatient(ga4gh_phenopacket=pp)
        if spat.has_pmid():
            pmid_count_d[spat.get_pmid()] += 1
            pmid_found += 1
        else:
            no_pmid_found += 1
        spat_list.append(spat)
    # Create caption
    if pmid_found == 0:
        capt = f"{len(ppack_list)} phenopackets - no PMIDs (consider adding this information to the MetaData)"
    else:
        pmid_strings = []
        for k, v in pmid_count_d.items():
            pmid_strings.append(f"{k} (n={v})")
        pmid_str = "; ".join(pmid_strings)
        n_phenopackets = len(ppack_list)
        if n_phenopackets == 1:
            capt = f"{n_phenopackets} phenopacket - {pmid_str}"
        else:
            capt = f"{n_phenopackets} phenopackets - {pmid_str}"
    header_items = ["Individual", "Disease", "Genotype", "Phenotypic features"]
    rows = []
    for spat in spat_list:
        rows.append(self._simple_patient_to_table_row(spat))
    generator = HtmlTableGenerator(caption=capt, header_items=header_items, rows=rows)
    return generator.get_html()