Mapping using Tabix and VCF files

Mapping using Tabix and VCF files#

We have seen how Ensembl can be used to localise variants in the genome. This works reasonably well for low throughput applications, however, it is not scalable. If you want to validate thousands or millions of variants then you will need to use locally stored files. This example demonstrates how to use a tabix indexed VCF file to map variants. The mapping file used in this example is a very small test one used in the package.

Having said that VCF files are locally stored, this does not have to be the case as all the files are read with pysam, which is a Python interface to the htslib library that allows remove access to VCF files. However, I do not think that Ensembl or the NCBI would thank you for hitting their FTP servers with long running variant mapping tasks.

If you have not done so already, It is recommended that you work your way through the Ensembl mapping tutorial first, as this contains some more background than this one.

[11]:
import csv
import gzip
import pandas as pd
from variant_mapper import mapper, examples, constants as con

We will download the test mapper and some test variants to map against it.

[2]:
outdir = "/data/test-mapper"
[3]:
files = examples.download_test_mapper(outdir)

The files are listed below. There is a primary and secondary mapper, in this example we will just use one of them. When we come to the scan mapper example in the next workbook, we will use both. There is also a reference genome sequence small-mepper-genome.faa.gz. In addition, there are indexes for all of these. Finally, there are some test variants to run the mapping on.

[4]:
for i in files:
    print(i)
/data/test-mapper/small-mapper-genome.faa.gz
/data/test-mapper/small-mapper-genome.faa.gz.fai
/data/test-mapper/small-mapper-genome.faa.gz.gzi
/data/test-mapper/small-mapper-primary-mapper.vcf.gz
/data/test-mapper/small-mapper-primary-mapper.vcf.gz.tbi
/data/test-mapper/small-mapper-secondary-mapper.vcf.gz
/data/test-mapper/small-mapper-secondary-mapper.vcf.gz.tbi
/data/test-mapper/test-variants.txt.gz
[5]:
with gzip.open('/data/test-mapper/test-variants.txt.gz', 'rt') as infile:
    reader = csv.DictReader(infile, delimiter="\t")
    rows = [i for i in reader]
pd.DataFrame(rows)
[5]:
chr pos rsid ref alt notes
0 1 259 . T G all-match
1 1 3368 . C T ref-flip
2 1 5957 . G GGGTTGTCGC multi-site
3 1 6500 . A T missing
4 1 8366 . T A all-match
5 2 1613 . TCCTG T all-match
6 2 1807 . CTG C all-match
7 2 3136 . CCCATTC C all-match
8 2 8190 rs1941537900 T C all-match
9 2 8647 . G A missing
10 3 2984 rs1838945919 TTACGGACT T all-match
11 3 3621 . G T all-match
12 3 3646 . A G all-match
13 3 8244 . T G all-match
14 4 217 rs1146836854 A T all-match
15 4 3402 rs1684565759 C A all-match

Now we will use the primary mapper to run these, using Tabix as the method to localise the variant sites to the genome.

[12]:
mapper_file = "/data/test-mapper/small-mapper-primary-mapper.vcf.gz"

# Pass the mapping VCF file to the tabix mapper
with mapper.TabixVcfVariantMapper(mapper_file) as vmap:
    for i in rows:
        chr_name, start_pos, ref_allele, alt_allele = i['chr'], int(i['pos']), i['ref'], i['alt']

        # Map the variant
        mapped = vmap.map_variant(chr_name, start_pos, ref_allele, alt_allele=alt_allele)

        # Extract the mapped data
        i['map_bits'] = mapped.map_bits
        i['nsites'] = mapped.nsites
        try:
            # Get the mapping alleles
            i['mapped_ref'] = mapped.mapping_coords.ref_allele
            i['mapped_alt'] = mapped.mapping_coords.alt_allele
        except AttributeError:
            # When the variants can't be mapped, mapped.mapping_coords will be None
            i['mapped_ref'] = None
            i['mapped_alt'] = None
        # Decode the mapping bits into a human readable list
        i['decoded_map_bits'] = con.decode_mapping_flags(mapped.map_bits)

The results can be seen below, we have added the following columns:

  • map_bits - The mapping bits, the fields that have aligned with the site in the mapping file.

  • nsites - The number of other variant sites that overlap the mapping site.

  • mapped_ref - The reference allele that has been mapped.

  • mapped_alt - The alternate allele that has been mapped

  • decoded_map_bits - A human readable form of the mapping bits.

We can see that rows 3 and 9 have not mapped as all, as they are not present in the mapping file.

