Skip to content

ColocPIP eCAVIAR

gentropy.method.colocalisation.ColocPIPECaviar

Bases: ColocalisationMethodInterface

Calculate bayesian colocalisation based on overlapping signals from credible sets using both PIPs and eCAVIAR CLPP.

Source code in src/gentropy/method/colocalisation/coloc_pip_ecaviar.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
class ColocPIPECaviar(ColocalisationMethodInterface):
    """Calculate bayesian colocalisation based on overlapping signals from credible sets using both PIPs and eCAVIAR CLPP."""

    METHOD_NAME: str = "COLOC_PIP_ECAVIAR"
    METHOD_METRICS: list[str] = ["h4", "h3", "clpp"]

    @classmethod
    def colocalise(
        cls: type[ColocPIPECaviar],
        overlapping_signals: StudyLocusOverlap,
        **kwargs: Any,
    ) -> Colocalisation:
        """Colocalise using colocPIP and eCAVIAR metrics in a single fused pass.

        ColocPIP, eCAVIAR and the beta ratio all group the overlaps by the same keys, so
        every metric is computed in ONE groupBy -- replacing the previous two separate
        groupBys + inner merge join + a separate (multi-TiB) beta-ratio shuffle, which
        re-shuffled the overlaps several times.

        Args:
            overlapping_signals (StudyLocusOverlap): overlapping peaks
            **kwargs (Any): Additional parameters passed to the colocalise method.

        Keyword Args:
            priorc1 (float): Prior on variant being causal for trait 1. Defaults to 1e-4.
            priorc2 (float): Prior on variant being causal for trait 2. Defaults to 1e-4.
            priorc12 (float): Prior on variant being causal for traits 1 and 2. Defaults to 1e-5.

        Returns:
            Colocalisation: Colocalisation results
        """
        config = _ColocPIPConfig(**kwargs)
        # h3/h4 reuse ColocPIP's exact sums -> posteriors derivation (shared helper) so
        # the fused path cannot numerically diverge from the standalone ColocPIP method.
        h3, h4 = ColocPIP._pip_posteriors(
            f.col("sum_pip1"),
            f.col("sum_pip2"),
            f.col("sum_pip_prod"),
            config.priorc1,
            config.priorc2,
            config.priorc12,
        )

        # One groupBy over the (tag-variant-aligned) overlaps computes every metric for
        # both methods at once: ColocPIP needs S1/S2/B (floored PIP sums); eCAVIAR needs
        # clpp = sum(left_pp * right_pp) (raw); numberColocalisingVariants is identical in
        # both. h0=h1=h2 are not produced for the combined method (matching the original).
        aggregated = (
            overlapping_signals.df.withColumn(
                "tagVariantSource", cls.get_tag_variant_source(f.col("statistics"))
            )
            .select("*", "statistics.*")
            .groupBy(
                "chromosome",
                "leftStudyLocusId",
                "rightStudyLocusId",
                "rightStudyType",
            )
            .agg(
                f.sum(f.when(f.col("tagVariantSource") == "both", 1).otherwise(0))
                .cast(t.LongType())
                .alias("numberColocalisingVariants"),
                f.sum(ColocPIP._floored_pip(f.col("left_posteriorProbability"))).alias(
                    "sum_pip1"
                ),
                f.sum(ColocPIP._floored_pip(f.col("right_posteriorProbability"))).alias(
                    "sum_pip2"
                ),
                f.sum(
                    ColocPIP._floored_pip(f.col("left_posteriorProbability"))
                    * ColocPIP._floored_pip(f.col("right_posteriorProbability"))
                ).alias("sum_pip_prod"),
                f.sum(
                    ECaviar._get_clpp(
                        f.col("left_posteriorProbability"),
                        f.col("right_posteriorProbability"),
                    )
                ).alias("clpp"),
                # Beta ratio folded into the same groupBy: rightStudyType is functionally
                # determined by the right locus, so this 4-key grouping matches
                # calculate_beta_ratio's (left, right, chromosome) granularity. avg ignores
                # the nulls produced for filtered-out (null/zero beta) rows, so this equals
                # the original filter-then-avg -- and removes a full ~TiB re-shuffle.
                f.avg(
                    f.when(
                        f.col("left_beta").isNotNull()
                        & f.col("right_beta").isNotNull()
                        & (f.col("left_beta") != 0)
                        & (f.col("right_beta") != 0),
                        f.signum(f.col("left_beta") / f.col("right_beta")),
                    )
                ).alias("betaRatioSignAverage"),
            )
            .withColumn("h3", h3)
            .withColumn("h4", h4)
            .withColumn("colocalisationMethod", f.lit(cls.METHOD_NAME))
        )

        return Colocalisation(
            _df=aggregated.select(
                "leftStudyLocusId",
                "rightStudyLocusId",
                "rightStudyType",
                "chromosome",
                "colocalisationMethod",
                "numberColocalisingVariants",
                "h3",
                "h4",
                "clpp",
                "betaRatioSignAverage",
            ),
            _schema=Colocalisation.get_schema(),
        )

colocalise(overlapping_signals: StudyLocusOverlap, **kwargs: Any) -> Colocalisation classmethod

Colocalise using colocPIP and eCAVIAR metrics in a single fused pass.

ColocPIP, eCAVIAR and the beta ratio all group the overlaps by the same keys, so every metric is computed in ONE groupBy -- replacing the previous two separate groupBys + inner merge join + a separate (multi-TiB) beta-ratio shuffle, which re-shuffled the overlaps several times.

Parameters:

Name Type Description Default
overlapping_signals StudyLocusOverlap

overlapping peaks

required
**kwargs Any

Additional parameters passed to the colocalise method.

{}

Other Parameters:

Name Type Description
priorc1 float

Prior on variant being causal for trait 1. Defaults to 1e-4.

priorc2 float

Prior on variant being causal for trait 2. Defaults to 1e-4.

priorc12 float

Prior on variant being causal for traits 1 and 2. Defaults to 1e-5.

Returns:

Name Type Description
Colocalisation Colocalisation

Colocalisation results

Source code in src/gentropy/method/colocalisation/coloc_pip_ecaviar.py
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
@classmethod
def colocalise(
    cls: type[ColocPIPECaviar],
    overlapping_signals: StudyLocusOverlap,
    **kwargs: Any,
) -> Colocalisation:
    """Colocalise using colocPIP and eCAVIAR metrics in a single fused pass.

    ColocPIP, eCAVIAR and the beta ratio all group the overlaps by the same keys, so
    every metric is computed in ONE groupBy -- replacing the previous two separate
    groupBys + inner merge join + a separate (multi-TiB) beta-ratio shuffle, which
    re-shuffled the overlaps several times.

    Args:
        overlapping_signals (StudyLocusOverlap): overlapping peaks
        **kwargs (Any): Additional parameters passed to the colocalise method.

    Keyword Args:
        priorc1 (float): Prior on variant being causal for trait 1. Defaults to 1e-4.
        priorc2 (float): Prior on variant being causal for trait 2. Defaults to 1e-4.
        priorc12 (float): Prior on variant being causal for traits 1 and 2. Defaults to 1e-5.

    Returns:
        Colocalisation: Colocalisation results
    """
    config = _ColocPIPConfig(**kwargs)
    # h3/h4 reuse ColocPIP's exact sums -> posteriors derivation (shared helper) so
    # the fused path cannot numerically diverge from the standalone ColocPIP method.
    h3, h4 = ColocPIP._pip_posteriors(
        f.col("sum_pip1"),
        f.col("sum_pip2"),
        f.col("sum_pip_prod"),
        config.priorc1,
        config.priorc2,
        config.priorc12,
    )

    # One groupBy over the (tag-variant-aligned) overlaps computes every metric for
    # both methods at once: ColocPIP needs S1/S2/B (floored PIP sums); eCAVIAR needs
    # clpp = sum(left_pp * right_pp) (raw); numberColocalisingVariants is identical in
    # both. h0=h1=h2 are not produced for the combined method (matching the original).
    aggregated = (
        overlapping_signals.df.withColumn(
            "tagVariantSource", cls.get_tag_variant_source(f.col("statistics"))
        )
        .select("*", "statistics.*")
        .groupBy(
            "chromosome",
            "leftStudyLocusId",
            "rightStudyLocusId",
            "rightStudyType",
        )
        .agg(
            f.sum(f.when(f.col("tagVariantSource") == "both", 1).otherwise(0))
            .cast(t.LongType())
            .alias("numberColocalisingVariants"),
            f.sum(ColocPIP._floored_pip(f.col("left_posteriorProbability"))).alias(
                "sum_pip1"
            ),
            f.sum(ColocPIP._floored_pip(f.col("right_posteriorProbability"))).alias(
                "sum_pip2"
            ),
            f.sum(
                ColocPIP._floored_pip(f.col("left_posteriorProbability"))
                * ColocPIP._floored_pip(f.col("right_posteriorProbability"))
            ).alias("sum_pip_prod"),
            f.sum(
                ECaviar._get_clpp(
                    f.col("left_posteriorProbability"),
                    f.col("right_posteriorProbability"),
                )
            ).alias("clpp"),
            # Beta ratio folded into the same groupBy: rightStudyType is functionally
            # determined by the right locus, so this 4-key grouping matches
            # calculate_beta_ratio's (left, right, chromosome) granularity. avg ignores
            # the nulls produced for filtered-out (null/zero beta) rows, so this equals
            # the original filter-then-avg -- and removes a full ~TiB re-shuffle.
            f.avg(
                f.when(
                    f.col("left_beta").isNotNull()
                    & f.col("right_beta").isNotNull()
                    & (f.col("left_beta") != 0)
                    & (f.col("right_beta") != 0),
                    f.signum(f.col("left_beta") / f.col("right_beta")),
                )
            ).alias("betaRatioSignAverage"),
        )
        .withColumn("h3", h3)
        .withColumn("h4", h4)
        .withColumn("colocalisationMethod", f.lit(cls.METHOD_NAME))
    )

    return Colocalisation(
        _df=aggregated.select(
            "leftStudyLocusId",
            "rightStudyLocusId",
            "rightStudyType",
            "chromosome",
            "colocalisationMethod",
            "numberColocalisingVariants",
            "h3",
            "h4",
            "clpp",
            "betaRatioSignAverage",
        ),
        _schema=Colocalisation.get_schema(),
    )