Wprowadzenie


Kolor na wykresie to nie ozdobnik, tylko kanał informacji – tak samo jak położenie punktu na osi czy długość słupka. Dobrze dobrany kolor pomaga odbiorcy zobaczyć strukturę danych w ułamku sekundy, źle dobrany potrafi ją ukryć albo wręcz zasugerować coś, czego w danych nie ma.



Pakiety

# install.packages(c("viridis",
#                    "RColorBrewer",
#                    "wesanderson", 
#                    "ggsci",
#                    "ggplot2",
#                    "patchwork",
#                    "scales", 
#                    "colorspace", 
#                    "scico",
#                    "MetBrewer",
#                    "paletteer"))
library(ggplot2)
library(viridis)
library(RColorBrewer)
library(wesanderson)
library(ggsci)
library(patchwork)   # łączenie kilku wykresów w jeden
library(scales)      # show_col(), alpha()
library(colorspace)  # przestrzeń HCL, symulacja daltonizmu
library(scico)       # naukowe mapy kolorów F. Crameriego
library(MetBrewer)   # palety z dzieł sztuki (Metropolitan Museum of Art)
library(paletteer)   # jeden interfejs do ~2500 palet z wielu pakietów




Ramka danych

# Przykładowe dane
set.seed(123)
df <- data.frame(
  kategoria = rep(LETTERS[1:5], each = 20),
  x = rnorm(100),
  y = rnorm(100)
)

head(df)
##   kategoria           x           y
## 1         A -0.56047565 -0.71040656
## 2         A -0.23017749  0.25688371
## 3         A  1.55870831 -0.24669188
## 4         A  0.07050839 -0.34754260
## 5         A  0.12928774 -0.95161857
## 6         A  1.71506499 -0.04502772




Jak R rozumie kolory


Człowiek a kolory

Zanim przejdziemy do kodu, warto przypomnieć, skąd biorą się „trzy liczby”, którymi opisujemy kolor na ekranie.

  • Siatkówka człowieka zawiera trzy rodzaje czopków, najbardziej wrażliwych na fale krótkie (S, ok. 420 nm), średnie (M, ok. 530 nm) i długie (L, ok. 560 nm). Widzenie barw jest więc trójchromatyczne – i dlatego ekrany mieszają tylko trzy składowe: czerwoną, zieloną i niebieską (model RGB).

  • Dalsze etapy przetwarzania działają według teorii procesów przeciwstawnych (Hering): mózg porównuje sygnały w kanałach jasny–ciemny, czerwony–zielony i niebieski–żółty. Dlatego np. nie istnieje „czerwonawa zieleń”.

  • Wzrok jest najbardziej czuły na jasność (luminancję), a jasność różnych barw o tej samej „mocy” na ekranie jest postrzegana bardzo różnie – żółty wydaje się dużo jaśniejszy od niebieskiego.

  • Około 8% mężczyzn i 0,4% kobiet (w populacjach europejskich) ma wrodzone zaburzenia widzenia barw, najczęściej w osi czerwony–zielony (Birch, 2012). W grupie 30 osób (pół na pół) spodziewamy się średnio ok. 1 takiej osoby, a szansa, że jest co najmniej jedna, wynosi ok. 70%.


Źródła: Healy (2018), rozdz. 1 Look at data (fragment o percepcji koloru); Wilke (2019), rozdz. 4 Color scales; Zeileis, Hornik i Murrell (2009) Escaping RGBland; Birch (2012).


Trzy najpopularniejsze sposoby podawania kolorów w R

Sposób Przykład (biały) Przykład (czarny) Zakres wartości Typowe funkcje w R
Nazwa (słownie) "white" "black" 657 nazw (colors()) colors(), col2rgb()
HEX (szesnastkowo) "#FFFFFF" "#000000" 00–FF dla każdej składowej tekst "#RRGGBB" lub "#RRGGBBAA"
RGB rgb(1, 1, 1) lub rgb(255, 255, 255, maxColorValue = 255) rgb(0, 0, 0) domyślnie 0–1, opcjonalnie 0–255 rgb(), col2rgb()


Najważniejsza obserwacja: wszystkie te zapisy prowadzą do tego samego – R wewnętrznie i tak przechowuje kolor jako tekst w formacie HEX. Funkcja rgb() nie zwraca „obiektu koloru”, tylko napis typu "#FF0000".


Nazwy kolorów (słownie)

To najwygodniejszy sposób na start: wpisujemy po prostu "red", "steelblue" czy "tomato".

R zna 657 nazw:

length(colors())
## [1] 657
head(colors(), 20)
##  [1] "white"         "aliceblue"     "antiquewhite"  "antiquewhite1"
##  [5] "antiquewhite2" "antiquewhite3" "antiquewhite4" "aquamarine"   
##  [9] "aquamarine1"   "aquamarine2"   "aquamarine3"   "aquamarine4"  
## [13] "azure"         "azure1"        "azure2"        "azure3"       
## [17] "azure4"        "beige"         "bisque"        "bisque1"


Nazwy można przeszukiwać jak każdy wektor tekstowy:

# wszystkie odcienie, które w nazwie mają "blue"
grep("blue", colors(), value = TRUE)[1:15]
##  [1] "aliceblue"      "blue"           "blue1"          "blue2"         
##  [5] "blue3"          "blue4"          "blueviolet"     "cadetblue"     
##  [9] "cadetblue1"     "cadetblue2"     "cadetblue3"     "cadetblue4"    
## [13] "cornflowerblue" "darkblue"       "darkslateblue"


Funkcja col2rgb() „tłumaczy” nazwę na trzy składowe RGB (w skali 0–255):

col2rgb(c("tomato", "steelblue", "gold"))
##       [,1] [,2] [,3]
## red    255   70  255
## green   99  130  215
## blue    71  180    0


Szybki podgląd wybranych kolorów daje scales::show_col() – pokazuje kafelki wraz z kodem HEX:

show_col(c("tomato", "steelblue", "gold", "orchid", "seagreen", "chocolate"))


Do pokazania losowej próbki nazwanych kolorów przyda się mała funkcja, która dobiera kolor napisu (czarny/biały) do jasności tła. Wagi 0.299, 0.587 i 0.114 to klasyczne współczynniki luminancji – odzwierciedlają to, że oko jest najbardziej czułe na zieleń, a najmniej na błękit.

# Kolor tekstu dopasowany do jasności tła
kontrastowy_tekst <- function(kolory) {
  rgb_mat <- col2rgb(kolory)
  luminancja <- 0.299 * rgb_mat["red", ] + 0.587 * rgb_mat["green", ] + 0.114 * rgb_mat["blue", ]
  ifelse(luminancja > 140, "black", "white")
}

set.seed(2026)
probka <- sample(colors(distinct = TRUE), 24)

kafelki <- data.frame(
  nazwa = probka,
  x = rep(1:6, times = 4),
  y = rep(4:1, each = 6)
)

ggplot(kafelki, 
       aes(x, 
           y, 
           fill = nazwa)) +
  geom_tile(color = "white", 
            linewidth = 1) +
  geom_text(aes(label = nazwa), 
            color = kontrastowy_tekst(kafelki$nazwa),
            size = 3) +
  scale_fill_identity() +   # kolor brany wprost z kolumny 'nazwa'
  labs(title = "Losowe 24 nazwane kolory w R") +
  theme_void()


