Skip to content

Conversation

@DavisVaughan
Copy link
Member

@DavisVaughan DavisVaughan commented Sep 30, 2025

This description is outdated but I've left it for historical purposes, see below comments

Closes #7082

This is a breaking change.

Motivating example with CRAN dplyr:

x <- 5

# The user has made a typo here with `character()` on the RHS
#
# Note that all LHS inputs are size 1
#
# The common size from consulting BOTH the LHS and RHS inputs is 0, which
# results in a silent confusing result of `character()`
dplyr::case_when(
  x == 5 ~ "hello",
  x > 10 ~ character(),
  .default = "other"
)
#> character(0)

With this PR

x <- 5

# If you ONLY consult the size of the LHS inputs, then the common size is 1,
# and then all RHS inputs are recycled to this size, which RHS-2 `character()` can't
# do, and we correctly catch your error!
dplyr::case_when(
  x == 5 ~ "hello",
  x > 10 ~ character(),
  .default = "other"
)
#> Error in `dplyr::case_when()`:
#> ! `..2 (right)` must have size 1, not size 0.

In #7082 (comment) I explained how changing to this behavior increases the safety of case_when() (as seen above) and brings us one step closer to my ideal case_when() behavior. Unfortunately, having to support TRUE ~ will keep us from reaching case_when() nirvana.

In #7082 (comment), I note that this should only affect an extremely small fraction of cases - a prerequisite for this to be a change in behavior is that all of your LHS inputs must have been length 1, which is quite rare. Regardless, I will run revdeps to assess. "8fa7eaf9-c4e6-4fe9-b7e9-7855b11046a6"

@DavisVaughan DavisVaughan requested a review from hadley September 30, 2025 16:49
@DavisVaughan DavisVaughan force-pushed the feature/case-when-recycling-refinement branch from 17645b8 to cd8ad64 Compare October 1, 2025 16:42
@DavisVaughan DavisVaughan changed the title Only consult LHS inputs to determine case_when() output size Soft-deprecate case_when() with all size 1 LHS and any size >1 RHS Oct 1, 2025
@DavisVaughan
Copy link
Member Author

DavisVaughan commented Oct 1, 2025

We gave an attempt at throwing an error when case_when() was used with all size 1 LHS inputs and any size >1 RHS inputs, but it causes 60+ revdep failures because people seem to incorrectly use case_when() in place of a series of if statements, i.e.

# Scalars!
code <- 1L
flavor <- "vanilla"

# Many packages use case_when() like this
case_when(
  code == 1L && flavor == "chocolate" ~ x,
  code == 1L && flavor == "vanilla" ~ y,
  code == 2L && flavor == "vanilla" ~ z,
  .default = default
)

# This is what it should have been
if (code == 1L && flavor == "chocolate") {
  x
} else if (code == 1L && flavor == "vanilla") {
  y
} else if (code == 2L && flavor == "vanilla") {
  z
} else {
  default
}

This is very inefficient code because each scalar LHS is being recycled to the common size found by incorporating the RHS into the common size computation. It also leaves the door open for the silent bug in #7082.

To encourage people not to do this, case_when() now throws a soft deprecation warning when it sees this particular case of "all size 1 LHS and any size !=1 RHS".


I think the way to explain this to people is that it is the same rationale as how you use if_else():

flavor <- "vanilla"

# You'd never try this
# (and in fact it does not work at all because `if_else()` has correct recycling rules here. It forcibly recycles
#  `true` and `false` to the size of `condition`, that's very good)
if_else(flavor == "vanilla", x, y)

# You'd instead do this
if (flavor == "vanilla") {
  x
} else {
  y
}

This improper usage of case_when() is just an extension of this idea when you have >1 scalar condition to check.

@DavisVaughan DavisVaughan requested a review from hadley October 1, 2025 17:02
@DavisVaughan DavisVaughan force-pushed the feature/case-when-recycling-refinement branch from cd8ad64 to abf2409 Compare October 2, 2025 16:22
@DavisVaughan
Copy link
Member Author

I'm going to merge because it keeps things simple for me as I keep iterating here, but I can definitely still incorporate any feedback you may have here!

@DavisVaughan DavisVaughan merged commit ea305c0 into main Oct 2, 2025
14 checks passed
@DavisVaughan DavisVaughan deleted the feature/case-when-recycling-refinement branch October 2, 2025 17:24
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.

case_when returns length-0 option if any option is length-0

3 participants