statgen / pheweb

A tool to build a website to browse hundreds or thousands of GWAS.
MIT License
158 stars 65 forks source link

Use up/down triangles to show direction of effect in plots #56

Closed pjvandehaar closed 7 years ago

pjvandehaar commented 7 years ago

In LZ, PheWAS, and Manhattan, use upward/downward triangles to show direction of effect.

Frencil commented 7 years ago

In plots rendered with LZ would this be something rendered in tool tips? And if so, would conditional blocks in tool tip markup (#86) be required?

pjvandehaar commented 7 years ago

{{EDIT: it looks like https://github.com/statgen/locuszoom/wiki/Scale-Functions#chaining-scale-functions solves my problem with the format in https://github.com/statgen/locuszoom/wiki/Scale-Functions#scale-functions}}

I want:

if (variant.is_LDrefvar) point_shape = 'diamond'
else if(variant.beta&&variant.beta>0 || variant.or&&variant.or>0) point_shape = 'triangle-up'
else if(variant.beta&&variant.beta<0 || variant.or&&variant.or<0) point_shape = 'triangle-down'
else point_shape = 'circle'

I don't think that should require any changes in LZ. I'll do that in the layout if possible, but otherwise I can make a variable effect_direction: [-1,0,1] in .getData().

Docs for my reference

pjvandehaar commented 7 years ago

Looking through scale function docs, it looks like my options are:

Frencil commented 7 years ago

@pjvandehaar I see how the behavior you're looking for here:

if (variant.is_LDrefvar) point_shape = 'diamond'
else if(variant.beta&&variant.beta>0 || variant.or&&variant.or>0) point_shape = 'triangle-up'
else if(variant.beta&&variant.beta<0 || variant.or&&variant.or<0) point_shape = 'triangle-down'
else point_shape = 'circle'

Isn't quite possible with the current implementation of scale functions in layouts. The issue appears to be those more complex conditionals in the middle that operate on more than one field.

I can also see how recursive scale functions would accommodate that, but in my experience recursion is best regarded as a last resort. It certainly has its uses, but it's probably overly complex for emulating simple conditional logic.

I'm thinking more along the lines of expanding the scale function layout directive to allow for a single field or a list of named fields to operate on. Then you could define a scale function (e.g. effect_direction) that expects both a beta and an OR (gracefully handling undefined values) and returns a direction (-1, 0, or 1). Then the layout directive that defines the point shape would require an array of three values:

  1. if scale function object mapping the LD ref var boolean to the diamond shape (as currently implemented in the standard association layout)
  2. effect_direction function object mapping the beta and OR fields to the triangles or circle
  3. Just the string "circle" in case the above two both fail

This is a pretty minor expansion to how layouts parse scalable value directives and would allow for passing arbitrarily many fields to a scale function, which already has the scaffolding to allow for custom definitions. It looks like this would solve your problem without requiring recursion and without pushing more logic into the data source (which, in principle, should do minimal operations on the data itself). But maybe I'm missing something?

Let me know... I'll be developing some this weekend so can implement this and cut a new release by EOD Sunday.

sgagliano commented 7 years ago

@pjvandehaar I think that the most important plot for the up/down triangles would be the PheWAS plot in order to easily see if the direction of effect is the same for the other traits to which the variant is related.

Frencil commented 7 years ago

@pjvh now as of LocusZoom v0.5.6 you should be able to do this. Here's a quick overview...

First, see this test which is patterned off of this very use case. You should be able to define a scale function to essentially cover just the two more complex conditionals like this:

LocusZoom.ScaleFunctions.add("effect_direction", function(parameters, input){
    if (typeof input == "undefined"){
        return null;
    } else if ((input.beta && input.beta > 0) || (input.or && input.or > 0)){
        return parameters["+"] || null;
    } else if ((input.beta && input.beta < 0) || (input.or && input.or < 0)){
        return parameters["-"] || null;
    }
    return null;
});

Remember that in scale function land null is the magic value that signals no value was found, so keep stepping through other functions in the array.

The only difference in v0.5.6 is that if you fail to specify a field name in the layout which invokes the scale function then the entire data element is passed in. So with the above custom scale function defined, you should then be able to define your point shape in the layout like this:

...
point_shape: [
    {
        scale_function: "if",
        field: "ld:isrefvar",
        parameters: {
            field_value: 1,
            then: "diamond",
            else: null
        }
    },
    {
        scale_function: "effect_direction",
        parameters: 
            "+": "triangle-up",
            "-": "triangle-down"
    },
    "circle"
],
...

Note how the else on the first function is null (instead of "circle") - this will ensure it passed through to the effect direction function.

**

Ultimately it would probably be best to work the effect direction stuff back into the core library, but because you can define and use this as a custom method first it seems to be a better approach to let you work with it in PheWeb and iterate on it until it's performing really well. Then we can take your refined custom stuff and bake it into a future version.

pjvandehaar commented 7 years ago

TODO: