class_docstr: Class for event segmentation of continuous fMRI data
  
  Parameters
  ----------
  n_events: int
      Number of segments to learn
  
  step_var: Callable[[int], float] : default 4 * (0.98 ** (step - 1))
      The Gaussian variance to use during fitting, as a function of the
      number of steps. Should decrease slowly over time.
  
  n_iter: int, default: 500
      Maximum number of steps to run during fitting
  
  event_chains: ndarray with length = n_events
      Array with unique value for each separate chain of events, each linked
      in the order they appear in the array
  
  split_merge: bool, default: False
      Determines whether merge/split proposals are used during fitting with
      fit(). This can improve fitting performance when events are highly
      uneven in size, but requires additional time
  
  split_merge_proposals: int, default: 1
      Number of merges and splits to consider at each step. Computation time
      scales as O(proposals^2) so this should usually be a small value
  
  Attributes
  ----------
  p_start, p_end: length n_events+1 ndarray
      initial and final prior distributions over events
  
  P: n_events+1 by n_events+1 ndarray
      HMM transition matrix
  
  ll_ : ndarray with length = number of training datasets
      Log-likelihood for training datasets over the course of training
  
  segments_:  list of (time by event) ndarrays
      Learned (soft) segmentation for training datasets
  
  event_var_ : float
      Gaussian variance at the end of learning
  
  event_pat_ : voxel by event ndarray
      Learned mean patterns for each event
class_docstr_tok: ["Class","for","event","segmentation","of","continuous","fMRI","data","Parameters","--","--","--","--","--","n_events",":","int","Number","of","segments","to","learn","step_var",":","Callable","[","[","int","]",",","float","]",":","default","4","*","(","0.98","*","*","(","step","-","1",")",")","The","Gaussian","variance","to","use","during","fitting",",","as","a","function","of","the","number","of","steps",".","Should","decrease","slowly","over","time",".","n_iter",":","int",",","default",":","500","Maximum","number","of","steps","to","run","during","fitting","event_chains",":","ndarray","with","length","=","n_events","Array","with","unique","value","for","each","separate","chain","of","events",",","each","linked","in","the","order","they","appear","in","the","array","split_merge",":","bool",",","default",":","False","Determines","whether","merge\/split","proposals","are","used","during","fitting","with","fit","(",")",".","This","can","improve","fitting","performance","when","events","are","highly","uneven","in","size",",","but","requires","additional","time","split_merge_proposals",":","int",",","default",":","1","Number","of","merges","and","splits","to","consider","at","each","step",".","Computation","time","scales","as","O","(","proposals^2",")","so","this","should","usually","be","a","small","value","Attributes","--","--","--","--","--","p_start",",","p_end",":","length","n_events+1","ndarray","initial","and","final","prior","distributions","over","events","P",":","n_events+1","by","n_events+1","ndarray","HMM","transition","matrix","ll_",":","ndarray","with","length","=","number","of","training","datasets","Log-likelihood","for","training","datasets","over","the","course","of","training","segments_",":","list","of","(","time","by","event",")","ndarrays","Learned","(","soft",")","segmentation","for","training","datasets","event_var_",":","float","Gaussian","variance","at","the","end","of","learning","event_pat_",":","voxel","by","event","ndarray","Learned","mean","patterns","for","each","event"]
docstr: Attempt to improve log-likelihood with a merge/split
  
  The simulated annealing used in fit() is susceptible to getting
  stuck in a local minimum if there are some very short events. This
  function attempts to find
  a) pairs of neighboring events that are highly similar, to merge
  b) events that can be split into two dissimilar events
  It then tests to see whether simultaneously merging one of the
  pairs from (a) and splitting one of the events from (b) can improve
  the log-likelihood. The number of (a)/(b) pairs tested is determined
  by the split_merge_proposals class attribute.
  
  Parameters
  ----------
  X: list of voxel by time ndarrays
      fMRI datasets being fit
  
  log_gamma : list of time by event ndarrays
      Log probability of each timepoint belonging to each event,
      for each dataset
  
  iteration_var : float
      Current variance in simulated annealing
  
  curr_ll: float
      Log-likelihood of current model
  
  Returns
  -------
  return_ll : ndarray with length equal to length of X
      Log-likelihood after merge/split (same as curr_ll if no
      merge/split improved curr_ll)
  
  return_lg : list of time by event ndarrays
      Log probability of each timepoint belonging to each event,
      for each dataset (same as log_gamma if no merge/split
      improved curr_ll)
  
  return_mp : voxel by event ndarray
      Mean patterns of events (after possible merge/split)
