@@ -120,30 +120,61 @@ Stat <- ggproto("Stat",
120
120
self $ compute_group(data = group , scales = scales , ... )
121
121
})
122
122
123
+ # Record columns that are not constant within groups. We will drop them later.
124
+ non_constant_columns <- character (0 )
125
+
123
126
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
+
124
139
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
+
127
151
vec_cbind(
128
152
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 ]
130
157
)
131
158
}, stats , groups , SIMPLIFY = FALSE )
132
159
133
- data_new <- vec_rbind( !!! stats )
160
+ non_constant_columns <- unique0( non_constant_columns )
134
161
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
136
163
# 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 ]
139
167
if (length(dropped ) > 0 ) {
140
168
cli :: cli_warn(c(
141
169
" The following aesthetics were dropped during statistical transformation: {.field {glue_collapse(dropped, sep = ', ')}}" ,
142
170
" i" = " This can happen when ggplot fails to infer the correct grouping structure in the data." ,
143
171
" i" = " Did you forget to specify a {.code group} aesthetic or to convert a numerical variable into a factor?"
144
172
))
145
173
}
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 ]
147
178
},
148
179
149
180
compute_group = function (self , data , scales ) {
0 commit comments