import velocycle as vcy
import scvelo as scv
import scanpy as sc
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from velocycle import *
import anndata
import pyro
import torch
import copy
RNA velocity with VeloCycle
Download Presentation: VeloCycle
The cell cycle is an interesting case for RNA velocity estimation, as pseudotime methods along often fail as estimations of cyclical processes. Moreover, RNA velocity corresponds roughly to cell cycle speed, which is both experimentally verifiable. The cell cycle also unfolds on a timescale of less than 24 hours, which is well suited for studying cell dynamics using RNA lifecycle kinetics, such as with RNA velocity.
A recent method has been developed called VeloCycle to estimate RNA velocity of the cell cycle on the real time scale. This method offers several advantages over existing approaches: - The ability to estimate uncertainty of velocity estimates (i.e. velocity confidence). - The ability to estimate both the low dimensional manifold and the velocity jointly. - The ability to perform statistical tests of velocity between conditions. - The ability to convert velocity estimates to a “real” time scale.
Comparing cell cycle velocities might be useful in a variable of scientific contexts: - Do two cancer subtumors proliferate as similar speeds? - Does a particular gene knockout or mutant impact the cell cycle speed? - Do progenitor cells in different regions of an organ (i.e., brain) or at different developmental stages divide equally quickly?
Here, we will offer a short tutorial into VeloCycle, using the ductal cells from the pancreas dataset above. This will also offer insight into probabilistic modeling in Pyro, which is an advanced method used by many tools for modeling complex biological data.
= scv.datasets.pancreas()
adata_raw = adata_raw[adata_raw.obs["clusters"].isin(["Ductal"])].copy()
adata_cycling adata_cycling
AnnData object with n_obs × n_vars = 916 × 27998
obs: 'clusters_coarse', 'clusters', 'S_score', 'G2M_score'
var: 'highly_variable_genes'
uns: 'clusters_coarse_colors', 'clusters_colors', 'day_colors', 'neighbors', 'pca'
obsm: 'X_pca', 'X_umap'
layers: 'spliced', 'unspliced'
obsp: 'distances', 'connectivities'
Load and filter dataset
= adata_cycling.copy() adata
='clusters') sc.pl.umap(adata, color
= {"pancreas_ductal":adata[adata.obs["clusters"].isin(["Ductal"])].copy()} full_adatas
# Filter lowly-expressed genes and concatenate all datasets
for a in full_adatas.keys():
print(full_adatas[a].shape)
=int((full_adatas[a].n_obs)*0.10))
sc.pp.filter_genes(full_adatas[a], min_cells
= anndata.concat(full_adatas, label="batch", join ="outer") data
(916, 27998)
# Perform some very basic gene filtering by unspliced counts
= data[:, (data.layers["unspliced"].toarray().mean(0) > 0.1)].copy()
data
# Perform some very basic gene filtering by spliced counts
= data[:, data.layers["spliced"].toarray().mean(0) > 0.2].copy() data
= [i.upper() for i in data.var.index]
data.var.index data
AnnData object with n_obs × n_vars = 916 × 1394
obs: 'clusters_coarse', 'clusters', 'S_score', 'G2M_score', 'batch'
obsm: 'X_pca', 'X_umap'
layers: 'spliced', 'unspliced'
# Create design matrix for dataset with a single batch
= preprocessing.make_design_matrix(data, ids="batch") batch_design_matrix
# Rough approximation of the cell cycle phase using categorical approaches
=utils.S_genes_human, g2m_genes=utils.G2M_genes_human) sc.tl.score_genes_cell_cycle(data, s_genes
WARNING: genes are not in var_names and ignored: ['BLM', 'BRIP1', 'CASP8AP2', 'CCNE2', 'CDC45', 'CDC6', 'CDCA7', 'CHAF1B', 'CLSPN', 'DSCC1', 'DTL', 'E2F8', 'EXO1', 'FEN1', 'GINS2', 'GMNN', 'MCM2', 'MCM4', 'MCM5', 'MCM6', 'MLF1IP', 'MSH2', 'PCNA', 'POLD3', 'RAD51', 'RAD51AP1', 'RPA2', 'RRM1', 'RRM2', 'SLBP', 'UBR7', 'UHRF1', 'UNG', 'USP1', 'WDR76']
WARNING: genes are not in var_names and ignored: ['ANLN', 'AURKA', 'AURKB', 'BIRC5', 'BUB1', 'CCNB2', 'CDC20', 'CDC25C', 'CDCA3', 'CDCA8', 'CENPA', 'CENPF', 'CKAP2L', 'CKS1B', 'CTCF', 'DLGAP5', 'ECT2', 'FAM64A', 'G2E3', 'GAS2L3', 'GTSE1', 'HJURP', 'HMGB2', 'HMMR', 'HN1', 'KIF20B', 'KIF2C', 'LBR', 'MKI67', 'NDC80', 'NEK2', 'NUF2', 'PSRC1', 'TACC3', 'TMPO', 'TTK', 'TUBB4B', 'UBE2C']
# Create size-normalized data layers
preprocessing.normalize_total(data)
# Get biologically-relevant gene set to use for velocity estimation
= utils.get_cycling_gene_set(size="Medium", species="Human") full_keep_genes
Initialize cycle and phase objects with priors
= 1
n_harm = cycle.Cycle.trivial_prior(gene_names=full_keep_genes, harmonics=n_harm)
cycle_prior = preprocessing.filter_shared_genes(cycle_prior, data, filter_type="intersection") cycle_prior, data_to_fit
# Update the priors for gene harmonics
# to gene-specific means and stds
= data_to_fit.layers['spliced'].toarray()
S = S.mean(axis=0) #sum over cells
S_means = np.log(S_means)
nu0 = np.std(np.log(S+1), axis=0)/2
nu0std
=np.vstack((nu0, 0*nu0, 0*nu0))
S_frac_means
cycle_prior.set_means(S_frac_means)
=np.vstack((nu0std, 0.5*nu0std, 0.5*nu0std))
S_frac_stds cycle_prior.set_stds(S_frac_stds)
# Obtain a PCA prior for individual cell phases
= phases.Phases.from_pca_heuristic(data_to_fit,
phase_prior =5.0,
concentration=True,
plot=1) small_count
# Shift the phase prior to have maximum correlation with the total raw UMI counts
= phase_prior.max_corr(data_to_fit.obs.n_scounts)
(shift, maxcor, allcor) =-shift)
phase_prior.rotate(angle'.', c='black')
plt.plot(phase_prior.phis, data_to_fit.obs.n_scounts, 0, np.pi*2)
plt.xlim(0, np.pi, 2*np.pi],["0", "π", "2π"])
plt.xticks(["PCA Phase Prior")
plt.xlabel("Raw Spliced UMIs")
plt.ylabel( plt.show()
Run the manifold-learning module
pyro.clear_param_store()
# Set batch effect to zero because there is only a single dataset/batch
= torch.zeros((batch_design_matrix.shape[1], S.shape[1], 1)).float()
Δν = {"Δν": Δν} condition_on_dict
= preprocessing.preprocess_for_phase_estimation(anndata=data_to_fit,
metapar =cycle_prior,
cycle_obj=phase_prior,
phase_obj=batch_design_matrix,
design_mtx=n_harm,
n_harmonics=condition_on_dict) condition_on
= phase_inference_model.PhaseFitModel(metaparams=metapar,
phase_fit =condition_on_dict)
condition_on phase_fit.check_model()
Trace Shapes:
Param Sites:
Sample Sites:
cells dist |
value 916 |
genes dist |
value 61 |
batches dist |
value 1 |
ν dist 61 1 | 3
value 61 1 | 3
Δν dist 1 61 1 |
value 1 61 1 |
ϕxy dist 916 | 2
value 916 | 2
ϕ dist | 916
value | 916
ζ dist | 916 3
value | 916 3
ElogS dist | 1 1 61 916
value | 1 1 61 916
shape_inv dist 61 1 |
value 61 1 |
S dist 1 1 61 916 |
value 61 916 |
= 1000
num_steps = 0.03
initial_lr = 0.005
final_lr = final_lr / initial_lr
gamma = gamma ** (1 / num_steps)
lrd = pyro.optim.ClippedAdam({'lr': initial_lr, 'lrd': lrd, 'betas': (0.80, 0.99)})
adam
=adam, num_steps=num_steps) phase_fit.fit(optimizer
Visualize the results
# Put estimations in new objects
= phase_fit.cycle_pyro
cycle_pyro = phase_fit.phase_pyro phase_pyro
= phase_fit.posterior["ElogS"].squeeze().numpy()
fit_ElogS = phase_fit.posterior["ElogS2"].squeeze().numpy() fit_ElogS2
= {'G1':"tab:blue", 'S':"tab:orange", 'G2M':"tab:green"}
name2color = ["CDK1", "HELLS", "SON", "TOP2A", "HAT1"]
gene_list = np.array(data_to_fit.var.index)
gene_names None,(24, 4))
plt.figure(= 1
ix for i in range(0, len(gene_list)):
= gene_list[i]
g 1, len(gene_list), ix)
plt.subplot(
plt.scatter(phase_pyro.phis, ==g)[0][0], :].squeeze().cpu().numpy(),
metapar.S[np.where(gene_names=10, alpha=0.5, c=[name2color[x] for x in data_to_fit.obs["phase"]])
s
plt.scatter(phase_pyro.phis, ==g)[0][0], :]),
np.exp(fit_ElogS2[np.where(gene_names=10, c="black")
s
plt.title(g)"ϕ")
plt.xlabel("counts")
plt.ylabel(+=1
ix0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi],["0", "π/2", "π", "3π/2", "2π"])
plt.xticks([0, 2*np.pi)
plt.xlim(
plt.tight_layout() plt.show()
= phase_fit.fourier_coef[1]
xs = phase_fit.fourier_coef[2]
ys = np.log10( np.sqrt(xs**2+ys**2) / phase_fit.fourier_coef_sd[1:, :].sum(0) )
r = np.arctan2(xs, ys)
angle = (angle)%(2*np.pi)
angle
= pd.DataFrame([angle, r])
phis_df = data_to_fit.var.index
phis_df.columns
= pd.concat([phase_fit.cycle_pyro.means, phase_fit.cycle_pyro.stds, phis_df]).T
phase_data_frame = ["nu0 mean", "nu1sin mean", "nu1cos mean",
phase_data_frame.columns "nu0 std", "nu1sin std", "nu1cos std", "peak_phase", "amplitude"]
"is_seurat_marker"] = [True if i in list(utils.S_genes_human)+list(utils.G2M_genes_human) else False for i in phase_data_frame.index]
phase_data_frame[
phase_data_frame.head()
= pd.DataFrame(phase_fit.phase_pyro.phis.numpy())
phis_df = data_to_fit.obs.index
phis_df.index = ["cell_cycle_phi"]
phis_df.columns = data_to_fit.obs.merge(phis_df, left_index=True, right_index=True) phase_data_frame_cells
# Define the number of bins
= 10
num_bins = 2 * np.pi / num_bins
bin_width
# Calculate the bin index for each gene
'bin_index'] = ((phase_data_frame['peak_phase'] + 2 * np.pi) % (2 * np.pi) / bin_width).astype(int)
phase_data_frame[
# Group genes by bin index and find top 10 genes in each bin
= phase_data_frame.groupby('bin_index', group_keys=False).apply(lambda group: group.nlargest(5, 'amplitude')) top_genes_per_bin
= [a.upper() for a in cycle_prior.means.columns]
keep_genes
= np.array(keep_genes)
gene_names
from matplotlib.colors import ListedColormap, LinearSegmentedColormap
import matplotlib.transforms as mtransforms
import seaborn as sns
= [a.upper() for a in cycle_prior.means.columns]
keep_genes = np.array(keep_genes)
gene_names = list(utils.S_genes_human)
S_genes_human = list(utils.G2M_genes_human)
G2M_genes_human = [S_genes_human, G2M_genes_human, [i.upper() for i in gene_names if i.upper() not in S_genes_human+G2M_genes_human]]
phases_list
= []
g = []
gradient for i in range(len(phases_list)):
for j in range(len(phases_list[i])):
g.append(phases_list[i][j])
gradient.append(i)
= pd.DataFrame({'Gene': g, 'Color': gradient}).set_index('Gene').to_dict()['Color']
color_gradient_map = pd.Series(gene_names).map(color_gradient_map)
colored_gradient
= phase_fit.fourier_coef[1]
xs = phase_fit.fourier_coef[2]
ys = np.log10( np.sqrt(xs**2+ys**2) / phase_fit.fourier_coef_sd[1:, :].sum(0) )
r = np.arctan2(xs, ys)
angle = (angle)%(2*np.pi)
angle
=50
N= (2*np.pi) / N
width
= plt.figure(figsize = (6, 6))
fig = fig.add_subplot(projection='polar')
ax
# First: only plot dots with a color assignment
= angle[~np.isnan(colored_gradient.values)]
angle_subset = r[~np.isnan(colored_gradient.values)]
r_subset = colored_gradient.values[~np.isnan(colored_gradient.values)]
color_subset
# Remove genes with very low expression
= angle_subset[r_subset>=-12]
angle_subset = color_subset[r_subset>=-12]
color_subset = gene_names[r_subset>=-12]
gene_names_subset = r_subset[r_subset>=-12]
r_subset
=100
x# Take a subset of most highly expressing genes to print the names
= angle_subset[r_subset>np.percentile(r_subset, x)]
angle_subset_best = color_subset[r_subset>=np.percentile(r_subset, x)]
color_subset_best = gene_names_subset[r_subset>=np.percentile(r_subset, x)]
gene_names_subset_best = r_subset[r_subset>=np.percentile(r_subset, x)]
r_subset_best
# Plot all genes in phases list
= {0:"tab:orange", 1:"tab:green", 2:"tab:grey", 3:"tab:blue"}
num2color =[num2color[i] for i in color_subset], s=50, alpha=0.3, edgecolor='none', rasterized=True)
ax.scatter(angle_subset, r_subset, c
# Select and plot on top the genes marking S and G2M traditionally
= angle_subset[color_subset!=2]
angle_subset = r_subset[color_subset!=2]
r_subset = gene_names_subset[color_subset!=2]
gene_names_subset = color_subset[color_subset!=2]
color_subset
=[num2color[i] for i in color_subset], s=50, alpha=1, edgecolor='none',rasterized=True)
ax.scatter(angle_subset, r_subset, c
# Annotate genes
for (i, txt), c in zip(enumerate(gene_names), colored_gradient.values):
if txt in top_genes_per_bin.index:
= np.where(np.array(gene_names)==txt)[0][0]
ix 0]+txt[1:].upper(), (angle[ix], r[ix]+0.02))
ax.annotate(txt[
0, 2*np.pi)
plt.xlim(-1, )
plt.ylim(-1, -0.5, 0, 0.5, 1], size=15)
plt.yticks([0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi],["0", "π/2", "π", "3π/2", "2π"], size=15)
plt.xticks([
plt.tight_layout() plt.show()
Run the velocity-learning module
pyro.clear_param_store()
= copy.deepcopy(batch_design_matrix) condition_design_matrix
= 0
n_velo_harmonics = angularspeed.AngularSpeed.trivial_prior(condition_names=["pancreas_ductal"],
speed_prior =n_velo_harmonics) harmonics
= {"ϕxy":phase_pyro.phi_xy_tensor.T,
condition_on_dict "ν": cycle_pyro.means_tensor.T.unsqueeze(-2),
"Δν": torch.tensor(phase_fit.delta_nus),
"shape_inv": torch.tensor(phase_fit.disp_pyro).unsqueeze(-1)}
= preprocessing.preprocess_for_velocity_estimation(data_to_fit,
metaparameters_velocity
cycle_pyro,
phase_pyro,
speed_prior,float(),
condition_design_matrix.float(),
batch_design_matrix.=n_harm,
n_harmonics=metapar.count_factor,
count_factor=n_velo_harmonics,
ω_n_harmonics=torch.tensor(0.0).detach().clone().float(),
μγ=torch.tensor(0.5).detach().clone().float(),
σγ=torch.tensor(2.0).detach().clone().float(),
μβ=torch.tensor(3.0).detach().clone().float(),
σβ="lrmn",
model_type=condition_on_dict) condition_on
= velocity_inference_model.VelocityFitModel(metaparams=metaparameters_velocity,
velocity_fit =condition_on_dict) condition_on
velocity_fit.check_model()
Trace Shapes:
Param Sites:
Sample Sites:
cells dist |
value 916 |
genes dist |
value 61 |
harmonics dist |
value 1 |
conditions dist |
value 1 |
batches dist |
value 1 |
logγg dist 61 1 |
value 61 1 |
logβg dist 61 1 |
value 61 1 |
rho_real dist 61 1 |
value 61 1 |
γg dist 61 1 | 61 1
value | 61 1
ν dist 61 1 | 3
value 61 1 | 3
Δν dist 1 1 1 61 1 |
value 1 1 1 61 1 |
ϕxy dist 916 | 2
value 916 | 2
ϕ dist | 916
value | 916
ζ dist | 916 3
value | 916 3
ζ_dϕ dist | 916 3
value | 916 3
νω dist 1 1 1 1 |
value 1 1 1 1 |
ζω dist | 1 916
value | 1 916
ElogS dist | 1 1 61 916
value | 1 1 61 916
ω dist | 1 916
value | 1 916
ElogU dist | 1 1 61 916
value | 1 1 61 916
shape_inv dist 61 1 |
value 61 1 |
S dist 1 1 61 916 |
value 61 916 |
U dist 1 1 61 916 |
value 61 916 |
velocity_fit.check_guide()
Trace Shapes:
Param Sites:
ν_locs 61 1 3
ν_scales 61 1 3
Δν_locs 1 1 1 61 1
ϕxy_locs 916 2
logβg_locs 61 1
logβg_scales 61 1
loc 62
cov_factor 62 5
cov_diag 62
rho_real_loc 61
shape_inv_locs 61 1
Sample Sites:
cells dist |
value 916 |
genes dist |
value 61 |
harmonics dist |
value 1 |
conditions dist |
value 1 |
batches dist |
value 1 |
logγg dist 61 1 |
value 61 1 |
rho_real dist 61 1 |
value 61 1 |
logβg dist 61 1 |
value 61 1 |
νω dist 1 1 1 1 |
value 1 1 1 1 |
= 3000
num_steps = 0.03
initial_lr = 0.005
final_lr = final_lr / initial_lr
gamma = gamma ** (1 / num_steps)
lrd = pyro.optim.ClippedAdam({'lr': initial_lr, 'lrd': lrd, 'betas': (0.80, 0.99)})
adam
=adam, num_steps=num_steps) velocity_fit.fit(optimizer
# Put estimations in new objects
= velocity_fit.cycle_pyro
cycle_pyro = velocity_fit.phase_pyro
phase_pyro = velocity_fit.speed_pyro
speed_pyro
= velocity_fit.posterior["ElogS"].squeeze()
fit_ElogS = velocity_fit.posterior["ElogU"].squeeze()
fit_ElogU
= velocity_fit.posterior["ElogS2"].squeeze()
fit_ElogS2 = velocity_fit.posterior["ElogU2"].squeeze()
fit_ElogU2
= velocity_fit.log_gammas
log_gammas = velocity_fit.log_betas log_betas
# Store entire posterior sampling into an object
= velocity_fit.posterior full_pps_velo
= full_pps_velo["ω"].squeeze().numpy() / torch.exp(torch.mean(full_pps_velo["logγg"].squeeze().mean(0).detach())).numpy() velocity
1))
plt.hist(velocity.mean("Velocity Estimate")
plt.xlabel("Frequency")
plt.ylabel("Periodic Model Velocity Posterior")
plt.title( plt.show()
# Cell cycle time in hours
print(2*np.pi/velocity.mean())
16.528259838181253
= velocity_fit velocity_fit0
pyro.clear_param_store()
= copy.deepcopy(batch_design_matrix) condition_design_matrix
= 1
n_velo_harmonics = angularspeed.AngularSpeed.trivial_prior(condition_names=["pancreas_ductal"], harmonics=n_velo_harmonics,
speed_prior =0.0, stds=3.0)
means
"nu1_cos"] = 0.01
speed_prior.stds.loc["nu1_sin"] = 0.01 speed_prior.stds.loc[
= {"ϕxy":phase_pyro.phi_xy_tensor.T,
condition_on_dict "ν": cycle_pyro.means_tensor.T.unsqueeze(-2),
"Δν": torch.tensor(phase_fit.delta_nus),
"shape_inv": torch.tensor(phase_fit.disp_pyro).unsqueeze(-1)}
= preprocessing.preprocess_for_velocity_estimation(data_to_fit,
metaparameters_velocity
cycle_pyro,
phase_pyro,
speed_prior,float(),
condition_design_matrix.float(),
batch_design_matrix.=n_harm,
n_harmonics=metapar.count_factor,
count_factor=n_velo_harmonics,
ω_n_harmonics=torch.tensor(0.0).detach().clone().float(),
μγ=torch.tensor(0.5).detach().clone().float(),
σγ=torch.tensor(2.0).detach().clone().float(),
μβ=torch.tensor(3.0).detach().clone().float(),
σβ="lrmn",
model_type=condition_on_dict) condition_on
= velocity_inference_model.VelocityFitModel(metaparams=metaparameters_velocity,
velocity_fit =condition_on_dict, early_exit=False,
condition_on=500, n_per_bin=50) num_samples
velocity_fit.check_model()
Trace Shapes:
Param Sites:
Sample Sites:
cells dist |
value 916 |
genes dist |
value 61 |
harmonics dist |
value 3 |
conditions dist |
value 1 |
batches dist |
value 1 |
logγg dist 61 1 |
value 61 1 |
logβg dist 61 1 |
value 61 1 |
rho_real dist 61 1 |
value 61 1 |
γg dist 61 1 | 61 1
value | 61 1
ν dist 61 1 | 3
value 61 1 | 3
Δν dist 1 1 1 61 1 |
value 1 1 1 61 1 |
ϕxy dist 916 | 2
value 916 | 2
ϕ dist | 916
value | 916
ζ dist | 916 3
value | 916 3
ζ_dϕ dist | 916 3
value | 916 3
νω dist 1 3 1 1 |
value 1 3 1 1 |
ζω dist | 3 916
value | 3 916
ElogS dist | 1 1 61 916
value | 1 1 61 916
ω dist | 1 916
value | 1 916
ElogU dist | 1 1 61 916
value | 1 1 61 916
shape_inv dist 61 1 |
value 61 1 |
S dist 1 1 61 916 |
value 61 916 |
U dist 1 1 61 916 |
value 61 916 |
= 3000
num_steps = 0.03
initial_lr = 0.005
final_lr = final_lr / initial_lr
gamma = gamma ** (1 / num_steps)
lrd = pyro.optim.ClippedAdam({'lr': initial_lr, 'lrd': lrd, 'betas': (0.80, 0.99)})
adam
=adam, num_steps=num_steps) velocity_fit.fit(optimizer
# Put estimations in new objects
= velocity_fit.cycle_pyro
cycle_pyro = velocity_fit.phase_pyro
phase_pyro = velocity_fit.speed_pyro
speed_pyro
= velocity_fit.posterior["ElogS"].squeeze()
fit_ElogS = velocity_fit.posterior["ElogU"].squeeze()
fit_ElogU
= velocity_fit.posterior["ElogS2"].squeeze()
fit_ElogS2 = velocity_fit.posterior["ElogU2"].squeeze()
fit_ElogU2
= velocity_fit.log_gammas
log_gammas = velocity_fit.log_betas log_betas
# Store entire posterior sampling into an object
= velocity_fit.posterior full_pps_velo
# See the value of the mean gamma
"logγg"].squeeze().mean(0).detach())).numpy() torch.exp(torch.mean(full_pps_velo[
array(0.9531931, dtype=float32)
= full_pps_velo["ω"].squeeze().numpy() / torch.exp(torch.mean(full_pps_velo["logγg"].squeeze().mean(0).detach())).numpy()
omega = phase_pyro.phis
phi = []
omegas = []
phis = {"pancreas_ductal":0}
n2n = np.array([n2n[i] for i in np.array(data_to_fit.obs["batch"])])
ids for i in range(len(data_to_fit.obs["batch"].unique())):
= omega[:,np.where(ids == i)]
omega1 = phi[np.where(ids == i)]
phi1
omegas.append(omega1)
phis.append(phi1)
= np.array(data_to_fit.obs["batch"].unique()) #list(adatas.keys())
labels
= ["tab:blue"]
colors for i in range(len(omegas)):
0)[0][np.argsort(phis[i])], c="black", linestyle='dashed')
plt.plot(phis[i][np.argsort(phis[i])], omegas[i].mean(
= np.percentile(omega[:, ids==i], 5, axis=0)
tmp5 = np.percentile(omega[:, ids==i], 95, axis=0)
tmp95 print(((2*np.pi)/omega[:, ids==i]).mean(), ((2*np.pi)/omega[:, ids==i]).std())
= phi[ids==i]
phi_i =phi_i[np.argsort(phi_i)],
plt.fill_between(x=tmp5[np.argsort(phi_i)],
y1=tmp95[np.argsort(phi_i)],
y2=0.6, color=colors[i], label = labels[i])
alpha"Cell Cycle Phase")
plt.xlabel(0, 2*np.pi)
plt.xlim("Scaled Velocity")
plt.ylabel(
plt.legend() plt.show()
17.340261 1.869181