前言
ggplot2是R語言最流行的第三方擴展包,是RStudio首席科學(xué)家Hadley Wickham讀博期間的作品,是R相比其他語言一個獨領(lǐng)風(fēng)騷的特點。包名中“gg”是grammar of graphics的簡稱,是一套優(yōu)雅的繪圖語法。Wickham Hadley將這套語法詮釋如下:
一張統(tǒng)計圖形就是從數(shù)據(jù)到幾何對象(geometric object,縮寫geom)的圖形屬性(aesthetic attribute,縮寫aes)的一個映射。此外,圖形中還可能包含數(shù)據(jù)的統(tǒng)計變換(statistical transformation,縮寫stats),最后繪制在某個特定的坐標系(coordinate system,縮寫coord)中,而分面(facet)則可以用來生成數(shù)據(jù)不同子集的圖形。
這個解釋讀起來還是有點抽象。我們舉個具體的例子來解讀這個概念。假設(shè)現(xiàn)在我們要對一批連續(xù)取值的數(shù)據(jù)繪制直方圖。首先,要定義清楚需要幾個分組或者每個分組的區(qū)間,根據(jù)分組定義統(tǒng)計落在這個分組里的個數(shù),這個步驟就是把data變?yōu)?strong>stats。然后,需要選定表達數(shù)據(jù)的幾何對象,這個例子選用的是條塊bar,這個步驟就是選geom。geom有一堆屬性需要設(shè)定,比如x、y、顏色等,稱為aes,哪個aes由哪個stats指定,需要指定一個映射關(guān)系mapping,即指定誰對誰。知道誰對誰后,還需要知道怎么個對法,需要由scale決定,比如stats的color字段取值為1應(yīng)該對到什么顏色上,取值為2應(yīng)該對到什么顏色上。這些完成了以后,統(tǒng)計圖形的主體部分就成形了,但是假如我們希望在直方圖上,再畫一個概率密度曲線圖,怎么辦?ggplot2的思想非常精妙,把上面的主體部分稱為一個圖層layer,一個統(tǒng)計圖形可以擁有多個圖層,每個圖層疊加起來形成我們要的效果。接下來,再選定一個坐標系統(tǒng)coord,一張統(tǒng)計圖形plot就做好了。假如我們有多組數(shù)據(jù),每組數(shù)據(jù)都要按照相同的方法畫一張圖,每張圖重復(fù)敲代碼很繁瑣,就可以使用分面facet快速繪制多張統(tǒng)計圖形。這個過程用圖形總結(jié)如下:
我們可以看到ggplot2相比其他繪圖系統(tǒng)的幾個特性:
這兩大特性解放了數(shù)據(jù)分析師的思維,做到繪圖時所思即所見,非常優(yōu)雅高效。下面我們來逐步剖析每個元素的內(nèi)容。
數(shù)據(jù)
ggplot2接受的輸入數(shù)據(jù)一般是data.frame ,這是一個表格型結(jié)構(gòu),每一行是一個觀測(observation),每一列是一個變量(variable)。R語言內(nèi)置了許多著名的數(shù)據(jù)集,本文選取其中的iris進行講解。iris中文名是鳶尾花,有四個屬性,分別是Sepal.Length(花萼長度),Sepal.Width(花萼寬度),Petal.Length(花瓣長度),Petal.Width(花瓣寬度),以及一個類別標簽Species。我在網(wǎng)上找了一個圖片,做個標注,方便朋友理解。
我們可以使用str() 查看數(shù)據(jù)集的結(jié)構(gòu),用summary() 對每一個變量進行統(tǒng)計。
str(iris)
summary(iris)
Hadley對data.frame 提出了一個是否tidy的概念,抽象來講就是一個變量必須有自己獨立的一列,一個觀測必須有自己獨立的一行,每個取值必須有自己獨立的一個單元格。為了便于理解,我們從R for Data Science這本書截取出這個圖進行解釋:
左邊的數(shù)據(jù)是tidy的,右邊的數(shù)據(jù)是不tidy的,通過另一個包tidyr可以輕松完成二者的轉(zhuǎn)換。ggplot2的數(shù)據(jù)要求是tidy的。
統(tǒng)計圖形基本要素
幾何對象geom
幾何對象,說的直觀一些,就是你選擇什么幾何圖形來表示這組數(shù)據(jù)。ggplot2提供了眾多幾何對象geom_xyz() 供大家選擇。舉兩個常見的例子,geom_point() 用于表示兩個連續(xù)變量之間的關(guān)系,幾何形狀是點;geom_bar() 用于表示x軸為離散變量,y軸為連續(xù)連續(xù)變量之間的關(guān)系,幾何形狀是條塊。完整的幾何對象請下載RStudio公司總結(jié)的ggplot2 cheetsheet。
幾何對象需要解決一個問題,即相同數(shù)據(jù)的幾何對象位置相同,是放在一個位置相互覆蓋還是用別的排列方式。ggplot2的幾何對象有一個position 選項,用于指定如何在空間內(nèi)布置相同取值的集合對象。dodge 為并排模式;fill 為堆疊模式,并歸一化為相同的高度;stack 為純粹的堆疊模式;jitter 會在X和Y兩個方向增加隨機的擾動來防止對象之間的覆蓋。
統(tǒng)計變換stats
在ggplot2里,幾何對象與統(tǒng)計變換往往是一一對應(yīng)的。每個統(tǒng)計變換需要通過一個幾何對象來展現(xiàn);每個幾何對象的展現(xiàn)依賴統(tǒng)計變換的結(jié)果。舉個簡單例子,以下兩行代碼的效果是一樣的:
ggplot(iris) + geom_bar(aes(x=Sepal.Length), stat="bin", binwidth = 3)
ggplot(iris) + stat_bin(aes(x=Sepal.Length), geom="bar", binwidth = 3)
圖形屬性aes
每個幾何對象都有自己的屬性,這些屬性的取值需要通過數(shù)據(jù)提供。數(shù)據(jù)與圖形屬性之間的映射關(guān)系稱為mapping,在ggplot2中用aes() 進行定義。常見的圖形屬性有:x ,y ,size ,color ,group 。圖形屬性的任意一項都可以用數(shù)據(jù)的某一個變量來表示。
標尺scale
前面提到aes() 設(shè)定了數(shù)據(jù)與圖形屬性的映射關(guān)系,但是數(shù)據(jù)怎么映射為屬性,這就是標尺(Scales)的功能。對于任何一個圖形屬性,如x ,y ,alpha ,color ,fill ,linetype ,shape ,size ,ggplot2都提供以下四種標尺:
scale_*_continuous() :將數(shù)據(jù)的連續(xù)取值映射為圖形屬性的取值
scale_*_discrete() :將數(shù)據(jù)的離散取值映射為圖形屬性的取值
scale_*_identity() :使用數(shù)據(jù)的值作為圖形屬性的取值
scale_*_mannual() :將數(shù)據(jù)的離散取值作為手工指定的圖形屬性的取值
舉個例子
group_iris <- iris %>% group_by(Species) %>% dplyr::summarise(avg_sepal_length=mean(Sepal.Length))
str(group_iris)
p <- ggplot(group_iris) + geom_bar(aes(x=Species, weight=avg_sepal_length, fill=Species))
p
p + scale_fill_manual(
values = c("skyblue", "royalblue", "navy"), # mannual類scale特有的選項,指定圖形屬性的取值范圍
limits = c("setosa", "versicolor", "virginica"), # 數(shù)據(jù)的取值范圍
breaks = c("setosa", "versicolor", "virginica"), # 圖例和軸要顯示的分段點
name = "Species", # 圖例和軸使用的名稱
labels = c("set", "ver", "vir") # 圖例使用的標簽
)
除了上述四大類通用的標尺,特定的圖形屬性還有一些專門的標尺類型。對于x 和y 類圖形屬性,有如下幾種特殊的標尺:
對于color 和fill 類的圖形屬性,有如下幾類特殊標尺:
scale_fill_brewer(palette="Blues") :根據(jù)調(diào)色盤生成顏色標尺,可用的調(diào)色盤可以通過RColorBrewer::display.brewer.all() 命令查看;對于具體的一個調(diào)色盤,可以通過RColorBrewer::brewer.pal(n=4, name="Blues") 查看具體某個名字調(diào)色盤的n 個配色值。
`scale_fill_grey(start=0.2, end=0.8, na.value="red"):灰度標尺
scale_fill_gradient(low="red", high="yellow") :雙色漸變標尺
scale_fill_gradient2(low="red", high="blue", mid="white", midpoint=25) :三色漸變標尺
scale_fill_gradientn(colours=terrain.colors(6)) :n色漸標尺,其他的調(diào)色盤有rainbow() ,heat.colors() ,topo.colors() ,cm.colors() 以及RColorBrewer包的調(diào)色盤。
對于shape 類的圖形屬性,我們可以手工指定形狀:scale_shape_manual(values=c(3:7) 。每個形狀用數(shù)字表示,根據(jù)下圖可以選擇自己需要的形狀。
圖層layer
ggplot2的繪圖過程有點像Photoshop,有一個圖層的理念,每個圖層可以有自己的圖形對象和圖形屬性,通過+ 將不同圖層疊加起來生成最后的統(tǒng)計圖形。如果將數(shù)據(jù)定義在ggplot() 中,那么所有圖層都可以共用這個數(shù)據(jù);如果將數(shù)據(jù)定義在geom_xyz() 中,那么這個數(shù)據(jù)就只供這個幾何對象使用。
坐標系coord
ggplot2默認的坐標系是笛卡爾坐標系,可以用如下方法指定取值范圍:coord_cartesian(xlim=c(0,5), ylim=c(0,3)) 。如果想要讓x軸和y軸換位置,比如將柱形圖換成條形圖,可以使用coord_flip() 函數(shù)。coord_polar(theta="x", direction=1) 是角度坐標系,theta指定角度對應(yīng)的變量,start指定起點離12點鐘方向的偏離值,direction若為1表示順時針方向,若為-1表示逆時針方向。
掌握了數(shù)據(jù)、幾何對象、圖形屬性、圖層和坐標系的概念后,我們就可以開始繪制常見的統(tǒng)計圖形了。
練習(xí)
Kaggle數(shù)據(jù)挖掘競賽里有一個經(jīng)典的探索性分析例子,對iris數(shù)據(jù)集進行了各種形式的可視化,幫助人通過直觀的圖形更深地理解特征與label的關(guān)系。Kaggle官網(wǎng)給出了Python版本的實現(xiàn)。本節(jié)用R對該notebook的代碼進行重現(xiàn)。
library(ggplot2)
# Make scatter plot of Sepal.Length and Sepal.Width
p.scatter <- ggplot(iris) + geom_point(aes(x=Sepal.Length, y=Sepal.Width))
p.scatter
# One piece of information missing in the plots above is what species each plant is
p.scatter <- ggplot(iris) + geom_point(aes(x=Sepal.Length, y=Sepal.Width, color=Species))
p.scatter
# Boxplot to explore numeric variable
p.box <- ggplot(iris) + geom_boxplot(aes(x=Species, y=Petal.Length))
p.box
# One way we can extend this plot is adding a layer of individual points on top of it
p.box.jitter <- p.box + geom_jitter(aes(x=Species, y=Petal.Length))
p.box.jitter
# A violin plot combines the benefits of the previous two plots and simplifies them
# Denser regions of the data are fatter, and sparser thiner in a violin plot
p.violin <- ggplot(iris) + geom_violin(aes(x=Species, y=Petal.Length))
p.violin
# A final plot useful for looking at univariate relations is the kdeplot,
p.density <- ggplot(iris) + geom_density(aes(x=Petal.Length, colour=Species))
p.density
分面
分面,就是分組繪圖,根據(jù)定義的規(guī)則,將數(shù)據(jù)分為多個子集,每個子集按照統(tǒng)一的規(guī)則單獨制圖,排布在一個頁面上。ggplot2提供兩種分面模式:facet_grid() 和facet_wrap() 。
我們先來看一下facet_grid() 的效果。
library(tidyr)
library(dplyr)
# 將數(shù)據(jù)變?yōu)閠idy的
tidy_iris <- iris %>%
gather(feature_name, feature_value, one_of(c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width")))
p.box.facet <- ggplot(tidy_iris) + geom_boxplot(aes(x=Species, y=feature_value)) + facet_grid(feature_name~Species)
p.box.facet
可以看到facet_grid() 是一個二維的矩形布局,每個子集的位置由行位置變量~列位置變量 的決定,在上面的例子中就是每一個Species的取值作為一行,每一個feature_name的取值作為一列。
再來看一下facet_wrap() 的效果。
p.box.facet <- ggplot(tidy_iris) + geom_boxplot(aes(x=Species, y=feature_value)) + facet_wrap(~feature_name+Species, scales="free")
p.box.facet
facet_wrap() 生成一個動態(tài)調(diào)整的一維布局,根據(jù)~位置變量1+位置變量2+... 來確定每個子集的位置,先逐行排列,放不下了移動到下一行。scales="free" 讓每個子圖的坐標系適合自己的數(shù)據(jù),便于在有限的空間里充分展示子圖的細節(jié),但也失去了不同子圖之間比較的作用,需要謹慎使用。
題外話:復(fù)雜布局
分面的特點是可以快速生成多個子圖,每個子圖的生成方式是一樣的,因此只需要指定分組的規(guī)則即可。但是有時候我們希望繪制多個子圖,每個子圖的生成方法卻不一樣,這個時候分面就不起作用了,需要使用grid包提供的布局功能。下面我們用ggplot2和grid的布局實現(xiàn)一個較為復(fù)雜的統(tǒng)計圖形效果:
library(grid)
# Show bivariate scatter plot and univariate histogram
p.hist.len <- ggplot(iris) + geom_histogram(aes(x=Sepal.Length))
p.hist.wid <- ggplot(iris) + geom_histogram(aes(x=Sepal.Width)) + coord_flip()
grid.newpage()
pushViewport(viewport(layout = grid.layout(3, 3)))
print(p.scatter, vp=viewport(layout.pos.row=2:3, layout.pos.col=1:2))
print(p.hist.len, vp=viewport(layout.pos.row=1, layout.pos.col=1:2))
print(p.hist.wid, vp=viewport(layout.pos.row=2:3, layout.pos.col=3))
在做數(shù)據(jù)分析時,我們經(jīng)常需要觀察變量自身與變量之間的兩兩關(guān)系。這個過程中需要繪制大量的圖表,且每個業(yè)務(wù)的數(shù)據(jù)分析都需要這么做,因此算是一種重復(fù)性比較大的工作。我們可以使用GGally包來快速完成這個探索性分析的任務(wù)。
library(GGally)
# Another useful seaborn plot is the pairplot, which shows the bivariate relation
# between each pair of features
#
# From the pairplot, we'll see that the Iris-setosa species is separataed from the other
# two across all feature combinations
ggpairs(iris, aes(colour=Species), alpha=0.4) # R could be better!!
題外話:其他練習(xí)
Kaggle數(shù)據(jù)挖掘競賽剩下的例子是繪制Parallel coordinate graph、Andrews Curve、radviz,前兩個的實現(xiàn)參考如下,最后一個暫時沒找到對應(yīng)的方法。
# Parallel coordinate graph & Andrews Curve
# 修改自:http:///2009/03/parallel-coordinates-and-andrews-curve/
# 輪廓圖的思想非常簡單、直觀,它是在橫坐標上取n個點,依次表示各個指標(即變量);橫坐標上則對應(yīng)各個指標的值(或者經(jīng)過標準化變換后的值),然后將每一組數(shù)據(jù)對應(yīng)的點依次連接即可
# 調(diào)和曲線圖的思想和傅立葉變換十分相似:
# 根據(jù)三角變換方法將 n 維空間的點映射到二維平面上的曲線上,其中x取值范圍為[-pi,pi]。
# Another multivariate visualization technique pandas has is parallel_coordinates
# Parallel coordinates plots each feature on a separate column & then draws lines
# connecting the features for each data sample
p.paral <- ggplot(cbind(iris %>% gather(feature_name, feature_value, one_of(c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"))), id=1:nrow(iris))) + geom_line(aes(x=feature_name, y=feature_value, group=id, colour=Species))
p.paral
# One cool more sophisticated technique pandas has available is called Andrews Curves
# Andrews Curves involve using attributes of samples as coefficients for Fourier series
# and then plotting these
andrews_curve <- function(data, x_col, y_col, step=pi/30){
x = as.matrix(data[, x_col])
t = seq(-pi, pi, pi/30)
m = nrow(x)
n = ncol(x)
f = matrix(0, m, length(t))
for(i in 1:m) {
f[i,] = x[i,1]/sqrt(2)
for(j in 2:n) {
if (j%%2 == 0)
f[i, ] = f[i, ] + x[i, j] * sin(j/2 * t)
else f[i, ] = f[i, ] + x[i, j] * cos(j%/%2 * t)
}
}
colnames(f) <- t
label <- data[, y_col]
id <- c(1:nrow(f))
res <- cbind(as.data.frame(f), label, id) %>%
gather(x, y, -label, -id, convert = TRUE)
}
iris.andrew <- andrews_curve(iris, x_col=c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"), y_col="Species")
p.andrew <- ggplot(iris.andrew) + geom_line(aes(x, y, group=id, color=label))
p.andrew
# A final multivariate visualization technique pandas has is radviz
# Which puts each feature as a point on a 2D plane, and then simulates
# having each sample attached to those points through a spring weighted
# by the relative value for that feature
# 暫時沒能力實現(xiàn)
其他元素
主題
所有與數(shù)據(jù)不相關(guān)的圖形控制細節(jié)都放在theme() 這個函數(shù)里。ggplot2內(nèi)置了一些常見的主題:theme_bw() ,theme_classic() ,theme_grey() ,theme_minimal() 。如果需要更多的主題可以安裝ggthemes 包,也可以自定義主題。
圖例
ggplot2可以設(shè)定圖例的位置:theme(legend.position="bottom") ,其他選項有top、left和right。
每個圖形屬性都會有一個圖例,圖例的類型共有三種:colorbar為顏色條,適合連續(xù)變量;legend為鍵值對,適合有限取值的變量;none,將一個圖形屬性的圖例設(shè)置為none,則不顯示這個圖形屬性的圖例。
標簽
常用的繪圖標簽有:
ggtitle("New Plot Title") :指定圖形名稱
xlab("New X label") :指定x軸標簽
ylab("New Y label") :指定y軸標簽
圖例標簽需要使用scale_*() 的name 和labels 選項進行指定
關(guān)于作者:丹追兵,數(shù)據(jù)分析師一枚,編程語言python和R,使用Spark、Hadoop、Storm、ODPS。本文出自丹追兵的pytrafficR專欄,轉(zhuǎn)載請注明作者與出處:https://segmentfault.com/blog...
|