Ciekawostka: istnieją też całe skale szarości gray0 … gray100 (lub grey0 … grey100 – R akceptuje obie pisownie):

show_col(paste0("gray", seq(0, 100, by = 10)))



Nazwy w R ≠ nazwy w HTML/CSS

W HTML i CSS również używa się nazw kolorów (red, blue, green…), ale R korzysta z innej listy – pochodzącej z systemu okien X11 (plik rgb.txt z lat 80.). Większość nazw się pokrywa, ale nie wszystkie oznaczają ten sam kolor:

nazwy <- c("gray", "green", "maroon", "purple")
hex_R   <- rgb(t(col2rgb(nazwy)), maxColorValue = 255)
hex_CSS <- c("#808080", "#008000", "#800000", "#800080")   # wartości ze specyfikacji CSS

porownanie <- data.frame(
  nazwa  = rep(nazwy, 2),
  zrodlo = rep(c("R (X11)", "HTML / CSS"), each = 4),
  hex    = c(hex_R, hex_CSS)
)

ggplot(porownanie, aes(nazwa, zrodlo, fill = hex)) +
  geom_tile(color = "white", linewidth = 2) +
  geom_text(aes(label = hex), color = kontrastowy_tekst(porownanie$hex)) +
  scale_fill_identity() +
  labs(title = "Ta sama nazwa, inny kolor", x = NULL, y = NULL) +
  theme_minimal(base_size = 13)

Wniosek: nazwy są świetne do szybkich szkiców, ale gdy kolor ma być dokładnie taki sam w R, na stronie WWW i w prezentacji – używaj kodów HEX.

Źródła: Tian Zheng, Colors in R – klasyczna ściąga ze wszystkimi nazwami kolorów w R; hasło X11 color names w Wikipedii (porównanie z kolorami webowymi).



HEX (zapis szesnastkowy)


Kolor zapisujemy jako "#RRGGBB": po znaku # następują trzy pary znaków – intensywność czerwonego (RR), zielonego (GG) i niebieskiego (BB).

W systemie szesnastkowym używamy cyfr 0–9 oraz liter A–F (A = 10, …, F = 15). Para znaków to liczba od 00 (= 0) do FF (= 15·16 + 15 = 255). Daje to 256 × 256 × 256 ≈ 16,7 mln kolorów.

strtoi("FF", base = 16)   # szesnastkowo -> dziesiętnie
## [1] 255
strtoi("80", base = 16)   # 8 * 16 = 128, czyli "połowa"
## [1] 128
sprintf("%02X", 200)      # dziesiętnie -> szesnastkowo
## [1] "C8"
col2rgb("#FF8000")        # pomarańczowy: dużo czerwieni, połowa zieleni, zero niebieskiego
##       [,1]
## red    255
## green  128
## blue     0


Możemy napisać własną funkcję składającą kod HEX – to dobre ćwiczenie, żeby zrozumieć, że HEX to tylko inny zapis RGB:

hex_z_rgb <- function(r, g, b) sprintf("#%02X%02X%02X", r, g, b)

hex_z_rgb(255, 128, 0)
## [1] "#FF8000"
rgb(255, 128, 0, maxColorValue = 255)   # wbudowana funkcja daje to samo
## [1] "#FF8000"


Kilka praktycznych uwag:

  • Wielkość liter nie ma znaczenia: "#ff8000" i "#FF8000" to ten sam kolor.

  • Krótki zapis "#FFF" znany z CSS w R (co najmniej do wersji 4.3) nie działa – zawsze podawaj 6 znaków:

try(col2rgb("#FFF"))
##       [,1]
## red    255
## green  255
## blue   255


  • Można dopisać czwartą parę – kanał przezroczystości (alfa): "#RRGGBBAA", gdzie 00 to kolor całkowicie przezroczysty, a FF – w pełni kryjący. Np. "#FF000080" to czerwień w ok. 50% przezroczysta (więcej w punkcie 4).
col2rgb("#FF000080", alpha = TRUE)
##       [,1]
## red    255
## green    0
## blue     0
## alpha  128



RGB (Red, Green, Blue)


Model RGB jest addytywny: mieszamy światło, więc zero wszystkich składowych daje czerń, a maksimum – biel. W R kolor RGB tworzy funkcja rgb().

Pułapka: w CSS piszemy rgb(255, 255, 255), ale w R funkcja rgb() domyślnie przyjmuje wartości z przedziału 0–1:

rgb(1, 1, 1)        # biały
## [1] "#FFFFFF"
rgb(0, 0, 0)        # czarny
## [1] "#000000"
rgb(1, 0, 0)        # czerwony
## [1] "#FF0000"
try(rgb(255, 0, 0)) # błąd! wartość spoza [0, 1]
## Error in rgb(255, 0, 0) : color intensity 255, not in [0,1]
rgb(255, 0, 0, maxColorValue = 255)   # tak podajemy wartości 0–255
## [1] "#FF0000"

Zwróć uwagę, że rgb() zwraca napis HEX – to dlatego wynik można od razu przekazać do dowolnej funkcji graficznej.


Addytywne mieszanie najłatwiej zrozumieć, oglądając je. Poniżej siatka kolorów, w której zmieniamy ilość czerwieni (oś X) i zieleni (oś Y), a niebieski ustawiamy na trzech poziomach:

siatka <- expand.grid(
  r = seq(0, 1, by = 0.1),
  g = seq(0, 1, by = 0.1),
  b = c(0, 0.5, 1)
)
siatka$kolor <- rgb(siatka$r, siatka$g, siatka$b)

ggplot(siatka, aes(r, 
                   g, 
                   fill = kolor)) +
  geom_tile() +
  scale_fill_identity() +
  facet_wrap(~ b, 
             labeller = label_both) +
  coord_fixed() +
  labs(title = "Mieszanie addytywne w modelu RGB",
       x = "czerwony (R)",
       y = "zielony (G)") +
  theme_minimal()

Czerwony + zielony daje żółty (prawy górny róg pierwszego panelu), a wszystkie trzy na maksimum – biel. To dokładnie to, co dzieje się w pikselach Twojego ekranu.


Funkcja odwrotna, col2rgb(), zamienia dowolny kolor (nazwę lub HEX) na macierz składowych 0–255.



Przezroczystość (kanał alfa)


Przezroczystość przydaje się przede wszystkim przy nakładaniu się punktów (ang. overplotting) – gdy punktów jest dużo, pełne kolory zlewają się w jedną plamę, a przezroczyste pokazują, gdzie danych jest najwięcej.

set.seed(1)
duzo <- data.frame(x = rnorm(5000))
duzo$y <- 0.6 * duzo$x + rnorm(5000, sd = 0.8)

p_pelne <- ggplot(duzo, aes(x, y)) +
  geom_point(color = "steelblue") +
  labs(title = "alpha = 1") +
  theme_minimal()

p_przezr <- ggplot(duzo, aes(x, y)) +
  geom_point(color = "steelblue", alpha = 0.1) +
  labs(title = "alpha = 0.1") +
  theme_minimal()

p_pelne | p_przezr


Przezroczystość można podać na kilka sposobów:

rgb(70, 130, 180, 
    alpha = 128, 
    maxColorValue = 255) # w funkcji rgb()
