Skip to content

Support facet formulas y~1 and 1~x for 1-column or 1-row layouts#562

Open
zeileis wants to merge 2 commits intomainfrom
facet-formula
Open

Support facet formulas y~1 and 1~x for 1-column or 1-row layouts#562
zeileis wants to merge 2 commits intomainfrom
facet-formula

Conversation

@zeileis
Copy link
Copy Markdown
Collaborator

@zeileis zeileis commented Mar 24, 2026

In #558 we discussed the idea that facet = y ~ 1 could be used as a shortcut for facet = ~ y, facet.args = list(nrow = length(unique(y))). Analogously, facet = 1 ~ x could be used as a shortcut for facet = ~ x, facet.args = list(nrow = 1).

I have implemented this now by tweaking tinyframe() slightly. This now distinguishes ~ x from 1 ~ x by returning NULL for the yfacet in the former case but a zero-column data frame in the latter case. In other words, a zero-column data frame signals: This part of the formula had a specification but no variables in it. In tinyplot.formula() I've extended the processing of xfacet and yfacet correspondingly.

In principle, this works but there are two caveats:

  1. tinyplot.default() handles the case with facet.args slightly differently from facet attributes.
  2. The processing has only been extended in the formula method. I just noticed now that the is an additional function get_facet_fml() (https://github.com/grantmcdermott/tinyplot/blob/facet-formula/R/facet.R#L613-L658) that does something similar to my tinyformula()/tinyframe() processing but is only used in the default method. We should probably try to use the same code in both cases.

To illustrate the first point, consider

library("tinyplot")
tinytheme("clean2")
p = transform(penguins, group = factor(paste(species, island, sep = "-")))
tinyplot(bill_dep ~ bill_len, data = p, facet = 1 ~ group)                            ## top
tinyplot(bill_dep ~ bill_len, data = p, facet = ~ group, facet.args = list(nrow = 1)) ## bottom
facet2
tinyplot(bill_dep ~ bill_len, data = p, facet = group ~ 1)                            ## left
tinyplot(bill_dep ~ bill_len, data = p, facet = ~ group, facet.args = list(nrow = 5)) ## right
facet1

Note that in the formula-based specification we get the right facet strips as well. Probably this should only be done in case both the number of rows and the number of columns is greater than 1? I wasn't sure though where to best add this information.

@grantmcdermott
Copy link
Copy Markdown
Owner

Thanks @zeileis.

In #558 we discussed the idea that facet = y ~ 1 could be used as a shortcut for facet = ~ y, facet.args = list(nrow = length(unique(y))).

Minor comment: but why not just use facet = ~ y, facet.args = list(ncol = 1) instead of the latter?

Regarding your plots... Hmmm, I'm not sure that I like the "formula" output. Taking the very top plot as an example, why do we have a duplicate "Gentoo-Biscoe" facet label on the right (but none of the other groups)? Ideally, these should give the exact same output as the facet.args = list(nrow = 1) case, right? Apologies if I'm just re-stating your point 1, but I want to make sure that we're aligned on the desired plot/outcome.

Regarding 2, yes I agree. Ideally, we would have some singular (modular) code that both "methods" can call. It's been a long time since I wrote that up, and can't remember my exact reason. It might just have been the simplest solution at the time, even though it incurred a bit of tech debt.

@zeileis
Copy link
Copy Markdown
Collaborator Author

zeileis commented Mar 26, 2026

Regarding your three comments/questions:

When using facet.args you could indeed set ncol rather than nrow. But when adding passing the information through attributes of the facet variable, then only the facet_nrow attribute is used but not facet_ncol. Hence, in the formula method we only compute facet_nrow.

Correct, the formula-based output is currently not ideal. Apologies for not bringing this out clearly. I also think that we should get the same output as via facet.args! Note that the current behavior is not related to the formula processing in tinyplot.formula. It is caused by the difference in handling a plain facet variable plus facet.args in tinyplot.default vs. a facet variable with attributes. The following example causes the same problem:

p <- na.omit(p) ## omit missing values from transformed penguin data above
g <- p$group ## set up group variable with facet attributes
attr(g, "facet_grid") <- TRUE
attr(g, "facet_nrow") <- 1
tinyplot(p$bill_len, p$bill_dep, facet = g) ## unnecessary right facet strip
tinyplot(p$bill_len, p$bill_dep, facet = p$group, facet.args = list(nrow = 1)) ## ok

Should I try to replace get_facet_fml()? If so, anything I should try to pay attention to? Or better leave it as it is for now?

@grantmcdermott
Copy link
Copy Markdown
Owner

Thanks @zeileis. Appreciate the clarifications.

Stepping back for a sec, I'm planning to submit a patch 0.6.1 release to CRAN imminently---in part, b/c I'm trying to update my maintainer email across all of my packages. I was hoping to get this PR (and #558) in as part of the submission, but it seems that we still have a bit of work to do. So I think it's best to leave these out for the moment and we can address afterwards. Hope that's okay?

@zeileis
Copy link
Copy Markdown
Collaborator Author

zeileis commented Mar 26, 2026

Sure, perfectly fine! 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants