## 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 chartreuse

^{1}. Yellow-green is defined by the hex code #9acd32.green-yellow is a shade of yellow with a green tint

^{2}. 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 response^{3}, 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.

VERY IMPORTANT POLL

— Ryan Timpe 🏳️🌈🦕 (@ryantimpe) July 18, 2020

## 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 adverb^{4}, 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.

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

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

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

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