From 09316cdc7fc0358603bc9380daead5ffa3ad49f9 Mon Sep 17 00:00:00 2001 From: ejolly Date: Sun, 25 Mar 2018 15:28:05 -0400 Subject: [PATCH 01/15] Wipe fork sphinx gallery backrefs so we can grab from upstream. Former-commit-id: 7b9d100a15295aaa585699dc36887e2d31474705 --- docs/backreferences/nltools.analysis.Roc.calculate.examples | 0 docs/backreferences/nltools.analysis.Roc.examples | 0 docs/backreferences/nltools.analysis.Roc.plot.examples | 0 docs/backreferences/nltools.analysis.Roc.summary.examples | 0 .../nltools.cross_validation.KFoldStratified.examples | 0 .../nltools.cross_validation.KFoldStratified.split.examples | 0 docs/backreferences/nltools.cross_validation.examples | 0 docs/backreferences/nltools.cross_validation.set_cv.examples | 0 docs/backreferences/nltools.data.Adjacency.append.examples | 0 docs/backreferences/nltools.data.Adjacency.bootstrap.examples | 0 docs/backreferences/nltools.data.Adjacency.copy.examples | 0 docs/backreferences/nltools.data.Adjacency.distance.examples | 0 docs/backreferences/nltools.data.Adjacency.examples | 0 docs/backreferences/nltools.data.Adjacency.isempty.examples | 0 docs/backreferences/nltools.data.Adjacency.mean.examples | 0 docs/backreferences/nltools.data.Adjacency.plot.examples | 0 .../nltools.data.Adjacency.plot_label_distance.examples | 0 .../nltools.data.Adjacency.plot_silhouette.examples | 0 docs/backreferences/nltools.data.Adjacency.shape.examples | 0 docs/backreferences/nltools.data.Adjacency.similarity.examples | 0 docs/backreferences/nltools.data.Adjacency.square_shape.examples | 0 docs/backreferences/nltools.data.Adjacency.squareform.examples | 0 .../nltools.data.Adjacency.stats_label_distance.examples | 0 docs/backreferences/nltools.data.Adjacency.std.examples | 0 docs/backreferences/nltools.data.Adjacency.threshold.examples | 0 docs/backreferences/nltools.data.Adjacency.to_graph.examples | 0 docs/backreferences/nltools.data.Adjacency.ttest.examples | 0 docs/backreferences/nltools.data.Adjacency.write.examples | 0 docs/backreferences/nltools.data.Brain_Data.aggregate.examples | 0 docs/backreferences/nltools.data.Brain_Data.align.examples | 0 docs/backreferences/nltools.data.Brain_Data.append.examples | 0 docs/backreferences/nltools.data.Brain_Data.apply_mask.examples | 0 docs/backreferences/nltools.data.Brain_Data.astype.examples | 0 docs/backreferences/nltools.data.Brain_Data.bootstrap.examples | 0 docs/backreferences/nltools.data.Brain_Data.copy.examples | 0 docs/backreferences/nltools.data.Brain_Data.decompose.examples | 0 docs/backreferences/nltools.data.Brain_Data.detrend.examples | 0 docs/backreferences/nltools.data.Brain_Data.distance.examples | 0 docs/backreferences/nltools.data.Brain_Data.dtype.examples | 0 docs/backreferences/nltools.data.Brain_Data.empty.examples | 0 docs/backreferences/nltools.data.Brain_Data.examples | 0 docs/backreferences/nltools.data.Brain_Data.extract_roi.examples | 0 docs/backreferences/nltools.data.Brain_Data.filter.examples | 0 docs/backreferences/nltools.data.Brain_Data.groupby.examples | 0 docs/backreferences/nltools.data.Brain_Data.icc.examples | 0 docs/backreferences/nltools.data.Brain_Data.isempty.examples | 0 docs/backreferences/nltools.data.Brain_Data.mean.examples | 0 .../nltools.data.Brain_Data.multivariate_similarity.examples | 0 docs/backreferences/nltools.data.Brain_Data.plot.examples | 0 docs/backreferences/nltools.data.Brain_Data.predict.examples | 0 docs/backreferences/nltools.data.Brain_Data.r_to_z.examples | 0 docs/backreferences/nltools.data.Brain_Data.regions.examples | 0 docs/backreferences/nltools.data.Brain_Data.regress.examples | 0 docs/backreferences/nltools.data.Brain_Data.shape.examples | 0 docs/backreferences/nltools.data.Brain_Data.similarity.examples | 0 docs/backreferences/nltools.data.Brain_Data.standardize.examples | 0 docs/backreferences/nltools.data.Brain_Data.std.examples | 0 docs/backreferences/nltools.data.Brain_Data.sum.examples | 0 docs/backreferences/nltools.data.Brain_Data.threshold.examples | 0 docs/backreferences/nltools.data.Brain_Data.to_nifti.examples | 0 .../nltools.data.Brain_Data.transform_pairwise.examples | 0 docs/backreferences/nltools.data.Brain_Data.ttest.examples | 0 .../nltools.data.Brain_Data.upload_neurovault.examples | 0 docs/backreferences/nltools.data.Brain_Data.write.examples | 0 .../nltools.data.Design_Matrix.add_dct_basis.examples | 0 docs/backreferences/nltools.data.Design_Matrix.addpoly.examples | 0 docs/backreferences/nltools.data.Design_Matrix.append.examples | 0 docs/backreferences/nltools.data.Design_Matrix.convolve.examples | 0 .../backreferences/nltools.data.Design_Matrix.downsample.examples | 0 docs/backreferences/nltools.data.Design_Matrix.examples | 0 docs/backreferences/nltools.data.Design_Matrix.heatmap.examples | 0 docs/backreferences/nltools.data.Design_Matrix.horzcat.examples | 0 docs/backreferences/nltools.data.Design_Matrix.info.examples | 0 docs/backreferences/nltools.data.Design_Matrix.upsample.examples | 0 docs/backreferences/nltools.data.Design_Matrix.vertcat.examples | 0 docs/backreferences/nltools.data.Design_Matrix.vif.examples | 0 docs/backreferences/nltools.data.Design_Matrix.zscore.examples | 0 docs/backreferences/nltools.data.Groupby.apply.examples | 0 docs/backreferences/nltools.data.Groupby.combine.examples | 0 docs/backreferences/nltools.data.Groupby.examples | 0 docs/backreferences/nltools.data.Groupby.split.examples | 0 docs/backreferences/nltools.datasets.download_collection.examples | 0 docs/backreferences/nltools.datasets.download_nifti.examples | 0 docs/backreferences/nltools.datasets.examples | 0 .../nltools.datasets.fetch_emotion_ratings.examples | 0 docs/backreferences/nltools.datasets.fetch_pain.examples | 0 .../nltools.datasets.get_collection_image_metadata.examples | 0 docs/backreferences/nltools.file_reader.examples | 0 docs/backreferences/nltools.file_reader.onsets_to_dm.examples | 0 docs/backreferences/nltools.mask.collapse_mask.examples | 0 docs/backreferences/nltools.mask.create_sphere.examples | 0 docs/backreferences/nltools.mask.examples | 0 docs/backreferences/nltools.mask.expand_mask.examples | 0 .../nltools.plotting.dist_from_hyperplane_plot.examples | 0 docs/backreferences/nltools.plotting.examples | 0 docs/backreferences/nltools.plotting.iBrainViewer.examples | 0 docs/backreferences/nltools.plotting.plotBrain.examples | 0 docs/backreferences/nltools.plotting.plotTBrain.examples | 0 .../nltools.plotting.plot_between_label_distance.examples | 0 .../nltools.plotting.plot_mean_label_distance.examples | 0 docs/backreferences/nltools.plotting.plot_silhouette.examples | 0 .../nltools.plotting.plot_stacked_adjacency.examples | 0 docs/backreferences/nltools.plotting.probability_plot.examples | 0 docs/backreferences/nltools.plotting.roc_plot.examples | 0 docs/backreferences/nltools.plotting.scatterplot.examples | 0 .../nltools.simulator.Simulator.create_cov_data.examples | 0 .../nltools.simulator.Simulator.create_data.examples | 0 .../nltools.simulator.Simulator.create_ncov_data.examples | 0 docs/backreferences/nltools.simulator.Simulator.examples | 0 docs/backreferences/nltools.simulator.Simulator.gaussian.examples | 0 .../backreferences/nltools.simulator.Simulator.n_spheres.examples | 0 .../nltools.simulator.Simulator.normal_noise.examples | 0 docs/backreferences/nltools.simulator.Simulator.sphere.examples | 0 docs/backreferences/nltools.simulator.Simulator.to_nifti.examples | 0 docs/backreferences/nltools.simulator.examples | 0 docs/backreferences/nltools.stats.align.examples | 0 docs/backreferences/nltools.stats.calc_bpm.examples | 0 .../backreferences/nltools.stats.correlation_permutation.examples | 0 docs/backreferences/nltools.stats.downsample.examples | 0 docs/backreferences/nltools.stats.examples | 0 docs/backreferences/nltools.stats.fdr.examples | 0 docs/backreferences/nltools.stats.fisher_r_to_z.examples | 0 docs/backreferences/nltools.stats.holm_bonf.examples | 0 docs/backreferences/nltools.stats.make_cosine_basis.examples | 0 docs/backreferences/nltools.stats.multi_threshold.examples | 0 docs/backreferences/nltools.stats.one_sample_permutation.examples | 0 docs/backreferences/nltools.stats.pearson.examples | 0 docs/backreferences/nltools.stats.procrustes.examples | 0 docs/backreferences/nltools.stats.summarize_bootstrap.examples | 0 docs/backreferences/nltools.stats.threshold.examples | 0 docs/backreferences/nltools.stats.trim.examples | 0 docs/backreferences/nltools.stats.two_sample_permutation.examples | 0 docs/backreferences/nltools.stats.upsample.examples | 0 docs/backreferences/nltools.stats.winsorize.examples | 0 docs/backreferences/nltools.stats.zscore.examples | 0 docs/backreferences/nltools.utils.concatenate.examples | 0 docs/backreferences/nltools.utils.examples | 0 docs/backreferences/nltools.utils.get_anatomical.examples | 0 docs/backreferences/nltools.utils.get_resource_path.examples | 0 docs/backreferences/nltools.utils.glover_hrf.examples | 0 docs/backreferences/nltools.utils.glover_time_derivative.examples | 0 docs/backreferences/nltools.utils.set_algorithm.examples | 0 .../nltools.utils.set_decomposition_algorithm.examples | 0 .../nltools.utils.spm_dispersion_derivative.examples | 0 docs/backreferences/nltools.utils.spm_hrf.examples | 0 docs/backreferences/nltools.utils.spm_time_derivative.examples | 0 146 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/backreferences/nltools.analysis.Roc.calculate.examples delete mode 100644 docs/backreferences/nltools.analysis.Roc.examples delete mode 100644 docs/backreferences/nltools.analysis.Roc.plot.examples delete mode 100644 docs/backreferences/nltools.analysis.Roc.summary.examples delete mode 100644 docs/backreferences/nltools.cross_validation.KFoldStratified.examples delete mode 100644 docs/backreferences/nltools.cross_validation.KFoldStratified.split.examples delete mode 100644 docs/backreferences/nltools.cross_validation.examples delete mode 100644 docs/backreferences/nltools.cross_validation.set_cv.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.append.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.bootstrap.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.copy.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.distance.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.isempty.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.mean.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.plot.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.plot_label_distance.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.plot_silhouette.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.shape.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.similarity.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.square_shape.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.squareform.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.stats_label_distance.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.std.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.threshold.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.to_graph.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.ttest.examples delete mode 100644 docs/backreferences/nltools.data.Adjacency.write.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.aggregate.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.align.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.append.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.apply_mask.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.astype.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.bootstrap.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.copy.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.decompose.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.detrend.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.distance.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.dtype.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.empty.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.extract_roi.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.filter.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.groupby.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.icc.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.isempty.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.mean.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.multivariate_similarity.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.plot.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.predict.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.r_to_z.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.regions.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.regress.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.shape.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.similarity.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.standardize.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.std.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.sum.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.threshold.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.to_nifti.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.transform_pairwise.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.ttest.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.upload_neurovault.examples delete mode 100644 docs/backreferences/nltools.data.Brain_Data.write.examples delete mode 100644 docs/backreferences/nltools.data.Design_Matrix.add_dct_basis.examples delete mode 100644 docs/backreferences/nltools.data.Design_Matrix.addpoly.examples delete mode 100644 docs/backreferences/nltools.data.Design_Matrix.append.examples delete mode 100644 docs/backreferences/nltools.data.Design_Matrix.convolve.examples delete mode 100644 docs/backreferences/nltools.data.Design_Matrix.downsample.examples delete mode 100644 docs/backreferences/nltools.data.Design_Matrix.examples delete mode 100644 docs/backreferences/nltools.data.Design_Matrix.heatmap.examples delete mode 100644 docs/backreferences/nltools.data.Design_Matrix.horzcat.examples delete mode 100644 docs/backreferences/nltools.data.Design_Matrix.info.examples delete mode 100644 docs/backreferences/nltools.data.Design_Matrix.upsample.examples delete mode 100644 docs/backreferences/nltools.data.Design_Matrix.vertcat.examples delete mode 100644 docs/backreferences/nltools.data.Design_Matrix.vif.examples delete mode 100644 docs/backreferences/nltools.data.Design_Matrix.zscore.examples delete mode 100644 docs/backreferences/nltools.data.Groupby.apply.examples delete mode 100644 docs/backreferences/nltools.data.Groupby.combine.examples delete mode 100644 docs/backreferences/nltools.data.Groupby.examples delete mode 100644 docs/backreferences/nltools.data.Groupby.split.examples delete mode 100644 docs/backreferences/nltools.datasets.download_collection.examples delete mode 100644 docs/backreferences/nltools.datasets.download_nifti.examples delete mode 100644 docs/backreferences/nltools.datasets.examples delete mode 100644 docs/backreferences/nltools.datasets.fetch_emotion_ratings.examples delete mode 100644 docs/backreferences/nltools.datasets.fetch_pain.examples delete mode 100644 docs/backreferences/nltools.datasets.get_collection_image_metadata.examples delete mode 100644 docs/backreferences/nltools.file_reader.examples delete mode 100644 docs/backreferences/nltools.file_reader.onsets_to_dm.examples delete mode 100644 docs/backreferences/nltools.mask.collapse_mask.examples delete mode 100644 docs/backreferences/nltools.mask.create_sphere.examples delete mode 100644 docs/backreferences/nltools.mask.examples delete mode 100644 docs/backreferences/nltools.mask.expand_mask.examples delete mode 100644 docs/backreferences/nltools.plotting.dist_from_hyperplane_plot.examples delete mode 100644 docs/backreferences/nltools.plotting.examples delete mode 100644 docs/backreferences/nltools.plotting.iBrainViewer.examples delete mode 100644 docs/backreferences/nltools.plotting.plotBrain.examples delete mode 100644 docs/backreferences/nltools.plotting.plotTBrain.examples delete mode 100644 docs/backreferences/nltools.plotting.plot_between_label_distance.examples delete mode 100644 docs/backreferences/nltools.plotting.plot_mean_label_distance.examples delete mode 100644 docs/backreferences/nltools.plotting.plot_silhouette.examples delete mode 100644 docs/backreferences/nltools.plotting.plot_stacked_adjacency.examples delete mode 100644 docs/backreferences/nltools.plotting.probability_plot.examples delete mode 100644 docs/backreferences/nltools.plotting.roc_plot.examples delete mode 100644 docs/backreferences/nltools.plotting.scatterplot.examples delete mode 100644 docs/backreferences/nltools.simulator.Simulator.create_cov_data.examples delete mode 100644 docs/backreferences/nltools.simulator.Simulator.create_data.examples delete mode 100644 docs/backreferences/nltools.simulator.Simulator.create_ncov_data.examples delete mode 100644 docs/backreferences/nltools.simulator.Simulator.examples delete mode 100644 docs/backreferences/nltools.simulator.Simulator.gaussian.examples delete mode 100644 docs/backreferences/nltools.simulator.Simulator.n_spheres.examples delete mode 100644 docs/backreferences/nltools.simulator.Simulator.normal_noise.examples delete mode 100644 docs/backreferences/nltools.simulator.Simulator.sphere.examples delete mode 100644 docs/backreferences/nltools.simulator.Simulator.to_nifti.examples delete mode 100644 docs/backreferences/nltools.simulator.examples delete mode 100644 docs/backreferences/nltools.stats.align.examples delete mode 100644 docs/backreferences/nltools.stats.calc_bpm.examples delete mode 100644 docs/backreferences/nltools.stats.correlation_permutation.examples delete mode 100644 docs/backreferences/nltools.stats.downsample.examples delete mode 100644 docs/backreferences/nltools.stats.examples delete mode 100644 docs/backreferences/nltools.stats.fdr.examples delete mode 100644 docs/backreferences/nltools.stats.fisher_r_to_z.examples delete mode 100644 docs/backreferences/nltools.stats.holm_bonf.examples delete mode 100644 docs/backreferences/nltools.stats.make_cosine_basis.examples delete mode 100644 docs/backreferences/nltools.stats.multi_threshold.examples delete mode 100644 docs/backreferences/nltools.stats.one_sample_permutation.examples delete mode 100644 docs/backreferences/nltools.stats.pearson.examples delete mode 100644 docs/backreferences/nltools.stats.procrustes.examples delete mode 100644 docs/backreferences/nltools.stats.summarize_bootstrap.examples delete mode 100644 docs/backreferences/nltools.stats.threshold.examples delete mode 100644 docs/backreferences/nltools.stats.trim.examples delete mode 100644 docs/backreferences/nltools.stats.two_sample_permutation.examples delete mode 100644 docs/backreferences/nltools.stats.upsample.examples delete mode 100644 docs/backreferences/nltools.stats.winsorize.examples delete mode 100644 docs/backreferences/nltools.stats.zscore.examples delete mode 100644 docs/backreferences/nltools.utils.concatenate.examples delete mode 100644 docs/backreferences/nltools.utils.examples delete mode 100644 docs/backreferences/nltools.utils.get_anatomical.examples delete mode 100644 docs/backreferences/nltools.utils.get_resource_path.examples delete mode 100644 docs/backreferences/nltools.utils.glover_hrf.examples delete mode 100644 docs/backreferences/nltools.utils.glover_time_derivative.examples delete mode 100644 docs/backreferences/nltools.utils.set_algorithm.examples delete mode 100644 docs/backreferences/nltools.utils.set_decomposition_algorithm.examples delete mode 100644 docs/backreferences/nltools.utils.spm_dispersion_derivative.examples delete mode 100644 docs/backreferences/nltools.utils.spm_hrf.examples delete mode 100644 docs/backreferences/nltools.utils.spm_time_derivative.examples diff --git a/docs/backreferences/nltools.analysis.Roc.calculate.examples b/docs/backreferences/nltools.analysis.Roc.calculate.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.analysis.Roc.examples b/docs/backreferences/nltools.analysis.Roc.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.analysis.Roc.plot.examples b/docs/backreferences/nltools.analysis.Roc.plot.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.analysis.Roc.summary.examples b/docs/backreferences/nltools.analysis.Roc.summary.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.cross_validation.KFoldStratified.examples b/docs/backreferences/nltools.cross_validation.KFoldStratified.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.cross_validation.KFoldStratified.split.examples b/docs/backreferences/nltools.cross_validation.KFoldStratified.split.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.cross_validation.examples b/docs/backreferences/nltools.cross_validation.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.cross_validation.set_cv.examples b/docs/backreferences/nltools.cross_validation.set_cv.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.append.examples b/docs/backreferences/nltools.data.Adjacency.append.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.bootstrap.examples b/docs/backreferences/nltools.data.Adjacency.bootstrap.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.copy.examples b/docs/backreferences/nltools.data.Adjacency.copy.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.distance.examples b/docs/backreferences/nltools.data.Adjacency.distance.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.examples b/docs/backreferences/nltools.data.Adjacency.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.isempty.examples b/docs/backreferences/nltools.data.Adjacency.isempty.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.mean.examples b/docs/backreferences/nltools.data.Adjacency.mean.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.plot.examples b/docs/backreferences/nltools.data.Adjacency.plot.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.plot_label_distance.examples b/docs/backreferences/nltools.data.Adjacency.plot_label_distance.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.plot_silhouette.examples b/docs/backreferences/nltools.data.Adjacency.plot_silhouette.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.shape.examples b/docs/backreferences/nltools.data.Adjacency.shape.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.similarity.examples b/docs/backreferences/nltools.data.Adjacency.similarity.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.square_shape.examples b/docs/backreferences/nltools.data.Adjacency.square_shape.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.squareform.examples b/docs/backreferences/nltools.data.Adjacency.squareform.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.stats_label_distance.examples b/docs/backreferences/nltools.data.Adjacency.stats_label_distance.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.std.examples b/docs/backreferences/nltools.data.Adjacency.std.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.threshold.examples b/docs/backreferences/nltools.data.Adjacency.threshold.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.to_graph.examples b/docs/backreferences/nltools.data.Adjacency.to_graph.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.ttest.examples b/docs/backreferences/nltools.data.Adjacency.ttest.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Adjacency.write.examples b/docs/backreferences/nltools.data.Adjacency.write.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.aggregate.examples b/docs/backreferences/nltools.data.Brain_Data.aggregate.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.align.examples b/docs/backreferences/nltools.data.Brain_Data.align.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.append.examples b/docs/backreferences/nltools.data.Brain_Data.append.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.apply_mask.examples b/docs/backreferences/nltools.data.Brain_Data.apply_mask.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.astype.examples b/docs/backreferences/nltools.data.Brain_Data.astype.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.bootstrap.examples b/docs/backreferences/nltools.data.Brain_Data.bootstrap.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.copy.examples b/docs/backreferences/nltools.data.Brain_Data.copy.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.decompose.examples b/docs/backreferences/nltools.data.Brain_Data.decompose.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.detrend.examples b/docs/backreferences/nltools.data.Brain_Data.detrend.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.distance.examples b/docs/backreferences/nltools.data.Brain_Data.distance.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.dtype.examples b/docs/backreferences/nltools.data.Brain_Data.dtype.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.empty.examples b/docs/backreferences/nltools.data.Brain_Data.empty.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.examples b/docs/backreferences/nltools.data.Brain_Data.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.extract_roi.examples b/docs/backreferences/nltools.data.Brain_Data.extract_roi.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.filter.examples b/docs/backreferences/nltools.data.Brain_Data.filter.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.groupby.examples b/docs/backreferences/nltools.data.Brain_Data.groupby.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.icc.examples b/docs/backreferences/nltools.data.Brain_Data.icc.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.isempty.examples b/docs/backreferences/nltools.data.Brain_Data.isempty.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.mean.examples b/docs/backreferences/nltools.data.Brain_Data.mean.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.multivariate_similarity.examples b/docs/backreferences/nltools.data.Brain_Data.multivariate_similarity.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.plot.examples b/docs/backreferences/nltools.data.Brain_Data.plot.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.predict.examples b/docs/backreferences/nltools.data.Brain_Data.predict.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.r_to_z.examples b/docs/backreferences/nltools.data.Brain_Data.r_to_z.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.regions.examples b/docs/backreferences/nltools.data.Brain_Data.regions.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.regress.examples b/docs/backreferences/nltools.data.Brain_Data.regress.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.shape.examples b/docs/backreferences/nltools.data.Brain_Data.shape.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.similarity.examples b/docs/backreferences/nltools.data.Brain_Data.similarity.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.standardize.examples b/docs/backreferences/nltools.data.Brain_Data.standardize.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.std.examples b/docs/backreferences/nltools.data.Brain_Data.std.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.sum.examples b/docs/backreferences/nltools.data.Brain_Data.sum.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.threshold.examples b/docs/backreferences/nltools.data.Brain_Data.threshold.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.to_nifti.examples b/docs/backreferences/nltools.data.Brain_Data.to_nifti.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.transform_pairwise.examples b/docs/backreferences/nltools.data.Brain_Data.transform_pairwise.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.ttest.examples b/docs/backreferences/nltools.data.Brain_Data.ttest.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.upload_neurovault.examples b/docs/backreferences/nltools.data.Brain_Data.upload_neurovault.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Brain_Data.write.examples b/docs/backreferences/nltools.data.Brain_Data.write.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Design_Matrix.add_dct_basis.examples b/docs/backreferences/nltools.data.Design_Matrix.add_dct_basis.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Design_Matrix.addpoly.examples b/docs/backreferences/nltools.data.Design_Matrix.addpoly.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Design_Matrix.append.examples b/docs/backreferences/nltools.data.Design_Matrix.append.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Design_Matrix.convolve.examples b/docs/backreferences/nltools.data.Design_Matrix.convolve.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Design_Matrix.downsample.examples b/docs/backreferences/nltools.data.Design_Matrix.downsample.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Design_Matrix.examples b/docs/backreferences/nltools.data.Design_Matrix.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Design_Matrix.heatmap.examples b/docs/backreferences/nltools.data.Design_Matrix.heatmap.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Design_Matrix.horzcat.examples b/docs/backreferences/nltools.data.Design_Matrix.horzcat.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Design_Matrix.info.examples b/docs/backreferences/nltools.data.Design_Matrix.info.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Design_Matrix.upsample.examples b/docs/backreferences/nltools.data.Design_Matrix.upsample.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Design_Matrix.vertcat.examples b/docs/backreferences/nltools.data.Design_Matrix.vertcat.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Design_Matrix.vif.examples b/docs/backreferences/nltools.data.Design_Matrix.vif.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Design_Matrix.zscore.examples b/docs/backreferences/nltools.data.Design_Matrix.zscore.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Groupby.apply.examples b/docs/backreferences/nltools.data.Groupby.apply.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Groupby.combine.examples b/docs/backreferences/nltools.data.Groupby.combine.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Groupby.examples b/docs/backreferences/nltools.data.Groupby.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.data.Groupby.split.examples b/docs/backreferences/nltools.data.Groupby.split.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.datasets.download_collection.examples b/docs/backreferences/nltools.datasets.download_collection.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.datasets.download_nifti.examples b/docs/backreferences/nltools.datasets.download_nifti.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.datasets.examples b/docs/backreferences/nltools.datasets.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.datasets.fetch_emotion_ratings.examples b/docs/backreferences/nltools.datasets.fetch_emotion_ratings.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.datasets.fetch_pain.examples b/docs/backreferences/nltools.datasets.fetch_pain.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.datasets.get_collection_image_metadata.examples b/docs/backreferences/nltools.datasets.get_collection_image_metadata.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.file_reader.examples b/docs/backreferences/nltools.file_reader.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.file_reader.onsets_to_dm.examples b/docs/backreferences/nltools.file_reader.onsets_to_dm.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.mask.collapse_mask.examples b/docs/backreferences/nltools.mask.collapse_mask.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.mask.create_sphere.examples b/docs/backreferences/nltools.mask.create_sphere.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.mask.examples b/docs/backreferences/nltools.mask.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.mask.expand_mask.examples b/docs/backreferences/nltools.mask.expand_mask.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.plotting.dist_from_hyperplane_plot.examples b/docs/backreferences/nltools.plotting.dist_from_hyperplane_plot.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.plotting.examples b/docs/backreferences/nltools.plotting.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.plotting.iBrainViewer.examples b/docs/backreferences/nltools.plotting.iBrainViewer.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.plotting.plotBrain.examples b/docs/backreferences/nltools.plotting.plotBrain.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.plotting.plotTBrain.examples b/docs/backreferences/nltools.plotting.plotTBrain.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.plotting.plot_between_label_distance.examples b/docs/backreferences/nltools.plotting.plot_between_label_distance.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.plotting.plot_mean_label_distance.examples b/docs/backreferences/nltools.plotting.plot_mean_label_distance.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.plotting.plot_silhouette.examples b/docs/backreferences/nltools.plotting.plot_silhouette.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.plotting.plot_stacked_adjacency.examples b/docs/backreferences/nltools.plotting.plot_stacked_adjacency.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.plotting.probability_plot.examples b/docs/backreferences/nltools.plotting.probability_plot.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.plotting.roc_plot.examples b/docs/backreferences/nltools.plotting.roc_plot.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.plotting.scatterplot.examples b/docs/backreferences/nltools.plotting.scatterplot.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.simulator.Simulator.create_cov_data.examples b/docs/backreferences/nltools.simulator.Simulator.create_cov_data.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.simulator.Simulator.create_data.examples b/docs/backreferences/nltools.simulator.Simulator.create_data.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.simulator.Simulator.create_ncov_data.examples b/docs/backreferences/nltools.simulator.Simulator.create_ncov_data.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.simulator.Simulator.examples b/docs/backreferences/nltools.simulator.Simulator.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.simulator.Simulator.gaussian.examples b/docs/backreferences/nltools.simulator.Simulator.gaussian.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.simulator.Simulator.n_spheres.examples b/docs/backreferences/nltools.simulator.Simulator.n_spheres.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.simulator.Simulator.normal_noise.examples b/docs/backreferences/nltools.simulator.Simulator.normal_noise.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.simulator.Simulator.sphere.examples b/docs/backreferences/nltools.simulator.Simulator.sphere.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.simulator.Simulator.to_nifti.examples b/docs/backreferences/nltools.simulator.Simulator.to_nifti.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.simulator.examples b/docs/backreferences/nltools.simulator.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.align.examples b/docs/backreferences/nltools.stats.align.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.calc_bpm.examples b/docs/backreferences/nltools.stats.calc_bpm.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.correlation_permutation.examples b/docs/backreferences/nltools.stats.correlation_permutation.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.downsample.examples b/docs/backreferences/nltools.stats.downsample.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.examples b/docs/backreferences/nltools.stats.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.fdr.examples b/docs/backreferences/nltools.stats.fdr.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.fisher_r_to_z.examples b/docs/backreferences/nltools.stats.fisher_r_to_z.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.holm_bonf.examples b/docs/backreferences/nltools.stats.holm_bonf.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.make_cosine_basis.examples b/docs/backreferences/nltools.stats.make_cosine_basis.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.multi_threshold.examples b/docs/backreferences/nltools.stats.multi_threshold.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.one_sample_permutation.examples b/docs/backreferences/nltools.stats.one_sample_permutation.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.pearson.examples b/docs/backreferences/nltools.stats.pearson.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.procrustes.examples b/docs/backreferences/nltools.stats.procrustes.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.summarize_bootstrap.examples b/docs/backreferences/nltools.stats.summarize_bootstrap.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.threshold.examples b/docs/backreferences/nltools.stats.threshold.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.trim.examples b/docs/backreferences/nltools.stats.trim.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.two_sample_permutation.examples b/docs/backreferences/nltools.stats.two_sample_permutation.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.upsample.examples b/docs/backreferences/nltools.stats.upsample.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.winsorize.examples b/docs/backreferences/nltools.stats.winsorize.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.stats.zscore.examples b/docs/backreferences/nltools.stats.zscore.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.utils.concatenate.examples b/docs/backreferences/nltools.utils.concatenate.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.utils.examples b/docs/backreferences/nltools.utils.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.utils.get_anatomical.examples b/docs/backreferences/nltools.utils.get_anatomical.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.utils.get_resource_path.examples b/docs/backreferences/nltools.utils.get_resource_path.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.utils.glover_hrf.examples b/docs/backreferences/nltools.utils.glover_hrf.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.utils.glover_time_derivative.examples b/docs/backreferences/nltools.utils.glover_time_derivative.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.utils.set_algorithm.examples b/docs/backreferences/nltools.utils.set_algorithm.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.utils.set_decomposition_algorithm.examples b/docs/backreferences/nltools.utils.set_decomposition_algorithm.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.utils.spm_dispersion_derivative.examples b/docs/backreferences/nltools.utils.spm_dispersion_derivative.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.utils.spm_hrf.examples b/docs/backreferences/nltools.utils.spm_hrf.examples deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/backreferences/nltools.utils.spm_time_derivative.examples b/docs/backreferences/nltools.utils.spm_time_derivative.examples deleted file mode 100644 index e69de29b..00000000 From 4e753d8b0f8f1c2d3c6b675fb5eb2cf778cd5dc4 Mon Sep 17 00:00:00 2001 From: ejolly Date: Mon, 26 Mar 2018 00:49:38 -0400 Subject: [PATCH 02/15] Fixed import issue for statsmodels. Fixed parallelization in regress with arma. Former-commit-id: ab2b3bd9ce063c2cbafc2a06c31fe46f0bb382cd --- nltools/stats.py | 59 +++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/nltools/stats.py b/nltools/stats.py index 75da2438..71ca11c8 100644 --- a/nltools/stats.py +++ b/nltools/stats.py @@ -49,8 +49,8 @@ MAX_INT = np.iinfo(np.int32).max # Optional dependencies -ARMA = attempt_to_import('statsmodels.tsa.arima_model',name='ARMA', - fromlist=['ARMA']) +sm = attempt_to_import('statsmodels.tsa.arima_model',name='sm') + def pearson(x, y): """ Correlates row vector x with each row vector in 2D array y. From neurosynth.stats.py - author: Tal Yarkoni @@ -705,6 +705,33 @@ def summarize_bootstrap(data, save_weights=False): output['samples'] = data return output +def _arma_func(X,Y,idx=None,**kwargs): + + """ + Fit an ARMA(p,q) model. If Y is a matrix and not a vector, expects an idx argument that refers to columns of Y. Used by regress(). + """ + method = kwargs.pop('method','css-mle') + order = kwargs.pop('order',(1,1)) + + maxiter = kwargs.pop('maxiter',50) + disp = kwargs.pop('disp',-1) + start_ar_lags = kwargs.pop('start_ar_lags',order[0]+1) + transparams = kwargs.pop('transparams',False) + trend = kwargs.pop('trend','nc') + + if len(Y.shape) == 2: + model = sm.tsa.arima_model.ARMA(endog=Y[:,idx],exog=X.values,order=order) + else: + model = sm.tsa.arima_model.ARMA(endog=Y,exog=X.values,order=order) + try: + res = model.fit(trend=trend,method=method,transparams=transparams, + maxiter=maxiter,disp=disp,start_ar_lags=start_ar_lags,**kwargs) + except: + res = model.fit(trend=trend,method=method,transparams=transparams, + maxiter=maxiter,disp=disp,start_ar_lags=start_ar_lags,start_params=np.repeat(1.,X.shape[1]+2)) + + return (res.params[:-2], res.tvalues[:-2],res.pvalues[:-2],res.df_resid, res.resid) + def regress(X,Y,mode='ols',**kwargs): """ This is a flexible function to run several types of regression models provided X and Y numpy arrays. Y can be a 1d numpy array or 2d numpy array. In the latter case, results will be output with shape 1 x Y.shape[1], in other words fitting a separate regression model to each column of Y. @@ -791,36 +818,12 @@ def regress(X,Y,mode='ols',**kwargs): max_nbytes = kwargs.pop('max_nbytes',1e8) verbose = kwargs.pop('verbose',0) - # Use function scope to get X and Y - def _arma_func(idx=None,**kwargs): - - """ - Fit an ARMA(p,q) model. If Y is a matrix and not a vector, expects an idx argument that refers to columns of Y. - """ - method = kwargs.pop('method','css-mle') - order = kwargs.pop('order',(1,1)) - - maxiter = kwargs.pop('maxiter',50) - disp = kwargs.pop('disp',-1) - start_ar_lags = kwargs.pop('start_ar_lags',order[0]+1) - transparams = kwargs.pop('transparams',False) - trend = kwargs.pop('trend','nc') - - if len(Y.shape) == 2: - model = ARMA(endog=Y[:,idx],exog=X,order=order) - else: - model = ARMA(endog=Y,exog=X,order=order) - res = model.fit(trend=trend,method=method,transparams=transparams, - maxiter=maxiter,disp=disp,start_ar_lags=start_ar_lags) - - return (res.params[:-2], res.tvalues[:-2],res.pvalues[:-2],res.df_resid, res.resid) - # Parallelize if Y vector contains more than 1 column if len(Y.shape) == 2: if backend == 'threading' and n_jobs == -1: n_jobs = 10 par_for = Parallel(n_jobs=n_jobs,verbose=verbose,backend=backend,max_nbytes=max_nbytes) - out_arma = par_for(delayed(_arma_func)(idx=i,**kwargs) for i in range(Y.shape[-1])) + out_arma = par_for(delayed(_arma_func)(X,Y,idx=i,**kwargs) for i in range(Y.shape[-1])) b = np.column_stack([elem[0] for elem in out_arma]) t = np.column_stack([elem[1] for elem in out_arma]) @@ -829,7 +832,7 @@ def _arma_func(idx=None,**kwargs): res = np.column_stack([elem[4] for elem in out_arma]) else: - b,t,p,df,res = _arma_func() + b,t,p,df,res = _arma_func(X,Y,**kwargs) return b, t, p, df, res From 19fd3032c3612009acd93a54babcd93ee5b16efe Mon Sep 17 00:00:00 2001 From: ejolly Date: Fri, 20 Apr 2018 18:38:47 -0400 Subject: [PATCH 03/15] Fixed bug in checking during appending. Removed camel case. Former-commit-id: ea603252f9d0ff75dcafb98e7305fe7d73405f23 --- nltools/data/design_matrix.py | 16 +++++++++------- nltools/file_reader.py | 5 ++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/nltools/data/design_matrix.py b/nltools/data/design_matrix.py index 2abe0928..fbd1fca2 100644 --- a/nltools/data/design_matrix.py +++ b/nltools/data/design_matrix.py @@ -89,7 +89,7 @@ def _inherit_attributes(self, 'polys']): """ - This is helper function that simply ensures that attributes are copied over from an the current Design_Matrix to a new Design_Matrix. + This is helper function that simply ensures that attributes are copied over from the current Design_Matrix to a new Design_Matrix. Args: dm_out (Design_Matrix): the new design matrix to copy attributes to @@ -118,9 +118,7 @@ def details(self): ) def append(self, dm, axis=0, keep_separate = True, add_poly = None, add_dct_basis = None, unique_cols = [], include_lower = True,fill_na=0): - """Method for concatenating another design matrix row or column-wise. - Can "uniquify" certain columns when appending row-wise, and by - default will attempt to do that with all polynomial terms (e.g. intercept, polynomial trends). Can also add new polynomial terms during vertical concatentation (when axis == 0). This will by default create new polynomial terms separately for each design matrix + """Method for concatenating another design matrix row or column-wise. When concatenating row-wise, has the ability to keep certain columns separated if they exist in multiple design matrices (e.g. keeping separate intercepts for multiple runs). This is on by default and will automatically separate out polynomial columns (i.e. anything added with the `add_poly` or `add_dct_basis` methods). Additional columns can be separate by run using the `unique_cols` parameter. Can also add new polynomial terms during vertical concatentation (when axis == 0). This will by default create new polynomial terms separately for each design matrix Args: dm (Design_Matrix or list): design_matrix or list of design_matrices to append @@ -185,15 +183,16 @@ def _vertcat(self, df, keep_separate, add_poly, add_dct_basis, unique_cols, incl """ - if unique_cols: + if len(unique_cols): if not keep_separate: raise ValueError("unique_cols provided but keep_separate set to False. Set keep_separate to True to separate unique_cols") to_append = df[:] # need to make a copy because we're altering df if keep_separate: - if not all([set(self.polys) == set(elem.polys) for elem in to_append]): - raise ValueError("Design matrices do not match on their polynomial terms (i.e. intercepts, polynomial trends, basis functions). This makes appending with separation ambigious and is not currently supported. Either make sure all constant terms are the same or make sure no Design Matrix has any constant terms and add them during appending with the 'add_poly' and 'unique_cols' arguments") + if not self.empty: + if not all([set(self.polys) == set(elem.polys) for elem in to_append]): + raise ValueError("Design matrices do not match on their polynomial terms (i.e. intercepts, polynomial trends, basis functions). This makes appending with separation ambigious and is not currently supported. Either make sure all constant terms are the same or make sure no Design Matrix has any constant terms and add them during appending with the 'add_poly' and 'unique_cols' arguments") orig = self.copy() # Make a copy of the original cause we might alter it @@ -226,6 +225,9 @@ def _vertcat(self, df, keep_separate, add_poly, add_dct_basis, unique_cols, incl all_polys.append(cols_dict[c]) dm = dm.rename(columns=cols_dict) all_dms[i] = dm + elif len(dm.polys): + for p in dm.polys: + all_polys.append(p) out = pd.concat(all_dms,axis=0,ignore_index=True) if fill_na is not None: diff --git a/nltools/file_reader.py b/nltools/file_reader.py index e3630e3f..a9df1131 100644 --- a/nltools/file_reader.py +++ b/nltools/file_reader.py @@ -15,7 +15,7 @@ import warnings -def onsets_to_dm(F, TR, runLength, header='infer', sort=False, keep_separate=True, +def onsets_to_dm(F, TR, run_length, header='infer', sort=False, keep_separate=True, add_poly=None, unique_cols=[], fill_na=None, **kwargs): """ @@ -30,9 +30,8 @@ def onsets_to_dm(F, TR, runLength, header='infer', sort=False, keep_separate=Tru Args: F (filepath/DataFrame/list): path to file, pandas dataframe, or list of files or pandas dataframes - df (str or dataframe): path to file or pandas dataframe TR (float): length of TR in seconds the run was collected at - runLength (int): number of TRs in the run these onsets came from + run_length (int): number of TRs in the run these onsets came from sort (bool, optional): whether to sort the columns of the resulting design matrix alphabetically; defaults to False From 441c4ade85159a7525e14ddb78fab2bb0bdb6a05 Mon Sep 17 00:00:00 2001 From: ejolly Date: Sat, 21 Apr 2018 17:45:55 -0400 Subject: [PATCH 04/15] Fixed bug in onsets_to_dm Former-commit-id: ce43aa1d366f004f9165a455399a86c195262705 --- nltools/file_reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nltools/file_reader.py b/nltools/file_reader.py index a9df1131..817ec43f 100644 --- a/nltools/file_reader.py +++ b/nltools/file_reader.py @@ -75,7 +75,7 @@ def onsets_to_dm(F, TR, run_length, header='infer', sort=False, keep_separate=Tr df['Onset'] = df['Onset'].apply(lambda x: int(np.floor(x/TR))) #Build dummy codes - X = Design_Matrix(np.zeros([runLength,len(df['Stim'].unique())]),columns=df['Stim'].unique(),sampling_rate=TR) + X = Design_Matrix(np.zeros([run_length,len(df['Stim'].unique())]),columns=df['Stim'].unique(),sampling_rate=TR) for i, row in df.iterrows(): if df.shape[1] == 3: dur = np.ceil(row['Duration']/TR) From 5dbe786aecc1076e3650ff462d06fb12d52b401e Mon Sep 17 00:00:00 2001 From: ejolly Date: Sat, 21 Apr 2018 18:46:11 -0400 Subject: [PATCH 05/15] Modified Brain_Data regress to not pass on Design_Matrix to outputs. Also made Brain_Data.append handle none types for X and Y attributes, and pass additional kwargs Design_Matrix append. Former-commit-id: 34a161971a50369ad381667899314aee2ed271d9 --- nltools/data/brain_data.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/nltools/data/brain_data.py b/nltools/data/brain_data.py index 513854b9..572fe553 100644 --- a/nltools/data/brain_data.py +++ b/nltools/data/brain_data.py @@ -401,16 +401,22 @@ def regress(self,mode='ols',**kwargs): b_out = deepcopy(self) b_out.data = b + b_out.X, b_out.Y = None, None t_out = deepcopy(self) t_out.data = t + t_out.X, t_out.Y = None, None p_out = deepcopy(self) p_out.data = p + p_out.X, p_out.Y = None, None df_out = deepcopy(self) df_out.data = df + df_out.X, df_out.Y = None, None sigma_out = deepcopy(self) sigma_out.data = sigma + sigma_out.X, sigma_out.Y = None, None res_out = deepcopy(self) res_out.data = res + res_out.X, res_out.Y = None, None return {'beta': b_out, 't': t_out, 'p': p_out, 'df': df_out, 'sigma': sigma_out, 'residual': res_out} @@ -497,11 +503,12 @@ def ttest(self, threshold_dict=None): return out - def append(self, data): + def append(self, data, **kwargs): """ Append data to Brain_Data instance Args: data: Brain_Data instance to append + kwargs: optional inputs to Design_Matrix append Returns: out: new appended Brain_Data instance @@ -528,11 +535,11 @@ def append(self, data): raise ValueError(error_string) out = deepcopy(self) out.data = np.vstack([self.data, data.data]) - if out.Y.size: + if out.Y: out.Y = self.Y.append(data.Y) - if self.X.size: + if self.X: if isinstance(self.X, pd.DataFrame): - out.X = self.X.append(data.X) + out.X = self.X.append(data.X,**kwargs) else: out.X = np.vstack([self.X, data.X]) return out From 68d4e18fc380905241fec8adaf5ba6505fa0fb32 Mon Sep 17 00:00:00 2001 From: ejolly Date: Sat, 21 Apr 2018 19:31:41 -0400 Subject: [PATCH 06/15] Added grand-mean scaling method to Brain_Data Former-commit-id: 7c6b7c5cda548fd28bfbfa71e36ae699485e2640 --- nltools/data/brain_data.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/nltools/data/brain_data.py b/nltools/data/brain_data.py index 572fe553..7b1d4bc8 100644 --- a/nltools/data/brain_data.py +++ b/nltools/data/brain_data.py @@ -327,6 +327,19 @@ def write(self, file_name=None): self.to_nifti().to_filename(file_name) + def scale(self, scale_val=100.): + """ Scale all values such that theya re on the range 0 - scale_val, via grand-mean scaling. This is NOT global-scaling/intensity normalization. This is useful for ensuring that data is on a common scale (e.g. good for multiple runs, participants, etc) and if the default value of 100 is used, can be interpreted as something akin to (but not exactly) "percent signal change." This is consistent with default behavior in AFNI and SPM. Change this value to 10000 to make consistent with FSL. + + Args: + scale_val (int/float): what value to send the grand-mean to; default 100 + + """ + + out = deepcopy(self) + out.data = out.data / out.data.mean() * scale_val + + return out + def plot(self, limit=5, anatomical=None, **kwargs): """ Create a quick plot of self.data. Will plot each image separately From 97ec3ad414362a21693ed23e318bbb73d271e77c Mon Sep 17 00:00:00 2001 From: ejolly Date: Sat, 21 Apr 2018 19:54:07 -0400 Subject: [PATCH 07/15] Cleaned up extra copying during regress operation. Switched back old append method. Former-commit-id: a04e7c29f185d9a3b71b06abde462ca1429fc30f --- nltools/data/brain_data.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/nltools/data/brain_data.py b/nltools/data/brain_data.py index 7b1d4bc8..fbd13c53 100644 --- a/nltools/data/brain_data.py +++ b/nltools/data/brain_data.py @@ -412,24 +412,23 @@ def regress(self,mode='ols',**kwargs): b,t,p,df,res = regress(self.X,self.data,mode=mode,**kwargs) sigma = np.std(res,axis=0,ddof=self.X.shape[1]) - b_out = deepcopy(self) - b_out.data = b - b_out.X, b_out.Y = None, None - t_out = deepcopy(self) + # Prevent copy of all data in self multiple times; instead start with an empty instance and copy only needed attributes from self, and use this as a template for other outputs + b_out = self.__class__() + b_out.mask = deepcopy(self.mask) + b_out.nifti_masker = deepcopy(self.nifti_masker) + + # Use this as template for other outputs before setting data + t_out = b_out.copy() t_out.data = t - t_out.X, t_out.Y = None, None - p_out = deepcopy(self) + p_out = b_out.copy() p_out.data = p - p_out.X, p_out.Y = None, None - df_out = deepcopy(self) + df_out = b_out.copy() df_out.data = df - df_out.X, df_out.Y = None, None - sigma_out = deepcopy(self) + sigma_out = b_out.copy() sigma_out.data = sigma - sigma_out.X, sigma_out.Y = None, None - res_out = deepcopy(self) + res_out = b_out.copy() res_out.data = res - res_out.X, res_out.Y = None, None + b_out.data = b return {'beta': b_out, 't': t_out, 'p': p_out, 'df': df_out, 'sigma': sigma_out, 'residual': res_out} @@ -548,9 +547,9 @@ def append(self, data, **kwargs): raise ValueError(error_string) out = deepcopy(self) out.data = np.vstack([self.data, data.data]) - if out.Y: + if out.Y.size: out.Y = self.Y.append(data.Y) - if self.X: + if self.X.size: if isinstance(self.X, pd.DataFrame): out.X = self.X.append(data.X,**kwargs) else: From faa1580f9202619003ec75c15c5d94215647a143 Mon Sep 17 00:00:00 2001 From: ejolly Date: Sat, 21 Apr 2018 22:48:51 -0400 Subject: [PATCH 08/15] Bug fix in adding intercept only polynomials during file reading and appending design matrices. Former-commit-id: 8e6155c7ff3c202be67c052fb7212bbf9fa5ec98 --- nltools/data/design_matrix.py | 2 +- nltools/file_reader.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/nltools/data/design_matrix.py b/nltools/data/design_matrix.py index fbd1fca2..0d1547f4 100644 --- a/nltools/data/design_matrix.py +++ b/nltools/data/design_matrix.py @@ -196,7 +196,7 @@ def _vertcat(self, df, keep_separate, add_poly, add_dct_basis, unique_cols, incl orig = self.copy() # Make a copy of the original cause we might alter it - if add_poly: + if add_poly is not None: orig = orig.add_poly(add_poly,include_lower) for i,d in enumerate(to_append): d = d.add_poly(add_poly,include_lower) diff --git a/nltools/file_reader.py b/nltools/file_reader.py index 817ec43f..be6ecd11 100644 --- a/nltools/file_reader.py +++ b/nltools/file_reader.py @@ -89,6 +89,9 @@ def onsets_to_dm(F, TR, run_length, header='infer', sort=False, keep_separate=Tr if len(out) > 1: out_dm = out[0].append(out[1:],keep_separate = keep_separate, add_poly=add_poly, unique_cols=unique_cols,fill_na=fill_na) else: - out_dm = out[0].add_poly(add_poly) + if add_poly is not None: + out_dm = out[0].add_poly(add_poly) + else: + out_dm = out[0] return out_dm From b5561b52fe4ce302a851d602fd0bf09f50e8b77b Mon Sep 17 00:00:00 2001 From: ejolly Date: Tue, 24 Apr 2018 12:45:00 -0400 Subject: [PATCH 09/15] Changed all instances of sampling_rate to sampling_freq in Design_Matrix and Brain_Data Former-commit-id: 5b5ea092d2fae3c0a8512faa940db07c5efe5b98 --- nltools/data/brain_data.py | 10 +++---- nltools/data/design_matrix.py | 52 +++++++++++++++++------------------ nltools/stats.py | 4 +-- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/nltools/data/brain_data.py b/nltools/data/brain_data.py index fbd13c53..4ddcc312 100644 --- a/nltools/data/brain_data.py +++ b/nltools/data/brain_data.py @@ -1174,11 +1174,11 @@ def r_to_z(self): out.data = fisher_r_to_z(out.data) return out - def filter(self,sampling_rate=None, high_pass=None,low_pass=None,**kwargs): + def filter(self,sampling_freq=None, high_pass=None,low_pass=None,**kwargs): ''' Apply 5th order butterworth filter to data. Wraps nilearn functionality. Does not default to detrending and standardizing like nilearn implementation, but this can be overridden using kwargs. Args: - sampling_rate: sampling rate in seconds (i.e. TR) + sampling_freq: sampling freq in hertz (i.e. 1 / TR) high_pass: high pass cutoff frequency low_pass: low pass cutoff frequency kwargs: other keyword arguments to nilearn.signal.clean @@ -1187,18 +1187,18 @@ def filter(self,sampling_rate=None, high_pass=None,low_pass=None,**kwargs): Brain_Data: Filtered Brain_Data instance ''' - if sampling_rate is None: + if sampling_freq is None: raise ValueError("Need to provide sampling rate (TR)!") if high_pass is None and low_pass is None: raise ValueError("high_pass and/or low_pass cutoff must be" "provided!") - if sampling_rate is None: + if sampling_freq is None: raise ValueError("Need to provide TR!") standardize = kwargs.get('standardize',False) detrend = kwargs.get('detrend',False) out = self.copy() - out.data = clean(out.data,t_r=sampling_rate,detrend=detrend,standardize=standardize,high_pass=high_pass,low_pass=low_pass,**kwargs) + out.data = clean(out.data,t_r= 1. / sampling_freq,detrend=detrend,standardize=standardize,high_pass=high_pass,low_pass=low_pass,**kwargs) return out def dtype(self): diff --git a/nltools/data/design_matrix.py b/nltools/data/design_matrix.py index 0d1547f4..9fa0823c 100644 --- a/nltools/data/design_matrix.py +++ b/nltools/data/design_matrix.py @@ -53,21 +53,21 @@ class Design_Matrix(DataFrame): new design matrix instance. Args: - sampling_rate (float): sampling rate of each row in seconds (e.g. TR in neuroimaging) + sampling_freq (float): sampling rate of each row in hertz; To covert seconds to hertz (e.g. in the case of TRs for neuroimaging) using hertz = 1 / TR convolved (list, optional): on what columns convolution has been performed; defaults to None polys (list, optional): list of polynomial terms in design matrix, e.g. intercept, polynomial trends, basis functions, etc; default None """ - _metadata = ['sampling_rate', 'convolved', 'polys'] + _metadata = ['sampling_freq', 'convolved', 'polys'] def __init__(self, *args, **kwargs): - sampling_rate = kwargs.pop('sampling_rate',None) + sampling_freq = kwargs.pop('sampling_freq',None) convolved = kwargs.pop('convolved', []) polys = kwargs.pop('polys', []) - self.sampling_rate = sampling_rate + self.sampling_freq = sampling_freq self.convolved = convolved self.polys = polys @@ -84,7 +84,7 @@ def _constructor_sliced(self): def _inherit_attributes(self, dm_out, atts=[ - 'sampling_rate', + 'sampling_freq', 'convolved', 'polys']): @@ -108,10 +108,10 @@ def details(self): """Print class meta data. """ - return '%s.%s(sampling_rate=%s, shape=%s, convolved=%s, polynomials=%s)' % ( + return '%s.%s(sampling_freq=%s (hz), shape=%s, convolved=%s, polynomials=%s)' % ( self.__class__.__module__, self.__class__.__name__, - self.sampling_rate, + self.sampling_freq, self.shape, self.convolved, self.polys @@ -144,7 +144,7 @@ def append(self, dm, axis=0, keep_separate = True, add_poly = None, add_dct_basi # Check all items to be appended are Design Matrices and have the same sampling rate if not all([isinstance(elem,self.__class__) for elem in to_append]): raise TypeError("Each object in list must be a Design_Matrix!") - if not all([elem.sampling_rate == self.sampling_rate for elem in to_append]): + if not all([elem.sampling_freq == self.sampling_freq for elem in to_append]): raise ValueError("All Design Matrices must have the same sampling rate!") if axis == 1: @@ -233,7 +233,7 @@ def _vertcat(self, df, keep_separate, add_poly, add_dct_basis, unique_cols, incl if fill_na is not None: out = out.fillna(fill_na) - out.sampling_rate = self.sampling_rate + out.sampling_freq = self.sampling_freq out.convolved = self.convolved out.polys = all_polys data_cols = [elem for elem in out.columns if elem not in out.polys] @@ -293,12 +293,12 @@ def convolve(self, conv_func='hrf', columns=None): """Perform convolution using an arbitrary function. Args: - conv_func (ndarray or string): either a 1d numpy array containing output of a function that you want to convolve; a samples by kernel 2d array of several kernels to convolve; or th string 'hrf' which defaults to a glover HRF function at the Design_matrix's sampling_rate + conv_func (ndarray or string): either a 1d numpy array containing output of a function that you want to convolve; a samples by kernel 2d array of several kernels to convolve; or th string 'hrf' which defaults to a glover HRF function at the Design_matrix's sampling_freq columns (list): what columns to perform convolution on; defaults to all skipping intercept, and columns containing 'poly' or 'cosine' """ - assert self.sampling_rate is not None, "Design_matrix has no sampling_rate set!" + assert self.sampling_freq is not None, "Design_matrix has no sampling_freq set!" if columns is None: columns = [col for col in self.columns if 'intercept' not in col and 'poly' not in col and 'cosine' not in col] @@ -307,8 +307,8 @@ def convolve(self, conv_func='hrf', columns=None): if isinstance(conv_func,six.string_types): assert conv_func == 'hrf',"Did you mean 'hrf'? 'hrf' can generate a kernel for you, otherwise custom kernels should be passed in as 1d or 2d arrays." - assert self.sampling_rate is not None, "Design_matrix sampling rate not set. Can't figure out how to generate HRF!" - conv_func = glover_hrf(self.sampling_rate, oversampling=1) + assert self.sampling_freq is not None, "Design_matrix sampling rate not set. Can't figure out how to generate HRF!" + conv_func = glover_hrf(1. / self.sampling_freq, oversampling=1) else: assert type(conv_func) == np.ndarray, 'Must provide a function for convolution!' @@ -336,19 +336,18 @@ def downsample(self, target,**kwargs): design matrix. Args: - target(float): downsampling target, typically in samples not - seconds + target(float): desired frequency in hz kwargs: additional inputs to nltools.stats.downsample """ - if target < self.sampling_rate: + if target < self.sampling_freq: raise ValueError("Target must be longer than current sampling rate") - df = Design_Matrix(downsample(self, sampling_freq=1./self.sampling_rate, target=target,target_type='seconds', **kwargs)) + df = Design_Matrix(downsample(self, sampling_freq= self.sampling_freq, target=target,target_type='hz', **kwargs)) # convert df to a design matrix newMat = self._inherit_attributes(df) - newMat.sampling_rate = target + newMat.sampling_freq = target return newMat def upsample(self, target,**kwargs): @@ -357,19 +356,18 @@ def upsample(self, target,**kwargs): design matrix. Args: - target(float): downsampling target, typically in samples not - seconds + target(float): desired frequence in hz kwargs: additional inputs to nltools.stats.downsample """ - if target > self.sampling_rate: + if target > self.sampling_freq: raise ValueError("Target must be shorter than current sampling rate") - df = Design_Matrix(upsample(self, sampling_freq=1./self.sampling_rate, target=target, target_type='seconds',**kwargs)) + df = Design_Matrix(upsample(self, sampling_freq= self.sampling_freq, target=target, target_type='hz',**kwargs)) # convert df to a design matrix newMat = self._inherit_attributes(df) - newMat.sampling_rate = target + newMat.sampling_freq = target return newMat def zscore(self, columns=[]): @@ -433,7 +431,7 @@ def add_poly(self, order=0, include_lower=True): else: polyDict['poly_'+str(order)] = (range(self.shape[0]) - np.mean(range(self.shape[0])))**order - toAdd = Design_Matrix(polyDict,sampling_rate=self.sampling_rate) + 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()) @@ -451,11 +449,11 @@ def add_dct_basis(self,duration=180): duration (int): length of filter in seconds """ - assert self.sampling_rate is not None, "Design_Matrix has no sampling_rate set!" - basis_mat = make_cosine_basis(self.shape[0],self.sampling_rate,duration) + assert self.sampling_freq is not None, "Design_Matrix has no sampling_freq set!" + basis_mat = make_cosine_basis(self.shape[0],self.sampling_freq,duration) basis_frame = Design_Matrix(basis_mat, - sampling_rate=self.sampling_rate) + sampling_freq=self.sampling_freq) basis_frame.columns = ['cosine_'+str(i+1) for i in range(basis_frame.shape[1])] diff --git a/nltools/stats.py b/nltools/stats.py index 71ca11c8..3e57a151 100644 --- a/nltools/stats.py +++ b/nltools/stats.py @@ -301,7 +301,7 @@ def downsample(data,sampling_freq=None, target=None, target_type='samples', Args: data: Pandas DataFrame or Series - sampling_freq: Sampling frequency of data + sampling_freq: Sampling frequency of data in hertz target: downsampling target target_type: type of target can be [samples,seconds,hz] method: (str) type of downsample method ['mean','median'], @@ -340,7 +340,7 @@ def upsample(data,sampling_freq=None, target=None, target_type='samples',method= Args: data: Pandas Series or DataFrame (Note: will drop non-numeric columns from DataFrame) - sampling_freq: Sampling frequency of data + sampling_freq: Sampling frequency of data in hertz target: downsampling target target_type: type of target can be [samples,seconds,hz] method (str):'linear', 'nearest', 'zero', 'slinear', 'quadratic', 'cubic' where 'zero', 'slinear', 'quadratic' and 'cubic' refer to a spline interpolation of zeroth, first, second or third order default: linear From f6b93f4e721cef9601471b0b6e828303eda1bb67 Mon Sep 17 00:00:00 2001 From: ejolly Date: Tue, 24 Apr 2018 16:15:10 -0400 Subject: [PATCH 10/15] Updated onsets_to_dm function to use sampling frequency. Former-commit-id: a354fcf94b7699218e5f054e183672c74e8964de --- nltools/file_reader.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nltools/file_reader.py b/nltools/file_reader.py index be6ecd11..8303f047 100644 --- a/nltools/file_reader.py +++ b/nltools/file_reader.py @@ -15,11 +15,11 @@ import warnings -def onsets_to_dm(F, TR, run_length, header='infer', sort=False, keep_separate=True, +def onsets_to_dm(F, sampling_freq, run_length, header='infer', sort=False, keep_separate=True, add_poly=None, unique_cols=[], fill_na=None, **kwargs): """ - This function can assist in reading in one or several in a 2-3 column onsets files, specified in seconds and converting it to a Design Matrix organized as TRs X Stimulus Classes. Onsets files **must** be organized with columns in one of the following 4 formats: + This function can assist in reading in one or several in a 2-3 column onsets files, specified in seconds and converting it to a Design Matrix organized as samples X Stimulus Classes. Onsets files **must** be organized with columns in one of the following 4 formats: 1) 'Stim, Onset' 2) 'Onset, Stim' @@ -30,8 +30,7 @@ def onsets_to_dm(F, TR, run_length, header='infer', sort=False, keep_separate=Tr Args: F (filepath/DataFrame/list): path to file, pandas dataframe, or list of files or pandas dataframes - TR (float): length of TR in seconds the run was collected at - run_length (int): number of TRs in the run these onsets came from + sampling_freq (float): sampling frequency in hertz; for TRs use (1 / TR) run_length (int): number of TRs in the run these onsets came from sort (bool, optional): whether to sort the columns of the resulting design matrix alphabetically; defaults to False @@ -51,6 +50,7 @@ def onsets_to_dm(F, TR, run_length, header='infer', sort=False, keep_separate=Tr F = [F] out = [] + TR = 1. / sampling_freq for f in F: if isinstance(f,six.string_types): df = pd.read_csv(f, header=header,**kwargs) From 8ce9f27b7857a82ace3b54e80775aec8e022b597 Mon Sep 17 00:00:00 2001 From: ejolly Date: Tue, 24 Apr 2018 16:26:57 -0400 Subject: [PATCH 11/15] Hide development folder with notebooks from git. Former-commit-id: 0d8e8d3aad7d857873b696a73337271b272d3399 --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8ff6ce74..1f92f3c7 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ dist/ .cache/ htmlcov .pytest_cache/* - +dev/ # Logs and databases # ###################### *.log From f26e39c71735cb4ce4483417e05d42048355daea Mon Sep 17 00:00:00 2001 From: ejolly Date: Tue, 24 Apr 2018 22:53:31 -0400 Subject: [PATCH 12/15] Design matrix vertical appending is much more flexible and intuitive to use. Dropped add_poly and add_dct_basis functionality during append. Added some additional checks during those methods for pre-appended Design Matrices. Additional unique_cols during append currently not functional. Former-commit-id: 43223f0a04bc5b90d97c8eadd7944991937f7650 --- nltools/data/design_matrix.py | 192 ++++++++++++++++++++-------------- nltools/stats.py | 3 +- nltools/utils.py | 3 + 3 files changed, 115 insertions(+), 83 deletions(-) diff --git a/nltools/data/design_matrix.py b/nltools/data/design_matrix.py index 9fa0823c..363fab10 100644 --- a/nltools/data/design_matrix.py +++ b/nltools/data/design_matrix.py @@ -24,6 +24,7 @@ zscore, make_cosine_basis ) +from nltools.utils import AmbiguityError class Design_Matrix_Series(Series): @@ -117,7 +118,7 @@ def details(self): self.polys ) - def append(self, dm, axis=0, keep_separate = True, add_poly = None, add_dct_basis = None, unique_cols = [], include_lower = True,fill_na=0): + def append(self, dm, axis=0, keep_separate = True, unique_cols = [], fill_na=0, verbose=False): """Method for concatenating another design matrix row or column-wise. When concatenating row-wise, has the ability to keep certain columns separated if they exist in multiple design matrices (e.g. keeping separate intercepts for multiple runs). This is on by default and will automatically separate out polynomial columns (i.e. anything added with the `add_poly` or `add_dct_basis` methods). Additional columns can be separate by run using the `unique_cols` parameter. Can also add new polynomial terms during vertical concatentation (when axis == 0). This will by default create new polynomial terms separately for each design matrix Args: @@ -126,14 +127,11 @@ def append(self, dm, axis=0, keep_separate = True, add_poly = None, add_dct_basi keep_separate (bool,optional): whether try and uniquify columns; defaults to True; only applies when axis==0 - add_poly (int,optional): what order polynomial terms to add during append, only applied when axis = 0; default None - add_dct_basis (int,optional): add discrete cosine bassi function during append, only applied when axis = 0; default None - only applies when axis==0 unique_cols (list,optional): what additional columns to try to keep separated by uniquifying, only applies when axis = 0; defaults to None - include_lower (bool,optional): whether to also add lower order polynomial terms; only applies when add_poly is not None fill_na (str/int/float): if provided will fill NaNs with this value during row-wise appending (when axis = 0) if separate columns are desired; default 0 + verbose (bool): print messages during append about how polynomials are going to be separated """ if not isinstance(dm, list): @@ -143,25 +141,23 @@ def append(self, dm, axis=0, keep_separate = True, add_poly = None, add_dct_basi # Check all items to be appended are Design Matrices and have the same sampling rate if not all([isinstance(elem,self.__class__) for elem in to_append]): - raise TypeError("Each object in list must be a Design_Matrix!") + raise TypeError("Each object to be appended must be a Design_Matrix!") if not all([elem.sampling_freq == self.sampling_freq for elem in to_append]): raise ValueError("All Design Matrices must have the same sampling rate!") if axis == 1: if any([not set(self.columns).isdisjoint(elem.columns) for elem in to_append]): print("Duplicate column names detected. Will be repeated.") - if add_poly or unique_cols: - print("add_poly and unique_cols only apply when axis=0...ignoring") - return self._horzcat(to_append) + return self._horzcat(to_append,fill_na=fill_na) elif axis == 0: - return self._vertcat(to_append, keep_separate=keep_separate,add_poly=add_poly,add_dct_basis=add_dct_basis,unique_cols=unique_cols,include_lower=include_lower,fill_na=fill_na) + return self._vertcat(to_append, keep_separate=keep_separate,unique_cols=unique_cols,fill_na=fill_na,verbose=verbose) else: raise ValueError("Axis must be 0 (row) or 1 (column)") - def _horzcat(self, to_append): + def _horzcat(self, to_append,fill_na): """Used by .append(). Append another design matrix, column-wise (horz cat). Always returns a new design_matrix. @@ -173,11 +169,13 @@ def _horzcat(self, to_append): out.polys = self.polys[:] for elem in to_append: out.polys += elem.polys + if fill_na is not None: + out = out.fillna(fill_na) else: raise ValueError("All Design Matrices must have the same number of rows!") return out - def _vertcat(self, df, keep_separate, add_poly, add_dct_basis, unique_cols, include_lower,fill_na): + def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): """Used by .append(). Append another design matrix row-wise (vert cat). Always returns a new design matrix. @@ -187,68 +185,99 @@ def _vertcat(self, df, keep_separate, add_poly, add_dct_basis, unique_cols, incl if not keep_separate: raise ValueError("unique_cols provided but keep_separate set to False. Set keep_separate to True to separate unique_cols") - to_append = df[:] # need to make a copy because we're altering df + to_append = df[:] # make a copy of the dms to append + orig = self.copy() # Make a copy of the original cause we might alter it + + # Start out assuming we can append normally without separation + all_dms = [orig] + to_append + all_polys = [] if keep_separate: - if not self.empty: - if not all([set(self.polys) == set(elem.polys) for elem in to_append]): - raise ValueError("Design matrices do not match on their polynomial terms (i.e. intercepts, polynomial trends, basis functions). This makes appending with separation ambigious and is not currently supported. Either make sure all constant terms are the same or make sure no Design Matrix has any constant terms and add them during appending with the 'add_poly' and 'unique_cols' arguments") - - orig = self.copy() # Make a copy of the original cause we might alter it - - if add_poly is not None: - orig = orig.add_poly(add_poly,include_lower) - for i,d in enumerate(to_append): - d = d.add_poly(add_poly,include_lower) - to_append[i] = d - - if add_dct_basis: - orig = orig.add_dct_basis(add_dct_basis) - for i,d in enumerate(to_append): - d = d.add_dct_basis(add_dct_basis) - to_append[i] = d - - all_cols = unique_cols + orig.polys - all_dms = [orig] + to_append - all_polys = [] - is_data = [] - for i,dm in enumerate(all_dms): - # Figure out what columns we need to relabel - cols_to_relabel = [col for col in dm.columns if col in all_cols] - if cols_to_relabel: - # Create a dictionary with the new names, e.g. {'intercept': '0_intercept'} - cols_dict = {} - # Rename the columns and update the dm - for c in cols_to_relabel: - cols_dict[c] = str(i) + '_' + c - if c not in unique_cols: - all_polys.append(cols_dict[c]) - dm = dm.rename(columns=cols_dict) - all_dms[i] = dm - elif len(dm.polys): - for p in dm.polys: - all_polys.append(p) - - out = pd.concat(all_dms,axis=0,ignore_index=True) - if fill_na is not None: - out = out.fillna(fill_na) + if not len(self.polys): + # Self no polys; append has polys + if any([len(elem.polys) for elem in to_append]): + if verbose: + print("Keep separate requested but original Design Matrix has no polynomial terms but matrices to be appended do. Inherting appended Design Matrices' polynomials...") + for dm in to_append: + for p in dm.polys: + all_polys.append(p) + else: + # Self no polys; append no polys + if verbose: + print("Keep separate requested but neither original Design Matrix nor matrices to be appended have any polynomial terms Ignoring...") + else: + # Self has polys; append has polys + if any([len(elem.polys) for elem in to_append]): + if verbose: + print("Keep separate requested and both original Design Matrix and matrices to be appended have polynomial terms. Separating...") + # Get the unique polynomials that currently exist + # [name, count/None, isRoot] + current_polys = [] + for p in self.polys: + if p.count('_') == 2: + isRoot = False + pSplit = p.split('_') + pName = '_'.join(pSplit[1:]) + pCount = int(pSplit[0]) + else: + isRoot = True + pName = p + pCount = 0 + current_polys.append([pName,pCount,isRoot]) + + # Mixed type numpy array to make things a little easier + current_polys = pd.DataFrame(current_polys).values + + # If current polynomials dont begin with a prepended numerical identifier, created one, e.g. 0_poly_1 + if any(current_polys[:,2]): + renamed_polys = {} + for i in range(current_polys.shape[0]): + renamed_polys[current_polys[i,0]] = str(current_polys[i,1]) + '_' + current_polys[i,0] + orig = orig.rename(columns = renamed_polys) + all_polys += list(renamed_polys.values()) + else: + all_polys += self.polys + + current_poly_max = current_polys[:,1].max() + for i,dm in enumerate(to_append): + to_rename = {} + for p in dm.polys: + if p.count('_') == 2: + pSplit = p.split('_') + pName = '_'.join(pSplit[1:]) + pCount = int(pSplit[0]) + current_poly_max + 1 + else: + pName = p + pCount = 0 + current_poly_max + 1 + to_rename[p] = str(pCount) + '_' + pName + to_append[i] = dm.rename(columns = to_rename) + current_poly_max += 1 + all_polys += list(to_rename.values()) + + # Recombine with the updated/renamed dms to be appended + all_dms = [orig] + to_append - out.sampling_freq = self.sampling_freq - out.convolved = self.convolved - out.polys = all_polys - data_cols = [elem for elem in out.columns if elem not in out.polys] - out = out[data_cols + out.polys] - else: - out = pd.concat([self] + to_append,axis=0,ignore_index=True) - out = self._inherit_attributes(out) - if add_poly: - out = out.add_poly(add_poly,include_lower) + else: + # Self has polys; append no polys + if verbose: + print("Keep separate requested but only original Design Matrix has polynomial terms. Retaining original Design Matrix's polynomials only...") + all_polys += self.polys + out = pd.concat(all_dms,axis=0,ignore_index=True) + + if fill_na is not None: + out = out.fillna(fill_na) + + out.sampling_freq = self.sampling_freq + out.convolved = self.convolved + out.polys = all_polys + data_cols = [elem for elem in out.columns if elem not in out.polys] + out = out[data_cols + out.polys] return out def vif(self): """Compute variance inflation factor amongst columns of design matrix, - ignoring the intercept. Much faster that statsmodels and more + ignoring polynomial terms. Much faster that statsmodels and more reliable too. Uses the same method as Matlab and R (diagonal elements of the inverted correlation matrix). @@ -267,7 +296,6 @@ def vif(self): except np.linalg.LinAlgError: print("ERROR: Cannot compute vifs! Design Matrix is singular because it has some perfectly correlated or duplicated columns. Using .clean() method may help.") - def heatmap(self, figsize=(8, 6), **kwargs): """Visualize Design Matrix spm style. Use .plot() for typical pandas plotting functionality. Can pass optional keyword args to seaborn @@ -301,13 +329,11 @@ def convolve(self, conv_func='hrf', columns=None): assert self.sampling_freq is not None, "Design_matrix has no sampling_freq set!" if columns is None: - columns = [col for col in self.columns if 'intercept' not in col and 'poly' not in col and 'cosine' not in col] + columns = [col for col in self.columns if 'poly' not in col and 'cosine' not in col] nonConvolved = [col for col in self.columns if col not in columns] if isinstance(conv_func,six.string_types): assert conv_func == 'hrf',"Did you mean 'hrf'? 'hrf' can generate a kernel for you, otherwise custom kernels should be passed in as 1d or 2d arrays." - - assert self.sampling_freq is not None, "Design_matrix sampling rate not set. Can't figure out how to generate HRF!" conv_func = glover_hrf(1. / self.sampling_freq, oversampling=1) else: @@ -402,24 +428,23 @@ def add_poly(self, order=0, include_lower=True): if order < 0: raise ValueError("Order must be 0 or greater") + if self.polys: + if 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 = {} - if order == 0 and 'intercept' in self.polys: - print("Design Matrix already has intercept...skipping") - return self - elif 'poly_'+str(order) in self.polys: + 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(0, order+1): - if i == 0: - if 'intercept' in self.polys: print("Design Matrix already has intercept...skipping") - else: - polyDict['intercept'] = np.repeat(1, self.shape[0]) + if 'poly_'+str(i) in self.polys: + print("Design Matrix already has {}th order polynomial...skipping".format(i)) else: - if 'poly_'+str(i) in self.polys: - print("Design Matrix already has {}th order polynomial...skipping".format(i)) + if i == 0: + polyDict['poly_0'] = np.repeat(1, self.shape[0]) else: # Unit scale polynomial terms so they don't blow up vals = np.arange(self.shape[0]) @@ -427,7 +452,7 @@ def add_poly(self, order=0, include_lower=True): polyDict['poly_' + str(i)] = vals ** i else: if order == 0: - polyDict['intercept'] = np.repeat(1, self.shape[0]) + polyDict['poly_0'] = np.repeat(1, self.shape[0]) else: polyDict['poly_'+str(order)] = (range(self.shape[0]) - np.mean(range(self.shape[0])))**order @@ -450,6 +475,11 @@ def add_dct_basis(self,duration=180): """ assert self.sampling_freq is not None, "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],self.sampling_freq,duration) basis_frame = Design_Matrix(basis_mat, diff --git a/nltools/stats.py b/nltools/stats.py index 3e57a151..ebc4e464 100644 --- a/nltools/stats.py +++ b/nltools/stats.py @@ -563,8 +563,7 @@ def make_cosine_basis(nsamples, sampling_rate, filter_length, drop=0): drop += 1 C = C[:, drop:] if C.size == 0: - raise ValueError('Basis function creation failed! nsamples is too small' - 'for requested filter_length.') + raise ValueError('Basis function creation failed! nsamples is too small for requested filter_length.') else: return C diff --git a/nltools/utils.py b/nltools/utils.py index 25943169..a9dee000 100644 --- a/nltools/utils.py +++ b/nltools/utils.py @@ -205,3 +205,6 @@ def _bootstrap_apply_func(data, function, random_state=None, *args, **kwargs): size=len(data_row_id), replace=True)] return getattr(new_dat, function)( *args, **kwargs) + +class AmbiguityError(Exception): + pass From 49c1050187720baa9fc4736169110846ac1fb68b Mon Sep 17 00:00:00 2001 From: ejolly Date: Fri, 27 Apr 2018 00:34:53 -0400 Subject: [PATCH 13/15] Design matrix now adds legendre polys and unit scaled cosine bases for better behaved regression. Fixed minor naming issues in stats. Unique cols still not working for append. Former-commit-id: 04bbe76946dd7b5654425c634d4fa8241104fe78 --- nltools/data/design_matrix.py | 53 +++++++++++++++++++++++------------ nltools/stats.py | 19 ++++++++----- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/nltools/data/design_matrix.py b/nltools/data/design_matrix.py index 363fab10..ec2d5c18 100644 --- a/nltools/data/design_matrix.py +++ b/nltools/data/design_matrix.py @@ -17,6 +17,7 @@ import seaborn as sns import matplotlib.pyplot as plt from scipy.stats import pearsonr +from scipy.special import legendre import six from ..external.hrf import glover_hrf from nltools.stats import (downsample, @@ -185,9 +186,29 @@ def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): if not keep_separate: raise ValueError("unique_cols provided but keep_separate set to False. Set keep_separate to True to separate unique_cols") + # Make sure unique_cols are in original Design Matrix + # cols_to_separate = [] + # to_rename = {} + # for u in unique_cols: + # if u.endswith('*'): + # searchstr = u.split('*')[0] + # elif u.startswith('*'): + # searchstr = u.split('*')[1] + # else: + # searchstr = u + # if not any([searchstr in elem for elem in self.columns]): + # raise ValueError("{} not part of any column name in original Design Matrix".format(searchstr)) + # else: + # for c in self.columns: + # if searchstr in c: + # to_rename[c] = '0_' + c + # + # cols_to_separate.append(searchstr) + to_append = df[:] # make a copy of the dms to append orig = self.copy() # Make a copy of the original cause we might alter it - + # if len(unique_cols): + # orig = orig.rename(columns=to_rename) # Start out assuming we can append normally without separation all_dms = [orig] + to_append all_polys = [] @@ -302,8 +323,9 @@ def heatmap(self, figsize=(8, 6), **kwargs): heatmap. """ + cmap = kwargs.pop('cmap','gray') fig, ax = plt.subplots(1, figsize=figsize) - ax = sns.heatmap(self, cmap='gray', cbar=False, ax=ax, **kwargs) + ax = sns.heatmap(self, cmap=cmap, cbar=False, ax=ax, **kwargs) for _, spine in ax.spines.items(): spine.set_visible(True) for i, label in enumerate(ax.get_yticklabels()): @@ -417,7 +439,7 @@ def zscore(self, columns=[]): return newMat def add_poly(self, order=0, include_lower=True): - """Add nth order polynomial terms as columns to design matrix. + """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 @@ -433,6 +455,8 @@ def add_poly(self, order=0, include_lower=True): 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)) @@ -443,18 +467,9 @@ def add_poly(self, order=0, include_lower=True): if 'poly_'+str(i) in self.polys: print("Design Matrix already has {}th order polynomial...skipping".format(i)) else: - if i == 0: - polyDict['poly_0'] = np.repeat(1, self.shape[0]) - else: - # Unit scale polynomial terms so they don't blow up - vals = np.arange(self.shape[0]) - vals = (vals - np.mean(vals)) / np.std(vals) - polyDict['poly_' + str(i)] = vals ** i + polyDict['poly_' + str(i)] = legendre(i)(norm_order) else: - if order == 0: - polyDict['poly_0'] = np.repeat(1, self.shape[0]) - else: - polyDict['poly_'+str(order)] = (range(self.shape[0]) - np.mean(range(self.shape[0])))**order + polyDict['poly_' + str(i)] = legendre(i)(norm_order) toAdd = Design_Matrix(polyDict,sampling_freq=self.sampling_freq) out = self.append(toAdd, axis=1) @@ -465,13 +480,14 @@ def add_poly(self, order=0, include_lower=True): out.polys = list(polyDict.keys()) return out - def add_dct_basis(self,duration=180): - """Adds cosine basis functions to Design_Matrix columns, + 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. + 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 """ assert self.sampling_freq is not None, "Design_Matrix has no sampling_freq set!" @@ -480,11 +496,12 @@ def add_dct_basis(self,duration=180): 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],self.sampling_freq,duration) + basis_mat = make_cosine_basis(self.shape[0],self.sampling_freq,duration,drop=drop) basis_frame = Design_Matrix(basis_mat, sampling_freq=self.sampling_freq) + basis_frame *= 1. / basis_frame.iat[0,0] basis_frame.columns = ['cosine_'+str(i+1) for i in range(basis_frame.shape[1])] if self.polys: diff --git a/nltools/stats.py b/nltools/stats.py index ebc4e464..4ef6daa2 100644 --- a/nltools/stats.py +++ b/nltools/stats.py @@ -523,8 +523,8 @@ def correlation_permutation(data1, data2, n_permute=5000, metric='spearman', stats['p'] = _calc_pvalue(all_p,stats['correlation'],tail) return stats -def make_cosine_basis(nsamples, sampling_rate, filter_length, drop=0): - """ Create a series of cosines basic functions for discrete cosine +def make_cosine_basis(nsamples, sampling_freq, filter_length, unit_scale=True, drop=0): + """ Create a series of cosine basis functions for a discrete cosine transform. Based off of implementation in spm_filter and spm_dctmtx because scipy dct can only apply transforms but not return the basis functions. Like SPM, does not add constant (i.e. intercept), but does @@ -532,12 +532,13 @@ def make_cosine_basis(nsamples, sampling_rate, filter_length, drop=0): Args: nsamples (int): number of observations (e.g. TRs) - sampling_freq (float): sampling rate in seconds (e.g. TR length) + sampling_freq (float): sampling frequency in hertz (i.e. 1 / TR) filter_length (int): length of filter in seconds + unit_scale (true): assure that the basis functions are on the normalized range [-1, 1]; default True drop (int): index of which early/slow bases to drop if any; default is to 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 0,1,2) + up to and inclusive of index provided (e.g. 2, drops bases 1 and 2) Returns: out (ndarray): nsamples x number of basis sets numpy array @@ -545,7 +546,7 @@ def make_cosine_basis(nsamples, sampling_rate, filter_length, drop=0): """ #Figure out number of basis functions to create - order = int(np.fix(2 * (nsamples * sampling_rate)/filter_length + 1)) + order = int(np.fix(2 * (nsamples * sampling_freq)/filter_length + 1)) n = np.arange(nsamples) @@ -559,8 +560,12 @@ def make_cosine_basis(nsamples, sampling_rate, filter_length, drop=0): for i in range(1,order): C[:,i] = np.sqrt(2./nsamples) * np.cos(np.pi*(2*n+1) * i/(2*nsamples)) - #Drop desired bases - drop += 1 + # Drop intercept ala SPM + C = C[:,1:] + + if unit_scale: + C *= 1. / C[0,0] + C = C[:, drop:] if C.size == 0: raise ValueError('Basis function creation failed! nsamples is too small for requested filter_length.') From 16634637f2769069a3447637f9e955ce5a323a7e Mon Sep 17 00:00:00 2001 From: ejolly Date: Fri, 27 Apr 2018 22:43:47 -0400 Subject: [PATCH 14/15] Design matrix can now keep multiple columns separated and has basic wildcard string matching. Better tests, and lots of clean ups and bug fixes to other methods. Former-commit-id: 52753dacf568591f9305006f3d5f569808861517 --- nltools/data/design_matrix.py | 243 +++++++++++++++++++++++++--------- nltools/tests/test_data.py | 163 +++++++++++------------ 2 files changed, 257 insertions(+), 149 deletions(-) diff --git a/nltools/data/design_matrix.py b/nltools/data/design_matrix.py index ec2d5c18..b30b033c 100644 --- a/nltools/data/design_matrix.py +++ b/nltools/data/design_matrix.py @@ -47,22 +47,16 @@ def _constructor_expanddim(self): class Design_Matrix(DataFrame): - """Design_Matrix is a class to represent design matrices with convenience - functionality for convolution, upsampling and downsampling. It plays - nicely with Brain_Data and can be used to build an experimental design - to pass to Brain_Data's X attribute. It is essentially an enhanced - pandas df, with extra attributes and methods. Methods always return a - new design matrix instance. + """Design_Matrix is a class to represent design matrices with special methods for data processing (e.g. convolution, upsampling, downsampling) and also intelligent and flexible and intelligent appending (e.g. auto-matically keep certain columns or polynomial terms separated during concatentation). It plays nicely with Brain_Data and can be used to build an experimental design to pass to Brain_Data's X attribute. It is essentially an enhanced pandas df, with extra attributes and methods. Methods always return a new design matrix instance (copy). Column names are always string types. Args: sampling_freq (float): sampling rate of each row in hertz; To covert seconds to hertz (e.g. in the case of TRs for neuroimaging) using hertz = 1 / TR convolved (list, optional): on what columns convolution has been performed; defaults to None polys (list, optional): list of polynomial terms in design matrix, e.g. intercept, polynomial trends, basis functions, etc; default None - """ - _metadata = ['sampling_freq', 'convolved', 'polys'] + _metadata = ['sampling_freq', 'convolved', 'polys', 'multi'] def __init__(self, *args, **kwargs): @@ -72,8 +66,12 @@ def __init__(self, *args, **kwargs): self.sampling_freq = sampling_freq self.convolved = convolved self.polys = polys + self.multi = False super(Design_Matrix, self).__init__(*args, **kwargs) + # Ensure that column names are string types to all methods work + if not self.empty: + self.columns = [str(elem) for elem in self.columns] @property def _constructor(self): @@ -88,7 +86,8 @@ def _inherit_attributes(self, atts=[ 'sampling_freq', 'convolved', - 'polys']): + 'polys', + 'multi']): """ This is helper function that simply ensures that attributes are copied over from the current Design_Matrix to a new Design_Matrix. @@ -110,11 +109,12 @@ def details(self): """Print class meta data. """ - return '%s.%s(sampling_freq=%s (hz), shape=%s, convolved=%s, polynomials=%s)' % ( + return '%s.%s(sampling_freq=%s (hz), shape=%s, multi=%s, convolved=%s, polynomials=%s)' % ( self.__class__.__module__, self.__class__.__name__, self.sampling_freq, self.shape, + self.multi, self.convolved, self.polys ) @@ -182,50 +182,113 @@ def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): """ + # make a copy of the dms to append + to_append = df[:] + orig = self.copy() # Make a copy of the original cause we might alter it + + # In order to append while keeping things separated we're going to create a new list of dataframes to append with renamed columns + modify_to_append = [] + all_polys = [] + cols_to_separate = [] + if len(unique_cols): if not keep_separate: raise ValueError("unique_cols provided but keep_separate set to False. Set keep_separate to True to separate unique_cols") - # Make sure unique_cols are in original Design Matrix - # cols_to_separate = [] - # to_rename = {} - # for u in unique_cols: - # if u.endswith('*'): - # searchstr = u.split('*')[0] - # elif u.startswith('*'): - # searchstr = u.split('*')[1] - # else: - # searchstr = u - # if not any([searchstr in elem for elem in self.columns]): - # raise ValueError("{} not part of any column name in original Design Matrix".format(searchstr)) - # else: - # for c in self.columns: - # if searchstr in c: - # to_rename[c] = '0_' + c - # - # cols_to_separate.append(searchstr) - - to_append = df[:] # make a copy of the dms to append - orig = self.copy() # Make a copy of the original cause we might alter it - # if len(unique_cols): - # orig = orig.rename(columns=to_rename) - # Start out assuming we can append normally without separation - all_dms = [orig] + to_append - all_polys = [] - + # 1) Make sure unique_cols are in original Design Matrix + if not self.empty: + to_rename = {} + unique_count = [] + for u in unique_cols: + if u.endswith('*'): + searchstr = u.split('*')[0] + elif u.startswith('*'): + searchstr = u.split('*')[1] + else: + searchstr = u + if not any([searchstr in elem for elem in self.columns]): + raise ValueError("'{}' not present in any column name of original Design Matrix".format(searchstr)) + # 2) Prepend them with a 0_ if this dm has never been appended to be for otherwise grab their current prepended index are and start a unique_cols counter + else: + for c in self.columns: + if searchstr in c: + if self.multi and c[0].isdigit(): + count = c.split('_')[0] + unique_count.append(int(count)) + else: + to_rename[c] = '0_' + c + cols_to_separate.append(searchstr) + + if to_rename: + orig = orig.rename(columns=to_rename) + max_unique_count = 0 + else: + max_unique_count = np.array(unique_count).max() + + # 3) Handle several different cases: + # a) original has no polys, dms to append do + # b) original has no polys, dms to append dont + # c) original has polys, dms to append do + # d) original has polys, dms to append dont + # Within each of these also keep a counter, update, and check for unique cols if needed + # This unique_col checking code is uglyly repeated in each conditional branch of a-d, but differs in subtle ways; probably could be cleaned up in a refactor if keep_separate: if not len(self.polys): - # Self no polys; append has polys + # Self no polys; append has polys. if any([len(elem.polys) for elem in to_append]): if verbose: print("Keep separate requested but original Design Matrix has no polynomial terms but matrices to be appended do. Inherting appended Design Matrices' polynomials...") - for dm in to_append: + for i,dm in enumerate(to_append): for p in dm.polys: all_polys.append(p) + + # Handle renaming additional unique cols to keep separate + if cols_to_separate: + if verbose: + print("Unique cols requested. Trying to keep {} separated".format(cols_to_separate)) + to_rename = {} + data_cols = dm.drop(dm.polys,axis=1).columns + print(data_cols) + for u in cols_to_separate: + for c in data_cols: + if u in c: + if dm.multi: + count = int(c.split('_')[0]) + name = '_'.join(c.split('_')[1:]) + count += max_unique_count + 1 + to_rename[c] = str(count) + '_' + name + else: + to_rename[c] = str(max_unique_count + 1) + '_' + c + + modify_to_append.append(dm.rename(columns=to_rename)) + max_unique_count += 1 + else: + modify_to_append.append(dm) else: # Self no polys; append no polys if verbose: print("Keep separate requested but neither original Design Matrix nor matrices to be appended have any polynomial terms Ignoring...") + # Handle renaming additional unique cols to keep separate + for i,dm in enumerate(to_append): + if cols_to_separate: + if verbose: + print("Unique cols requested. Trying to keep {} separated".format(cols_to_separate)) + to_rename = {} + data_cols = dm.drop(dm.polys,axis=1).columns + for u in cols_to_separate: + for c in data_cols: + if u in c: + if dm.multi: + count = int(c.split('_')[0]) + name = '_'.join(c.split('_')[1:]) + count += max_unique_count + 1 + to_rename[c] = str(count) + '_' + name + else: + to_rename[c] = str(max_unique_count + 1) + '_' + c + modify_to_append.append(dm.rename(columns=to_rename)) + max_unique_count += 1 + else: + modify_to_append.append(dm) else: # Self has polys; append has polys if any([len(elem.polys) for elem in to_append]): @@ -260,6 +323,7 @@ def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): all_polys += self.polys current_poly_max = current_polys[:,1].max() + for i,dm in enumerate(to_append): to_rename = {} for p in dm.polys: @@ -269,20 +333,67 @@ def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): pCount = int(pSplit[0]) + current_poly_max + 1 else: pName = p - pCount = 0 + current_poly_max + 1 + pCount = current_poly_max + 1 to_rename[p] = str(pCount) + '_' + pName - to_append[i] = dm.rename(columns = to_rename) + temp_dm = dm.rename(columns = to_rename) current_poly_max += 1 all_polys += list(to_rename.values()) - # Recombine with the updated/renamed dms to be appended - all_dms = [orig] + to_append + # Handle renaming additional unique cols to keep separate + if cols_to_separate: + if verbose: + print("Unique cols requested. Trying to keep {} separated".format(cols_to_separate)) + to_rename = {} + data_cols = dm.drop(dm.polys,axis=1).columns + for u in cols_to_separate: + for c in data_cols: + if u in c: + if dm.multi: + count = int(c.split('_')[0]) + name = '_'.join(c.split('_')[1:]) + count += max_unique_count + 1 + to_rename[c] = str(count) + '_' + name + else: + to_rename[c] = str(max_unique_count + 1) + '_' + c + + # Combine renamed polynomials and renamed uniqu_cols + modify_to_append.append(temp_dm.rename(columns=to_rename)) + max_unique_count += 1 + else: + modify_to_append.append(temp_dm) else: # Self has polys; append no polys if verbose: print("Keep separate requested but only original Design Matrix has polynomial terms. Retaining original Design Matrix's polynomials only...") all_polys += self.polys + + # Handle renaming additional unique cols to keep separate + if cols_to_separate: + if verbose: + print("Unique cols requested. Trying to keep {} separated".format(cols_to_separate)) + for i,dm in enumerate(to_append): + to_rename = {} + data_cols = dm.drop(dm.polys,axis=1).columns + for u in cols_to_separate: + for c in data_cols: + if u in c: + if dm.multi: + count = int(c.split('_')[0]) + name = '_'.join(c.split('_')[1:]) + count += max_unique_count + 1 + to_rename[c] = str(count) + '_' + name + else: + to_rename[c] = str(max_unique_count + 1) + '_' + c + + modify_to_append.append(dm.rename(to_rename)) + max_unique_count += 1 + else: + modify_to_append.append(dm) + + # Combine original dm with the updated/renamed dms to be appended + all_dms = [orig] + modify_to_append + out = pd.concat(all_dms,axis=0,ignore_index=True) if fill_na is not None: @@ -290,13 +401,14 @@ def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): out.sampling_freq = self.sampling_freq out.convolved = self.convolved + out.multi = True out.polys = all_polys data_cols = [elem for elem in out.columns if elem not in out.polys] out = out[data_cols + out.polys] return out - def vif(self): + def vif(self,exclude_polys=True): """Compute variance inflation factor amongst columns of design matrix, ignoring polynomial terms. Much faster that statsmodels and more reliable too. Uses the same method as Matlab and R (diagonal @@ -304,14 +416,16 @@ def vif(self): Returns: vifs (list): list with length == number of columns - intercept + exclude_polys (bool): whether to skip checking of polynomial terms (i.e. intercept, trends, basis functions); default True """ assert self.shape[1] > 1, "Can't compute vif with only 1 column!" - if self.polys: + if self.polys and exclude_polys: out = self.drop(self.polys,axis=1) else: - out = self[self.columns] - + # Always drop intercept before computing VIF + intercepts = [elem for elem in self.columns if 'poly_0' in str(elem)] + out = self.drop(intercepts,axis=1) try: return np.diag(np.linalg.inv(out.corr()), 0) except np.linalg.LinAlgError: @@ -343,26 +457,27 @@ def convolve(self, conv_func='hrf', columns=None): """Perform convolution using an arbitrary function. Args: - conv_func (ndarray or string): either a 1d numpy array containing output of a function that you want to convolve; a samples by kernel 2d array of several kernels to convolve; or th string 'hrf' which defaults to a glover HRF function at the Design_matrix's sampling_freq + conv_func (ndarray or string): either a 1d numpy array containing output of a function that you want to convolve; a samples by kernel 2d array of several kernels to convolve; or the string 'hrf' which defaults to a glover HRF function at the Design_matrix's sampling_freq columns (list): what columns to perform convolution on; defaults - to all skipping intercept, and columns containing 'poly' or 'cosine' + to all non-polynomial columns """ assert self.sampling_freq is not None, "Design_matrix has no sampling_freq set!" if columns is None: - columns = [col for col in self.columns if 'poly' not in col and 'cosine' not in col] + columns = [col for col in self.columns if col not in self.polys] nonConvolved = [col for col in self.columns if col not in columns] - if isinstance(conv_func,six.string_types): + if isinstance(conv_func, np.ndarray): + assert len(conv_func.shape) <= 2, "2d conv_func must be formatted as samplex X kernals!" + elif isinstance(conv_func, six.string_types): assert conv_func == 'hrf',"Did you mean 'hrf'? 'hrf' can generate a kernel for you, otherwise custom kernels should be passed in as 1d or 2d arrays." conv_func = glover_hrf(1. / self.sampling_freq, oversampling=1) else: - assert type(conv_func) == np.ndarray, 'Must provide a function for convolution!' + raise TypeError("conv_func must be a 1d or 2d numpy array organized as samples x kernels, or the string 'hrf' for the canonical glover hrf") if len(conv_func.shape) > 1: - assert conv_func.shape[0] > conv_func.shape[1], '2d conv_func must be formatted as, samples X kernels!' conv_mats = [] for i in range(conv_func.shape[1]): c = self[columns].apply(lambda x: np.convolve(x, conv_func[:,i])[:self.shape[0]]) @@ -388,7 +503,7 @@ def downsample(self, target,**kwargs): kwargs: additional inputs to nltools.stats.downsample """ - if target < self.sampling_freq: + if target > self.sampling_freq: raise ValueError("Target must be longer than current sampling rate") df = Design_Matrix(downsample(self, sampling_freq= self.sampling_freq, target=target,target_type='hz', **kwargs)) @@ -408,7 +523,7 @@ def upsample(self, target,**kwargs): kwargs: additional inputs to nltools.stats.downsample """ - if target > self.sampling_freq: + if target < self.sampling_freq: raise ValueError("Target must be shorter than current sampling rate") df = Design_Matrix(upsample(self, sampling_freq= self.sampling_freq, target=target, target_type='hz',**kwargs)) @@ -469,7 +584,7 @@ def add_poly(self, order=0, include_lower=True): else: polyDict['poly_' + str(i)] = legendre(i)(norm_order) else: - polyDict['poly_' + str(i)] = legendre(i)(norm_order) + polyDict['poly_' + str(order)] = legendre(order)(norm_order) toAdd = Design_Matrix(polyDict,sampling_freq=self.sampling_freq) out = self.append(toAdd, axis=1) @@ -499,9 +614,8 @@ def add_dct_basis(self,duration=180,drop=0): basis_mat = make_cosine_basis(self.shape[0],self.sampling_freq,duration,drop=drop) basis_frame = Design_Matrix(basis_mat, - sampling_freq=self.sampling_freq) + sampling_freq=self.sampling_freq,columns = [str(elem) for elem in range(basis_mat.shape[1])]) - basis_frame *= 1. / basis_frame.iat[0,0] basis_frame.columns = ['cosine_'+str(i+1) for i in range(basis_frame.shape[1])] if self.polys: @@ -541,13 +655,14 @@ def replace_data(self,data,column_names=None): else: raise TypeError("New data must be numpy array, pandas DataFrame or python dictionary type") - def clean(self,fill_na=0,exclude_polys=False,verbose=True): + def clean(self,fill_na=0,exclude_polys=False,thresh=.95,verbose=True): """ - Method to fill NaNs in Design Matrix and remove duplicate columns based on data values, NOT names. Columns are dropped if they cause the Design Matrix to become singular i.e. are perfectly correlated. In this case, only the first instance of that column will be retained and all others will be dropped. + Method to fill NaNs in Design Matrix and remove duplicate columns based on data values, NOT names. Columns are dropped if they are correlated >= the requested threshold (default = .95). In this case, only the first instance of that column will be retained and all others will be dropped. Args: fill_na (str/int/float): value to fill NaNs with set to None to retain NaNs; default 0 exclude_polys (bool): whether to skip checking of polynomial terms (i.e. intercept, trends, basis functions); default False + thresh (float): correlation threshold to use to drop redundant columns; default .95 verbose (bool): print what column names were dropped; default True """ @@ -565,15 +680,15 @@ def clean(self,fill_na=0,exclude_polys=False,verbose=True): for i, c in out.iteritems(): for j, c2 in out.iteritems(): if i != j: - r = pearsonr(c,c2)[0] - if (r > 0.99) and (j not in keep) and (j not in remove): + r = np.abs(pearsonr(c,c2)[0]) + if (r >= thresh) and (j not in keep) and (j not in remove): + if verbose: + print("{} and {} correlated at {} which is >= threshold of {}. Dropping {}".format(i,j,np.round(r,2),thresh,j)) keep.append(i) remove.append(j) if remove: out = out.drop(remove, axis=1) else: print("Dropping columns not needed...skipping") - if verbose: - print("Dropping columns: ", remove) np.seterr(**old_settings) return out diff --git a/nltools/tests/test_data.py b/nltools/tests/test_data.py index 2c142848..a4a9638f 100644 --- a/nltools/tests/test_data.py +++ b/nltools/tests/test_data.py @@ -10,6 +10,7 @@ Design_Matrix) from nltools.stats import threshold, align from nltools.mask import create_sphere +from nltools.external.hrf import glover_hrf from sklearn.metrics import pairwise_distances import matplotlib import matplotlib.pyplot as plt @@ -604,93 +605,85 @@ def test_groupby(tmpdir): def test_designmat(tmpdir): - mat1 = Design_Matrix({ - 'X':[1, 4, 2, 7, 5, 9, 2, 1, 3, 2], - 'Y':[3, 0, 0, 6, 9, 9, 10, 10, 1, 10], - 'Z':[2, 2, 2, 2, 7, 0, 1, 3, 3, 2], - 'intercept':[1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - }, - sampling_rate=2.0,polys=['intercept']) - - mat2 = Design_Matrix({ - 'X':[9, 9, 2, 7, 5, 0, 1, 1, 1, 2], - 'Y':[3, 3, 3, 6, 9, 0, 1, 10, 1, 10], - 'Z':[2, 6, 3, 2, 7, 0, 1, 7, 8, 8], - 'intercept':[1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - }, - sampling_rate=2.0, polys=['intercept']) + # Design matrices are specified in terms of sampling frequency + TR = 2.0 + sampling_freq = 1. / TR - # Appending - # Basic horz cat - new_mat = mat1.append(mat1,axis=1) - assert new_mat.shape == (mat1.shape[0], mat1.shape[1] + mat2.shape[1]) - both_cols = list(mat1.columns) + list(mat1.columns) - assert all(new_mat.columns == both_cols) - # Basic vert cat - new_mat = mat1.append(mat1,axis=0) - assert new_mat.shape == (mat1.shape[0]*2, mat1.shape[1]+1) - # Advanced vert cat - new_mat = mat1.append(mat1,axis=0,keep_separate=False) - assert new_mat.shape == (mat1.shape[0]*2,mat1.shape[1]) - # More advanced vert cat - new_mat = mat1.append(mat1,axis=0,add_poly=2) - assert new_mat.shape == (mat1.shape[0]*2, 9) - - #convolution doesn't affect intercept - assert all(mat1.convolve().iloc[:, -1] == mat1.iloc[:, -1]) - #but it still works - assert (mat1.convolve().iloc[:, :3].values != mat1.iloc[:, :3].values).any() - - #Test vifs - expectedVifs = np.array([ 1.03984251, 1.02889877, 1.02261945]) - assert np.allclose(expectedVifs,mat1.vif()) - - #poly - mat1.add_poly(order=4).shape[1] == mat1.shape[1]+4 - mat1.add_poly(order=4, include_lower=False).shape[1] == mat1.shape[1]+1 - - #zscore - z = mat1.zscore(columns=['X', 'Z']) - assert (z['Y'] == mat1['Y']).all() - assert z.shape == mat1.shape - - # clean - mat = Design_Matrix({ - 'X':[1, 4, 2, 7, 5, 9, 2, 1, 3, 2], - 'A':[1, 4, 2, 7, 5, 9, 2, 1, 3, 2], - 'Y':[3, 0, 0, 6, 9, 9, 10, 10, 1, 10], - 'Z':[2, 2, 2, 2, 7, 0, 1, 3, 3, 2], - 'C':[1, 4, 2, 7, 5, 9, 2, 1, 3, 2], - 'intercept':[1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - }, - sampling_rate=2.0,polys=['intercept']) - mat = mat[['X','A','Y','Z','C','intercept']] - assert all(mat.clean().columns == ['X','Y','Z','intercept']) + mat = Design_Matrix(np.random.randint(2,size=(500,4)),columns=['face_A','face_B','house_A','house_B'],sampling_freq=sampling_freq) + + # Basic ops + matp = mat.add_poly(2) + assert matp.shape[1] == 7 + assert mat.add_poly(2,include_lower=False).shape[1] == 5 + + matpd = matp.add_dct_basis() + assert matpd.shape[1] == 9 + + assert all(matpd.vif() < 2.0) + assert not all(matpd.vif(exclude_polys=False) < 2.0) + + matc = matpd.clean() + assert matc.shape[1] == 7 + + # Standard convolve + assert matpd.convolve().shape == matpd.shape + # Multi-kernal convolve + hrf = glover_hrf(TR,oversampling=1.) + assert matpd.convolve(conv_func=np.column_stack([hrf,hrf])).shape[1] == matpd.shape[1] + 4 + + matz = mat.zscore(columns = ['face_A','face_B']) + assert (matz[['house_A','house_B']] == mat[['house_A','house_B']]).all().all() # replace data - mat = Design_Matrix({ - 'X':[1, 4, 2, 7, 5, 9, 2, 1, 3, 2], - 'A':[1, 4, 2, 7, 5, 9, 2, 1, 3, 2], - 'Y':[3, 0, 0, 6, 9, 9, 10, 10, 1, 10], - 'Z':[2, 2, 2, 2, 7, 0, 1, 3, 3, 2], - 'C':[1, 4, 2, 7, 5, 9, 2, 1, 3, 2] - }, - sampling_rate=2.0) - - mat = mat.replace_data(np.ones((mat.shape[0],mat.shape[1]-1)),column_names=['a','b','c','d']) - - assert(np.allclose(mat.values,1)) - assert(all(mat.columns == ['a','b','c','d'])) - - #DCT basis_mat - mat = Design_Matrix(np.random.randint(2,size=(500,3)),sampling_rate=2.0) - mat = mat.add_dct_basis() - assert len(mat.polys) == 11 - assert mat.shape[1] == 14 + assert matpd.replace_data(np.zeros((500,4))).shape == matpd.shape #Up and down sampling - mat = Design_Matrix(np.random.randint(2,size=(500,4)),sampling_rate=2.0,columns=['a','b','c','d']) - target = 1 - assert mat.upsample(target).shape[0] == mat.shape[0]*2 - target*2 - target = 4 - assert mat.downsample(target).shape[0] == mat.shape[0]/2 + newTR = 1. + target = 1./newTR + assert matpd.upsample(target).shape[0] == matpd.shape[0]*2 - target*2 + newTR = 4. + target = 1./newTR + assert mat.downsample(target).shape[0] == matpd.shape[0]/2 + + # Appending + mats = matpd.append(matpd) + assert mats.shape[0] == mat.shape[0] * 2 + # Keep polys separate by default + assert (mats.shape[1] - 4) == (matpd.shape[1] - 4) * 2 + # Otherwise stack them + assert matpd.append(matpd,keep_separate=False).shape[1] == matpd.shape[1] + # Keep a single stimulus column separate + assert matpd.append(matpd,unique_cols=['face_A']).shape[1] == 15 + # Keep a common stimulus class separate + assert matpd.append(matpd,unique_cols=['face*']).shape[1] == 16 + # Keep a common stimulus class and a different single stim separate + assert matpd.append(matpd,unique_cols=['face*','house_A']).shape[1] == 17 + # Keep multiple stimulus class separate + assert matpd.append(matpd,unique_cols=['face*','house*']).shape[1] == 18 + + # Growing a multi-run design matrix; keeping things separate + num_runs = 4 + all_runs = Design_Matrix(sampling_freq=.5) + run_list = [] + for i in range(num_runs): + run = Design_Matrix(np.array([ + [1,0,0,0], + [1,0,0,0], + [0,0,0,0], + [0,1,0,0], + [0,1,0,0], + [0,0,0,0], + [0,0,1,0], + [0,0,1,0], + [0,0,0,0], + [0,0,0,1], + [0,0,0,1] + ]), + sampling_freq = .5, + columns=['stim_A','stim_B','cond_C','cond_D'] + ) + run = run.add_poly(2) + run_list.append(run) + all_runs = all_runs.append(run,unique_cols=['stim*','cond*']) + + assert all_runs.shape == (44, 28) From 774a3a61c14eaa3eb609a4df34b44c49b557e523 Mon Sep 17 00:00:00 2001 From: ejolly Date: Sat, 28 Apr 2018 01:33:02 -0400 Subject: [PATCH 15/15] Appending guarantees deterministic sort order. Rewrote and cleaned up Design Matrix tutorial. Former-commit-id: 0fa3615a341924b174387aea1f5eadb0f16d3d86 --- .../sphx_glr_plot_design_matrix_001.png | Bin 7069 -> 8721 bytes .../sphx_glr_plot_design_matrix_002.png | Bin 9490 -> 10489 bytes .../sphx_glr_plot_design_matrix_003.png | Bin 23891 -> 10764 bytes .../sphx_glr_plot_design_matrix_004.png | Bin 24033 -> 11943 bytes .../sphx_glr_plot_design_matrix_005.png | Bin 18025 -> 23373 bytes .../sphx_glr_plot_design_matrix_006.png | Bin 31245 -> 18025 bytes .../sphx_glr_plot_design_matrix_007.png | Bin 44258 -> 11607 bytes .../sphx_glr_plot_design_matrix_008.png | Bin 27736 -> 14147 bytes .../sphx_glr_plot_design_matrix_thumb.png | Bin 4356 -> 4986 bytes .../plot_design_matrix.ipynb | 78 ++--- .../01_DataOperations/plot_design_matrix.py | 201 +++++++++---- .../plot_design_matrix.py.md5 | 2 +- .../01_DataOperations/plot_design_matrix.rst | 284 +++++++++++------- .../plot_design_matrix_codeobj.pickle | Bin 411 -> 411 bytes .../plot_mask_codeobj.pickle | Bin 480 -> 480 bytes .../plot_neurovault_io_codeobj.pickle | Bin 280 -> 280 bytes .../plot_decomposition_codeobj.pickle | Bin 503 -> 503 bytes ...multivariate_classification_codeobj.pickle | Bin 380 -> 380 bytes .../plot_similarity_example_codeobj.pickle | Bin 228 -> 228 bytes .../plot_univariate_regression_codeobj.pickle | Bin 335 -> 335 bytes docs/auto_examples/auto_examples_jupyter.zip | Bin 85184 -> 79380 bytes docs/auto_examples/auto_examples_python.zip | Bin 56676 -> 52878 bytes docs/auto_examples/index.rst | 4 +- .../01_DataOperations/plot_design_matrix.py | 201 +++++++++---- nltools/data/design_matrix.py | 57 ++-- nltools/file_reader.py | 2 +- nltools/stats.py | 9 +- nltools/tests/test_data.py | 14 +- 28 files changed, 571 insertions(+), 281 deletions(-) diff --git a/docs/auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_001.png b/docs/auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_001.png index 529e65a3385e3b1bb130d38149573fecf55694f9..8e78acfbe665564c6075d9a895cbcd640e45f42a 100644 GIT binary patch literal 8721 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 literal 7069 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 diff --git a/docs/auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_002.png b/docs/auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_002.png index f9d2eae7e6c07b1c34287d2d89a9632212258742..28ae543b1862db2c9b4cf1e8de45495bae606618 100644 GIT binary patch literal 10489 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 diff --git a/docs/auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_003.png b/docs/auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_003.png index 88c53f1182a6336dae9c44efda7d9bcf461ed073..3b87be0423a6c035124971e50bc529991673b183 100644 GIT binary patch literal 10764 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 literal 23891 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 diff --git a/docs/auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_004.png b/docs/auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_004.png index b9af28889bea7b445adfa94cbaaa294ffdf69b9d..856c01b46e956a2245d190c40d706490df38dce5 100644 GIT binary patch literal 11943 zcmeHt2UL^kw)O`eWh|&13n~T}7z?5x0@5TnBZ`86f&)lZP!SPh=q-t5R73#_2-2d` zL_h?jm*`LxBtis)08t_(^iUE=A$NZ`|C#y!nYI3N=iYT@opmoRR?J88eeZtv-p_va zv)`LX%#0+LY*>OIh~%Mzh8762KpsKFZN6CqS7LpyT!fdudHrzc*f;PK{LQHlc)$4K z!Q);Cvf?EAEB4eN%?&QYx|OWzy8b?d(Ws% zkGCDHLL~pTAnWz2@s+5fvf414192MG{+I%)M zdgXVUJFhJMR$lqdB84-%F$&lP4>?j_$K$el28aRfP)^uL$?f<0NLtc!>!-QC?E zi|{VD*Q;+fPO)ghBgl-pQp(2y54*#OdvvZZV;`RzJ+gPv-7p!g13^JSO=<}8@!Ip} z&$T~YS`ZqZV{JiSbXU!#W;I3(xkMcIqmSl^?+V#7`6A=Ng9qyjh{2cL5XacyC+#+c zot`F5QnB6cPW+B1iW(`Uqb16+LBlz`g34Y$viPopVR8o8?#V>%YRrO9n_NHtd~Ltc z*>iJe-OGCGqopyAmEOf_np~EZU;Y|GCim!G1i5s6^&K%zOEyt6)huCy;(^;CF5x!L+HSM9yydHQ?=ok9M9gj+Mj<~ zHCQOQe%E(q+MoaCngmM`)w=mh)IHc?=iJ|zpmy@-pRk4IF+0B{PR23z^t+tPxBYM~ zpXY0CZoW@l9NF8Hd1>|2ygc=7QO1Oqg@w|X#R#&&SZuvKe~dy}hSAm7jqC30=e zs;#=Yiae=?!C;PNSq&v+WGEXQLy)bv#dqzswY7DwxxGHeo7K$oC@(KZ{}ZF>-s*)- zwO28+v$G?MA;`N1KQM2hwEargc>IBlJ|B*%dG$5S^hi_IMHtNamkl|LZPVR=Aj_64 zsY7&yQ#9sXBiWk^m##JR^Yhz?uae3+_2!-{K@Ec-j?ZAbeMgJ?Yl{05?Yx&E$hyd2 ziN7I8-2pLp(*JSEO28c%rynGcTsIGo9lAfz=r2z01IJrrSt-N27k?t-nZ^SPpS7GL zJb%_sqgkGS<(}Q6EWdDYc=$HNtlY@R$bKVnh_Ll_(YcY>m`s1$_Kr|ZD3vsIdH5-m*v<%Yq_4OB~ zLcY}GiCa8sY;1gbY}<5GcekVWcVOApg~@w=b$7l=4IoDND}(>K7kBKNK1CXTRKsKW?!43JQOG zlhuEG*?w{P-hA5~kfDUqw6wHW8nE5|RuD+-?dzkAQ?MO$`mOmuN`aJ3N>W7Q(A&pn z&3wwf{nlpTw&?n!D$%#XAqCq1=`C#&Tc3P=g`DiF&4;(@9-y&CM^$viUzync;wi<5 zJr>?SrA~dhZw}%2@8zjG8vr&BldrB&1OG^j7K~&vbYHlk*;_xfw=8m9-eXulN z^KWO^IXAbPZ|-lA#w4*KjUf&-$|EhBq%M7*nV+9O@QrCUgb%#9K>_%02u6C8i5PO$ z_g@l;zN4M%?FujKfJl6}^W+bJ6^?-ZzcCFr+4l~`Kd3VQ4W|8z^Q-{JkR58vRz
uph?;qO0K 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 Wy?5m7>vRNu4(&HHeE!3UEB^u0RRkgc literal 24033 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 Hy(j-43Nc&O literal 18025 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 diff --git a/docs/auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_007.png b/docs/auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_007.png index 6e385435561f407f36a5c3f921713971d620c6b9..a3a6f3f41265bed053b6401523736f7c0b35729f 100644 GIT binary patch literal 11607 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 literal 27736 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 nY&mgINybhJQz2v-zG8~3 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 literal 4356 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 diff --git a/docs/auto_examples/01_DataOperations/plot_design_matrix.ipynb b/docs/auto_examples/01_DataOperations/plot_design_matrix.ipynb index 19820921..6d348e83 100644 --- a/docs/auto_examples/01_DataOperations/plot_design_matrix.ipynb +++ b/docs/auto_examples/01_DataOperations/plot_design_matrix.ipynb @@ -15,7 +15,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\nDesign Matrix Creation\n======================\n\nThis tutorial illustrates how to use the Design_Matrix class to flexibly create design matrices that can then be used with the Brain_Data class to perform univariate regression.\n\nDesign Matrices can be thought of as \"enhanced\" pandas dataframes; they can do everything a pandas dataframe is capable of, with some added features. Design Matrices follow a data organization format common in many machine learning applications such as the sci-kit learn API: 2d tables organized as observations by features. In the context of neuro-imaging this often translates to TRs by conditions of interest + nuisance covariates (1st level analysis), or participants by conditions/groups (2nd level analysis).\n\n\n" + "\nDesign Matrix\n==============\n\nThis tutorial illustrates how to use the Design_Matrix class to flexibly create design matrices that can then be used with the Brain_Data class to perform univariate regression.\n\nDesign Matrices can be thought of as \"enhanced\" pandas dataframes; they can do everything a pandas dataframe is capable of, with some added features. Design Matrices follow a data organization format common in many machine learning applications such as the sci-kit learn API: 2d tables organized as observations by features. In the context of neuro-imaging this often translates to TRs by conditions of interest + nuisance covariates (1st level analysis), or participants by conditions/groups (2nd level analysis).\n\n\n" ] }, { @@ -33,7 +33,7 @@ }, "outputs": [], "source": [ - "from nltools.data import Design_Matrix\nimport numpy as np\n\ndm = Design_Matrix(np.array([\n [1,0,0,0],\n [1,0,0,0],\n [0,0,0,0],\n [0,1,0,0],\n [0,1,0,0],\n [0,0,0,0],\n [0,0,1,0],\n [0,0,1,0],\n [0,0,0,0],\n [0,0,0,1],\n [0,0,0,1]\n ]),\n sampling_rate = 1.5,\n columns=['stim_A','stim_B','stim_C','stim_D']\n )" + "from nltools.data import Design_Matrix\nimport numpy as np\n\nTR = 1.5 # Design Matrices take a sampling_freq argument specified in hertz which can be converted as 1./TR\n\ndm = Design_Matrix(np.array([\n [0,0,0,0],\n [0,0,0,0],\n [1,0,0,0],\n [1,0,0,0],\n [0,0,0,0],\n [0,1,0,0],\n [0,1,0,0],\n [0,0,0,0],\n [0,0,1,0],\n [0,0,1,0],\n [0,0,0,0],\n [0,0,0,1],\n [0,0,0,1],\n [0,0,0,0],\n [0,0,0,0],\n [0,0,0,0],\n [0,0,0,0],\n [0,0,0,0],\n [0,0,0,0],\n [0,0,0,0],\n [0,0,0,0],\n [0,0,0,0]\n ]),\n sampling_freq = 1./TR,\n columns=['face_A','face_B','house_A','house_B']\n )" ] }, { @@ -94,7 +94,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "A common operation might include adding an intercept and polynomial trend terms (e.g. linear and quadtratic) as nuisance regressors. This is easy to do. Note that polynomial terms are normalized to unit variance (i.e. mean = 0, std = 1) before inclusion to keep values on approximately the same scale.\n\n" + "Adding nuisiance covariates\n---------------------------\n\nLegendre Polynomials\n********************\n\nA common operation is adding an intercept and polynomial trend terms (e.g. linear and quadtratic) as nuisance regressors. This is easy to do. Consistent with other software packages, these are orthogonal Legendre poylnomials on the scale -1 to 1.\n\n" ] }, { @@ -105,7 +105,7 @@ }, "outputs": [], "source": [ - "# with include_lower = True (default), 1 here means: 0-intercept, 1-linear-trend, 2-quadtratic-trend\ndm_with_nuissance = dm.add_poly(2,include_lower=True)\ndm_with_nuissance.heatmap()" + "# with include_lower = True (default), 2 here means: 0-intercept, 1-linear-trend, 2-quadtratic-trend\ndm_with_nuissance = dm.add_poly(2,include_lower=True)\ndm_with_nuissance.heatmap()" ] }, { @@ -130,7 +130,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Polynomial variables are not the only type of nuisance covariates that can be generate for you. Design Matrix also supports the creation of discrete-cosine basis functions ala SPM. This will create a series of filters added as new columns based on a specified duration, defaulting to 180s.\n\n" + "Discrete Cosine Basis Functions\n*******************************\n\nPolynomial variables are not the only type of nuisance covariates that can be generated for you. Design Matrix also supports the creation of discrete-cosine basis functions ala SPM. This will create a series of filters added as new columns based on a specified duration, defaulting to 180s. Let's create DCT filters for 20s durations in our toy data.\n\n" ] }, { @@ -141,14 +141,14 @@ }, "outputs": [], "source": [ - "# Short filter duration for our simple example\ndm_with_cosine = dm.add_dct_basis(duration=5)\nprint(dm_with_cosine.details())" + "# Short filter duration for our simple example\ndm_with_cosine = dm.add_dct_basis(duration=20)\ndm_with_cosine.heatmap()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Load and Manipulate an Onsets File\n-----------------------------------\n\nNltools provides basic file-reading support for 2 or 3 column formatted onset files. Users can look at the onsets_to_dm function as a template to build more complex file readers if desired or to see additional features. Nltools includes an example onsets file where each event lasted exactly 1 TR. Lets use that to create a design matrix with an intercept and linear trend\n\n" + "Data operations\n---------------\n\nPerforming convolution\n**********************\n\nDesign Matrix makes it easy to perform convolution and will auto-ignore all columns that are consider polynomials. The default convolution kernel is the Glover (1999) HRF parameterized by the glover_hrf implementation in nipy (see nltools.externals.hrf for details). However, any arbitrary kernel can be passed as a 1d numpy array, or multiple kernels can be passed as a 2d numpy array for highly flexible convolution across many types of data (e.g. SCR).\n\n" ] }, { @@ -159,32 +159,21 @@ }, "outputs": [], "source": [ - "from nltools.utils import get_resource_path\nfrom nltools.file_reader import onsets_to_dm\nfrom nltools.data import Design_Matrix\nimport os\n\nonsetsFile = os.path.join(get_resource_path(),'onsets_example.txt')\ndm = onsets_to_dm(onsetsFile, TR=2.0, runLength=160, sort=True,add_poly=1)\ndm.heatmap()" + "dm_with_nuissance_c = dm_with_nuissance.convolve()\nprint(dm_with_nuissance_c.details())\ndm_with_nuissance_c.heatmap()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Design Matrix makes it easy to perform convolution and will auto-ignore all columns that are consider polynomials. By default it will use the one-parameter glover_hrf kernel (see nipy for details). However, any kernel can be passed as an argument, including a list of different kernels for highly flexible convolution across many types of data (e.g. SCR).\n\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "dm = dm.convolve()\nprint(dm.details())\ndm.heatmap()" + "Design Matrix can do many different data operations in addition to convolution such as upsampling and downsampling to different frequencies, zscoring, etc. Check out the API documentation for how to use these methods.\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Load and Z-score a Covariates File\n----------------------------------\n\nNow we're going to handle a covariates file that's been generated by a preprocessing routine. First we'll read in the text file using pandas and convert it to a design matrix.\n\n" + "File Reading\n------------\n\nCreating a Design Matrix from an onsets file\n********************************************\n\nNltools provides basic file-reading support for 2 or 3 column formatted onset files. Users can look at the onsets_to_dm function as a template to build more complex file readers if desired or to see additional features. Nltools includes an example onsets file where each event lasted exactly 1 TR and TR = 2s. Lets use that to create a design matrix with an intercept and linear trend\n\n" ] }, { @@ -195,14 +184,14 @@ }, "outputs": [], "source": [ - "import pandas as pd\n\ncovariatesFile = os.path.join(get_resource_path(),'covariates_example.csv')\ncov = pd.read_csv(covariatesFile)\ncov = Design_Matrix(cov, sampling_rate = 2.0)\n# Design matrix uses seaborn's heatmap for plotting so excepts all keyword arguments\n# We're just adjusting colors here to visualize things a bit more nicely\ncov.heatmap(vmin=-1,vmax=1)" + "from nltools.utils import get_resource_path\nfrom nltools.file_reader import onsets_to_dm\nfrom nltools.data import Design_Matrix\nimport os\n\nTR = 2.0\nsampling_freq = 1./TR\nonsetsFile = os.path.join(get_resource_path(),'onsets_example.txt')\ndm = onsets_to_dm(onsetsFile, sampling_freq=sampling_freq, run_length=160, sort=True,add_poly=1)\ndm.heatmap()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Similar to adding polynomial terms, Design Matrix has multiple methods for data processing and transformation such as downsampling, upsampling, and z-scoring. Let's use the z-score method to normalize the covariates we just loaded.\n\n" + "Creating a Design Matrix from a generic csv file\n************************************************\n\nAlternatively you can read a generic csv file and transform it into a Design Matrix using pandas file reading capability. Here we'll read in an example covariates file that contains the output of motion realignment estimated during a fMRI preprocessing pipeline.\n\n" ] }, { @@ -213,14 +202,14 @@ }, "outputs": [], "source": [ - "# Use pandas built-in fillna to fill NaNs in the covariates files introduced during the pre-processing pipeline, before z-scoring\n# Z-score takes an optional argument of which columns to z-score. Since we don't want to z-score any spikes, so let's select everything except that column\ncov = cov.fillna(0).zscore(cov.columns[:-1])\ncov.heatmap(vmin=-1,vmax=1)" + "import pandas as pd\n\ncovariatesFile = os.path.join(get_resource_path(),'covariates_example.csv')\ncov = pd.read_csv(covariatesFile)\ncov = Design_Matrix(cov, sampling_freq =sampling_freq)\ncov.heatmap(vmin=-1,vmax=1) # alter plot to scale of covs; heatmap takes Seaborn heatmap arguments" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Concatenate Multiple Design Matrices\n------------------------------------\n\nA really nice feature of Design Matrix is simplified, but intelligent matrix concatentation. Here it's trivial to horizontally concatenate our convolved onsets and covariates, while keeping our column names and order.\n\n" + "Working with multiple Design Matrices\n-------------------------------------\n\nVertically \"stacking\" Design Matrices\n*************************************\nA common task is creating a separate design matrix for multiple runs of an experiment, (or multiple subjects) and vertically appending them to each other so that regression can be performed across all runs of an experiment. However, in order to account for run-differences its important (and common practice) to include separate run-wise polynomials (e.g. intercepts). Design Matrix's append method is intelligent and flexible enough to keep columns separated during appending automatically.\n\n" ] }, { @@ -231,14 +220,14 @@ }, "outputs": [], "source": [ - "full = dm.append(cov,axis=1)\nfull.heatmap(vmin=-1,vmax=1)" + "# Lets use the design matrix with polynomials from above\n# Stack \"run 1\" on top of \"run 2\"\nruns_1_and_2 = dm_with_nuissance.append(dm_with_nuissance,axis=0)\nruns_1_and_2.heatmap()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "But we can also intelligently vertically concatenate design matrices to handle say, different experimental runs, or participants. The method enables the user to indicate which columns to keep separated (if any) during concatenation or which to treat as extensions along the first dimension. By default the class will keep all polylnomial terms separated. This is extremely useful when building 1 large design matrix composed of several runs or participants with separate means.\n\n" + "Separating columns during append operations\n*******************************************\nNotice that all polynomials have been kept separated for you automatically and have been renamed to reflect the fact that they come from different runs. But Design Matrix is even more flexible. Let's say you want to estimate separate run-wise coefficients for all house stimuli too. Simply pass that into the `unique_cols` parameter of append.\n\n" ] }, { @@ -249,14 +238,21 @@ }, "outputs": [], "source": [ - "dm2 = dm.append(dm, axis=0)\ndm2.heatmap(vmin=-1,vmax=1)" + "runs_1_and_2 = dm_with_nuissance.append(dm_with_nuissance,unique_cols=['house*'],axis=0)\nruns_1_and_2.heatmap()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now notice how all stimuli that begin with 'house' have been made into separate columns for each run. In general `unique_cols` can take a list of columns to keep separated or simple wild cards that either begin with a term e.g. \"house*\" or end with one \"*house\".\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Specific columns of interest can also be kept separate during concatenation (e.g. keeping run-wise spikes separate). As an example, we treat our first experimental regressor as different across our two design matrices. Notice that the class also preserves (as best as possible) column ordering.\n\n" + "Putting it all together\n-----------------------\n\nA realistic workflow\n********************\nLet's combine all the examples above to build a work flow for a realistic first-level analysis fMRI analysis. This will include loading onsets from multiple experimental runs, and concatenating them into a large multi-run design matrix where we estimate a single set of coefficients for our variables of interest, but make sure we account for run-wise differences nuisiance covarites (e.g. motion) and baseline, trends, etc. For simplicity we'll just reuse the same onsets and covariates file multiple times.\n\n" ] }, { @@ -267,14 +263,21 @@ }, "outputs": [], "source": [ - "dm2 = dm.append(dm, axis=0, unique_cols=['BillyRiggins'])\ndm2.heatmap(vmin=-1,vmax=1)" + "num_runs = 4\nTR = 2.0\nsampling_freq = 1./TR\nall_runs = Design_Matrix(sampling_freq = sampling_freq)\nfor i in range(num_runs):\n\n # 1) Load in onsets for this run\n onsetsFile = os.path.join(get_resource_path(),'onsets_example.txt')\n dm = onsets_to_dm(onsetsFile, sampling_freq=sampling_freq,run_length=160,sort=True)\n\n # 2) Convolve them with the hrf\n dm = dm.convolve()\n\n # 2) Load in covariates for this run\n covariatesFile = os.path.join(get_resource_path(),'covariates_example.csv')\n cov = pd.read_csv(covariatesFile)\n cov = Design_Matrix(cov, sampling_freq = sampling_freq)\n\n # 3) In the covariates, fill any NaNs with 0, add intercept and linear trends and dct basis functions\n cov = cov.fillna(0)\n\n # Retain a list of nuisance covariates (e.g. motion and spikes) which we'll also want to also keep separate for each run\n cov_columns = cov.columns\n cov = cov.add_poly(1).add_dct_basis()\n\n # 4) Join the onsets and covariates together\n full = dm.append(cov,axis=1)\n\n # 5) Append it to the master Design Matrix keeping things separated by run\n all_runs = all_runs.append(full,axis=0,unique_cols=cov.columns)\n\nall_runs.heatmap(vmin=-1,vmax=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see the left most columns of our multi-run design matrix contain our conditions of interest (stacked across all runs), the middle columns includes separate run-wise nuisiance covariates (motion, spikes) and the right most columns contain run specific polynomials (intercept, trends, etc).\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Design Matrix can also create polynomial terms and intelligently keep them separate during concatenation. For example lets concatenate 4 design matrices and create separate 2nd order polynomials for all of them\n\n" + "Data Diagnostics\n----------------\n\nLet's actually check if our design is estimable. Design Matrix provides a few tools for cleaning up highly correlated columns (resulting in failure if trying to perform regression), replacing data, and computing collinearity. By default the `clean` method will drop any columns correlated at r >= .95\n\n" ] }, { @@ -285,14 +288,21 @@ }, "outputs": [], "source": [ - "# Notice that append can take a list of Design Matrices in addition to just a single one\ndm_all = dm.append([dm,dm,dm], axis=0, add_poly=2)\ndm_all.heatmap(vmin=-1,vmax=1)" + "all_runs_cleaned = all_runs.clean(verbose=True)\nall_runs_cleaned.heatmap(vmin=-1,vmax=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Whoops, looks like above some of our polynomials and dct basis functions are highly correlated, but the clean method detected that and dropped them for us. In practice you'll often include polynomials or dct basis functions rather than both, but this was just an illustrative example.\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Data Diagnostics\n----------------\n\nDesign Matrix also provides a few tools for cleaning up perfectly correlated columns (resulting in failure if trying to perform regression), replacing data, and computing collinearity.\n\n" + "Estimating a first-level model\n------------------------------\n\nYou can now set this multi-run Design Matrix as the `X` attribute of a Brain_Data object containing EPI data for these four runs and estimate a regression in just a few lines of code.\n\n" ] }, { @@ -303,7 +313,7 @@ }, "outputs": [], "source": [ - "# We have a good design here so no problems\ndm_all.clean(verbose=False)\ndm_all.vif()" + "# This code is commented because we don't actually have niftis loaded for the purposes of this tutorial\n# See the other tutorials for more details on working with nifti files and Brain_Data objects\n\n# Assuming you already loaded up Nifti images like this\n# list_of_niftis = ['run_1.nii.gz','run_2.nii.gz','run_3.nii.gz','run_4.nii.gz']\n# all_run_data = Brain_Data(list_of_niftis)\n\n# Set our Design Matrix to the X attribute of Brain_Data object\n# all_run_data.X = all_runs_cleaned\n\n# Run the regression\n# results = all_run_data.regress()\n\n# This will produce N beta, t, and p images\n# where N is the number of columns in the design matrix" ] } ], diff --git a/docs/auto_examples/01_DataOperations/plot_design_matrix.py b/docs/auto_examples/01_DataOperations/plot_design_matrix.py index c22947d9..cc4ac352 100644 --- a/docs/auto_examples/01_DataOperations/plot_design_matrix.py +++ b/docs/auto_examples/01_DataOperations/plot_design_matrix.py @@ -1,6 +1,6 @@ """ -Design Matrix Creation -====================== +Design Matrix +============== This tutorial illustrates how to use the Design_Matrix class to flexibly create design matrices that can then be used with the Brain_Data class to perform univariate regression. @@ -17,7 +17,11 @@ from nltools.data import Design_Matrix import numpy as np +TR = 1.5 # Design Matrices take a sampling_freq argument specified in hertz which can be converted as 1./TR + dm = Design_Matrix(np.array([ + [0,0,0,0], + [0,0,0,0], [1,0,0,0], [1,0,0,0], [0,0,0,0], @@ -28,10 +32,19 @@ [0,0,1,0], [0,0,0,0], [0,0,0,1], - [0,0,0,1] + [0,0,0,1], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0] ]), - sampling_rate = 1.5, - columns=['stim_A','stim_B','stim_C','stim_D'] + sampling_freq = 1./TR, + columns=['face_A','face_B','house_A','house_B'] ) ######################################################################### # Notice how this look exactly like a pandas dataframe. That's because design matrices are *subclasses* of dataframes with some extra attributes and methods. @@ -48,10 +61,17 @@ dm.heatmap() + ######################################################################### -# A common operation might include adding an intercept and polynomial trend terms (e.g. linear and quadtratic) as nuisance regressors. This is easy to do. Note that polynomial terms are normalized to unit variance (i.e. mean = 0, std = 1) before inclusion to keep values on approximately the same scale. +# Adding nuisiance covariates +# --------------------------- +# +# Legendre Polynomials +# ******************** +# +# A common operation is adding an intercept and polynomial trend terms (e.g. linear and quadtratic) as nuisance regressors. This is easy to do. Consistent with other software packages, these are orthogonal Legendre poylnomials on the scale -1 to 1. -# with include_lower = True (default), 1 here means: 0-intercept, 1-linear-trend, 2-quadtratic-trend +# with include_lower = True (default), 2 here means: 0-intercept, 1-linear-trend, 2-quadtratic-trend dm_with_nuissance = dm.add_poly(2,include_lower=True) dm_with_nuissance.heatmap() @@ -61,91 +81,162 @@ print(dm_with_nuissance.details()) ######################################################################### -# Polynomial variables are not the only type of nuisance covariates that can be generate for you. Design Matrix also supports the creation of discrete-cosine basis functions ala SPM. This will create a series of filters added as new columns based on a specified duration, defaulting to 180s. +# Discrete Cosine Basis Functions +# ******************************* +# +# Polynomial variables are not the only type of nuisance covariates that can be generated for you. Design Matrix also supports the creation of discrete-cosine basis functions ala SPM. This will create a series of filters added as new columns based on a specified duration, defaulting to 180s. Let's create DCT filters for 20s durations in our toy data. # Short filter duration for our simple example -dm_with_cosine = dm.add_dct_basis(duration=5) -print(dm_with_cosine.details()) +dm_with_cosine = dm.add_dct_basis(duration=20) +dm_with_cosine.heatmap() ######################################################################### -# Load and Manipulate an Onsets File -# ----------------------------------- +# Data operations +# --------------- +# +# Performing convolution +# ********************** # -# Nltools provides basic file-reading support for 2 or 3 column formatted onset files. Users can look at the onsets_to_dm function as a template to build more complex file readers if desired or to see additional features. Nltools includes an example onsets file where each event lasted exactly 1 TR. Lets use that to create a design matrix with an intercept and linear trend +# Design Matrix makes it easy to perform convolution and will auto-ignore all columns that are consider polynomials. The default convolution kernel is the Glover (1999) HRF parameterized by the glover_hrf implementation in nipy (see nltools.externals.hrf for details). However, any arbitrary kernel can be passed as a 1d numpy array, or multiple kernels can be passed as a 2d numpy array for highly flexible convolution across many types of data (e.g. SCR). + +dm_with_nuissance_c = dm_with_nuissance.convolve() +print(dm_with_nuissance_c.details()) +dm_with_nuissance_c.heatmap() + +######################################################################### +# Design Matrix can do many different data operations in addition to convolution such as upsampling and downsampling to different frequencies, zscoring, etc. Check out the API documentation for how to use these methods. + +######################################################################### +# File Reading +# ------------ +# +# Creating a Design Matrix from an onsets file +# ******************************************** +# +# Nltools provides basic file-reading support for 2 or 3 column formatted onset files. Users can look at the onsets_to_dm function as a template to build more complex file readers if desired or to see additional features. Nltools includes an example onsets file where each event lasted exactly 1 TR and TR = 2s. Lets use that to create a design matrix with an intercept and linear trend from nltools.utils import get_resource_path from nltools.file_reader import onsets_to_dm from nltools.data import Design_Matrix import os +TR = 2.0 +sampling_freq = 1./TR onsetsFile = os.path.join(get_resource_path(),'onsets_example.txt') -dm = onsets_to_dm(onsetsFile, TR=2.0, runLength=160, sort=True,add_poly=1) -dm.heatmap() - -######################################################################### -# Design Matrix makes it easy to perform convolution and will auto-ignore all columns that are consider polynomials. By default it will use the one-parameter glover_hrf kernel (see nipy for details). However, any kernel can be passed as an argument, including a list of different kernels for highly flexible convolution across many types of data (e.g. SCR). - -dm = dm.convolve() -print(dm.details()) +dm = onsets_to_dm(onsetsFile, sampling_freq=sampling_freq, run_length=160, sort=True,add_poly=1) dm.heatmap() ######################################################################### -# Load and Z-score a Covariates File -# ---------------------------------- +# Creating a Design Matrix from a generic csv file +# ************************************************ # -# Now we're going to handle a covariates file that's been generated by a preprocessing routine. First we'll read in the text file using pandas and convert it to a design matrix. +# Alternatively you can read a generic csv file and transform it into a Design Matrix using pandas file reading capability. Here we'll read in an example covariates file that contains the output of motion realignment estimated during a fMRI preprocessing pipeline. import pandas as pd covariatesFile = os.path.join(get_resource_path(),'covariates_example.csv') cov = pd.read_csv(covariatesFile) -cov = Design_Matrix(cov, sampling_rate = 2.0) -# Design matrix uses seaborn's heatmap for plotting so excepts all keyword arguments -# We're just adjusting colors here to visualize things a bit more nicely -cov.heatmap(vmin=-1,vmax=1) +cov = Design_Matrix(cov, sampling_freq =sampling_freq) +cov.heatmap(vmin=-1,vmax=1) # alter plot to scale of covs; heatmap takes Seaborn heatmap arguments ######################################################################### -# Similar to adding polynomial terms, Design Matrix has multiple methods for data processing and transformation such as downsampling, upsampling, and z-scoring. Let's use the z-score method to normalize the covariates we just loaded. +# Working with multiple Design Matrices +# ------------------------------------- +# +# Vertically "stacking" Design Matrices +# ************************************* +# A common task is creating a separate design matrix for multiple runs of an experiment, (or multiple subjects) and vertically appending them to each other so that regression can be performed across all runs of an experiment. However, in order to account for run-differences its important (and common practice) to include separate run-wise polynomials (e.g. intercepts). Design Matrix's append method is intelligent and flexible enough to keep columns separated during appending automatically. -# Use pandas built-in fillna to fill NaNs in the covariates files introduced during the pre-processing pipeline, before z-scoring -# Z-score takes an optional argument of which columns to z-score. Since we don't want to z-score any spikes, so let's select everything except that column -cov = cov.fillna(0).zscore(cov.columns[:-1]) -cov.heatmap(vmin=-1,vmax=1) +# Lets use the design matrix with polynomials from above +# Stack "run 1" on top of "run 2" +runs_1_and_2 = dm_with_nuissance.append(dm_with_nuissance,axis=0) +runs_1_and_2.heatmap() ######################################################################### -# Concatenate Multiple Design Matrices -# ------------------------------------ -# -# A really nice feature of Design Matrix is simplified, but intelligent matrix concatentation. Here it's trivial to horizontally concatenate our convolved onsets and covariates, while keeping our column names and order. +# Separating columns during append operations +# ******************************************* +# Notice that all polynomials have been kept separated for you automatically and have been renamed to reflect the fact that they come from different runs. But Design Matrix is even more flexible. Let's say you want to estimate separate run-wise coefficients for all house stimuli too. Simply pass that into the `unique_cols` parameter of append. -full = dm.append(cov,axis=1) -full.heatmap(vmin=-1,vmax=1) +runs_1_and_2 = dm_with_nuissance.append(dm_with_nuissance,unique_cols=['house*'],axis=0) +runs_1_and_2.heatmap() ######################################################################### -# But we can also intelligently vertically concatenate design matrices to handle say, different experimental runs, or participants. The method enables the user to indicate which columns to keep separated (if any) during concatenation or which to treat as extensions along the first dimension. By default the class will keep all polylnomial terms separated. This is extremely useful when building 1 large design matrix composed of several runs or participants with separate means. - -dm2 = dm.append(dm, axis=0) -dm2.heatmap(vmin=-1,vmax=1) +# Now notice how all stimuli that begin with 'house' have been made into separate columns for each run. In general `unique_cols` can take a list of columns to keep separated or simple wild cards that either begin with a term e.g. "house*" or end with one "*house". ######################################################################### -# Specific columns of interest can also be kept separate during concatenation (e.g. keeping run-wise spikes separate). As an example, we treat our first experimental regressor as different across our two design matrices. Notice that the class also preserves (as best as possible) column ordering. +# Putting it all together +# ----------------------- +# +# A realistic workflow +# ******************** +# Let's combine all the examples above to build a work flow for a realistic first-level analysis fMRI analysis. This will include loading onsets from multiple experimental runs, and concatenating them into a large multi-run design matrix where we estimate a single set of coefficients for our variables of interest, but make sure we account for run-wise differences nuisiance covarites (e.g. motion) and baseline, trends, etc. For simplicity we'll just reuse the same onsets and covariates file multiple times. -dm2 = dm.append(dm, axis=0, unique_cols=['BillyRiggins']) -dm2.heatmap(vmin=-1,vmax=1) +num_runs = 4 +TR = 2.0 +sampling_freq = 1./TR +all_runs = Design_Matrix(sampling_freq = sampling_freq) +for i in range(num_runs): -######################################################################### -# Design Matrix can also create polynomial terms and intelligently keep them separate during concatenation. For example lets concatenate 4 design matrices and create separate 2nd order polynomials for all of them + # 1) Load in onsets for this run + onsetsFile = os.path.join(get_resource_path(),'onsets_example.txt') + dm = onsets_to_dm(onsetsFile, sampling_freq=sampling_freq,run_length=160,sort=True) + + # 2) Convolve them with the hrf + dm = dm.convolve() + + # 2) Load in covariates for this run + covariatesFile = os.path.join(get_resource_path(),'covariates_example.csv') + cov = pd.read_csv(covariatesFile) + cov = Design_Matrix(cov, sampling_freq = sampling_freq) + + # 3) In the covariates, fill any NaNs with 0, add intercept and linear trends and dct basis functions + cov = cov.fillna(0) + + # Retain a list of nuisance covariates (e.g. motion and spikes) which we'll also want to also keep separate for each run + cov_columns = cov.columns + cov = cov.add_poly(1).add_dct_basis() + + # 4) Join the onsets and covariates together + full = dm.append(cov,axis=1) -# Notice that append can take a list of Design Matrices in addition to just a single one -dm_all = dm.append([dm,dm,dm], axis=0, add_poly=2) -dm_all.heatmap(vmin=-1,vmax=1) + # 5) Append it to the master Design Matrix keeping things separated by run + all_runs = all_runs.append(full,axis=0,unique_cols=cov.columns) + +all_runs.heatmap(vmin=-1,vmax=1) + +######################################################################### +# We can see the left most columns of our multi-run design matrix contain our conditions of interest (stacked across all runs), the middle columns includes separate run-wise nuisiance covariates (motion, spikes) and the right most columns contain run specific polynomials (intercept, trends, etc). ######################################################################### # Data Diagnostics # ---------------- # -# Design Matrix also provides a few tools for cleaning up perfectly correlated columns (resulting in failure if trying to perform regression), replacing data, and computing collinearity. +# Let's actually check if our design is estimable. Design Matrix provides a few tools for cleaning up highly correlated columns (resulting in failure if trying to perform regression), replacing data, and computing collinearity. By default the `clean` method will drop any columns correlated at r >= .95 + +all_runs_cleaned = all_runs.clean(verbose=True) +all_runs_cleaned.heatmap(vmin=-1,vmax=1) + +######################################################################### +# Whoops, looks like above some of our polynomials and dct basis functions are highly correlated, but the clean method detected that and dropped them for us. In practice you'll often include polynomials or dct basis functions rather than both, but this was just an illustrative example. + +######################################################################### +# Estimating a first-level model +# ------------------------------ +# +# You can now set this multi-run Design Matrix as the `X` attribute of a Brain_Data object containing EPI data for these four runs and estimate a regression in just a few lines of code. + +# This code is commented because we don't actually have niftis loaded for the purposes of this tutorial +# See the other tutorials for more details on working with nifti files and Brain_Data objects + +# Assuming you already loaded up Nifti images like this +# list_of_niftis = ['run_1.nii.gz','run_2.nii.gz','run_3.nii.gz','run_4.nii.gz'] +# all_run_data = Brain_Data(list_of_niftis) + +# Set our Design Matrix to the X attribute of Brain_Data object +# all_run_data.X = all_runs_cleaned + +# Run the regression +# results = all_run_data.regress() -# We have a good design here so no problems -dm_all.clean(verbose=False) -dm_all.vif() +# This will produce N beta, t, and p images +# where N is the number of columns in the design matrix diff --git a/docs/auto_examples/01_DataOperations/plot_design_matrix.py.md5 b/docs/auto_examples/01_DataOperations/plot_design_matrix.py.md5 index 91f275b9..869cb99a 100644 --- a/docs/auto_examples/01_DataOperations/plot_design_matrix.py.md5 +++ b/docs/auto_examples/01_DataOperations/plot_design_matrix.py.md5 @@ -1 +1 @@ -674c1c7e55385d8ebc2e109812f5c3b8 \ No newline at end of file +d25dc9c6791965dca7df05760d6549bf \ No newline at end of file diff --git a/docs/auto_examples/01_DataOperations/plot_design_matrix.rst b/docs/auto_examples/01_DataOperations/plot_design_matrix.rst index f4cd77d0..bd4abb81 100644 --- a/docs/auto_examples/01_DataOperations/plot_design_matrix.rst +++ b/docs/auto_examples/01_DataOperations/plot_design_matrix.rst @@ -3,8 +3,8 @@ .. _sphx_glr_auto_examples_01_DataOperations_plot_design_matrix.py: -Design Matrix Creation -====================== +Design Matrix +============== This tutorial illustrates how to use the Design_Matrix class to flexibly create design matrices that can then be used with the Brain_Data class to perform univariate regression. @@ -25,7 +25,11 @@ Lets just create a basic toy design matrix by hand corresponding to a single par from nltools.data import Design_Matrix import numpy as np + TR = 1.5 # Design Matrices take a sampling_freq argument specified in hertz which can be converted as 1./TR + dm = Design_Matrix(np.array([ + [0,0,0,0], + [0,0,0,0], [1,0,0,0], [1,0,0,0], [0,0,0,0], @@ -36,10 +40,19 @@ Lets just create a basic toy design matrix by hand corresponding to a single par [0,0,1,0], [0,0,0,0], [0,0,0,1], - [0,0,0,1] + [0,0,0,1], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0] ]), - sampling_rate = 1.5, - columns=['stim_A','stim_B','stim_C','stim_D'] + sampling_freq = 1./TR, + columns=['face_A','face_B','house_A','house_B'] ) @@ -64,18 +77,29 @@ Notice how this look exactly like a pandas dataframe. That's because design matr Out:: - stim_A stim_B stim_C stim_D - 0 1 0 0 0 - 1 1 0 0 0 - 2 0 0 0 0 - 3 0 1 0 0 - 4 0 1 0 0 - 5 0 0 0 0 - 6 0 0 1 0 - 7 0 0 1 0 - 8 0 0 0 0 - 9 0 0 0 1 - 10 0 0 0 1 + face_A face_B house_A house_B + 0 0 0 0 0 + 1 0 0 0 0 + 2 1 0 0 0 + 3 1 0 0 0 + 4 0 0 0 0 + 5 0 1 0 0 + 6 0 1 0 0 + 7 0 0 0 0 + 8 0 0 1 0 + 9 0 0 1 0 + 10 0 0 0 0 + 11 0 0 0 1 + 12 0 0 0 1 + 13 0 0 0 0 + 14 0 0 0 0 + 15 0 0 0 0 + 16 0 0 0 0 + 17 0 0 0 0 + 18 0 0 0 0 + 19 0 0 0 0 + 20 0 0 0 0 + 21 0 0 0 0 Let's take a look at some of that meta-data. We can see that no columns have been convolved as of yet and this design matrix has no polynomial terms (e.g. such as an intercept or linear trend). @@ -95,7 +119,7 @@ Let's take a look at some of that meta-data. We can see that no columns have bee Out:: - nltools.data.design_matrix.Design_Matrix(sampling_rate=1.5, shape=(11, 4), convolved=[], polynomials=[]) + nltools.data.design_matrix.Design_Matrix(sampling_freq=0.6666666666666666 (hz), shape=(22, 4), multi=False, convolved=[], polynomials=[]) We can also easily visualize the design matrix using an SPM/AFNI/FSL style heatmap @@ -110,20 +134,27 @@ We can also easily visualize the design matrix using an SPM/AFNI/FSL style heatm + .. image:: /auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_001.png :align: center -A common operation might include adding an intercept and polynomial trend terms (e.g. linear and quadtratic) as nuisance regressors. This is easy to do. Note that polynomial terms are normalized to unit variance (i.e. mean = 0, std = 1) before inclusion to keep values on approximately the same scale. +Adding nuisiance covariates +--------------------------- + +Legendre Polynomials +******************** + +A common operation is adding an intercept and polynomial trend terms (e.g. linear and quadtratic) as nuisance regressors. This is easy to do. Consistent with other software packages, these are orthogonal Legendre poylnomials on the scale -1 to 1. .. code-block:: python - # with include_lower = True (default), 1 here means: 0-intercept, 1-linear-trend, 2-quadtratic-trend + # with include_lower = True (default), 2 here means: 0-intercept, 1-linear-trend, 2-quadtratic-trend dm_with_nuissance = dm.add_poly(2,include_lower=True) dm_with_nuissance.heatmap() @@ -153,10 +184,13 @@ We can see that 3 new columns were added to the design matrix. We can also inspe Out:: - nltools.data.design_matrix.Design_Matrix(sampling_rate=1.5, shape=(11, 7), convolved=[], polynomials=['intercept', 'poly_1', 'poly_2']) + nltools.data.design_matrix.Design_Matrix(sampling_freq=0.6666666666666666 (hz), shape=(22, 7), multi=False, convolved=[], polynomials=['poly_0', 'poly_1', 'poly_2']) -Polynomial variables are not the only type of nuisance covariates that can be generate for you. Design Matrix also supports the creation of discrete-cosine basis functions ala SPM. This will create a series of filters added as new columns based on a specified duration, defaulting to 180s. +Discrete Cosine Basis Functions +******************************* + +Polynomial variables are not the only type of nuisance covariates that can be generated for you. Design Matrix also supports the creation of discrete-cosine basis functions ala SPM. This will create a series of filters added as new columns based on a specified duration, defaulting to 180s. Let's create DCT filters for 20s durations in our toy data. @@ -164,77 +198,89 @@ Polynomial variables are not the only type of nuisance covariates that can be ge # Short filter duration for our simple example - dm_with_cosine = dm.add_dct_basis(duration=5) - print(dm_with_cosine.details()) + dm_with_cosine = dm.add_dct_basis(duration=20) + dm_with_cosine.heatmap() +.. image:: /auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_003.png + :align: center -.. rst-class:: sphx-glr-script-out - Out:: - nltools.data.design_matrix.Design_Matrix(sampling_rate=1.5, shape=(11, 10), convolved=[], polynomials=['cosine_1', 'cosine_2', 'cosine_3', 'cosine_4', 'cosine_5', 'cosine_6']) +Data operations +--------------- -Load and Manipulate an Onsets File ------------------------------------ +Performing convolution +********************** -Nltools provides basic file-reading support for 2 or 3 column formatted onset files. Users can look at the onsets_to_dm function as a template to build more complex file readers if desired or to see additional features. Nltools includes an example onsets file where each event lasted exactly 1 TR. Lets use that to create a design matrix with an intercept and linear trend +Design Matrix makes it easy to perform convolution and will auto-ignore all columns that are consider polynomials. The default convolution kernel is the Glover (1999) HRF parameterized by the glover_hrf implementation in nipy (see nltools.externals.hrf for details). However, any arbitrary kernel can be passed as a 1d numpy array, or multiple kernels can be passed as a 2d numpy array for highly flexible convolution across many types of data (e.g. SCR). .. code-block:: python - from nltools.utils import get_resource_path - from nltools.file_reader import onsets_to_dm - from nltools.data import Design_Matrix - import os - - onsetsFile = os.path.join(get_resource_path(),'onsets_example.txt') - dm = onsets_to_dm(onsetsFile, TR=2.0, runLength=160, sort=True,add_poly=1) - dm.heatmap() + dm_with_nuissance_c = dm_with_nuissance.convolve() + print(dm_with_nuissance_c.details()) + dm_with_nuissance_c.heatmap() -.. image:: /auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_003.png +.. image:: /auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_004.png :align: center +.. rst-class:: sphx-glr-script-out + + Out:: + + nltools.data.design_matrix.Design_Matrix(sampling_freq=0.6666666666666666 (hz), shape=(22, 7), multi=False, convolved=['face_A', 'face_B', 'house_A', 'house_B'], polynomials=['poly_0', 'poly_1', 'poly_2']) + + +Design Matrix can do many different data operations in addition to convolution such as upsampling and downsampling to different frequencies, zscoring, etc. Check out the API documentation for how to use these methods. -Design Matrix makes it easy to perform convolution and will auto-ignore all columns that are consider polynomials. By default it will use the one-parameter glover_hrf kernel (see nipy for details). However, any kernel can be passed as an argument, including a list of different kernels for highly flexible convolution across many types of data (e.g. SCR). +File Reading +------------ + +Creating a Design Matrix from an onsets file +******************************************** + +Nltools provides basic file-reading support for 2 or 3 column formatted onset files. Users can look at the onsets_to_dm function as a template to build more complex file readers if desired or to see additional features. Nltools includes an example onsets file where each event lasted exactly 1 TR and TR = 2s. Lets use that to create a design matrix with an intercept and linear trend .. code-block:: python - dm = dm.convolve() - print(dm.details()) + from nltools.utils import get_resource_path + from nltools.file_reader import onsets_to_dm + from nltools.data import Design_Matrix + import os + + TR = 2.0 + sampling_freq = 1./TR + onsetsFile = os.path.join(get_resource_path(),'onsets_example.txt') + dm = onsets_to_dm(onsetsFile, sampling_freq=sampling_freq, run_length=160, sort=True,add_poly=1) dm.heatmap() -.. image:: /auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_004.png +.. image:: /auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_005.png :align: center -.. rst-class:: sphx-glr-script-out - - Out:: - - nltools.data.design_matrix.Design_Matrix(sampling_rate=2.0, shape=(160, 15), convolved=['BillyRiggins', 'BuddyGarrity', 'CoachTaylor', 'GrandmaSaracen', 'JasonStreet', 'JulieTaylor', 'LandryClarke', 'LylaGarrity', 'MattSaracen', 'SmashWilliams', 'TamiTaylor', 'TimRiggins', 'TyraCollette'], polynomials=['intercept', 'poly_1']) -Load and Z-score a Covariates File ----------------------------------- +Creating a Design Matrix from a generic csv file +************************************************ -Now we're going to handle a covariates file that's been generated by a preprocessing routine. First we'll read in the text file using pandas and convert it to a design matrix. +Alternatively you can read a generic csv file and transform it into a Design Matrix using pandas file reading capability. Here we'll read in an example covariates file that contains the output of motion realignment estimated during a fMRI preprocessing pipeline. @@ -245,91 +291,112 @@ Now we're going to handle a covariates file that's been generated by a preproces covariatesFile = os.path.join(get_resource_path(),'covariates_example.csv') cov = pd.read_csv(covariatesFile) - cov = Design_Matrix(cov, sampling_rate = 2.0) - # Design matrix uses seaborn's heatmap for plotting so excepts all keyword arguments - # We're just adjusting colors here to visualize things a bit more nicely - cov.heatmap(vmin=-1,vmax=1) + cov = Design_Matrix(cov, sampling_freq =sampling_freq) + cov.heatmap(vmin=-1,vmax=1) # alter plot to scale of covs; heatmap takes Seaborn heatmap arguments -.. image:: /auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_005.png +.. image:: /auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_006.png :align: center -Similar to adding polynomial terms, Design Matrix has multiple methods for data processing and transformation such as downsampling, upsampling, and z-scoring. Let's use the z-score method to normalize the covariates we just loaded. +Working with multiple Design Matrices +------------------------------------- + +Vertically "stacking" Design Matrices +************************************* +A common task is creating a separate design matrix for multiple runs of an experiment, (or multiple subjects) and vertically appending them to each other so that regression can be performed across all runs of an experiment. However, in order to account for run-differences its important (and common practice) to include separate run-wise polynomials (e.g. intercepts). Design Matrix's append method is intelligent and flexible enough to keep columns separated during appending automatically. .. code-block:: python - # Use pandas built-in fillna to fill NaNs in the covariates files introduced during the pre-processing pipeline, before z-scoring - # Z-score takes an optional argument of which columns to z-score. Since we don't want to z-score any spikes, so let's select everything except that column - cov = cov.fillna(0).zscore(cov.columns[:-1]) - cov.heatmap(vmin=-1,vmax=1) + # Lets use the design matrix with polynomials from above + # Stack "run 1" on top of "run 2" + runs_1_and_2 = dm_with_nuissance.append(dm_with_nuissance,axis=0) + runs_1_and_2.heatmap() -.. image:: /auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_006.png +.. image:: /auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_007.png :align: center -Concatenate Multiple Design Matrices ------------------------------------- - -A really nice feature of Design Matrix is simplified, but intelligent matrix concatentation. Here it's trivial to horizontally concatenate our convolved onsets and covariates, while keeping our column names and order. +Separating columns during append operations +******************************************* +Notice that all polynomials have been kept separated for you automatically and have been renamed to reflect the fact that they come from different runs. But Design Matrix is even more flexible. Let's say you want to estimate separate run-wise coefficients for all house stimuli too. Simply pass that into the `unique_cols` parameter of append. .. code-block:: python - full = dm.append(cov,axis=1) - full.heatmap(vmin=-1,vmax=1) + runs_1_and_2 = dm_with_nuissance.append(dm_with_nuissance,unique_cols=['house*'],axis=0) + runs_1_and_2.heatmap() -.. image:: /auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_007.png +.. image:: /auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_008.png :align: center -But we can also intelligently vertically concatenate design matrices to handle say, different experimental runs, or participants. The method enables the user to indicate which columns to keep separated (if any) during concatenation or which to treat as extensions along the first dimension. By default the class will keep all polylnomial terms separated. This is extremely useful when building 1 large design matrix composed of several runs or participants with separate means. +Now notice how all stimuli that begin with 'house' have been made into separate columns for each run. In general `unique_cols` can take a list of columns to keep separated or simple wild cards that either begin with a term e.g. "house*" or end with one "*house". +Putting it all together +----------------------- -.. code-block:: python - - - dm2 = dm.append(dm, axis=0) - dm2.heatmap(vmin=-1,vmax=1) +A realistic workflow +******************** +Let's combine all the examples above to build a work flow for a realistic first-level analysis fMRI analysis. This will include loading onsets from multiple experimental runs, and concatenating them into a large multi-run design matrix where we estimate a single set of coefficients for our variables of interest, but make sure we account for run-wise differences nuisiance covarites (e.g. motion) and baseline, trends, etc. For simplicity we'll just reuse the same onsets and covariates file multiple times. +.. code-block:: python -.. image:: /auto_examples/01_DataOperations/images/sphx_glr_plot_design_matrix_008.png - :align: center + num_runs = 4 + TR = 2.0 + sampling_freq = 1./TR + all_runs = Design_Matrix(sampling_freq = sampling_freq) + for i in range(num_runs): + # 1) Load in onsets for this run + onsetsFile = os.path.join(get_resource_path(),'onsets_example.txt') + dm = onsets_to_dm(onsetsFile, sampling_freq=sampling_freq,run_length=160,sort=True) + # 2) Convolve them with the hrf + dm = dm.convolve() -Specific columns of interest can also be kept separate during concatenation (e.g. keeping run-wise spikes separate). As an example, we treat our first experimental regressor as different across our two design matrices. Notice that the class also preserves (as best as possible) column ordering. + # 2) Load in covariates for this run + covariatesFile = os.path.join(get_resource_path(),'covariates_example.csv') + cov = pd.read_csv(covariatesFile) + cov = Design_Matrix(cov, sampling_freq = sampling_freq) + # 3) In the covariates, fill any NaNs with 0, add intercept and linear trends and dct basis functions + cov = cov.fillna(0) + # Retain a list of nuisance covariates (e.g. motion and spikes) which we'll also want to also keep separate for each run + cov_columns = cov.columns + cov = cov.add_poly(1).add_dct_basis() -.. code-block:: python + # 4) Join the onsets and covariates together + full = dm.append(cov,axis=1) + # 5) Append it to the master Design Matrix keeping things separated by run + all_runs = all_runs.append(full,axis=0,unique_cols=cov.columns) - dm2 = dm.append(dm, axis=0, unique_cols=['BillyRiggins']) - dm2.heatmap(vmin=-1,vmax=1) + all_runs.heatmap(vmin=-1,vmax=1) @@ -340,16 +407,21 @@ Specific columns of interest can also be kept separate during concatenation (e.g -Design Matrix can also create polynomial terms and intelligently keep them separate during concatenation. For example lets concatenate 4 design matrices and create separate 2nd order polynomials for all of them +We can see the left most columns of our multi-run design matrix contain our conditions of interest (stacked across all runs), the middle columns includes separate run-wise nuisiance covariates (motion, spikes) and the right most columns contain run specific polynomials (intercept, trends, etc). + + +Data Diagnostics +---------------- + +Let's actually check if our design is estimable. Design Matrix provides a few tools for cleaning up highly correlated columns (resulting in failure if trying to perform regression), replacing data, and computing collinearity. By default the `clean` method will drop any columns correlated at r >= .95 .. code-block:: python - # Notice that append can take a list of Design Matrices in addition to just a single one - dm_all = dm.append([dm,dm,dm], axis=0, add_poly=2) - dm_all.heatmap(vmin=-1,vmax=1) + all_runs_cleaned = all_runs.clean(verbose=True) + all_runs_cleaned.heatmap(vmin=-1,vmax=1) @@ -362,41 +434,47 @@ Design Matrix can also create polynomial terms and intelligently keep them separ Out:: - Design Matrix already has intercept...skipping - Design Matrix already has 1th order polynomial...skipping - Design Matrix already has intercept...skipping - Design Matrix already has 1th order polynomial...skipping - Design Matrix already has intercept...skipping - Design Matrix already has 1th order polynomial...skipping - Design Matrix already has intercept...skipping - Design Matrix already has 1th order polynomial...skipping + 0_poly_1 and 0_cosine_1 correlated at 0.99 which is >= threshold of 0.95. Dropping 0_cosine_1 + 1_poly_1 and 1_cosine_1 correlated at 0.99 which is >= threshold of 0.95. Dropping 1_cosine_1 + 2_poly_1 and 2_cosine_1 correlated at 0.99 which is >= threshold of 0.95. Dropping 2_cosine_1 + 3_poly_1 and 3_cosine_1 correlated at 0.99 which is >= threshold of 0.95. Dropping 3_cosine_1 -Data Diagnostics ----------------- +Whoops, looks like above some of our polynomials and dct basis functions are highly correlated, but the clean method detected that and dropped them for us. In practice you'll often include polynomials or dct basis functions rather than both, but this was just an illustrative example. + + +Estimating a first-level model +------------------------------ -Design Matrix also provides a few tools for cleaning up perfectly correlated columns (resulting in failure if trying to perform regression), replacing data, and computing collinearity. +You can now set this multi-run Design Matrix as the `X` attribute of a Brain_Data object containing EPI data for these four runs and estimate a regression in just a few lines of code. .. code-block:: python - # We have a good design here so no problems - dm_all.clean(verbose=False) - dm_all.vif() + # This code is commented because we don't actually have niftis loaded for the purposes of this tutorial + # See the other tutorials for more details on working with nifti files and Brain_Data objects + # Assuming you already loaded up Nifti images like this + # list_of_niftis = ['run_1.nii.gz','run_2.nii.gz','run_3.nii.gz','run_4.nii.gz'] + # all_run_data = Brain_Data(list_of_niftis) + # Set our Design Matrix to the X attribute of Brain_Data object + # all_run_data.X = all_runs_cleaned + + # Run the regression + # results = all_run_data.regress() + + # This will produce N beta, t, and p images + # where N is the number of columns in the design matrix -.. rst-class:: sphx-glr-script-out - Out:: - Dropping columns not needed...skipping -**Total running time of the script:** ( 0 minutes 1.402 seconds) +**Total running time of the script:** ( 0 minutes 2.540 seconds) diff --git a/docs/auto_examples/01_DataOperations/plot_design_matrix_codeobj.pickle b/docs/auto_examples/01_DataOperations/plot_design_matrix_codeobj.pickle index ea62ebef59fc3f00c4145c7fdc5aba3da6e9cc9e..280911a60f508159c54f05586ad56a2d77ad03f5 100644 GIT binary patch delta 183 zcmbQuJe#?`fo19hMg}mboubjh>ylcWnVuK#n^;nmSuq79#FCeon>qzuriU#zKczGW zD8ZALQ<9&bQ>>SgSds|jLd4^XGxCc{ru48wWv7((@Z{$er p70h6rXfMW;!44!RR;e)wmQGG!6qy{&XgBeK>BPm_Eb^tLdH@J*Le>BP delta 177 zcmbQuJe%3Rfo19hMg}mboubjhlb=_dT2dTek{_RvI|U@dl9!m9It5Llhb=chr8FmX z%EW##{vI9(C%!l%zo-NxG;yyQBhTb4MqNgM$!3gZj=V0Z#hK}O@xF;AMVS>KV>6h0 l&;&DBdwBA4O7in_iuF&dejohJt{dQW_v%%WOass{jaDhUGs delta 22 ecmaFB{D67F>&caj$&AvIbs2pozIJ9R)dK)&FbF>Y diff --git a/docs/auto_examples/01_DataOperations/plot_neurovault_io_codeobj.pickle b/docs/auto_examples/01_DataOperations/plot_neurovault_io_codeobj.pickle index 6c283062093bb1c23493f86a23946f37b1b33353..3852af7b9c871d477aba18caea45bf113849efe7 100644 GIT binary patch literal 280 zcmZvXK?=e!5JeTM1*uT*60ULx@g8ApLKB!~q?zDK@BmrH`Ir`OoU!2VDOrKirtS%Z)oP)Mu`0b ziRFmky}Zw0mqfFx=w_cIENYmYC`WAFm`qy{Lp)xKmAvI9<%8_ERv(f1O`ZyR4llvdW*gOHC CtZX9y diff --git a/docs/auto_examples/02_Analysis/plot_decomposition_codeobj.pickle b/docs/auto_examples/02_Analysis/plot_decomposition_codeobj.pickle index 2ed98b882beb357dd3044766defff5b484ce239c..f44a13cd1669767e6daa454c4251f047de6c1f8d 100644 GIT binary patch delta 163 zcmey){GBYWMI}=**d~7QnB2x_%P27Mfc3;iM;-3sykfnK)Wnk9!~&41 m8O%NGFm48G4|{QHVp4ul-jodX4B5$6j6M^?lv&hDOZ5O2lsjbr delta 165 zcmey){GB|fn{nTBLf)JPSNP$DM-fY`OU+7zRbk|x_}^z@iLok2UV&a!YEgbM$jl7p e9##k^gSCe>uQa!ya!Lk!hU~-}YAlMSrFsBc+&51E delta 154 zcmeyv^oPl}fn{nTBLf)JPSNP$$ScsRN-fGSo&w^rv?f?J) diff --git a/docs/auto_examples/02_Analysis/plot_similarity_example_codeobj.pickle b/docs/auto_examples/02_Analysis/plot_similarity_example_codeobj.pickle index 465f2e72feec34a302704d40ae62ac1283fc121b..a5e3374db374b9789a6b24698850eb32a004c75c 100644 GIT binary patch literal 228 zcmZvWO$x#=5QPO>TO^Af!&RCjS-x$!d2VDFjS0Z4TMN$MDow8m+5jkbbZx$PR)JkYjx~-$rXe5X~*Wg#WF%KQ?u) z`6#`Q>H>DLxonNs&Sq(=Naw=DSirN3YnGwHm>?{ap<~&fEuCPDdiJFN+b@Au4jw54 rOls1klckhl_Xk;qh@=vmh~f2ATm5G*C8wzzaW*QhsLi|dgUsp)OF&nY diff --git a/docs/auto_examples/02_Analysis/plot_univariate_regression_codeobj.pickle b/docs/auto_examples/02_Analysis/plot_univariate_regression_codeobj.pickle index 25181d70e16575301b1c02ea6a53955513df1b04..92b31f0436dc97ee68e3227f8b3ce56b8bc2e360 100644 GIT binary patch delta 144 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 SSuK&4$mM7x@}F%Qp8prRCcA9_ diff --git a/docs/auto_examples/auto_examples_python.zip b/docs/auto_examples/auto_examples_python.zip index 6b09ddff859c62cb1350ed5877d1b940d396d310..4862341fb847c3d2ea5fcbc6408936e3846b8024 100644 GIT binary patch delta 7281 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 GjQ;|PG|E^2 delta 4041 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 diff --git a/docs/auto_examples/index.rst b/docs/auto_examples/index.rst index 8b1af970..5ff409e2 100644 --- a/docs/auto_examples/index.rst +++ b/docs/auto_examples/index.rst @@ -235,13 +235,13 @@ Neuroimaging Analysis Examples .. container:: sphx-glr-download - :download:`Download all examples in Python source code: auto_examples_python.zip ` + :download:`Download all examples in Python source code: auto_examples_python.zip ` .. container:: sphx-glr-download - :download:`Download all examples in Jupyter notebooks: auto_examples_jupyter.zip ` + :download:`Download all examples in Jupyter notebooks: auto_examples_jupyter.zip ` .. only:: html diff --git a/examples/01_DataOperations/plot_design_matrix.py b/examples/01_DataOperations/plot_design_matrix.py index c22947d9..cc4ac352 100644 --- a/examples/01_DataOperations/plot_design_matrix.py +++ b/examples/01_DataOperations/plot_design_matrix.py @@ -1,6 +1,6 @@ """ -Design Matrix Creation -====================== +Design Matrix +============== This tutorial illustrates how to use the Design_Matrix class to flexibly create design matrices that can then be used with the Brain_Data class to perform univariate regression. @@ -17,7 +17,11 @@ from nltools.data import Design_Matrix import numpy as np +TR = 1.5 # Design Matrices take a sampling_freq argument specified in hertz which can be converted as 1./TR + dm = Design_Matrix(np.array([ + [0,0,0,0], + [0,0,0,0], [1,0,0,0], [1,0,0,0], [0,0,0,0], @@ -28,10 +32,19 @@ [0,0,1,0], [0,0,0,0], [0,0,0,1], - [0,0,0,1] + [0,0,0,1], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0], + [0,0,0,0] ]), - sampling_rate = 1.5, - columns=['stim_A','stim_B','stim_C','stim_D'] + sampling_freq = 1./TR, + columns=['face_A','face_B','house_A','house_B'] ) ######################################################################### # Notice how this look exactly like a pandas dataframe. That's because design matrices are *subclasses* of dataframes with some extra attributes and methods. @@ -48,10 +61,17 @@ dm.heatmap() + ######################################################################### -# A common operation might include adding an intercept and polynomial trend terms (e.g. linear and quadtratic) as nuisance regressors. This is easy to do. Note that polynomial terms are normalized to unit variance (i.e. mean = 0, std = 1) before inclusion to keep values on approximately the same scale. +# Adding nuisiance covariates +# --------------------------- +# +# Legendre Polynomials +# ******************** +# +# A common operation is adding an intercept and polynomial trend terms (e.g. linear and quadtratic) as nuisance regressors. This is easy to do. Consistent with other software packages, these are orthogonal Legendre poylnomials on the scale -1 to 1. -# with include_lower = True (default), 1 here means: 0-intercept, 1-linear-trend, 2-quadtratic-trend +# with include_lower = True (default), 2 here means: 0-intercept, 1-linear-trend, 2-quadtratic-trend dm_with_nuissance = dm.add_poly(2,include_lower=True) dm_with_nuissance.heatmap() @@ -61,91 +81,162 @@ print(dm_with_nuissance.details()) ######################################################################### -# Polynomial variables are not the only type of nuisance covariates that can be generate for you. Design Matrix also supports the creation of discrete-cosine basis functions ala SPM. This will create a series of filters added as new columns based on a specified duration, defaulting to 180s. +# Discrete Cosine Basis Functions +# ******************************* +# +# Polynomial variables are not the only type of nuisance covariates that can be generated for you. Design Matrix also supports the creation of discrete-cosine basis functions ala SPM. This will create a series of filters added as new columns based on a specified duration, defaulting to 180s. Let's create DCT filters for 20s durations in our toy data. # Short filter duration for our simple example -dm_with_cosine = dm.add_dct_basis(duration=5) -print(dm_with_cosine.details()) +dm_with_cosine = dm.add_dct_basis(duration=20) +dm_with_cosine.heatmap() ######################################################################### -# Load and Manipulate an Onsets File -# ----------------------------------- +# Data operations +# --------------- +# +# Performing convolution +# ********************** # -# Nltools provides basic file-reading support for 2 or 3 column formatted onset files. Users can look at the onsets_to_dm function as a template to build more complex file readers if desired or to see additional features. Nltools includes an example onsets file where each event lasted exactly 1 TR. Lets use that to create a design matrix with an intercept and linear trend +# Design Matrix makes it easy to perform convolution and will auto-ignore all columns that are consider polynomials. The default convolution kernel is the Glover (1999) HRF parameterized by the glover_hrf implementation in nipy (see nltools.externals.hrf for details). However, any arbitrary kernel can be passed as a 1d numpy array, or multiple kernels can be passed as a 2d numpy array for highly flexible convolution across many types of data (e.g. SCR). + +dm_with_nuissance_c = dm_with_nuissance.convolve() +print(dm_with_nuissance_c.details()) +dm_with_nuissance_c.heatmap() + +######################################################################### +# Design Matrix can do many different data operations in addition to convolution such as upsampling and downsampling to different frequencies, zscoring, etc. Check out the API documentation for how to use these methods. + +######################################################################### +# File Reading +# ------------ +# +# Creating a Design Matrix from an onsets file +# ******************************************** +# +# Nltools provides basic file-reading support for 2 or 3 column formatted onset files. Users can look at the onsets_to_dm function as a template to build more complex file readers if desired or to see additional features. Nltools includes an example onsets file where each event lasted exactly 1 TR and TR = 2s. Lets use that to create a design matrix with an intercept and linear trend from nltools.utils import get_resource_path from nltools.file_reader import onsets_to_dm from nltools.data import Design_Matrix import os +TR = 2.0 +sampling_freq = 1./TR onsetsFile = os.path.join(get_resource_path(),'onsets_example.txt') -dm = onsets_to_dm(onsetsFile, TR=2.0, runLength=160, sort=True,add_poly=1) -dm.heatmap() - -######################################################################### -# Design Matrix makes it easy to perform convolution and will auto-ignore all columns that are consider polynomials. By default it will use the one-parameter glover_hrf kernel (see nipy for details). However, any kernel can be passed as an argument, including a list of different kernels for highly flexible convolution across many types of data (e.g. SCR). - -dm = dm.convolve() -print(dm.details()) +dm = onsets_to_dm(onsetsFile, sampling_freq=sampling_freq, run_length=160, sort=True,add_poly=1) dm.heatmap() ######################################################################### -# Load and Z-score a Covariates File -# ---------------------------------- +# Creating a Design Matrix from a generic csv file +# ************************************************ # -# Now we're going to handle a covariates file that's been generated by a preprocessing routine. First we'll read in the text file using pandas and convert it to a design matrix. +# Alternatively you can read a generic csv file and transform it into a Design Matrix using pandas file reading capability. Here we'll read in an example covariates file that contains the output of motion realignment estimated during a fMRI preprocessing pipeline. import pandas as pd covariatesFile = os.path.join(get_resource_path(),'covariates_example.csv') cov = pd.read_csv(covariatesFile) -cov = Design_Matrix(cov, sampling_rate = 2.0) -# Design matrix uses seaborn's heatmap for plotting so excepts all keyword arguments -# We're just adjusting colors here to visualize things a bit more nicely -cov.heatmap(vmin=-1,vmax=1) +cov = Design_Matrix(cov, sampling_freq =sampling_freq) +cov.heatmap(vmin=-1,vmax=1) # alter plot to scale of covs; heatmap takes Seaborn heatmap arguments ######################################################################### -# Similar to adding polynomial terms, Design Matrix has multiple methods for data processing and transformation such as downsampling, upsampling, and z-scoring. Let's use the z-score method to normalize the covariates we just loaded. +# Working with multiple Design Matrices +# ------------------------------------- +# +# Vertically "stacking" Design Matrices +# ************************************* +# A common task is creating a separate design matrix for multiple runs of an experiment, (or multiple subjects) and vertically appending them to each other so that regression can be performed across all runs of an experiment. However, in order to account for run-differences its important (and common practice) to include separate run-wise polynomials (e.g. intercepts). Design Matrix's append method is intelligent and flexible enough to keep columns separated during appending automatically. -# Use pandas built-in fillna to fill NaNs in the covariates files introduced during the pre-processing pipeline, before z-scoring -# Z-score takes an optional argument of which columns to z-score. Since we don't want to z-score any spikes, so let's select everything except that column -cov = cov.fillna(0).zscore(cov.columns[:-1]) -cov.heatmap(vmin=-1,vmax=1) +# Lets use the design matrix with polynomials from above +# Stack "run 1" on top of "run 2" +runs_1_and_2 = dm_with_nuissance.append(dm_with_nuissance,axis=0) +runs_1_and_2.heatmap() ######################################################################### -# Concatenate Multiple Design Matrices -# ------------------------------------ -# -# A really nice feature of Design Matrix is simplified, but intelligent matrix concatentation. Here it's trivial to horizontally concatenate our convolved onsets and covariates, while keeping our column names and order. +# Separating columns during append operations +# ******************************************* +# Notice that all polynomials have been kept separated for you automatically and have been renamed to reflect the fact that they come from different runs. But Design Matrix is even more flexible. Let's say you want to estimate separate run-wise coefficients for all house stimuli too. Simply pass that into the `unique_cols` parameter of append. -full = dm.append(cov,axis=1) -full.heatmap(vmin=-1,vmax=1) +runs_1_and_2 = dm_with_nuissance.append(dm_with_nuissance,unique_cols=['house*'],axis=0) +runs_1_and_2.heatmap() ######################################################################### -# But we can also intelligently vertically concatenate design matrices to handle say, different experimental runs, or participants. The method enables the user to indicate which columns to keep separated (if any) during concatenation or which to treat as extensions along the first dimension. By default the class will keep all polylnomial terms separated. This is extremely useful when building 1 large design matrix composed of several runs or participants with separate means. - -dm2 = dm.append(dm, axis=0) -dm2.heatmap(vmin=-1,vmax=1) +# Now notice how all stimuli that begin with 'house' have been made into separate columns for each run. In general `unique_cols` can take a list of columns to keep separated or simple wild cards that either begin with a term e.g. "house*" or end with one "*house". ######################################################################### -# Specific columns of interest can also be kept separate during concatenation (e.g. keeping run-wise spikes separate). As an example, we treat our first experimental regressor as different across our two design matrices. Notice that the class also preserves (as best as possible) column ordering. +# Putting it all together +# ----------------------- +# +# A realistic workflow +# ******************** +# Let's combine all the examples above to build a work flow for a realistic first-level analysis fMRI analysis. This will include loading onsets from multiple experimental runs, and concatenating them into a large multi-run design matrix where we estimate a single set of coefficients for our variables of interest, but make sure we account for run-wise differences nuisiance covarites (e.g. motion) and baseline, trends, etc. For simplicity we'll just reuse the same onsets and covariates file multiple times. -dm2 = dm.append(dm, axis=0, unique_cols=['BillyRiggins']) -dm2.heatmap(vmin=-1,vmax=1) +num_runs = 4 +TR = 2.0 +sampling_freq = 1./TR +all_runs = Design_Matrix(sampling_freq = sampling_freq) +for i in range(num_runs): -######################################################################### -# Design Matrix can also create polynomial terms and intelligently keep them separate during concatenation. For example lets concatenate 4 design matrices and create separate 2nd order polynomials for all of them + # 1) Load in onsets for this run + onsetsFile = os.path.join(get_resource_path(),'onsets_example.txt') + dm = onsets_to_dm(onsetsFile, sampling_freq=sampling_freq,run_length=160,sort=True) + + # 2) Convolve them with the hrf + dm = dm.convolve() + + # 2) Load in covariates for this run + covariatesFile = os.path.join(get_resource_path(),'covariates_example.csv') + cov = pd.read_csv(covariatesFile) + cov = Design_Matrix(cov, sampling_freq = sampling_freq) + + # 3) In the covariates, fill any NaNs with 0, add intercept and linear trends and dct basis functions + cov = cov.fillna(0) + + # Retain a list of nuisance covariates (e.g. motion and spikes) which we'll also want to also keep separate for each run + cov_columns = cov.columns + cov = cov.add_poly(1).add_dct_basis() + + # 4) Join the onsets and covariates together + full = dm.append(cov,axis=1) -# Notice that append can take a list of Design Matrices in addition to just a single one -dm_all = dm.append([dm,dm,dm], axis=0, add_poly=2) -dm_all.heatmap(vmin=-1,vmax=1) + # 5) Append it to the master Design Matrix keeping things separated by run + all_runs = all_runs.append(full,axis=0,unique_cols=cov.columns) + +all_runs.heatmap(vmin=-1,vmax=1) + +######################################################################### +# We can see the left most columns of our multi-run design matrix contain our conditions of interest (stacked across all runs), the middle columns includes separate run-wise nuisiance covariates (motion, spikes) and the right most columns contain run specific polynomials (intercept, trends, etc). ######################################################################### # Data Diagnostics # ---------------- # -# Design Matrix also provides a few tools for cleaning up perfectly correlated columns (resulting in failure if trying to perform regression), replacing data, and computing collinearity. +# Let's actually check if our design is estimable. Design Matrix provides a few tools for cleaning up highly correlated columns (resulting in failure if trying to perform regression), replacing data, and computing collinearity. By default the `clean` method will drop any columns correlated at r >= .95 + +all_runs_cleaned = all_runs.clean(verbose=True) +all_runs_cleaned.heatmap(vmin=-1,vmax=1) + +######################################################################### +# Whoops, looks like above some of our polynomials and dct basis functions are highly correlated, but the clean method detected that and dropped them for us. In practice you'll often include polynomials or dct basis functions rather than both, but this was just an illustrative example. + +######################################################################### +# Estimating a first-level model +# ------------------------------ +# +# You can now set this multi-run Design Matrix as the `X` attribute of a Brain_Data object containing EPI data for these four runs and estimate a regression in just a few lines of code. + +# This code is commented because we don't actually have niftis loaded for the purposes of this tutorial +# See the other tutorials for more details on working with nifti files and Brain_Data objects + +# Assuming you already loaded up Nifti images like this +# list_of_niftis = ['run_1.nii.gz','run_2.nii.gz','run_3.nii.gz','run_4.nii.gz'] +# all_run_data = Brain_Data(list_of_niftis) + +# Set our Design Matrix to the X attribute of Brain_Data object +# all_run_data.X = all_runs_cleaned + +# Run the regression +# results = all_run_data.regress() -# We have a good design here so no problems -dm_all.clean(verbose=False) -dm_all.vif() +# This will produce N beta, t, and p images +# where N is the number of columns in the design matrix diff --git a/nltools/data/design_matrix.py b/nltools/data/design_matrix.py index b30b033c..3a513ada 100644 --- a/nltools/data/design_matrix.py +++ b/nltools/data/design_matrix.py @@ -105,6 +105,14 @@ def _inherit_attributes(self, setattr(dm_out, item, getattr(self,item)) return dm_out + def _sort_cols(self): + """ + This is a helper function that tries to ensure that columns of a Design Matrix are sorted according to: a) those not separated during append operations, b) those separated during append operations, c) polynomials. Called primarily during vertical concatentation and cleaning. + """ + data_cols = [elem for elem in self.columns if not elem.split('_')[0].isdigit() and elem not in self.polys] + separated_cols = [elem for elem in self.columns if elem.split('_')[0].isdigit() and elem not in self.polys] + return self[data_cols + separated_cols + self.polys] + def details(self): """Print class meta data. @@ -144,7 +152,7 @@ def append(self, dm, axis=0, keep_separate = True, unique_cols = [], fill_na=0, if not all([isinstance(elem,self.__class__) for elem in to_append]): raise TypeError("Each object to be appended must be a Design_Matrix!") if not all([elem.sampling_freq == self.sampling_freq for elem in to_append]): - raise ValueError("All Design Matrices must have the same sampling rate!") + raise ValueError("All Design Matrices must have the same sampling frequency!") if axis == 1: if any([not set(self.columns).isdisjoint(elem.columns) for elem in to_append]): @@ -190,6 +198,7 @@ def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): modify_to_append = [] all_polys = [] cols_to_separate = [] + all_separated = [] if len(unique_cols): if not keep_separate: @@ -216,7 +225,10 @@ def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): count = c.split('_')[0] unique_count.append(int(count)) else: - to_rename[c] = '0_' + c + new_name = '0_' + c + all_separated.append(new_name) + to_rename[c] = new_name + all_separated.append(new_name) cols_to_separate.append(searchstr) if to_rename: @@ -256,10 +268,12 @@ def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): count = int(c.split('_')[0]) name = '_'.join(c.split('_')[1:]) count += max_unique_count + 1 - to_rename[c] = str(count) + '_' + name + new_name = str(count) + '_' + name + to_rename[c] = new_name else: - to_rename[c] = str(max_unique_count + 1) + '_' + c - + new_name = str(max_unique_count + 1) + '_' + c + to_rename[c] = new_name + all_separated.append(new_name) modify_to_append.append(dm.rename(columns=to_rename)) max_unique_count += 1 else: @@ -282,9 +296,12 @@ def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): count = int(c.split('_')[0]) name = '_'.join(c.split('_')[1:]) count += max_unique_count + 1 - to_rename[c] = str(count) + '_' + name + new_name = str(count) + '_' + name + to_rename[c] = new_name else: - to_rename[c] = str(max_unique_count + 1) + '_' + c + new_name = str(max_unique_count + 1) + '_' + c + to_rename[c] = new_name + all_separated.append(new_name) modify_to_append.append(dm.rename(columns=to_rename)) max_unique_count += 1 else: @@ -339,7 +356,6 @@ def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): current_poly_max += 1 all_polys += list(to_rename.values()) - # Handle renaming additional unique cols to keep separate if cols_to_separate: if verbose: @@ -353,9 +369,12 @@ def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): count = int(c.split('_')[0]) name = '_'.join(c.split('_')[1:]) count += max_unique_count + 1 - to_rename[c] = str(count) + '_' + name + new_name = str(count) + '_' + name + to_rename[c] = new_name else: - to_rename[c] = str(max_unique_count + 1) + '_' + c + new_name = str(max_unique_count + 1) + '_' + c + to_rename[c] = new_name + all_separated.append(new_name) # Combine renamed polynomials and renamed uniqu_cols modify_to_append.append(temp_dm.rename(columns=to_rename)) @@ -382,10 +401,12 @@ def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): count = int(c.split('_')[0]) name = '_'.join(c.split('_')[1:]) count += max_unique_count + 1 - to_rename[c] = str(count) + '_' + name + new_name = str(count) + '_' + name + to_rename[c] = new_name else: - to_rename[c] = str(max_unique_count + 1) + '_' + c - + new_name = str(max_unique_count + 1) + '_' + c + to_rename[c] = new_name + all_separated.append(new_name) modify_to_append.append(dm.rename(to_rename)) max_unique_count += 1 else: @@ -403,10 +424,8 @@ def _vertcat(self, df, keep_separate, unique_cols, fill_na, verbose): out.convolved = self.convolved out.multi = True out.polys = all_polys - data_cols = [elem for elem in out.columns if elem not in out.polys] - out = out[data_cols + out.polys] - return out + return out._sort_cols() def vif(self,exclude_polys=True): """Compute variance inflation factor amongst columns of design matrix, @@ -472,7 +491,7 @@ def convolve(self, conv_func='hrf', columns=None): assert len(conv_func.shape) <= 2, "2d conv_func must be formatted as samplex X kernals!" elif isinstance(conv_func, six.string_types): assert conv_func == 'hrf',"Did you mean 'hrf'? 'hrf' can generate a kernel for you, otherwise custom kernels should be passed in as 1d or 2d arrays." - conv_func = glover_hrf(1. / self.sampling_freq, oversampling=1) + conv_func = glover_hrf(1. / self.sampling_freq, oversampling=1.) else: raise TypeError("conv_func must be a 1d or 2d numpy array organized as samples x kernels, or the string 'hrf' for the canonical glover hrf") @@ -611,7 +630,7 @@ def add_dct_basis(self,duration=180,drop=0): 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],self.sampling_freq,duration,drop=drop) + 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])]) @@ -688,6 +707,8 @@ def clean(self,fill_na=0,exclude_polys=False,thresh=.95,verbose=True): remove.append(j) if remove: out = out.drop(remove, axis=1) + out.polys = [elem for elem in out.polys if elem not in remove] + out = out._sort_cols() else: print("Dropping columns not needed...skipping") np.seterr(**old_settings) diff --git a/nltools/file_reader.py b/nltools/file_reader.py index 8303f047..8d809e34 100644 --- a/nltools/file_reader.py +++ b/nltools/file_reader.py @@ -75,7 +75,7 @@ def onsets_to_dm(F, sampling_freq, run_length, header='infer', sort=False, keep_ df['Onset'] = df['Onset'].apply(lambda x: int(np.floor(x/TR))) #Build dummy codes - X = Design_Matrix(np.zeros([run_length,len(df['Stim'].unique())]),columns=df['Stim'].unique(),sampling_rate=TR) + X = Design_Matrix(np.zeros([run_length,len(df['Stim'].unique())]),columns=df['Stim'].unique(),sampling_freq=sampling_freq) for i, row in df.iterrows(): if df.shape[1] == 3: dur = np.ceil(row['Duration']/TR) diff --git a/nltools/stats.py b/nltools/stats.py index 4ef6daa2..0fa0517e 100644 --- a/nltools/stats.py +++ b/nltools/stats.py @@ -563,14 +563,15 @@ def make_cosine_basis(nsamples, sampling_freq, filter_length, unit_scale=True, d # Drop intercept ala SPM C = C[:,1:] + if C.size == 0: + raise ValueError('Basis function creation failed! nsamples is too small for requested filter_length.') + if unit_scale: C *= 1. / C[0,0] C = C[:, drop:] - if C.size == 0: - raise ValueError('Basis function creation failed! nsamples is too small for requested filter_length.') - else: - return C + + return C def transform_pairwise(X, y): '''Transforms data into pairs with balanced labels for ranking diff --git a/nltools/tests/test_data.py b/nltools/tests/test_data.py index a4a9638f..e2fab982 100644 --- a/nltools/tests/test_data.py +++ b/nltools/tests/test_data.py @@ -617,13 +617,13 @@ def test_designmat(tmpdir): assert mat.add_poly(2,include_lower=False).shape[1] == 5 matpd = matp.add_dct_basis() - assert matpd.shape[1] == 9 + assert matpd.shape[1] == 18 assert all(matpd.vif() < 2.0) assert not all(matpd.vif(exclude_polys=False) < 2.0) matc = matpd.clean() - assert matc.shape[1] == 7 + assert matc.shape[1] == 16 # Standard convolve assert matpd.convolve().shape == matpd.shape @@ -653,18 +653,17 @@ def test_designmat(tmpdir): # Otherwise stack them assert matpd.append(matpd,keep_separate=False).shape[1] == matpd.shape[1] # Keep a single stimulus column separate - assert matpd.append(matpd,unique_cols=['face_A']).shape[1] == 15 + assert matpd.append(matpd,unique_cols=['face_A']).shape[1] == 33 # Keep a common stimulus class separate - assert matpd.append(matpd,unique_cols=['face*']).shape[1] == 16 + assert matpd.append(matpd,unique_cols=['face*']).shape[1] == 34 # Keep a common stimulus class and a different single stim separate - assert matpd.append(matpd,unique_cols=['face*','house_A']).shape[1] == 17 + assert matpd.append(matpd,unique_cols=['face*','house_A']).shape[1] == 35 # Keep multiple stimulus class separate - assert matpd.append(matpd,unique_cols=['face*','house*']).shape[1] == 18 + assert matpd.append(matpd,unique_cols=['face*','house*']).shape[1] == 36 # Growing a multi-run design matrix; keeping things separate num_runs = 4 all_runs = Design_Matrix(sampling_freq=.5) - run_list = [] for i in range(num_runs): run = Design_Matrix(np.array([ [1,0,0,0], @@ -683,7 +682,6 @@ def test_designmat(tmpdir): columns=['stim_A','stim_B','cond_C','cond_D'] ) run = run.add_poly(2) - run_list.append(run) all_runs = all_runs.append(run,unique_cols=['stim*','cond*']) assert all_runs.shape == (44, 28)