logo

pyleida.DataLoader

Class to retrieve and explore the LEiDA results.

Expand source code
"""Class to retrieve and explore the LEiDA results."""

import numpy as np
import pandas as pd
import os
from itertools import combinations
import matplotlib.pyplot as plt
import seaborn as sns

from .data_utils import (
    load_rois_labels,
    load_rois_coordinates, 
    load_tseries,
    load_classes
)
from .data_utils.validation import (
    _check_k_input,
    _check_metric,
    _check_state
)
from .plotting import (
    brain_states_network,
    brain_states_nodes,
    plot_pyramid,
    states_k_glass,
    states_in_bold,
    brain_states_on_surf,
    brain_states_on_surf2,
    _explore_state,
    _save_html
)
from .clustering import (
    barplot_states,
    barplot_eig,
    centroid2matrix,
    plot_clusters3D,
    plot_voronoi,
    plot_clustering_scores
)
from .clustering import rsnets_overlap as rsnets
from .dynamics_metrics import group_transition_matrix
from .stats import scatter_pvalues


class DataLoader:
    """
    Class to retrieve from local folder all
    the outputs and results of the execution
    of the LEiDA pipeline through the 'Leida'
    class provided in the package. The DataLoader
    provides the same methods of the Leida class,
    thus allowing to load and deepen easily in the
    results of the previously executed analysis.

    Params:
    -------
    data_path : str.
        Path to the folder that contains the
        BOLD time series, metadata, ROIs labels, 
        and ROIs coordinates.

    results_path : str.
        Path to the folder that contains the
        LEiDA results. (default: 'LEiDA_results')

    Attributes:
    -----------
    eigenvectors : pandas.dataframe.
        Contains the computed eigenvectors
        for each time point of each subject.

    predictions : pandas.dataframe.
        Contains the predicted cluster label of
        each eigenvector for each 'k' partition
        explored.

    rois_labels : list.
        The label/name of each ROI/parcel.

    rois_coordinates_ : ndarray of shape (n_rois,3).
        The MNI coordinates of each ROI/parcel.
    """
    def __init__(self,data_path='data',results_path='LEiDA_results'):
        #validation of input data
        for _path in (data_path,results_path):
            if not isinstance(_path,str):
                raise TypeError("'data_path' and 'results_path' must be strings!")
        #check if the provided paths exists
        if not os.path.exists(data_path):
            raise ValueError("The provided 'data_path' could't be founded.")
        if not os.path.exists(results_path):
            raise ValueError("The provided 'results_path' could't be founded.")
        
        #Paths in which the data and results are stored
        self._data_path_ = data_path
        self._results_path_ = results_path
        self._clustering_ = results_path + '/clustering'
        self._dynamics_ = results_path + '/dynamics_metrics'
        self._models_ = self._clustering_ + '/models'

        #check if the 'clustering' and 'dynamics_metrics' folders exists in 'results_path'
        if not os.path.exists(self._clustering_) or not os.path.exists(self._dynamics_):
            raise Exception(f"No results were found in the specified path.")
        
        #load eigenvectors and kmeans predictions dataframes.
        try:
            self.eigenvectors = pd.read_csv(f'{results_path}/eigenvectors.csv',sep='\t')
            nrois = self.eigenvectors.shape[1]-2
        except:
            raise Exception("The eigenvectors dataframe could't be loaded.")
        try:
            self.predictions = pd.read_csv(f'{self._clustering_}/predictions.csv',sep='\t')
        except:
            raise Exception("The dataframe with the KMeans models predictions couldn't be loaded.")

        #load rois labels
        self.rois_labels = load_rois_labels(self._data_path_)
        nlabels = len(self.rois_labels)

        #load rois coordinates
        self.rois_coordinates_ = load_rois_coordinates(self._data_path_)
        if self.rois_coordinates_ is None:
            print("The ROIs coordinates couldn't be loaded from the provided 'data_path'. "
                "Brain plots that show nodes in brain space will not be executed in consequence.")
        else:
            ncoords = self.rois_coordinates_.shape[0]

        #check that the N of ROIs coincide across
        #labels, coordinates, and eigenvectors
        if self.rois_coordinates_ is None:
            if not nrois==nlabels:
                raise Exception(f"The number of ROIs in 'eigenvectors.csv' ({nrois}) "
                                f"and 'rois_labels.txt' {nlabels} must coincide!")
        else:
            if not nrois==nlabels==ncoords:
                raise Exception(f"The number of ROIs in 'eigenvectors.csv' ({nrois}), "
                                f"'rois_labels.txt' ({nlabels}), and 'rois_coordinates.csv' "
                                f"({ncoords}) must coincide!")

        self._K_min_ = 2
        self._K_max_ = 20

        self._classes_lst_ = np.unique(self.eigenvectors.condition).tolist()
        self._N_classes_ = len(self._classes_lst_)

    def time_series(self):
        """
        Return a dictionary having the 'subject_ids'
        as keys, and 2D arrays (N_ROIs,N_volumes) with
        BOLD time series as values.

        Returns:
        --------
        time_series : dict.
        """
        return load_tseries(self._data_path_)

    def load_model(self,k=2):
        """
        Load fitted model for a specific 'k' partition.
        Given that each model is an instance of the KMeansLeida
        class, once loaded you can access all the object methods
        and attributes.

        Params:
        -------
        k : int.
            Select the partition of interest.
        
        Returns:
        -------
        model : KMeansLeida instance.
            The fitted model that was used to predict
            the cluster labels of each observation.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        try:
            model = pd.read_pickle(f'{self._models_}/model_k_{k}.pkl')
            return model
        except:
            raise Exception("Can't find results for the selected k.")

    def load_centroids(self,k=2):
        """
        Return the computed clusters centroids
        for a specific 'k' partition.

        Params:
        -------
        k : int.
            Select the partition of interest.
        
        Returns:
        -------
        centroids : pd.DataFrame with shape (n_centroids,n_rois).
            Contains the computed centroids for
            the selected k partition.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        try:
            centroids = pd.DataFrame(self.load_model(k=k).cluster_centers_,columns=self.rois_labels)
        except:
            print("Can't find results for the selected k.")

        return centroids

    def centroids_distances(self,k=2):
        """
        Transform eigenvectors to a cluster-distance space. 
        Returns the distance between each eigenvector and the
        cluster centroids of the selected 'k' partition.

        Params:
        -------
        k : int.
            Select the partition of interest.
        
        Returns:
        --------
        distances : pd.DataFrame.
            Contains the distace between each eigenvector and
            each cluster centroid for the select 'k' partition.
            1st column contains 'subject_id', 2nd column the
            'condition', and the rest of columns the distances
            to the centroids.
        """
        model = self.load_model(k=k)
        distances = pd.DataFrame(model.transform(self.eigenvectors.iloc[:,2:].values))
        distances.columns = [f'centroid_{centroid+1}' for centroid in range(k)]
        distances = pd.concat((self.eigenvectors[['subject_id','condition']],distances),axis=1)
        return distances

    def stats(self,k=2,metric='occupancies'):
        """
        Retrieve the results from the statistical analysis of 
        a 'metric' of interest ('occupancies' or 'dwell_times')
        for a specific 'k' partition.

        Params:
        -------
        k : int.
            Select the 'k' partition of interest.

        metric : str.
            Select the metric to retrieve results.

        Returns:
        --------
        stats : pandas.dataframe.
            Results of the statistical analysis
            of each PL state.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        _check_metric(metric)

        try:
            df_stats = pd.read_csv(f'{self._dynamics_}/k_{k}/{metric}_stats.csv',sep='\t')
        except:
            print("Can't find results for the selected k or metric.")
        return df_stats

    def dwell_times(self,k=2):
        """
        Return the computed dwell times of each
        phase-locking state for a specific 'k'
        partition.

        Params:
        -------
        k : int.
            Specify the K-Means partition of interest.

        Returns:
        --------
        dwell_times : pd.DataFrame.
            Contains the computed dwell times of each
            PL state for each subject for the selected
            'k' partition.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        try:
            dt = pd.read_csv(f'{self._dynamics_}/k_{k}/dwell_times.csv',sep='\t')
        except:
            print("Can't find results for the selected k.")
        return dt
    
    def occupancies(self,k=2):
        """
        Return the computed fractional occupancy of each
        phase-locking state for a specific 'k' partition.

        Params:
        -------
        k : int.
            Specify the K-Means partition of
            interest.

        Returns:
        --------
        occupancies : pd.DataFrame.
            Contains the computed fractional occupancy
            of each PL state for each subject for the
            selected 'k' partition.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        try:
            occ = pd.read_csv(f'{self._dynamics_}/k_{k}/occupancies.csv',sep='\t')
        except:
            print("Can't find results for the selected k.")
        return occ

    def transitions(self,k=2):
        """
        Return the computed transition probabilities between
        phase-locking states for a specific 'k' partition.

        Params:
        -------
        k : int.
            Specify the K-Means partition of interest.

        Returns:
        --------
        transitions : pd.DataFrame.
            Contains the computed transition probabilities
            between PL states for each subject for the selected
            'k' partition.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        try:
            tr = pd.read_csv(f'{self._dynamics_}/k_{k}/transitions_probabilities.csv',sep='\t')
        except:
            print("Can't find results for the selected k.")
        return tr
    
    def significant_states(self,metric='occupancies'):
        """
        Return a dataframe containing only the statistics
        of the phase-locking states that are significantly
        different between groups.

        Params:
        -------
        metric : str.
            Metric of interest (Options: 'occupancies',
            'dwell_times').

        Returns:
        --------
        stats : pandas.dataframe.
        """
        _check_metric(metric)

        try:
            stats = self._pool_stats(metric=metric)
        except:
            raise Exception("The stats could't be loaded.")
        has_results = True if stats[stats.reject_null==True].shape[0]>=1 else False #check if some result was significant
        if has_results:
            return stats[stats.reject_null==True]
        else:
            print("No significant results were detected.")
            return None
        
    def state_rois(self,k=2,state=1):
        """
        Get a list with the names of the ROIs/parcels that
        participates in a specific phase-locking (PL) state.

        Params:
        -------
        k : int.
            Select the partition.

        state : int.
            Select the PL pattern or state of
            interest.

        Returns:
        --------
        rois : list.
            Contains the names of the ROIs that
            are part of the selected PL state.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        _check_state(k,state)

        data_k = self.load_centroids(k=k).iloc[state-1,:].reset_index()
        data_k.columns = ['rois','value']
        rois = list(data_k[data_k.value>0]['rois'])
        return rois

    def group_static_fc(self,group=None,plot=True,cmap='jet',darkstyle=False):
        """
        Compute the mean static functional
        connectivity matrix of a particular
        group/condition.

        Params:
        --------
        group : str.
            Specify the group of interest.

        plot : bool.
            Whether to create a heatmap showing
            the connectivity matrix.

        cmap : str.
            If plot=True, then select the colormap
            to use for the heatmap. Default = 'jet'.

        darkstyle : bool.
            Whether to use a dark background
            for plotting.

        Returns:
        --------
        static_fc : ndarray with shape (N_ROIs, N_ROIs).
            The computed static functional connectivity
            matrix.
        """
        if not isinstance(group,str):
            raise TypeError("'group' must be a string.")

        classes = load_classes(self._data_path_)

        #create list with conditions labels
        conditions = []
        for val in classes.values():
            for item in val:
                conditions.append(item)

        if len(conditions) != len(self.time_series().keys()):
            raise Exception("This method is only available in "
                "cases where each subject has only one condition label.")
        if group not in conditions:
            raise ValueError("'group' must be present in your data. "
                f"Possible options are: {[i for i in np.unique(conditions)]}.")       

        signals = load_tseries(self._data_path_)
        subjects_ids = [sub for sub,condition in zip(signals.keys(),conditions) if condition==group]
        N_subjects = len(subjects_ids)
        N_rois = len(self.rois_labels)
        pooled_static_fc = np.empty((N_rois,N_rois,N_subjects))
        
        for idx,sub in enumerate(subjects_ids):
            pooled_static_fc[:,:,idx] = np.corrcoef(signals[sub])
        
        static_fc = np.mean(pooled_static_fc,axis=-1)

        if plot:
            plt.ion()
            with plt.style.context('dark_background' if darkstyle else 'default'):
                plt.figure()
                sns.heatmap(
                    static_fc,
                    vmin=-1,
                    vmax=1,
                    center=0,
                    square=True,
                    cmap=cmap,
                    cbar_kws={'label': 'Pearson\ncorrelation','shrink': 0.5}
                )

                plt.xlabel('Brain region',fontsize=16,labelpad=20)
                plt.ylabel('Brain region',fontsize=16,labelpad=20)
                plt.title(group)
                plt.xticks(
                        np.arange(20,N_rois,20),
                        np.arange(20,N_rois,20).tolist(),
                        rotation=0
                        )
                plt.yticks(
                        np.arange(20,N_rois,20),
                        np.arange(20,N_rois,20).tolist()
                        )
                plt.tick_params(
                    axis='both',         
                    which='both',     
                    bottom=False,
                    left=False
                    )
                plt.tight_layout()
                #plt.show()

        return static_fc

    def group_transitions(self,k=2,metric='mean',cmap='inferno',darkstyle=False):
        """
        Compute and plot the mean or median transition
        probabilities matrix of each group/condition.

        Params:
        -------
        k : int.
            The k-means partition of interest.

        metric : str.
            Whether to plot the 'mean' or 'median'
            matrices.

        cmap : str.
            Colormap to use in the created heatmaps.

        darkstyle : bool.
            Whether to use a dark background for
            plotting.
        """
        _check_k_input(self._K_min_,self._K_max_,k)

        data = pd.read_csv(f'{self._dynamics_}/k_{k}/transitions_probabilities.csv',sep='\t')

        mats = group_transition_matrix(
            data,
            metric=metric,
            cmap=cmap,
            darkstyle=darkstyle
            )

        return mats

    def overlap_withyeo(self,parcellation=None,n_areas=None,k=2,state=None,darkstyle=False):
        """
        Compute the overlap between the 7 resting-state
        networks defined in Yeo et al. (2011) and the brain
        cortical regions/parcels of the phase-locking state
        of interest. The correlations are shown in a barplot,
        and a dataframe with the correlations and p-values is
        returned.

        Params:
        --------
        parcellation : str.
            Specify path to your parcellation .nii file.
            Note: the parcellation must be of 2mm resolution.

        n_areas : None | int.
            Analyze only the first n areas from the provided
            parcellation. 
            Usefull when the parcellation contains subcortical
            regions that must be ignored when computing the overlap
            with Yeo's cortical networks.

        k : int.
            Select the partition.

        state : int.
            Select the PL pattern or state of
            interest.

        darkstyle : bool.
            Whether to use a dark theme for the plot.

        Returns:
        --------
        overlap : pandas.dataframe with shape (7networks,3).
            Contains the correlation coefficient (and p-value)
            between the selected phase-locking state and each
            of the 7 resting-state networks from Yeo (2011).
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        _check_state(k,state)

        centr = {}

        for k_ in range(self._K_min_,self._K_max_+1):
            cent = self.load_centroids(k_)
            cent.insert(0,'state',[i+1 for i in range(cent.shape[0])])
            cent.insert(0,'k',k_)
            centr[f'k{k_}'] = cent
        
        centr = pd.concat(centr,ignore_index=True)

        corr,pvals = rsnets.compute_overlap(
            centr,
            parcellation=parcellation,
            n_areas=n_areas
            )

        overlap = rsnets.state_overlap(
            corr,
            pvals,
            k=k,
            state=state,
            plot=True,
            darkstyle=darkstyle
            )

        return overlap

    def _pool_stats(self,metric="occupancies"):
        """
        Pool the stats for each k for the selected
        metric in a single dataframe.

        Params:
        --------
        metric : str.
            Specify the metric of interest
            ('occupancies','dwell_times','transitions').
        """
        data_path = self._dynamics_
        #get list with folder names
        k_folders = [folder for folder in os.listdir(data_path) 
                    if os.path.isdir(os.path.join(data_path,folder)) 
                    and folder.startswith('k_')]

        stats_all = []
        for folder in k_folders:
            stats_all.append(
                pd.read_csv(f'{data_path}/{folder}/{metric}_stats.csv',sep='\t')
                )
        stats_all = pd.concat((stats_all),ignore_index=True)

        stats_all = stats_all.sort_values(by='k').reset_index(drop=True)

        return stats_all

    def _pool_dynamics_metric(self,metric="occupancies"):
        """
        Pool the computed values for each k for the
        selected metric in a dictionary.

        Params:
        --------
        metric : str.
            Specify the metric of interest.
            ('occupancies','dwell_times','transitions').
        """
        data_path = self._dynamics_
        #get list with folder names
        k_folders = [folder for folder in os.listdir(data_path)
                    if os.path.isdir(os.path.join(data_path,folder)) 
                    and folder.startswith('k_')]

        pooled_metric = {}
        for folder in k_folders:
            pooled_metric[folder] = pd.read_csv(f'{data_path}/{folder}/{metric}.csv',sep='\t')

        return pooled_metric

    #plotting methods

    def plot_states_nodes(self,k=2,state='all',node_size=15,show_labels=True,open=True,save=False):
        """
        Create a 3D interactive figure embedded in a
        .html file showing the BOLD phase-locking (PL)
        states in anatomical MNI space. Each parcel/ROI
        is represented as a node. Nodes that are part of
        the PL pattern are coloured in red, and the rest
        of nodes are coloured in blue.

        Params:
        -------
        k : int.
            Select the partition of interest.

        state : int or str.
            Use an integer to plot a single
            PL state of interest, or 'all'
            to plot all the PL states of the
            selected K partition.

        node_size: int or float.
            Define the size of the nodes. Nodes
            that don't belong to the pattern are
            plotted smaller.

        show_labels : bool.
            Whether to show each ROI label.

        open : bool. 
            Whether to open the plots in web
            browser. If False, you can open the
            figures using the '.open_in_browser()'
            method of the returned object/s.

        save : bool.
            Whether to save each plot in a
            .html file. If True, the files
            are saved in 'LEiDA_results/brain_plots'.

        Returns:
        --------
        plot/s : dict or single figure.
            If state='all', return a dictionary
            that contains the constructed plots.
            They can be opened or saved using
            '.open()' and '.save_as_html(path)',
            respectively. If state=int, then return
            a single figure.
        """
        if self.rois_coordinates_ is None:
            raise Exception("You can't create this plot because the "
                            "ROI's coordinates could't be loaded.")

        centroids = self.load_centroids(k=k).values
        plots = brain_states_nodes(
            centroids,
            self.rois_coordinates_,
            node_size=node_size,
            state=state,
            nodes_labels=None if not show_labels else self.rois_labels,
            open=open
            )
        
        if save:
            _save_html(self._results_path_,plots,k,state,plot_type='nodes')

        return plots
    
    def plot_states_network(self,k=2,state='all',node_size=8,node_color='infer',linewidth=3,open=True,save=False):
        """
        Create a 3D interactive figure embedded in a
        .html file showing the BOLD phase-locking (PL)
        states as a connected network. All the ROIs/parcels
        that belong to the selected phase-locking state are
        connected between each other.

        Params:
        -------
        k : int.
            Select the partition of interest.

        state : int or str.
            Use an integer to plot a single
            PL state of interest, or 'all'
            to plot all the PL states of the
            selected K partition.

        node_size : int. 
            Select the size of the nodes.

        node_color : str. 
            Select the color of the nodes. If
            'infer', then the nodes participating
            in the PL states are colored red and
            the rest blue. If 'black', then all the
            nodes are colored in the same way.

        linewidth : int. 
            Select the size of the edges
            connecting the nodes.

        open : bool. 
            Whether to open the plots in web
            browser. If False, you can open the
            figures using the '.open_in_browser()'
            method of the returned object/s.

        save : bool.
            Whether to save each plot in a
            .html file. If True, the files
            are saved in 'LEiDA_results/brain_plots'.

        Returns:
        --------
        plot/s : dict or single figure.
            If state='all', return a dictionary
            that contains the constructed plots.
            They can be opened or saved using
            '.open()' and '.save_as_html(path)',
            respectively. If state=int, then return
            a single figure.
        """
        if self.rois_coordinates_ is None:
            raise Exception("You can't create this plot because the "
                            "ROI's coordinates could't be loaded.")

        _check_k_input(self._K_min_,self._K_max_,k)
        if not isinstance(state,(int,str)):
            raise TypeError("'state' must be either 'all' or an integer "
                            "specifying the number of a particular PL state.")
        elif isinstance(state,str):
            if state!='all':
                raise ValueError("If a string is provided, 'state' must be 'all'!")
        else:
            _check_state(k,state)

        centroids = self.load_centroids(k=k).values #load centroids for the selected k

        #plotting
        plot = brain_states_network(
            centroids,
            self.rois_coordinates_,
            state=state,
            node_size=node_size,
            node_color=node_color,
            linewidth=linewidth,
            open=open
            )

        #saving figures
        if save:
            _save_html(self._results_path_,plot,k,state,plot_type='network')

        return plot

    def plot_states_pyramid(self,metric='occupancies',conditions=None,despine=True):
        """
        Create a pyramid of barplots showing the 'metric'
        of interest for each group, cluster (PL state), and
        K partition. Each barplot (which represents a particular
        PL state) is coloured according to its associated p-value:
        -black: the p-value is higher than 0.05.
        -red: the p-value is lower than 0.05 but higher than 0.05 / k.
        -green: the p-value is lower than 0.05/k but higher than 0.05 / Σ(k).
        -blue: the p-value is lower than 0.05 / Σ(k).

        Params:
        -------
        metric : str.
            Select the dynamical systems theory metric
            of interest (either 'occupancies' or 'dwell_times').

        conditions : None | list. Optional.
            (Usefull only when your data contains more
            than two conditions). You can provide a list
            specifying only two conditions of interest to
            plot. Otherwise create a plot for each pair of
            conditions. 

        despine : bool. Default = True.
            Whether to despine top and right axes of the
            subplots.
        """
        _check_metric(metric)

        if conditions is not None:
            if not isinstance(conditions,list) or len(conditions)!=2:
                raise Exception("If provided, 'conditions' must be a list with two items.")
            for cond in conditions:
                if cond not in self._classes_lst_:
                    raise Exception(f"'{cond}' was not founded in the data. "
                                    f" Valid options are: {self._classes_lst_}")
        else:
            conditions = self._classes_lst_.copy()

        pooled_stats = self._pool_stats(metric=metric)
        K_min = np.min(pooled_stats.k)
        K_max = np.max(pooled_stats.k)

        for cond in combinations(conditions,2):
            pooled_stats_ = pooled_stats[
                    (pooled_stats.group_1.isin(cond))
                    &
                    (pooled_stats.group_2.isin(cond))
                    ].reset_index(drop=True)

            dyn_metric = self._pool_dynamics_metric(metric=metric) 
            dyn_metric = {k:v[v.condition.isin(conditions)] for k,v in dyn_metric.items()}

            plot_pyramid(
                dyn_metric,
                pooled_stats_,
                K_min=K_min,
                K_max=K_max,
                despine=despine
                )

    def plot_clusters3D(self,k=2,clusters_colors=None,grid=True,alpha=.7,dot_size=3,edgecolor=None,darkstyle=False):
        """
        Visualize the identified clusters (BOLD phase-locking
        states) in a 3D scatter plot, which constitutes a
        low-dimensional representation of the 'state space'. 
        Method : take the eigenvectors and extract the first
        three principal components to reduce the dimensionality
        of the data to a 3D space. Each dot in the plot thus
        represents a single eigenvector, and is coloured according
        to the cluster it belongs to.
        
        Params:
        -------
        k : int.
            Specify the partition to plot.

        clusters_colors : list (optional). 
            Provide a list with the desired color
            of each cluster. If not provided, then
            a predefined set of colors will be used.

        grid : bool. 
            Whether to show grid or not.

        alpha : float. 
            Set transparency of dots.

        dot_size : float. 
            Select the dot size.

        edge_color : None | str.
            Specify an edge color to use
            on dots.

        darkstyle : bool.
            Whether to use a dark theme for
            the plot.
        """
        X = self.eigenvectors.iloc[:,2:].values #keep array containing only the eigenvectors
        y = self.predictions[f'k_{k}'].values #keep 1D array with the labels of each eigenvector

        with plt.style.context('dark_background' if darkstyle else 'default'):
            plot_clusters3D(
                X,
                y,
                clusters_colors=clusters_colors,
                grid=grid,
                alpha=alpha,
                dot_size=dot_size,
                edgecolor=edgecolor
                )

    def barplot_centroids(self,k=2,state='all'):
        """
        Create either subplots with barplots showing the
        values of each cluster centroid for the selected 'k'
        partition, or a single barplot showing the values
        of a specific phase-locking state.

        Params:
        ------
        k : int.
            Select the partition of interest.

        state : str or int.
            Specify if plot all the states for
            the selected 'k', or a single state
            of interest.
        """
        centroids = np.array(self.load_centroids(k=k),dtype=np.float32) #get centroids of the selected 'k'.
        if state=='all':
            barplot_states(centroids,self.rois_labels)
        else:
            _check_state(k,state)
            barplot_eig(centroids[state-1,:],self.rois_labels)
            plt.title(f'PL pattern {state}',fontsize=18)
            plt.tight_layout()

    def plot_pvalues(self,metric='occupancies',conditions=None,darkstyle=False,fill_areas=True):
        """
        Create a scatter plot showing the p-values
        obtained by the statistical analysis of a given
        'metric' across the explored 'k' range.

        Params:
        -------
        metric : str. 
            Specify the metric of interest
            ('occupancies','dwell_times','transitions').

        conditions : None | list. Optional
            (Usefull only when your data contains more
            than two conditions). You can provide a list
            specifying only two conditions of interest to
            plot. Otherwise create a plot for each pair of
            conditions. 

        darkstyle : bool.
            Whether to use a dark theme for
            the plots.

        fill_areas : bool.
            Select whether to fill the significance
            areas with color.
        """
        _check_metric(metric)

        if not isinstance(fill_areas,bool) or not isinstance(darkstyle,bool):
            raise TypeError("'fill_areas' and 'darkstyle' must be True or False!")

        if conditions is not None:
            if not isinstance(conditions,list) or len(conditions)!=2:
                raise Exception("If provided, 'conditions' must be a list with two items.")
            for cond in conditions:
                if cond not in self._classes_lst_:
                    raise Exception(f"'{cond}' was not founded in the data. "
                                    f" Valid options are: {self._classes_lst_}")
        else:
            conditions = self._classes_lst_.copy()

        pooled_stats = self._pool_stats(metric=metric)

        for cond in combinations(conditions,2):
            pooled_stats_ = pooled_stats[
                    (pooled_stats.group_1.isin(cond))
                    &
                    (pooled_stats.group_2.isin(cond))
                    ].reset_index(drop=True)

            scatter_pvalues(pooled_stats_,metric=metric,darkstyle=darkstyle,fill_areas=fill_areas)

    def plot_voronoi_cells(self,k=2):
        """
        Plot the clusters centroids in a 2D Voronoi
        cells space. Performs a PCA to reduce the
        dimensionality of the original centroid space
        to a 2D space.

        Params:
        --------
        k : int.
            Select the clustering solution to plot.

        Note: see Vohryzek, Deco et al. (2020) p.4
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        centroids = self.load_centroids(k=k).values 
        plot_voronoi(centroids)

    def plot_clustering_performance(self):
        """
        Create a 2x2 panel with lineplots showing
        the clustering evaluation metrics for each
        k partition explored (Dunn score, distortion,
        silhouette score, and Davis-Bouldin score).
        """
        performance = pd.read_csv(f'{self._clustering_}/clustering_performance.csv',sep='\t')
        plot_clustering_scores(performance)

    def plot_states_network_glass(self,k=2,darkstyle=False):
        """
        Create a glass brain (axial view) showing the
        network representation of each phase-locking
        (PL) state for the selected 'k' partition.

        Params:
        -------
        k : int.
            Select the partition of interest.

        darkstyle : bool.
            Whether to use a dark theme for
            the plots.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        pl_states = self.load_centroids(k=k).values

        with plt.style.context("dark_background" if darkstyle else "default"):
            states_k_glass(pl_states,self.rois_coordinates_,darkstyle=darkstyle)

    def plot_states_in_bold(self,subject_id,k=2,alpha=.5,darkstyle=False):
        """
        Create plot showing the time-series of BOLD signals, 
        highlighting the dominant phase-locking (PL) state
        of each time point or volume.

        Params:
        -------
        subject_id : str.
            Specify the 'id' of the subject
            of interest.

        k : int.
            Select the k partition.

        alpha : float.
            Transparency of the colors that
            show the dominant PL pattern of
            each time point.

        darkstyle : bool.
            Whether to create the plot using
            a darkstyle.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        signals = load_tseries(self._data_path_)
        signals = signals[subject_id][:,1:-1] #get subject signals (and exclude 1st and last volumes)
        y = self.predictions[self.predictions.subject_id==subject_id][f'k_{k}'].values #get predictions for selected k.

        with plt.style.context('dark_background' if darkstyle else 'default'):
            states_in_bold(signals,y,alpha=alpha)

    def plot_states_on_surf(self,k=2,state='all',parcellation=None,discretize=True,cmap='auto',darkstyle=False,open=False,save=False):
        """
        Create a 3D interactive figure embedded in a
        .html file showing the BOLD phase-locking (PL)
        states on cortical surface. By default, all the
        cortical regions that belong to a given PL state
        or pattern are coloured in red(s), while the rest
        of cortical regions are coloured in blue(s). You
        can change the colormap throught the 'cmap' argument.

        Params:
        -------
        k : int.
            Partition of interest.

        state : str or int.
            Whether to plot 'all' the PL states of
            the selected partition or a single state
            of interest.

        parcellation : str.
            Path to the .nii file containing the
            parcellation from which the time series
            were extracted.

        discretize : bool. Default = True.
            Whether to plot the raw values of the
            phase-locking state/centroid, or plot
            all the brain regions that belong to
            the phase-locking state with the same
            intensity.

        cmap : str or matplotlib colormap, optional. Default = 'auto'.
            Colormap to use in the brain plot.
            If 'auto', then the brain regions that
            belong to the phase-locking state will
            be coloured in red(s), and the rest of
            regions in blue(s).

        darkstyle : bool.
            Whether to use a black background.

        open : bool. 
            Whether to open the plots in web
            browser. If False, you can open the
            figures using the '.open_in_browser()'
            method of the returned object/s.

        save : bool.
            Whether to save each plot in a
            .html file. If True, the files
            are saved in 'LEiDA_results/brain_plots'.

        Returns:
        --------
        g : SurfaceView or dictionarity of SurfaceViews. 
        """
        _check_k_input(self._K_min_,self._K_max_,k)

        if not isinstance(state,(int,str)):
            raise TypeError("'state' must be either 'all' or an integer specifying the number of a particular PL state")
        if isinstance(state,str):
            if state!='all':
                raise ValueError("If a string is provided, 'state' must be 'all'!")
        else:
            _check_state(k,state)

        centroids = self.load_centroids(k=k).values 
        if state!='all':
            centroids = centroids[state-1,:]
        
        g = brain_states_on_surf(
            centroids,
            parcellation=parcellation,
            black_bg=darkstyle,
            open=open,
            discretize=discretize,
            cmap=cmap
            )

        if save:
            _save_html(self._results_path_,g,k,state,plot_type='surface')

        return g

    def plot_states_on_surf2(self,k=2,state=1,parcellation=None,surface='pial',hemi='right',view='lateral',darkstyle=False,save=False):
        """
        Plot a BOLD phase-locking state of interest 
        on cortical surface mesh. 

        Params:
        -------
        k : int.
            Partition of interest.

        state : int.
            Select the PL state/pattern of
            interest.

        parcellation : str.
            Path to the .nii file containing
            the parcellation from which the
            signals were extracted.

        surface : str.
            Specify the surface type to plot
            the pattern on. Valid options are
            'pial','infl', and 'white'.

        hemi : str.
            Select the hemisphere to plot.
            Valid options are 'right', 'left',
            or 'both'.

        view : str
            View of the surface that is rendered. 
            Default='lateral'. Options = {'lateral',
            'medial', 'dorsal', 'ventral', 'anterior',
            'posterior'}. If 'hemi'='both', then 'dorsal'
            and 'lateral' views are displayed.

        darkstyle : bool
            Whether to use a black background.

        save : bool.
            Whether to save the created figure in
            local folder. If True, the files are
            saved in 'LEiDA_results/brain_plots',
            and the plot will not be displayed.

        Returns:
        --------
        g : matplotlib figure. 
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        _check_state(k,state)

        centroids = self.load_centroids(k=k).values 
        centroid = centroids[state-1,:]

        #plotting
        print("\n-Creating plot. This may take "
            "some minutes. Please wait...")
            
        with plt.style.context('dark_background' if darkstyle else 'default'):
            g = brain_states_on_surf2(
                centroid,
                parcellation=parcellation,
                hemi=hemi,
                surface=surface,
                view=view
                )
        
        if save:
            try:
                path = f'{self._results_path_}/brain_plots'
                if not os.path.exists(path): 
                    os.makedirs(path)
                filename = f"{path}/K{k}_PL_state_{state}_{surface}surf_{hemi}hemi_{view if hemi!='both' else 'multiview'}.png"
                g.savefig(filename,dpi=300)
                plt.close()
                del g
                print(f"The plot was save at: {filename}")
            except:
                raise Exception("An error occured when saving the plot.")
        else:
            return g

    def explore_state(self,k=2,state=1,darkstyle=False):
        """
        Create a figure showing a phase-locking state of
        interest in different formats:
        a barplot, a network representation in brain space,
        a matrix representation, and two boxplots with the
        occupancies and dwell times for each group/condition.

        Params:
        ------
        k : int.
            Select the partition of interest.

        state : int.
            Select the PL state of interest.

        darkstyle : bool.
            Whether to use a dark background.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        _check_state(k,state)

        centroid = self.load_centroids(k=k).values
        centroid = centroid[state-1,:]

        occ = self.occupancies(k=k)[['condition',f'PL_state_{state}']]
        dt = self.dwell_times(k=k)[['condition',f'PL_state_{state}']]

        with plt.style.context('dark_background' if darkstyle else 'default'):
            _explore_state(
                centroid,
                self.rois_labels,
                occ,
                dt,
                self.rois_coordinates_,
                state_number=state,
                darkstyle=darkstyle
                )

    def plot_states_matrices(self,k=2,cmap='jet',darkstyle=False):
        """
        Take the controids resulting from the k-means
        clustering (i.e., the phase-locking states) and
        reconstruct the connectivity patterns in matrix
        format.

        Params:
        -------
        k : int.
            Specify the K partition of interest.
        
        cmap : str. Default = 'jet'.
            Select the colormap to use.

        darkstyle : bool.
            Whether to use a black background.
        """
        _check_k_input(self._K_min_,self._K_max_,k)

        if not isinstance(cmap,str):
            raise TypeError("'cmap' must be a string!")
        if not isinstance(darkstyle,bool):
            raise TypeError("'darkstyle' must be True or False!")

        _ = centroid2matrix(
            self.load_centroids(k).values,
            plot=True,
            cmap=cmap,
            darkstyle=darkstyle
            )
class DataLoader (data_path='data', results_path='LEiDA_results')

Class to retrieve from local folder all the outputs and results of the execution of the LEiDA pipeline through the 'Leida' class provided in the package. The DataLoader provides the same methods of the Leida class, thus allowing to load and deepen easily in the results of the previously executed analysis.

Params:

data_path : str. Path to the folder that contains the BOLD time series, metadata, ROIs labels, and ROIs coordinates.

results_path : str. Path to the folder that contains the LEiDA results (default: 'LEiDA_results').

Attributes:

eigenvectors : pandas.dataframe. Contains the computed eigenvectors for each time point of each subject..

predictions : pandas.dataframe. Contains the predicted cluster label of each eigenvector for each 'k' partition explored.

rois_labels : list. The label/name of each ROI/parcel.

rois_coordinates_ : ndarray of shape (n_rois,3). The MNI coordinates of each ROI/parcel.

Expand source code
class DataLoader:
    """
    Class to retrieve from local folder all
    the outputs and results of the execution
    of the LEiDA pipeline through the 'Leida'
    class provided in the package. The DataLoader
    provides the same methods of the Leida class,
    thus allowing to load and deepen easily in the
    results of the previously executed analysis.

    Params:
    -------
    data_path : str.
        Path to the folder that contains the
        BOLD time series, metadata, ROIs labels, 
        and ROIs coordinates.

    results_path : str.
        Path to the folder that contains the
        LEiDA results (default: 'LEiDA_results').

    Attributes:
    -----------
    eigenvectors : pandas.dataframe.
        Contains the computed eigenvectors
        for each time point of each subject.

    predictions : pandas.dataframe.
        Contains the predicted cluster label
        of each eigenvector for each 'k' partition
        explored.

    rois_labels : list.
        The label/name of each ROI/parcel.

    rois_coordinates_ : ndarray of shape (n_rois,3).
        The MNI coordinates of each ROI/parcel.
    """
    def __init__(self,data_path='data',results_path='LEiDA_results'):
        #validation of input data
        for _path in (data_path,results_path):
            if not isinstance(_path,str):
                raise TypeError("'data_path' and 'results_path' must be strings!")
        #check if the provided paths exists
        if not os.path.exists(data_path):
            raise ValueError("The provided 'data_path' could't be founded.")
        if not os.path.exists(results_path):
            raise ValueError("The provided 'results_path' could't be founded.")
        
        #Paths in which the data and results are stored
        self._data_path_ = data_path
        self._results_path_ = results_path
        self._clustering_ = results_path + '/clustering'
        self._dynamics_ = results_path + '/dynamics_metrics'
        self._models_ = self._clustering_ + '/models'

        #check if the 'clustering' and 'dynamics_metrics' folders exists in 'results_path'
        if not os.path.exists(self._clustering_) or not os.path.exists(self._dynamics_):
            raise Exception(f"No results were found in the specified path.")
        
        #load eigenvectors and kmeans predictions dataframes.
        try:
            self.eigenvectors = pd.read_csv(f'{results_path}/eigenvectors.csv',sep='\t')
            nrois = self.eigenvectors.shape[1]-2
        except:
            raise Exception("The eigenvectors dataframe could't be loaded.")
        try:
            self.predictions = pd.read_csv(f'{self._clustering_}/predictions.csv',sep='\t')
        except:
            raise Exception("The dataframe with the KMeans models predictions couldn't be loaded.")

        #load rois labels
        self.rois_labels = load_rois_labels(self._data_path_)
        nlabels = len(self.rois_labels)

        #load rois coordinates
        self.rois_coordinates_ = load_rois_coordinates(self._data_path_)
        if self.rois_coordinates_ is None:
            print("The ROIs coordinates couldn't be loaded from the provided 'data_path'. "
                "Brain plots that show nodes in brain space will not be executed in consequence.")
        else:
            ncoords = self.rois_coordinates_.shape[0]

        #check that the N of ROIs coincide across
        #labels, coordinates, and eigenvectors
        if self.rois_coordinates_ is None:
            if not nrois==nlabels:
                raise Exception(f"The number of ROIs in 'eigenvectors.csv' ({nrois}) "
                                f"and 'rois_labels.txt' {nlabels} must coincide!")
        else:
            if not nrois==nlabels==ncoords:
                raise Exception(f"The number of ROIs in 'eigenvectors.csv' ({nrois}), "
                                f"'rois_labels.txt' ({nlabels}), and 'rois_coordinates.csv' "
                                f"({ncoords}) must coincide!")

        self._K_min_ = 2
        self._K_max_ = 20

        self._classes_lst_ = np.unique(self.eigenvectors.condition).tolist()
        self._N_classes_ = len(self._classes_lst_)

    def time_series(self):
        """
        Return a dictionary having the 'subject_ids'
        as keys, and 2D arrays (N_ROIs,N_volumes) with
        BOLD time series as values.

        Returns:
        --------
        time_series : dict.
        """
        return load_tseries(self._data_path_)

    def load_model(self,k=2):
        """
        Load fitted model for a specific 'k' partition.
        Given that each model is an instance of the KMeansLeida
        class, once loaded you can access all the object methods
        and attributes.

        Params:
        -------
        k : int.
            Select the partition of interest.
        
        Returns:
        -------
        model : KMeansLeida instance.
            The fitted model that was used to predict
            the cluster labels of each observation.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        try:
            model = pd.read_pickle(f'{self._models_}/model_k_{k}.pkl')
            return model
        except:
            raise Exception("Can't find results for the selected k.")

    def load_centroids(self,k=2):
        """
        Return the computed clusters centroids
        for a specific 'k' partition.

        Params:
        -------
        k : int.
            Select the partition of interest.
        
        Returns:
        -------
        centroids : pd.DataFrame with shape (n_centroids,n_rois).
            Contains the computed centroids for
            the selected k partition.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        try:
            centroids = pd.DataFrame(self.load_model(k=k).cluster_centers_,columns=self.rois_labels)
        except:
            print("Can't find results for the selected k.")

        return centroids

    def centroids_distances(self,k=2):
        """
        Transform eigenvectors to a cluster-distance space. 
        Returns the distance between each eigenvector and the
        cluster centroids of the selected 'k' partition.

        Params:
        -------
        k : int.
            Select the partition of interest.
        
        Returns:
        --------
        distances : pd.DataFrame.
            Contains the distace between each eigenvector and
            each cluster centroid for the select 'k' partition.
            1st column contains 'subject_id', 2nd column the
            'condition', and the rest of columns the distances
            to the centroids.
        """
        model = self.load_model(k=k)
        distances = pd.DataFrame(model.transform(self.eigenvectors.iloc[:,2:].values))
        distances.columns = [f'centroid_{centroid+1}' for centroid in range(k)]
        distances = pd.concat((self.eigenvectors[['subject_id','condition']],distances),axis=1)
        return distances

    def stats(self,k=2,metric='occupancies'):
        """
        Retrieve the results from the statistical analysis of 
        a 'metric' of interest ('occupancies' or 'dwell_times')
        for a specific 'k' partition.

        Params:
        -------
        k : int.
            Select the 'k' partition of interest.

        metric : str.
            Select the metric to retrieve results.

        Returns:
        --------
        stats : pandas.dataframe.
            Results of the statistical analysis
            of each PL state.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        _check_metric(metric)

        try:
            df_stats = pd.read_csv(f'{self._dynamics_}/k_{k}/{metric}_stats.csv',sep='\t')
        except:
            print("Can't find results for the selected k or metric.")
        return df_stats

    def dwell_times(self,k=2):
        """
        Return the computed dwell times of each
        phase-locking state for a specific 'k'
        partition.

        Params:
        -------
        k : int.
            Specify the K-Means partition of interest.

        Returns:
        --------
        dwell_times : pd.DataFrame.
            Contains the computed dwell times of each
            PL state for each subject for the selected
            'k' partition.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        try:
            dt = pd.read_csv(f'{self._dynamics_}/k_{k}/dwell_times.csv',sep='\t')
        except:
            print("Can't find results for the selected k.")
        return dt
    
    def occupancies(self,k=2):
        """
        Return the computed fractional occupancy of each
        phase-locking state for a specific 'k' partition.

        Params:
        -------
        k : int.
            Specify the K-Means partition of
            interest.

        Returns:
        --------
        occupancies : pd.DataFrame.
            Contains the computed fractional occupancy
            of each PL state for each subject for the
            selected 'k' partition.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        try:
            occ = pd.read_csv(f'{self._dynamics_}/k_{k}/occupancies.csv',sep='\t')
        except:
            print("Can't find results for the selected k.")
        return occ

    def transitions(self,k=2):
        """
        Return the computed transition probabilities between
        phase-locking states for a specific 'k' partition.

        Params:
        -------
        k : int.
            Specify the K-Means partition of interest.

        Returns:
        --------
        transitions : pd.DataFrame.
            Contains the computed transition probabilities
            between PL states for each subject for the selected
            'k' partition.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        try:
            tr = pd.read_csv(f'{self._dynamics_}/k_{k}/transitions_probabilities.csv',sep='\t')
        except:
            print("Can't find results for the selected k.")
        return tr
    
    def significant_states(self,metric='occupancies'):
        """
        Return a dataframe containing only the statistics
        of the phase-locking states that are significantly
        different between groups.

        Params:
        -------
        metric : str.
            Metric of interest (Options: 'occupancies',
            'dwell_times').

        Returns:
        --------
        stats : pandas.dataframe.
        """
        _check_metric(metric)

        try:
            stats = self._pool_stats(metric=metric)
        except:
            raise Exception("The stats could't be loaded.")
        has_results = True if stats[stats.reject_null==True].shape[0]>=1 else False #check if some result was significant
        if has_results:
            return stats[stats.reject_null==True]
        else:
            print("No significant results were detected.")
            return None
        
    def state_rois(self,k=2,state=1):
        """
        Get a list with the names of the ROIs/parcels that
        participates in a specific phase-locking (PL) state.

        Params:
        -------
        k : int.
            Select the partition.

        state : int.
            Select the PL pattern or state of
            interest.

        Returns:
        --------
        rois : list.
            Contains the names of the ROIs that
            are part of the selected PL state.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        _check_state(k,state)

        data_k = self.load_centroids(k=k).iloc[state-1,:].reset_index()
        data_k.columns = ['rois','value']
        rois = list(data_k[data_k.value>0]['rois'])
        return rois

    def group_static_fc(self,group=None,plot=True,cmap='jet',darkstyle=False):
        """
        Compute the mean static functional
        connectivity matrix of a particular
        group/condition.

        Params:
        --------
        group : str.
            Specify the group of interest.

        plot : bool.
            Whether to create a heatmap showing
            the connectivity matrix.

        cmap : str.
            If plot=True, then select the colormap
            to use for the heatmap. Default = 'jet'.

        darkstyle : bool.
            Whether to use a dark background
            for plotting.

        Returns:
        --------
        static_fc : ndarray with shape (N_ROIs, N_ROIs).
            The computed static functional connectivity
            matrix.
        """
        if not isinstance(group,str):
            raise TypeError("'group' must be a string.")

        classes = load_classes(self._data_path_)

        #create list with conditions labels
        conditions = []
        for val in classes.values():
            for item in val:
                conditions.append(item)

        if len(conditions) != len(self.time_series().keys()):
            raise Exception("This method is only available in "
                "cases where each subject has only one condition label.")
        if group not in conditions:
            raise ValueError("'group' must be present in your data. "
                f"Possible options are: {[i for i in np.unique(conditions)]}.")       

        signals = load_tseries(self._data_path_)
        subjects_ids = [sub for sub,condition in zip(signals.keys(),conditions) if condition==group]
        N_subjects = len(subjects_ids)
        N_rois = len(self.rois_labels)
        pooled_static_fc = np.empty((N_rois,N_rois,N_subjects))
        
        for idx,sub in enumerate(subjects_ids):
            pooled_static_fc[:,:,idx] = np.corrcoef(signals[sub])
        
        static_fc = np.mean(pooled_static_fc,axis=-1)

        if plot:
            plt.ion()
            with plt.style.context('dark_background' if darkstyle else 'default'):
                plt.figure()
                sns.heatmap(
                    static_fc,
                    vmin=-1,
                    vmax=1,
                    center=0,
                    square=True,
                    cmap=cmap,
                    cbar_kws={'label': 'Pearson\ncorrelation','shrink': 0.5}
                )

                plt.xlabel('Brain region',fontsize=16,labelpad=20)
                plt.ylabel('Brain region',fontsize=16,labelpad=20)
                plt.title(group)
                plt.xticks(
                        np.arange(20,N_rois,20),
                        np.arange(20,N_rois,20).tolist(),
                        rotation=0
                        )
                plt.yticks(
                        np.arange(20,N_rois,20),
                        np.arange(20,N_rois,20).tolist()
                        )
                plt.tick_params(
                    axis='both',         
                    which='both',     
                    bottom=False,
                    left=False
                    )
                plt.tight_layout()
                #plt.show()

        return static_fc

    def group_transitions(self,k=2,metric='mean',cmap='inferno',darkstyle=False):
        """
        Compute and plot the mean or median transition
        probabilities matrix of each group/condition.

        Params:
        -------
        k : int.
            The k-means partition of interest.

        metric : str.
            Whether to plot the 'mean' or 'median'
            matrices.

        cmap : str.
            Colormap to use in the created heatmaps.

        darkstyle : bool.
            Whether to use a dark background for
            plotting.
        """
        _check_k_input(self._K_min_,self._K_max_,k)

        data = pd.read_csv(f'{self._dynamics_}/k_{k}/transitions_probabilities.csv',sep='\t')

        mats = group_transition_matrix(
            data,
            metric=metric,
            cmap=cmap,
            darkstyle=darkstyle
            )

        return mats

    def overlap_withyeo(self,parcellation=None,n_areas=None,k=2,state=None,darkstyle=False):
        """
        Compute the overlap between the 7 resting-state
        networks defined in Yeo et al. (2011) and the brain
        cortical regions/parcels of the phase-locking state
        of interest. The correlations are shown in a barplot,
        and a dataframe with the correlations and p-values is
        returned.

        Params:
        --------
        parcellation : str.
            Specify path to your parcellation .nii file.
            Note: the parcellation must be of 2mm resolution.

        n_areas : None | int.
            Analyze only the first n areas from the provided
            parcellation. 
            Usefull when the parcellation contains subcortical
            regions that must be ignored when computing the overlap
            with Yeo's cortical networks.

        k : int.
            Select the partition.

        state : int.
            Select the PL pattern or state of
            interest.

        darkstyle : bool.
            Whether to use a dark theme for the plot.

        Returns:
        --------
        overlap : pandas.dataframe with shape (7networks,3).
            Contains the correlation coefficient (and p-value)
            between the selected phase-locking state and each
            of the 7 resting-state networks from Yeo (2011).
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        _check_state(k,state)

        centr = {}

        for k_ in range(self._K_min_,self._K_max_+1):
            cent = self.load_centroids(k_)
            cent.insert(0,'state',[i+1 for i in range(cent.shape[0])])
            cent.insert(0,'k',k_)
            centr[f'k{k_}'] = cent
        
        centr = pd.concat(centr,ignore_index=True)

        corr,pvals = rsnets.compute_overlap(
            centr,
            parcellation=parcellation,
            n_areas=n_areas
            )

        overlap = rsnets.state_overlap(
            corr,
            pvals,
            k=k,
            state=state,
            plot=True,
            darkstyle=darkstyle
            )

        return overlap

    def _pool_stats(self,metric="occupancies"):
        """
        Pool the stats for each k for the selected
        metric in a single dataframe.

        Params:
        --------
        metric : str.
            Specify the metric of interest
            ('occupancies','dwell_times','transitions').
        """
        data_path = self._dynamics_
        #get list with folder names
        k_folders = [folder for folder in os.listdir(data_path) 
                    if os.path.isdir(os.path.join(data_path,folder)) 
                    and folder.startswith('k_')]

        stats_all = []
        for folder in k_folders:
            stats_all.append(
                pd.read_csv(f'{data_path}/{folder}/{metric}_stats.csv',sep='\t')
                )
        stats_all = pd.concat((stats_all),ignore_index=True)

        stats_all = stats_all.sort_values(by='k').reset_index(drop=True)

        return stats_all

    def _pool_dynamics_metric(self,metric="occupancies"):
        """
        Pool the computed values for each k for the
        selected metric in a dictionary.

        Params:
        --------
        metric : str.
            Specify the metric of interest.
            ('occupancies','dwell_times','transitions').
        """
        data_path = self._dynamics_
        #get list with folder names
        k_folders = [folder for folder in os.listdir(data_path)
                    if os.path.isdir(os.path.join(data_path,folder)) 
                    and folder.startswith('k_')]

        pooled_metric = {}
        for folder in k_folders:
            pooled_metric[folder] = pd.read_csv(f'{data_path}/{folder}/{metric}.csv',sep='\t')

        return pooled_metric

    #plotting methods

    def plot_states_nodes(self,k=2,state='all',node_size=15,show_labels=True,open=True,save=False):
        """
        Create a 3D interactive figure embedded in a
        .html file showing the BOLD phase-locking (PL)
        states in anatomical MNI space. Each parcel/ROI
        is represented as a node. Nodes that are part of
        the PL pattern are coloured in red, and the rest
        of nodes are coloured in blue.

        Params:
        -------
        k : int.
            Select the partition of interest.

        state : int or str.
            Use an integer to plot a single
            PL state of interest, or 'all'
            to plot all the PL states of the
            selected K partition.

        node_size: int or float.
            Define the size of the nodes. Nodes
            that don't belong to the pattern are
            plotted smaller.

        show_labels : bool.
            Whether to show each ROI label.

        open : bool. 
            Whether to open the plots in web
            browser. If False, you can open the
            figures using the '.open_in_browser()'
            method of the returned object/s.

        save : bool.
            Whether to save each plot in a
            .html file. If True, the files
            are saved in 'LEiDA_results/brain_plots'.

        Returns:
        --------
        plot/s : dict or single figure.
            If state='all', return a dictionary
            that contains the constructed plots.
            They can be opened or saved using
            '.open()' and '.save_as_html(path)',
            respectively. If state=int, then return
            a single figure.
        """
        if self.rois_coordinates_ is None:
            raise Exception("You can't create this plot because the "
                            "ROI's coordinates could't be loaded.")

        centroids = self.load_centroids(k=k).values
        plots = brain_states_nodes(
            centroids,
            self.rois_coordinates_,
            node_size=node_size,
            state=state,
            nodes_labels=None if not show_labels else self.rois_labels,
            open=open
            )
        
        if save:
            _save_html(self._results_path_,plots,k,state,plot_type='nodes')

        return plots
    
    def plot_states_network(self,k=2,state='all',node_size=8,node_color='infer',linewidth=3,open=True,save=False):
        """
        Create a 3D interactive figure embedded in a
        .html file showing the BOLD phase-locking (PL)
        states as a connected network. All the ROIs/parcels
        that belong to the selected phase-locking state are
        connected between each other.

        Params:
        -------
        k : int.
            Select the partition of interest.

        state : int or str.
            Use an integer to plot a single
            PL state of interest, or 'all'
            to plot all the PL states of the
            selected K partition.

        node_size : int. 
            Select the size of the nodes.

        node_color : str. 
            Select the color of the nodes. If
            'infer', then the nodes participating
            in the PL states are colored red and
            the rest blue. If 'black', then all the
            nodes are colored in the same way.

        linewidth : int. 
            Select the size of the edges
            connecting the nodes.

        open : bool. 
            Whether to open the plots in web
            browser. If False, you can open the
            figures using the '.open_in_browser()'
            method of the returned object/s.

        save : bool.
            Whether to save each plot in a
            .html file. If True, the files
            are saved in 'LEiDA_results/brain_plots'.

        Returns:
        --------
        plot/s : dict or single figure.
            If state='all', return a dictionary
            that contains the constructed plots.
            They can be opened or saved using
            '.open()' and '.save_as_html(path)',
            respectively. If state=int, then return
            a single figure.
        """
        if self.rois_coordinates_ is None:
            raise Exception("You can't create this plot because the "
                            "ROI's coordinates could't be loaded.")

        _check_k_input(self._K_min_,self._K_max_,k)
        if not isinstance(state,(int,str)):
            raise TypeError("'state' must be either 'all' or an integer "
                            "specifying the number of a particular PL state.")
        elif isinstance(state,str):
            if state!='all':
                raise ValueError("If a string is provided, 'state' must be 'all'!")
        else:
            _check_state(k,state)

        centroids = self.load_centroids(k=k).values #load centroids for the selected k

        #plotting
        plot = brain_states_network(
            centroids,
            self.rois_coordinates_,
            state=state,
            node_size=node_size,
            node_color=node_color,
            linewidth=linewidth,
            open=open
            )

        #saving figures
        if save:
            _save_html(self._results_path_,plot,k,state,plot_type='network')

        return plot

    def plot_states_pyramid(self,metric='occupancies',conditions=None,despine=True):
        """
        Create a pyramid of barplots showing the 'metric'
        of interest for each group, cluster (PL state), and
        K partition. Each barplot (which represents a particular
        PL state) is coloured according to its associated p-value:
        -black: the p-value is higher than 0.05.
        -red: the p-value is lower than 0.05 but higher than 0.05 / k.
        -green: the p-value is lower than 0.05/k but higher than 0.05 / Σ(k).
        -blue: the p-value is lower than 0.05 / Σ(k).

        Params:
        -------
        metric : str.
            Select the dynamical systems theory metric
            of interest (either 'occupancies' or 'dwell_times').

        conditions : None | list. Optional.
            (Usefull only when your data contains more
            than two conditions). You can provide a list
            specifying only two conditions of interest to
            plot. Otherwise create a plot for each pair of
            conditions. 

        despine : bool. Default = True.
            Whether to despine top and right axes of the
            subplots.
        """
        _check_metric(metric)

        if conditions is not None:
            if not isinstance(conditions,list) or len(conditions)!=2:
                raise Exception("If provided, 'conditions' must be a list with two items.")
            for cond in conditions:
                if cond not in self._classes_lst_:
                    raise Exception(f"'{cond}' was not founded in the data. "
                                    f" Valid options are: {self._classes_lst_}")
        else:
            conditions = self._classes_lst_.copy()

        pooled_stats = self._pool_stats(metric=metric)
        K_min = np.min(pooled_stats.k)
        K_max = np.max(pooled_stats.k)

        for cond in combinations(conditions,2):
            pooled_stats_ = pooled_stats[
                    (pooled_stats.group_1.isin(cond))
                    &
                    (pooled_stats.group_2.isin(cond))
                    ].reset_index(drop=True)

            dyn_metric = self._pool_dynamics_metric(metric=metric) 
            dyn_metric = {k:v[v.condition.isin(conditions)] for k,v in dyn_metric.items()}

            plot_pyramid(
                dyn_metric,
                pooled_stats_,
                K_min=K_min,
                K_max=K_max,
                despine=despine
                )

    def plot_clusters3D(self,k=2,clusters_colors=None,grid=True,alpha=.7,dot_size=3,edgecolor=None,darkstyle=False):
        """
        Visualize the identified clusters (BOLD phase-locking
        states) in a 3D scatter plot, which constitutes a
        low-dimensional representation of the 'state space'. 
        Method : take the eigenvectors and extract the first
        three principal components to reduce the dimensionality
        of the data to a 3D space. Each dot in the plot thus
        represents a single eigenvector, and is coloured according
        to the cluster it belongs to.
        
        Params:
        -------
        k : int.
            Specify the partition to plot.

        clusters_colors : list (optional). 
            Provide a list with the desired color
            of each cluster. If not provided, then
            a predefined set of colors will be used.

        grid : bool. 
            Whether to show grid or not.

        alpha : float. 
            Set transparency of dots.

        dot_size : float. 
            Select the dot size.

        edge_color : None | str.
            Specify an edge color to use
            on dots.

        darkstyle : bool.
            Whether to use a dark theme for
            the plot.
        """
        X = self.eigenvectors.iloc[:,2:].values #keep array containing only the eigenvectors
        y = self.predictions[f'k_{k}'].values #keep 1D array with the labels of each eigenvector

        with plt.style.context('dark_background' if darkstyle else 'default'):
            plot_clusters3D(
                X,
                y,
                clusters_colors=clusters_colors,
                grid=grid,
                alpha=alpha,
                dot_size=dot_size,
                edgecolor=edgecolor
                )

    def barplot_centroids(self,k=2,state='all'):
        """
        Create either subplots with barplots showing the
        values of each cluster centroid for the selected 'k'
        partition, or a single barplot showing the values
        of a specific phase-locking state.

        Params:
        ------
        k : int.
            Select the partition of interest.

        state : str or int.
            Specify if plot all the states for
            the selected 'k', or a single state
            of interest.
        """
        centroids = np.array(self.load_centroids(k=k),dtype=np.float32) #get centroids of the selected 'k'.
        if state=='all':
            barplot_states(centroids,self.rois_labels)
        else:
            _check_state(k,state)
            barplot_eig(centroids[state-1,:],self.rois_labels)
            plt.title(f'PL pattern {state}',fontsize=18)
            plt.tight_layout()

    def plot_pvalues(self,metric='occupancies',conditions=None,darkstyle=False,fill_areas=True):
        """
        Create a scatter plot showing the p-values
        obtained by the statistical analysis of a given
        'metric' across the explored 'k' range.

        Params:
        -------
        metric : str. 
            Specify the metric of interest
            ('occupancies','dwell_times','transitions').

        conditions : None | list. Optional
            (Usefull only when your data contains more
            than two conditions). You can provide a list
            specifying only two conditions of interest to
            plot. Otherwise create a plot for each pair of
            conditions. 

        darkstyle : bool.
            Whether to use a dark theme for
            the plots.

        fill_areas : bool.
            Select whether to fill the significance
            areas with color.
        """
        _check_metric(metric)

        if not isinstance(fill_areas,bool) or not isinstance(darkstyle,bool):
            raise TypeError("'fill_areas' and 'darkstyle' must be True or False!")

        if conditions is not None:
            if not isinstance(conditions,list) or len(conditions)!=2:
                raise Exception("If provided, 'conditions' must be a list with two items.")
            for cond in conditions:
                if cond not in self._classes_lst_:
                    raise Exception(f"'{cond}' was not founded in the data. "
                                    f" Valid options are: {self._classes_lst_}")
        else:
            conditions = self._classes_lst_.copy()

        pooled_stats = self._pool_stats(metric=metric)

        for cond in combinations(conditions,2):
            pooled_stats_ = pooled_stats[
                    (pooled_stats.group_1.isin(cond))
                    &
                    (pooled_stats.group_2.isin(cond))
                    ].reset_index(drop=True)

            scatter_pvalues(pooled_stats_,metric=metric,darkstyle=darkstyle,fill_areas=fill_areas)

    def plot_voronoi_cells(self,k=2):
        """
        Plot the clusters centroids in a 2D Voronoi
        cells space. Performs a PCA to reduce the
        dimensionality of the original centroid space
        to a 2D space.

        Params:
        --------
        k : int.
            Select the clustering solution to plot.

        Note: see Vohryzek, Deco et al. (2020) p.4
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        centroids = self.load_centroids(k=k).values 
        plot_voronoi(centroids)

    def plot_clustering_performance(self):
        """
        Create a 2x2 panel with lineplots showing
        the clustering evaluation metrics for each
        k partition explored (Dunn score, distortion,
        silhouette score, and Davis-Bouldin score).
        """
        performance = pd.read_csv(f'{self._clustering_}/clustering_performance.csv',sep='\t')
        plot_clustering_scores(performance)

    def plot_states_network_glass(self,k=2,darkstyle=False):
        """
        Create a glass brain (axial view) showing the
        network representation of each phase-locking
        (PL) state for the selected 'k' partition.

        Params:
        -------
        k : int.
            Select the partition of interest.

        darkstyle : bool.
            Whether to use a dark theme for
            the plots.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        pl_states = self.load_centroids(k=k).values

        with plt.style.context("dark_background" if darkstyle else "default"):
            states_k_glass(pl_states,self.rois_coordinates_,darkstyle=darkstyle)

    def plot_states_in_bold(self,subject_id,k=2,alpha=.5,darkstyle=False):
        """
        Create plot showing the time-series of BOLD signals, 
        highlighting the dominant phase-locking (PL) state
        of each time point or volume.

        Params:
        -------
        subject_id : str.
            Specify the 'id' of the subject
            of interest.

        k : int.
            Select the k partition.

        alpha : float.
            Transparency of the colors that
            show the dominant PL pattern of
            each time point.

        darkstyle : bool.
            Whether to create the plot using
            a darkstyle.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        signals = load_tseries(self._data_path_)
        signals = signals[subject_id][:,1:-1] #get subject signals (and exclude 1st and last volumes)
        y = self.predictions[self.predictions.subject_id==subject_id][f'k_{k}'].values #get predictions for selected k.

        with plt.style.context('dark_background' if darkstyle else 'default'):
            states_in_bold(signals,y,alpha=alpha)

    def plot_states_on_surf(self,k=2,state='all',parcellation=None,discretize=True,cmap='auto',darkstyle=False,open=False,save=False):
        """
        Create a 3D interactive figure embedded in a
        .html file showing the BOLD phase-locking (PL)
        states on cortical surface. By default, all the
        cortical regions that belong to a given PL state
        or pattern are coloured in red(s), while the rest
        of cortical regions are coloured in blue(s). You
        can change the colormap throught the 'cmap' argument.

        Params:
        -------
        k : int.
            Partition of interest.

        state : str or int.
            Whether to plot 'all' the PL states of
            the selected partition or a single state
            of interest.

        parcellation : str.
            Path to the .nii file containing the
            parcellation from which the time series
            were extracted.

        discretize : bool. Default = True.
            Whether to plot the raw values of the
            phase-locking state/centroid, or plot
            all the brain regions that belong to
            the phase-locking state with the same
            intensity.

        cmap : str or matplotlib colormap, optional. Default = 'auto'.
            Colormap to use in the brain plot.
            If 'auto', then the brain regions that
            belong to the phase-locking state will
            be coloured in red(s), and the rest of
            regions in blue(s).

        darkstyle : bool.
            Whether to use a black background.

        open : bool. 
            Whether to open the plots in web
            browser. If False, you can open the
            figures using the '.open_in_browser()'
            method of the returned object/s.

        save : bool.
            Whether to save each plot in a
            .html file. If True, the files
            are saved in 'LEiDA_results/brain_plots'.

        Returns:
        --------
        g : SurfaceView or dictionarity of SurfaceViews. 
        """
        _check_k_input(self._K_min_,self._K_max_,k)

        if not isinstance(state,(int,str)):
            raise TypeError("'state' must be either 'all' or an integer specifying the number of a particular PL state")
        if isinstance(state,str):
            if state!='all':
                raise ValueError("If a string is provided, 'state' must be 'all'!")
        else:
            _check_state(k,state)

        centroids = self.load_centroids(k=k).values 
        if state!='all':
            centroids = centroids[state-1,:]
        
        g = brain_states_on_surf(
            centroids,
            parcellation=parcellation,
            black_bg=darkstyle,
            open=open,
            discretize=discretize,
            cmap=cmap
            )

        if save:
            _save_html(self._results_path_,g,k,state,plot_type='surface')

        return g

    def plot_states_on_surf2(self,k=2,state=1,parcellation=None,surface='pial',hemi='right',view='lateral',darkstyle=False,save=False):
        """
        Plot a BOLD phase-locking state of interest 
        on cortical surface mesh. 

        Params:
        -------
        k : int.
            Partition of interest.

        state : int.
            Select the PL state/pattern of
            interest.

        parcellation : str.
            Path to the .nii file containing
            the parcellation from which the
            signals were extracted.

        surface : str.
            Specify the surface type to plot
            the pattern on. Valid options are
            'pial','infl', and 'white'.

        hemi : str.
            Select the hemisphere to plot.
            Valid options are 'right', 'left',
            or 'both'.

        view : str
            View of the surface that is rendered. 
            Default='lateral'. Options = {'lateral',
            'medial', 'dorsal', 'ventral', 'anterior',
            'posterior'}. If 'hemi'='both', then 'dorsal'
            and 'lateral' views are displayed.

        darkstyle : bool
            Whether to use a black background.

        save : bool.
            Whether to save the created figure in
            local folder. If True, the files are
            saved in 'LEiDA_results/brain_plots',
            and the plot will not be displayed.

        Returns:
        --------
        g : matplotlib figure. 
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        _check_state(k,state)

        centroids = self.load_centroids(k=k).values 
        centroid = centroids[state-1,:]

        #plotting
        print("\n-Creating plot. This may take "
            "some minutes. Please wait...")
            
        with plt.style.context('dark_background' if darkstyle else 'default'):
            g = brain_states_on_surf2(
                centroid,
                parcellation=parcellation,
                hemi=hemi,
                surface=surface,
                view=view
                )
        
        if save:
            try:
                path = f'{self._results_path_}/brain_plots'
                if not os.path.exists(path): 
                    os.makedirs(path)
                filename = f"{path}/K{k}_PL_state_{state}_{surface}surf_{hemi}hemi_{view if hemi!='both' else 'multiview'}.png"
                g.savefig(filename,dpi=300)
                plt.close()
                del g
                print(f"The plot was save at: {filename}")
            except:
                raise Exception("An error occured when saving the plot.")
        else:
            return g

    def explore_state(self,k=2,state=1,darkstyle=False):
        """
        Create a figure showing a phase-locking state of
        interest in different formats:
        a barplot, a network representation in brain space,
        a matrix representation, and two boxplots with the
        occupancies and dwell times for each group/condition.

        Params:
        ------
        k : int.
            Select the partition of interest.

        state : int.
            Select the PL state of interest.

        darkstyle : bool.
            Whether to use a dark background.
        """
        _check_k_input(self._K_min_,self._K_max_,k)
        _check_state(k,state)

        centroid = self.load_centroids(k=k).values
        centroid = centroid[state-1,:]

        occ = self.occupancies(k=k)[['condition',f'PL_state_{state}']]
        dt = self.dwell_times(k=k)[['condition',f'PL_state_{state}']]

        with plt.style.context('dark_background' if darkstyle else 'default'):
            _explore_state(
                centroid,
                self.rois_labels,
                occ,
                dt,
                self.rois_coordinates_,
                state_number=state,
                darkstyle=darkstyle
                )

    def plot_states_matrices(self,k=2,cmap='jet',darkstyle=False):
        """
        Take the controids resulting from the k-means
        clustering (i.e., the phase-locking states) and
        reconstruct the connectivity patterns in matrix
        format.

        Params:
        -------
        k : int.
            Specify the K partition of interest.
        
        cmap : str. Default = 'jet'.
            Select the colormap to use.

        darkstyle : bool.
            Whether to use a black background.
        """
        _check_k_input(self._K_min_,self._K_max_,k)

        if not isinstance(cmap,str):
            raise TypeError("'cmap' must be a string!")
        if not isinstance(darkstyle,bool):
            raise TypeError("'darkstyle' must be True or False!")

        _ = centroid2matrix(
            self.load_centroids(k).values,
            plot=True,
            cmap=cmap,
            darkstyle=darkstyle
            )

Methods

def barplot_centroids(self, k=2, state='all')

Create either subplots with barplots showing the values of each cluster centroid for the selected 'k' partition, or a single barplot showing the values of a specific phase-locking state.

Params:

k : int. Select the partition of interest.

state : str or int. Specify if plot all the states for the selected 'k', or a single state of interest.

Expand source code
def barplot_centroids(self,k=2,state='all'):
    """
    Create either subplots with barplots showing the
    values of each cluster centroid for the selected 'k'
    partition, or a single barplot showing the values
    of a specific phase-locking state.

    Params:
    ------
    k : int.
        Select the partition of interest.

    state : str or int.
        Specify if plot all the states for
        the selected 'k', or a single state
        of interest.
    """
    centroids = np.array(self.load_centroids(k=k),dtype=np.float32) #get centroids of the selected 'k'.
    if state=='all':
        barplot_states(centroids,self.rois_labels)
    else:
        _check_state(k,state)
        barplot_eig(centroids[state-1,:],self.rois_labels)
        plt.title(f'PL pattern {state}',fontsize=18)
        plt.tight_layout()
def centroids_distances(self, k=2)

Transform eigenvectors to a cluster-distance space. Returns the distance between each eigenvector and the cluster centroids of the selected 'k' partition.

Params:

k : int. Select the partition of interest.

Returns:

distances : pd.DataFrame. Contains the distace between each eigenvector and each cluster centroid for the select 'k' partition. 1st column contains 'subject_id', 2nd column the 'condition', and the rest of columns the distances to the centroids.

Expand source code
def centroids_distances(self,k=2):
    """
    Transform eigenvectors to a cluster-distance space. 
    Returns the distance between each eigenvector and the
    cluster centroids of the selected 'k' partition.

    Params:
    -------
    k : int.
        Select the partition of interest.
    
    Returns:
    --------
    distances : pd.DataFrame.
        Contains the distace between each eigenvector and
        each cluster centroid for the select 'k' partition.
        1st column contains 'subject_id', 2nd column the
        'condition', and the rest of columns the distances
        to the centroids.
    """
    model = self.load_model(k=k)
    distances = pd.DataFrame(model.transform(self.eigenvectors.iloc[:,2:].values))
    distances.columns = [f'centroid_{centroid+1}' for centroid in range(k)]
    distances = pd.concat((self.eigenvectors[['subject_id','condition']],distances),axis=1)
    return distances
def dwell_times(self, k=2)

Return the computed dwell times of each phase-locking state for a specific 'k' partition.

Params:

k : int. Specify the K-Means partition of interest.

Returns:

dwell_times : pd.DataFrame. Contains the computed dwell times of each PL state for each subject for the selected 'k' partition.

Expand source code
def dwell_times(self,k=2):
    """
    Return the computed dwell times of each
    phase-locking state for a specific 'k'
    partition.

    Params:
    -------
    k : int.
        Specify the K-Means partition of interest.

    Returns:
    --------
    dwell_times : pd.DataFrame.
        Contains the computed dwell times of each
        PL state for each subject for the selected
        'k' partition.
    """
    _check_k_input(self._K_min_,self._K_max_,k)
    try:
        dt = pd.read_csv(f'{self._dynamics_}/k_{k}/dwell_times.csv',sep='\t')
    except:
        print("Can't find results for the selected k.")
    return dt
def explore_state(self, k=2, state=1, darkstyle=False)

Create a figure showing a phase-locking state of interest in different formats: a barplot, a network representation in brain space, a matrix representation, and two boxplots with the occupancies and dwell times for each group/condition.

Params:

k : int. Select the partition of interest.

state : int. Select the PL state of interest.

darkstyle : bool. Whether to use a dark background.

Expand source code
def explore_state(self,k=2,state=1,darkstyle=False):
    """
    Create a figure showing a phase-locking state of
    interest in different formats:
    a barplot, a network representation in brain space,
    a matrix representation, and two boxplots with the
    occupancies and dwell times for each group/condition.

    Params:
    ------
    k : int.
        Select the partition of interest.

    state : int.
        Select the PL state of interest.

    darkstyle : bool.
        Whether to use a dark background.
    """
    _check_k_input(self._K_min_,self._K_max_,k)
    _check_state(k,state)

    centroid = self.load_centroids(k=k).values
    centroid = centroid[state-1,:]

    occ = self.occupancies(k=k)[['condition',f'PL_state_{state}']]
    dt = self.dwell_times(k=k)[['condition',f'PL_state_{state}']]

    with plt.style.context('dark_background' if darkstyle else 'default'):
        _explore_state(
            centroid,
            self.rois_labels,
            occ,
            dt,
            self.rois_coordinates_,
            state_number=state,
            darkstyle=darkstyle
            )
def group_static_fc(self, group=None, plot=True, cmap='jet', darkstyle=False)

Compute the mean static functional connectivity matrix of a particular group/condition.

Params:

group : str. Specify the group of interest.

plot : bool. Whether to create a heatmap showing the connectivity matrix.

cmap : str. If plot=True, then select the colormap to use for the heatmap. Default = 'jet'.

darkstyle : bool. Whether to use a dark background for plotting.

Returns:

static_fc : ndarray with shape (N_ROIs, N_ROIs). The computed static functional connectivity matrix.

Expand source code
def group_static_fc(self,group=None,plot=True,cmap='jet',darkstyle=False):
    """
    Compute the mean static functional
    connectivity matrix of a particular
    group/condition.

    Params:
    --------
    group : str.
        Specify the group of interest.

    plot : bool.
        Whether to create a heatmap showing
        the connectivity matrix.

    cmap : str.
        If plot=True, then select the colormap
        to use for the heatmap. Default = 'jet'.

    darkstyle : bool.
        Whether to use a dark background
        for plotting.

    Returns:
    --------
    static_fc : ndarray with shape (N_ROIs, N_ROIs).
        The computed static functional connectivity
        matrix.
    """
    if not isinstance(group,str):
        raise TypeError("'group' must be a string.")

    classes = load_classes(self._data_path_)

    #create list with conditions labels
    conditions = []
    for val in classes.values():
        for item in val:
            conditions.append(item)

    if len(conditions) != len(self.time_series().keys()):
        raise Exception("This method is only available in "
            "cases where each subject has only one condition label.")
    if group not in conditions:
        raise ValueError("'group' must be present in your data. "
            f"Possible options are: {[i for i in np.unique(conditions)]}.")       

    signals = load_tseries(self._data_path_)
    subjects_ids = [sub for sub,condition in zip(signals.keys(),conditions) if condition==group]
    N_subjects = len(subjects_ids)
    N_rois = len(self.rois_labels)
    pooled_static_fc = np.empty((N_rois,N_rois,N_subjects))
    
    for idx,sub in enumerate(subjects_ids):
        pooled_static_fc[:,:,idx] = np.corrcoef(signals[sub])
    
    static_fc = np.mean(pooled_static_fc,axis=-1)

    if plot:
        plt.ion()
        with plt.style.context('dark_background' if darkstyle else 'default'):
            plt.figure()
            sns.heatmap(
                static_fc,
                vmin=-1,
                vmax=1,
                center=0,
                square=True,
                cmap=cmap,
                cbar_kws={'label': 'Pearson\ncorrelation','shrink': 0.5}
            )

            plt.xlabel('Brain region',fontsize=16,labelpad=20)
            plt.ylabel('Brain region',fontsize=16,labelpad=20)
            plt.title(group)
            plt.xticks(
                    np.arange(20,N_rois,20),
                    np.arange(20,N_rois,20).tolist(),
                    rotation=0
                    )
            plt.yticks(
                    np.arange(20,N_rois,20),
                    np.arange(20,N_rois,20).tolist()
                    )
            plt.tick_params(
                axis='both',         
                which='both',     
                bottom=False,
                left=False
                )
            plt.tight_layout()
            #plt.show()

    return static_fc
def group_transitions(self, k=2, metric='mean', cmap='inferno', darkstyle=False)

Compute and plot the mean or median transition probabilities matrix of each group/condition.

Params:

k : int. The k-means partition of interest.

metric : str. Whether to plot the 'mean' or 'median' matrices.

cmap : str. Colormap to use in the created heatmaps.

darkstyle : bool. Whether to use a dark background for plotting.

Expand source code
def group_transitions(self,k=2,metric='mean',cmap='inferno',darkstyle=False):
    """
    Compute and plot the mean or median transition
    probabilities matrix of each group/condition.

    Params:
    -------
    k : int.
        The k-means partition of interest.

    metric : str.
        Whether to plot the 'mean' or 'median'
        matrices.

    cmap : str.
        Colormap to use in the created heatmaps.

    darkstyle : bool.
        Whether to use a dark background for
        plotting.
    """
    _check_k_input(self._K_min_,self._K_max_,k)

    data = pd.read_csv(f'{self._dynamics_}/k_{k}/transitions_probabilities.csv',sep='\t')

    mats = group_transition_matrix(
        data,
        metric=metric,
        cmap=cmap,
        darkstyle=darkstyle
        )

    return mats
def load_centroids(self, k=2)

Return the computed clusters centroids for a specific 'k' partition.

Params:

k : int. Select the partition of interest.

Returns:

centroids : pd.DataFrame with shape (n_centroids,n_rois). Contains the computed centroids for the selected k partition.

Expand source code
def load_centroids(self,k=2):
    """
    Return the computed clusters centroids
    for a specific 'k' partition.

    Params:
    -------
    k : int.
        Select the partition of interest.
    
    Returns:
    -------
    centroids : pd.DataFrame with shape (n_centroids,n_rois).
        Contains the computed centroids for
        the selected k partition.
    """
    _check_k_input(self._K_min_,self._K_max_,k)
    try:
        centroids = pd.DataFrame(self.load_model(k=k).cluster_centers_,columns=self.rois_labels)
    except:
        print("Can't find results for the selected k.")

    return centroids
def load_model(self, k=2)

Load fitted model for a specific 'k' partition. Given that each model is an instance of the KMeansLeida class, once loaded you can access all the object methods and attributes.

Params:

k : int. Select the partition of interest.

Returns:

model : KMeansLeida instance. The fitted model that was used to predict the cluster labels of each observation.

Expand source code
def load_model(self,k=2):
    """
    Load fitted model for a specific 'k' partition.
    Given that each model is an instance of the KMeansLeida
    class, once loaded you can access all the object methods
    and attributes.

    Params:
    -------
    k : int.
        Select the partition of interest.
    
    Returns:
    -------
    model : KMeansLeida instance.
        The fitted model that was used to predict
        the cluster labels of each observation.
    """
    _check_k_input(self._K_min_,self._K_max_,k)
    try:
        model = pd.read_pickle(f'{self._models_}/model_k_{k}.pkl')
        return model
    except:
        raise Exception("Can't find results for the selected k.")
def occupancies(self, k=2)

Return the computed fractional occupancy of each phase-locking state for a specific 'k' partition.

Params:

k : int. Specify the K-Means partition of interest.

Returns:

occupancies : pd.DataFrame. Contains the computed fractional occupancy of each PL state for each subject for the selected 'k' partition.

Expand source code
def occupancies(self,k=2):
    """
    Return the computed fractional occupancy of each
    phase-locking state for a specific 'k' partition.

    Params:
    -------
    k : int.
        Specify the K-Means partition of
        interest.

    Returns:
    --------
    occupancies : pd.DataFrame.
        Contains the computed fractional occupancy
        of each PL state for each subject for the
        selected 'k' partition.
    """
    _check_k_input(self._K_min_,self._K_max_,k)
    try:
        occ = pd.read_csv(f'{self._dynamics_}/k_{k}/occupancies.csv',sep='\t')
    except:
        print("Can't find results for the selected k.")
    return occ
def overlap_withyeo(self, parcellation=None, n_areas=None, k=2, state=None, darkstyle=False)

Compute the overlap between the 7 resting-state networks defined in Yeo et al. (2011) and the brain cortical regions/parcels of the phase-locking state of interest. The correlations are shown in a barplot, and a dataframe with the correlations and p-values is returned.

Params:

parcellation : str. Specify path to your parcellation .nii file. Note: the parcellation must be of 2mm resolution.

n_areas : None | int. Analyze only the first n areas from the provided parcellation. Usefull when the parcellation contains subcortical regions that must be ignored when computing the overlap with Yeo's cortical networks.

k : int. Select the partition.

state : int. Select the PL pattern or state of interest.

darkstyle : bool. Whether to use a dark theme for the plot.

Returns:

overlap : pandas.dataframe with shape (7networks,3). Contains the correlation coefficient (and p-value) between the selected phase-locking state and each of the 7 resting-state networks from Yeo (2011).

Expand source code
def overlap_withyeo(self,parcellation=None,n_areas=None,k=2,state=None,darkstyle=False):
    """
    Compute the overlap between the 7 resting-state
    networks defined in Yeo et al. (2011) and the brain
    cortical regions/parcels of the phase-locking state
    of interest. The correlations are shown in a barplot,
    and a dataframe with the correlations and p-values is
    returned.

    Params:
    --------
    parcellation : str.
        Specify path to your parcellation .nii file.
        Note: the parcellation must be of 2mm resolution.

    n_areas : None | int.
        Analyze only the first n areas from the provided
        parcellation. 
        Usefull when the parcellation contains subcortical
        regions that must be ignored when computing the overlap
        with Yeo's cortical networks.

    k : int.
        Select the partition.

    state : int.
        Select the PL pattern or state of
        interest.

    darkstyle : bool.
        Whether to use a dark theme for the plot.

    Returns:
    --------
    overlap : pandas.dataframe with shape (7networks,3).
        Contains the correlation coefficient (and p-value)
        between the selected phase-locking state and each
        of the 7 resting-state networks from Yeo (2011).
    """
    _check_k_input(self._K_min_,self._K_max_,k)
    _check_state(k,state)

    centr = {}

    for k_ in range(self._K_min_,self._K_max_+1):
        cent = self.load_centroids(k_)
        cent.insert(0,'state',[i+1 for i in range(cent.shape[0])])
        cent.insert(0,'k',k_)
        centr[f'k{k_}'] = cent
    
    centr = pd.concat(centr,ignore_index=True)

    corr,pvals = rsnets.compute_overlap(
        centr,
        parcellation=parcellation,
        n_areas=n_areas
        )

    overlap = rsnets.state_overlap(
        corr,
        pvals,
        k=k,
        state=state,
        plot=True,
        darkstyle=darkstyle
        )

    return overlap
def plot_clustering_performance(self)

Create a 2x2 panel with lineplots showing the clustering evaluation metrics for each k partition explored (Dunn score, distortion, silhouette score, and Davis-Bouldin score).

Expand source code
def plot_clustering_performance(self):
    """
    Create a 2x2 panel with lineplots showing
    the clustering evaluation metrics for each
    k partition explored (Dunn score, distortion,
    silhouette score, and Davis-Bouldin score).
    """
    performance = pd.read_csv(f'{self._clustering_}/clustering_performance.csv',sep='\t')
    plot_clustering_scores(performance)
def plot_clusters3D(self, k=2, clusters_colors=None, grid=True, alpha=0.7, dot_size=3, edgecolor=None, darkstyle=False)

Visualize the identified clusters (BOLD phase-locking states) in a 3D scatter plot, which constitutes a low-dimensional representation of the 'state space'. Method : take the eigenvectors and extract the first three principal components to reduce the dimensionality of the data to a 3D space. Each dot in the plot thus represents a single eigenvector, and is coloured according to the cluster it belongs to.

Params:

k : int. Specify the partition to plot.

clusters_colors : list (optional). Provide a list with the desired color of each cluster. If not provided, then a predefined set of colors will be used.

grid : bool. Whether to show grid or not.

alpha : float. Set transparency of dots.

dot_size : float. Select the dot size.

edge_color : None | str. Specify an edge color to use on dots.

darkstyle : bool. Whether to use a dark theme for the plot.

Expand source code
def plot_clusters3D(self,k=2,clusters_colors=None,grid=True,alpha=.7,dot_size=3,edgecolor=None,darkstyle=False):
    """
    Visualize the identified clusters (BOLD phase-locking
    states) in a 3D scatter plot, which constitutes a
    low-dimensional representation of the 'state space'. 
    Method : take the eigenvectors and extract the first
    three principal components to reduce the dimensionality
    of the data to a 3D space. Each dot in the plot thus
    represents a single eigenvector, and is coloured according
    to the cluster it belongs to.
    
    Params:
    -------
    k : int.
        Specify the partition to plot.

    clusters_colors : list (optional). 
        Provide a list with the desired color
        of each cluster. If not provided, then
        a predefined set of colors will be used.

    grid : bool. 
        Whether to show grid or not.

    alpha : float. 
        Set transparency of dots.

    dot_size : float. 
        Select the dot size.

    edge_color : None | str.
        Specify an edge color to use
        on dots.

    darkstyle : bool.
        Whether to use a dark theme for
        the plot.
    """
    X = self.eigenvectors.iloc[:,2:].values #keep array containing only the eigenvectors
    y = self.predictions[f'k_{k}'].values #keep 1D array with the labels of each eigenvector

    with plt.style.context('dark_background' if darkstyle else 'default'):
        plot_clusters3D(
            X,
            y,
            clusters_colors=clusters_colors,
            grid=grid,
            alpha=alpha,
            dot_size=dot_size,
            edgecolor=edgecolor
            )
def plot_pvalues(self, metric='occupancies', conditions=None, darkstyle=False, fill_areas=True)

Create a scatter plot showing the p-values obtained by the statistical analysis of a given 'metric' across the explored 'k' range.

Params:

metric : str. Specify the metric of interest ('occupancies','dwell_times','transitions').

conditions : None | list. Optional (Usefull only when your data contains more than two conditions). You can provide a list specifying only two conditions of interest to plot. Otherwise create a plot for each pair of conditions.

darkstyle : bool. Whether to use a dark theme for the plots.

fill_areas : bool. Select whether to fill the significance areas with color.

Expand source code
def plot_pvalues(self,metric='occupancies',conditions=None,darkstyle=False,fill_areas=True):
    """
    Create a scatter plot showing the p-values
    obtained by the statistical analysis of a given
    'metric' across the explored 'k' range.

    Params:
    -------
    metric : str. 
        Specify the metric of interest
        ('occupancies','dwell_times','transitions').

    conditions : None | list. Optional
        (Usefull only when your data contains more
        than two conditions). You can provide a list
        specifying only two conditions of interest to
        plot. Otherwise create a plot for each pair of
        conditions. 

    darkstyle : bool.
        Whether to use a dark theme for
        the plots.

    fill_areas : bool.
        Select whether to fill the significance
        areas with color.
    """
    _check_metric(metric)

    if not isinstance(fill_areas,bool) or not isinstance(darkstyle,bool):
        raise TypeError("'fill_areas' and 'darkstyle' must be True or False!")

    if conditions is not None:
        if not isinstance(conditions,list) or len(conditions)!=2:
            raise Exception("If provided, 'conditions' must be a list with two items.")
        for cond in conditions:
            if cond not in self._classes_lst_:
                raise Exception(f"'{cond}' was not founded in the data. "
                                f" Valid options are: {self._classes_lst_}")
    else:
        conditions = self._classes_lst_.copy()

    pooled_stats = self._pool_stats(metric=metric)

    for cond in combinations(conditions,2):
        pooled_stats_ = pooled_stats[
                (pooled_stats.group_1.isin(cond))
                &
                (pooled_stats.group_2.isin(cond))
                ].reset_index(drop=True)

        scatter_pvalues(pooled_stats_,metric=metric,darkstyle=darkstyle,fill_areas=fill_areas)
def plot_states_in_bold(self, subject_id, k=2, alpha=0.5, darkstyle=False)

Create plot showing the time-series of BOLD signals, highlighting the dominant phase-locking (PL) state of each time point or volume.

Params:

subject_id : str. Specify the 'id' of the subject of interest.

k : int. Select the k partition.

alpha : float. Transparency of the colors that show the dominant PL pattern of each time point.

darkstyle : bool. Whether to create the plot using a darkstyle.

Expand source code
def plot_states_in_bold(self,subject_id,k=2,alpha=.5,darkstyle=False):
    """
    Create plot showing the time-series of BOLD signals, 
    highlighting the dominant phase-locking (PL) state
    of each time point or volume.

    Params:
    -------
    subject_id : str.
        Specify the 'id' of the subject
        of interest.

    k : int.
        Select the k partition.

    alpha : float.
        Transparency of the colors that
        show the dominant PL pattern of
        each time point.

    darkstyle : bool.
        Whether to create the plot using
        a darkstyle.
    """
    _check_k_input(self._K_min_,self._K_max_,k)
    signals = load_tseries(self._data_path_)
    signals = signals[subject_id][:,1:-1] #get subject signals (and exclude 1st and last volumes)
    y = self.predictions[self.predictions.subject_id==subject_id][f'k_{k}'].values #get predictions for selected k.

    with plt.style.context('dark_background' if darkstyle else 'default'):
        states_in_bold(signals,y,alpha=alpha)
def plot_states_matrices(self, k=2, cmap='jet', darkstyle=False)

Take the controids resulting from the k-means clustering (i.e., the phase-locking states) and reconstruct the connectivity patterns in matrix format.

Params:

k : int. Specify the K partition of interest.

cmap : str. Default = 'jet'. Select the colormap to use.

darkstyle : bool. Whether to use a black background.

Expand source code
def plot_states_matrices(self,k=2,cmap='jet',darkstyle=False):
    """
    Take the controids resulting from the k-means
    clustering (i.e., the phase-locking states) and
    reconstruct the connectivity patterns in matrix
    format.

    Params:
    -------
    k : int.
        Specify the K partition of interest.
    
    cmap : str. Default = 'jet'.
        Select the colormap to use.

    darkstyle : bool.
        Whether to use a black background.
    """
    _check_k_input(self._K_min_,self._K_max_,k)

    if not isinstance(cmap,str):
        raise TypeError("'cmap' must be a string!")
    if not isinstance(darkstyle,bool):
        raise TypeError("'darkstyle' must be True or False!")

    _ = centroid2matrix(
        self.load_centroids(k).values,
        plot=True,
        cmap=cmap,
        darkstyle=darkstyle
        )
def plot_states_network(self, k=2, state='all', node_size=8, node_color='infer', linewidth=3, open=True, save=False)

Create a 3D interactive figure embedded in a .html file showing the BOLD phase-locking (PL) states as a connected network. All the ROIs/parcels that belong to the selected phase-locking state are connected between each other.

Params:

k : int. Select the partition of interest.

state : int or str. Use an integer to plot a single PL state of interest, or 'all' to plot all the PL states of the selected K partition.

node_size : int. Select the size of the nodes.

node_color : str. Select the color of the nodes. If 'infer', then the nodes participating in the PL states are colored red and the rest blue. If 'black', then all the nodes are colored in the same way.

linewidth : int. Select the size of the edges connecting the nodes.

open : bool. Whether to open the plots in web browser. If False, you can open the figures using the '.open_in_browser()' method of the returned object/s.

save : bool. Whether to save each plot in a .html file. If True, the files are saved in 'LEiDA_results/brain_plots'.

Returns:

plot/s : dict or single figure. If state='all', return a dictionary that contains the constructed plots. They can be opened or saved using '.open()' and '.save_as_html(path)', respectively. If state=int, then return a single figure.

Expand source code
def plot_states_network(self,k=2,state='all',node_size=8,node_color='infer',linewidth=3,open=True,save=False):
    """
    Create a 3D interactive figure embedded in a
    .html file showing the BOLD phase-locking (PL)
    states as a connected network. All the ROIs/parcels
    that belong to the selected phase-locking state are
    connected between each other.

    Params:
    -------
    k : int.
        Select the partition of interest.

    state : int or str.
        Use an integer to plot a single
        PL state of interest, or 'all'
        to plot all the PL states of the
        selected K partition.

    node_size : int. 
        Select the size of the nodes.

    node_color : str. 
        Select the color of the nodes. If
        'infer', then the nodes participating
        in the PL states are colored red and
        the rest blue. If 'black', then all the
        nodes are colored in the same way.

    linewidth : int. 
        Select the size of the edges
        connecting the nodes.

    open : bool. 
        Whether to open the plots in web
        browser. If False, you can open the
        figures using the '.open_in_browser()'
        method of the returned object/s.

    save : bool.
        Whether to save each plot in a
        .html file. If True, the files
        are saved in 'LEiDA_results/brain_plots'.

    Returns:
    --------
    plot/s : dict or single figure.
        If state='all', return a dictionary
        that contains the constructed plots.
        They can be opened or saved using
        '.open()' and '.save_as_html(path)',
        respectively. If state=int, then return
        a single figure.
    """
    if self.rois_coordinates_ is None:
        raise Exception("You can't create this plot because the "
                        "ROI's coordinates could't be loaded.")

    _check_k_input(self._K_min_,self._K_max_,k)
    if not isinstance(state,(int,str)):
        raise TypeError("'state' must be either 'all' or an integer "
                        "specifying the number of a particular PL state.")
    elif isinstance(state,str):
        if state!='all':
            raise ValueError("If a string is provided, 'state' must be 'all'!")
    else:
        _check_state(k,state)

    centroids = self.load_centroids(k=k).values #load centroids for the selected k

    #plotting
    plot = brain_states_network(
        centroids,
        self.rois_coordinates_,
        state=state,
        node_size=node_size,
        node_color=node_color,
        linewidth=linewidth,
        open=open
        )

    #saving figures
    if save:
        _save_html(self._results_path_,plot,k,state,plot_type='network')

    return plot
def plot_states_network_glass(self, k=2, darkstyle=False)

Create a glass brain (axial view) showing the network representation of each phase-locking (PL) state for the selected 'k' partition.

Params:

k : int. Select the partition of interest.

darkstyle : bool. Whether to use a dark theme for the plots.

Expand source code
def plot_states_network_glass(self,k=2,darkstyle=False):
    """
    Create a glass brain (axial view) showing the
    network representation of each phase-locking
    (PL) state for the selected 'k' partition.

    Params:
    -------
    k : int.
        Select the partition of interest.

    darkstyle : bool.
        Whether to use a dark theme for
        the plots.
    """
    _check_k_input(self._K_min_,self._K_max_,k)
    pl_states = self.load_centroids(k=k).values

    with plt.style.context("dark_background" if darkstyle else "default"):
        states_k_glass(pl_states,self.rois_coordinates_,darkstyle=darkstyle)
def plot_states_nodes(self, k=2, state='all', node_size=15, show_labels=True, open=True, save=False)

Create a 3D interactive figure embedded in a .html file showing the BOLD phase-locking (PL) states in anatomical MNI space. Each parcel/ROI is represented as a node. Nodes that are part of the PL pattern are coloured in red, and the rest of nodes are coloured in blue.

Params:

k : int. Select the partition of interest.

state : int or str. Use an integer to plot a single PL state of interest, or 'all' to plot all the PL states of the selected K partition.

node_size: int or float. Define the size of the nodes. Nodes that don't belong to the pattern are plotted smaller.

show_labels : bool. Whether to show each ROI label.

open : bool. Whether to open the plots in web browser. If False, you can open the figures using the '.open_in_browser()' method of the returned object/s.

save : bool. Whether to save each plot in a .html file. If True, the files are saved in 'LEiDA_results/brain_plots'.

Returns:

plot/s : dict or single figure. If state='all', return a dictionary that contains the constructed plots. They can be opened or saved using '.open()' and '.save_as_html(path)', respectively. If state=int, then return a single figure.

Expand source code
def plot_states_nodes(self,k=2,state='all',node_size=15,show_labels=True,open=True,save=False):
    """
    Create a 3D interactive figure embedded in a
    .html file showing the BOLD phase-locking (PL)
    states in anatomical MNI space. Each parcel/ROI
    is represented as a node. Nodes that are part of
    the PL pattern are coloured in red, and the rest
    of nodes are coloured in blue.

    Params:
    -------
    k : int.
        Select the partition of interest.

    state : int or str.
        Use an integer to plot a single
        PL state of interest, or 'all'
        to plot all the PL states of the
        selected K partition.

    node_size: int or float.
        Define the size of the nodes. Nodes
        that don't belong to the pattern are
        plotted smaller.

    show_labels : bool.
        Whether to show each ROI label.

    open : bool. 
        Whether to open the plots in web
        browser. If False, you can open the
        figures using the '.open_in_browser()'
        method of the returned object/s.

    save : bool.
        Whether to save each plot in a
        .html file. If True, the files
        are saved in 'LEiDA_results/brain_plots'.

    Returns:
    --------
    plot/s : dict or single figure.
        If state='all', return a dictionary
        that contains the constructed plots.
        They can be opened or saved using
        '.open()' and '.save_as_html(path)',
        respectively. If state=int, then return
        a single figure.
    """
    if self.rois_coordinates_ is None:
        raise Exception("You can't create this plot because the "
                        "ROI's coordinates could't be loaded.")

    centroids = self.load_centroids(k=k).values
    plots = brain_states_nodes(
        centroids,
        self.rois_coordinates_,
        node_size=node_size,
        state=state,
        nodes_labels=None if not show_labels else self.rois_labels,
        open=open
        )
    
    if save:
        _save_html(self._results_path_,plots,k,state,plot_type='nodes')

    return plots
def plot_states_on_surf(self, k=2, state='all', parcellation=None, discretize=True, cmap='auto', darkstyle=False, open=False, save=False)

Create a 3D interactive figure embedded in a .html file showing the BOLD phase-locking (PL) states on cortical surface. By default, all the cortical regions that belong to a given PL state or pattern are coloured in red(s), while the rest of cortical regions are coloured in blue(s). You can change the colormap throught the 'cmap' argument.

Params:

k : int. Partition of interest.

state : str or int. Whether to plot 'all' the PL states of the selected partition or a single state of interest.

parcellation : str. Path to the .nii file containing the parcellation from which the time series were extracted.

discretize : bool. Default = True. Whether to plot the raw values of the phase-locking state/centroid, or plot all the brain regions that belong to the phase-locking state with the same intensity.

cmap : str or matplotlib colormap, optional. Default = 'auto'. Colormap to use in the brain plot. If 'auto', then the brain regions that belong to the phase-locking state will be coloured in red(s), and the rest of regions in blue(s).

darkstyle : bool. Whether to use a black background.

open : bool. Whether to open the plots in web browser. If False, you can open the figures using the '.open_in_browser()' method of the returned object/s.

save : bool. Whether to save each plot in a .html file. If True, the files are saved in 'LEiDA_results/brain_plots'.

Returns:

g : SurfaceView or dictionarity of SurfaceViews.

Expand source code
def plot_states_on_surf(self,k=2,state='all',parcellation=None,discretize=True,cmap='auto',darkstyle=False,open=False,save=False):
    """
    Create a 3D interactive figure embedded in a
    .html file showing the BOLD phase-locking (PL)
    states on cortical surface. By default, all the
    cortical regions that belong to a given PL state
    or pattern are coloured in red(s), while the rest
    of cortical regions are coloured in blue(s). You
    can change the colormap throught the 'cmap' argument.

    Params:
    -------
    k : int.
        Partition of interest.

    state : str or int.
        Whether to plot 'all' the PL states of
        the selected partition or a single state
        of interest.

    parcellation : str.
        Path to the .nii file containing the
        parcellation from which the time series
        were extracted.

    discretize : bool. Default = True.
        Whether to plot the raw values of the
        phase-locking state/centroid, or plot
        all the brain regions that belong to
        the phase-locking state with the same
        intensity.

    cmap : str or matplotlib colormap, optional. Default = 'auto'.
        Colormap to use in the brain plot.
        If 'auto', then the brain regions that
        belong to the phase-locking state will
        be coloured in red(s), and the rest of
        regions in blue(s).

    darkstyle : bool.
        Whether to use a black background.

    open : bool. 
        Whether to open the plots in web
        browser. If False, you can open the
        figures using the '.open_in_browser()'
        method of the returned object/s.

    save : bool.
        Whether to save each plot in a
        .html file. If True, the files
        are saved in 'LEiDA_results/brain_plots'.

    Returns:
    --------
    g : SurfaceView or dictionarity of SurfaceViews. 
    """
    _check_k_input(self._K_min_,self._K_max_,k)

    if not isinstance(state,(int,str)):
        raise TypeError("'state' must be either 'all' or an integer specifying the number of a particular PL state")
    if isinstance(state,str):
        if state!='all':
            raise ValueError("If a string is provided, 'state' must be 'all'!")
    else:
        _check_state(k,state)

    centroids = self.load_centroids(k=k).values 
    if state!='all':
        centroids = centroids[state-1,:]
    
    g = brain_states_on_surf(
        centroids,
        parcellation=parcellation,
        black_bg=darkstyle,
        open=open,
        discretize=discretize,
        cmap=cmap
        )

    if save:
        _save_html(self._results_path_,g,k,state,plot_type='surface')

    return g
def plot_states_on_surf2(self, k=2, state=1, parcellation=None, surface='pial', hemi='right', view='lateral', darkstyle=False, save=False)

Plot a BOLD phase-locking state of interest on cortical surface mesh.

Params:

k : int. Partition of interest.

state : int. Select the PL state/pattern of interest.

parcellation : str. Path to the .nii file containing the parcellation from which the signals were extracted.

surface : str. Specify the surface type to plot the pattern on. Valid options are 'pial','infl', and 'white'.

hemi : str. Select the hemisphere to plot. Valid options are 'right', 'left', or 'both'.

view : str View of the surface that is rendered. Default='lateral'. Options = {'lateral', 'medial', 'dorsal', 'ventral', 'anterior', 'posterior'}. If 'hemi'='both', then 'dorsal' and 'lateral' views are displayed.

darkstyle : bool Whether to use a black background.

save : bool. Whether to save the created figure in local folder. If True, the files are saved in 'LEiDA_results/brain_plots', and the plot will not be displayed.

Returns:

g : matplotlib figure.

Expand source code
def plot_states_on_surf2(self,k=2,state=1,parcellation=None,surface='pial',hemi='right',view='lateral',darkstyle=False,save=False):
    """
    Plot a BOLD phase-locking state of interest 
    on cortical surface mesh. 

    Params:
    -------
    k : int.
        Partition of interest.

    state : int.
        Select the PL state/pattern of
        interest.

    parcellation : str.
        Path to the .nii file containing
        the parcellation from which the
        signals were extracted.

    surface : str.
        Specify the surface type to plot
        the pattern on. Valid options are
        'pial','infl', and 'white'.

    hemi : str.
        Select the hemisphere to plot.
        Valid options are 'right', 'left',
        or 'both'.

    view : str
        View of the surface that is rendered. 
        Default='lateral'. Options = {'lateral',
        'medial', 'dorsal', 'ventral', 'anterior',
        'posterior'}. If 'hemi'='both', then 'dorsal'
        and 'lateral' views are displayed.

    darkstyle : bool
        Whether to use a black background.

    save : bool.
        Whether to save the created figure in
        local folder. If True, the files are
        saved in 'LEiDA_results/brain_plots',
        and the plot will not be displayed.

    Returns:
    --------
    g : matplotlib figure. 
    """
    _check_k_input(self._K_min_,self._K_max_,k)
    _check_state(k,state)

    centroids = self.load_centroids(k=k).values 
    centroid = centroids[state-1,:]

    #plotting
    print("\n-Creating plot. This may take "
        "some minutes. Please wait...")
        
    with plt.style.context('dark_background' if darkstyle else 'default'):
        g = brain_states_on_surf2(
            centroid,
            parcellation=parcellation,
            hemi=hemi,
            surface=surface,
            view=view
            )
    
    if save:
        try:
            path = f'{self._results_path_}/brain_plots'
            if not os.path.exists(path): 
                os.makedirs(path)
            filename = f"{path}/K{k}_PL_state_{state}_{surface}surf_{hemi}hemi_{view if hemi!='both' else 'multiview'}.png"
            g.savefig(filename,dpi=300)
            plt.close()
            del g
            print(f"The plot was save at: {filename}")
        except:
            raise Exception("An error occured when saving the plot.")
    else:
        return g
def plot_states_pyramid(self, metric='occupancies', conditions=None, despine=True)

Create a pyramid of barplots showing the 'metric' of interest for each group, cluster (PL state), and K partition. Each barplot (which represents a particular PL state) is coloured according to its associated p-value: -black: the p-value is higher than 0.05. -red: the p-value is lower than 0.05 but higher than 0.05 / k. -green: the p-value is lower than 0.05/k but higher than 0.05 / Σ(k). -blue: the p-value is lower than 0.05 / Σ(k).

Params:

metric : str. Select the dynamical systems theory metric of interest (either 'occupancies' or 'dwell_times').

conditions : None | list. Optional. (Usefull only when your data contains more than two conditions). You can provide a list specifying only two conditions of interest to plot. Otherwise create a plot for each pair of conditions.

despine : bool. Default = True. Whether to despine top and right axes of the subplots.

Expand source code
def plot_states_pyramid(self,metric='occupancies',conditions=None,despine=True):
    """
    Create a pyramid of barplots showing the 'metric'
    of interest for each group, cluster (PL state), and
    K partition. Each barplot (which represents a particular
    PL state) is coloured according to its associated p-value:
    -black: the p-value is higher than 0.05.
    -red: the p-value is lower than 0.05 but higher than 0.05 / k.
    -green: the p-value is lower than 0.05/k but higher than 0.05 / Σ(k).
    -blue: the p-value is lower than 0.05 / Σ(k).

    Params:
    -------
    metric : str.
        Select the dynamical systems theory metric
        of interest (either 'occupancies' or 'dwell_times').

    conditions : None | list. Optional.
        (Usefull only when your data contains more
        than two conditions). You can provide a list
        specifying only two conditions of interest to
        plot. Otherwise create a plot for each pair of
        conditions. 

    despine : bool. Default = True.
        Whether to despine top and right axes of the
        subplots.
    """
    _check_metric(metric)

    if conditions is not None:
        if not isinstance(conditions,list) or len(conditions)!=2:
            raise Exception("If provided, 'conditions' must be a list with two items.")
        for cond in conditions:
            if cond not in self._classes_lst_:
                raise Exception(f"'{cond}' was not founded in the data. "
                                f" Valid options are: {self._classes_lst_}")
    else:
        conditions = self._classes_lst_.copy()

    pooled_stats = self._pool_stats(metric=metric)
    K_min = np.min(pooled_stats.k)
    K_max = np.max(pooled_stats.k)

    for cond in combinations(conditions,2):
        pooled_stats_ = pooled_stats[
                (pooled_stats.group_1.isin(cond))
                &
                (pooled_stats.group_2.isin(cond))
                ].reset_index(drop=True)

        dyn_metric = self._pool_dynamics_metric(metric=metric) 
        dyn_metric = {k:v[v.condition.isin(conditions)] for k,v in dyn_metric.items()}

        plot_pyramid(
            dyn_metric,
            pooled_stats_,
            K_min=K_min,
            K_max=K_max,
            despine=despine
            )
def plot_voronoi_cells(self, k=2)

Plot the clusters centroids in a 2D Voronoi cells space. Performs a PCA to reduce the dimensionality of the original centroid space to a 2D space.

Params:

k : int. Select the clustering solution to plot.

Note: see Vohryzek, Deco et al. (2020) p.4

Expand source code
def plot_voronoi_cells(self,k=2):
    """
    Plot the clusters centroids in a 2D Voronoi
    cells space. Performs a PCA to reduce the
    dimensionality of the original centroid space
    to a 2D space.

    Params:
    --------
    k : int.
        Select the clustering solution to plot.

    Note: see Vohryzek, Deco et al. (2020) p.4
    """
    _check_k_input(self._K_min_,self._K_max_,k)
    centroids = self.load_centroids(k=k).values 
    plot_voronoi(centroids)
def significant_states(self, metric='occupancies')

Return a dataframe containing only the statistics of the phase-locking states that are significantly different between groups.

Params:

metric : str. Metric of interest (Options: 'occupancies', 'dwell_times').

Returns:

stats : pandas.dataframe.

Expand source code
def significant_states(self,metric='occupancies'):
    """
    Return a dataframe containing only the statistics
    of the phase-locking states that are significantly
    different between groups.

    Params:
    -------
    metric : str.
        Metric of interest (Options: 'occupancies',
        'dwell_times').

    Returns:
    --------
    stats : pandas.dataframe.
    """
    _check_metric(metric)

    try:
        stats = self._pool_stats(metric=metric)
    except:
        raise Exception("The stats could't be loaded.")
    has_results = True if stats[stats.reject_null==True].shape[0]>=1 else False #check if some result was significant
    if has_results:
        return stats[stats.reject_null==True]
    else:
        print("No significant results were detected.")
        return None
def state_rois(self, k=2, state=1)

Get a list with the names of the ROIs/parcels that participates in a specific phase-locking (PL) state.

Params:

k : int. Select the partition.

state : int. Select the PL pattern or state of interest.

Returns:

rois : list. Contains the names of the ROIs that are part of the selected PL state.

Expand source code
def state_rois(self,k=2,state=1):
    """
    Get a list with the names of the ROIs/parcels that
    participates in a specific phase-locking (PL) state.

    Params:
    -------
    k : int.
        Select the partition.

    state : int.
        Select the PL pattern or state of
        interest.

    Returns:
    --------
    rois : list.
        Contains the names of the ROIs that
        are part of the selected PL state.
    """
    _check_k_input(self._K_min_,self._K_max_,k)
    _check_state(k,state)

    data_k = self.load_centroids(k=k).iloc[state-1,:].reset_index()
    data_k.columns = ['rois','value']
    rois = list(data_k[data_k.value>0]['rois'])
    return rois
def stats(self, k=2, metric='occupancies')

Retrieve the results from the statistical analysis of a 'metric' of interest ('occupancies' or 'dwell_times') for a specific 'k' partition.

Params:

k : int. Select the 'k' partition of interest.

metric : str. Select the metric to retrieve results.

Returns:

stats : pandas.dataframe. Results of the statistical analysis of each PL state.

Expand source code
def stats(self,k=2,metric='occupancies'):
    """
    Retrieve the results from the statistical analysis of 
    a 'metric' of interest ('occupancies' or 'dwell_times')
    for a specific 'k' partition.

    Params:
    -------
    k : int.
        Select the 'k' partition of interest.

    metric : str.
        Select the metric to retrieve results.

    Returns:
    --------
    stats : pandas.dataframe.
        Results of the statistical analysis
        of each PL state.
    """
    _check_k_input(self._K_min_,self._K_max_,k)
    _check_metric(metric)

    try:
        df_stats = pd.read_csv(f'{self._dynamics_}/k_{k}/{metric}_stats.csv',sep='\t')
    except:
        print("Can't find results for the selected k or metric.")
    return df_stats
def time_series(self)

Return a dictionary having the 'subject_ids' as keys, and 2D arrays (N_ROIs,N_volumes) with BOLD time series as values.

Returns:

time_series : dict.

Expand source code
def time_series(self):
    """
    Return a dictionary having the 'subject_ids'
    as keys, and 2D arrays (N_ROIs,N_volumes) with
    BOLD time series as values.

    Returns:
    --------
    time_series : dict.
    """
    return load_tseries(self._data_path_)
def transitions(self, k=2)

Return the computed transition probabilities between phase-locking states for a specific 'k' partition.

Params:

k : int. Specify the K-Means partition of interest.

Returns:

transitions : pd.DataFrame. Contains the computed transition probabilities between PL states for each subject for the selected 'k' partition.

Expand source code
def transitions(self,k=2):
    """
    Return the computed transition probabilities between
    phase-locking states for a specific 'k' partition.

    Params:
    -------
    k : int.
        Specify the K-Means partition of interest.

    Returns:
    --------
    transitions : pd.DataFrame.
        Contains the computed transition probabilities
        between PL states for each subject for the selected
        'k' partition.
    """
    _check_k_input(self._K_min_,self._K_max_,k)
    try:
        tr = pd.read_csv(f'{self._dynamics_}/k_{k}/transitions_probabilities.csv',sep='\t')
    except:
        print("Can't find results for the selected k.")
    return tr