SlowLearner
SlowLearner

Reputation: 7997

R: how to conditionally change values for 1 variable out of 3 used in a ggplot facet plot

Q. How can I prevent values < 0 being shown in a ggplot2 facet plot for just one of three different variables plotted, each of different magnitudes?

I have made the following facet plot.

enter image description here

As you can see, the value plotted on the y-axis is significantly different for each variable, but using scales = "free" resolves that issue.

I want to suppress the values less than zero in the "profit_margin" facet (coloured in blue at the bottom) by either limiting the scale or setting values of less than zero to zero, but I cannot work out how to accomplish this. I could directly zap the values in the data frame but I would prefer to leave the data untouched. I tried using a function in scale_y_continuous() but was unable to make any headway.

Here is the code used to generate the above plot:

require(lubridate)
require(reshape2)
require(ggplot2)
require(scales)

## Create dummy time series data
set.seed(12345)
monthsback <- 12
startdate <- as.Date(paste(year(now()),month(now()),"1",sep = "-")) - months(monthsback)
mydf <- data.frame(mydate = seq(as.Date(startdate), by = "month", length.out = monthsback),
                   sales = runif(monthsback, min = 600, max = 800),
                   profit = runif(monthsback, min = -50, max = 80))
## Add calculation based on data
mydf$profit_margin <- mydf$profit/mydf$sales

## Reshape...
mymelt <- melt(mydf, id = c('mydate'))

## Plot
p <- ggplot(data = mymelt, aes(x = mydate, y = value, fill = variable)) +
     geom_bar(stat = "identity") +
     facet_wrap( ~ variable, ncol = 1, scales = "free")

print(p)

And this was my attempt to use a function and lapply to set sub-zero values to zero:

require(lubridate)
require(reshape2)
require(ggplot2)
require(scales)

## Create dummy time series data
set.seed(12345)
monthsback <- 12
startdate <- as.Date(paste(year(now()),month(now()),"1",sep = "-")) - months(monthsback)
mydf <- data.frame(mydate = seq(as.Date(startdate), by = "month", length.out = monthsback),
                   sales = runif(monthsback, min = 600, max = 800),
                   profit = runif(monthsback, min = -50, max = 80))
## Add calculation based on data
mydf$profit_margin <- mydf$profit/mydf$sales

## Reshape...
mymelt <- melt(mydf, id = c('mydate'))

scales_function <- function(myvar, myvalue){
    mycount <- 1
    newval <- lapply(myvalue, function(myarg) {
        myarg <- ifelse(myvar[mycount] == "profit_margin", ifelse(myarg < 0, 0, myarg), myarg)
    }
                  )
    return(newval)
}

## Plot
p <- ggplot(data = mymelt, aes(x = mydate, y = value, fill = variable)) +
     geom_bar(stat = "identity") +
     facet_wrap( ~ variable, ncol = 1, scales = "free") +
     scale_y_continuous(breaks = scales_function(mymelt$variable, mymelt$value))

print(p)

Upvotes: 2

Views: 974

Answers (1)

mnel
mnel

Reputation: 115392

You can leave the data untouched, but just plot a subset.

ggplot(data = subset(mymelt,!((variable == 'profit_margin') & value < 0)), 
       aes(x = mydate, y = value, fill = variable)) +
     geom_bar(stat = "identity") +
     facet_wrap( ~ variable, ncol = 1, scales = "free")

Or replace within the call

ggplot(data = mymelt, aes(x = mydate, y = replace(value, (variable == 'profit_margin') & value <0, NA), fill = variable)) +
 geom_bar(stat = "identity") +
 facet_wrap( ~ variable, ncol = 1, scales = "free") +
 ylab('value')

Upvotes: 5

Related Questions