2
\$\begingroup\$

I am working on a problem where I need to get specific information for min and max outliers.

This is in relation to the ggplot library. I will be getting all my information from ggplot_build object.

The data that is used will look something like this:

cleandata <- data.frame(middle=c(1, 4, 7), xmiddle=c(2, 9, 1))
cleandata$outliers <- list(c(-3, 1.5, 6), 5, 8)
flipped <- TRUE

Or to have actual example data yourself this code would work:

boxplot = ggplot(mpg, aes(class, hwy)) + geom_boxplot(varwidth = TRUE)
boxplot_b = ggplot_build(boxplot ) 
flipped <- boxplot_b [["data"]][[1]][["flipped_aes"]] == length(boxplot_b [["data"]][[1]][["flipped_aes"]])
cleandata = boxplot_b [["data"]][[1]]

Although any boxplot will work with a little change of code listed.

I have this bit of code that will get the min and max outliers in two vectors.

maxoutliers = c()
minoutliers = c()
for (box in seq_along(cleandata$outliers)) {
  outliers = cleandata$outliers[[box]]
  if (flipped) {
    middle = cleandata$xmiddle[box]
  } else {
    middle = cleandata$middle[box]
  }
  maxoutliers[length(maxoutliers)+1] = outliers[outliers > middle] |>
    length()
  minoutliers[length(minoutliers)+1] = outliers[outliers < middle] |>
    length()
}

flipped is whether the boxplot is horizontal or not. And cleandata is the data frame you can see above.

I just feel like there is a cleaner way to do this but I can’t seem to think of a way.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Welcome to Code review! Does the data contain information about fuel economy (given mpg)? It would benefit reviewers to have a bit more information about the code in the description. From the help center page How to ask: "You will get more insightful reviews if you not only provide your code, but also give an explanation of what it does. The more detail, the better." \$\endgroup\$ Commented Dec 6, 2022 at 6:52

1 Answer 1

2
\$\begingroup\$

R is a vectorized language, and code is often nicer when we keep things as vectors instead of looping through their elements. For instance, we could replace your code selecting the "middle" element for each row of your data with:

middle <- if (flipped) cleandata$xmiddle else cleandata$middle

Now, middle is a vector containing the correct "middle" value for each row of your data.

At this point, you want to compute the number of elements in each of your outliers vectors that are above or below the corresponding middle value for that row. You do this by computing outliers[outliers > middle] |> length() and outliers[outliers < middle] |> length() and then concatenating those results one element at a time into vectors you are building. Building a vector this way is actually a big no-no in R -- it needs to reallocate the whole output vector each iteration of your loop and can be painfully slow for large loops (for more discussion of this topic, see Circle 2 of The R Inferno).

At the end of the day, you want to loop through each pair of middle/outliers and do your calculations, storing the result into a properly-sized vector. Looping through multiple lists at once (akin to zip in python) and outputting to a properly sized output can be accomplished with mapply in R:

minoutliers <- mapply(function(x, y) sum(x < y), cleandata$outliers, middle)
maxoutliers <- mapply(function(x, y) sum(x > y), cleandata$outliers, middle)

Notice that keeping things vectors as long as possible has given us one final advantage -- our code is much shorter (just 3 lines).

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.