## [1] "#4682B480"
adjustcolor("steelblue", alpha.f = 0.5) # bazowy R
## [1] "#4682B480"
scales::alpha("steelblue", 0.5) # pakiet scales
## [1] "#4682B480"

W ggplot2 najprościej użyć argumentu alpha = w warstwie (geom_point(alpha = 0.1)).



Inne przestrzenie kolorów: HSV i HCL

RGB opisuje, jak ekran wytwarza kolor, ale nie to, jak człowiek go postrzega. Dlatego istnieją przestrzenie bliższe ludzkiej intuicji:

  • HSV (Hue, Saturation, Value) – odcień, nasycenie, „moc”. Funkcja hsv(h, s, v), wszystkie wartości 0–1. Intuicyjna, ale nadal techniczna.
  • HCL (Hue, Chroma, Luminance) – odcień (w stopniach 0–360), nasycenie i jasność postrzegana przez człowieka. Funkcja hcl(h, c, l). To przestrzeń percepcyjnie jednorodna: ta sama zmiana liczby oznacza podobną zmianę odczuwaną przez oko.
hsv(0, 1, 1) # "#FF0000" – czerwony w HSV
## [1] "#FF0000"
hcl(h = 0, c = 80, l = 60) # "#E16A86" – czerwień o kontrolowanej jasności (HCL)
## [1] "#E16A86"


Dlaczego to ważne? Porównajmy klasyczną „tęczę” (rainbow(), tworzoną w HSV) z paletą HCL – a potem obie przekształćmy do odcieni szarości, czyli zostawmy tylko informację o jasności:

swatchplot(list(
  "rainbow()"                    = rainbow(7),
  "rainbow() – w szarości"       = desaturate(rainbow(7)),
  "qualitative_hcl()"            = qualitative_hcl(7),
  "qualitative_hcl() – w szarości" = desaturate(qualitative_hcl(7))
))

W tęczy żółty i błękit „wybijają się” jasnością, a niebieski jest dużo ciemniejszy – oko odczyta to jako ważniejsze/mniej ważne kategorie, choć dane tego nie mówią. W palecie HCL wszystkie kolory mają podobną jasność, więc żaden nie dominuje.


Funkcja colorspace::specplot() pokazuje, jak w palecie zmieniają się składowe – zwróć uwagę na krzywą jasności (Luminance): w tęczy skacze w górę i w dół, w viridis rośnie równomiernie.

specplot(rainbow(100, end = 0.7), 
         main = "rainbow()")

specplot(hcl.colors(100, "viridis"),
         main = "hcl.colors('viridis')")



Na koniec – ten sam kolor w różnych zapisach:

(kol <- "steelblue")
## [1] "steelblue"


col2rgb(kol) # RGB 0–255
##       [,1]
## red     70
## green  130
## blue   180
rgb(t(col2rgb(kol)), 
    maxColorValue = 255) # HEX
## [1] "#4682B4"
rgb2hsv(col2rgb(kol)) # HSV
##        [,1]
## h 0.5757576
## s 0.6111111
## v 0.7058824
as(hex2RGB("#4682B4"), 
   "polarLUV") # HCL (L, C, H)
##             L        C        H
## [1,] 52.46539 54.48801 242.5573

Źródła: Zeileis, Hornik i Murrell (2009) Escaping RGBland – artykuł, który spopularyzował przestrzeń HCL w R; Zeileis i in. (2020) Journal of Statistical Software colorspace; Ihaka (2003) Colour for presentation graphics.


Kolory w ggplot2: najczęstsza pułapka i rodziny skal


Pułapka: w ggplot2 jest różnica między kolorem zmapowanym na zmienną (wewnątrz aes()) a kolorem ustawionym na stałe (poza aes()):

p_zle <- ggplot(df, 
                aes(x, 
                    y, 
                    color = "blue")) +
  geom_point(size = 2) +
  labs(title = 'aes(color = "blue")', 
       subtitle = "Źle: 'blue' potraktowane jak kategoria") +
  theme_minimal()

p_dobrze <- ggplot(df, 
                   aes(x, 
                       y)) +
  geom_point(size = 2, 
             color = "blue") +
  labs(title = 'geom_point(color = "blue")', 
       subtitle = "Dobrze: kolor ustawiony na stałe") +
  theme_minimal()

p_zle | p_dobrze

Zasada: w aes() piszemy nazwy zmiennych, poza aes() – konkretne wartości.


Druga ważna rzecz: color (colour) dotyczy punktów, linii i obramowań, a fill – wypełnienia (słupków, pudełek, kafelków). Każdej z tych estetyk odpowiada rodzina funkcji scale_color_*() i scale_fill_*():


Funkcja Typ zmiennej Do czego
scale_*_manual(values = ...) dyskretna ręcznie wybrane kolory
scale_*_identity() dowolna kolor zapisany wprost w danych
scale_*_gradient(low, high) ciągła gradient między dwoma kolorami
scale_*_gradient2(low, mid, high, midpoint) ciągła gradient dywergentny (z punktem środkowym)
scale_*_gradientn(colours = ...) ciągła gradient przez wiele kolorów
scale_*_brewer() / _distiller() / _fermenter() dyskretna / ciągła / przedziały palety ColorBrewer
scale_*_viridis_d() / _c() / _b() dyskretna / ciągła / przedziały palety viridis

W scale_*_manual() warto podawać kolory jako wektor nazwany – wtedy każda kategoria dostaje „swój” kolor niezależnie od kolejności poziomów (przydatne, gdy ta sama grupa występuje na wielu wykresach):

moje_kolory <- c(A = "#E69F00", 
                 B = "#56B4E9", 
                 C = "#009E73", 
                 D = "#F0E442", 
                 E = "#0072B2")

p_manual <- ggplot(df,
                   aes(x, y, 
                       color = kategoria)) +
  geom_point(size = 3) +
  scale_color_manual(values = moje_kolory) +
  labs(title = "scale_color_manual()") +
  theme_minimal()

p_grad2 <- ggplot(df, 
                  aes(x, y, 
                      color = y)) +
  geom_point(size = 3) +
  scale_color_gradient2(low = "#2166AC", 
                        mid = "grey90", 
                        high = "#B2182B", 
                        midpoint = 0) +
  labs(title = "scale_color_gradient2()") +
  theme_minimal()

p_manual | p_grad2

Źródła: Wickham, Navarro i Pedersen, ggplot2: Elegant Graphics for Data Analysis (3. wyd.), rozdział o skalach kolorów; dokumentacja ggplot2 (?scale_colour_manual).



Palety wbudowane w R (bez dodatkowych pakietów)

Bazowy R ma całkiem bogaty zestaw palet. Od wersji R 4.0.0 domyślna paleta (palette()) została zmieniona na bardziej wyważoną, a dodano funkcje palette.colors() i hcl.colors():

palette()        # obecna domyślna paleta
## [1] "black"   "#DF536B" "#61D04F" "#2297E6" "#28E2E5" "#CD0BBC" "#F5C710"
## [8] "gray62"
palette.pals()   # palety jakościowe dostępne w palette.colors()
##  [1] "R3"              "R4"              "ggplot2"         "Okabe-Ito"      
##  [5] "Accent"          "Dark 2"          "Paired"          "Pastel 1"       
##  [9] "Pastel 2"        "Set 1"           "Set 2"           "Set 3"          
## [13] "Tableau 10"      "Classic Tableau" "Polychrome 36"   "Alphabet"


