banner

Workshop 6 - Training course in data analysis for genomic surveillance of African malaria vectors


Module 1 - Haplotypes#

Theme: Data

In previous workshops we have analysed data on individual genetic variations, such as single nucleotide polymorphisms. This module introduces a new type of genetic variation data called “haplotypes”. The benefit of haplotype data is that it conveys the combinations of mutations that occur together on the same segment of a DNA sequence. This in turn allows us to study how those DNA sequences are inherited and transmitted between generations. Many novel and powerful methods of analysis are possible with haplotype data, including analyses to detect genome regions under strong positive selection, which we will study in the subsequent modules of this workshop.

Learning objectives#

At the end of this module you will be able to:

  • Explain what haplotypes are.

  • Explain why haplotypes are useful for vector genomic surveillance.

  • Explain how haplotypes are inferred from genotype data (phasing).

  • Access haplotype data from MalariaGEN and explain how the data are stored.

Lecture#

English#

Français#

Please note that the code in the cells below might differ from that shown in the video. This can happen because Python packages and their dependencies change due to updates, necessitating tweaks to the code.

What are haplotypes?#

Please see the lecture videos for an explanation of what haplotypes are, why they are useful, and how we infer haplotypes from next-generation sequence data.

Accessing MalariaGEN haplotype data#

MalariaGEN routinely generates haplotype data from whole-genome sequencing of Anopheles mosquitoes. These data can be accessed as follows.

Setup#

%pip install -q --no-warn-conflicts malariagen_data
Note: you may need to restart the kernel to use updated packages.
import malariagen_data

Note that authentication is required to access data through the package, please follow the instructions here.

ag3 = malariagen_data.Ag3()
ag3
MalariaGEN Ag3 API client
Please note that data are subject to terms of use, for more information see the MalariaGEN website or contact support@malariagen.net. See also the Ag3 API docs.
Storage URL gs://vo_agam_release/
Data releases available 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9
Results cache None
Cohorts analysis 20240418
AIM analysis 20220528
Site filters analysis dt_20200416
Software version malariagen_data 9.0.0
Client location unknown

Haplotype data can be accessed via the haplotypes() function. Let’s look at the function documentation.

ag3.haplotypes?

Let’s demonstrate this function, accessing haplotype data for a small genome region on chromosome arm 2L from position 2,422,600 to 2,422,700. To keep things simple we’ll also only access haplotypes from 20 randomly chosen samples from Cameroon. This function returns an xarray dataset.

ds_haps = ag3.haplotypes(
    region="2L:2,422,600-2,422,700",
    analysis="gamb_colu_arab",
    sample_sets = '3.0',
    sample_query="country == 'Cameroon'",
    cohort_size=20,
)
ds_haps
                                     
<xarray.Dataset> Size: 1kB
Dimensions:           (variants: 26, alleles: 2, samples: 20, ploidy: 2)
Coordinates:
    variant_position  (variants) int32 104B dask.array<chunksize=(26,), meta=np.ndarray>
    variant_contig    (variants) uint8 26B dask.array<chunksize=(26,), meta=np.ndarray>
    sample_id         (samples) object 160B dask.array<chunksize=(11,), meta=np.ndarray>
Dimensions without coordinates: variants, alleles, samples, ploidy
Data variables:
    variant_allele    (variants, alleles) |S1 52B dask.array<chunksize=(26, 1), meta=np.ndarray>
    call_genotype     (variants, samples, ploidy) int8 1kB dask.array<chunksize=(26, 3, 2), meta=np.ndarray>
Attributes:
    contigs:   ('2R', '2L', '3R', '3L', 'X')
    analysis:  gamb_colu_arab

There are 20 samples and thus 40 haplotypes in this dataset. We also have data for 26 variants (SNPs).

n_samples = ds_haps.sizes["samples"]
n_samples
20
n_haps = n_samples * 2
n_haps
40
n_variants = ds_haps.sizes["variants"]
n_variants
26

The “variant_position” array has data on the genomic coordinates of the SNPs.

pos = ds_haps["variant_position"].values
pos
array([2422601, 2422606, 2422609, 2422610, 2422611, 2422613, 2422617,
       2422621, 2422625, 2422629, 2422630, 2422634, 2422643, 2422645,
       2422651, 2422652, 2422666, 2422678, 2422680, 2422683, 2422685,
       2422687, 2422688, 2422691, 2422695, 2422697], dtype=int32)

The “variant_allele” array has the reference and alternate alleles for the SNPs.

