def add_dct_basis(self, duration=180, drop=0): """Adds unit scaled cosine basis functions to Design_Matrix columns, based on spm-style discrete cosine transform for use in high-pass filtering. Does not add intercept/constant. Care is recommended if using this along with `.add_poly()`, as some columns will be highly-correlated. Args: duration (int): length of filter in seconds drop (int): index of which early/slow bases to drop if any; will always drop constant (i.e. intercept) like SPM. Unlike SPM, retains first basis (i.e. linear/sigmoidal). Will cumulatively drop bases up to and inclusive of index provided (e.g. 2, drops bases 1 and 2); default None """ if self.sampling_freq is None: raise ValueError("Design_Matrix has no sampling_freq set!") if self.polys: if any([ elem.count('_') == 2 and 'cosine' in elem for elem in self.polys ]): raise AmbiguityError( "It appears that this Design Matrix contains cosine bases that were kept seperate from a previous append operation. This makes it ambiguous for adding polynomials terms. Try calling .add_dct_basis() on each separate Design Matrix before appending them instead." ) basis_mat = make_cosine_basis(self.shape[0], 1. / self.sampling_freq, duration, drop=drop) basis_frame = Design_Matrix( basis_mat, sampling_freq=self.sampling_freq, columns=[str(elem) for elem in range(basis_mat.shape[1])]) basis_frame.columns = [ 'cosine_' + str(i + 1) for i in range(basis_frame.shape[1]) ] if self.polys: # Only add those we don't already have basis_to_add = [ b for b in basis_frame.columns if b not in self.polys ] else: basis_to_add = list(basis_frame.columns) if not basis_to_add: print("All basis functions already exist...skipping") return self else: if len(basis_to_add) != len(basis_frame.columns): print("Some basis functions already exist...skipping") basis_frame = basis_frame[basis_to_add] out = self.append(basis_frame, axis=1) new_polys = out.polys + list(basis_frame.columns) out.polys = new_polys return out
def add_poly(self, order=0, include_lower=True): """Add nth order Legendre polynomial terms as columns to design matrix. Good for adding constant/intercept to model (order = 0) and accounting for slow-frequency nuisance artifacts e.g. linear, quadratic, etc drifts. Care is recommended when using this with `.add_dct_basis()` as some columns will be highly correlated. Args: order (int): what order terms to add; 0 = constant/intercept (default), 1 = linear, 2 = quadratic, etc include_lower: (bool) whether to add lower order terms if order > 0 """ if order < 0: raise ValueError("Order must be 0 or greater") if self.polys and any(elem.count("_") == 2 for elem in self.polys): raise AmbiguityError( "It appears that this Design Matrix contains polynomial terms that were kept seperate from a previous append operation. This makes it ambiguous for adding polynomials terms. Try calling .add_poly() on each separate Design Matrix before appending them instead." ) polyDict = {} # Normal/canonical legendre polynomials on the range -1,1 but with size defined by number of observations; keeps all polynomials on similar scales (i.e. big polys don't blow up) and betas are better behaved norm_order = np.linspace(-1, 1, self.shape[0]) if "poly_" + str(order) in self.polys: print( "Design Matrix already has {}th order polynomial...skipping".format( order ) ) return self if include_lower: for i in range(order + 1): if "poly_" + str(i) in self.polys: print( "Design Matrix already has {}th order polynomial...skipping".format( i ) ) else: polyDict["poly_" + str(i)] = legendre(i)(norm_order) else: polyDict["poly_" + str(order)] = legendre(order)(norm_order) toAdd = Design_Matrix(polyDict, sampling_freq=self.sampling_freq) out = self.append(toAdd, axis=1) if out.polys: new_polys = out.polys + list(polyDict.keys()) out.polys = new_polys else: out.polys = list(polyDict.keys()) return out