ggplot2雙坐標軸的解決方案

本來沒有打算寫這一篇的,因為在一幅圖表中使用雙坐標軸確實不是一個很好地習慣,無論是信息傳遞的效率還是數據表達的準確性而言。

但是最近有好幾個小夥伴兒跟我諮詢關於ggplot2的次坐標軸問題,平時的一些業務分析中,有些場景出於數據呈現的需要,或者閱讀習慣等,往往需要在一幅圖中呈現兩個量級不等的坐標。

所以我覺得這一篇推送很有必要,確實在最新版的ggplot2(ggplot 2.2.0以上版本)中,已經加入了次坐標軸參數,通過這個次坐標軸的轉換,我們可以模擬出不同數量級的次坐標軸效果。

因為其中用到了英文月份簡寫,這裡對系統日期顯示格式做了特殊設置:

lct <- Sys.getlocale("LC_TIME") #備份本地默認日期顯示格式Sys.setlocale("LC_TIME", "C") #指定標準日期顯示格式Sys.setlocale("LC_TIME",lct) #這一句是恢復默認系統日期顯示格式#(記得要在使用完下面的month函數之後再運行這一句,否則月份返回的是中文)載入包:library("lubridate")library("ggplot2")library("scales")library("magrittr")library("tidyr")

生成作圖數據

作圖數據1——單序列柱形圖

data1 <- data.frame( Month = seq(from = as.Date("2017-01-01"),to=as.Date("2017-06-01"),by="1 month") %>% month(label=TRUE), Value = runif(6,10,50) %>% round() ) Month Value1 Jan 392 Feb 383 Mar 504 Apr 335 May 186 Jun 49

作圖數據2——二分類折線圖(帶散點)

data2 <- data.frame( Month = seq(from = as.Date("2017-01-01"),to=as.Date("2017-06-01"),by="1 month") %>% month(label=TRUE), Categroy1 = runif(6,0.1,0.5) %>% round(2), Categroy2 = runif(6,0.1,0.5) %>% round(2) ) %>% gather(Category,Value,-1) Month Category Value1 Jan Categroy1 0.492 Feb Categroy1 0.233 Mar Categroy1 0.104 Apr Categroy1 0.385 May Categroy1 0.346 Jun Categroy1 0.137 Jan Categroy2 0.488 Feb Categroy2 0.389 Mar Categroy2 0.4810 Apr Categroy2 0.1511 May Categroy2 0.4012 Jun Categroy2 0.16

以下是整個過程代碼,基本是司空見慣的內容,這裡不做過多解釋,僅提示其中兩處重點,注意第二行geom_line內的y參數賦值以及第四行的scale_y_continuous語句:

ggplot() + geom_col( data = data1,aes(x = Month,y = Value),fill="#6794a7") + geom_line(data = data2,aes(x = Month,y = rescale(Value,c(0,55)),colour=Category,group=Category),size=1.5) + geom_point(data = data2,aes(x = Month,y = rescale(Value,c(0,55)),colour=Category),shape=21,fill="white",size=4)+ scale_y_continuous(breaks=pretty_breaks(5),sec.axis = sec_axis( ~rescale(.,c(0,0.5)),name = "Categroy",labels=sprintf("%d%%",(0:5)*10)))+ scale_color_manual(label = c("Categroy1", "Categroy2"),values = c("#ee8f71","#C10534")) + labs( title="This is a Title!", subtitle="This is a Subtitle", caption="This is a Caption" )+ theme_minimal(base_size=16) %+replace% theme( plot.caption = element_text(hjust=0), plot.margin = unit(c(1,0.5,1,0.5), "lines") )

這段代碼與我們經常用的有兩點不同:

第一次自定義映射——折線度量數據的映射轉換:

geom_line(geom_point,因為點圖是附屬於折線圖,僅做修飾之用,這裡只重點說折線圖層)中的y參數指定的對象使用了一個統計變換函數,rescale函數其實很好理解,就是將一個數值向量按照給定的另一個數值向量的極差(range),等比例標準化。

如果你知道如何將一組向量按照0~1標準化的話,那麼這個函數就不難理解 ,其實就是將標準化的尺度給了一個自定義的範圍。

因為在ggplot2標度系統中,不容許在一個圖形中出現兩個量級不等的標度(一山不容二虎),但是想要提供度量不等的次坐標軸,折中的方法就是,將次坐標軸的所有量級按照主坐標軸的量級進行縮放(如果次坐標軸量級大於主坐標軸,那麼就是等比例放大,如果比主坐標軸量級大則縮小)。

針對本例而言,就是將折線圖的數據源量級(0.0~0.5)放大到0~35的區間上,所有的單個指標的縮放比例都是相同的,這樣你在圖上就不會感受到太大的視角誤差。

value1<-data1$Valuevalue21 <- data2[data2$Category == "Categroy1","Value"]value22 <- data2[data2$Category == "Categroy2","Value"]mydata <- data.frame(value1,value21,value22)mydata$value31 <- rescale(mydata$value21,c(0,50))mydata$value32 <- rescale(mydata$value22,c(0,50)) value1 value21 value22 value31 value321 39 0.49 0.48 50.000000 50.0000002 38 0.23 0.38 16.666667 34.8484853 50 0.10 0.48 0.000000 50.0000004 33 0.38 0.15 35.897436 0.0000005 18 0.34 0.40 30.769231 37.8787886 49 0.13 0.16 3.846154 1.515152

這是最終的折現結果,在geom_line中使用rescale函數實際上就是做的這種度量重新自定義映射的過程。

第二次自定義映射——次坐標軸刻度標籤轉換:

僅僅做以上步驟還不夠,因為這隻能保障次坐標軸的數據點位置相對於整個坐標系統而言,不會出現太大的視覺誤差,但是現在的問題是這個圖形對象中有兩套不同的度量,所以必須聲明不同的y軸度量標準,也就是y軸的刻度線及刻度標籤,刻度標籤的定義就是本案例的第二個重點,它仍然是通過rescale函數進行了一次度量的重新映射。

不過這次映射的過程剛好是相反的操作,即將之前已經被標準化到0~50區間內的原始度量標籤通過rescale函數再次標準化到0~0.5區間內,這樣保障顯示在次坐標軸上的度量是符合原始數據極差範圍呢。

說的有些拗口了,實際上以上過程思路很簡單,就是先將數據映射到正確的位置,然後將次坐標軸刻度線度量標籤再按照真實極差進行分布,一虛一實,正好達到了模擬效果。

scale_y_continuous( breaks=pretty_breaks(5), #創建主坐標軸的刻度區間(這裡是5個區間6個刻度點) sec.axis = sec_axis( ~rescale(.,c(0,0.5)), #對次坐標軸刻度標籤的二次映射(極差範圍指定真實極差即可) name = "Categroy", #次坐標軸名稱 labels=sprintf("%d%%",(0:5)*10)) #刻度標籤顯示格式(這裡是百分號) )

思路大體上就是這樣子,希望這一篇文章可以幫到大家!

在線課程請點擊文末原文鏈接:

Hellobi Live | R語言可視化在商務場景中的應用

往期案例數據請移步本人GitHub:

github.com/ljtyduyu/Dat


推薦閱讀:

Quantmod Tutorial:數據獲取(一)
Learn R | SVM of Data Mining(二)
一個小案例,教你如何從數據抓取、數據清洗到數據可視化
索洛模型的R語言簡單實現
《R語言實戰》第四部分第十七章-分類學習筆記

TAG:R编程语言 | ggplot2 | 数据可视化 |