GLM Fingertapping Example
[1]:
import matplotlib.pyplot as p
import numpy as np
import pandas as pd
import xarray as xr
import cedalion
import cedalion.datasets
import cedalion.io
import cedalion.models.glm as glm
import cedalion.nirs
import cedalion.plots as plots
import cedalion.sigproc.frequency
from cedalion import units
xr.set_options(display_expand_data=False);
Loading and preprocessing the dataset
This notebook uses a finger-tapping dataset in BIDS layout provided by Rob Luke. It can can be downloaded via cedalion.datasets
.
[2]:
rec = cedalion.datasets.get_fingertapping()
# rename trials
rec.stim.cd.rename_events(
{
"1.0": "control",
"2.0": "Tapping/Left",
"3.0": "Tapping/Right",
"15.0": "sentinel",
}
)
rec.stim = rec.stim[rec.stim.trial_type != "sentinel"]
# differential pathlenght factors
dpf = xr.DataArray(
[6, 6],
dims="wavelength",
coords={"wavelength": rec["amp"].wavelength},
)
# calculate optical density and concentrations
rec["od"] = cedalion.nirs.int2od(rec["amp"])
rec["conc"] = cedalion.nirs.od2conc(rec["od"], rec.geo3d, dpf, spectrum="prahl")
# Bandpass filter remove cardiac component and slow drifts.
# Here we use a highpass to remove drift. Another possible option would be to
# use drift regressors in the design matrix.
fmin = 0.02 * units.Hz
#fmax = 0.3 * units.Hz
fmax = 0 * units.Hz
rec["conc_filtered"] = cedalion.sigproc.frequency.freq_filter(rec["conc"], fmin, fmax)
display(rec)
<Recording | timeseries: ['amp', 'od', 'conc', 'conc_filtered'], masks: [], stim: ['control', 'Tapping/Left', 'Tapping/Right'], aux_ts: [], aux_obj: []>
Plot freq. filtered concentration data for two channels on the left (S1D1, S1D3) and right (S5D5, S5D7) hemispheres.
[3]:
ts = rec["conc_filtered"]
f, ax = p.subplots(4, 1, sharex=True, figsize=(12, 6))
for i, ch in enumerate(["S1D1", "S1D3", "S5D5", "S5D7"]):
ax[i].plot(ts.time, ts.sel(channel=ch, chromo="HbO"), "r-", label="HbO")
ax[i].plot(ts.time, ts.sel(channel=ch, chromo="HbR"), "b-", label="HbR")
ax[i].set_title(f"Ch. {ch}")
cedalion.plots.plot_stim_markers(ax[i], rec.stim, y=1)
ax[i].set_ylabel(r"$\Delta$ c / uM")
ax[0].legend(ncol=6)
ax[3].set_label("time / s")
ax[3].set_xlim(0,300)
p.tight_layout()

