Closed Tariq-K closed 4 years ago
The side
argument is not intended to be interpreted that way (I do agree that it is a little confusing)
Basically gate_tail
always return a gate that capture the higher end of the 1d signal. The side
argument is used to determine where the cutpoint is located
Here is the doc
Setting the side argument to "left" modifies the procedure to put the cutpoint on the left side of the reference peak to isolate a left tail.
par(mfrow = c(1,2))
g_right <- gate_tail(f, "FSC-H", adjust = 1.5, tol = 0.001, auto_tol = FALSE, side = "right", plot = T)
abline(v= g_right@min)
g_left <- gate_tail(f, "FSC-H", adjust = 1.5, tol = 0.001, auto_tol = FALSE, side = "left", plot = T)
abline(v= g_left@min)
This is generally the case for all other gating functions from openCyto
, which has a different mechanism to retain the cells from the lower end, that is by setting pop = '-'
argument in gs_add_gating_method
call
f <- read.FCS(file.name, transformation=FALSE)
gs <- GatingSet(as(f, "flowSet"))
gs_add_gating_method(gs, "high", pop = "+", parent = "root", dims = "FSC-H", gating_method = "gate_tail")
autoplot(gs, "high", y = "SSC-H")
gs_add_gating_method(gs, "low", pop = "-", parent = "root", dims = "FSC-H", gating_method = "gate_tail")
autoplot(gs, "low", y = "SSC-H")
Even that case, the gate coordinates are still right hand side
g <- gs_pop_get_gate(gs, "low")[[1]]
g
Rectangular gate 'low' with dimensions:
FSC-H: (475.423776291218,Inf)
It is through the negate
flag to achieve the lower-end of cell population, i.e.
> gh_pop_is_negated(gs[[1]], "low")
[1] TRUE
So to conclude, you will need to create your own wrapper around gate_tail
(which is very straightforward) in order to do what you described.
The weird truncation of the side
arg's brief doc is now fixed. The behavior under side="left"
seems awkward to me. Based on the intent of the method, I'd expect the positive/+
population under that definition to be (-Inf, cutpoint]
(where cutpoint
is to the left of the peak). I'll have to take a look at any downstream assumptions that might break, though.
Alternatively, if we need to keep the gate_tail
definition as it is for the openCyto
machinery, we could export an easy wrapper of .cytokine_cutpoint to do the heavy lifting of determining the start of the tail and let users define a rectangleGate
putting the other bound wherever they want (it need not be infinite).
I agree, @jacobpwagner . I don't see why it would have been otherwise. I don't see the use case for the current behaviour.
Thanks all for your comments, and for updating the documentation. I'd certainly appreciate it if the functionality I described could be incorporated into gate_tail
or .cytokine_cutpoint
somehow, it's a use case that I often require and I'm certain that other users of openCyto
would benefit!
@Tariq-K , this should be fixed by https://github.com/RGLab/openCyto/commit/54513238a21c58578c513ebb62a927dd33546f1e. I just had to make sure openCyto
would handle it appropriately through openCyto::gs_add_gating_method
, but it seems to be handled as expected by both openCyto:gs_add_gating_method
and through directly adding the resulting gate via flowWorkspace::gs_pop_add
.
@mikejiang , we still need to work through the refGate
logic of handling a gate added with pop=="+"
but bounds (-Inf, a]
.
https://github.com/RGLab/openCyto/blob/1e728cd31d71768c6a02ed4c32ec748110fa8db4/R/gating-methods.R#L592-L613
Using the current state of that logic, I think that will get flipped back to [a, Inf)
. I've gone ahead with the changes in https://github.com/RGLab/openCyto/commit/54513238a21c58578c513ebb62a927dd33546f1e because I doubt users were intentionally using the old incorrect behavior of gate_tail
with side == "left"
.
As discussed offline, I believe the 1-D Inf
-flipping logic comes from the requirements for the 1-D components of 2-D quad gates, but it does seem a bit problematic for natively 1-D gates.
Just adding a test script here for demonstration that it works to add the gate using either approach.
library(ggcyto)
library(flowCore)
library(openCyto)
# Right tail
gs1 <- load_gs(system.file("extdata", "gs_manual", package = "flowWorkspaceData"))
gs2 <- gs_clone(gs1)
# flowWorkspace::gs_pop_add approach
right_tail_gate <- gate_tail(gh_pop_get_data(gs1[[1]], "CD4"), channel = "<G560-A>", side = "right")
right_tail_gate@filterId <- "CCR7_right_tail"
right_tail_gate
gs_pop_add(gs1, right_tail_gate, parent = "CD4", name = "CCR7_right_tail_pos")
gs_pop_add(gs1, right_tail_gate, parent = "CD4", name = "CCR7_right_tail_neg", negated = TRUE)
recompute(gs1)
autoplot(gs1[[1]], "CCR7_right_tail_pos")
autoplot(gs1[[1]], "CCR7_right_tail_neg")
ggcyto(gh_pop_get_data(gs1, "CD4"), aes("CCR7")) +
geom_density() +
geom_gate(right_tail_gate)
# openCyto::gs_add_gating_method approach
openCyto::gs_add_gating_method(gs2, alias = "CCR7_right_tail_pos", pop = "+", parent = "CD4",
dims = "<G560-A>", gating_method = "gate_tail",
gating_args = "side='right'")
openCyto::gs_add_gating_method(gs2, alias = "CCR7_right_tail_neg", pop = "-", parent = "CD4",
dims = "<G560-A>", gating_method = "gate_tail",
gating_args = "side='right'")
autoplot(gs2[[1]], "CCR7_right_tail_pos")
autoplot(gs2[[1]], "CCR7_right_tail_neg")
ggcyto(gh_pop_get_data(gs2[[1]], "CD4"), aes("CCR7")) +
geom_density() +
geom_gate(gh_pop_get_gate(gs2[[1]], "CCR7_right_tail_pos"))
ggcyto(gh_pop_get_data(gs2[[1]], "CD4"), aes("CCR7")) +
geom_density() +
geom_gate(gh_pop_get_gate(gs2[[1]], "CCR7_right_tail_neg"))
# Verifying both approaches agree
gh_pop_get_stats(gs1[[1]], "CCR7_right_tail_pos")
gh_pop_get_stats(gs2[[1]], "CCR7_right_tail_pos")
gh_pop_get_stats(gs1[[1]], "CCR7_right_tail_neg")
gh_pop_get_stats(gs2[[1]], "CCR7_right_tail_neg")
# Left tail
gs1 <- load_gs(system.file("extdata", "gs_manual", package = "flowWorkspaceData"))
gs2 <- gs_clone(gs1)
# flowWorkspace::gs_pop_add approach
left_tail_gate <- gate_tail(gh_pop_get_data(gs1[[1]], "CD4"), channel = "<G560-A>", side = "left")
left_tail_gate@filterId <- "CCR7_left_tail"
left_tail_gate
gs_pop_add(gs1, left_tail_gate, parent = "CD4", name = "CCR7_left_tail_pos")
gs_pop_add(gs1, left_tail_gate, parent = "CD4", name = "CCR7_left_tail_neg", negated = TRUE)
recompute(gs1)
autoplot(gs1[[1]], "CCR7_left_tail_pos")
autoplot(gs1[[1]], "CCR7_left_tail_neg")
ggcyto(gh_pop_get_data(gs1, "CD4"), aes("CCR7")) +
geom_density() +
geom_gate(left_tail_gate)
# openCyto::gs_add_gating_method approach
openCyto::gs_add_gating_method(gs2, alias = "CCR7_left_tail_pos", pop = "+", parent = "CD4",
dims = "<G560-A>", gating_method = "gate_tail",
gating_args = "side='left'")
openCyto::gs_add_gating_method(gs2, alias = "CCR7_left_tail_neg", pop = "-", parent = "CD4",
dims = "<G560-A>", gating_method = "gate_tail",
gating_args = "side='left'")
autoplot(gs2[[1]], "CCR7_left_tail_pos")
autoplot(gs2[[1]], "CCR7_left_tail_neg")
ggcyto(gh_pop_get_data(gs2[[1]], "CD4"), aes("CCR7")) +
geom_density() +
geom_gate(gh_pop_get_gate(gs2[[1]], "CCR7_left_tail_pos"))
ggcyto(gh_pop_get_data(gs2[[1]], "CD4"), aes("CCR7")) +
geom_density() +
geom_gate(gh_pop_get_gate(gs2[[1]], "CCR7_left_tail_neg"))
# Verifying both approaches agree
gh_pop_get_stats(gs1[[1]], "CCR7_left_tail_pos")
gh_pop_get_stats(gs2[[1]], "CCR7_left_tail_pos")
gh_pop_get_stats(gs1[[1]], "CCR7_left_tail_neg")
gh_pop_get_stats(gs2[[1]], "CCR7_left_tail_neg")
Brilliant, thanks very much @jacobpwagner
@jacobpwagner I'm reopening this. I'm responsible for the confusion. I misread this issue and suggested a "fix" that introduced a bug. My apologies.
Indeed, @mikejiang is correct in his assertion that gate_tail
with arg side="left"
should capture the positive population by default.
Otherwise is breaks the openCyto templates as in #221.
The question is, since gate_tail is being used in this way, should we consider passing in the "+" or "-" argument and have the directionality of the gate handled here rather than in the caller.
Or, perhaps we introduce a helper function that can just flip the gate coordinates for those users that call gate_tail
directly.
I think we'll have to revert this change and update the docs.
Personally I think the better solution is making the 1-D inversion logic more robust as discussed in https://github.com/RGLab/openCyto/issues/221#issuecomment-715631937. That lets gate_tail
have the expected behavior as a standalone function and makes the openCyto::refGate
logic handle these issues better moving forward. But I'm happy to fix it either way.
If it's any consolation, I think the original logic made sense in my head. The side
argument places the gate on the appropriate side of the ref peak, while the pop
argument decides if you want the population on the right or left side of that line included in the gate.
Yeah, we discussed it and that's effectively what we will be going back to, but with the option to add a positive = FALSE
argument that lets you choose the (-Inf, cutpoint]
option outside of the gatingTemplate
context.
That makes sense, thanks!
After https://github.com/RGLab/openCyto/commit/47687e6d42900bc0af1b964cb7e5b63d7e25ba81, the default behavior under side="left"
is back to taking the positive orientation [cutpoint, Inf)
. However, there is now the option to add positive=FALSE
to flip that to (-Inf, cutpoint]
. That's most relevant if you truly need the gate orientation flipped if you plan on using the gate object for other purposes. If that's not necessary, you can simply use pop="-"
in a gatingTemplate
(or in gs_add_gating_method
which goes through gatingTemplate
) or negated = TRUE
in flowWorkspace::gs_pop_add
.
Bug
Expected behaviour
Reproducible example
Suggested fix
Session info
Created on 2020-08-19 by the reprex package (v0.3.0)