def plot_rigidity(self, title: str = None, outdir: Path = None) -> None: # label is always g1_v_g2, we want "attention" to be orange, "nonattend" # to be black if self.label in ["rest_v_task", "nopain_v_pain", "control_v_control_pre", "park_pre_v_parkinsons"]: c1, c2 = "#000000", "#FD8208" elif self.label in [ "allpain_v_nopain", "allpain_v_duloxetine", "high_v_low", "control_pre_v_park_pre", "control_v_parkinsons", ]: c1, c2 = "#FD8208", "#000000" else: c1, c2 = "#EA00FF", "#FD8208" df1 = pd.read_pickle(self.rigidity[0]).set_index("L") df2 = pd.read_pickle(self.rigidity[1]).set_index("L") L = np.array(df1.index) _configure_sbn_style() fig: plt.Figure ax: plt.Axes fig, ax = plt.subplots() # plot curves for each group for col in df1: sbn.lineplot(x=L, y=df1[col], color=c1, alpha=0.05, ax=ax) for col in df2: sbn.lineplot(x=L, y=df2[col], color=c2, alpha=0.05, ax=ax) # plot bootstrapped means and CIs for each group boots = _percentile_boot(df1) sbn.lineplot(x=L, y=boots["mean"], color=c1, label=self.subgroup1, ax=ax) ax.fill_between(x=L, y1=boots["low"], y2=boots["high"], color=c1, alpha=0.3) boots = _percentile_boot(df2) sbn.lineplot(x=L, y=boots["mean"], color=c2, label=self.subgroup2, ax=ax) ax.fill_between(x=L, y1=boots["low"], y2=boots["high"], color=c2, alpha=0.3) # plot theoretically-expected curves sbn.lineplot(x=L, y=Poisson.spectral_rigidity(L=L), color="#08FD4F", label="Poisson", ax=ax) sbn.lineplot(x=L, y=GOE.spectral_rigidity(L=L), color="#0066FF", label="GOE", ax=ax) ax.legend().set_visible(True) ax.set_title(f"{self.label}: Rigidity" if title is None else title) ax.set_xlabel("L") ax.set_ylabel("∆₃(L)", fontname="DejaVu Sans") fig.set_size_inches(w=8, h=8) if outdir is None: plt.show() plt.close() else: os.makedirs(outdir, exist_ok=True) outfile = outdir / f"{self.rigidity[0].stem}_{self.label}.png" fig.savefig(outfile, dpi=300) plt.close() print(f"Pooled rigidity plots saved to {relpath(outfile)}")
def _brody_fit( unfolded: ndarray, method: str = "spacing", title: str = "Brody distribution fit", mode: PlotMode = "block", outfile: Path = None, save_dpi: int = None, ensembles: List[str] = ["goe", "poisson"], bins: int = 50, kde: bool = True, trim: float = 5.0, trim_kde: bool = False, kde_bw: Union[float, str] = "scott", ) -> PlotResult: _configure_sbn_style() fig, axes = plt.subplots(1, 2, sharex=True, sharey=True) _spacings( unfolded=unfolded, bins=bins, kde=kde, trim=trim, trim_kde=trim_kde, kde_bw=kde_bw, brody=True, brody_fit=method, title="Brody distribution fit: density", mode="return", ensembles=ensembles, fig=fig, axes=axes[0], ) spacings = np.diff(unfolded) s = spacings[spacings > 0] res = brody_fit_evaluate(s, method=method) x = res["spacings"] ecdf, bcdf = res["ecdf"], res["brody_cdf"] ax = axes.flat[1] ax_e = ax.plot(x, ecdf) plt.setp(ax_e, label="Empirical CDF") ax_b = ax.plot(x, bcdf) plt.setp(ax_b, label="Brody CDF", color="#9d0000", linestyle="--") if "goe" in ensembles: ax_goe = ax.plot(x, GOE.nnsd_cdf(spacings=x)) plt.setp(ax_goe, label="GOE", color="#FD8208") if "poisson" in ensembles: ax_poi = ax.plot(x, Poisson.nnsd_cdf(spacings=x)) plt.setp(ax_poi, label="Poisson", color="#08FD4F") ax.legend().set_visible(True) ax.set_title("Brody distribution fit: cumulative density") return _handle_plot_mode(mode, fig, axes, outfile, save_dpi)
def _next_spacings( unfolded: ndarray, bins: int = 50, kde: bool = True, trim: float = 0.0, trim_kde: bool = False, brody: bool = False, brody_fit: str = "spacing", title: str = "next Nearest-Neigbors Spacing Distribution", mode: PlotMode = "block", outfile: Path = None, ensembles: List[str] = ["goe", "poisson"], fig: Figure = None, axes: Axes = None, ) -> PlotResult: """Plots a histogram of the next Nearest-Neighbors Spacing Distribution Parameters ---------- unfolded: ndarray the unfolded eigenvalues bins: int the number of (equal-sized) bins to display and use for the histogram kde: boolean If False (default), do not display a kernel density estimate. If true, use [statsmodels.nonparametric.kde.KDEUnivariate](https://www.statsmodels.org/stable/generated/statsmodels.nonparametric.kde.KDEUnivariate.html#statsmodels.nonparametric.kde.KDEUnivariate) with arguments {kernel="gau", bw="scott", cut=0} to compute and display the kde trim: float If True, only use spacings <= `trim` for computing the KDE and plotting. Useful for when large spacings distort the histogram. trim_kde: bool If True, fit the KDE using only spacings <= `trim`. Otherwise, fit the KDE using all available spacings. brody: bool If True, compute the best-fitting Brody distribution via MLE, and plot that distribution. brody_fit: "spacing" | "mle" Method for parametric distribution fitting of the Brody distribution to the data. If "spacing", use [maximum spacing estimation](https://en.wikipedia.org/wiki/Maximum_spacing_estimation). If "mle", use the maximum likelihood method. The default is "spacing", as this may be preferable for the J-shape of the Brody distribution. title: string The plot title string mode: "block" | "noblock" | "save" | "return" If "block", call plot.plot() and display plot in a blocking fashion. If "noblock", attempt to generate plot in nonblocking fashion. If "save", save plot to pathlib Path specified in `outfile` argument If "return", return (fig, axes), the matplotlib figure and axes object for modification. outfile: Path If mode="save", save generated plot to Path specified in `outfile` argument. Intermediate directories will be created if needed. ensembles: List["poisson", "goe"] Which ensembles to display the expected next-NNSD curves for. fig: Figure If provided with `axes`, configure plotting with the provided `fig` object instead of creating a new figure. Useful for creating subplots. axes: Axes If provided with `fig`, plot to the provided `axes` object. Useful for creating subplots. Returns ------- (fig, axes): (Figure, Axes) The handles to the matplotlib objects, only if `mode` is "return". """ _configure_sbn_style() fig, axes = _setup_plotting(fig, axes) _spacings = np.sort((unfolded[2:] - unfolded[:-2]) / 2) all_spacings = np.copy(_spacings) if trim > 0.0: _spacings = _spacings[_spacings <= trim] # Generate expected distributions for classical ensembles s_min, s_max = _spacings.min(), _spacings.max() s = np.linspace(s_min, s_max, 10000) axes = sbn.distplot( _spacings, norm_hist=True, bins=bins, # doane kde=False, label="next NNSD", axlabel="spacing (s_2)", color="black", ax=axes, ) if kde is True: if trim_kde: _kde_plot(_spacings, s, axes) else: _kde_plot(all_spacings, s, axes) if brody is True: beta = fit_brody(_spacings) brody_vals = brody_dist(s, beta=beta) brod = axes.plot(s, brody_vals, label="Brody dist.") plt.setp(brod, color="#9d0000", linestyle="--") if "goe" in ensembles: goe = GOE.nnnsd(spacings=s) goe = axes.plot(s, goe, label="Gaussian Orthogonal") plt.setp(goe, color="#FD8208") if "poisson" in ensembles: poisson = Poisson.nnnsd(spacings=s) poisson = axes.plot(s, poisson, label="Poisson") plt.setp(poisson, color="#08FD4F") # adjusting the right bounds can be necessary when / if there are # many large eigenvalue spacings axes.set_ylim(top=2.0, bottom=0) axes.set_xlim(left=0, right=2.5) axes.set(title=title, ylabel="Density p(s)") axes.legend().set_visible(True) return _handle_plot_mode(mode, fig, axes, outfile)
def plot_nnsd( self, trim_args: str, unfold_args: dict, n_bins: int = 20, max_spacing: float = 5.0, title: str = None, outdir: Path = None, ) -> None: # label is always g1_v_g2, we want "attention" to be orange, "nonattend" # to be black if self.label in ["rest_v_task", "nopain_v_pain", "control_v_control_pre", "park_pre_v_parkinsons"]: c1, c2 = "#000000", "#FD8208" elif self.label in [ "allpain_v_nopain", "allpain_v_duloxetine", "high_v_low", "control_pre_v_park_pre", "control_v_parkinsons", ]: c1, c2 = "#FD8208", "#000000" elif self.label in ["control_pre_v_parkinsons", "control_v_park_pre"]: return # meaningless comparison else: c1, c2 = "#EA00FF", "#FD8208" unfolded1 = [] for path in self.eigs1: vals = np.load(path) if trim_args in ["(1,:)", "", "(0,:)"]: vals = vals[1:] # smallest eigenvalue is always spurious here else: low, high = eval(trim_args) vals = vals[low:high] unfolded1.append(np.sort(Eigenvalues(vals).unfold(**unfold_args).vals)) unfolded2 = [] for path in self.eigs2: vals = np.load(path) if trim_args in ["(1,:)", "", "(0,:)"]: vals = vals[1:] # smallest eigenvalue is always spurious here else: low, high = eval(trim_args) vals = vals[low:high] unfolded2.append(np.sort(Eigenvalues(vals).unfold(**unfold_args).vals)) spacings1 = [np.diff(unfolded) for unfolded in unfolded1] spacings2 = [np.diff(unfolded) for unfolded in unfolded2] # trim largest histogram skewing spacing spacings1 = [spacings[spacings < max_spacing] for spacings in spacings1] spacings2 = [spacings[spacings < max_spacing] for spacings in spacings2] mean_brody1 = np.round(float(pd.read_pickle(self.brody[0]).mean(axis=1)), 2) mean_brody2 = np.round(float(pd.read_pickle(self.brody[1]).mean(axis=1)), 2) _configure_sbn_style() fig, axes = plt.subplots(ncols=2, sharex=True, sharey=True) # plot curves for each group for spacings in spacings1: sbn.distplot( spacings, norm_hist=True, bins=n_bins, kde=True, axlabel="spacing (s)", color=c1, kde_kws={"alpha": np.max([0.1, 1 / len(spacings1)])}, hist_kws={"alpha": np.max([0.1, 1 / len(spacings1)])}, ax=axes[0], ) for spacings in spacings2: sbn.distplot( spacings, norm_hist=True, bins=n_bins, kde=True, axlabel="spacing (s)", color=c2, kde_kws={"alpha": np.max([0.1, 1 / len(spacings1)])}, hist_kws={"alpha": np.max([0.1, 1 / len(spacings2)])}, ax=axes[1], ) # plot bootstrapped means and CIs for each group # boots = _percentile_boot(df1) # sbn.lineplot(x=L, y=boots["mean"], color=c1, label=self.subgroup1, ax=ax) # ax.fill_between(x=L, y1=boots["low"], y2=boots["high"], color=c1, alpha=0.3) # boots = _percentile_boot(df2) # sbn.lineplot(x=L, y=boots["mean"], color=c2, label=self.subgroup2, ax=ax) # ax.fill_between(x=L, y1=boots["low"], y2=boots["high"], color=c2, alpha=0.3) # plot theoretically-expected curves s = np.linspace(0, max_spacing, 5000) sbn.lineplot(x=s, y=Poisson.nnsd(spacings=s), color="#08FD4F", label="Poisson", ax=axes[0]) sbn.lineplot(x=s, y=Poisson.nnsd(spacings=s), color="#08FD4F", label="Poisson", ax=axes[1]) sbn.lineplot(x=s, y=GOE.nnsd(spacings=s), color="#0066FF", label="GOE", ax=axes[0]) sbn.lineplot(x=s, y=GOE.nnsd(spacings=s), color="#0066FF", label="GOE", ax=axes[1]) # ensure all plots have identical axes axes[0].set_ylim(top=2.0) axes[1].set_ylim(top=2.0) # ax.legend().set_visible(True) fig.suptitle(f"{self.label}: NNSD" if title is None else title) for i, ax in enumerate(axes): ax.set_xlabel("spacing (s)") ax.set_ylabel("density p(s)", fontname="DejaVu Sans") ax.set_title( f"{self.subgroup1 if i == 0 else self.subgroup2} (<β> = {mean_brody1 if i == 0 else mean_brody2})" ) if outdir is None: plt.show() plt.close() else: os.makedirs(outdir, exist_ok=True) prefix = _prefix(trim_args, unfold_args) outfile = outdir / f"{prefix}_{self.label}_nnsd.png" fig.set_size_inches(10, 5) plt.savefig(outfile, dpi=300) plt.close() print(f"Pooled nnsd plot saved to {relpath(outfile)}")
def plot_subject_levelvar(summaries: Dict[str, Dict[Observable, Path]], args: Any, outfile: Path = None) -> None: sbn.set_context("paper") sbn.set_style("ticks", {"ytick.left": False}) c1 = "#000000" fig, axes = plt.subplots(ncols=6, nrows=4, sharex=True, sharey=True) top = [] for i, (subj_id, summary) in enumerate(summaries.items()): ax: Axes = axes.flat[i] label = f"subj-{subj_id}" levelvars = pd.read_pickle(summary["levelvar"]).set_index("L") rename_columns(levelvars) L = levelvars.index.to_numpy(dtype=float).ravel() for col in levelvars: sbn.lineplot(x=L, y=levelvars[col], color=c1, alpha=1.0 / 8.0, ax=ax) # sbn.lineplot(x=L, y=levelvars[col], label=f"run-{col}", color=c1, alpha=1.0/8.0, ax=ax) boots = _percentile_boot(levelvars, B=2000) top.append(np.max(boots["mean"])) # sbn.lineplot(x=L, y=boots["mean"], color=c1, label=label) sbn.lineplot(x=L, y=boots["mean"], color=c1, ax=ax) ax.fill_between(x=L, y1=boots["low"], y2=boots["high"], color=c1, alpha=0.3) # plot theoretically-expected curves sbn.lineplot(x=L, y=Poisson.level_variance(L=L), color="#08FD4F", label="Poisson", ax=ax) sbn.lineplot(x=L, y=GOE.level_variance(L=L), color="#0066FF", label="GOE", ax=ax) ax.set_ylabel("") ax.set_title(label) ax.legend(frameon=False, framealpha=0) plt.setp(ax.get_legend().get_texts(), fontsize="6") if i != 0: handle, labels = ax.get_legend_handles_labels() ax._remove_legend(handle) ticks = [1, 5, 10] ax.set_xticks(ticks) ax.set_xticklabels(ticks, rotation=45, horizontalalignment="right") plt.setp(axes, ylim=(0.0, np.percentile(top, 90))) # better use of space fig.suptitle("Per-Subject Task Level Number Variances") fig.set_size_inches(10, 6) fig.subplots_adjust(left=0.13, bottom=0.15, wspace=0.1, hspace=0.35) fig.text(0.5, 0.04, "L", ha="center", va="center") # xlabel fig.text(0.05, 0.5, "Σ²(L)", ha="center", va="center", rotation="vertical", fontname="DejaVu Sans") # ylabel if outfile is None: plt.show() else: fig.savefig(str(outfile.resolve()), dpi=300) print(f"Saved levelvar plot to {str(outfile.relative_to(DATA_ROOT))}") plt.close()
def plot_subject_nnsd(args: Any, n_bins: int = 20, outfile: Path = None) -> None: subjects = get_subjects_dict() sbn.set_context("paper") sbn.set_style("ticks", {"ytick.left": False}) c1 = "#000000" fig, axes = plt.subplots(ncols=6, nrows=4, sharex=True, sharey=True) pbar = tqdm(desc="NNSD", total=8 * len(subjects)) for i, (subj_id, subject) in enumerate(subjects.items()): ax: Axes = axes.flat[i] eigpaths = subject["runs"] unfoldeds = [] for path in eigpaths: vals = np.load(path) if args.trim in ["(1,:)", "", "(0,:)"]: vals = vals[1:] # smallest eigenvalue is always spurious here else: low, high = eval(args.trim) vals = vals[low:high] unfoldeds.append( np.sort(Eigenvalues(vals).unfold(**args.unfold).vals)) all_spacings = [np.diff(unfolded) for unfolded in unfoldeds] kde_gridsize = 1000 kdes = np.empty([len(all_spacings), kde_gridsize], dtype=float) s = np.linspace(0, 3, kde_gridsize) bins = np.linspace(0, 3, n_bins + 1) for j, spacings in enumerate(all_spacings): sbn.distplot( spacings[(spacings > 0) & (spacings <= 3)], norm_hist=True, bins=bins, kde=False, color=c1, hist_kws={ "alpha": 1.0 / 8, "range": (0.0, 3.0) }, ax=ax, ) kde = _kde(spacings, grid=s) kdes[j, :] = kde pbar.update() # sbn.lineplot(x=s, y=kde, color=c1, alpha=1.0 / 8, ax=ax) # sbn.lineplot(x=s, y=kdes.mean(axis=0), color="#9d0000", label="Mean KDE", ax=ax) sbn.lineplot(x=s, y=kdes.mean(axis=0), color=c1, label="Mean KDE", ax=ax) sbn.lineplot(x=s, y=Poisson.nnsd(spacings=s), color="#08FD4F", label="Poisson", ax=ax) sbn.lineplot(x=s, y=GOE.nnsd(spacings=s), color="#0066FF", label="GOE", ax=ax) ax.set_title(f"subj-{subj_id}") ax.set_ylabel("") ax.legend(frameon=False, framealpha=0) plt.setp(ax.get_legend().get_texts(), fontsize="6") # if i != 0: handle, labels = ax.get_legend_handles_labels() ax._remove_legend(handle) ticks = [0, 1, 2, 3] ax.set_xticks(ticks) # ax.set_xticklabels(ticks, rotation=45, horizontalalignment="right") ax.set_xticklabels(ticks) pbar.close() fig.suptitle("Per-Subject NNSD") fig.set_size_inches(10, 6) fig.subplots_adjust(left=0.13, bottom=0.15, wspace=0.1, hspace=0.35) fig.text(0.5, 0.04, "spacing (s)", ha="center", va="center") # xlabel fig.text(0.05, 0.5, "density p(s)", ha="center", va="center", rotation="vertical") # ylabel plt.setp(axes, xlim=(0.0, 3.0), ylim=(0.0, 1.2)) # better use of space handles, labels = axes.flat[-1].get_legend_handles_labels() fig.legend(handles, labels, loc="center right", frameon=False, framealpha=0, fontsize="8") if outfile is None: plt.show() else: fig.savefig(str(outfile.resolve()), dpi=300) print(f"Saved NNSD plot to {str(outfile.relative_to(DATA_ROOT))}") plt.close()