The difference between yellow-green & green-yellow

Introduction

According to a Twitter bot that tracks deleted article titles, Wikipedia removed an article last week called “The difference between yellow-green and green-yellow.”

Either Wikipedia was grossly negligent in deleting this article or I have lower editorial standards at ryantimpe dot com. Either way, I will boldly go where Wikipedia refused and write this article myself.

The Goal

Using the tools I know (R, always R), and considering that I know almost nothing about the physics and perception of color (or the English language), can I quantify the difference between yellow-green and green-yellow?

tl;dr

Green-yellow is the true hybrid color of green and yellow. The ‘green’ in ‘green-yellow’ is more than an adverb: it is the Thelma to yellow’s Louise.

Yellow-green on the other hand is a yellow-ish tint of a shade of green already far removed from true green.

Methodology

Knowns

For this analysis, assume yellow-green and green-yellow refer to two distinct colors, where the first word is a descriptor of the second:

  • yellow-green is a shade of green with a yellow tint, or according to Wikipedia, a dull medium shade of chartreuse1. Yellow-green is defined by the hex code #9acd32.

  • green-yellow is a shade of yellow with a green tint2. Green-yellow is defined by the hex code #adff2f.

  • For completeness I define green as hex code #00ff00 (true, super bright green) and yellow as hex code #ffff00 (true, super bright yellow).

Tools

I use a few R packages to make this analysis easier.

  • {farver} by Thomas Lin Pedersen provides tools to translate & compare colors in different color spaces.

  • {tidyverse}, of course. I make an effort try out some of the new {dplyr} 1.0 features. I also use some functions from {tidyr}, {purrr}, and {stringr}.

  • {rgl} for 3D plotting and {patchwork}, also by Thomas Lin Pedersen, for organizing charts.

Color spaces

The first challenge is defining & describing the colors. Hexcodes define colors in RGB space, which quantifies the intensity of red, green, and blue light in the color. All 3 channels at max value (ff or 255) will result in white, all 3 set at 0 will result in black. Anywhere in between is a shade of grey. When you start changing the ratio between the 3 channels, you get colors. Our green, #00ff00, only has the green channel turned on, while our yellow #ffff00 has equal parts red and green at full brightness.

Humans are not computers and our eyes see colors slightly differently, so scientists have tried to come up with other ways to define colors. There are many different ways to define color spaces, and for this analysis I’ll focus on two others.

  • HSL defines colors as a hue, saturation, and lightness value. Still not great for human vision but slightly more intuitive for our brains.

  • CIELAB (lab) defines colors on a scale of l = white to black, a = green to red, & b = blue to yellow. This color space is supposed to emulate human color processing more accurately.

The numerical difference

Converting the pair of hex codes for yellow-green and green-yellow into each of the color spaces results in three pairs of 3-dimensional coordinates. These coordinates quantify the differences between the colors.

#Grab yellow-green and green-yellow from my list of colors
gycodes[c("yellow-green", "green-yellow")] %>% 
  unlist() %>% 
  #Decode color converts the 2 hexcodes to a matrix of 2 rows...
  #... and a column for each color channel
  farver::decode_colour(to = "rgb")  %>% 
  as.data.frame() %T>%
  print() %>% 
  mutate(color = row.names(.)) %>% 
  select(color, everything()) %>% 
  mutate(across(c(r, g, b), ~scales::percent(./(r+g+b), accuracy = 2))) %>% 
  knitr::kable(align = c("lccc"))
##                r   g  b
## yellow-green 154 205 50
## green-yellow 173 255 47
color r g b
yellow-green 38% 50% 12%
green-yellow 36% 54% 10%

In the RGB color space the values of the 3 channels are higher for green-yellow, making it the brighter of the two colors. The two colors have similar proportions of each channel, with slight variations. Yellow-green has more equal proportions of R and G… which would make yellow-green (which we define as green) the “yellow” color. That’s odd. Green-yellow, the yellow color, has a higher proportion of green. Maybe this isn’t the best way to compare the colors.

color h s l
yellow-green 80 61 50
green-yellow 84 100 59

In the HSL color space, yellow-green has a lower value for hue, which means the color is closer to yellow than green-yellow. Interestingly, green-yellow is at 100% saturation, meaning it’s more colorful than pale and slightly brighter than yellow-green.

color l a b
yellow-green 77 -38 67
green-yellow 92 -52 82

