Quantifying rhythmicity in one condition

Introduction

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).

Load packages

library('data.table')
library('ggplot2')
library('limorhyde2')
library('qs')

# doParallel::registerDoParallel() # register a parallel backend to minimize runtime
theme_set(theme_bw())

Load the data

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.

y = GSE54650$y
y[1:5, 1:5]
#>           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

metadata = GSE54650$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

Fit linear models

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'.

fit = getModelFit(y, metadata)

Get posterior fits

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.

fit = getPosteriorFit(fit)

Get rhythm statistics

We can now use the posterior fits to compute rhythm statistics, i.e. properties of the curve, for each gene.

rhyStats = getRhythmStats(fit)

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))

Get observed and fitted time-courses

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).

genes = data.table(
  id = c('13088', '13170', '13869'),
  symbol = c('Cyp2b10', 'Dbp', 'Erbb4'))

measFit = getExpectedMeas(fit, times = seq(0, 24, 0.5), features = genes$id)
measFit[genes, symbol := i.symbol, on = .(feature = id)]
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.

measObs = mergeMeasMeta(y, metadata, features = genes$id)
measObs[genes, symbol := i.symbol, on = .(feature = id)]
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))