[13]:
pd.DataFrame(rows)
[13]:
chr pos rsid ref alt notes map_bits nsites mapped_ref mapped_alt decoded_map_bits
0 1 259 . T G all-match 28160 1 T G [CHR, START, STRAND, REF, ALT]
1 1 3368 . C T ref-flip 28224 1 T C [CHR, START, STRAND, REF, ALT, REF_FLIP]
2 1 5957 . G GGGTTGTCGC multi-site 28160 3 G GGGTTGTCGC [CHR, START, STRAND, REF, ALT]
3 1 6500 . A T missing 0 0 None None [NO_DATA]
4 1 8366 . T A all-match 28160 1 T A [CHR, START, STRAND, REF, ALT]
5 2 1613 . TCCTG T all-match 28160 2 TCCTG T [CHR, START, STRAND, REF, ALT]
6 2 1807 . CTG C all-match 28160 1 CTG C [CHR, START, STRAND, REF, ALT]
7 2 3136 . CCCATTC C all-match 28160 1 CCCATTC C [CHR, START, STRAND, REF, ALT]
8 2 8190 rs1941537900 T C all-match 28160 1 T C [CHR, START, STRAND, REF, ALT]
9 2 8647 . G A missing 0 0 None None [NO_DATA]
10 3 2984 rs1838945919 TTACGGACT T all-match 28160 1 TTACGGACT T [CHR, START, STRAND, REF, ALT]
11 3 3621 . G T all-match 28160 1 G T [CHR, START, STRAND, REF, ALT]
12 3 3646 . A G all-match 28160 2 A G [CHR, START, STRAND, REF, ALT]
13 3 8244 . T G all-match 28160 1 T G [CHR, START, STRAND, REF, ALT]
14 4 217 rs1146836854 A T all-match 28160 1 A T [CHR, START, STRAND, REF, ALT]
15 4 3402 rs1684565759 C A all-match 28160 2 C A [CHR, START, STRAND, REF, ALT]

Now we will perform the same operation but passing the reference genome assembly for the mapping file. This time, if a variant does not map then it is checked for a reference allele match in the reference genome assembly.

[21]:
# Pass the mapping VCF file to the tabix mapper
with mapper.TabixVcfVariantMapper(mapper_file, ref_genome="/data/test-mapper/small-mapper-genome.faa.gz") as vmap:
    for i in rows:
        chr_name, start_pos, ref_allele, alt_allele = i['chr'], int(i['pos']), i['ref'], i['alt']

        # Map the variant
        mapped = vmap.map_variant(chr_name, start_pos, ref_allele, alt_allele=alt_allele)

        # Extract the mapped data
        i['map_bits'] = mapped.map_bits
        i['nsites'] = mapped.nsites
        try:
            # Get the mapping alleles
            i['mapped_ref'] = mapped.mapping_coords.ref_allele
            i['mapped_alt'] = mapped.mapping_coords.alt_allele
        except AttributeError:
            # When the variants can't be mapped, mapped.mapping_coords will be None
            i['mapped_ref'] = None
            i['mapped_alt'] = None
        # Decode the mapping bits into a human readable list
        i['decoded_map_bits'] = con.decode_mapping_flags(mapped.map_bits)
        # print(mapped)

Here we can see that the mapping bits for the two rows that do not match are errors, as they do not even map to the reference assembly.

[15]:
pd.DataFrame(rows)
[15]:
chr pos rsid ref alt notes map_bits nsites mapped_ref mapped_alt decoded_map_bits
0 1 259 . T G all-match 28160 1 T G [CHR, START, STRAND, REF, ALT]
1 1 3368 . C T ref-flip 28224 1 T C [CHR, START, STRAND, REF, ALT, REF_FLIP]
2 1 5957 . G GGGTTGTCGC multi-site 28160 3 G GGGTTGTCGC [CHR, START, STRAND, REF, ALT]
3 1 6500 . A T missing 1 0 None None [ERROR]
4 1 8366 . T A all-match 28160 1 T A [CHR, START, STRAND, REF, ALT]
5 2 1613 . TCCTG T all-match 28160 2 TCCTG T [CHR, START, STRAND, REF, ALT]
6 2 1807 . CTG C all-match 28160 1 CTG C [CHR, START, STRAND, REF, ALT]
7 2 3136 . CCCATTC C all-match 28160 1 CCCATTC C [CHR, START, STRAND, REF, ALT]
8 2 8190 rs1941537900 T C all-match 28160 1 T C [CHR, START, STRAND, REF, ALT]
9 2 8647 . G A missing 1 0 None None [ERROR]
10 3 2984 rs1838945919 TTACGGACT T all-match 28160 1 TTACGGACT T [CHR, START, STRAND, REF, ALT]
11 3 3621 . G T all-match 28160 1 G T [CHR, START, STRAND, REF, ALT]
12 3 3646 . A G all-match 28160 2 A G [CHR, START, STRAND, REF, ALT]
13 3 8244 . T G all-match 28160 1 T G [CHR, START, STRAND, REF, ALT]
14 4 217 rs1146836854 A T all-match 28160 1 A T [CHR, START, STRAND, REF, ALT]
15 4 3402 rs1684565759 C A all-match 28160 2 C A [CHR, START, STRAND, REF, ALT]

Finally, lets try to map a variant that is not in the mappping file but is in the reference assembly to see what happens.

[19]:
# Pass the mapping VCF file to the tabix mapper
with mapper.TabixVcfVariantMapper(mapper_file, ref_genome="/data/test-mapper/small-mapper-genome.faa.gz") as vmap:
    # Map the variant
    mapped = vmap.map_variant("1", 259, "T", alt_allele="A")
    print(mapped)
MappingResult(source_coords=MapCoord(chr_name='1', start_pos=259, strand=1, ref_allele='T', alt_allele='A'), mapping_coords=MapCoord(chr_name='1', start_pos=259, strand=1, ref_allele='T', alt_allele='A'), map_bits=32768, source_row=None, map_row=None, errors=None, nsites=1, resolver=None)

We can see that while there is no mapping variant the mapping bits now indicate that the reference assembly does cantain the reference allele.

[20]:
con.decode_mapping_flags(mapped.map_bits)
[20]:
['REF_GENOME_MATCH']

Summary#

The above is the very basic usage of the tabix mapper. we have not covered using resolvers to extract data from the mapping file. will eventually update this notebook to give some examples.