크게 보면 기계 컴퓨터가 색을 이해하고 표현하는 RGB 체계와 사람이 색을 인지하고 이해하는 HCL 체계로 나누어 진다. 2진수로 표현된 시각적 데이터는 RGB 16진수로 변환되어 모니터에 표시되고, 물리적 광자(photon)로 사람눈에 위치한 망막에 꽂히게 되고, 뇌에서 사람이 인지한 후에 이를 처리하여 시각적인 정보를 인식하게 된다.
따라서, 사람의 뇌에 인식할 수 있는 시각적인 정보로 데이터를 구성해야만 다양한 종류의 모니터를 통해 효율적이고 효과적으로 정보가 전달될 수 있다.
작은 양수나 크레파스 명칭 대신에, 일반적이고 컴퓨터가 읽어들일 수 있는 색상 표색법이 16진수 팔레트다. RColorBrewer Dark2 팔레트가 실제로 저장된 방법이 다음에 나와 있다.
[1] "#1B9E77" "#D95F02" "#7570B3" "#E7298A" "#66A61E" "#E6AB02" "#A6761D"
[8] "#666666"
#
기호는 관례로 붙이는 것이고, 16진수 문자열을 다음과 같이 파싱한다: #rrggbb
에서 rr
, gg
, bb
각각은 적색, 녹색, 청색 채널에 대한 생상농도를 나타낸다. 각 색상은 2를 밑으로하는 16개 숫자를 나타내고, “16진수(hexadecimal)” 혹은 줄여서 헥스(hex)로 부른다. 다음에 밑을 10으로 하는 십진수와 16진수 비교표가 다음에 나와 있다.
library(xtable)
foo <- t(cbind(hex = I(c(as.character(0:9), LETTERS[1:6])),
decimal = I(as.character(0:15))))
foo <- xtable(foo)
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
예를 들어, 팔렛트 첫 색상이 #1B9E77
으로 명세되어 있다. 따라서, 녹색 채널 색상농도는 9E
가 된다.
\[ 9E = 9 * 16^1 + 14 * 16^0 = 9 * 16 + 14 = 158 \]
무슨 뜻일까? 해당 채널의 가장 낮은 값은 00
=0 이 되고, 가장 높은 값은 FF
=255 가 된다.
도움이 되는 기억해야될 중요한 사례가 다음에 나타나 있다. 적색, 녹색, 청색에 대한 강렬한 RGB 색상은 다음과 같다.
foo <- data.frame(color_name = c("blue", "green", "red"),
hex = c("#0000FF", "#00FF00", "#FF0000"),
red = c(0, 0, 255),
green = c(0, 255, 0),
blue = c(255, 0, 0))
foo <- xtable(foo, digits = 0)
color_name | hex | red | green | blue |
---|---|---|---|---|
blue | #0000FF | 0 | 0 | 255 |
green | #00FF00 | 0 | 255 | 0 |
red | #FF0000 | 255 | 0 | 0 |
다음에 흑백, 회색을 표현한 것이 나타나 있다.
j_intensity <- c(255, 171, 84, 0)
foo <- data.frame(color_name = c("white, gray100", "gray67",
"gray33", "black, gray0"),
hex = c("#FFFFFF", "#ABABAB", "#545454", "#000000"),
red = j_intensity,
green = j_intensity,
blue = j_intensity)
foo <- xtable(foo, digits = 0)
color_name | hex | red | green | blue |
---|---|---|---|---|
white, gray100 | #FFFFFF | 255 | 255 | 255 |
gray67 | #ABABAB | 171 | 171 | 171 |
gray33 | #545454 | 84 | 84 | 84 |
black, gray0 | #000000 | 0 | 0 | 0 |
“gray” 회색으로 치환하게 되면, “gray”를 보게되는 어느 곳에서나 동일한 결과를 얻게 됨에 주목한다. 모든 채널을 최대값으로 하면 흰색, 모든 채널을 최소값으로 하면 검정색이 된다.
R에서 색상을 지정하는 방법
palette()
함수로 조작하거나 검색한 현재 색상 팔레트에 인덱스를 사용.colors()
함수로 검색된 색상rgb()
, col2rgb()
, convertColor()
함수도 유용하니, 자세한 내용은 도움말을 참조한다.
RGB 색공간과 색상모형이 유일무이하고 가장 최고는 아니다. 컴퓨터 화면에 색상을 표현하는데는 자연스럽지만, 일부 영역에서 색상을 선택하는 작업에는 이런 모형을 적용하기 어렵다. 예를 들어, 사람이 구별하기는 쉽지만, 인지적으로 색상별로 비교되는 생각으로 구성된 정성적인 팔레트를 만들어 내는 방법은 명확하지 않다. 컴퓨터에 사용되는 색상을 기술하는데 RGB를 사용하지만, 사람이 색상체계를 구축하는 색공간에 RGB체계를 사용할 이유는 없다. 이점은 사람과 컴퓨터가 다른 것이고, 이를 인정해야만 된다.
색상모형은 일반적으로 RGB와 마찬가지로 세가지 차원으로 구성된다. 이는 망막에 세가지 다른 수용체를 인간이 갖는 생리적 사실에 기인한다. RGB와 인간 시각 체계에 대한 자세한 정보는 블로그를 참고한다. 색상모형의 차원이 사람이 인식할 수 있는 식별가능한 정보량에 더 가까이 부합되면 될수록, 더욱 유용하다. 이런 부합성이 사려깊게 작성된 팔레트 생성을 가능하게 하고, 더불어 특정한 특성을 갖는 색공간에 대한 길을 연다. RGB 색체계는 인간의 인식체계와 일치성이 떨어진다. 적색, 녹색, 청색광을 탐지할 수 있는 광수용체를 갖기 때문에, 색을 인지하는 체험이 RGB 방식으로 분해된다는 것을 의미하지 않는다. 적색과 녹색을 섞은 것으로 황색을 인식하는 체험을 했는가? 물론 아니다. 생리학적인 현실은 그렇다. 또다른 RGB 대안 모형이 HSV(Hue-Saturation-Value, 색상-채도-명도)모형이다. 불행하게도, 색을 선택하는데 문제가 많은데, 이유는 색상이 서로 중첩되는 차원을 갖기 때문이다.
사람이 인지하기 좋은 색모형은 무엇일까? CIELUV 와 CIELAB 이 가장 잘 알려진 사례다. CIELUV의 변종인 HCL(Hue-Chroma-Luminance, 색상-채도-휘도) 모형을 좀더 살펴보자. Zeileis와 동료들이 R 사용자를 위한 팩키지로 멋지게 작성했다.2 colorspace
R 팩키지에 딸려있고, HCL 색상모형을 탐색하고 이용하는데 도움을 준다. 마지막으로, HCL 색모형이 ggplot2
에 RColorBrewer
와 마찬가지로 잘 녹여져있다.
HCL 색상모형의 세가지 차원
저자는 채도와 휘도를 이해하고 구별하는데 힘든 시간을 보냈다. 위에서 살펴봤듯이, 색체계는 서로 독립된 것이 아니고, 3차원 HCL 공간에 기이한 모형으로 정보를 제공하고 있다.
위캠의 ggplot2
책에 나온 6.6 그림이 HCL 색공간을 이해하는데 도움이 된다.
위캠 책에 언급된 내용을 다시 적으면 다음과 같다: 각 측면, 창은 휘도에 따라 가장 낮은 값에서 높은 값 순으로 HCL 공간을 슬라이스로 나누어 도식화한 것을 보여주고 있다. 0 과 100 극단 휘도값은 생략되었는데, 이유는 각각 검은 점과 흰점으로 나타나기 때문이다. 슬라이스 내부에, 중심은 채도가 0 으로, 회색에 대응된다. 슬라이스 끝쪽으로 이동하면, 채도가 증가하고, 색상이 더 순색에 가까워지고 농도가 짖어진다. 색상은 각도로 매핑된다.
colorspace
팩키지에 가치있는 기여는 아마도 함수를 사용해서 색상공간을 합리적 방식으로 색공간을 이리저리 돌아다닐 수 있게 만든 것이다. 이와는 대조적으로 RColorBrewer
팩키지가 제공하는 팔레트는 정교하게 제작되었지만, 불행히도 고정이다.
인지기반 색상체계를 사용하는 것에 대한 옹호 사례와 더불어 색공간에 0 이 자리하는 것을 알려주는 중요성을 시연하고 있다.
CMYK 색상표는 시안(Cyan), 마젠타(Magenta), 옐로(Yellow), 블랙(Black = Key)를 원색으로 하여 명도가 낮아지는 감산혼합으로 주로 출력물 인쇄 혹은 사진 필림 현상에 사용되며 쿼크익스프레스, 일러스트레이터, 포토샵 등에서 CMYK 감산혼합을 지원한다. 현실적인 문제 때문에 RGB나 HSB(HSV)보다 표현 가능한 색이 적은 것으로 알려져 있다.
학창시절 감산혼합의 색의 3원색은 빨강, 노랑, 파랑인데, CMYK는 생뚱맞게도 시안(Cyan), 마젠타(Magenta), 옐로(Yellow), 블랙(Black = Key)를을 원색으로 하는데 이유는 빨강은 사실 자홍색(마젠타), 파랑은 청록색(시안)이라 정확한 색상이 후자가 맞다. 우리가 잘못 배운 탓이 크다.
RGB 생상과 CMYK 생상을 PDF 파일로 찍어 상호 비교해보자. 5
PDF 혹은 PNG 파일로 그래프를 저장시킬 경우 폰트를 비롯하여 원하는 결과가 나올까?
gapminder
데이터를 불러온다.
tibble [1,704 × 6] (S3: tbl_df/tbl/data.frame)
$ country : Factor w/ 142 levels "Afghanistan",..: 1 1 1 1 1 1 1 1 1 1 ...
$ continent: Factor w/ 5 levels "Africa","Americas",..: 3 3 3 3 3 3 3 3 3 3 ...
$ year : int [1:1704] 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 ...
$ lifeExp : num [1:1704] 28.8 30.3 32 34 36.1 ...
$ pop : int [1:1704] 8425333 9240934 10267083 11537966 13079460 14880372 12881816 13867957 16317921 22227415 ...
$ gdpPercap: num [1:1704] 779 821 853 836 740 ...
다음에 나오는 색상 시연 프로그램은 속이 꽉찬 원으로 기호를 기본디폴트 설정하면 더욱 효과적이다. 기본 R 그래픽으로 한정하고자 par()
설정을 사용한다. par()
함수는 기본 R 그래픽 매개변수를 설정하고 조회하는 함수다. 인터랙티브 세션 혹은 일반 R 스크립트로 다음 명령어를 실행한다.
기술적으로, 대입연산자가 필요하지만, 명시적으로 표현하는 것은 좋은 코딩 습관이다. 상기 명령어는 일석이조의 효과를 갖는다:
opar
에 저장되었다.par()
함수를 통해 그래픽 매개변수를 변경하게 되면, 원래 값이 반환되고, opar
변수에 대입되어 잡아내게 된다. 학습 마지막 단계에서, opar
변수를 사용해서 원래 상태로 되돌린다.
큰 그림으로 보면, R 세션에 영향을 주는 숨겨진 것들에 대한 최초 기본디폴트 환경설정 상태를 되돌리는 것은 좋은 습관이다. 본인 코드를 다른 사람에게 안기는 경우 이런 습관은 예의바르고 정중한 관례가 된다. R 사막 동떨어진 섬에 거주하고 있지만, 이런 관습이 납기를 얼마 남기지 않은 한밤중에 사소하지만 미칠 것같은 난제를 예방하는 효과도 있다.
knitr
에서 그림이 처리되는 방식 때문에, R 마크다운 문서 전체에 걸쳐 기본디폴트 제도 기호를 변경하는 것은 더욱 복잡하다. 이 문제를 저자가 해결한 방식을 살펴보려면, GitHub 페이지에 나와 있는 숨겨진 코드 덩어리를 참고한다.
시연목적으로 gapminder
데이터에서 일부 작은 표본 데이터가 필요하다. 2007 년부터 8 개국에 대한 데이터를 뽑아 1 인당 GDP로 행기준으로 정렬한다. jdat
데이터를 살펴보자.
set.seed(1903)
n_c <- 8
j_year <- 2007
countries_to_keep <- levels(gapminder$country) %>%
sample(size = n_c) %>%
as.character()
jdat <- gapminder %>%
filter(country %in% countries_to_keep, year == j_year) %>%
droplevels() %>%
arrange(gdpPercap)
jdat
# A tibble: 8 x 6
country continent year lifeExp pop gdpPercap
<fct> <fct> <int> <dbl> <int> <dbl>
1 Eritrea Africa 2007 58.0 4906585 641.
2 Nepal Asia 2007 63.8 28901790 1091.
3 Chad Africa 2007 50.7 10238807 1704.
4 Jamaica Americas 2007 72.6 2780132 7321.
5 Cuba Americas 2007 78.3 11416987 8948.
6 Costa Rica Americas 2007 78.8 4133884 9645.
7 Germany Europe 2007 79.4 82400996 32170.
8 Norway Europe 2007 80.2 4627926 49357.
R 기본 graphics
팩키지에서 plot()
함수를 사용해서 간단한 산점도를 그려본다.
j_xlim <- c(460, 60000)
j_ylim <- c(47, 82)
plot(lifeExp ~ gdpPercap, jdat, log = 'x', xlim = j_xlim, ylim = j_ylim,
main = "Start your engines ...")
이름을 지정해서 색상을 명시적으로 명세할 수 있는데, 한개 이상 색상명을 문자벡터로 전달한다. 점 8 개에 색상이 필요한데, 적은 색상명을 전달하게 되면 재활용이 일어난다. 즉, plot()
제도함수에 col=
인자로 한개 혹은 두개 색상만 명세하면 다음과 같이 된다.
plot(lifeExp ~ gdpPercap, jdat, log = 'x', xlim = j_xlim, ylim = j_ylim,
col = "red", main = 'col = "red"')
plot(lifeExp ~ gdpPercap, jdat, log = 'x', xlim = j_xlim, ylim = j_ylim,
col = c("blue", "orange"), main = 'col = c("blue", "orange")')
작은 양의 정수로 색상을 명세할 수 있는데, 현재 팔레트에 인덱스로 해석되고, palette()
함수로 인덱스 값을 확인할 수 있다. 정수와 색상명을 라벨로 그림에 추가했다. 기본디폴트 설정된 팔레트는 색상이 8개다. 이것이 데이터에서 8 개국을 선정한 이유다. 기본설정 팔레트는 사실 엉망이다.
plot(lifeExp ~ gdpPercap, jdat, log = 'x', xlim = j_xlim, ylim = j_ylim,
col = 1:n_c, main = paste0('col = 1:', n_c))
with(jdat, text(x = gdpPercap, y = lifeExp, pos = 1))
plot(lifeExp ~ gdpPercap, jdat, log = 'x', xlim = j_xlim, ylim = j_ylim,
col = 1:n_c, main = 'the default palette()')
with(jdat, text(x = gdpPercap, y = lifeExp, labels = palette(),
pos = rep(c(1, 3, 1), c(5, 1, 2))))
대신에 본인이 정의한 색상벡터를 사용할 수도 있다. 저자가 의도적으로 가장 모범사례를 모형화했다: 맞춤형 색상을 사용하려면, 정확하게 한 장소에 객체를 저장하고, 도식화하는 제도 호출, 범례생성 같은 시점에 해당 객체를 사용한다. 이런 방식이 맞춤형 색상표를 가지고 노는데 용이하다.
j_colors <- c('chartreuse3', 'cornflowerblue', 'darkgoldenrod1', 'peachpuff3',
'mediumorchid2', 'turquoise3', 'wheat4', 'slategray2')
plot(lifeExp ~ gdpPercap, jdat, log = 'x', xlim = j_xlim, ylim = j_ylim,
col = j_colors, main = 'custom colors!')
with(jdat, text(x = gdpPercap, y = lifeExp, labels = j_colors,
pos = rep(c(1, 3, 1), c(5, 1, 2))))
R이 “peachpuff3” 색상을 알 것이라고 상상이라도 했겠는가? colors()
함수를 사용해서 657 개 내장된 색상명칭을 살펴본다.
[1] "white" "aliceblue" "antiquewhite" "antiquewhite1"
[5] "antiquewhite2" "antiquewhite3"
[1] "yellow" "yellow1" "yellow2" "yellow3" "yellow4"
[6] "yellowgreen"
히지만, 색상을 화면으로 볼 수 있는 것이 훨씬더 흥미롭다. 많은 사람들이 색상, 기호, 선유형에 대한 문제를 다루었고 작업 결과를 인터넷에 공개했다. 일부 사례가 다음에 나와 있다:
RColorBrewer
와 viridis
색상선택이 가장 논란이 많고, 이리저리 만지작 거리면서 정말 많은 시간을 보내는 분야다. 지리학자이며 생상 전문가 Cynthia Brewer 교수가 출판과 웹에서 사용되는 색상표를 제작했고, 이는 RColorBrewer 팩키지에 반영되어 있다. 팩키지를 설치하고 사용하면 된다. 연관된 전체 팔레트를 살펴보는 명령어는 display.brewer.all()
이다.
팔레트는 종류가 많지만 다음 세가지 범주에 속한다. 위에서 아래부터 다음과 같다.
명칭을 명세해서 RColorBrewer 팔렛트 하나만 볼 수 있다.
n
갯수를 지정(3–8)해야 된다는 점에서, 솔직히 팩키지가 다소 투박하다. 하지만 어쩔 수 없이 그래야만 된다.
장인정신이 뭍어 나는 peachpuff3
색상표 대신에 RColorBrewer 에서 선택해서 맞춤형 색상을 지정하여 명세하자. 색상은 맞춤형 색상표에 따라 색상을 칠하게 되지만, 색상에 딸린 명칭은 기대한 바가 아니다. 다음 학습주제에서 이를 다룰 것이다.
j_brew_colors <- brewer.pal(n = 8, name = "Dark2")
plot(lifeExp ~ gdpPercap, jdat, log = 'x', xlim = j_xlim, ylim = j_ylim,
col = j_brew_colors, main = 'Dark2 qualitative palette from RColorBrewer')
with(jdat, text(x = gdpPercap, y = lifeExp, labels = j_brew_colors,
pos = rep(c(1, 3, 1), c(5, 1, 2))))
2015년 Stéfan van der Walt 와 Nathaniel Smith는 파이썬 matplotlib
팩키지에 사용될 새로운 색상 지도를 설계했고, SciPy 2015에서 발표했다. viridis
팩키지로 인해 R에 4가지 신규 팔레트가 추가되었다. CRAN과, GitHub에서 팩키지를 만날 수 있다.
viridis
색상표는 완벽하게 균등하게 지각되도록 설계되었고, 정규형식에서나 흑백으로 전환되었을 때도 마찬가지다. 또한 색망을 갖는 독자도 올바르게 지각될 수 있도록 설계되었다.
아직 나온지 얼마되지 않아서, 자세한 사항은 viridis
팩키지를 설치하고 소품문을 읽고 직접 경험하기 바란다.
dichromat
팩키지(CRAN)는 2색시자에 대한 효과적인 색상조합을 선택하는데 도움이 된다.
colorschems
목록에는 17 가지 색상조합이 담겨있는데, 적색과 녹색을 구별하는 능력이 없거나 예외적인 시력을 갖는 2색시자에게 적합하다.
library(ggplot2)
x_boundaries <-
lapply(colorschemes,
function(x) seq(from = 0, to = 1, length = length(x) + 1))
df <- data.frame(
xmin = unlist(lapply(x_boundaries, function(x) rev(rev(x)[-1]))),
xmax = unlist(lapply(x_boundaries, function(x) x[-1])),
ymax = rep(seq_along(colorschemes), sapply(colorschemes, length)))
anno_df <- data.frame(
scheme = names(colorschemes),
num = seq_along(colorschemes))
ggplot(df, aes(xmin = xmin, xmax = xmax, ymin = ymax - 0.85, ymax = ymax)) +
geom_rect(fill = unlist(colorschemes)) + xlim(c(-0.6, 1)) +
annotate("text", x = -0.05, y = anno_df$num - 0.5, label = anno_df$scheme,
hjust = 1) +
theme_bw() +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
axis.title = element_blank())
dichmat()
함수는 색상을 변환해서 다른 형태의 색맹에 근사적인 효과를 구현할 수 있어서, 후보 색상조합에 대한 효과를 평가할 수 있게 한다. data("dalton")
명령어는 256 색상 팔레트를 표현하는 객체를 생성하는데, 정상 시야로 표현되는 것과, 적록(red-green) 색맹과 청녹(green-blue) 생맹으로 표현되는 것이다.
colorspace
소품문colors()
함수로 활용가능한 색상명칭 참고자료