Szczególnie warta zapamiętania jest paleta Okabe-Ito – zaprojektowana tak, by była czytelna dla osób z zaburzeniami widzenia barw:

show_col(palette.colors(palette = "Okabe-Ito"))


hcl.colors() daje dostęp do ponad 100 palet HCL – w tym wersji "Viridis", "Cividis", a nawet "Zissou 1" (inspirowanej… filmem Wesa Andersona):

head(hcl.pals(type = "sequential"), 12)
##  [1] "Grays"       "Light Grays" "Blues 2"     "Blues 3"     "Purples 2"  
##  [6] "Purples 3"   "Reds 2"      "Reds 3"      "Greens 2"    "Greens 3"   
## [11] "Oslo"        "Purple-Blue"
show_col(hcl.colors(8, "Zissou 1"))


Własny gradient z dowolnych kolorów tworzy colorRampPalette() – zwraca funkcję, która generuje tyle kolorów, ile potrzebujemy:

bialo_czerwona <- colorRampPalette(c("white", "#DC143C"))
show_col(bialo_czerwona(7))


Te same palety działają oczywiście w grafice bazowej:

par(mfrow = c(1, 2))
barplot(table(df$kategoria), col = hcl.colors(5, "Zissou 1"),
        main = "barplot() + hcl.colors()")
plot(df$x, df$y, pch = 19,
     col = palette.colors(6, "Okabe-Ito")[-1][factor(df$kategoria)],
     main = "plot() + Okabe-Ito", xlab = "x", ylab = "y")

par(mfrow = c(1, 1))

Źródła: Zeileis i Murrell (2019), wpis na blogu R Core A New palette() for R, https://blog.r-project.org/2019/11/21/a-new-palette-for-r/; Zeileis i Murrell (2023) Coloring in R’s Blind Spot (The R Journal); Okabe i Ito (2008) Color Universal Design.




Gotowe palety z pakietów

Trzy typy palet


Zanim sięgniemy po konkretne pakiety – najważniejsza decyzja to typ palety, który musi pasować do typu danych. Ten podział zaproponowała kartografka Cynthia Brewer:

  • sekwencyjna – dla danych uporządkowanych od małych do dużych (np. czas reakcji, wiek),
  • dywergentna – dla danych z naturalnym punktem środkowym (np. korelacja od −1 do 1, różnica względem średniej),
  • jakościowa – dla kategorii bez porządku (np. grupa eksperymentalna, kierunek studiów).
swatchplot(list(
  "Sekwencyjna (Blues)" = brewer.pal(7, "Blues"),
  "Dywergentna (RdBu)"  = brewer.pal(7, "RdBu"),
  "Jakościowa (Set2)"   = brewer.pal(7, "Set2")
))




{viridis} - palety przyjazne dla osób z zaburzeniami widzenia kolorów

Palety viridis powstały w 2015 roku. Od 2017 jako nowa domyślna mapa kolorów biblioteki matplotlib (Python), w odpowiedzi na krytykę „tęczowej” mapy jet. Są percepcyjnie jednorodne, zachowują porządek po konwersji do skali szarości i są czytelne przy najczęstszych zaburzeniach widzenia barw. Wariant cividis zoptymalizowano specjalnie pod kątem osób z deuteranopią i protanopią.

Źródła: Smith i van der Walt (2015), wystąpienie A Better Default Colormap for Matplotlib (SciPy 2015); winieta pakietu Introduction to the viridis color maps (Garnier i in.); Nuñez, Anderton i Renslow (2018) – paleta cividis.


# a) scale_color_viridis_d() - dla zmiennych dyskretnych
ggplot(df, 
       aes(x = x, 
           y = y, 
           color = kategoria)) +
  geom_point(size = 3) +
  scale_color_viridis_d() +  # domyślnie opcja "viridis"
  labs(title = "Viridis - zmienna dyskretna") +
  theme_minimal()

# b) scale_fill_viridis_c() - dla zmiennych ciągłych
# kolor pudełka = mediana w grupie (after_stat() sięga po wartości policzone przez geom_boxplot)
ggplot(df,
       aes(x = kategoria, 
           y = y, 
           fill = after_stat(middle))) +
  geom_boxplot() +
  scale_fill_viridis_c(option = "plasma") +  # opcje: viridis, magma, plasma, inferno, cividis
  labs(title = "Viridis - zmienna ciągła (plasma)",
       fill = "mediana") +
  theme_minimal()

# c) Różne opcje viridis
p1 <- ggplot(df, 
             aes(x = x, 
                 y = y,
                 color = kategoria)) +
  geom_point(size = 3) +
  scale_color_viridis_d(option = "magma") +
  labs(title = "Magma") +
  theme_minimal()

p2 <- ggplot(df, 
             aes(x = x, 
                 y = y, 
                 color = kategoria)) +
  geom_point(size = 3) +
  scale_color_viridis_d(option = "inferno") +
  labs(title = "Inferno") +
  theme_minimal()

p1 | p2   # wyświetlenie obu wykresów obok siebie (patchwork)




{RColorBrewer} - klasyczne palety kolorystyczne

Palety ColorBrewer zaprojektowała Cynthia Brewer (Penn State) z myślą o mapach tematycznych. Każda paleta była testowana pod kątem czytelności, a strona colorbrewer2.org pozwala filtrować palety „bezpieczne” dla osób z daltonizmem, do druku i do kserowania. W ggplot2 palety te są wbudowane: scale_*_brewer() (dane dyskretne), scale_*_distiller() (ciągłe) i scale_*_fermenter() (ciągłe pocięte na przedziały).

Źródła: Harrower i Brewer (2003) ColorBrewer.org: An Online Tool for Selecting Colour Schemes for Maps; strona colorbrewer2.org.


# a) Palety sekwencyjne (dla danych uporządkowanych)
ggplot(df, 
       aes(x = kategoria, 
           fill = kategoria)) +
  geom_bar() +
  scale_fill_brewer(palette = "Blues") +
  labs(title = "RColorBrewer - paleta Blues (sekwencyjna)") +
  theme_minimal()

# b) Palety dywergentne (dla danych z punktem centralnym)
ggplot(df, 
       aes(x = x,
           y = y,
           color = y)) +
  geom_point(size = 3) +
  scale_color_distiller(palette = "RdYlBu") +
  labs(title = "RColorBrewer - paleta RdYlBu (dywergentna)") +
  theme_minimal()

# c) Palety jakościowe (dla kategorii)
ggplot(df, 
       aes(x = x,
           y = y, 
           color = kategoria)) +
  geom_point(size = 3) +
  scale_color_brewer(palette = "Set2") +
  labs(title = "RColorBrewer - paleta Set2 (jakościowa)") +
  theme_minimal()

# Podgląd dostępnych palet
display.brewer.all()

Tylko palety przyjazne dla osób z zaburzeniami widzenia barw:

display.brewer.all(colorblindFriendly = TRUE)




{wesanderson} - palety inspirowane filmami Wesa Andersona

