Solutions
As it turns out, payment_type is a very important feature for what we're trying to predict. For whatever reason, tip_amount does not show up in the data when a customer pays in cash. In other words, as far as the data is concerned, cash-paying customers do not tip. Let's set aside the reason, although it is something that is worth investigating. Knowing this, what we expect to happen is that predictions made by rxlm_3 will be heavily influenced by payment_type.
(1) We begin by building the model and storing it in the model object rxlm_3.
form_3 <- as.formula(tip_percent ~ payment_type + pickup_nb:dropoff_nb + pickup_dow:pickup_hour)
rxlm_3 <- rxLinMod(form_3, data = mht_xdf, dropFirst = TRUE, covCoef = TRUE)
(2) There are different ways of combining the predictions. One approach is to first let's make the predictions and store them in separate datasets and use cbind to combine them (as long as the order of the rows doesn't change). We can then use the mutate_at function in dplyr to apply the binning transformation to the two predictions (we can use mutate too but mutate_at has a more concise notation).
rxs <- rxSummary( ~ payment_type + pickup_nb + dropoff_nb + pickup_hour + pickup_dow, mht_xdf)
ll <- lapply(rxs$categorical, function(x) x[ , 1])
names(ll) <- c('payment_type', 'pickup_nb', 'dropoff_nb', 'pickup_hour', 'pickup_dow')
pred_df <- expand.grid(ll)
pred_df_1 <- rxPredict(rxlm_1, data = pred_df, computeStdErrors = TRUE, writeModelVars = TRUE)
pred_df_3 <- rxPredict(rxlm_3, data = pred_df, computeStdErrors = TRUE, writeModelVars = TRUE)
pred_df %>%
cbind(select(rename(pred_df_1, p1 = tip_percent_Pred), p1)) %>%
cbind(select(rename(pred_df_3, p3 = tip_percent_Pred), p3)) %>%
mutate_at(vars(p1, p3), funs(cut(., c(-Inf, 8, 12, 15, 18, Inf)))) -> pred_all
(3) Once we have the data ready, we feed it to ggplot to create bar plots comparing predictions made by each models broken up by whether the customer paid with a card or using cash.
ggplot(data = pred_all) +
geom_bar(aes(x = p1, fill = "model 1", alpha = .5)) +
geom_bar(aes(x = p3, fill = "model 3", alpha = .5)) +
facet_grid(~ payment_type) +
xlab('tip percent prediction') +
theme(axis.text.x = element_text(angle = 90, hjust = 1))

The model does in fact predict as we expected. It is also possible that are predictions are good, but need to be calibrated. We can recalibrate the predictions by adding a single line of code just before we bin the predictions. To recalibrate the predictions, we use the rescale function in the scales library. In this case, we are rescaling predictions so that both models predict a number between 0 and 20% tip.
library(scales)
pred_df %>%
cbind(select(rename(pred_df_1, p1 = tip_percent_Pred), p1)) %>%
cbind(select(rename(pred_df_3, p3 = tip_percent_Pred), p3)) %>%
mutate_at(vars(p1, p3), funs(rescale(., to = c(0, 20)))) %>%
mutate_at(vars(p1, p3), funs(cut(., c(-Inf, 8, 12, 15, 18, Inf)))) -> pred_all
ggplot(data = pred_all) +
geom_bar(aes(x = p1, fill = "model 1", alpha = .5)) +
geom_bar(aes(x = p3, fill = "model 3", alpha = .5)) +
facet_grid(~ payment_type) +
xlab('tip percent prediction') +
theme(axis.text.x = element_text(angle = 90, hjust = 1))