Build design matrix
use the
glm.make_design_matrix
method to build regressorsto account for signal components from superficial layers use short-distance channel regression: for each long channel the closest short channel is selected. From these the channel-wise regressor’short’ is derived.
[4]:
# split time series into two based on channel distance
ts_long, ts_short = cedalion.nirs.split_long_short_channels(
rec["conc_filtered"], rec.geo3d, distance_threshold=1.5 * units.cm
)
dms = (
glm.design_matrix.hrf_regressors(
ts_long, rec.stim, glm.Gamma(tau=0 * units.s, sigma=3 * units.s, T=3 * units.s)
)
& glm.design_matrix.drift_regressors(ts, drift_order=1)
& glm.design_matrix.closest_short_channel_regressor(ts_long, ts_short, rec.geo3d)
)
The design matrix dm
holds all regressors that apply to all channels. It has dimensions ‘time’, ‘chromo’ and ‘regressor’. Regressors have string labels.
[5]:
display(dms)
display(dms.common)
DesignMatrix(universal=['HRF control','HRF Tapping/Left','HRF Tapping/Right','Drift 0','Drift 1'], channel_wise=['short'])
<xarray.DataArray (time: 23239, regressor: 5, chromo: 2)> Size: 2MB 0.0 0.0 0.0 0.0 0.0 0.0 1.0 1.0 4.303e-05 ... 0.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0 Coordinates: * time (time) float64 186kB 0.0 0.128 0.256 ... 2.974e+03 2.974e+03 * regressor (regressor) <U17 340B 'HRF control' ... 'Drift 1' * chromo (chromo) <U3 24B 'HbO' 'HbR'
channel_wise_regressors
is list of additional xr.DataArrays that contain regressors which differ between channels. Each such array may contain only one regressor (i.e. the size of the regressor dimension must be 1). The regressors for each channel are arranged in the additional ‘channel’ dimension.
[6]:
display(dms.channel_wise[0])
<xarray.DataArray 'concentration' (regressor: 1, chromo: 2, channel: 20, time: 23239)> Size: 7MB [µM] -0.1671 -0.3522 -0.03758 0.1025 ... 0.06639 -0.001217 0.005855 -0.001648 Coordinates: * chromo (chromo) <U3 24B 'HbO' 'HbR' * time (time) float64 186kB 0.0 0.128 0.256 ... 2.974e+03 2.974e+03 samples (time) int64 186kB 0 1 2 3 4 ... 23235 23236 23237 23238 * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' short_channel (channel) object 160B 'S1D9' 'S1D9' ... 'S8D16' 'S8D16' comp_group (channel) int64 160B 0 0 0 1 1 1 2 2 3 3 4 4 4 5 5 5 6 6 7 7 * regressor (regressor) <U5 20B 'short'
[7]:
dms.channel_wise[0] = dms.channel_wise[0].pint.dequantify()
dms.channel_wise[0] /= dms.channel_wise[0].max("time")
Visualize the design matrix
[8]:
dm = dms.common
display(dm)
# using xr.DataArray.plot
f, ax = p.subplots(1,1,figsize=(12,5))
dm.sel(chromo="HbO", time=dm.time < 600).T.plot()
p.xticks(rotation=90)
p.show()
# line plots of all regressors
f, ax = p.subplots(2,1,sharex=True, figsize=(12,5))
ch = "S5D5"
for i, chromo in enumerate(["HbO", "HbR"]):
for reg in dm.regressor.values:
ax[i].plot(dm.time, dm.sel(chromo=chromo, regressor=reg), label=reg)
for cwr in dms.channel_wise:
for reg in cwr.regressor.values:
ax[i].plot(cwr.time, cwr.sel(chromo=chromo, regressor=reg, channel=ch), label=reg)
plots.plot_stim_markers(ax[i], rec.stim, y=1)
ax[i].grid()
ax[i].set_title(chromo)
ax[i].set_ylim(-1.5,1.5)
ax[0].legend(ncol=5)
ax[0].set_xlim(0,240);
<xarray.DataArray (time: 23239, regressor: 5, chromo: 2)> Size: 2MB 0.0 0.0 0.0 0.0 0.0 0.0 1.0 1.0 4.303e-05 ... 0.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0 Coordinates: * time (time) float64 186kB 0.0 0.128 0.256 ... 2.974e+03 2.974e+03 * regressor (regressor) <U17 340B 'HRF control' ... 'Drift 1' * chromo (chromo) <U3 24B 'HbO' 'HbR'