Pakiet Karthika Rama powstał z inspiracji blogiem Wes Anderson Palettes na Tumblrze, gdzie autorzy wycinali palety z kadrów filmów. To przykład palet estetycznych, a nie projektowanych pod kątem percepcji – świetnie wyglądają w prezentacji, ale przed publikacją warto sprawdzić je pod kątem czytelności.

Źródła: repozytorium github.com/karthik/wesanderson; blog wesandersonpalettes.tumblr.com.


# a) Podstawowe użycie - sprawdzamy liczbę dostępnych kolorów
ggplot(df, 
       aes(x = x,
           y = y, 
           color = kategoria)) +
  geom_point(size = 3) +
  scale_color_manual(values = wes_palette("Darjeeling1",
                                          n = 5, 
                                          type = "continuous")) +
  labs(title = "Wes Anderson - Darjeeling1") +
  theme_minimal()

# b) Różne palety
ggplot(df, 
       aes(x = kategoria, 
           fill = kategoria)) +
  geom_bar() +
  scale_fill_manual(values = wes_palette("GrandBudapest1",
                                         n = 5, 
                                         type = "continuous")) +
  labs(title = "Wes Anderson - Grand Budapest Hotel") +
  theme_minimal()

# c) Paleta z większą liczbą kolorów (interpolacja)
# Niektóre palety mają mniej kolorów - trzeba użyć type = "continuous" do interpolacji
ggplot(df, 
       aes(x = x,
           y = y, 
           color = kategoria)) +
  geom_point(size = 3) +
  scale_color_manual(values = wes_palette("Zissou1", 
                                          n = 5, 
                                          type = "continuous")) +
  labs(title = "Wes Anderson - Zissou (interpolacja)") +
  theme_minimal()

Dostępne palety:

  • “BottleRocket1”,
  • “BottleRocket2”,
  • “Rushmore1”,
  • “Royal1”,
  • “Royal2”,
  • “Zissou1”,
  • “Darjeeling1”,
  • “Darjeeling2”,
  • “Chevalier1”,
  • “FantasticFox1”,
  • “Moonrise1”,
  • “Moonrise2”,
  • “Moonrise3”,
  • “Cavalcanti1”,
  • “GrandBudapest1”,
  • “GrandBudapest2”,
  • “IsleofDogs1”,
  • “IsleofDogs2”

Nowsze wersje pakietu dodają kolejne palety (np. z The French Dispatch i Asteroid City) – aktualną listę zawsze sprawdzisz poleceniem:

names(wes_palettes)
##  [1] "BottleRocket1"     "BottleRocket2"     "Rushmore1"        
##  [4] "Rushmore"          "Royal1"            "Royal2"           
##  [7] "Zissou1"           "Zissou1Continuous" "Darjeeling1"      
## [10] "Darjeeling2"       "Chevalier1"        "FantasticFox1"    
## [13] "Moonrise1"         "Moonrise2"         "Moonrise3"        
## [16] "Cavalcanti1"       "GrandBudapest1"    "GrandBudapest2"   
## [19] "IsleofDogs1"       "IsleofDogs2"       "FrenchDispatch"   
## [22] "AsteroidCity1"     "AsteroidCity2"     "AsteroidCity3"




{ggsci} - palety z czasopism naukowych i science fiction

Pakiet Nana Xiao zbiera palety inspirowane stylem graficznym czasopism naukowych (Nature, Science, Lancet, NEJM…) oraz popkultury. Uwaga: to nie są oficjalne wymagania redakcji – raczej sposób, by wykres „wyglądał jak z Nature”.

Źródło: dokumentacja pakietu nanx.me/ggsci.

# a) Nature Publishing Group
ggplot(df, 
       aes(x = x, 
           y = y, 
           color = kategoria)) +
  geom_point(size = 3) +
  scale_color_npg() +
  labs(title = "ggsci - paleta NPG (Nature)") +
  theme_minimal()

# b) AAAS (Science)
ggplot(df, 
       aes(x = kategoria, 
           fill = kategoria)) +
  geom_bar() +
  scale_fill_aaas() +
  labs(title = "ggsci - paleta AAAS (Science)") +
  theme_minimal()

# c) Lancet
ggplot(df,
       aes(x = x, 
           y = y, 
           color = kategoria)) +
  geom_point(size = 3) +
  scale_color_lancet() +
  labs(title = "ggsci - paleta Lancet") +
  theme_minimal()

# d) Star Trek
ggplot(df, 
       aes(x = x, 
           y = y, 
           color = kategoria)) +
  geom_point(size = 3) +
  scale_color_startrek() +
  labs(title = "ggsci - paleta Star Trek") +
  theme_minimal()

# e) Rick and Morty
ggplot(df, 
       aes(x = kategoria, 
           fill = kategoria)) +
  geom_bar() +
  scale_fill_rickandmorty() +
  labs(title = "ggsci - paleta Rick and Morty") +
  theme_minimal()


Inne dostępne palety:

  • scale_color_jco() (Journal of Clinical Oncology),
  • scale_color_nejm() (New England Journal of Medicine),
  • scale_color_gsea() (GSEA GenePattern),
  • scale_color_d3(), scale_color_igv(), scale_color_simpsons(), etc.




Wszystkie cztery palety na jednym wykresie


p_viridis <- ggplot(df, 
                    aes(x = x, 
                        y = y, 
                        color = kategoria)) +
  geom_point(size = 2) +
  scale_color_viridis_d() +
  labs(title = "viridis") +
  theme_minimal() +
  theme(legend.position = "none")

p_brewer <- ggplot(df, 
                   aes(x = x, 
                       y = y, 
                      color = kategoria)) +
  geom_point(size = 2) +
  scale_color_brewer(palette = "Set2") +
  labs(title = "RColorBrewer") +
  theme_minimal() +
  theme(legend.position = "none")

p_wes <- ggplot(df, 
                aes(x = x,
                    y = y, 
                    color = kategoria)) +
  geom_point(size = 2) +
  scale_color_manual(values = wes_palette("Darjeeling1", 
                                          n = 5, 
                                          type = "continuous")) +
  labs(title = "wesanderson") +
  theme_minimal() +
  theme(legend.position = "none")

p_ggsci <- ggplot(df, 
                  aes(x = x, 
                      y = y, 
                      color = kategoria)) +
  geom_point(size = 2) +
  scale_color_npg() +
  labs(title = "ggsci") +
  theme_minimal() +
  theme(legend.position = "none")

(p_viridis | p_brewer) / (p_wes | p_ggsci)




Ciekawe zastosowania i pakiety

Dlaczego nie „tęcza”? Przykład z wulkanem


Tęczowa mapa kolorów (w Matlabie i dawnym matplotlib znana jako jet) przez lata była domyślna w wielu programach. Problem: jasność zmienia się w niej nieregularnie, więc oko widzi „granice” i „pasma”, których w danych nie ma, a ciągła zmiana bywa ukryta. Zobaczmy to na wbudowanym zbiorze volcano (mapa wysokości wulkanu Maungawhau w Auckland):

wulkan <- expand.grid(x = 1:nrow(volcano), 
                      y = 1:ncol(volcano))
wulkan$wysokosc <- as.vector(volcano)

rysuj_wulkan <- function(kolory, tytul) {
  ggplot(wulkan, aes(x, y, fill = wysokosc)) +
    geom_raster() +
    scale_fill_gradientn(colours = kolory) +
    coord_fixed() +
    labs(title = tytul) +
    theme_void() +
    theme(legend.position = "none")
}