In the lab color space, green-yellow has more light, is more green than red (and more green than yellow-green), and is more yellow than blue (and more yellow than yellow-green).

The literal difference

The most basic idea in euclidean geometry is that you can draw a line between two points… and we have two points. That line will have a finite lenght, which means it will have a center point. And since our two points are in a color space, that midpoint will have a color associated with it.

So what’s the middle color between yellow-green and green-yellow in each of the 3 spaces?

c("rgb", "hsl", "lab") %>% 
  purrr::map_chr(~{
    gycodes[c("yellow-green", "green-yellow")] %>% 
      unlist() %>% 
      farver::decode_colour(to = .x) %>% 
      #Throwback and keeping it as a matrix
      t() %>% 
      apply(1, mean) %>% 
      matrix(ncol=3)%>% 
      farver::encode_colour(from = .x) 
  }) %>%
  #One of the coolest helper functions in R
  scales::show_col()

Almost the same hex code across each of the three color spaces, and as far as my eyes can tell, each is a very similar shade of yellow-green. (…or a shade of green-yellow?)

The crowd-sourced difference

I posted the RGB version of this color on Twitter and asked my 1,900 followers if they considered this color to be yellow-green or green-yellow. After an overwhelming response3, the results were a statistical tie.

According to my Twitter bubble, the difference between yellow-green and green-yellow is an ambiguous color that is either one of those two, or neither of them.

The relative difference

Another way to understand the difference between yellow-green and green-yellow is to compare them to their two namesake colors: green and yellow.

The starting point again would be to measure the euclidean distance between each color with yellow and green. {farver} makes this easy.

#Convert hex codes to rgb
farver::decode_colour(unlist(gycodes)) %>% 
  #Calculate the euclidean distance between each point
  farver::compare_colour(from_space = "rgb") %>% 
  round()
##              green yellow-green green-yellow yellow
## green            0          169          179    255
## yellow-green     0            0           54    123
## green-yellow     0            0            0     95
## yellow           0            0            0      0

Euclidean distances in RGB are nice because the value is intuitive. On the extreme end, we see the distance between green and yellow is 255, which we know is the toggled on R channel with yellow. We also see that yellow-green and green-yellow are closest to each other. Yellow-green is a distance of 123 from yellow and 169 from green, reaffirming our above conclusion that yellow-green is seems a bit more yellow. However, this measurement confirms that green-yellow is also more of a yellow color.

Just like with there being different ways to define color space, this question is more complicated than just viewing the euclidean distance. There are other color matching methodologies that are designed to better relate to the human perception of color, like the CIE2000 and CMC methods. Let’s see how these colors compare across multiple color spaces and methodologies.

Click here for code & too many numbers

gy_distances = function(space, method){
  farver::decode_colour(unlist(gycodes)[c("green-yellow", "yellow-green")]) %>% 
    farver::compare_colour(to = farver::decode_colour(unlist(gycodes)[c("green", "yellow")]),
                           from_space = space,
                           method = method) %>% 
    as.data.frame() %>% 
    mutate(target_color = rownames(.),
           space = space, 
           method = method) %>% 
    select(space, method, target_color, everything())
}
crossing(space = c("rgb", "lab", "hsl"), 
         method = c("euclidean", "cie2000", "cmc")) %>% 
  purrr::pmap_dfr(gy_distances) %>% 
  pivot_longer(c(green, yellow), names_to = "matched_color", values_to = "distance") %>% 
  mutate(true_color = str_extract(target_color, "green$|yellow$"),
         match = matched_color == true_color) %>% 
  select(target_color, true_color, space, method, matched_color, distance, match) -> full_yq_dist
full_yq_dist %>% 
  select(-match, -true_color) %>% 
  mutate(target_color = str_remove_all(target_color, "een|llow")) %>% 
  pivot_wider(names_from = c(target_color, matched_color), names_sep = " | ",
              values_from = distance) %>% 
  knitr::kable(digits = 0, label = "Distance methods: Target color | Match color")
space method gr-ye | green gr-ye | yellow ye-gr | green ye-gr | yellow
hsl cie2000 82 82 88 88
hsl cmc 37 37 41 41
hsl euclidean 320 320 293 293
lab cie2000 100 8 101 11
lab cmc 34 1 35 10
lab euclidean 3406 117 1488 1859
rgb cie2000 9 14 15 18
rgb cmc 7 2 15 10
rgb euclidean 179 95 169 123

