7 Data visualization with ggplot2

7.1 Building a plot

ggplot2 คือ Package ย่อยอีกตัวของ tidyverse ซึ่งใช้สำหรับการพล็อตกราฟ

7.1.1 Anatomy of ggplot

ggplot(data = your_data, aes(x = x, y = y, col = col, fill = fill)) +
  geom_*() +
  theme_*() +
  ...
  • aes คือ aesthetic ซึ่งหมายถึงการ map ข้อมูลของท่านเข้ากับตำแหน่งของกราฟ
    • x = แกน x, y = แกน y
    • col = สี, fill = สีพื้นหลัง
  • geom_*() คือ การกำหนดว่าท่านต้องการที่จะ plot กราฟอะไร
  • theme_*() คือ การกำหนด theme ของกราฟ เพื่อความสวยงาม เช่น theme_bw(), theme_classic()
  • คือการปรับแต่งอื่นๆ ในส่วนของ Customization

การสร้างภาพที่สมบูรณ์นั้นมีส่วนจำเป็นที่ประกอบด้วย aes และ geom ตรงส่วนอื่นเป็นส่วนเสริมที่จะช่วยให้ภาพมีความสวยงามขึ้น

ggplot(df, aes(x = Sepal.Width, y = Sepal.Length))

สังเกตว่าจะยังไม่มีกราฟใดๆ ปรากฏ ท่านจำเป็นต้องใช้ geom เพื่อทำการสร้างภาพนั้นขึ้น

7.1.2 Scatter plot

ggplot(df, aes(x = Sepal.Width, y = Sepal.Length, 
               col = Species, shape = Species)) + 
  geom_point()

สังเกตการ mapping ของ aes()

7.1.3 Straight line

ท่านสามารถเพิ่มเส้นที่ท่านต้องการได้โดย geom_hline (แนวตั้ง), geom_vline (แนวนอน), geom_abline (แนวเฉียง)

ggplot(df, aes(x = Sepal.Width, y = Sepal.Length, col = Species)) + 
  geom_point() + 
  geom_hline(yintercept = 6, linetype = "dashed", 
             col = "red", linewidth = 1) + 
  geom_vline(xintercept = 2.7, linetype = "dotted", 
             col = "blue", linewidth = 1.25) + 
  geom_abline(intercept = 2, slope = 1, 
              linetype = "dotdash", col = "black", linewidth = 1.5)

สังเกตว่าท่านสามารถปรับค่าจำพวก สี ลักษณะเส้นต่างๆ ได้ โดย Parameter นั้น ต้องอยู่นอก aes มิเช่นนั้น ฟังก์ชันจะพยายามไปดึงข้อมูลจากกราฟมา

ggplot(df, aes(x = Sepal.Width, y = Sepal.Length, col = "darkviolet")) + # ผิด
  geom_point() + 
  geom_hline(yintercept = 6, linetype = "dashed", 
             col = "red", linewidth = 1) + 
  geom_vline(xintercept = 2.7, linetype = "dotted", 
             col = "blue", linewidth = 1.25) + 
  geom_abline(intercept = 2, slope = 1, 
              linetype = "dotdash", col = "black", linewidth = 1.5)

ที่ถูกต้อง ท่านต้องนำ col ไปอยู่นอก aes จึงจะได้สีที่ท่านต้องการ

ggplot(df, aes(x = Sepal.Width, y = Sepal.Length)) + # ผิด
  geom_point(col = "darkviolet") + 
  geom_hline(yintercept = 6, linetype = "dashed", 
             col = "red", linewidth = 1) + 
  geom_vline(xintercept = 2.7, linetype = "dotted", 
             col = "blue", linewidth = 1.25) + 
  geom_abline(intercept = 2, slope = 1, linetype = "dotdash", 
              col = "black", linewidth = 1.5)

7.1.4 Bar chart

geom_bar() ใช้สำหรับนับจำนวนของคอลัมน์นั้น ไม่มีค่า y

ggplot(df, aes(x = Species, fill = Species)) + # fill ไว้สำหรับแบ่งสีใน barchart
  geom_bar(col = "black", width = 0.5) # ความกว้าง 50% 

ส่วน geom_col() จะรับค่า y ด้วย โดยข้อมูล x ที่ซ้ำกันจะถูกนำมารวมกัน

ggplot(df, aes(x = Species, y = Sepal.Width, fill = Species)) + 
  geom_col(col = "black", width = 0.5) 

สังเกตว่าค่าที่ได้เกิดจากการรวมกันของข้อมูลทั้งคอลัมน์ (สังเกตที่เส้นสีดำเป็นเส้นต่อๆ กัน ไม่ใช่เส้นเดียว) ซึ่งมักไม่เป็นที่ต้องการในการแสดง โดยมักเกิดจากความผิดพลาดมากกว่า (โดยเฉพาะถ้าไม่ได้ใส่ col = black) และส่วนใหญ่มักจะใช้ในการแสดงค่าเฉลี่ยมากกว่าผลรวม ในการนี้ ควรใช้คำสั่ง dplyr::summarize() ในการสรุปข้อมูลก่อน

