-
Shiny : 대시보드 배포하기R 이모저모 2019. 4. 24. 21:55
Shiny : 대시보드 배포하기
지난 9일에 올린 'R과 대시보드 : Shiny' 포스팅에서 우리는 shiny를 구성하는 기초 요소들과 그것들이 어떤 상호작용을 통해 shiny앱을 구성하는지를 보았습니다. 이번 글은 좀 더 나아가서, 직접 제작한 대시보드를 shinyapps.io를 통해 배포하는 법을 살펴보고, 예제가 어떻게 만들어졌고 웹에 구동되고 있는지 보도록 하겠습니다.
예제는 제가 올린 shinyapp을 활용할 것이며, 주소는 아래와 같습니다.
https://jmnam47.shinyapps.io/post9_shinyapp/
1. shinyapps.io 사이트 이해
Shiny는 받는 사용자가 R환경을 가지고 있다면 매우 쉽게 사용할 수 있습니다. Gist에 UI.R, SERVER.R이란 이름으로 저장한 뒤 runApp('Gist주소')를 사용해도 되며, 그냥 스크립트를 받은 후 Run App버튼을 눌러서 진행해도 됩니다. 로컬 환경이 설령 맞지 않는다 하더라도 명령어를 활용하여 설치를 해주면 쉽게 맞춰줄 수 있습니다.
하지만 받는 사람이 R환경을 모르는 사람이라면 어떨까요? 물론 설치하도록 가이드라인을 준 후 스크립트를 넘기는 방식을 취할수도 있겠지만, 이는 분석 업무를 하지 않는 사람에겐 불편하고 귀찮은 일일 뿐입니다. 또한 개발자 자신이 짠 소스코드를 사용자에게 넘기고 싶지 않다면 이 때 또한 위와 같은 방식으로 배포할 수 없습니다. 즉 shiny를 설치하지 않고 웹상에서 앱을 배포할 방법이 필요하며, 이를 위해 shinyapps.io에 업로드를 하게 됩니다.
shinyapps 사이트는 매우 유용한 환경이지만, 몇가지 주의해야할 점이 있습니다. 먼저 서버는 리눅스 환경을 사용한다는 것인데요, 이는 평상시엔 문제가 없으나 유니코드(한글 등)가 들어간 글자를 사용할 때 깨지는 문제를 발생시킵니다. 따라서 보내는 스크립트를 UTF-8인코딩으로 지정해줘야하며, ggplot등의 출력물에도 폰트 지정을 별도로 해줘야만 합니다. 이는 showtext패키지를 활용하여 나눔고딕 폰트를 입히게하면 쉽게 해결할 수 있습니다. (이 문제를 푸는 방법에 대해 나와있는 shiny gallery의 예제 : https://shiny.rstudio.com/gallery/unicode-characters.html 의 방법은 잘 먹히지 않으니 밑의 방법을 사용하세요!)
또한 shinyapps의 요금 정책은 무료도 있으나, 이 무료 멤버십은 최대 5개앱, 25 active hour(앱이 켜져있는 시간) 밖에 지원을 하지 않습니다. 만약 이 active hour를 초과하면 앱이 실행되지 않으니 이 점을 유의해야합니다.
마지막으로, 서버에서 앱 코드를 읽을 때 패킹해서 보내야 할 패키지를 library함수 기준으로 읽는다는 점입니다. 즉, 과거 제 포스팅에서 썼었던 sapply를 활용한 패키지 로딩을 하게되면 shinyapps 서버는 무슨 패키지가 필요한지 읽지 못하고 에러를 만들게 됩니다.
1.1 rsconnect 패키지를 활용한 앱 배포
그럼 이 shinyapps라는 곳에 대체 어떻게 배포를 해야 할까요? 먼저 shinyapp에 회원가입을 한 후 Rstudio에 해당 계정을 연결해줘야 합니다. 이를 위해 rsconnet라는 패키지를 이용해서 연결을 하게 되며, 복잡해보이지만 여러분이 계정을 생성해서 로그인 한 후 제일 먼저 보이는 화면에서 자세히 안내를 해줍니다.
위 사진에서 보는것처럼 나오는 코드를 그대로 복사(copy to clipboard버튼 활용)한 후 r콘솔에 치면 연결이 완료됩니다. 놀랍게도, 이 계정연결만 하면 앱을 배포할 준비는 완료됩니다.
이제 앱을 배포해야 합니다. 앱을 배포하기 위한 파일 준비방법은 여러가지가 있습니다. 먼저 앱 이름에 맞게 해당 디렉토리 폴더를 생성한 후, 그곳에 app.R이라고 앱을 구동하는 R스크립트를 넣거나 ui와 서버 부분을 나눠서 각각 ui.R, server.R로 저장해두거나 합니다.
이제 rsconnect 패키지에서 deployApp 함수를 이용해서 샤이니 앱을 배포합니다. 이 때 deployApp함수에는 해당 디렉토리가 들어가야하며, 저같은 경우에는 바탕화면\Blog\post9_shinyapp이 되겠습니다.(밑의 예제는 이미 저 경로를 작업공간으로 지정하였기에 별도의 문제 없이 작동되었습니다)
만약 HTTP599: Time out과 같은 오류가 뜬다면, 해당 코드를 몇번 더 실행해보면 됩니다. 서버 연결성 문제이나 아직 정확한 원인은 모르는 상태입니다. 성공적으로 업데이트를 했다면 아래와 같이 Applications 탭에 앱이 올라가있음을 볼 수 있습니다.
만약 앱을 이미 올린 상태에서 업데이트를 하고 싶다면, 똑같이 deployApp을 한번 더 해주면 아래와 같은 선택 콘솔이 뜨게 되는데 여기서 y를 눌러주면 됩니다.
앱이 일정시간 사용하는 사람이 없다면 sleeping상태에 들어가게 되며, 앱의 status가 변하게 됩니다.
2. 예제 살펴보기 : Clustering 대시보드
제가 사용한 예제는 간단한 PCA 및 k-means clustering을 해주는 대시보드입니다.
그림에서 보다시피 대시보드의 사이드바는 2개의 탭과 하나의 슬라이더 바, 그리고 액션버튼(Do k-means clustering)으로 구성되어 있으며, 위에 보이는 load data 탭은 저번에 소개한 fileInput(파일 불러오는 기능)과 Data Table, 액션버튼 등으로 구성되어 있습니다. 이제 이 대시보드의 구성요소를 저번처럼 하나하나 뜯어보도록 하겠습니다.
*지난 포스팅에서 설명한 부분은 간략히 넘어가겠습니다
2.1 헤더와 사이드바
헤더는 말 그대로 제목, 사이드바는 앞서 얘기한대로 그림 왼쪽의 메뉴들을 담는 공간을 의미합니다. shinydashboard패키지에서는 각각 dashboardHeader, dashboardSidebar 함수로 구현됩니다
제목 부분은 딱히 건드릴만한 것이 별로 없습니다. 만약 어떤 메세지나 task, 알림을 오른쪽 상단에 띄워주고 싶다면 messageItem, notificationItem, taskItem을 dropdownMenu라는 함수 안에 넣고 header부분에 포함하여 쓸 수 있으나, 그리 자주 쓰는 기능도 아니고 dashboardHeader함수의 설명 예제만 봐도 충분히 따라할 수 있기에 넘어가도록 하겠습니다.
사이드바에서는 보통 sidebarMenu로 형태를 정하게 됩니다. 이 함수를 통해 어떤 메뉴 아이템, 즉 탭이 들어갈 지 확인할 수 있으며, menuItem이란 함수에서 tabName을 지정함으로서 해당 메뉴를 클릭하면 다른 화면을 쓸 수 있게 해줍니다. 즉 Load data라고 써져있는 메뉴를 클릭하면 data_tab이라는 탭을 body부분에서 출력하게 되며, PCA and Clustering이란 탭을 누르면 clust_tab이란 탭을 출력하게 됩니다. 이 외에도 평범한 인풋, 액션버튼들을 집어넣을 수 있는데, 이 예제에서는 슬라이더 인풋과 버튼을 넣었습니다.
2.2 몸통 : tab
사이드바에서 메뉴 형식으로 tab들을 지정하였으므로, 예제에서는 탭을 활용하여 몸통을 구분하게 됩니다. 이 몸통, 즉 body부분이 오른쪽 대부분 칸을 차지하는 관계로 대시보드의 핵심 요소를 담게 됩니다.
dashboardBody라는 몸통구성 함수 안에서 tabItmes라는 함수로 앞으로 탭들을 담게 될것을 선언하며, 뒤에 tabItem함수로 탭을 정의하게 됩니다. 첫번째 탭 아이템 함수에서는 사이드바에서 지정한 data_tab을 정의하며, 파일을 업로드하는 기능과 버튼 2개, 그리고 렌더링된 데이터 테이블을 보여줄 output함수를 담습니다.
두번째 탭에선 clust_tab을 아이디로 사용하며, 여기서는 tabBox를 이용해서 탭 안에 작은 탭을 만들게 합니다. 이 탭들은 아래와 같은 모양을 하며, tabPanel이라는 함수로 탭 안의 탭 내용들을 정의할 수 있습니다.
첫번째 탭박스는 PCA결과에 따른 plot(요소마다 데이터의 분산을 얼마나 설명할 수 있는지), 계층 클러스터링을 한 결과를 나타낸 덴드로그램, 그리고 상위 2개 요소를 가지고 데이터를 보여주는 plot을 가지고 있습니다.
2.3 서버
서버에서 처음으로 볼 부분은 reactive값을 담는 공간을 정의하는 것입니다. 저번 포스트에서 설명한것처럼 reactive 값은 앱의 input에 반응하는 값을 저장하기 위한 공간이고, 이 값을 일일히 저장해주기엔 번거로우므로 vars라는 리스트를 생성해서 이 안에 값을 저장하도록 합니다. 이 작업은 reactive값 하나만 지정할 경우 해당 값을 다른 과정에서 덮어씌울 수가 없는 문제를 해결하기 위해서기도 하여 꼭 필요한 작업입니다.
이후 observeEvent함수를 이용해서 데이터를 읽는 동작을 정의합니다. UI부분에서 fileInput의 ID를 'file'로 정의했기에 input의 file 객체의 datapath가 생기면 사용자가 browse 버튼으로 데이터를 올렸다고 생각하여 데이터를 읽는 동작을 진행하도록 합니다. 이 때 grepl함수를 활용하여 데이터가 .csv를 가지고 있는지 아닌지를 파악하여 이 데이터를 fread로 읽을지, 아니면 read_excel함수로 읽을지 선택하게 합니다.
다음은 데이터 변환입니다. PCA나 k-means 클러스터링은 수치변수만 사용할 수 있으며, 클러스터링의 경우 scaling을 해야할 수도 있기 때문에 이 작업을 위해 첫번째 탭에 만든 버튼 두 개(액션버튼, ID : select와 normalization)를 활용합니다. 먼저 수치 변수를 찾기 위해 열별로 class함수를 사용하고 class가 numeric, integer, double인지 확인하며, scaling을 할 경우 minmax normalization을 하도록 합니다. 중요한 점은 이 때 lapply를 해야 데이터프레임으로 형식이 깨지지 않고 바꾸기 편합니다.
이제 만들어진 데이터를 렌더링해서 UI에 보여주도록 합니다 renderDataTable함수를 활용합니다.
이제 PCA 결과물을 보여주기 위한 객체들을 정의합니다. princomp 함수를 사용한 결과물은 reactive값으로 저장하게 하며, 여기서 vars$df를 사용하는 이유는 PCA에서는 스케일링을 하지 않기 때문입니다.
다음으로 위에서 언급한 첫번째 탭박스에 들어갈 plot 3개를 정의합니다. 각각 screeplot, dendrogram, 2dplot으로 위의 UI에서 정의한 ID대로 output에 정의해줍니다.
여기서 주의할점은 pca는 reactive객체이므로 객체를 쓰려면 pca라고 쓰지 않고 pca()라고 써야 한다는 점입니다. 또한 지금까지 정의해온 output들은 서로 전 단계를 필요로 하기 때문에 꼭 req함수를 이용해서 필요로 함을 명시해줍니다.
다음은 kmeans 클러스터링을 진행합니다. 여기서는 사이드바에서 정의한 액션버튼을 감지하여 진행하도록 하며, 사이드바의 슬라이더를 통해 조정된 k값을 쓰도록 합니다. 만들어진 객체는 vars에 저장하도록 합니다.
마지막으로 두번째 탭의 두번째 탭박스에 들어갈 결과물 3개를 정의합니다. 각각 텍스트, plot, rglwidget이므로 해당 양식에 맞게 정의를 해줍니다. rglWidget의 경우 rgl패키지로 만드는 3d plot을 샤이니 대시보드 안에서 띄우도록 도와주는 함수로, 아래와 같이 renderRglwidget에 화면을 정의(open3d)하고 점(points3d)을 집어넣은 후 축(axes3d)을 정의한 후 위젯 정의를 마치면 됩니다.
이렇게 모든 정의를 마치게되면 다음과 같이 기능들이 구현되어 있음을 확인할 수 있습니다.
3. 마치며
위에서 보듯이 샤이니는 간편하게 자기가 원하는 앱을 만들기 좋은 도구입니다. 하지만 제약사항 또한 많은 편이며, 한글 사용이 불편하고 작동이 느리다는 단점도 있습니다. 또 가장 중요하고 명심해야 할 점은, 샤이니는 어디까지나 R을 편히 배포하기 위한 도구일 뿐이며, 이 도구에 어떤 내용을 담는지가 더 중요하다는 것입니다.
[컨텐츠 코드]
'R 이모저모' 카테고리의 다른 글
R과 네트워크 분석(2) (0) 2019.05.29 R과 네트워크 분석 (1) (0) 2019.05.12 R과 워드 (0) 2019.04.19 R과 대시보드 : Shiny (0) 2019.04.09 R과 병렬처리 (4) 2019.04.03