(rysuj_wulkan(rainbow(100, end = 0.7), "Tęcza") |
 rysuj_wulkan(desaturate(rainbow(100, end = 0.7)), "Tęcza – w szarości")) /
(rysuj_wulkan(viridis(100), "viridis") |
 rysuj_wulkan(desaturate(viridis(100)), "viridis – w szarości"))


W wersji tęczowej po przejściu na szarość nie wiadomo już, gdzie jest szczyt – jasne pasmo żółci i błękitu leży w środku skali. Viridis w skali szarości nadal czytelnie pokazuje „niżej–wyżej”.


Liu i Heer (2018) sprawdzili to eksperymentalnie: badani oceniali, który z dwóch kolorów leży bliżej trzeciego na skali. Tęcza wypadła słabo (wolniej i mniej trafnie), a viridis dawał konsekwentnie niski odsetek błędów.


Źródła: Borland i Taylor (2007) Rainbow Color Map (Still) Considered Harmful; Liu i Heer (2018) Somewhere Over the Rainbow: An Empirical Assessment of Quantitative Colormaps; Crameri, Shephard i Heron (2020) The misuse of colour in science communication.




Jak Twój wykres widzi osoba z daltonizmem?

Pakiet colorspace ma funkcje symulujące najczęstsze zaburzenia widzenia barw: deutan() (deuteranopia – brak/osłabienie czopków M), protan() (protanopia – czopki L) i tritan() (tritanopia – czopki S). Symulacja opiera się na fizjologicznym modelu Machado, Oliveiry i Fernandesa (2009).

klasyczna <- brewer.pal(5, "Set1")             # czerwony, niebieski, zielony, fioletowy, pomarańczowy
okabe     <- palette.colors(6, "Okabe-Ito")[-1]

swatchplot(list(
  "Set1"                     = klasyczna,
  "Set1 – deuteranopia"      = deutan(klasyczna),
  "Set1 – protanopia"        = protan(klasyczna),
  "Okabe-Ito"                = okabe,
  "Okabe-Ito – deuteranopia" = deutan(okabe),
  "Okabe-Ito – protanopia"   = protan(okabe)
))


Klasyczny problem z badań: grupa kontrolna na zielono, eksperymentalna na czerwono.

set.seed(7)
badanie <- data.frame(
  grupa = rep(c("kontrolna", 
                "eksperymentalna"),
              each = 60),
  x = rnorm(120),
  y = c(rnorm(60, 0), 
        rnorm(60, 0.8))
)

czerw_ziel <- c(kontrolna = "#4DAF4A", eksperymentalna = "#E41A1C")

wykres_grup <- function(kolory, tytul) {
  ggplot(badanie, 
         aes(x, 
             y, 
             color = grupa)) +
    geom_point(size = 2.5) +
    scale_color_manual(values = kolory) +
    labs(title = tytul, 
         color = NULL) +
    theme_minimal() +
    theme(legend.position = "bottom")
}

wykres_grup(czerw_ziel, "Czerwony vs zielony") |
  wykres_grup(setNames(deutan(czerw_ziel), 
                       names(czerw_ziel)), 
              "…tak samo w deuteranopii")

Rozwiązania: paleta Okabe-Ito albo viridis; dodatkowe kodowanie kształtem (shape = grupa) lub podpisy bezpośrednio przy danych zamiast legendy.


Do interaktywnego sprawdzania własnych wykresów przydadzą się colorspace::cvd_emulator() (aplikacja Shiny, do której wgrywa się obrazek) oraz pakiet colorblindr Clausa Wilkego (dostępny na GitHubie), z funkcją cvd_grid() pokazującą wykres w czterech wariantach naraz.

Źródła: Okabe i Ito (2008); Machado, Oliveira i Fernandes (2009); Birch (2012); Wilke (2019), rozdz. 19 Common pitfalls of color use.




Kolor kieruje uwagę – wyszukiwanie wzrokowe na wykresie

Klasyczne eksperymenty Anne Treisman pokazały, że cecha taka jak kolor jest przetwarzana przeduwagowo (ang. preattentive): pojedynczy czerwony element wśród niebieskich „wyskakuje” (pop-out) niezależnie od liczby dystraktorów. Jeśli jednak cel różni się dopiero połączeniem cech (czerwone kółko wśród czerwonych trójkątów i niebieskich kółek), szukanie staje się powolne i sekwencyjne. Możemy odtworzyć oba warunki w ggplot2:

set.seed(42)
n <- 40
dystr <- data.frame(x = runif(n), y = runif(n))
cel   <- data.frame(x = 0.62, y = 0.41)

popout <- rbind(
  data.frame(dystr, kolor = "steelblue", ksztalt = 16),
  data.frame(cel,   kolor = "firebrick", ksztalt = 16)
)

koniunkcja <- rbind(
  data.frame(dystr[1:20, ],  kolor = "firebrick", ksztalt = 17),  # czerwone trójkąty
  data.frame(dystr[21:40, ], kolor = "steelblue", ksztalt = 16),  # niebieskie kółka
  data.frame(cel,            kolor = "firebrick", ksztalt = 16)   # cel: czerwone kółko
)

rysuj_szukanie <- function(dane, tytul) {
  ggplot(dane, aes(x, y, color = kolor, shape = ksztalt)) +
    geom_point(size = 4) +
    scale_color_identity() +
    scale_shape_identity() +
    coord_fixed() +
    labs(title = tytul) +
    theme_void() +
    theme(panel.border = element_rect(fill = NA, color = "grey70"))
}

rysuj_szukanie(popout, "Pop-out: znajdź czerwone kółko") |
  rysuj_szukanie(koniunkcja, "Koniunkcja: znajdź czerwone kółko")


Ta sama zasada działa na wykresach: jeśli chcesz, żeby odbiorca zobaczył jedną grupę, pomaluj ją wyrazistym kolorem, a resztę – na szaro.

df_wyr <- df[order(df$kategoria == "C"), ]   # grupa C rysowana na wierzchu

ggplot(df_wyr, aes(x, y, color = kategoria == "C")) +
  geom_point(size = 3) +
  scale_color_manual(values = c(`FALSE` = "grey80", `TRUE` = "firebrick"),
                     labels = c("pozostałe kategorie", "kategoria C"), name = NULL) +
  labs(title = "Wyróżnienie jednej grupy: kolor jako „wskaźnik laserowy\"") +
  theme_minimal()

Gotowe rozwiązanie tego typu oferuje pakiet gghighlight.

Źródła: Treisman i Gelade (1980) A feature-integration theory of attention; Healey i Enns (2012) Attention and Visual Memory in Visualization and Computer Graphics.




Efekt Stroopa w ggplot2

Kolor to także materiał eksperymentalny. Słynne zadanie Stroopa (1935): nazwij kolor czcionki, ignorując znaczenie słowa. W warunku niezgodnym (słowo „CZERWONY” napisane na niebiesko) czas reakcji rośnie – automatyczne czytanie interferuje z nazywaniem koloru. Bodziec możemy wygenerować w kilku linijkach:

