Table of Contents
The GRM module builds the genomic relationship matrices (GRMs) used as random-effect covariance matrices by UVLMM, REMMA, fastREML and the other mixed-model modules. It also post-processes existing GRMs. The files it writes use the GCTA format, so a GRM built by fastGMA can be used in GCTA, and a GRM built by GCTA can be used in fastGMA.
There are two entry points:
| Command | Short form | Purpose |
|---|---|---|
fastgma --make-grm [options] |
-m |
Compute a GRM from genotypes or a feature matrix |
fastgma --process-grm [options] |
-d |
Merge, convert, invert, decompose or filter an existing GRM |
As elsewhere in fastGMA, a GRM is referred to by its prefix: --grm output/test means the files output/test.grm.bin, output/test.grm.id and so on.
1. Building a GRM (--make-grm) #
GRM types #
Choose the matrix with --grm-type. Let \(x\) be the genotype dosage (0/1/2 copies of the A1 allele), \(p\) the A1 frequency and \(q = 1 - p\).
--grm-type |
Matrix | Genotype coding for each SNP | GCTA equivalent |
|---|---|---|---|
add (default) |
Additive | \(x - 2p\) | --make-grm |
dom |
Dominance, breeding-value (“statistical”) coding | \(0 \to -2p^2\), \(1 \to 2pq\), \(2 \to -2q^2\) | --make-grm-d |
Under Hardy–Weinberg equilibrium the dominance code is uncorrelated with the additive code, so fitting the add and dom GRMs together splits the genetic variance into the classical additive and dominance variances.
The scaling is set with --code-type:
--code-type |
Additive GRM | GCTA equivalent |
|---|---|---|
2 (default) |
each SNP standardized by its own SD, then averaged: \(A_{jk} = \frac{1}{N_{jk}} \sum_i z_{ij} z_{ik}\), with \(z = (x - 2p)/\sqrt{2pq}\) | --make-grm |
1 |
VanRaden-type: \(A_{jk} = \sum_i (x_{ij} - 2p_i)(x_{ik} - 2p_i) \big/ \sum_i 2p_i q_i\) | --make-grm-alg 1 |
Both additive scalings are special cases of --alpha, which assumes that the variance of a SNP effect is proportional to \((2pq)^\alpha\). With \(v_i = 2p_i q_i\), the additive GRM is
so that the expected diagonal is 1 (for --ffile input see “Inputs” below). --alpha -1 gives --code-type 2 and --alpha 0 gives --code-type 1, with identical output files. Under \(\alpha = -1\) every SNP explains the same share of the genetic variance; values above \(-1\) shift the variance toward common SNPs and values below \(-1\) toward rare SNPs. Speed et al. (2017, Nature Genetics 49:986–992) recommended \(\alpha = -0.25\) for human complex traits. To choose \(\alpha\) for your data, build GRMs over a grid of values (for example −1, −0.75, −0.5, −0.25, 0) and compare the REML log-likelihoods. --alpha cannot be combined with --code-type and applies only to add. With missing genotypes, each element is rescaled by \(M/N_{jk}\) as for the other scalings (see “Quality control and missing genotypes” below).
For the dominance GRM the two --code-type choices apply, using the variance of the dominance coding instead of \(2pq\). An epistatic GRM (for example additive-by-additive) is built from existing GRMs with --process-grm --make-epis (see Section 3).
Inputs #
Provide exactly one of:
| Option | Input | Files read |
|---|---|---|
--bfile <prefix> |
PLINK binary genotypes | <prefix>.bed, <prefix>.bim, <prefix>.fam |
--ffile <prefix> |
Any numeric feature-by-sample matrix (gene expression, methylation, dosages, …) | <prefix>.fmat, <prefix>.bim, <prefix>.fam |
With --ffile:
<prefix>.fmathas one row per feature and one whitespace-separated column per sample, in.famorder. It has no header and no row names.<prefix>.bimneeds one line per feature; only the line count is used.- Each feature is centred by its mean over the observed values. With the default
--alpha -1(--code-type 2) it is also scaled by its standard deviation, so the GRM does not depend on the units of the features. Negative values are fine. --code-type 1and--alphause the observed variance of feature \(i\) as \(v_i\) in place of \(2p_i q_i\). Multiplying one feature by a constant \(c\) multiplies its contribution to the GRM by \(c^{2(1+\alpha)}\), so only \(\alpha = -1\) is independent of the units. Use other values only when all features are on a common scale (for example log-expression from one assay). \(\alpha\) then sets how strongly features with a large variance dominate the GRM; unlike for SNPs, it has no population-genetic interpretation. With complete data the mean diagonal is exactly 1 for every \(\alpha\).- Missing values are written as any of the
--missing-genotokens (defaultNA NaN nan N/A ...). --mafdoes not apply, and only--grm-type addis supported.
Selecting individuals and SNPs #
| Option | File or value | Effect |
|---|---|---|
--keep <file> |
one individual per line: IID, or FID IID |
use only these individuals |
--remove <file> |
same format | leave these individuals out |
--extract <file> |
SNP IDs in the first column | use only these SNPs |
--exclude <file> |
SNP IDs in the first column | leave these SNPs out |
--chr <list> |
chromosomes, e.g. 1-22, 1,3,5 or 2 7 19 |
use only these chromosomes |
--not-chr <list> |
same format | leave these chromosomes out |
A line with one column is matched to the IID; a line with two or more columns is matched to FID and IID, as in PLINK. A leading chr in chromosome names is ignored. The options can be combined: individuals are kept and then removed, and SNPs are extracted, excluded and then restricted by chromosome. The output keeps the .fam order of the individuals. Allele frequencies and missing rates are computed in the selected individuals, so the result is identical to running on a file subset with plink --keep-allele-order --make-bed. The SNP options require --bfile.
A leave-one-chromosome-out (LOCO) GRM for chromosome c is built with --not-chr c, and the GRM of one chromosome with --chr c.
Allele frequencies #
By default p is estimated from the selected individuals. --read-freq <file> uses given A1 frequencies instead, for example from a reference population, so that GRMs built on different subsets of individuals are on the same scale. The file needs a header line with the columns SNP, A1 and FREQ (other columns are ignored), which is the format written by --write-freq:
CHR SNP A1 A2 FREQ NOBS
0 rs1155740 T C 0.27383863 409
FREQ is the frequency of the allele in the A1 column. If that allele is the .bim A2, the frequency is converted to 1 − FREQ. SNPs that are absent from the file, or whose A1 matches neither .bim allele, are excluded and counted in the log. The frequencies are used for centring, scaling and the --maf filter; the missing-rate filter still uses the analysed individuals. With the same frequencies, the GRM is identical to GCTA --update-freq.
--write-freq writes <out>.freq with one line per selected SNP: the A1 frequency used (estimated or read) and NOBS, the number of individuals with an observed genotype. With --npart, only part 1 writes the file. Both options require --bfile.
Quality control and missing genotypes #
A SNP (or feature) is excluded when any of the following holds:
- its minor allele frequency \(\min(p, 1 - p)\) is below
--maf(BED input only) - its missing rate is above
--missing-rate - it has zero variance
Missing genotypes are handled as in GCTA. A missing call contributes nothing to the cross-product, and each pair of samples is scaled by N_jk, the number of SNPs observed in both samples. These counts are written to .grm.N.bin.
Note. Because each element has its own denominator, a GRM built from data with missing genotypes is not guaranteed to be positive semi-definite (GCTA has the same property). With complete data it always is. If you need to invert such a GRM, use a larger
--valwith--process-grm --make-inv.
Options #
| Option | Default | Meaning |
|---|---|---|
--bfile / --ffile |
— | Input prefix (exactly one is required) |
--out |
— | Output prefix (required) |
--grm-type |
add |
add or dom |
--code-type |
2 |
2: per-SNP standardization; 1: VanRaden-type scaling |
--alpha |
-1 |
Additive GRM with SNP-effect variance proportional to \((2pq)^\alpha\); excludes --code-type |
--maf |
0.01 |
Minimum minor allele frequency (BED input) |
--missing-rate |
0.05 |
Maximum missing rate per SNP/feature |
--keep / --remove |
— | Individuals to use / leave out (see above) |
--extract / --exclude |
— | SNPs to use / leave out (BED input) |
--chr / --not-chr |
— | Chromosomes to use / leave out (BED input) |
--read-freq |
— | A1 frequencies to use instead of estimating them (BED input) |
--write-freq |
off | Write <out>.freq (BED input) |
--val |
0 |
Value added to the diagonal of the output |
--npart <n> <i> |
1 1 |
Compute only part i of n of the lower triangle (see below) |
--npart-snp |
20 |
Number of chunks the SNPs are read in (exactly this many, unlike the association modules); raise it to lower memory use |
--missing-geno |
NA NaN nan N/A ... |
Tokens treated as missing in --ffile input |
--threads |
10 |
Number of threads |
The full genotype matrix is never held in memory: the SNPs are read in --npart-snp chunks. Memory is dominated by the GRM block (about \(8n^2\) bytes for \(n\) individuals) plus one chunk (about \(8nm/S\) bytes for \(m\) SNPs and \(S\) = --npart-snp chunks), so raise --npart-snp when \(m\) is large. For very large samples, split the lower triangle into row blocks with --npart and merge them afterwards:
for i in 1 2 3 4; do
fastgma --make-grm --bfile test --npart 4 $i --out output/test
done
fastgma --process-grm --merge --grm output/test --npart 4
Each job writes output/test.4_<i>.grm.* and its own log output/test.4_<i>.log. The merge step concatenates the .grm.id, .grm.bin and .grm.N.bin blocks into output/test.grm.* and copies .grm.N from the first block. The result is identical to a single full run.
2. Output files #
| File | Content |
|---|---|
<out>.grm.bin |
Lower triangle including the diagonal, row by row (\(A_{11}, A_{21}, A_{22}, A_{31}, \ldots\)), little-endian float32 |
<out>.grm.id |
One line per sample: FID<TAB>IID, in matrix row order |
<out>.grm.N.bin |
Per-pair SNP counts N_jk in the same layout as .grm.bin (float32). GCTA needs this file for --mgrm merging, --grm-cutoff, --make-bK-sparse and --adj-grm |
<out>.grm.N |
Text file: number of SNPs used and the scale factor |
<out>.freq |
With --write-freq: CHR SNP A1 A2 FREQ NOBS for every selected SNP |
These are exactly the files GCTA writes (plus the text .grm.N).
Every module that reads a GRM accepts both value widths of .grm.bin (float32 as written now, or float64 from older fastGMA versions; the width is detected from the file size) and a .grm.id with one column (IID) or two columns (FID, IID). Samples are matched by IID (the second column, or the only column).
Each run also writes a log. It is appended to <out>.log, so several steps that share one --out prefix collect in one file. A partitioned run (--npart n i) logs to <out>.<n>_<i>.log. Set the environment variable FASTGMA_LOG_LEVEL=debug for more detail.
Reading a GRM in Python:
import numpy as np
ids = [line.split()[-1] for line in open("output/test.grm.id") if line.strip()]
n = len(ids)
tri = np.fromfile("output/test.grm.bin", dtype=np.float32)
assert tri.size == n * (n + 1) // 2
A = np.zeros((n, n))
A[np.tril_indices(n)] = tri
A = A + A.T - np.diag(np.diag(A))
3. Processing an existing GRM (--process-grm) #
Pass --process-grm together with exactly one operation:
| Operation | What it does | Main output |
|---|---|---|
--merge |
Concatenate the --npart blocks of a partitioned run |
<grm>.grm.bin/.id/.N.bin/.N |
--pca |
Leading eigenvectors of the GRM | <out>.eigenval, <out>.eigenvec |
--make-epis |
Elementwise product of two GRMs (e.g. additive × additive) | <out>.grm.bin/.id |
--reformat |
Convert to another storage format (--out-fmt) |
see the format table below |
--make-inv |
Inverse of the GRM after adding --val to the diagonal (LAPACK Cholesky) |
inverse in the --out-fmt format, plus <out>.grm.id |
--out-close |
Greedily select individuals to drop until no pair exceeds --cut-value |
<out>.grm.pruned_iid |
--group |
Split samples into connected groups of relatives (pairs above --cut-value) |
<grm>.grm.group, <grm>.grm.group.size, <grm>.grm.sp.bin |
| Option | Default | Meaning |
|---|---|---|
--grm |
— | Input GRM prefix |
--out |
— | Output prefix |
--npart |
— | Number of partitions (--merge); must be greater than 1 |
--prefix1, --prefix2 |
— | The two input GRMs (--make-epis); their .grm.id must have the same order |
--out-fmt |
2 |
Output format for --reformat / --make-inv (table below) |
--val |
0.001 |
Diagonal value added before --make-inv (this default is separate from the --make-grm default of 0) |
--num-pc |
20 |
Number of leading principal components written by --pca; 0 writes all |
--cut-value |
0.05 |
Relatedness threshold for --out-close / --group |
--sparse |
off | The input is a sparse .grm.sp.bin (--reformat, --pca, --out-close; not --make-epis, --make-inv or --group) |
--threads |
10 |
Number of threads |
Output formats for --out-fmt:
--out-fmt |
File | Layout |
|---|---|---|
0 |
<out>.grm.matrix |
Full \(n \times n\) text matrix |
1 |
<out>.grm.index_triplet |
row col value per line, lower triangle, 1-based indices |
2 |
<out>.grm.iid_triplet |
IID_row IID_col value per line, lower triangle |
3 |
<out>.grm.bin (dense) or <out>.grm.sp.bin (sparse) |
Dense: GCTA float32 lower triangle. Sparse: records of int64 row, int64 col, float64 value, 0-based |
Notes:
--merge. The blocks are read from<grm>.<n>_<i>.grm.*fori = 1..nand the merged GRM is written to<grm>.grm.*..grm.N.binis merged when the blocks have it.--outonly names the log file (without it, the log goes to the console only).--pca. The files follow the GCTA layout.<out>.eigenvalhas one eigenvalue per line, largest first.<out>.eigenvechas one line per sample in.grm.idorder with the columnsFID IID PC1 PC2 ...(tab-separated, no header line). With a one-column.grm.id, the IID is written in both ID columns.--num-pc 20(default) computes only the 20 leading eigenpairs, which is faster than a full decomposition;--num-pc 0writes all of them.--make-epis. The two GRMs must have the same.grm.idorder. The output is float32<out>.grm.binplus<out>.grm.id;.grm.Nis the concatenation of the two input.grm.Nfiles when both exist. No.grm.N.binis written.--group. The output goes next to the input GRM, not to--out. Besides the group tables, it writes a sparse GRM (.grm.sp.bin) that keeps only within-group pairs.--make-inv. The inverse is computed with a LAPACK Cholesky factorization (dpotrf,dpotri). A GRM centred by sample allele frequencies has rank at most \(n - 1\), so it cannot be inverted without a diagonal value. That is why--valdefaults to0.001here, while--make-grmitself adds nothing. With missing genotypes the GRM can have slightly negative eigenvalues (Section 1). If the matrix is not positive definite,--make-invstops, reports the smallest eigenvalue and asks to rerun with--valabove a given value. With--out-fmt 3the inverse is stored as float32, like every.grm.bin.
4. Compatibility with GCTA #
| fastGMA | GCTA |
|---|---|
--make-grm |
--make-grm |
--make-grm --code-type 1 |
--make-grm-alg 1 |
--make-grm --grm-type dom |
--make-grm-d |
--keep, --remove, --extract, --exclude, --chr |
same options |
--read-freq |
--update-freq |
--process-grm --merge |
concatenating --make-grm-part outputs |
--process-grm --pca |
--pca (same .eigenval / .eigenvec layout) |
With the same SNPs, the values agree with GCTA to float32 precision (largest difference about 1e-7), with or without missing genotypes.
- GCTA GRMs in fastGMA. Pass the GCTA prefix directly, for example
--grm gcta_out. - fastGMA GRMs in GCTA.
--reml,--mlma,--pca,--grm-cutoff,--make-bK-sparseand--mgrmall read the files directly. - Files from older fastGMA versions (float64
.grm.bin, one-column.grm.id) are still read by every module (see Section 2).
Differences to keep in mind when comparing the two tools:
| Item | fastGMA | GCTA |
|---|---|---|
Default --maf |
0.01 |
0 (only monomorphic SNPs removed) |
| Default missing-rate filter | 0.05 |
none |
| Chromosomes used | all SNPs in the input | autosomes only by default |
| Partition file names | <out>.<n>_<i>.grm.* |
<out>.part_<n>_<i>.grm.* |
| X chromosome GRM | not supported | --make-grm-xchr |
Feature-matrix input (--ffile) |
supported | not available |
To reproduce a GCTA GRM exactly, use --maf 0 (or the same --maf in both tools), --missing-rate 1, and only autosomal SNPs (--chr 1-22 for human data).
LDAK. With the same SNPs and complete genotypes, --alpha \(\alpha\) gives the kinship matrix of LDAK --calc-kins-direct --ignore-weights YES --power \(\alpha\) up to a constant factor: LDAK divides by the observed mean diagonal, so its matrix has mean diagonal exactly 1, whereas fastGMA (like GCTA) divides by the expected value \(\sum_i v_i^{1+\alpha}\). After dividing the fastGMA matrix by its mean diagonal, the two agree to float32 precision. With missing genotypes they also differ in the treatment of missing calls (LDAK uses one denominator for all pairs instead of N_jk).
5. Examples #
Additive GRM for uvLMM #
fastgma --make-grm --bfile test --out output/test
fastgma --uvlmm --grm output/test --bfile test \
--data test_simu_pheno.txt --trait pheno --out output/test_uvlmm
Leave-one-chromosome-out GRMs #
for c in $(seq 1 22); do
fastgma --make-grm --bfile test --not-chr $c --out output/test_loco$c
done
output/test_loco<c> is used when testing the SNPs of chromosome c.
Choosing α #
Build a GRM for each value on a grid and compare the REML log-likelihoods of the null model. Without --bfile, --uvlmm fits only the null model:
for a in -1 -0.75 -0.5 -0.25 0; do
fastgma --make-grm --bfile test --alpha $a --out output/alpha_$a
fastgma --uvlmm --grm output/alpha_$a --data test_simu_pheno.txt --trait pheno --out output/alpha_$a
done
grep "logL (REML" output/alpha_*.log
All models have the same fixed effects and the same number of variance components, so the log-likelihoods can be compared directly; the highest one gives the best-supported \(\alpha\). For the example data:
| \(\alpha\) | G |
residual |
Heritability | REML logL |
|---|---|---|---|---|
| \(-1\) | 0.2204 | 0.6551 | 0.2533 | −187.9204 |
| \(-0.75\) | 0.2221 | 0.6534 | 0.2553 | −187.9097 |
| \(-0.5\) | 0.2237 | 0.6518 | 0.2571 | −187.8994 |
| \(-0.25\) | 0.2251 | 0.6504 | 0.2587 | −187.8895 |
| 0 | 0.2263 | 0.6491 | 0.2601 | −187.8802 |
With 427 individuals the log-likelihood changes by only 0.04 over the grid, so these data cannot distinguish the values of \(\alpha\); estimating \(\alpha\) needs large samples.
Additive, dominance and epistatic GRMs for a multi-GRM model #
fastgma --make-grm --grm-type add --bfile test --out output/add
fastgma --make-grm --grm-type dom --bfile test --out output/dom
fastgma --process-grm --make-epis --prefix1 output/add --prefix2 output/add --out output/axa
fastgma --uvlmm --model multi-grm --mgrm output/add output/dom output/axa \
--bfile test --data test_simu_pheno.txt --trait pheno --out output/test_mgrm
GRM from gene expression #
# expr.fmat: genes x samples; expr.fam: samples; expr.bim: one line per gene
fastgma --make-grm --ffile expr --missing-rate 0.2 --out output/expr
PCA, related-sample pruning and inverse #
fastgma --process-grm --pca --grm output/test --out output/test # top 20 PCs
fastgma --process-grm --pca --num-pc 0 --grm output/test --out output/test # all PCs
fastgma --process-grm --out-close --grm output/test --cut-value 0.125 --out output/test
fastgma --process-grm --make-inv --grm output/test --out-fmt 3 --out output/test.inv
If the genotypes contain missing calls, --make-inv may stop with “not positive definite”. The message gives the smallest eigenvalue and the --val needed; rerun with a larger value, for example --val 0.02.
Use the GRM in GCTA #
gcta64 --grm output/test --pheno test.phen --reml --out output/test_gcta
gcta64 --grm output/test --make-bK-sparse 0.05 --out output/test_sp