Fitting the model
[9]:
results = glm.fit(ts_long, dms, noise_model="ar_irls", max_jobs=1)
display(results)
#pd.set_option('display.max_rows', None)
#display(betas.rename("beta").to_dataframe())
<xarray.DataArray (channel: 20, chromo: 2)> Size: 320B <statsmodels.robust.robust_linear_model.RLMResultsWrapper object at 0x7fe667f... Coordinates: * chromo (chromo) <U3 24B 'HbO' 'HbR' * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' source (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8' detector (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8' Attributes: description: AR_IRLS
[10]:
betas = results.sm.params
betas
[10]:
<xarray.DataArray (channel: 20, chromo: 2, regressor: 7)> Size: 2kB 1.986e-07 0.01244 0.2439 0.3886 -0.02322 ... -0.0298 0.02813 1.21e-06 0.09318 Coordinates: * regressor (regressor) object 56B 'index' 'HRF control' ... 'short' * chromo (chromo) <U3 24B 'HbO' 'HbR' * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' source (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8' detector (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8' Attributes: description: AR_IRLS
[11]:
betas.rename("betas").to_dataframe()
[11]:
source | detector | betas | |||
---|---|---|---|---|---|
channel | chromo | regressor | |||
S1D1 | HbO | index | S1 | D1 | 1.986168e-07 |
HRF control | S1 | D1 | 1.244468e-02 | ||
HRF Tapping/Left | S1 | D1 | 2.439477e-01 | ||
HRF Tapping/Right | S1 | D1 | 3.886186e-01 | ||
Drift 0 | S1 | D1 | -2.321850e-02 | ||
... | ... | ... | ... | ... | ... |
S8D8 | HbR | HRF Tapping/Left | S8 | D8 | -4.158567e-02 |
HRF Tapping/Right | S8 | D8 | -2.980406e-02 | ||
Drift 0 | S8 | D8 | 2.812522e-02 | ||
Drift 1 | S8 | D8 | 1.210228e-06 | ||
short | S8 | D8 | 9.317619e-02 |
280 rows × 3 columns
[12]:
# best fit parameters + confidence intervals
pd.concat([results[0,0].item().conf_int(), results[0,0].item().params.rename("beta")], axis=1)
[12]:
0 | 1 | beta | |
---|---|---|---|
index | -0.000004 | 3.936040e-06 | 1.986168e-07 |
HRF control | -0.030763 | 5.565234e-02 | 1.244468e-02 |
HRF Tapping/Left | 0.200736 | 2.871596e-01 | 2.439477e-01 |
HRF Tapping/Right | 0.345392 | 4.318451e-01 | 3.886186e-01 |
Drift 0 | -0.025848 | -2.058892e-02 | -2.321850e-02 |
Drift 1 | -0.000001 | -8.860806e-07 | -9.991095e-07 |
short | 0.226738 | 2.462663e-01 | 2.365021e-01 |
Model Predictions
using
glm.predict
one can scale the regressors indm
andchannel_wise_regressors
with the estimated coefficients to obtain a model predictionby giving only a subset of betas to
glm.predict
one can predict subcomponents of the model
[13]:
# prediction using all regressors
betas = results.sm.params
pred = glm.predict(ts_long, betas, dms)#, channel_wise_regressors)
# prediction of all nuisance regressors, i.e. all regressors that don't start with 'HRF '
pred_wo_hrf = glm.predict(
ts_long,
betas.sel(regressor=~betas.regressor.str.startswith("HRF ")),
dms,
)
# prediction of all HRF regressors, i.e. all regressors that start with 'HRF '
pred_hrf = glm.predict(
ts_long,
betas.sel(regressor=betas.regressor.str.startswith("HRF ")),
dms,
)
Plot model predictions
[14]:
# plot the data and model prediction
#ch = "S6D7"
ch = "S5D5"
f, ax = p.subplots(1,1, figsize=(12, 4))
p.plot(ts_long.time, ts_long.sel(chromo="HbO", channel=ch), "r-", label="data HbO", alpha=.5)
p.plot(pred.time, pred.sel(chromo="HbO", channel=ch), "r-", label="model", lw=2 )
p.plot(pred.time, pred_wo_hrf.sel(chromo="HbO", channel=ch), "k:", label="model w/o HRF", alpha=.5)
plots.plot_stim_markers(ax, rec.stim, y=1)
p.xlim(60,300)
p.ylim(-.4,.4)
p.xlabel("time / s")
p.ylabel(r"$\Delta$ c / uM")
p.legend(ncol=4)
# subtract from data nuisance regressors and plot against predicted HRF components
f, ax = p.subplots(1,1, figsize=(12, 4))
p.plot(pred_hrf.time, pred_hrf.sel(chromo="HbO", channel=ch), "r-", label="HRF HbO")
p.plot(pred_hrf.time, pred_hrf.sel(chromo="HbR", channel=ch), "b-", label="HRF HbR")
p.plot(
pred_hrf.time,
ts_long.sel(chromo="HbO", channel=ch).pint.dequantify() - pred_wo_hrf.sel(chromo="HbO", channel=ch),
"r-", label="data HbO - nuisance reg.", alpha=.5
)
p.plot(
pred_hrf.time,
ts_long.sel(chromo="HbR", channel=ch).pint.dequantify() - pred_wo_hrf.sel(chromo="HbR", channel=ch),
"b-", label="data HbR - nuisance reg.", alpha=.5
)
plots.plot_stim_markers(ax, rec.stim, y=1)
p.legend(ncol=4, loc="lower right")
p.xlim(60,500)
p.xlabel("time / s")
p.ylabel(r"$\Delta$ c / uM");


Scalp plots
Betas
[15]:
f, ax = p.subplots(2, 3, figsize=(12, 8))
vlims = {"HbO" : [0.,0.3], "HbR" : [-0.1, 0.05]}
for i_chr, chromo in enumerate(betas.chromo.values):
vmin, vmax = vlims[chromo]
for i_reg, reg in enumerate(["HRF Tapping/Left", "HRF Tapping/Right", "HRF control"]):
cedalion.plots.scalp_plot(
rec["amp"],
rec.geo3d,
betas.sel(chromo=chromo, regressor=reg),
ax[i_chr, i_reg],
min_dist=1.5 * cedalion.units.cm,
title=f"{chromo} {reg}",
vmin=vmin,
vmax=vmax,
optode_labels=True,
cmap="RdBu_r",
cb_label=r"$\beta$"
)
p.tight_layout()

T-Values
[16]:
display(results.sm.tvalues)
results.sm.tvalues.min().item(), results.sm.tvalues.max().item()
<xarray.DataArray (channel: 20, chromo: 2, regressor: 7)> Size: 2kB 0.1042 0.5645 11.06 17.62 -17.31 -17.32 ... -3.23 -2.315 53.12 53.16 26.0 Coordinates: * regressor (regressor) object 56B 'index' 'HRF control' ... 'short' * chromo (chromo) <U3 24B 'HbO' 'HbR' * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' source (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8' detector (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8' Attributes: description: AR_IRLS
[16]:
(-38.69743054194642, 252.5603397409748)
[17]:
f, ax = p.subplots(2, 3, figsize=(12, 8))
vlims = {"HbO" : [-20,20], "HbR" : [-20, 20]}
for i_chr, chromo in enumerate(betas.chromo.values):
vmin, vmax = vlims[chromo]
for i_reg, reg in enumerate(["HRF Tapping/Left", "HRF Tapping/Right", "HRF control"]):
cedalion.plots.scalp_plot(
rec["amp"],
rec.geo3d,
results.sm.tvalues.sel(chromo=chromo, regressor=reg),
ax[i_chr, i_reg],
min_dist=1.5 * cedalion.units.cm,
title=f"{chromo} {reg}",
vmin=vmin,
vmax=vmax,
optode_labels=True,
cmap="RdBu_r",
cb_label=r"$t$"
)
p.tight_layout()

SM Functionality to Document
[18]:
results.sm.params
[18]:
<xarray.DataArray (channel: 20, chromo: 2, regressor: 7)> Size: 2kB 1.986e-07 0.01244 0.2439 0.3886 -0.02322 ... -0.0298 0.02813 1.21e-06 0.09318 Coordinates: * regressor (regressor) object 56B 'index' 'HRF control' ... 'short' * chromo (chromo) <U3 24B 'HbO' 'HbR' * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' source (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8' detector (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8' Attributes: description: AR_IRLS
[19]:
results.sm.conf_int(alpha=0.05)
[19]:
<xarray.DataArray (channel: 20, chromo: 2, regressor: 7, conf_int: 2)> Size: 4kB -3.539e-06 3.936e-06 -0.03076 0.05565 ... 1.166e-06 1.255e-06 0.08615 0.1002 Coordinates: * regressor (regressor) object 56B 'index' 'HRF control' ... 'short' * conf_int (conf_int) int64 16B 0 1 * chromo (chromo) <U3 24B 'HbO' 'HbR' * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' source (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8' detector (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8' Attributes: description: AR_IRLS
[20]:
results.sm.cov_params()
[20]:
<xarray.DataArray (channel: 20, chromo: 2, regressor_r: 7, regressor_c: 7)> Size: 16kB 3.636e-12 -4.555e-10 3.26e-10 3.074e-10 ... -5.246e-07 -2.257e-11 1.284e-05 Coordinates: * regressor_r (regressor_r) object 56B 'index' 'HRF control' ... 'short' * regressor_c (regressor_c) object 56B 'index' 'HRF control' ... 'short' * chromo (chromo) <U3 24B 'HbO' 'HbR' * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' source (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S8' 'S8' detector (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D7' 'D7' 'D8' Attributes: description: AR_IRLS
[21]:
p.imshow(results.sm.cov_params()[0,0,:,:]);

[22]:
# convenience function to access the diagonal elements of the cov matrices
results.sm.regressor_variances()
[22]:
<xarray.DataArray (channel: 20, chromo: 2, regressor: 7)> Size: 2kB 3.636e-12 0.000486 0.0004861 0.0004864 ... 2.804e-07 5.182e-16 1.284e-05 Coordinates: * regressor (regressor) object 56B 'index' 'HRF control' ... 'short' * chromo (chromo) <U3 24B 'HbO' 'HbR' * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' source (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8' detector (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8' Attributes: description: AR_IRLS
Statistical Tests
[23]:
results.sm.tvalues
[23]:
<xarray.DataArray (channel: 20, chromo: 2, regressor: 7)> Size: 2kB 0.1042 0.5645 11.06 17.62 -17.31 -17.32 ... -3.23 -2.315 53.12 53.16 26.0 Coordinates: * regressor (regressor) object 56B 'index' 'HRF control' ... 'short' * chromo (chromo) <U3 24B 'HbO' 'HbR' * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' source (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8' detector (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8' Attributes: description: AR_IRLS
specifying contrasts through strings results in an array of ContrastResult objects
[24]:
hypotheses = "HRF Tapping/Left = HRF control, HRF Tapping/Right = HRF control"
results.sm.t_test(hypotheses)
[24]:
<xarray.DataArray (channel: 20, chromo: 2)> Size: 320B Test for Constraints ... Coordinates: * chromo (chromo) <U3 24B 'HbO' 'HbR' * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' source (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8' detector (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8' Attributes: description: AR_IRLS
extract tvalues and pvalues with map
FIXME: add convenience functions
[25]:
display(results.sm.t_test(hypotheses).sm.map(lambda i : i.tvalue, name="hypothesis"))
<xarray.DataArray (channel: 20, chromo: 2, hypothesis: 2)> Size: 640B 7.464 12.13 -7.041 -9.004 5.186 7.922 ... -2.348 1.474 0.8593 -1.862 -1.202 Coordinates: * chromo (chromo) <U3 24B 'HbO' 'HbR' * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' source (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8' detector (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8' Dimensions without coordinates: hypothesis Attributes: description: AR_IRLS
[26]:
display(results.sm.t_test(hypotheses).sm.map(lambda i : i.pvalue, name="hypothesis"))
<xarray.DataArray (channel: 20, chromo: 2, hypothesis: 2)> Size: 640B 8.389e-14 7.6e-34 1.908e-12 2.176e-19 2.15e-07 ... 0.1406 0.3902 0.06257 0.2295 Coordinates: * chromo (chromo) <U3 24B 'HbO' 'HbR' * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' source (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8' detector (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8' Dimensions without coordinates: hypothesis Attributes: description: AR_IRLS
Plotting Uncertainty Bands
[27]:
betas = results.sm.params
cov = results.sm.cov_params()
[28]:
sampled_betas = xr.zeros_like(betas).expand_dims({"sample" : 100}, axis=-1).copy()
for i_ch in range(sampled_betas.shape[0]):
for i_cr in range(sampled_betas.shape[1]):
sampled_betas[i_ch, i_cr, :, :] = np.random.multivariate_normal(
betas[i_ch, i_cr, :],
cov[i_ch, i_cr, :, :],
size=100,
).T
[29]:
sampled_betas
[29]:
<xarray.DataArray (channel: 20, chromo: 2, regressor: 7, sample: 100)> Size: 224kB 1.908e-06 1.903e-06 -1.916e-06 -1.118e-06 ... 0.09279 0.09628 0.09014 0.08959 Coordinates: * regressor (regressor) object 56B 'index' 'HRF control' ... 'short' * chromo (chromo) <U3 24B 'HbO' 'HbR' * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' source (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S7' 'S8' 'S8' detector (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D6' 'D7' 'D7' 'D8' Dimensions without coordinates: sample Attributes: description: AR_IRLS
[30]:
pred = glm.predict(ts_long, sampled_betas, dms)
[31]:
pred
[31]:
<xarray.DataArray (time: 23239, channel: 20, chromo: 2, sample: 100)> Size: 744MB -0.05276 -0.05342 -0.05097 -0.05139 -0.0537 ... 0.02783 0.02785 0.02849 0.02915 Coordinates: * time (time) float64 186kB 0.0 0.128 0.256 ... 2.974e+03 2.974e+03 * chromo (chromo) <U3 24B 'HbO' 'HbR' samples (time) int64 186kB 0 1 2 3 4 ... 23235 23236 23237 23238 * channel (channel) object 160B 'S1D1' 'S1D2' 'S1D3' ... 'S8D7' 'S8D8' short_channel (channel) object 160B 'S1D9' 'S1D9' ... 'S8D16' 'S8D16' comp_group (channel) int64 160B 0 0 0 1 1 1 2 2 3 3 4 4 4 5 5 5 6 6 7 7 source (channel) object 160B 'S1' 'S1' 'S1' 'S2' ... 'S7' 'S8' 'S8' detector (channel) object 160B 'D1' 'D2' 'D3' 'D1' ... 'D7' 'D7' 'D8' Dimensions without coordinates: sample
[32]:
pred_mean = pred.mean("sample")
pred_std = pred.std("sample")
[33]:
mm = pred_mean.loc[slice(60,80), "S5D5", "HbO"]
ss = pred_std.loc[slice(60,80), "S5D5", "HbO"]
p.plot(mm.time, mm, c="r")
p.fill_between(mm.time, mm-3*ss, mm+3*ss, fc="y", alpha=.8)
p.xlabel("time / s")
p.ylabel(r"$\Delta$ c / uM");
