ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • moonBook 패키지를 이용한 도넛 원그래프 작성하기
    R 2018. 12. 27. 20:34

    Pie chart 와 donut chart를 결합한 PieDonut plot을 만들고자 합니다. 여러가지 방법이 있겠지만 저는 ggforce 패키지의 geom_arc_bar() 함수를 이용하였습니다. ggforce 패키지의 vignette는 여기서 보실 수 있습니다.

    https://cran.r-project.org/web/packages/ggforce/vignettes/Visual_Guide.html

    오늘의 문제

    moonBook패키지의 acs데이터를 이용하여 다음과 같은 그림을 그리는 것이 문제였습니다.

    이 그림은 먼저 진단명으로 pie chart를 그리고 각 진단명에 해당하는 환자들의 흡연상태에 따라 세군으로 나누어 같은 계통의 색깔로 doughnut plot을 그린 것입니다

    PieDonut()함수의 이용

    위의 그림를 그리기 위해 PieDonut()함수를 만들어 webr패키지에 넣었습니다. 다음 명령어를 사용하시면 됩니다.

    require(moonBook)
    require(ggplot2)
    require(webr)
    
    PieDonut(acs,aes(Dx,smoking),explode=1,explodeDonut=TRUE,labelposition=1)

    또한 이 함수의 자세한 사용 예는 다음에 있습니다. 단순히 이 함수를 사용하기를 원하시는 분들은 다음의 사용 예만 읽어보시면 됩니다.

    http://rpubs.com/cardiomoon/398623

    어떻게 그릴 것인가?

    먼저 이 그림은 가운데의 pie chart와 주변의 donut chart로 나누어 생각해야 합니다. 먼저 pie chart 를 그리고 pie chart의 색깔을 알아낸 후 흰색에서부터 원하는 색깔로 변해가는 gradient color를 만들어 doughnut의 색깔을 입히면 되겠습니다.

    필요한 패키지 불러오기

    다음 패키지들이 필요합니다.

    require(moonBook)
    require(ztable)
    require(ggplot2)
    require(dplyr)
    require(ggforce)

    1. 준비운동

    ggforce 패키지의 geom_arc_bar()함수를 어떻게 쓸까요? ggforce 패키지 설명서에 있는 예제를 한번 보겠습니다. pie라는 data.frame을 정의합니다.

    pie <- data.frame(
        state = c('eaten', 'eaten but said you didn\'t', 'cat took it', 
                  'for tonight', 'will decompose slowly'),
        start = c(0, 1, 2, 3, 4),
        end = c(1, 2, 3, 4, 2*pi),
        focus = c(0.2, 0, 0, 0, 0),
        stringsAsFactors = FALSE
    )
    pie
                          state start      end focus
    1                     eaten     0 1.000000   0.2
    2 eaten but said you didn't     1 2.000000   0.0
    3               cat took it     2 3.000000   0.0
    4               for tonight     3 4.000000   0.0
    5     will decompose slowly     4 6.283185   0.0

    이 데이터는 모두 5개의 파이로 되어있고 start는 파이의 시작각도(radian), end는 파이의 끝각도, focus는 튀어나오는 거리가 담겨 있습니다. 이 데이터를 이용하여 pie chart 를 그리려면 다음과 같이 합니다.

    p <- ggplot() + theme_no_axes() + coord_fixed()
    
    # For low level control you define the start and end angles yourself
    p + geom_arc_bar(aes(x0 = 0, y0 = 0, r0 = 0, r = 1, start = start, end = end, 
                         fill = state),
                     data = pie)

    geom_arc_bar()함수의 인수를 보면 파이의 중심점의 x,y 좌표를 x0, y0 로 주고 파이모양 원호의 짧은쪽 반지름이 r0, 긴쪽 반지름을 r1으로 줍니다. 이제 arc_bar를 튀어나오게 하려면 explode인수를 줍니다.

    # For low level control you define the start and end angles yourself
    p + geom_arc_bar(aes(x0 = 0, y0 = 0, r0 = 0, r = 1, start = start, end = end, 
                         fill = state, explode=focus),
                     data = pie)

    2. Pie Chart 에 사용할 데이터 만들기

    먼저 pie chart를 그리기 위해서는 세개의 병명에 해당하는 환자들의 명수를 구해야 합니다.

    data<-acs
    pies="Dx"
    donuts="smoking"
    df=table(data[[pies]]) %>% as.data.frame 
    colnames(df)[1]=pies
    df
                   Dx Freq
    1          NSTEMI  153
    2           STEMI  304
    3 Unstable Angina  400

    시작각도와 끝 각도를 정해주어야 하므로 전체 인원 중 각 군에 해당하는 숫자들의 누적합계를 구하여 end로 하고 시작점을 start로 합니다. 전체 인원 수를 total에 넣은 후 start와 end를 각도로 바꾸기 위하여 2*pi로 곱하고 total로 나누어 줍니다.그리고 원호의 반지름은 r0는 0.3, r1은 1을 defalut로 하겠습니다. 시작각도 start는 0으로 합니다. 그리고 튀어나오는 파이는 explode에 저장하고 튀어나오는 거리는 explodePos에 저장합니다. 시작각도와 끝 각도의 중간 각도를 계산해 mid에 넣습니다. 파이가 튀어나올 경우 원점이 달라지므로 원점의 x,y 좌표를 계산해 x,y에 넣습니다. 이때기초적인 삼각함수가 필요합니다. 즉 튀어나오는 거리가 explodePos이고 각도가 mid이므로 x좌표는 거리*sin(각도), y좌표는 거리*cos(각도)가 됩니다. 또한 원호의 중심에 라벨을 붙이기 위해 라벨을 만듭니다. 그리고 원호의 중심좌표 labelx,labely를 구합니다. maxx는 plot의 x좌표 끝입니다.

    explode=1
    explodePos=0.1
    r0=0.3
    r1=1
    r2=1.2
    start=0
    maxx=NULL
    df$end=cumsum(df$Freq)
    df$start=dplyr::lag(df$end)
    df$start[1]=0
    total=sum(df$Freq)
    df$start1=df$start*2*pi/total
    df$end1=df$end*2*pi/total
    df$start1=df$start1+start
    df$end1=df$end1+start
    df$focus=0
    df$focus[explode]=explodePos
    df$mid=(df$start1+df$end1)/2
    df$x=ifelse(df$focus==0,0,df$focus*sin(df$mid))
    df$y=ifelse(df$focus==0,0,df$focus*cos(df$mid))
    df$label=df[[pies]]
    df$ratio=df$Freq/sum(df$Freq)
    df$label=paste0(df$label,"\n(",scales::percent(df$ratio),")")
    df$labelx=(r0+r1)/2*sin(df$mid)+df$x
    df$labely=(r0+r1)/2*cos(df$mid)+df$y
    if(!is.factor(df[[pies]])) df[[pies]]<-factor(df[[pies]])
    df
                   Dx Freq end start   start1     end1 focus       mid
    1          NSTEMI  153 153     0 0.000000 1.121736   0.1 0.5608678
    2           STEMI  304 457   153 1.121736 3.350543   0.0 2.2361395
    3 Unstable Angina  400 857   457 3.350543 6.283185   0.0 4.8168643
               x          y                    label     ratio     labelx
    1 0.05319212 0.08467938          NSTEMI\n(17.9%) 0.1785298  0.3989409
    2 0.00000000 0.00000000           STEMI\n(35.5%) 0.3547258  0.5113583
    3 0.00000000 0.00000000 Unstable Angina\n(46.7%) 0.4667445 -0.6464558
           labely
    1  0.63509538
    2 -0.40126392
    3  0.06778552

    3. Pie Chart 그리기

    ggplot의 기본 색깔은 다음과 같이 구할 수 있습니다.

    gg_color_hue <- function(n) {
            hues = seq(15, 375, length = n + 1)
            hcl(h = hues, l = 65, c = 100)[1:n]
    }
    
    mainCol=gg_color_hue(nrow(df))

    가운데의 pie chart는 이 데이터로 그리면 됩니다. 나중에 파이챠트와 도넛챠트를 같이 그리려면 배경을 투명하게 만들어야 하므로 다음 함수를 사용합니다.

    transparent=function(size=0){
    
            temp=theme(rect= element_rect(fill = 'transparent',size=size),
                       panel.background=element_rect(fill = 'transparent'),
                       panel.border=element_rect(size=size),
                       panel.grid.major=element_blank(),
                       panel.grid.minor=element_blank())
            temp
    }
    
     p <- ggplot() + theme_no_axes() + coord_fixed()
    
     if(is.null(maxx)) {
                    r3=r2+0.3
            } else{
                    r3=maxx
            }
     p1<-p + geom_arc_bar(aes_string(x0 = "x", y0 = "y",
                                            r0 = as.character(r0), r = as.character(r1),
                                            start="start1",end="end1",
                                            fill = pies),alpha=0.7,color="white",
                                 data = df)+transparent()+
                    scale_fill_manual(values=mainCol)+
                    xlim(r3*c(-1,1))+ylim(r3*c(-1,1))+guides(fill=FALSE)
     
     p1 <-p1+geom_text(aes_string(x="labelx",y="labely",label="label"),data=df)
     p1<-p1+annotate("text",x=0,y=0,label=pies)
     p1

    3. 도넛에 사용할 색깔 정하기

    도넛에 사용할 색깔을 정하기 위해 다음과 같은 함수를 사용했습니다.

    makeSubColor=function(main,no=3){
            result=c()
            for(i in 1:length(main)){
                    temp=ztable::gradientColor(main[i],n=no+2)[2:(no+1)]
                    result=c(result,temp)
            }
            result
    }
    
    subColor=makeSubColor(mainCol,no=length(unique(data[[donuts]])))
    subColor
    [1] "#FFDED9" "#FFBCB3" "#FF9A90" "#D0EFCD" "#9FDE9C" "#69CD6C" "#DDE5FF"
    [8] "#BACCFF" "#92B3FF"

    4. 도넛에 사용할 데이터 만들기

    도넛에 사용할 데이터를 만듭니다. 만일 도넛의 일부를 튀어나오게 만들고 싶다면 selected에 지정해줍니다(default는 NULL입니다). 파이의 경우와 마찬가지로 시작각도와 끝각도를 구합니다.

    selected=NULL
    df3=data.frame(table(data[[donuts]],data[[pies]]),stringsAsFactors = FALSE)
    colnames(df3)[1:2]=c(donuts,pies)
    a=table(data[[donuts]],data[[pies]])
    df3$group=rep(colSums(a),each=nrow(a))
    df3$pie=rep(1:ncol(a),each=nrow(a))
    total=sum(df3$Freq)
    
    df3$ratio1=df3$Freq/total
    df3$ratio=scales::percent(df3$Freq/df3$group)
    df3$end=cumsum(df3$Freq)
    df3$start=dplyr::lag(df3$end)
    df3$start[1]=0
    df3$start1=df3$start*2*pi/total
    df3$end1=df3$end*2*pi/total
    df3$start1=df3$start1+start
    df3$end1=df3$end1+start
    df3$mid=(df3$start1+df3$end1)/2
    df3$focus=0
    
    if(!is.null(selected)){
                            df3$focus[selected]=explodePos
    } else if(!is.null(explode)) {
                            selected=c()
                            for(i in 1:length(explode)){
                                    start=1+nrow(a)*(explode[i]-1)
                                    selected=c(selected,start:(start+nrow(a)-1))
                            }
                            selected
                            df3$focus[selected]=explodePos
    }

    파이가 튀어나오는 경우 원점을 다시 계산해야 합니다. 원점의 x좌표와 y좌표를 계산합니다. 라벨을 만들고 라벨의 hjust, vjust를 구합니다.

    df3$x=0
    df3$y=0
    if(!is.null(explode)){
                            explode
                            for(i in 1:length(explode)){
    
                                    xpos=df$focus[explode[i]]*sin(df$mid[explode[i]])
                                    ypos=df$focus[explode[i]]*cos(df$mid[explode[i]])
    
                                    df3$x[df3$pie==explode[i]]=xpos
                                    df3$y[df3$pie==explode[i]]=ypos
                            }
    }
    df3$no=1:nrow(df3)
    df3$label=df3[[donuts]]
    df3$label=paste0(df3$label,"\n(",df3$ratio,")")
    df3$hjust=ifelse((df3$mid %% (2*pi))>pi,1,0)
    df3$vjust=ifelse(((df3$mid %% (2*pi)) <(pi/2))|(df3$mid %% (2*pi) >(pi*3/2)),0,1)
    df3$no=factor(df3$no)
    df3
        smoking              Dx Freq group pie     ratio1 ratio end start
    1 Ex-smoker          NSTEMI   42   153   1 0.04900817 27.5%  42     0
    2     Never          NSTEMI   50   153   1 0.05834306 32.7%  92    42
    3    Smoker          NSTEMI   61   153   1 0.07117853 39.9% 153    92
    4 Ex-smoker           STEMI   66   304   2 0.07701284 21.7% 219   153
    5     Never           STEMI   97   304   2 0.11318553 31.9% 316   219
    6    Smoker           STEMI  141   304   2 0.16452742 46.4% 457   316
    7 Ex-smoker Unstable Angina   96   400   3 0.11201867 24.0% 553   457
    8     Never Unstable Angina  185   400   3 0.21586931 46.2% 738   553
    9    Smoker Unstable Angina  119   400   3 0.13885648 29.8% 857   738
         start1      end1       mid focus          x          y no
    1 0.0000000 0.3079274 0.1539637   0.1 0.05319212 0.08467938  1
    2 0.3079274 0.6745076 0.4912175   0.1 0.05319212 0.08467938  2
    3 0.6745076 1.1217355 0.8981216   0.1 0.05319212 0.08467938  3
    4 1.1217355 1.6056214 1.3636785   0.0 0.00000000 0.00000000  4
    5 1.6056214 2.3167871 1.9612043   0.0 0.00000000 0.00000000  5
    6 2.3167871 3.3505434 2.8336653   0.0 0.00000000 0.00000000  6
    7 3.3505434 4.0543775 3.7024604   0.0 0.00000000 0.00000000  7
    8 4.0543775 5.4107243 4.7325509   0.0 0.00000000 0.00000000  8
    9 5.4107243 6.2831853 5.8469548   0.0 0.00000000 0.00000000  9
                   label hjust vjust
    1 Ex-smoker\n(27.5%)     0     0
    2     Never\n(32.7%)     0     0
    3    Smoker\n(39.9%)     0     0
    4 Ex-smoker\n(21.7%)     0     0
    5     Never\n(31.9%)     0     1
    6    Smoker\n(46.4%)     0     1
    7 Ex-smoker\n(24.0%)     1     1
    8     Never\n(46.2%)     1     0
    9    Smoker\n(29.8%)     1     0

    라벨의 x좌표와 y좌표를 계산합니다. 또한 도넛과 라벨을 이어줄 선분의 좌표를 구합니다.

    explodeDonut=TRUE 
    df3$radius=r2
    if(explodeDonut) df3$radius[df3$focus!=0]=df3$radius[df3$focus!=0]+df3$focus[df3$focus!=0]
    
    df3$segx=df3$radius*sin(df3$mid)+df3$x
    df3$segy=df3$radius*cos(df3$mid)+df3$y
    df3$segxend=(df3$radius+0.05)*sin(df3$mid)+df3$x
    df3$segyend=(df3$radius+0.05)*cos(df3$mid)+df3$y
    
    df3$labelx= (df3$radius)*sin(df3$mid)+df3$x
    df3$labely= (df3$radius)*cos(df3$mid)+df3$y
    
    df3
        smoking              Dx Freq group pie     ratio1 ratio end start
    1 Ex-smoker          NSTEMI   42   153   1 0.04900817 27.5%  42     0
    2     Never          NSTEMI   50   153   1 0.05834306 32.7%  92    42
    3    Smoker          NSTEMI   61   153   1 0.07117853 39.9% 153    92
    4 Ex-smoker           STEMI   66   304   2 0.07701284 21.7% 219   153
    5     Never           STEMI   97   304   2 0.11318553 31.9% 316   219
    6    Smoker           STEMI  141   304   2 0.16452742 46.4% 457   316
    7 Ex-smoker Unstable Angina   96   400   3 0.11201867 24.0% 553   457
    8     Never Unstable Angina  185   400   3 0.21586931 46.2% 738   553
    9    Smoker Unstable Angina  119   400   3 0.13885648 29.8% 857   738
         start1      end1       mid focus          x          y no
    1 0.0000000 0.3079274 0.1539637   0.1 0.05319212 0.08467938  1
    2 0.3079274 0.6745076 0.4912175   0.1 0.05319212 0.08467938  2
    3 0.6745076 1.1217355 0.8981216   0.1 0.05319212 0.08467938  3
    4 1.1217355 1.6056214 1.3636785   0.0 0.00000000 0.00000000  4
    5 1.6056214 2.3167871 1.9612043   0.0 0.00000000 0.00000000  5
    6 2.3167871 3.3505434 2.8336653   0.0 0.00000000 0.00000000  6
    7 3.3505434 4.0543775 3.7024604   0.0 0.00000000 0.00000000  7
    8 4.0543775 5.4107243 4.7325509   0.0 0.00000000 0.00000000  8
    9 5.4107243 6.2831853 5.8469548   0.0 0.00000000 0.00000000  9
                   label hjust vjust radius       segx        segy    segxend
    1 Ex-smoker\n(27.5%)     0     0    1.3  0.2525551  1.36930166  0.2602229
    2     Never\n(32.7%)     0     0    1.3  0.6664019  1.23096635  0.6899868
    3    Smoker\n(39.9%)     0     0    1.3  1.0699974  0.89468375  1.1091053
    4 Ex-smoker\n(21.7%)     0     0    1.2  1.1743532  0.24676823  1.2232846
    5     Never\n(31.9%)     0     1    1.2  1.1097047 -0.45667885  1.1559424
    6    Smoker\n(46.4%)     0     1    1.2  0.3637010 -1.14355655  0.3788552
    7 Ex-smoker\n(24.0%)     1     1    1.2 -0.6383055 -1.01615262 -0.6649015
    8     Never\n(46.2%)     1     0    1.2 -1.1997561  0.02419266 -1.2497459
    9    Smoker\n(29.8%)     1     0    1.2 -0.5070312  1.08762098 -0.5281575
          segyend     labelx      labely
    1  1.41871021  0.2525551  1.36930166
    2  1.27505432  0.6664019  1.23096635
    3  0.92583777  1.0699974  0.89468375
    4  0.25705024  1.1743532  0.24676823
    5 -0.47570713  1.1097047 -0.45667885
    6 -1.19120474  0.3637010 -1.14355655
    7 -1.05849231 -0.6383055 -1.01615262
    8  0.02520068 -1.1997561  0.02419266
    9  1.13293852 -0.5070312  1.08762098

    5. 도넛 그리기

    p3<-p+geom_arc_bar(aes_string(x0 = "x", y0 = "y", r0 = as.character(r1),
                                  r = as.character(r2), start="start1",end="end1",
                                  fill="no", explode="focus"),color="white", 
                       data = df3)
    p3<-p3+transparent()+ scale_fill_manual(values=subColor)+
                            xlim(r3*c(-1,1))+ylim(r3*c(-1,1))+guides(fill=FALSE)
    
                    
                    
    p3<-p3+ geom_segment(aes_string(x="segx",y="segy",
                                    xend="segxend",yend="segyend"),data=df3)+
         geom_text(aes_string(x="segxend",y="segyend",label="label",hjust="hjust",vjust="vjust"),
                  data=df3)
                     
    p3                

    6. 파이와 도넛 같이 그리기

    두개의 같이 그리기 위해 grid 패키지의 viewport를 사용합니다.

    require(grid)
    grid::grid.newpage()
    print(p1,vp=grid::viewport(height=1,width=1))
    print(p3,vp=grid::viewport(height=1,width=1))

    7. 기타

    실제 webr 패키지에 있는 PieDonut 함수는 여러가지 옵션들을 지원하기 위해 훨씬 복잡합니다. 실제 함수의 소스를 분석하고자 하시는 분은 github 페이지를 이용하시기 바랍니다.

    https://github.com/cardiomoon/webr

    github페이지에서 스타를 눌러주시면 개발자들에게 큰힘이 됩니다. 긴 글 읽으시느라 수고하셨습니다.



    [출처] http://rpubs.com/cardiomoon/398646?fbclid=IwAR0d7E8Ur0JHsRnzMr7DwSrY7zyoH-aQFtm_QqQE9nfIPXOT1BJG4VS0PFc

Designed by Tistory.