Here we show how to use limorhyde2
to quantify
rhythmicity in data from one condition. The data are based on mouse
liver samples from the circadian gene expression
atlas in mammals (GSE54650).
library('data.table')
library('ggplot2')
library('limorhyde2')
library('qs')
# doParallel::registerDoParallel() # register a parallel backend to minimize runtime
theme_set(theme_bw())
The expression data are in a matrix with one row per gene and one column per sample. The metadata are in a table with one row per sample. To save time and space, the expression data include only a subset of genes.
= GSE54650$y
y 1:5, 1:5]
y[#> GSM1321182 GSM1321183 GSM1321184 GSM1321185 GSM1321186
#> 13088 10.209968 10.503358 9.714828 9.477728 10.134381
#> 13170 7.871989 7.281691 6.946495 6.266409 7.150140
#> 13869 6.851685 7.406234 7.781294 7.924764 8.089538
#> 100009600 5.055931 5.199767 5.121511 5.155912 5.219873
#> 100418030 6.174130 6.547075 6.521593 6.413817 6.451957
= GSE54650$metadata
metadata
metadata#> sample tissue time
#> <char> <char> <int>
#> 1: GSM1321182 liver 18
#> 2: GSM1321183 liver 20
#> 3: GSM1321184 liver 22
#> 4: GSM1321185 liver 24
#> 5: GSM1321186 liver 26
#> 6: GSM1321187 liver 28
#> 7: GSM1321188 liver 30
#> 8: GSM1321189 liver 32
#> 9: GSM1321190 liver 34
#> 10: GSM1321191 liver 36
#> 11: GSM1321192 liver 38
#> 12: GSM1321193 liver 40
#> 13: GSM1321194 liver 42
#> 14: GSM1321195 liver 44
#> 15: GSM1321196 liver 46
#> 16: GSM1321197 liver 48
#> 17: GSM1321198 liver 50
#> 18: GSM1321199 liver 52
#> 19: GSM1321200 liver 54
#> 20: GSM1321201 liver 56
#> 21: GSM1321202 liver 58
#> 22: GSM1321203 liver 60
#> 23: GSM1321204 liver 62
#> 24: GSM1321205 liver 64
#> sample tissue time
The first step is to fit a series of linear models based on periodic
splines for each genomic feature, in this case each gene, using limma.
getModelFit()
takes several arguments besides the
expression data and metadata, but here we just use the defaults. For
example, the data are from one condition, so we leave
condColname
as NULL
. Also, the expression data
are from microarrays and already log-transformed, so we leave
method
as 'trend'
.
= getModelFit(y, metadata) fit
The next step is obtain posterior estimates of the model coefficients using multivariate adaptive shrinkage (mashr), which learns patterns in the data and accounts for noise in the original fits.
= getPosteriorFit(fit) fit
We can now use the posterior fits to compute rhythm statistics, i.e. properties of the curve, for each gene.
= getRhythmStats(fit) rhyStats
We can examine the distributions of the statistics in various ways, such as ranking genes by peak-to-trough amplitude (no p-values necessary) or plotting peak-to-trough amplitude vs. peak phase.
print(rhyStats[order(-peak_trough_amp)], nrows = 10L)
#> feature peak_phase peak_value trough_phase trough_value peak_trough_amp
#> <char> <num> <num> <num> <num> <num>
#> 1: 13170 8.6775145 10.290602 22.346123 6.765579 3.525022206
#> 2: 266645 19.5106275 9.800471 8.768400 8.237576 1.562895177
#> 3: 13088 18.6818895 10.412693 9.015083 8.883997 1.528695535
#> 4: 13869 8.3932348 8.524375 18.785793 7.207670 1.316705132
#> 5: 68396 20.3289764 9.678209 8.714920 8.809740 0.868468524
#> ---
#> 46: 100009600 2.4406727 5.125981 18.711851 5.124404 0.001577536
#> 47: 12613 0.4067764 5.049751 9.355932 5.048220 0.001530589
#> 48: 14616 0.4067829 5.420731 17.491505 5.419337 0.001394137
#> 49: 23801 16.2711757 5.371221 9.355932 5.370178 0.001043270
#> 50: 115485688 19.9322039 4.235861 11.796608 4.235586 0.000275261
#> mesor
#> <num>
#> 1: 8.478849
#> 2: 9.092471
#> 3: 9.761742
#> 4: 7.853238
#> 5: 9.283828
#> ---
#> 46: 5.125043
#> 47: 5.048960
#> 48: 5.420226
#> 49: 5.370578
#> 50: 4.235683
ggplot(rhyStats) +
geom_point(aes(x = peak_phase, y = peak_trough_amp), alpha = 0.2) +
xlab('Peak phase (h)') +
ylab('Peak-to-trough amplitude (norm.)') +
scale_x_continuous(breaks = seq(0, 24, 4))
We can also compute the expected measurements for one or more genes at one or more time-points, which correspond to the fitted curves. Here we plot the posterior fits and observed expression for three of the top rhythmic genes (converting from gene id to gene symbol).
= data.table(
genes id = c('13088', '13170', '13869'),
symbol = c('Cyp2b10', 'Dbp', 'Erbb4'))
= getExpectedMeas(fit, times = seq(0, 24, 0.5), features = genes$id)
measFit := i.symbol, on = .(feature = id)]
measFit[genes, symbol print(measFit, nrows = 10L)
#> time feature value symbol
#> <num> <char> <num> <char>
#> 1: 0.0 13088 10.146744 Cyp2b10
#> 2: 0.0 13170 6.949242 Dbp
#> 3: 0.0 13869 7.631226 Erbb4
#> 4: 0.5 13088 10.108186 Cyp2b10
#> 5: 0.5 13170 7.081859 Dbp
#> ---
#> 143: 23.5 13170 6.853361 Dbp
#> 144: 23.5 13869 7.570816 Erbb4
#> 145: 24.0 13088 10.146744 Cyp2b10
#> 146: 24.0 13170 6.949242 Dbp
#> 147: 24.0 13869 7.631226 Erbb4
Next we combine the observed expression data and metadata. The curves
show how limorhyde2
is able to fit non-sinusoidal
rhythms.
= mergeMeasMeta(y, metadata, features = genes$id)
measObs := i.symbol, on = .(feature = id)]
measObs[genes, symbol print(measObs, nrows = 10L)
#> Key: <sample>
#> sample tissue time feature meas symbol
#> <char> <char> <int> <char> <num> <char>
#> 1: GSM1321182 liver 18 13088 10.209968 Cyp2b10
#> 2: GSM1321182 liver 18 13170 7.871989 Dbp
#> 3: GSM1321182 liver 18 13869 6.851685 Erbb4
#> 4: GSM1321183 liver 20 13088 10.503358 Cyp2b10
#> 5: GSM1321183 liver 20 13170 7.281691 Dbp
#> ---
#> 68: GSM1321204 liver 62 13170 8.656560 Dbp
#> 69: GSM1321204 liver 62 13869 6.970524 Erbb4
#> 70: GSM1321205 liver 64 13088 10.352095 Cyp2b10
#> 71: GSM1321205 liver 64 13170 8.157525 Dbp
#> 72: GSM1321205 liver 64 13869 7.048457 Erbb4
ggplot() +
facet_wrap(vars(symbol), scales = 'free_y', nrow = 1) +
geom_line(aes(x = time, y = value), data = measFit) +
geom_point(aes(x = time %% 24, y = meas), shape = 21, size = 1.5,
data = measObs) +
labs(x = 'Circadian time (h)', y = 'Expression (norm.)') +
scale_x_continuous(breaks = seq(0, 24, 4))