docstr_tok: ["Attempt","to","improve","log-likelihood","with","a","merge\/split","The","simulated","annealing","used","in","fit","(",")","is","susceptible","to","getting","stuck","in","a","local","minimum","if","there","are","some","very","short","events",".","This","function","attempts","to","find","a",")","pairs","of","neighboring","events","that","are","highly","similar",",","to","merge","b",")","events","that","can","be","split","into","two","dissimilar","events","It","then","tests","to","see","whether","simultaneously","merging","one","of","the","pairs","from","(","a",")","and","splitting","one","of","the","events","from","(","b",")","can","improve","the","log-likelihood",".","The","number","of","(","a",")","\/","(","b",")","pairs","tested","is","determined","by","the","split_merge_proposals","class","attribute",".","Parameters","--","--","--","--","--","X",":","list","of","voxel","by","time","ndarrays","fMRI","datasets","being","fit","log_gamma",":","list","of","time","by","event","ndarrays","Log","probability","of","each","timepoint","belonging","to","each","event",",","for","each","dataset","iteration_var",":","float","Current","variance","in","simulated","annealing","curr_ll",":","float","Log-likelihood","of","current","model","Returns","--","--","--","-","return_ll",":","ndarray","with","length","equal","to","length","of","X","Log-likelihood","after","merge\/split","(","same","as","curr_ll","if","no","merge\/split","improved","curr_ll",")","return_lg",":","list","of","time","by","event","ndarrays","Log","probability","of","each","timepoint","belonging","to","each","event",",","for","each","dataset","(","same","as","log_gamma","if","no","merge\/split","improved","curr_ll",")","return_mp",":","voxel","by","event","ndarray","Mean","patterns","of","events","(","after","possible","merge\/split",")"]
code: def _split_merge(self, X, log_gamma, iteration_var, curr_ll):
      """Attempt to improve log-likelihood with a merge/split
  
      The simulated annealing used in fit() is susceptible to getting
      stuck in a local minimum if there are some very short events. This
      function attempts to find
      a) pairs of neighboring events that are highly similar, to merge
      b) events that can be split into two dissimilar events
      It then tests to see whether simultaneously merging one of the
      pairs from (a) and splitting one of the events from (b) can improve
      the log-likelihood. The number of (a)/(b) pairs tested is determined
      by the split_merge_proposals class attribute.
  
      Parameters
      ----------
      X: list of voxel by time ndarrays
          fMRI datasets being fit
  
      log_gamma : list of time by event ndarrays
          Log probability of each timepoint belonging to each event,
          for each dataset
  
      iteration_var : float
          Current variance in simulated annealing
  
      curr_ll: float
          Log-likelihood of current model
  
      Returns
      -------
      return_ll : ndarray with length equal to length of X
          Log-likelihood after merge/split (same as curr_ll if no
          merge/split improved curr_ll)
  
      return_lg : list of time by event ndarrays
          Log probability of each timepoint belonging to each event,
          for each dataset (same as log_gamma if no merge/split
          improved curr_ll)
  
      return_mp : voxel by event ndarray
          Mean patterns of events (after possible merge/split)
      """
  
      # Compute current probabilities and mean patterns
      n_train = len(X)
      n_dim = X[0].shape[0]
  
      seg_prob = [
          np.exp(lg) / np.sum(np.exp(lg), axis=0) for lg in log_gamma
      ]
      mean_pat = np.empty((n_train, n_dim, self.n_events))
      for i in range(n_train):
          mean_pat[i, :, :] = X[i].dot(seg_prob[i])
      mean_pat = np.mean(mean_pat, axis=0)
  
      # For each event, merge its probability distribution
      # with the next event, and also split its probability
      # distribution at its median into two separate events.
      # Use these new event probability distributions to compute
      # merged and split event patterns.
      merge_pat = np.empty((n_train, n_dim, self.n_events))
      split_pat = np.empty((n_train, n_dim, 2 * self.n_events))
      for i, sp in enumerate(seg_prob):  # Iterate over datasets
          m_evprob = np.zeros((sp.shape[0], sp.shape[1]))
          s_evprob = np.zeros((sp.shape[0], 2 * sp.shape[1]))
          cs = np.cumsum(sp, axis=0)
          for e in range(sp.shape[1]):
              # Split distribution at midpoint and normalize each half
              mid = np.where(cs[:, e] >= 0.5)[0][0]
              cs_first = cs[mid, e] - sp[mid, e]
              cs_second = 1 - cs_first
              s_evprob[:mid, 2 * e] = sp[:mid, e] / cs_first
              s_evprob[mid:, 2 * e + 1] = sp[mid:, e] / cs_second
  
              # Merge distribution with next event distribution
              m_evprob[:, e] = sp[:, e : (e + 2)].mean(1)
  
          # Weight data by distribution to get event patterns
          merge_pat[i, :, :] = X[i].dot(m_evprob)
          split_pat[i, :, :] = X[i].dot(s_evprob)
  
      # Average across datasets
      merge_pat = np.mean(merge_pat, axis=0)
      split_pat = np.mean(split_pat, axis=0)
  
      # Correlate the current event patterns with the split and
      # merged patterns
      merge_corr = np.zeros(self.n_events)
      split_corr = np.zeros(self.n_events)
      for e in range(self.n_events):
          split_corr[e] = np.corrcoef(
              mean_pat[:, e],
              split_pat[:, (2 * e) : (2 * e + 2)],
              rowvar=False,
          )[0, 1:3].max()
          merge_corr[e] = np.corrcoef(
              merge_pat[:, e], mean_pat[:, e : (e + 2)], rowvar=False
          )[0, 1:3].min()
      merge_corr = merge_corr[:-1]
  
      # Find best merge/split candidates
      # A high value of merge_corr indicates that a pair of events are
      # very similar to their merged pattern, and are good candidates for
      # being merged.
      # A low value of split_corr indicates that an event's pattern is
      # very dissimilar from the patterns in its first and second half,
      # and is a good candidate for being split.
      best_merge = np.flipud(np.argsort(merge_corr))
      best_merge = best_merge[: self.split_merge_proposals]
      best_split = np.argsort(split_corr)
      best_split = best_split[: self.split_merge_proposals]
  
      # For every pair of merge/split candidates, attempt the merge/split
      # and measure the log-likelihood. If any are better than curr_ll,
      # accept this best merge/split
      mean_pat_last = mean_pat.copy()
      return_ll = curr_ll
      return_lg = copy.deepcopy(log_gamma)
      return_mp = mean_pat.copy()
      for m_e, s_e in itertools.product(best_merge, best_split):
          if m_e == s_e or m_e + 1 == s_e:
              # Don't attempt to merge/split same event
              continue
  
          # Construct new set of patterns with merge/split
          mean_pat_ms = np.delete(mean_pat_last, s_e, axis=1)
          mean_pat_ms = np.insert(
              mean_pat_ms,
              [s_e, s_e],
              split_pat[:, (2 * s_e) : (2 * s_e + 2)],
              axis=1,
          )
          mean_pat_ms = np.delete(
              mean_pat_ms,
              [m_e + (s_e < m_e), m_e + (s_e < m_e) + 1],
              axis=1,
          )
          mean_pat_ms = np.insert(
              mean_pat_ms, m_e + (s_e < m_e), merge_pat[:, m_e], axis=1
          )
  
          # Measure log-likelihood with these new patterns
          ll_ms = np.zeros(n_train)
          log_gamma_ms = list()
          for i in range(n_train):
              logprob = self._logprob_obs(
                  X[i], mean_pat_ms, iteration_var
              )
              lg, ll_ms[i] = self._forward_backward(logprob)
              log_gamma_ms.append(lg)
  
          # If better than best ll so far, save to return to fit()
          if ll_ms.mean() > return_ll:
              return_mp = mean_pat_ms.copy()
              return_ll = ll_ms
              for i in range(n_train):
                  return_lg[i] = log_gamma_ms[i].copy()
              logger.debug(
                  "Identified merge %d,%d and split %d",
                  m_e,
                  m_e + 1,
                  s_e,
              )
  
      return return_ll, return_lg, return_mp
  
imports: import numpy
  from scipy import stats
  import logging
  import copy
  from sklearn.base import BaseEstimator
  from sklearn.utils.validation import check_is_fitted, check_array
  from sklearn.exceptions import NotFittedError
  import itertools
  from .None import _utils
label_desc: Use image node_id 13 for calling the EventSegment obj's underlying member method code with example usage: obj._split_merge(X, log_gamma, iteration_var, curr_ll) and returns: return_ll,  return_lg,  return_mp