slowa  <- c("CZERWONY", "ZIELONY", "NIEBIESKI", "ŻÓŁTY")
kolory <- c("#D7191C", "#1A9641", "#2B83BA", "#E6AB02")

set.seed(1935)
stroop <- data.frame(
  x = rep(1:4, times = 4) * 1.6,
  y = rep(4:1, each = 4),
  slowo = sample(slowa, 16, replace = TRUE)
)
stroop$zgodny <- kolory[match(stroop$slowo, slowa)]
stroop$niezgodny <- sapply(stroop$slowo, function(s) sample(kolory[slowa != s], 1))

rysuj_stroop <- function(kolumna, tytul) {
  ggplot(stroop, aes(x, y, label = slowo, color = .data[[kolumna]])) +
    geom_text(size = 4.5, fontface = "bold") +
    scale_color_identity() +
    xlim(0.6, 7.4) +
    labs(title = tytul) +
    theme_void()
}

rysuj_stroop("zgodny", "Warunek zgodny") / rysuj_stroop("niezgodny", "Warunek niezgodny")

Źródła / inspiracje: Stroop (1935) Studies of interference in serial verbal reactions; MacLeod (1991) Half a century of research on the Stroop effect: An integrative review.




{scico} – naukowe mapy kolorów (np. macierz korelacji)

Fabio Crameri opracował zestaw Scientific Colour Maps – palet percepcyjnie jednorodnych i czytelnych dla osób z daltonizmem, szeroko stosowanych w naukach o Ziemi. Pakiet scico udostępnia je w R. Dobre zastosowanie statystyczne: macierz korelacji, gdzie zero jest naturalnym środkiem, więc potrzebna jest paleta dywergentna.

kor <- cor(mtcars[, c("mpg", "cyl", "disp", "hp", "wt", "qsec")])
kor_df <- as.data.frame(as.table(kor))
names(kor_df) <- c("zm1", "zm2", "r")

wykres_kor <- function(skala, tytul) {
  ggplot(kor_df, aes(zm1, zm2, fill = r)) +
    geom_tile(color = "white") +
    geom_text(aes(label = sprintf("%.2f", r)), size = 3) +
    skala +
    coord_fixed() +
    labs(title = tytul, x = NULL, y = NULL) +
    theme_minimal()
}

wykres_kor(scale_fill_viridis_c(limits = c(-1, 1)), "Sekwencyjna (gorzej)") |
  wykres_kor(scale_fill_scico(palette = "vik", limits = c(-1, 1)), "Dywergentna scico 'vik' (lepiej)")


Na lewym wykresie trudno od razu stwierdzić, które korelacje są ujemne. Na prawym znak widać od razu (niebieski vs brąz), a siła związku odpowiada nasyceniu.

scico_palette_show()   # podgląd wszystkich palet scico

Źródła: Crameri, Shephard i Heron (2020); strona fabiocrameri.ch/colourmaps; dokumentacja pakietu scico (Thomas Lin Pedersen).




{MetBrewer} – palety z obrazów w Metropolitan Museum of Art

Blake Robert Mills stworzył palety na podstawie dzieł z kolekcji nowojorskiego Met – od Hiroshige i Hokusaia po Van Gogha. Pakiet oznacza też, które palety są przyjazne dla osób z daltonizmem.

p_hiro <- ggplot(df, aes(x = kategoria, fill = kategoria)) +
  geom_bar() +
  scale_fill_met_d("Hiroshige") +
  labs(title = "MetBrewer – Hiroshige") +
  theme_minimal() +
  theme(legend.position = "none")

p_hok <- ggplot(df, aes(x = x, y = y, color = y)) +
  geom_point(size = 3) +
  scale_color_met_c("Hokusai2") +
  labs(title = "MetBrewer – Hokusai2 (ciągła)") +
  theme_minimal()

p_hiro | p_hok

# Palety oznaczone jako przyjazne dla daltonistów.
# Nazwa obiektu zależy od wersji pakietu (CRAN: colorblind_palettes,
# GitHub: colorblind_met_palettes), więc wyszukujemy go po wzorcu:
obiekt_cb <- grep("^colorblind.*palettes$", ls("package:MetBrewer"), value = TRUE)[1]
get(obiekt_cb, envir = asNamespace("MetBrewer"))
##  [1] "Archambault" "Cassatt1"    "Cassatt2"    "Demuth"      "Derain"     
##  [6] "Egypt"       "Greek"       "Hiroshige"   "Hokusai2"    "Hokusai3"   
## [11] "Ingres"      "Isfahan1"    "Isfahan2"    "Java"        "Johnson"    
## [16] "Kandinsky"   "Morgenstern" "OKeeffe1"    "OKeeffe2"    "Pillement"  
## [21] "Tam"         "Troy"        "VanGogh3"    "Veronese"

Źródło: github.com/BlakeRMills/MetBrewer.




{paletteer} – jeden interfejs do tysięcy palet

Zamiast uczyć się składni kilkudziesięciu pakietów, można skorzystać z paletteer (Emil Hvitfeldt), który zbiera ponad 2500 palet dyskretnych i kilkaset ciągłych. Paletę wskazujemy jako "pakiet::paleta":

nrow(palettes_d_names)   # liczba palet dyskretnych
## [1] 2545
nrow(palettes_c_names)   # liczba palet ciągłych
## [1] 323
paletteer_d("nord::aurora")
## <colors>
## #BF616AFF #D08770FF #EBCB8BFF #A3BE8CFF #B48EADFF
p_nord <- ggplot(df, 
                 aes(x = x, 
                     y = y, 
                     color = kategoria)) +
  geom_point(size = 3) +
  scale_color_manual(values = as.character(paletteer_d("nord::aurora"))) +
  # skrót: scale_color_paletteer_d("nord::aurora") – wymaga aktualnych wersji ggplot2 i paletteer
  labs(title = "paletteer – nord::aurora") +
  theme_minimal()

p_tab <- ggplot(df, 
                aes(x = kategoria, 
                    fill = kategoria)) +
  geom_bar() +
  scale_fill_manual(values = as.character(paletteer_d("ggthemes::Tableau_10"))) +   # paleta jako zwykły wektor
  labs(title = "paletteer – ggthemes::Tableau_10") +
  theme_minimal() +
  theme(legend.position = "none")

p_nord | p_tab

Przeglądanie palet wygodnie robi się w repozytorium r-color-palettes tego samego autora – to najpełniejszy katalog palet dostępnych w R.

Źródła: github.com/EmilHvitfeldt/paletteer; github.com/EmilHvitfeldt/r-color-palettes.




Własna paleta – np. dla zespołu badawczego lub koła naukowego

Gdy przygotowujemy pracę, plakat lub serię wykresów, warto mieć jedną, spójną paletę i zdefiniować ją raz. Poniższe kolory pochodzą z popularnej palety z serwisu coolors.co:

kogni_kolory <- c(
  granat    = "#264653",
  turkus    = "#2A9D8F",
  piasek    = "#E9C46A",
  pomarancz = "#F4A261",
  ceglasty  = "#E76F51"
)

# skala dyskretna
scale_color_kogni_d <- function(...) {
  scale_color_manual(values = unname(kogni_kolory), ...)
}

# skala ciągła (gradient przez trzy kolory)
scale_fill_kogni_c <- function(...) {
  scale_fill_gradientn(colours = kogni_kolory[c("granat", "turkus", "piasek")], ...)
}