df |> 
  group_by(Species) |> 
  summarize(across(everything(), mean)) |> 
  ggplot(aes(x = Species, y = Sepal.Width, fill = Species)) + 
  geom_col(col = "black", width = 0.5) 

จะเห็นว่ากราฟแสดงค่าเฉลี่ยซึ่งตรงตามความต้องการทั่วไปมากกว่า (สังเกตแกน y)

7.1.5 Box plot

ทำการสร้าง Box plot

ggplot(df, aes(x = Species, y = Sepal.Width, fill = Species)) +
  geom_boxplot(width = 0.5) 

ถ้าท่านต้องการสร้าง Plot ที่แสดงหลาย metrics ท่านจะต้องเปลี่ยนข้อมูลเป็น Long form เสียก่อน

head(long_df, 10)
long_df |> 
    ggplot(aes(x = Species, y = cm, fill = Metrics)) +
    geom_boxplot() 

7.1.6 Histogram

ในการทำงานสถิตินั้น โดยส่วนใหญ่จะต้องทำการตรวจสอบการกระจายของข้อมูลก่อนวิเคราะห์ทางสถิติ ซึ่งสามารถทำได้โดยใช้ geom_histogram() หรือ geom_density() โดยท่านสามารถเลือกประเภทของข้อมูลที่ใช้ในแกน y ได้โดยใช้ after_stat()

ggplot(df, aes(x = Sepal.Width)) + 
  geom_histogram(fill = "skyblue", binwidth = 0.1)  # binwidth = ความกว้างของแต่ละช่วงข้อมูล
ggplot(df, aes(x = Sepal.Width, y = after_stat(density))) + # ใช้ density
  geom_histogram(fill = "skyblue", binwidth = 0.1)  
ggplot(df, aes(x = Sepal.Width)) + 
  geom_density(fill = "violet", alpha = 0.5)
ggplot(df, aes(x = Sepal.Width, y = after_stat(count))) + # ใช้ count 
  geom_density(fill = "violet", alpha = 0.5)

ทั้งนี้ ท่านสามารถพล็อตหลายกราฟเข้าด้วยกันได้ ด้วยการ + ตามหลังไปเรื่อยๆ เพียงแต่ต้องระวังเรื่อง scale ที่ต้องเป็นระดับเดียวกัน

ggplot(df, aes(x = Sepal.Width)) +
  geom_histogram(aes(y = after_stat(density)), 
                 binwidth = 0.1, fill = "skyblue") + # ปรับเป็นความถี่
  geom_density(fill = "violet", alpha = 0.5) +
  theme_bw() # ลบภาพพื้นหลังสีเทาออก

7.1.7 Fitting a statistical model

ท่านสามารถที่จะพล็อต Statistical model ได้โดยใช้ geom_smooth() ยกตัวอย่าง เช่น ถ้าอยากดูความสัมพันธ์ของ Sepal.Length และ Petal.Length

ggplot(df, aes(x = Sepal.Length, y = Petal.Length, color = Species)) + # สีตาม Species
  geom_point(color = "black") +
  geom_smooth(method = "loess") # fit a LOESS model
ggplot(df, aes(x = Sepal.Length, y = Petal.Length, color = Species)) + # สีตาม Species
  geom_point(color = "black") +
  geom_smooth(method = "lm") # fit a linear model

7.2 Customization

7.2.1 Faceting

ในบางครั้งท่านอาจจะต้องการที่จะพล็อตกราฟแยกกันเป็นส่วนๆ มากกว่ารวมกันในกราฟเดียว ท่านสามารถแบ่ง Partition ของการพล็อตแต่ละกลุ่มได้โดยใช้ facet_wrap()

ggplot(df, aes(x = Sepal.Length, y = Petal.Length, 
               color = Species)) + # สีตาม Species
  geom_point(color = "black") +
  geom_smooth(method = "lm") +
  facet_wrap(~Species) + # แบ่งเป็นหลายกลุ่ม
  theme_bw()
ggplot(df, aes(x = Sepal.Length,  fill = Species)) + # สีตาม Species
  geom_histogram(binwidth = 0.1) +
  facet_wrap(~Species, scales = "free_x", nrow = 2) + # ทำให้แกน x ไม่ fix ค่า
  theme_bw() 

และถ้าท่านต้องการเรียงข้อมูลให้เป็นไปตามแกนที่ท่านต้องการ สามารถใช้ facet_grid()

ggplot(mtcars, 
       aes(x = mpg, y = disp, color = as.factor(cyl))) + 
  facet_grid(vs + am ~ gear, 
             labeller = label_both, # แสดงชื่อและข้อมูล 
             scale = "free") +
  geom_point() +
  theme_bw()

7.2.2 Labels

ท่านสามารถปรับแต่งชื่อของแต่ละตำแหน่งได้โดยใช้ labs()

ggplot(df, aes(x = Sepal.Width, y = Sepal.Length, col = Species)) + 
  geom_point() +
  labs(title = "Iris dataset", subtitle = "Sepal length vs Sepal width",
       x = "Sepal width", y = "Sepal length", 
       color = "List of species",
       tag = "1") +
  theme_bw()

