Jonathan Carroll
Jonathan Carroll

Reputation: 3947

facet_wrap on a nudged variable

With lots of overlapping data to show, it can be helpful to jitter points so they can be better identified. For discrete (not necessarily factor) data it can be helpful to offset the entire series slightly. This can now be done quite neatly using ggplot2::position_nudge(). As far as I can tell, that function does not accept NSE variables from the ggplot() call, so (and by all means correct me if I'm wrong) one needs to specify the data source if providing a variable offset from some data structure.

If, say, we want to nudge a value by an amount dependent on another value, we could do it this way:

library(ggplot2)
p <- ggplot(mtcars, aes(x = mpg, y = factor(cyl))) + 
  geom_point(aes(col = factor(am)), 
             position = position_nudge(y = 0.1*mtcars$am))
p

Trying to use just am in the position_nudge() call fails ("non-numeric argument to binary operator"). Otherwise this is acceptable.

The problem with this is that facet_wrap on such a call breaks the effect since, presumably, the faceting code knows nothing about this explicit data

p + facet_wrap(~gear)

Is there a way to allow this to happen (short of converting to continuous, shifting manually, then relabelling axis), or a more suitable function? If nothing comes up I'll raise an issue in the repo, but I figured I should consult the hivemind first.

Upvotes: 1

Views: 710

Answers (1)

eipi10
eipi10

Reputation: 93871

UPDATE: Based on the discussion in the comments, maybe the best approach is to just add fake data in a location outside the plot area so that all factor levels will have at least one data point. This will result in every point being dodged in every facet. For example:

First, we'll add data points for all unique values of all variables that will become factors in the plot, but we'll assign them an mpg value of -1, which is outside the plot area of the real data.

library(ggplot2)
library(ggstance)
library(dplyr)

mtcars = bind_rows(mtcars, 
                   expand.grid(mpg=-1, 
                               cyl=unique(mtcars$cyl), 
                               am=unique(mtcars$am), 
                               gear=unique(mtcars$gear)))

Now we can plot with vertical dodging as in the original answer, but all points will be dodged:

ggplot(mtcars, aes(x = mpg, y = factor(cyl))) + 
  geom_point(aes(col = factor(am)), position = position_dodgev(0.5)) +
  facet_wrap(~gear) +
  coord_cartesian(xlim=range(mtcars$mpg[mtcars$mpg>0]))

enter image description here


Original Answer

You can use position_dodge if you switch your x and y aesthetics, and then add coord_flip to put cyl back on the vertical axis:

p = ggplot(mtcars, aes(y = mpg, x = factor(cyl))) + 
  geom_point(aes(col = factor(am)), position = position_dodge(0.3)) +
  coord_flip() 

enter image description here

p + facet_wrap(~gear)

enter image description here

Another, option is to use position_dodgev from the ggstance package, which provides vertical dodging, and avoids the need to flip the axes:

library(ggstance)

p = ggplot(mtcars, aes(x = mpg, y = factor(cyl))) + 
  geom_point(aes(col = factor(am)), position = position_dodgev(0.3))

Although it's unnecessary, something like your original method could work if you do it inside aes so that all the ggplot components know about the mapping. For example:

ggplot(mtcars, aes(x = mpg, y = cyl + 0.2*(am - mean(am)))) + 
  geom_point(aes(col = factor(am))) +
  scale_y_continuous(breaks=unique(mtcars$cyl), minor_breaks=NULL) +
  facet_wrap(~ gear)

Upvotes: 2

Related Questions