alleles = ds_haps["variant_allele"].values
alleles
array([[b'T', b'A'],
       [b'T', b'G'],
       [b'C', b'T'],
       [b'C', b'A'],
       [b'T', b'A'],
       [b'C', b'T'],
       [b'C', b'A'],
       [b'T', b'G'],
       [b'C', b'A'],
       [b'G', b'A'],
       [b'C', b'A'],
       [b'T', b'C'],
       [b'A', b'G'],
       [b'G', b'A'],
       [b'T', b'C'],
       [b'A', b'T'],
       [b'C', b'T'],
       [b'G', b'T'],
       [b'C', b'G'],
       [b'A', b'C'],
       [b'A', b'G'],
       [b'C', b'A'],
       [b'G', b'T'],
       [b'T', b'A'],
       [b'C', b'T'],
       [b'T', b'A']], dtype='|S1')
ref = alleles[:, 0]
ref
array([b'T', b'T', b'C', b'C', b'T', b'C', b'C', b'T', b'C', b'G', b'C',
       b'T', b'A', b'G', b'T', b'A', b'C', b'G', b'C', b'A', b'A', b'C',
       b'G', b'T', b'C', b'T'], dtype='|S1')
alt = alleles[:, 1]
alt
array([b'A', b'G', b'T', b'A', b'A', b'T', b'A', b'G', b'A', b'A', b'A',
       b'C', b'G', b'A', b'C', b'T', b'T', b'T', b'G', b'C', b'G', b'A',
       b'T', b'A', b'T', b'A'], dtype='|S1')

The haplotypes themselves are stored in the “call_genotype” array. The shape of this array is (number of variants, number of samples, number of haplotypes per sample).

gt = ds_haps["call_genotype"].values
gt.shape
(26, 20, 2)

This array uses the numeric encoding for the alleles. We can see that by inspecting its dtype and also inspecting the data values.

gt.dtype
dtype('int8')
gt
array([[[0, 0],
        [0, 0],
        [0, 0],
        ...,
        [0, 0],
        [0, 0],
        [0, 0]],

       [[0, 0],
        [0, 0],
        [0, 0],
        ...,
        [0, 0],
        [0, 0],
        [0, 0]],

       [[0, 0],
        [0, 0],
        [0, 0],
        ...,
        [0, 0],
        [0, 0],
        [0, 0]],

       ...,

       [[0, 0],
        [0, 0],
        [0, 0],
        ...,
        [0, 0],
        [0, 0],
        [0, 0]],

       [[0, 0],
        [0, 0],
        [0, 0],
        ...,
        [0, 0],
        [0, 0],
        [0, 0]],

       [[0, 0],
        [0, 0],
        [0, 0],
        ...,
        [0, 0],
        [0, 0],
        [0, 0]]], dtype=int8)

The basic numpy representation of this array is a bit hard to understand, so to help build our intuition of this data structure let’s make a simple plotting function to visualise haplotypes.

def plot_haplotypes(
    ds,
    hide_text=False,
    hide_non_seg=False,
):
    import plotly.express as px
    import allel

    # convert to 2D array of haplotypes
    pos = ds["variant_position"].values
    gt = ds["call_genotype"].values
    ht = gt.reshape(gt.shape[0], -1)

    # size rows and columns
    if hide_text:
        # can make smaller because text not shown
        row_height = 8
        col_width = 5
    else:
        # make a bit bigger so we can see the text
        row_height = 14
        col_width = 12

    # deal with non-segregating sites
    if hide_non_seg:
        ac = allel.HaplotypeArray(ht).count_alleles(max_allele=1)
        loc_seg = ac.is_segregating()
        ht = ht[loc_seg]

    # plot a heatmap
    fig = px.imshow(
        ht.T,
        text_auto=not hide_text,
        labels=dict(
            x="Variants",
            y="Haplotypes",
        ),
        width=ht.shape[0] * col_width + 200,
        height=ht.shape[1] * row_height + 200,
        zmin=0,
        zmax=0,
        color_continuous_scale="greys",
        template="simple_white",
        aspect="auto"
    )

    # visual styling
    fig.update_layout(
        coloraxis_showscale=False,
        plot_bgcolor="#cccccc",
    )
    fig.update_traces(xgap=0, ygap=1)
    fig.update_xaxes(tickvals=[])
    fig.update_yaxes(tickvals=[])

    return fig

This function will visualise haplotypes as a heatmap, with haplotypes as rows and variants as columns. A reference allele has a white background, and an alternate allele has a black background.

Let’s use the function to visualise the haplotypes we accessed above.

plot_haplotypes(ds_haps)

In this set of haplotype data we can see there are only two variants which are segregating in this set of samples. All the other variants are non-segregating, which means all haplotypes carry the same allele (in this case a “0” meaning the reference allele).

The non-segregating variants are not particularly informative, so let’s hide them.

plot_haplotypes(ds_haps, hide_non_seg=True)

We can see now that within this genome region, and within this set of 20 samples, we have two segregating sites and three unique haplotypes.

Well done!#

Hopefully this module has been a useful introduction to haplotype data. There are no practical exercises for this module, but you might like to launch this notebook for yourself, and try running the code examples above, or changing them to load data from a different genome region or a different set of samples.