สำหรับสัญลักษณ์ทางคณิตศาสตร์สามารถแสดงได้โดยใช้คำสั่ง bquote()

poly_data <- data.frame(x = seq(-10, 10, length.out = 100)) |> 
  mutate(y = x^9+x+10+rnorm(100, mean = 0, sd =3))

ggplot(poly_data, aes(x = x, y = y)) + geom_point() + 
  geom_smooth(method = "lm", se = FALSE) +
  labs(y = bquote("This function is" ~ f(x) == x^9+x+10+epsilon ~ ".")) +
  theme_bw()

Mathematics quote ที่ใช้บ่อยมีดังนี้

Syntax Meaning
"Text" ~ quote() ~ "Text" ตัวอักษรปกติ ~ Mathematical expression ~ ตัวอักษรปกติ
x %+-% y x บวก หรือ ลบ y
x %/% y x หาร y
x[i] x ห้อยด้วย i (Subscript)
x^i x ยกกำลัง i (Superscript)
x ~~ y x เว้นวรรค y (ใน Mathematical expression เดียวกัน)
sqrt(x, y) รากที่ y ของ x
x <= y, x >= y x น้อยกว่าหรือเท่ากับ มากกว่าหรือเท่ากับ y
infinity อนันต์
frac(x, y) เศษส่วน

สามารถศึกษาเพิ่มเติมได้ที่ ?plotmath

7.2.3 Scales

ท่านสามารถปรับแต่ง Scale ของแกน x แกน y และสีต่างได้โดยใช้ฟังก์ชัน scale_*_*()

ggplot(df, aes(x = Sepal.Width, y = Sepal.Length, col = Species)) + 
  geom_point() +
  scale_x_continuous(breaks = seq(0,5,0.5), # Major breaks
                     minor_breaks = seq(0,5,0.1), # Minor breaks
                     limits = c(1,5)) + # Axis limits
  scale_y_continuous(breaks = seq(0,8,0.5), 
                     minor_breaks = seq(0,8,0.1),
                     limits = c(5,8)
                    ) +
  theme_bw()
## Warning: Removed 22 rows containing missing values (`geom_point()`).

Note: ท่านจะเห็นข้อความเตือนว่าข้อมูลที่เกินกว่า Axis นั้นจะถูกตัดออกไป

นอกจากนั้น ท่านยังสามารถเปลี่ยนข้อมูลเป็นรูปแบบอื่นๆ ได้ด้วย

ggplot(df, aes(x = Sepal.Width, y = Sepal.Length, col = Species)) + 
  geom_point() +
  scale_x_continuous(name = bquote(sqrt(Sepal.Width)), 
                     trans = "sqrt") + # เปลี่ยนชื่อได้เลย
  scale_y_continuous(name = "Reverse Sepal.Length",
                  trans = "reverse") + # More information ?scale_x_continuous 
  theme_bw()

ในส่วนตัวแปรประเภท Discrete นั้นก็สามารถปรับเปลี่ยนได้เช่นกัน

ggplot(df, aes(x = Sepal.Width, y = Sepal.Length, col = Species)) + 
  geom_point() +
  scale_color_manual(values = c("darkred", "darkblue", "darkgreen"),
    labels = c("Setosa", "Versicolor", "Virginica")) +
  theme_bw()

7.2.4 Color palettes

ggplot2 นั้นมีชุดของสีเตรียมไว้ให้ส่วนหนึ่งแล้วสำหรับการสร้างกราฟ โดยใช้ scale_*_brewer()

ggplot(df, aes(x = Sepal.Width, y = Sepal.Length, col = Species)) + 
  geom_point() +
  scale_color_brewer(palette = "Dark2") +
  theme_bw()

สีทั้งหมดที่มาพร้อมกับ ggplot2() มีดังนี้

RColorBrewer::display.brewer.all()

และยังมีสีเพิ่มเติมสำหรับการวาดกราฟที่ใช้บ่อยในสารสาร ต่างๆ ใน Package ggsci ศึกษารายละเอียดเพิ่มเติมได้ที่ https://nanx.me/ggsci/

library(ggsci)
ggplot(df, aes(x = Sepal.Width, y = Sepal.Length, col = Species)) + 
  geom_point() +
  scale_color_nejm() +
  theme_bw()

ในส่วนของสีที่เป็นพื้นที่ จะใช้ scale_fill_*() ซึ่งมีลักษณะการใช้ในแบบเดียวกัน

7.2.5 Themes

ในส่วนของภาพรวมของกราฟทั้งหมดนั้น สามารถปรับแต่งได้โดยฟังก์ชัน theme_*()

ggplot(df, aes(x = Sepal.Width, y = Sepal.Length, col = Species)) + 
  geom_point() +
  theme_classic()

ซึ่ง Theme ที่ใช้บ่อยนั้น ได้แก่


ทั้งหมดที่แสดงนี้ เป็นเพียงกราฟพื้นฐานเท่านั้น ยังมีการปรับแต่งอื่นๆ ได้อีกมาก สามารถศึกษาเพิ่มเติมได้ที่