Source code for pbxplore.analysis.utils

#! /usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function, absolute_import


# Local module
from .. import PB

# Python2/Python3 compatibility
# The range function in python 3 behaves as the range function in python 2
# and returns a generator rather than a list. To produce a list in python 3,
# one should use list(range). Here we change range to behave the same in
# python 2 and in python 3. In both cases, range will return a generator.
try:
    range = xrange
except NameError:
    pass


def _slice_matrix(mat, residue_min=1, residue_max=None):
    """
    Slice a matrix given the lower and upper bond in parameters.
    The matrix has to be a numpy array with one row per residue.
    The slice will occurs on the rows and the sub-matrix is returned.

    Parameters
    ----------
    mat : numpy array
        the matrix to slice
    residue_min: int
        the lower bound of residue frame
    residue_max: int
        the upper bound of residue frame

    Returns
    -------
    sub_mat: numpy 2D array
        the matrix sliced

    Raises
    ------
    IndexError
        when something is wrong about the lower/upper bound
    """

    if residue_max is None:
        residue_max = mat.shape[0]

    if residue_min <= 0 or residue_max <= 0:
        raise IndexError("Index start at 1")

    # range of indexes of the matrix
    residues_idx = range(1, mat.shape[0] + 1)

    if residue_min not in residues_idx or residue_max not in residues_idx:
        raise IndexError("Index out of range")

    if residue_min >= residue_max:
        raise IndexError("Lower bound > upper bound")

    return mat[residue_min - 1: residue_max]


[docs]def compute_freq_matrix(count_mat): """ Compute a PB frequency matrix from an occurence matrix. The frequency matrix has one row per sequence, and one column per block. The columns are ordered in as PB.NAMES. Parameters ---------- count_mat : numpy array an occurence matrix returned by `count_matrix`. Returns ------- freq : numpy array The frequency matrix """ # Assert the occurence matrix is in the good format nb_pb = count_mat.shape[1] if nb_pb != len(PB.NAMES): raise ValueError("Wrong number of PB({} should be {}".format(nb_pb, len(PB.NAMES))) # Retrieve the number of sequences compiled # use the sum of all residue at position 3 since positions 1 and 2 have no PBs assignement nb_sequences = sum(count_mat[2, :]) return count_mat / float(nb_sequences)
[docs]def compute_score_by_position(score_mat, seq1, seq2): """ Computes substitution score between two sequences position per position The substitution score can represent a similarity or a distance depending on the score matrix provided. The score matrix should be provided as a 2D numpy array with score[i, j] the score to swich the PB at the i-th position in PB.NAMES to the PB at the j-th position in PB.NAMES. The function returns the result as a list of substitution scores to go from `seq1` to `seq2` for each position. Both sequences must have the same length. ..note: The score to move from or to a Z block (dummy block) is always 0. Raises ------ InvalidBlockError encountered an unexpected PB """ assert len(seq1) == len(seq2), \ "sequences have different sizes:\n{}\nvs\n{}".format(seq1, seq2) score = [] for pb1, pb2 in zip(seq1, seq2): # score is 0 for Z (dummy PB) if "z" in [pb1.lower(), pb2.lower()]: score.append(0) elif pb1 in PB.NAMES and pb2 in PB.NAMES: score.append(score_mat[PB.NAMES.index(pb1)][PB.NAMES.index(pb2)]) else: invalid = [] for pb in (pb1, pb2): if pb not in PB.NAMES: invalid.append(pb) raise PB.InvalidBlockError(', '.join(invalid)) return score
[docs]def substitution_score(substitution_matrix, seqA, seqB): """ Compute the substitution score to go from `seqA` to `seqB` Both sequences must have the same length. The score is either expressed as a similarity or a distance depending on the substitution matrix. """ return sum(compute_score_by_position(substitution_matrix, seqA, seqB))