This table shows the distance between the each yellow-green & green-yellow with green & yellow across the different color spaces. I probably shouldn’t average these different color space distances together, but if I do…

#Average distance... sketchy math tho
full_yq_dist %>% 
  group_by(target_color, matched_color) %>% 
  summarize(across(distance, mean), .groups = "drop") %>% 
  knitr::kable(digits = 0)
target_color matched_color distance
green-yellow green 464
green-yellow yellow 75
yellow-green green 249
yellow-green yellow 273

… we end up with this table. On average, green-yellow is much closer to yellow ✅ and yellow-green is slightly closer to green ✅.

full_yq_dist %>% 
  group_by(target_color, method, space) %>% 
  slice_min(distance, n=1) %>% #Fancy new replacement for top_n()
  ungroup() %>% 
  select(-distance, -match) %>% 
  #Pivot over space, but allow for scenario where color is equidistant to both Y and G
  pivot_wider(names_from = "space", values_from = "matched_color", 
              values_fn = function(x){if(length(x)==2){"both"}else{x}}) %>% 
  knitr::kable()
target_color true_color method hsl lab rgb
green-yellow yellow cie2000 both yellow green
green-yellow yellow cmc both yellow yellow
green-yellow yellow euclidean both yellow yellow
yellow-green green cie2000 both yellow green
yellow-green green cmc both yellow yellow
yellow-green green euclidean both green yellow

None of the three distance methods are able to classify both yellow-green and green-yellow as their base colors in each of the 3 color spaces. HSL space is interesting as yellow-green and green-yellow are the same distance each from yellow and green in each of the methods.

The combination of lab space with euclidean matching is the only one to correctly classify both colors, so let’s explore that one.

The plotted difference

Since the data is 3D, I played around with making a 3D scatter plot in {rgl}, though it’s a bit ugly. Additionally, I plot each of the 3 dimensions in pairs, suppressing the 3rd. Using {patchwork}, I can combine all four plots into one graphic.

Click here for code

plot_color_space_single = function(space, this_x, this_y){
  farver::decode_colour(unlist(gycodes), to = space) %>% 
    as.data.frame() %>% 
    mutate(color_name = row.names(.),
           hex = unlist(gycodes)) -> dc_space
  
  dc_space %>% 
    ggplot(aes(x=.data[[this_x]], y=.data[[this_y]])) +
    geom_line(data = dc_space %>% filter(color_name %in% c("green", "yellow")),
              color = "#333333", size = 2) +
    geom_point(color = "black", size = 4) +
    geom_point(aes(color = hex), size = 3) +
    scale_color_identity() +
    ggrepel::geom_text_repel(aes(label = color_name), vjust=0, nudge_y = 3)
}
#will work for at least lab, hsl, rgb
plot_color_space = function(space, image){
  strsplit(space, "") %>% unlist() -> space_char
  
  image_plot <- ggplot() +
    ggpubr::background_image(png::readPNG(image))
  
  list(
    plot_color_space_single(space, space_char[1], space_char[2]),
    plot_color_space_single(space, space_char[3], space_char[2]),
    image_plot,
    plot_color_space_single(space, space_char[3], space_char[1])
  ) %>% 
    patchwork::wrap_plots(design = "AB
                                    CD")
}
plot_color_space("lab", "Colors/lab_3d.png")

While we know from the distance calculations that yellow-green and green-yellow are closer green & yellow, respectively, this plot shows us something new. On each of the three slices, green-yellow is much closer to being the line between yellow and green than yellow-green.

Conclusion

So what’s the difference between yellow-green and green-yellow?

Green-yellow is closer to being a true combination of the colors green and yellow. In green-yellow, the ‘green’ is not merely an adverb4, but an equal-ish part of the unique compound color.

Yellow-green is a yellow tint of an unidentified shade of not-true green. The ‘yellow’ here is simply and adverb of ‘green’.

Your move, Wikipedia.


  1. Also according to the Wikipedia entry, it’s also the color of French goose droppings. 🐦↩︎

  2. To add to the confusion, Wikipedia also list this shade of yellow on it’s shades of green entry.↩︎

  3. 51 votes. Less than 3% of my followers. Gonna have to assume 97% of my followers are bots. 🤖↩︎

  4. Full disclosure, I don’t know what I’m talking about when it comes to parts of speech.↩︎

Avatar
Ryan Timpe
Data Science | Economics

Related