p_wlasna_d <- ggplot(df, 
                     aes(x = x, y = y, 
                         color = kategoria)) +
  geom_point(size = 3) +
  scale_color_kogni_d() +
  labs(title = "Własna paleta – dyskretna") +
  theme_minimal()

p_wlasna_c <- ggplot(wulkan, 
                     aes(x, y,
                         fill = wysokosc)) +
  geom_raster() +
  scale_fill_kogni_c() +
  coord_fixed() +
  labs(title = "Własna paleta – ciągła") +
  theme_void()

p_wlasna_d | p_wlasna_c

Przed użyciem sprawdźmy, czy paleta przechodzi test daltonizmu i skali szarości:

swatchplot(list(
  "oryginał"    = kogni_kolory,
  "deuteranopia" = deutan(kogni_kolory),
  "szarość"     = desaturate(kogni_kolory)
))

Źródło: Simon Jackson (2018) Creating corporate colour palettes for ggplot2 (blog drsimonj.svbtle.com).




Narzędzia pomocnicze

  • colorbrewer2.org – wybór palet z filtrami (daltonizm, druk, ksero).
  • Viz Palette (projects.susielu.com/viz-palette) – sprawdza, czy kolory z palety są dla oka rozróżnialne, także w symulacji daltonizmu.
  • Chroma.js Color Palette Helper (gka.github.io/palettes) – tworzenie gradientów z korekcją jasności.
  • coolors.co, Adobe Color – generowanie i wyciąganie palet ze zdjęć.
  • colorspace::hcl_wizard() / choose_palette() – interaktywny kreator palet HCL w R.
  • colourpicker (Dean Attali) – dodatek do RStudio do wybierania kolorów myszką i wklejania kodów HEX.




Dobre praktyki

Lista kontrolna przed oddaniem wykresu

  • Typ palety pasuje do danych: sekwencyjna dla wartości uporządkowanych, dywergentna dla danych z punktem środkowym, jakościowa dla kategorii.

  • Nie za dużo kategorii: powyżej ok. 7–8 kolorów oko przestaje je niezawodnie rozróżniać – rozważ grupowanie, panele (facet_wrap()) lub wyróżnienie tylko kluczowych grup.

  • Kolor nie jest jedynym nośnikiem informacji: dodaj kształt, typ linii lub etykiety bezpośrednio przy danych.

  • Test szarości i daltonizmu: desaturate() i deutan() z pakietu colorspace.

  • Spójność: ta sama grupa ma ten sam kolor na wszystkich wykresach (nazwany wektor w scale_*_manual()).

  • Unikaj „tęczy” dla danych ciągłych.

  • Uważaj na konotacje: czerwony bywa odczytywany jako „źle/niebezpiecznie”, zielony jako „dobrze” – nie przypisuj ich przypadkowo.




Podsumowanie

Systemy zapisu kolorów w R:

  • Nazwy ("steelblue") – wygodne do szkiców; 657 nazw z listy X11, nie wszystkie zgodne z CSS.
  • HEX ("#4682B4") – uniwersalny, precyzyjny; działa tak samo w R, HTML, PowerPoincie; opcjonalnie z alfą "#4682B480".
  • RGB (rgb(70, 130, 180, maxColorValue = 255)) – liczby zamiast kodu; pamiętaj, że w R domyślna skala to 0–1.
  • HCL (hcl(), pakiet colorspace) – przestrzeń zgodna z ludzką percepcją; podstawa dobrych palet.

{viridis} - najlepsze dla:

  • Dostępności (osoby z zaburzeniami widzenia kolorów)
  • Danych ciągłych
  • Publikacji naukowych

{RColorBrewer} - najlepsze dla:

  • Klasycznych, profesjonalnych wykresów
  • 3 typy palet: sekwencyjne, dywergentne, jakościowe
  • Uniwersalne zastosowania

{wesanderson} - najlepsze dla:

  • Estetycznych prezentacji
  • Oryginalnego wyglądu
  • Projektów kreatywnych

{ggsci} - najlepsze dla:

  • Publikacji naukowych (Nature, Science, Lancet)
  • Spójności z czasopismami
  • Fanów sci-fi (Star Trek, Rick and Morty)

{colorspace} - najlepsze dla:

  • Rozumienia i projektowania palet (HCL)
  • Sprawdzania wykresów pod kątem daltonizmu i druku w szarości

{scico} - najlepsze dla:

  • Map i macierzy (np. korelacji) w publikacjach naukowych
  • Danych dywergentnych

{MetBrewer} i {paletteer} - najlepsze dla:

  • Poszukiwania inspiracji
  • Dostępu do tysięcy palet jednym poleceniem






Źródła i inspiracje

Podręczniki i przewodniki (dostępne bezpłatnie online)

Kolor w R i przestrzeń HCL

  • Ihaka, R. (2003). Colour for presentation graphics. Proceedings of the 3rd International Workshop on Distributed Statistical Computing (DSC 2003).
  • Zeileis, A., Hornik, K., & Murrell, P. (2009). Escaping RGBland: Selecting colors for statistical graphics. Computational Statistics & Data Analysis, 53(9), 3259–3270.
  • Zeileis, A., Fisher, J. C., Hornik, K., Ihaka, R., McWhite, C. D., Murrell, P., Stauffer, R., & Wilke, C. O. (2020). colorspace: A toolbox for manipulating and assessing colors and palettes. Journal of Statistical Software, 96(1), 1–49.
  • Zeileis, A., & Murrell, P. (2019). A new palette() for R. R Blog. https://blog.r-project.org/2019/11/21/a-new-palette-for-r/
  • Zeileis, A., & Murrell, P. (2023). Coloring in R’s blind spot. The R Journal, 15(3), 240–256.

Palety i ich twórcy

Percepcja koloru i uwaga (perspektywa kognitywistyczna)

  • Birch, J. (2012). Worldwide prevalence of red-green color deficiency. Journal of the Optical Society of America A, 29(3), 313–320.
  • Machado, G. M., Oliveira, M. M., & Fernandes, L. A. F. (2009). A physiologically-based model for simulation of color vision deficiency. IEEE Transactions on Visualization and Computer Graphics, 15(6), 1291–1298.
  • Borland, D., & Taylor, R. M. (2007). Rainbow color map (still) considered harmful. IEEE Computer Graphics and Applications, 27(2), 14–17.
  • Liu, Y., & Heer, J. (2018). Somewhere over the rainbow: An empirical assessment of quantitative colormaps. Proceedings of CHI 2018.
  • Treisman, A. M., & Gelade, G. (1980). A feature-integration theory of attention. Cognitive Psychology, 12(1), 97–136.
  • Healey, C. G., & Enns, J. T. (2012). Attention and visual memory in visualization and computer graphics. IEEE Transactions on Visualization and Computer Graphics, 18(7), 1170–1188.
  • Stroop, J. R. (1935). Studies of interference in serial verbal reactions. Journal of Experimental Psychology, 18(6), 643–662.
  • MacLeod, C. M. (1991). Half a century of research on the Stroop effect: An integrative review. Psychological Bulletin, 109(2), 163–203.


Materiał przygotowano z pomocą asystenta AI Claude (Anthropic); treści i źródła zweryfikował autor.