@@ -120,30 +120,61 @@ Stat <- ggproto("Stat",
120120 self $ compute_group(data = group , scales = scales , ... )
121121 })
122122
123+ # Record columns that are not constant within groups. We will drop them later.
124+ non_constant_columns <- character (0 )
125+
123126 stats <- mapply(function (new , old ) {
127+ # In this function,
128+ #
129+ # - `new` is the computed result. All the variables will be picked.
130+ # - `old` is the original data. There are 3 types of variables:
131+ # 1) If the variable is already included in `new`, it's ignored
132+ # because the values of `new` will be used.
133+ # 2) If the variable is not included in `new` and the value is
134+ # constant within the group, it will be picked.
135+ # 3) If the variable is not included in `new` and the value is not
136+ # constant within the group, it will be dropped. We need to record
137+ # the dropped columns to drop it consistently later.
138+
124139 if (empty(new )) return (data_frame0())
125- unique <- uniquecols(old )
126- missing <- ! (names(unique ) %in% names(new ))
140+
141+ # First, filter out the columns already included `new` (type 1).
142+ old <- old [, ! (names(old ) %in% names(new )), drop = FALSE ]
143+
144+ # Then, check whether the rest of the columns have constant values (type 2)
145+ # or not (type 3).
146+ non_constant <- vapply(old , function (x ) length(unique0(x )) > 1 , logical (1L ))
147+
148+ # Record the non-constant columns.
149+ non_constant_columns <<- c(non_constant_columns , names(old )[non_constant ])
150+
127151 vec_cbind(
128152 new ,
129- unique [rep(1 , nrow(new )), missing ,drop = FALSE ]
153+ # Note that, while the non-constant columns should be dropped, we don't
154+ # do this here because it can be filled by vec_rbind() later if either
155+ # one of the group has a constant value (see #4394 for the details).
156+ old [rep(1 , nrow(new )), , drop = FALSE ]
130157 )
131158 }, stats , groups , SIMPLIFY = FALSE )
132159
133- data_new <- vec_rbind( !!! stats )
160+ non_constant_columns <- unique0( non_constant_columns )
134161
135- # The above code will drop columns that are not constant within groups and not
162+ # We are going to drop columns that are not constant within groups and not
136163 # carried over/recreated by the stat. This can produce unexpected results,
137- # and hence we warn about it.
138- dropped <- base :: setdiff(names(data ), base :: union(self $ dropped_aes , names(data_new )))
164+ # and hence we warn about it (variables in dropped_aes are expected so
165+ # ignored here).
166+ dropped <- non_constant_columns [! non_constant_columns %in% self $ dropped_aes ]
139167 if (length(dropped ) > 0 ) {
140168 cli :: cli_warn(c(
141169 " The following aesthetics were dropped during statistical transformation: {.field {glue_collapse(dropped, sep = ', ')}}" ,
142170 " i" = " This can happen when ggplot fails to infer the correct grouping structure in the data." ,
143171 " i" = " Did you forget to specify a {.code group} aesthetic or to convert a numerical variable into a factor?"
144172 ))
145173 }
146- data_new
174+
175+ # Finally, combine the results and drop columns that are not constant.
176+ data_new <- vec_rbind(!!! stats )
177+ data_new [, ! names(data_new ) %in% non_constant_columns , drop = FALSE ]
147178 },
148179
149180 compute_group = function (self , data , scales ) {
0 commit comments