web application development with r using shiny · is the author of web application development with...
TRANSCRIPT
WebApplicationDevelopmentwithRUsingShinyThirdEdition
Buildstunninggraphicsandinteractivedatavisualizationstodelivercutting-edgeanalytics
ChrisBeeleyShitalkumarR.Sukhdeve
BIRMINGHAM-MUMBAI
WebApplicationDevelopmentwithRUsingShinyThirdEditionCopyright©2018PacktPublishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthors,norPacktPublishingoritsdealersanddistributors,willbeheldliableforanydamagescausedorallegedtohavebeencauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
AcquisitionEditor:DevanshiDoshiContentDevelopmentEditor:AishwaryaGawankarTechnicalEditor:RutujaVazeCopyEditor:SafisEditingProjectCoordinator:SheejalShahProofreader:SafisEditingIndexer:PratikShirodkarGraphics:AlishonMendonsaProductionCoordinator:ArvindkumarGupta
Firstpublished:October2013Secondpublished:January2016Thirdedition:September2018
Productionreference:1260918
PublishedbyPacktPublishingLtd.LiveryPlace35LiveryStreetBirminghamB32PB,UK.
ISBN978-1-78899-312-8
www.packtpub.com
mapt.io
Maptisanonlinedigitallibrarythatgivesyoufullaccesstoover5,000booksandvideos,aswellasindustryleadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.Formoreinformation,pleasevisitourwebsite.
Whysubscribe?SpendlesstimelearningandmoretimecodingwithpracticaleBooksandVideosfromover4,000industryprofessionals
ImproveyourlearningwithSkillPlansbuiltespeciallyforyou
GetafreeeBookorvideoeverymonth
Maptisfullysearchable
Copyandpaste,print,andbookmarkcontent
Packt.comDidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.packt.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatcustomercare@packtpub.comformoredetails.
Atwww.packt.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewsletters,andreceiveexclusivediscountsandoffersonPacktbooksandeBooks.
Contributors
AbouttheauthorsChrisBeeleyhasbeenusingRandotheropensourcesoftwarefortenyearstobettercapture,analyze,andvisualizedatainthehealthcaresectorintheUK.HeistheauthorofWebApplicationDevelopmentwithRUsingShiny.Heworksfull-time,developingsoftwaretostore,collate,andpresentquestionnairedatausingopentechnologies(MySQL,PHP,R,andShiny),withaparticularemphasisonusingthewebandShinytoproducesimpleandattractivedatasummaries.ChrisisworkinghardtoincreasetheuseofRandShiny,bothwithinhisownorganizationandthroughouttherestofthehealthcaresector,aswelltoenablehisorganizationtobetteruseavarietyofotherdatasciencetools.ChrishasalsodeliveredtalksaboutShinyalloverthecountry.
ShitalkumarR.SukhdeveisaseniordatascientistatPTSmartfrenTelecomTbk,Jakarta,Indonesia.Onhiscareerjourney,hehasworkedwithRelianceJioasadatascientist,entrepreneur,andcorporatetrainer.Hehastrainedover1,000professionalsandstudentsandhasdeliveredover200lecturesonRandmachinelearning.ResearchanddevelopmentinAI-drivenself-optimizingnetworks,predictivemaintenance,optimalnetworkquality,anomalydetection,andcustomerexperiencemanagementfor4GLTEnetworksareallareasofinteresttoShitalkumar.HeisveryexperiencedwithR,Spark,RShiny,H2O,Python,KNIME,theHadoopecosystem,MapReduce,Hive,andconfiguringtheopensourceRShinyserverformachinelearningmodelsanddashboarddeployment.
Iwouldliketothankmyfather,Mr.RajendraSukhdeve;mother,Mrs.ManjuSukhdeve;wife,Sandika;family;andfriendsforalwaysbelievinginmeandallowingmetodevotetimetowritingthisbook.MyspecialthankstoMr.NikolaSucevic,Mr.BhupeshDaheria,andMr.PramodKolhatkarforgivingmeanopportunitytoworkwiththemandexploredatascience.ThankstoPacktPublishingandteamfortheircontinuoussupportandguidance.
AboutthereviewerAbhinavAgrawalhasmorethan13years'ITexperienceandhasworkedwithtopconsultingfirmsandUSfinancialinstitutions.Hisexpertiseliesinthebankingandfinancialservicesdomainandheisaseasonedproject/programmanagementprofessionalwithapassionfordataanalytics,machinelearning,artificialintelligence,roboticsprocessautomation,digitaltransformation,andemergingdigitalpaymentssolutions.HestartedusingRandShinyin2014todevelopweb-basedanalyticssolutionsforclients.HeworksasaprogrammanagerandisafreelanceRinstructorandRShinyconsultant.Inhissparetime,helovestomentordatasciencestudents,makedataanalytics-relatedinstructionalvideosonYouTube,andshareknowledgewiththecommunity.
PacktissearchingforauthorslikeyouIfyou'reinterestedinbecominganauthorforPackt,pleasevisitauthors.packtpub.comandapplytoday.Wehaveworkedwiththousandsofdevelopersandtechprofessionals,justlikeyou,tohelpthemsharetheirinsightwiththeglobaltechcommunity.Youcanmakeageneralapplication,applyforaspecifichottopicthatwearerecruitinganauthorfor,orsubmityourownidea.
TableofContentsTitlePage
CopyrightandCredits
WebApplicationDevelopmentwithRUsingShinyThirdEdition
www.PacktPub.com
Whysubscribe?
Packt.com
Contributors
Abouttheauthors
Aboutthereviewer
Packtissearchingforauthorslikeyou
Preface
Whothisbookisfor
Whatthisbookcovers
Togetthemostoutofthisbook
Downloadtheexamplecodefiles
Downloadthecolorimages
Conventionsused
Getintouch
Reviews
1. BeginningRandShinyInstallingR
TheRconsole
CodeeditorsandIDEs
LearningR
Gettinghelp
Loadingdata
Datatypesandstructures
Dataframes,lists,arrays,andmatrices
Variabletypes
Functions
Objects
Basegraphicsandggplot2
Barchart
Linechart
Introductiontothetidyverse
Cecin'estpasunepipe
Gapminder
AsimpleShiny-enabledlineplot
InstallingShinyandrunningtheexamples
Summary
2. ShinyFirstStepsTypesofShinyapplication
InteractiveShinydocumentsinRMarkdown
AminimalexampleofafullShinyapplication
Theui.Roftheminimalexample
AnoteonHTMLhelperfunctions
Thefinishedinterface
Theserver.Roftheminimalexample
Theprogramstructure
Anoptionalexercise
Embeddingapplicationsindocuments
Widgettypes
TheGapminderapplication
TheUI
Dataprocessing
Reactiveobjects
Outputs
Textsummary
Trendgraphs
Amapusingleaflet
Advancedlayoutfeatures
Summary
3. IntegratingShinywithHTMLRunningtheapplicationsandcode
ShinyandHTML
CustomHTMLlinksinShiny
ui.R
server.R
AminimalHTMLinterface
index.html
server.R
IncludingaShinyapponawebpage
HTMLtemplates
Inlinetemplatecode
server.R
ui.Randtemplate.html
Definingcodeintheui.Rfile
ui.R
Takeastepbackandrewind
Exercise
Debugging
Bootstrap3andShiny
Summary
4. MasteringShiny'sUIFunctionsShiny'slayoutfunctions
Simple
Complete
Doityourself
Combininglayoutfunctions
StreamliningtheUIbyhidingelements
NamingtabPanelelements
BeautifultableswithDataTable
Reactiveuserinterfaces
Thereactiveuserinterfaceexample– server.R
Thereactiveuserinterfaceexample– ui.R
Progressbars
Progressbarwithshinycssloaders
Modals
AlternativeShinydesigns
Summary
5. EasyJavaScriptandCustomJavaScriptFunctionsJavaScriptandShiny
Example1– readingandwritingtheDOM
ui.R
appendText.js
Example2 –sendingmessagesbetweenclientandserver
ui.R
server.R
dropdownDepend.js
Shinyjs
Extendshinyjs
ui.R
server.R
JavaScript
RespondingtoeventsinJavaScript
htmlwidgets
Dygraphs
rCharts
d3heatmap
threejs
Summary
6. DashboardsApplicationsinthischapter
Flexdashboards
Sidebarapplicationwithextrastyling
AddingiconstoyourUI
Usingshinythemes
Usingthegridlayout
ui.R
Fulldashboard
Notifications
Infoboxes
ui.R
GoogleChartsgauge
ResizingtheGooglechart
ui.R
Summary
7. PowerShinyAnimation
ReadingclientinformationandGETrequestsinShiny
CustominterfacesfromGETstrings
Downloadinggraphicsandreports
Downloadablereportswithknitr
Downloadinganduploadingdata
Bookmarking
Bookmarkingstate
EncodingthestateintoaURL
Single-fileapplication
Multiple-fileapplication
Bookmarkingbysavingthestatetotheserver
Interactiveplots
Interactivetables
Rowselection
Columnselection
CellSelection
Linkinginteractivewidgets
Shinygadgets
Addingapassword
Summary
8. CodePatternsinShinyApplicationsReactivityinRShiny
Acloserlookatreactivity
Controllingspecificinputwiththeisolate()function
Runningreactivefunctionsovertime(executionscheduling)
Event-handlingusingobserveEventandeventReactive
Functionsandmodules
Shinytest
Debugging
Handlingerrors(includingvalidate()andreq())
Validate
Handlingmissinginputwithreq()
ProfilingRcode
Debounceandthrottle
Summary
9. PersistentStorageandSharingShinyApplicationsSharingoverGitHub
AnintroductiontoGit
UsingGitandGitHubwithinRstudio
ProjectsinRStudio(h3)
SharingapplicationsusingGit
Sharingusing.zipand.tar
Sharingwiththeworld
Shinyapps.io
Shinyapps.iowithoutRStudio
Shinyserver
RunningShinyapponAmazonAWS
Scoping,loading,andreusingdatainShinyapplications
Temporarydatainput/output
Persistentdatastorage
DatabaseusingDplyr,DBI,andPOOL
SQLInjection
Summary
OtherBooksYouMayEnjoy
Leaveareview-letotherreadersknowwhatyouthink
PrefaceWiththisbook,youwillbeabletoharnessthegraphicalandstatisticalpowerofRandrapidlydevelopinteractiveandengaginguserinterfacesusingthesuperbShinypackage,whichmakesprogrammingforuserinteractionsimple.Risahighlyflexibleandpowerfultoolusedforanalyzingandvisualizingdata.ShinyistheperfectcompaniontoR,makingitquickandsimpletoshareanalysisandgraphicsfromRforuserstotheninteractwithandqueryovertheweb.LetShinydothehardworkwhileyouspendyourtimegeneratingcontentandstyling,ratherthanwritingcodetohandleuserinputs.Thisbookisfullofpracticalexamplesandshowsyouhowtowritecutting-edgeinteractivecontentfortheweb,rightfromaminimalexampleallthewaytofullystyledandextensibleapplications.
ThisbookincludesanintroductiontoShinyandRandtakesyouallthewaytoadvancedfunctionsinShinyaswellasusingShinyinconjunctionwithHTML,CSS,andJavaScripttoproduceattractiveandhighlyinteractiveapplicationsquicklyandeasily.ItalsoincludesadetailedlookatotherpackagesavailableforR,whichcanbeusedinconjunctionwithShinytoproducedashboards,maps,advancedD3graphics,andmuchmore.
WhothisbookisforThisbookisforanybodywhowantstoproduceinteractivedatasummariesovertheweb,whetheryouwanttosharethemwithafewcolleaguesorthewholeworld.
WhatthisbookcoversChapter1,BeginningRandShiny,runsthroughthebasicsofstatisticalgraphics,datainput,andanalysiswithR.WealsodiscussdatastructuresandprogrammingbasicsinRinordertogiveyouathoroughgroundinginRbeforewelookatShiny.
Chapter2,ShinyFirstSteps,helpsyoubuildyourfirstShinyapplication.WebeginbysimplyaddinginteractivecontenttoadocumentwritteninMarkdown;andthendelvedeeperintoShiny,buildingaveryprimitiveandminimalexample;andfinally,we'lllookatmorecomplexapplicationsandtheinputsandoutputsnecessarytobuildthem.
Chapter3,IntegratingShinywithHTML,covershowShinyworkswithexistingwebcontentinHTMLandCSS.WediscusstheShinyhelperfunctionsthatallowyoutoaddacustomHTMLtoastandardShinyapplicationandhowtobuildaminimalexampleofaShinyapplicationinyourownrawHTMLwithShinyrunninginthebackground.We'llalsogetintotheuseofHTMLtemplates,whichmakeintegratingShinywithHTMLeasy.
Chapter4,MasteringShiny'sUIFunctions,describesallthedifferentwaysthatShinyofferstohelpyouachievethelayoutandappearancethatyouwantyourapplicationtohave.Itdiscusseshowtoshowandhideelementsoftheinterface,aswellashowtomaketheinterfacereacttothestateoftheapplication.Producingattractivedatatablesisdiscussed,aswellashowtogiveyourusersmessageswithprogressbarsandmodals.
Chapter5,EasyJavaScriptandCustomJavaScriptFunctions,coversusingJavaScriptwithShiny,rightfromaddingsimpleJavaScriptrightonthepagetoenhanceaprogram'sappearanceorfunctionality,tosendingmessagestoandfromtheclient'sbrowserusingmessagestoandfromJavaScript.Theuseoftheshinyjsandhtmlwidgetspackagesisalsodiscussed,whichfurtheraddtoyourabilitytoaddcustomorcannedJavaScripttoaShinyapplication.
Chapter6,Dashboards,includesacoupleofdifferenttypesofShinydashboard,anddescribeshowtomakeattractiveShinydashboards,usingcolor,icons,anda
widerangeofinputsandoutputs,aswellashowtolaythemoutusingtheveryflexiblelayoutfunctions,whichcanbeaccessedwithaShinydashboard.
Chapter7,PowerShiny,includesmanypowerfulfeaturesofShiny,suchasanimatingplots,readingclientinformation,andGETrequestsinShiny.Wewillgothroughgraphicsandreportgenerationandhowtodownloadthemusingknitr.Downloadinganduploadingisalsoaninterestingpartofanyapplication,andwe'lltakealookatitinShinywithsomeexamples.Bookmarkingthestateoftheapplicationisanadd-ontoregeneratetheoutputontheapplication.Wewillseeademonstrationoffastapplicationdevelopmentusingwidgetsandgadgets.Attheendofthechapter,wewillseehowtoauthenticatetheapplicationusingapassword.
Chapter8,CodePatternsinShinyApplications,coversthecodingpatternsavailableinShiny.WewilldiscussreactivityinRShiny,controllingspecificinputwiththeisolate()function,runningreactivefunctionsovertime,eventhandlingusingtheobserveEventfunctionsandtheShinytestmodules,debugging,handlingerrors(includingvalidate()andreq()),profilingRcode,debounce,andthrottle.
Chapter9,PersistentStorageandSharingShinyApplications,willexplorehowtokeepyourcodeonGitHub.ThischapterwillincludeanintroductiontoGitHubandhowtointegrateGitwithRStudio.WewillalsolearnhowtoshareyourreportsandaliveapplicationwithShinyapps.io.Thischapterwillalsofocusonthedeploymentoptionsavailable,suchasShinyServerandrunningShinyinAWS.WewillgothroughsomeoftheconceptsthatarevitalfordevelopingagoodShinyapplication,suchasscoping,loading,andreusingdatainShinyapplications.We'llalsolookattemporarydatainput/output,permanentdatafunctions,databases,SQLinjection,anddatabaseswiththepoolpackage.
TogetthemostoutofthisbookNopreviousexperiencewithR,Shiny,HTML,orCSSisrequiredtousethisbook,althoughyoushouldpossesssomepreviousexperiencewithprogramminginadifferentlanguage.ThisbookcanbeusedwiththeWindows,macOS,orLinuxoperatingsystems.ItrequirestheinstallationofRaswellasseveraluser-contributedpackageswithinR.Randitsassociatedpackagesareallavailableforfree.TheRStudioIDEisrecommendedbecauseitsimplifiessomeofthetaskscoveredinthisbook,butisnotessential.Again,thissoftwareisavailablefreeofcharge.
DownloadtheexamplecodefilesYoucandownloadtheexamplecodefilesforthisbookfromyouraccountatwww.packt.com.Ifyoupurchasedthisbookelsewhere,youcanvisitwww.packt.com/supportandregistertohavethefilesemaileddirectlytoyou.
Youcandownloadthecodefilesbyfollowingthesesteps:
1. Loginorregisteratwww.packt.com.2. SelecttheSUPPORTtab.3. ClickonCodeDownloads&Errata.4. EnterthenameofthebookintheSearchboxandfollowtheonscreen
instructions.
Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:
WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinux
ThecodebundleforthebookisalsohostedonGitHubathttps://github.com/PacktPublishing/Web-Application-Development-with-R-Using-Shiny-third-edition.Incasethere'sanupdatetothecode,itwillbeupdatedontheexistingGitHubrepository.
Wealsohaveothercodebundlesfromourrichcatalogofbooksandvideosavailableathttps://github.com/PacktPublishing/.Checkthemout!
DownloadthecolorimagesWealsoprovideaPDFfilethathascolorimagesofthescreenshots/diagramsusedinthisbook.Youcandownloadithere:https://www.packtpub.com/sites/default/files/downloads/9781788993128_ColorImages.pdf.
ConventionsusedThereareanumberoftextconventionsusedthroughoutthisbook.
CodeInText:Indicatescodewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandles.Hereisanexample:"The[1]phrasetellsyouthatRreturnedoneresult;inthiscase,4."
Ablockofcodeissetasfollows:
<ul>
<li>Firstbullet</li>
<li>Secondbullet</li>
<li>Thirdbullet</li>
</ul>
Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:
tabsetPanel(id="theTabs",
tabPanel("Summary",textOutput("summary"),
value="summary"),
tabPanel("Trend",plotOutput("trend"),
value="trend"),
tabPanel("Map",leafletOutput("map"),
p("Mapdataisfromthemostrecentyearintheselectedrange"),
value="map")
)
Anycommand-lineinputoroutputiswrittenasfollows:
>2+2
[1]4
Bold:Indicatesanewterm,animportantword,orwordsthatyouseeonscreen.Forexample,wordsinmenusordialogboxesappearinthetextlikethis.Hereisanexample:"Tosetupanewproject,gotoFile|NewProjectinRStudio."
Warningsorimportantnotesappearlikethis.
Tipsandtricksappearlikethis.
GetintouchFeedbackfromourreadersisalwayswelcome.
Generalfeedback:Ifyouhavequestionsaboutanyaspectofthisbook,mentionthebooktitleinthesubjectofyourmessageandemailusatcustomercare@packtpub.com.
Errata:Althoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyouhavefoundamistakeinthisbook,wewouldbegratefulifyouwouldreportthistous.Pleasevisitwww.packt.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetails.
Piracy:IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,wewouldbegratefulifyouwouldprovideuswiththelocationaddressorwebsitename.Pleasecontactusatcopyright@packt.comwithalinktothematerial.
Ifyouareinterestedinbecominganauthor:Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,pleasevisitauthors.packtpub.com.
ReviewsPleaseleaveareview.Onceyouhavereadandusedthisbook,whynotleaveareviewonthesitethatyoupurchaseditfrom?Potentialreaderscanthenseeanduseyourunbiasedopiniontomakepurchasedecisions,weatPacktcanunderstandwhatyouthinkaboutourproducts,andourauthorscanseeyourfeedbackontheirbook.Thankyou!
FormoreinformationaboutPackt,pleasevisitpackt.com.
BeginningRandShinyRisfreeandopensource,andisthepre-eminenttoolforstatisticiansanddatascientists.Ithasmorethan6,000user-contributedpackages,whichhelpusersworkinginfieldsasdiverseaschemistry,biology,physics,finance,psychology,andmedicalscience.R'sextremelypowerfulandflexiblestatisticalgraphicsgreatlyhelptheseusersintheirwork.
Inrecentyears,Rhasbecomemoreandmorepopular,andthereareanincreasingnumberofpackagesforRthatmakecleaning,analyzing,andpresentingdataonthewebeasyforeverybody.TheShinypackageinparticularmakesitincrediblyeasytodeliverinteractivedatasummariesandqueriestoendusersthroughanymodernwebbrowser.You'rereadingthisbookbecauseyouwanttousethesepowerfulandflexibletoolsforyourowncontent.
Thisbookwillshowyouhow,rightfromwhenyoujuststartwithR,youcanbuildyourowninterfaceswithShinyandintegratethemwithyourownwebsites.Inthischapter,we'regoingtocoverthefollowingtopics:
DownloadingandinstallingRChoosingacode-editingenvironment/IDELookingatthepowerofRLearningabouthowRStudioandcontributedpackagescanmakewritingcode,managingprojects,andworkingwithdataeasierInstallingShinyandrunningtheexamplesHowtousesomeofShiny'sawesomeapplications,andsomeoftheelementsoftheShinyapplicationthatwewillbuildoverthecourseofthisbook
Risabigsubject,andthisisawhistle-stoptour,soifyougetalittlelostalongtheway,don'tworry.Thischapterisreallyallaboutshowingyouwhat'soutthere,andwillbothencourageyoutodelvedeeperintothebitsthatinterestyouandshowyouplacesyoucangoforhelpifyouwanttolearnmoreonaparticularsubject.
InstallingRRisavailableforWindows,MacOSX,andLinuxatcran.r-project.org.Thesourcecodeisalsoavailableatthesameaddress.ItisalsoincludedinmanyLinuxpackagemanagementsystems;Linuxusersareadvisedtocheckbeforedownloadingfromtheweb.DetailsoninstallingfromsourceorbinaryforWindows,MacOSX,andLinuxareallavailableatcran.r-project.org/doc/manuals/R-admin.html.
TheRconsoleWindowsandMacOSXuserscanruntheRapplicationtolaunchtheRconsole.LinuxandMacOSXuserscanalsoruntheRconsolestraightfromtheTerminalbytypingR.
Ineithercase,theRconsoleitselfwilllooksomethinglikethefollowingscreenshot:
RwillrespondtoyourcommandsrightfromtheTerminal.Let'shaveago.RunthefollowingcommandintheRconsole:
>2+2
[1]4
The[1]phrasetellsyouthatRreturnedoneresult,inthiscase,4.ThefollowingcommandshowsyouhowtoprintHelloworld:
>print("Helloworld!")
[1]"Helloworld!"
Thefollowingcommandshowsthemultiplesofpi:
>1:10*pi
[1]3.1415936.2831859.42477812.56637115.70796318.849556
[7]21.99114925.13274128.27433431.415927
Thisexampleillustratesvector-basedprogramminginR.The1:10phrasegeneratesthenumbers1:10asavector,andeachisthenmultipliedbypi,whichreturnsanothervector,theelementseachbeingpitimeslargerthantheoriginal.OperatingonvectorsisanimportantpartofwritingsimpleandefficientRcode.Asyoucansee,Ragainindexesthevaluesitreturnsattheconsole,withtheseventhvaluebeing21.99.
OneofthebigstrengthsofusingRisthegraphicscapability,whichisexcellent,eveninavanillainstallationofR(thesegraphicsarereferredtoasthebasegraphicsbecausetheyshipwithR).Whenaddingpackagessuchasggplot2andsomeoftheJavaScript-basedpackages,Rbecomesagraphicaltourdeforce,whetherproducingstatistical,mathematical,ortopographicalfigures,orindeedanyothertypeofgraphicaloutput.Togetaflavorofthepowerofthebasegraphics,simplytypethefollowingintheConsoleandseethetypesofplotsthatcanbemadeusingR:
>demo(graphics)
Youcanalsotypethefollowingcommand:
>demo(persp)
Therewillbemoreonggplot2andbasegraphicslaterinthechapter.
Enjoy!TherearemanymoreexamplesofRgraphicsatr-graph-gallery.com.
CodeeditorsandIDEsTheWindowsandOSXversionsofRbothcomewithbuilt-incodeeditors,whichallowcodetobeedited,saved,andsenttotheRconsole.It'shardtorecommendthatyouusethisbecauseitisratherprimitive.MostuserswouldbebestservedbyRStudio(foundatrstudio.com/),whichincludesprojectmanagementandversioncontrol(includingsupportforGit,whichiscoveredinChapter9,PersistentStorageandSharingShinyApplications),theviewingofdataandgraphics,codecompletion,packagemanagement,andmanyotherfeatures.ThefollowingisanillustrativescreenshotofanRStudiosession:
Ascanbeseen,inthetop-leftcorner,thereisthecode-editingpane(withsyntaxhighlighting).Movingclockwisefromtherewilltakeyoutotheenvironmentpane(inwhichyoucanseethedifferentobjectsthatareloadedintothesession),whichistheviewingpanecontainingvariousoptionssuchasFiles,Plots,Build,Help,andfinally,atthebottomleft,theConsole.Inthemiddle,thereisoneofthemostusefulfeaturesofRStudio,theabilitytoviewdataframes.ThisviewcanbecreatedbyclickingadataframeintheEnvironmentpanelatthetopright.
Thisfunctionalsoenablessortingandfilteringbycolumn.
However,ifyoualreadyuseanIDEforothertypesofcode,itisquitelikelythatRcanbewellintegratedintoit.ExamplesofIDEswithgoodRintegrationincludethefollowing:
EmacswiththeEmacsSpeaksStatisticspluginVimwiththeVim-RpluginEclipsewiththeStatETplugin
LearningRTherearealmostasmanyusesforRastherearepeopleusingit.Itisnotpossiblethatyourspecificneedswillbecoveredinthisbook.However,youprobablywanttouseRtoprocess,query,andvisualizedata,suchassalesfigures,satisfactionsurveys,concurrentusers,sportingresults,orwhatevertypesofdatayourorganizationprocesses.Fornow,let'sjusttakealookatthebasics.
GettinghelpTherearemanybooksandonlinematerialsthatcoverallaspectsofR.ThenameRcanmakeitdifficulttocomeupwithusefulwebsearchhits(substitutingCRANforRcansometimeshelp);nonetheless,searchingforRtutorialbringsupusefulresults.Someusefulresourcesincludethefollowing:
AnexcellentintroductiontosyntaxanddatastructuresinR(atgoo.gl/M0RQ5z)VideosonusingRfromGoogle(atgoo.gl/A3uRsh)Swirl(atswirlstats.com)Quick-R(atstatmethods.net)
AttheRconsole,thecodephrase?functionnamecanbeusedtoshowthehelpfileforafunction.Forexample,?helpbringsuphelpmaterials,andusing??helpwillbringupalistofpotentiallyrelevantfunctionsfrominstalledpackages.
SubscribingtoandaskingquestionsontheR-helpmailinglistatstat.ethz.ch/mailman/listinfo/r-helpallowsyoutocommunicatewithsomeoftheleadingfiguresintheRcommunity,aswellasmanyothertalentedenthusiasts.Readthepostingguideanddoyourresearchbeforeyouaskanyquestions,becauseit'sabusyandsometimesunforgivinglist.
TherearetwoStackExchangecommunitiesthatcanprovidefurtherhelpatstats.stackexchange.com/(forquestionsaboutstatisticsandvisualizationwithR)andstackoverflow.com/(forquestionsaboutprogrammingwithR).
TherearemanywaystolearnRandrelatedsubjectsonline;RStudiohasaveryusefullistontheirwebsiteatgoo.gl/8tX7FP.
LoadingdataThesimplestwayofloadingdataintoRisprobablyusingacomma-separatedvalue(.csv)spreadsheetfile,whichcanbedownloadedfrommanydatasourcesandloadedandsavedinallspreadsheetsoftware(suchasExcelorLibreOffice).Theread.table()commandimportsdataofthistypebyspecifyingtheseparatorasacomma,orusingread.csv(),afunctionspecificallyfor.csvfiles,asshowninthefollowingcommand:
>analyticsData=read.table("~/example.csv",sep=",")
Otherwise,youcanusethefollowingcommand:
>analyticsData=read.csv("~/example.csv")
Notethatunlikeotherlanguages,Ruses<-forassignmentaswellas=.Assignmentcanbemadetheotherwayusing->.Theresultofthisisthatycanbetoldtoholdthevalueof4inay<-4or4->yformat.Therearesomeother,moreadvancedthingsthatcanbedonewithassignmentinR,butdon'tworryaboutthemnow.Inthisbook,Iwillpreferthe=operator,sinceIusethisinmyowncode.Justbeawareofbothmethodssothatyoucanunderstandthecodeyoucomeacrossinforumsandblogposts.
Eitheroftheprecedingcodeexampleswillassignthecontentsoftheexample.csvfiletoadataframenamedanalyticsData,withthefirstrowofthespreadsheetprovidingthevariablenames.AdataframeisaspecialtypeofobjectinR,whichisdesignedtobeusefulforthestorageandanalysisofdata.
RStudiowilleventakecareofloading.csvfilesforyou,ifyouclickontheminthefileselectorpane(inthebottomrightbydefault)andselectImportdataset....Thiscanbeusefultohelpyougetstarted,butasyougetmoreconfidentit'sreallybettertodoeverythingwithcoderatherthanpointingandclicking.RStudiowill,toitsgreatcredit,showyouthecodethatmakesyourpointingandclickingwork,sotakeanoteofitanduseittoloadthedatathenexttimeyourself.
DatatypesandstructuresTherearemanydatatypesandstructuresofdatawithinR.ThefollowingtopicssummarizesomeofthemaintypesandstructuresthatyouwillusewhenbuildingShinyapplications.
Dataframes,lists,arrays,andmatricesDataframeshaveseveralimportantfeaturesthatmakethemusefulfordataanalysis:
Rectangulardatastructures,withthetypicalusebeingcases(forexample,thedaysinonemonth)listeddowntherowsandvariables(pageviews,uniquevisitors,orreferrers)listedalongthecolumnsAmixofdatatypesissupported.Atypicaldataframemightincludevariablescontainingdates,numbers(integersorfloats),andtextWithsubsettingandvariableextraction,Rprovidesalotofbuilt-infunctionalitytoselectrowsandvariableswithinadataframeManyfunctionsincludeadataargument,whichmakesitverysimpletopassdataframesintofunctionsandprocessonlythevariablesandcasesthatarerelevant,whichmakesforcleanerandsimplercode
Wecaninspectthefirstfewrowsofthedataframeusingthehead(analyticsData)command.Thefollowingscreenshotshowstheoutputofthiscommand:
Asyoucansee,therearefourvariableswithinthedataframe:onecontainsdates,twocontainintegervariables,andonecontainsanumericvariable.
Variablescanbeextractedfromdataframesverysimplyusingthe$operator,asfollows:
>analyticsData$pageViews
[1]836676940689647899934718776570651816
[13]731604627946634990994599657642894983
[25]646540756989965821
Variablescanalsobeextractedfromdataframesusing[],asshowninthefollowingcommand:
>analyticsData[,"pageViews"]
Notetheuseofacommawithnothingbeforeittoindicatethatallrowsarerequired.Ingeneral,dataframescanbeaccessedusingdataObject[x,y],withxbeingthenumber(s)orname(s)oftherowsrequiredandybeingthenumber(s)orname(s)ofthecolumnsrequired.Forexample,ifthefirst10rowswererequiredfromthepageViewscolumn,itcouldbeachievedlikethis:
>analyticsData[1:10,"pageViews"]
[1]836676940689647899934718776570
Leavingthespacebeforethecommablankreturnsallrows,andleavingthespaceafterthecommablankreturnsallvariables.Forexample,thefollowingcommandreturnsthefirstthreerowsofallvariables:
>analyticsData[1:3,]
Thefollowingscreenshotshowstheoutputofthiscommand:
Dataframesareaspecialtypeoflist.Listscanholdmanydifferenttypesofdata,includinglists.AswithmanydatatypesinR,theirelementscanbenamed,whichcanbeusefultowritecodethatiseasytounderstand.Let'smakealistoftheoptionsfordinner,withdrinkquantitiesexpressedinmilliliters.
Inthefollowingexample,pleasealsonotetheuseofthec()function,whichisusedtoproducevectorsandlistsbygivingtheirelementsseparatedbycommas.Rwillpickanappropriateclassforthereturnvalue,stringforvectorsthatcontainstrings,numericforthosethatonlycontainnumbers,logicalforBooleanvalues,andsoon:
>dinnerList<-list("Vegetables"=
c("Potatoes","Cabbage","Carrots"),
"Dessert"=c("Icecream","Applepie"),
"Drinks"=c(250,330,500)
)
Notethatcodeisindentedthroughout,althoughenteringcodedirectlyintotheconsolewillnotproduceindentations;itisdoneforreadability.
Indexingissimilartodataframes(whichare,afterall,justaspecialinstanceofalist).Theycanbeindexedbynumber,asshowninthefollowingcommand:
>dinnerList[1:2]
$Vegetables
[1]"Potatoes""Cabbage""Carrots"
$Dessert
[1]"Icecream""Applepie"
Thisreturnsalist.Returninganobjectoftheappropriateclassisachievedusing[[]]:
>dinnerList[[3]]
[1]250330500
Inthiscase,anumericvectorisreturned.Theycanalsobeindexedbyname,asshowninthefollowingcode:
>dinnerList["Drinks"]
$Drinks
[1]250330500
Notethatthisalsoreturnsalist.
Matricesandarrays,which,unlikedataframes,onlyholdonetypeofdata,alsomakeuseofsquarebracketsforindexing,withanalyticsMatrix[,3:6]returningallrowsofthethirdtosixthcolumns,analyticsMatrix[1,3]returningjustthefirstrowofthethirdcolumn,andanalyticsArray[1,2,]returningthefirstrowofthesecondcolumnacrossalloftheelementswithinthethirddimension.
VariabletypesRisadynamicallytypedlanguage,andyouarenotrequiredtodeclarethetypeofyourvariableswhenusingit.Ofcourse,itisworthknowingaboutthedifferenttypesofvariablethatyoumightreadorwriteusingR.Thedifferenttypesofvariablecanbestoredinavarietyofstructures,suchasvectors,matrices,anddataframes,althoughsomerestrictionsapplyasmentionedpreviously(forexample,matricesmustcontainonlyonevariabletype).Thefollowingbulletlistcontainsthespecificsofusingthesevariabletypes:
Declaringavariablewithatleastonestringinitwillproduceavectorofstrings(inR,thecharacterdatatype),asshowninthefollowingcode:
>c("First","Third",4,"Second")
[1]"First""Third""4""Second"
Youwillnoticethatthenumeral4isconvertedtoastring,"4".Thisisasaresultofcoercion,inwhichelementsofadatastructureareconvertedtootherdatatypesinordertofitwithinthetypesthatareallowedwithinthedatastructure.Coercionoccursautomatically,asinthiscase,orwithanexplicitcalltotheas()function—forexample,as.numeric(),oras.Date().
Declaringavariablethatcontainsonlynumberswillproduceanumericvector,asshowninthefollowingcode:
>c(15,10,20,11,0.4,-4)
[1]15.010.020.011.00.4-4.0
R,ofcourse,alsoincludesalogicaldatatype,asshowninthefollowingcode:
>c(TRUE,FALSE,TRUE,TRUE,FALSE)
[1]TRUEFALSETRUETRUEFALSE
Adatatypeexistsfordates,whichareoftenasourceofproblemsforbeginners,asshowninthefollowingcode:
>as.Date(c("2013/10/24","2012/12/05","2011/09/02"))
[1]"2013-10-24""2012-12-05""2011-09-02"
TheuseofthefactordatatypetellsRallofthepossiblevaluesofacategoricalvariable,suchasgenderorspecies,asshowninthefollowingcode:
>factor(c("Male","Female","Female","Male","Male"),
levels=c("Female","Male"))
[1]MaleFemaleFemaleMaleMale
Levels:FemaleMale
FunctionsAsyougrowinconfidencewithR,youwillwanttobeginwritingyourownfunctions.Thisisachievedverysimply,andinamannerquitesimilartomanyotherlanguages.YouwillnodoubtwanttoreadmoreaboutwritingfunctionsinRinmoredetail,butjusttogiveyouanidea,thefollowingcodeisafunctioncalledthesumMultiplyfunctionthataddstogetherxandyandmultipliestheresultbyz:
sumMultiply<-function(x,y,z){
final=(x+y)*z
return(final)
}
ThisfunctioncannowbecalledusingsumMultiply(2,3,6),whichwillreturn2plus3times6,whichgives30.
ObjectsTherearemanyspecialobjecttypeswithinRthataredesignedtomakeiteasiertoanalyzedata.FunctionsinRcanbepolymorphic—thatistosay,theycanrespondtodifferentdatatypesindifferentwaysinordertoproducetheoutputthattheuserdesires.Forexample,theplot()functioninRrespondstoawidevarietyofdatatypesandobjects,includingsingle-dimensionvectors(eachvalueofyplottedsequentially)andtwo-dimensionalmatrices(producingascatterplot),aswellasspecializedstatisticalobjects,suchasregressionmodelsandtimeseriesdata.Inthelattercase,plotsthatarespecializedforthesepurposesareproduced.
Aswiththerestofthisintroduction,don'tworryifyouhaven'twrittenfunctionsbefore,ordon'tunderstandobjectconceptsandaren'tsurewhatthisallmeans.Youcanproducegreatapplicationswithoutunderstandingallthesethings,butasyoudomoreandmorewithR,youwillstarttowanttolearnmoredetailsabouthowRworksandhowexpertsproduceRcode.Thisintroductionisdesignedtogiveyouajumping-offpointtolearnmoreabouthowtogetthebestoutofR(andShiny).
Basegraphicsandggplot2Therearelotsofuser-contributedgraphicspackagesinRthatcanproducesomewonderfulgraphics.YoumaywishtotakealookforyourselfattheCRANtaskviewatcran.r-project.org/web/views/Graphics.html.Wewillhaveaveryquicklookattwoapproaches:basegraphics,socalledbecausetheyformthedefaultgraphicalenvironmentwithinavanillainstallationofR,andggplot2,ahighlypopularuser-contributedpackageproducedbyHadleyWickham,whichisalittletrickiertomasterthanbasegraphics,butcanveryrapidlyproduceawiderangeofgraphicaldatasummaries.Wewillcovertwographsthatarefamiliartoeveryone:thebarchartandthelinechart.
BarchartUsefulwhencomparingquantitiesacrosscategories,barchartsareverysimpleinbasegraphics,particularlywhencombinedwiththetable()command.Wewillusethempgdataset,whichcomeswiththeggplot2package;itsummarizesthedifferentcharacteristicsofarangeofcars.First,let'sinstalltheggplot2package.Youcandothisstraightfromtheconsoleusingthefollowingcode:
>install.packages("ggplot2")
Alternatively,youcanusethebuilt-inpackagefunctionsinIDEssuchasRStudioorRKWard.We'llneedtoloadthepackageatthebeginningofeachsessioninwhichwewanttousethisdataset,orintheggplot2packageitself.Fromtheconsole,typethefollowingcommand:
>library(ggplot2)
Wewillusethetable()commandtocountthenumberofeachtypeofcarfeaturedinthedataset,asshowninthefollowingcode:
>table(mpg$class)
Thisreturnsatableobject(anotherspecialobjecttypewithinR)thatcontainsthecolumnsshowninthefollowingscreenshot:
Producingabarchartofthisobjectisachievedsimplyusingthefollowingcode:
>barplot(table(mpg$class),main="Basegraphics")
Thebarplotfunctiontakesavectoroffrequencies.Wheretheyarenamed,asisthecaseinourexample(thetable()commandreturningnamedfrequenciesin
tableform),namesareautomaticallyincludedonthexaxis.Thedefaultsforthisgraphareratherplain.Explore?barplotand?partolearnmoreaboutfine-tuningyourgraphics.
We'vealreadyloadedtheggplot2packageinordertousethempgdataset,butifyouhaveshutdownRinbetweenthesetwoexamples,youwillneedtoreloaditbyusingthefollowingcommand:
>library(ggplot2)
Thesamegraphisproducedinggplot2asfollows:
>ggplot(data=mpg,aes(x=class))+geom_bar()+
ggtitle("ggplot2")
Thisggplotcallshowsthethreefundamentalelementsofggplotcalls:theuseofadataframe(data=mpg);thesetupofaesthetics(aes(x=class)),whichdetermineshowvariablesaremappedontoaxes,colors,andothervisualfeatures;andtheuseof+geom_xxx().Aggplotcallsetsupthedataandaesthetics,butdoesnotplotanything.Functionssuchasgeom_bar()(therearemanyothers;see??geom)tellggplotwhattypeofgraphtoplot,aswellasinstructingittotakeoptionalarguments—forexample,geom_bar()optionallytakesapositionargument,whichdefineswhetherthebarsshouldbestacked,offset,orstretchedtoacommonheighttoshowproportionsinsteadoffrequencies.
Theseelementsarethekeytothepowerandflexibilitythatggplot2offers.Oncethedatastructureisdefined,waysofvisualizingthatdatastructurecanbeaddedandtakenawayeasily,notonlyintermsofthetypeofgraphic(bar,line,orscattergraph)butalsothescalesandcoordinatessystem(log10,polarcoordinates,andsoon)andstatisticaltransformations(smoothingdata,summarizingoverspatialcoordinates,andsoon).Theappearanceofplotscanbeeasilychangedwithpresetanduser-definedthemes,andmultipleplotscanbeaddedinlayers(thatis,addingthemtooneplot)orfacets(thatis,drawingmultipleplotswithonefunctioncall).
Thebasegraphicsandggplotversionsofthebarchartareshowninthefollowingscreenshotforthepurposesofcomparison:
LinechartLinechartsaremostoftenusedtoindicatechange,particularlyovertime.Thistime,wewillusethelongleydataset,featuringeconomicvariablesfrombetween1947and1962,asshowninthefollowingcode:
>plot(x=1947:1962,y=longley$GNP,type="l",
xlab="Year",main="Basegraphics")
Thexaxisisgivenverysimplybythe1947:1962phrase,whichenumeratesallthenumbersbetween1947and1962,andthetype="l"argumentspecifiestheplottingofthelines,asopposedtopointsorboth.
Theggplotcalllooksalotlikethebarchart,exceptwithanxandydimensionintheaestheticsthistime.Thecommandlooksasfollows:
>ggplot(longley,aes(x=1947:1962,y=GNP))+geom_line()+
xlab("Year")+ggtitle("ggplot2")
IntroductiontothetidyverseThetidyverseis,accordingtoitshomepage,"anopinionatedcollectionofRpackagesdesignedfordatascience"(seehttps://www.tidyverse.org/).Allofthepackagesareinstalledusinginstall.packages("tidyverse"),andcallinglibrary(tidyverse)loadsasubsetofthesepackages,thoseconsideredtohavethemostvalueinday-to-daydatascience.Callinglibrary(tidyverse)loadsthefollowingpackages:
ggplot2:Forplottingdplyr:Fordatawranglingtidyr:Fortidying(anduntidying!)datareadr:Betterfunctionsforreadingcomma-andtab-delimiteddataandothertypesofflatfilespurrr:Foriteratingtibble:Betterdataframesstringr:Fordealingwithstringsforcats:Betterhandlingoffactors,anRpropertyusedtodescribethecategoriesofavariable,describedbrieflyearlierinthechapter
Installingthetidyversealsoinstallsthefollowingpackages,whichthenneedtobeloadedseparatelywiththeirownlibrary()instruction:readxl,haven,jsonlite,xml2,httr,rvest,DBI,lubridate,hms,blob,rlang,magrittr,glue,andbroom.Formoredetailsonthesepackages,consultthedocumentationattidyverse.org/.
Thekeywordinthisdescriptionisopinionated.ThetidyverseisasetofRpackagesthatworkwithtidydata,eitherproducingit,orconsumingit,orboth.TidydatawasdescribedbyHadleyWickhamintheJournalofStatisticalSoftware,Vol59,Issue10(https://www.jstatsoft.org/article/view/v059i10/v59i10.pdf).Tidydataobeysthreeprinciples:
EachvariableformsacolumnEachobservationformsarowEachtypeofobservationalunitformsatable
FormanyRusers,theirfirstintroductiontotidydatawillhavebeenggplot2,
whichconsumestidydataandrequiresothertypesofdatatobemungedintothisform.Inordertoexplainwhatthismeans,wewilllookatasimpleexample.Forthepurposesofthisdiscussion,wewillignorethelastprinciple,whichismoreaboutorganizinggroupsofdatasetsratherthanindividualdatasets.
Let'shavealookatasimpleexampleofamessydataset.Intherealworld,youwillfinddatasetsthatarealotmessierthanthis,butthiswillservetoillustratetheprinciplesweareusinghere.ThefollowingarethefirstthreerowsofthemedaltableforthePyeongchangWinterOlympics,whichtookplacein2018,asanRdataframe:
medals=data.frame(country=c("Norway","Germany","Canada"),
gold=c(14,14,11),
silver=c(14,10,8),
bronze=c(11,7,10)
)
Ifweprintitattheconsole,itlookslikethefollowingscreenshot:
Thisisperhapsthemostcommonsortofmessydatayouwillcomeacross:greatasasummary,certainlyintelligibletopeoplewatchingtheWinterOlympics,butnottidy.Therearemedalsinthreedifferentcolumns.Atidydatasetwouldcontainonlyonecolumnforthemedaltallies.Let'stidyitupusingthetidyrpackage,whichisloadedwithlibrary(tidyverse),oryoucanloaditseparatelywithlibrary(tidyr).Wecantidythedataverysimplyusingthegather()function.Thegather()functiontakesadataframeasanargument,alongwithkeyandvaluecolumnnames(whichyoucansettowhatyoulike),andthecolumnnamesthatyouwishtobegathered(allothercolumnswillbeduplicatedasappropriate).Inthiscase,wewanttogathereverythingexceptthecountry,sowecanuse-variableNametoindicatethatwewishtogathereverythingexceptvariableName.Thefinalcodelookslikethefollowing:
library(tidyr)
gather(medals,key=Type,value=Medals,-country)
Thishasanicetidyoutput,asshowninthefollowingscreenshot:
Cecin'estpasunepipeNowthatwe'vecoveredtidydata,thereisonemoreconceptthatisverycommoninthetidyversethatweshoulddiscuss.Thisisthepipe(%>%)fromthemagrittrpackage.ThisissimilartotheUnixpipe,andittakestheleft-handsideofthepipeandappliestheright-handsidefunctiontoit.Takethefollowingcode:
mpg%>%summary()
Theprecedingcodeisequivalenttothefollowingcode:
summary(mpg)
Asanotherexample,lookatthefollowingcode:
gapminder%>%filter(year>1960)
Theprecedingcodeisequivalenttothefollowingcode:
filter(gapminder,year>1960)
Pipinggreatlyenhancesthereadabilityofcodethatrequiresseveralstepstoexecute.Takethefollowingcode:
x%>%f%>%g%>%h
Theprecedingcodeisequivalenttothefollowingcode:
h(g(f(x)))
Todemonstratewitharealexample,takethefollowingcode:
groupedData=gapminder%>%
filter(year>1960)%>%
group_by(continent,year)%>%
summarise(meanLife=mean(lifeExp))
Theprecedingcodeisequivalenttothefollowingcode:
summarise(
group_by(
filter(gapminder,year>1960),
continent,year),
meanLife=mean(lifeExp))
Hopefully,itshouldbeobviouswhichistheeasiertoreadofthetwo.
GapminderNowwe'velookedattidyingdata,let'shaveaquicklookatusingdplyrandggplottofilter,process,andplotsomedata.Inthissection,andthroughoutthisbook,we'regoingtobeusingtheGapminderdatathatwasmadefamousbyHansRoslingandtheGapminderfoundation.Anexcerptofthisdataisavailablefromthegapminderpackage,asassembledbyJennyBryan,anditcanbeinstalledandloadedverysimplyusinginstall.packages("gapminder");library(gapminder).Asthepackagedescriptionindicates,itincludes,foreachofthe142countriesthatareincluded,thevaluesforlifeexpectancy,GDPpercapita,andpopulation,everyfiveyears,from1952to2007.
Inordertopreparethedataforplotting,wewillmakeuseofdplyr,asshowninthefollowingcode:
groupedData=gapminder%>%
filter(year>1960)%>%
group_by(continent,year)%>%
summarise(meanLife=mean(lifeExp))
Thissingleblockofcode,allexecutedinoneline,producesadataframesuitableforplotting,anduseschainingtoenhancethesimplicityofthecode.Threeseparatedataoperations,filter(),group_by(),andsummarise(),areallused,withtheresultsfromeachbeingsenttothenextinstructionusingthe%>%operator.Thethreeinstructionscarryoutthefollowingtasks:
filter():Thisissimilartosubset().Thisoperationonlykeepsrowsthatmeetcertainrequirements—inthiscase,yearsbeyond1960.group_by():Thisallowsoperationstobecarriedoutonsubsetsofdatapoints—inthiscase,eachcontinentforeachoftheyearswithinthedataset.summarise():Thiscarriesoutsummaryfunctions,suchassumandmean,onseveraldatapoints—inthiscasethemeanlifeexpectancywithineachcontinentandavailableyear.
So,tosummarize,theprecedingcodefiltersthedatatoselectonlyyearsbeyond1960,groupsitbythecontinentandyear,andfindsthemeanlifeexpectancywithinthatcontinentoryear.Printingtheoutputfromtheprecedingcodeyieldsthefollowing:
Asyoucansee,theoutputisatibble,whichhasaniceprintmethodthatonlyprintsthefirstseveralrows.Tibblesareverysimilartodataframes,andareoftenproducedbydefaultinsteadofdataframeswithinthetidyverse.Therearesomenicedifferences,buttheyarefairlyinterchangeablewithdataframesforourpurposes,sowewillnotgetsidetrackedbythedifferenceshere.
Nowwehavementionedtibbles,youcanseethatthedataframeisanicesummaryofthemeanlifeexpectancybyyearandcontinent.
AsimpleShiny-enabledlineplotWehavealreadyseenhoweasyitistodrawlineplotsinggplot2.Let'saddsomeShinymagictoalineplotnow.ThiscanbeachievedveryeasilyindeedinRStudiobyjustnavigatingtoFile|NewFile|RMarkdown|NewShinydocumentandinstallingthedependencieswhenprompted.Onceatitlehasbeenadded,thiswillcreateanewRMarkdowndocumentwithinteractiveShinyelements.RMarkdownisanextensionofMarkdown(seedaringfireball.net/projects/markdown/),whichisitselfamarkuplanguage,suchasHTMLorLaTeX,whichisdesignedtobeeasytouseandread.RMarkdownallowsRcodechunkstoberunwithinaMarkdowndocument,whichrendersthecontentsdynamic.ThereismoreinformationaboutMarkdownandRMarkdowninChapter2,ShinyFirstSteps.ThissectiongivesaveryrapidintroductiontothetypeofresultspossibleusingShiny-enabledRMarkdowndocuments.
FormoredetailsonhowtoruninteractivedocumentsoutsideRStudio,refertogoo.gl/Ngubdo.Bydefault,anewdocumentwillhaveplaceholdercodeinitwhichyoucanruntodemonstratethefunctionality.Wewilladdthefollowing:
---
title:"Gapminder"
author:"ChrisBeeley"
output:html_document
runtime:shiny
---
```{r,echo=FALSE,message=FALSE}
library(tidyverse)
library(gapminder)
inputPanel(
checkboxInput("linear",label="Addtrendline?",value=FALSE)
)
#drawtheplot
renderPlot({
thePlot=gapminder%>%
filter(year>1960)%>%
group_by(continent,year)%>%
summarise(meanLife=mean(lifeExp))%>%
ggplot(aes(x=year,y=meanLife,
group=continent,colour=continent))+
geom_line()
if(input$linear){
thePlot=thePlot+geom_smooth(method="lm")
}
print(thePlot)
})
```
Thefirstpartbetweenthe---istheYAML,whichperformsthesetupofthedocument.InthecaseofproducingthisdocumentwithinRStudio,thiswillalreadybepopulatedforyou.
Rchunksaremarkedasshown,with```{r}tobeginand```toclose.Theechoandmessageargumentsareoptional—weusethemheretosuppressoutputoftheactualcodeandanymessagesfromRinthefinaldocument.
We'llgointomoredetailabouthowShinyinputsandoutputsaresetuplateroninthebook.Fornow,justknowthattheinputissetupwithacalltocheckboxInput(),which,asthenamesuggests,createsacheckbox.It'sgivenaname("linear")andalabeltodisplaytotheuser("Addtrendline?").Theoutput,beingaplot,iswrappedinrenderPlot().Youcanseethecheckboxvaluebeingaccessedontheif(input$linear){...}line.Shinyinputsarealwaysaccessedwithinput$andthentheirname,so,inthiscase,input$linear.Whentheboxisclicked—thatis,whenitequalsTRUE—wecanseeatrendlinebeingaddedwithgeom_smooth().We'llgointomoredetailabouthowallofthiscodeworkslaterinthebook;thisisafirstlooksothatyoucanstarttoseehowdifferenttasksarecarriedoutusingRandShiny.
You'llhaveaninteractivegraphiconceyourunthedocument(clickonRundocumentinRStudioorusetherun()commandfromthermarkdownpackage),asshowninthefollowingscreenshot:
Asyoucansee,Shinyallowsustoturnonoroffatrendlinecourtesyofgeom_smooth()fromtheggplot2package.
InstallingShinyandrunningtheexamplesShinycanbeinstalledusingstandardpackagemanagementfunctions,asdescribedpreviously(usingtheGUIorrunninginstall.packages("shiny")attheconsole).
Let'srunsomeoftheexamples,asshowninthefollowingcode:
>library(shiny)
>runExample("01_hello")
Yourwebbrowsershouldlaunchanddisplaythefollowingscreenshot(notethatIclickedontheshowbelowbuttonontheapptobetterfitthegraphiconthepage):
Thegraphshowsthefrequencyofasetofrandomnumbersdrawnfromastatisticaldistributionknownasthenormaldistribution,andthesliderallowsuserstoselectthesizeofthedraw,from0to1,000.Youwillnotethatwhenyoumovetheslider,thegraphupdatesautomatically.ThisisafundamentalfeatureofShiny,whichmakesuseofareactiveprogrammingparadigm.
Thisisatypeofprogrammingthatusesreactiveexpressions,whichkeeptrackofthevaluesonwhichtheyarebased.Thesevaluescanchange(theyareknownasreactivevalues)andupdatethemselveswheneveranyoftheirreactivevalueschange.So,inthisexample,thefunctionthatgeneratestherandomdataanddrawsthegraphisareactiveexpression,andthenumberofrandomdrawsthatitmakesisareactivevalueonwhichtheexpressiondepends.So,wheneverthenumberofdrawschanges,thefunctionre-executes.
Youcanfindmoreinformationaboutthisexample,aswellasacomprehensivetutorialforShiny,atshiny.rstudio.com/tutorial/.
Also,notethelayoutandstyleofthewebpage.ShinyisbasedbydefaultontheBootstraptheme(seegetbootstrap.com/).However,youarenotlimitedbythestylingatall,andcanbuildthewholeUIusingamixofHTML,CSS,andShinycode.
Let'slookataninterfacethatismadewithbare-bonesHTMLandShiny.Notethatinthisandallsubsequentexamples,we'regoingtoassumethatyourunlibrary(shiny)atthebeginningofeachsession.Youdon'thavetorunitbeforeeachexample,exceptatthebeginningofeachRsession.So,ifyouhaveclosedRandhavecomeback,thenrunitontheconsole.Ifyoucan'tremember,runitagaintobesure,asfollows:
library(shiny)
runExample("08_html")
Andhereitis,inallitscustomizableglory:
Now,thereareafewdifferentstatisticaldistributionstopickfromandadifferentmethodofselectingthenumberofobservations.Bynow,youshouldbelookingatthewebpageandimaginingallthepossibilitiestherearetoproduceyourowninteractivedatasummariesandstylethemjusthowyouwant,quicklyandsimply.Bytheendofthenextchapter,you'llhavemadeyourownapplicationwiththedefaultUI,andbytheendofthebook,you'llhavecompletecontroloverthestylingandbeponderingwhereelseyoucango.
TherearelotsofotherexamplesincludedwiththeShinylibrary;justtyperunExample()intheconsoletobeprovidedwithalist.
Toseesomereallypowerfulandwell-featuredShinyapplications,takealookattheshowcaseatshiny.rstudio.com/gallery/.
SummaryInthischapter,weinstalledRandexploredthedifferentoptionsforGUIsandIDEs,andlookedatsomeexamplesofthepowerofR.WesawhowRmakesiteasytomanageandreformatdataandproducebeautifulplotswithafewlinesofcode.YoualsolearnedalittleaboutthecodingconventionsanddatastructuresofR.Wesawhowtoformatadatasetandproduceaninteractiveplotinadocumentquicklyandeasily.Finally,weinstalledShiny,rantheexamplesincludedinthepackage,andwereintroducedtoacoupleofbasicconceptsinShiny.
Inthenextchapter,wewillgoontobuildourownShinyapplicationusingthedefaultUI.
ShinyFirstStepsInthepreviouschapter,welookedatR,learnedsomeofitsbasicsyntax,andsawsomeexamplesofthepowerandflexibilitythatRandShinyoffer.ThischapterintroducesthebasicsofShiny.Inthischapter,we'regoingtobuildourownapplicationtointeractivelyexploretheGapminderdatadescribedinthepreviouschapter.Wewillcoverthefollowingtopics:
ThetypesofShinyapplication—RMarkdown,single-file,two-file,ShinygadgetsInteractiveShinydocumentsinRMarkdownSingle-fileShinyapplicationsTwo-fileShinyapplicationsAminimalexampleofafullShinyapplicationWidgettypesThebasicstructureofaShinyprogramTheselectionofsimpleinputwidgets(checkboxesandcombobuttons)Theselectionofsimpleoutputtypes(renderingplotsandmaps,andreturningtext)Theselectionofsimplelayouttypes(pagewithsidebarandtabbedoutputpanel)ReactiveobjectsAbriefsummaryofmoreadvancedlayoutfeatures
TypesofShinyapplicationInthefirsteditionofthisbook,whichwasbasedonShiny0.6,wedescribedonlytwotypesofapplication.First,afairlysimpleBootstrap-themedinterfacewithinputwidgetsdowntheleftandoutput(asinglepageoratabbedoutputwindow)ontheright.Thesecondtypeconsistedofcustom-builtwebpageswiththeirownHTMLandCSSfiles.Shinyhasdevelopedquiteabitsincethen,andthereareactuallymanytypesofShinyapplicationandmanywaysofbuildingthem.Theseareasfollows:
InteractivemarkdowndocumentswithShinywidgetsembeddedShinyapplications(defaultCSS,writtenentirelyinR)Webpages(forexample,customCSS,HTML,andJavaScript)ShinygadgetsFlexdashboards
Inthischapter,wewillbeconsideringthefirsttwo:interactivedocumentsandfullapplications.Chapter3,IntegratingShinywithHTML,willcoverhowtobuildyourownwebpagescontainingShinyapplications.ShinygadgetsaretoolsforRprogrammersratherthanforendusers,andtheyallowRuserstoexploredataandgenerategraphicsandsummarieswithShinyinterfaces.TheywillbedescribedfurtherinChapter7,PowerShiny.FlexdashboardswillbelookedatinChapter6,Dashboards.
InteractiveShinydocumentsinRMarkdownAswesawinthepreviouschapter,interactivedocumentscanbemadeveryeasilyusingRMarkdowninRStudio.EvenifyouarenotusingRStudio,itisasimplematterofwritinganRMarkdownfilewithShinycodeinit.IfyoudonotuseRStudio,youwillneedanup-to-dateversionofPandoc(theversioninmanyLinuxdistributionsisnotrecentenough).FormoreoninstallingPandoconLinux,Windows,orMac,gotopandoc.org/installing.html.
RMarkdownisbasedonMarkdown,whichisamarkuplanguagedesignedtobeeasilyconvertedintoHTML,butwhichlooksmuchmorelikeanaturaldocumentinitsrawformat,asopposedtoHTMLorothermarkuplanguages(suchasLaTeX),whichhavemoreprominentandstrange-lookingtags.Forexample,theMarkdownsyntaxforabulletedlistisasfollows:
*Firstbullet
*Secondbullet
*Thirdbullet
TheHTMLequivalentisasfollows:
<ul>
<li>Firstbullet</li>
<li>Secondbullet</li>
<li>Thirdbullet</li>
</ul>
TaggingmarkupinLaTeXisevenmoreverbose.RMarkdownusesMarkdownconventions,butallowscodechunksofRtoberunwithinthedocument,andallowstextandgraphicaloutputtobegeneratedfromthosechunks.CoupledwithPandoc(theSwissArmyknifeofdocumentrendering),MarkdownandRMarkdowncanberenderedintomanyformats,includingXHTML,HTML,epub,LaTeX,.pdf,.doc,.docx,and.odt.
RMarkdownwithShinygoesonestepfurtherandallowsuserstointeractwiththedocumentonawebpage.
Let'sbuildaminimalexample.IfyouareusingRStudio,youwillbegivenaboilerplateShinyMarkdowndocumenttoworkfrom,whichmakesthingsabiteasier,butherewe'llignorethatandbuilditfromscratch.Thecodeisavailableatgoo.gl/N7Qkv8.
Let'sgothrougheachpartofthedocument.NavigatetoFile|NewFile|RMarkdown|Newdocumentandenterthefollowingcode:
#ExampleRMarkdowndocument
Thisisaninteractivedocumentwrittenin*markdown*.Asyoucanseeitiseasytoinclude:
1.Orderedlists
2.*Italics*
3.**Boldtype**
4.Linksto[Documentation](http://example.com/)
##Thisisheadingtwo
Perhapsthisintroducesthevisualisationbelow.
ThisisthedocumentpartoftheShinydocument,writteninMarkdown.Thefollowingconventionscanbenoted:
The#characterisusedforheadingsatlevel1,and##forheadingsatlevel2Numbered(ordered)listsaredesignatedwith1,2,andsoonItalicsaregivenwith*singleasterisks*andaboldformatisgivenwith**doubleasterisks**
Linksarerepresentedusingtheformatof(http://example.com/)
Nextfollowsacodechunk,beginningwith```{r}andendingwith```.Theecho=FALSEargumentisaddedtothechunktopreventtheprintingoftheRcode.Youwillusuallywanttodothis,butnotoneveryoccasion—forexample,whenproducingateachingresource:
```{r,echo=FALSE}sliderInput("sampleSize",label="Sizeofsample",min=10,max=100,value=50,step=1)renderPlot({hist(runif(input$sampleSize))})```
Straightaway,wecanseesomeofthedesignprinciplesinShinyapplications.Wecanseetheseparationofinputcode,sliderInput(),andoutputcode,renderPlot().ThesliderInput()function,asthenamesuggests,definesaninputwidgetthatallowstheusertoselectfromarangeofnumericvalues,inthiscase,between10and100,withastartingvalueof50andastepincreaseof1.TherenderPlot()functionproducesareactiveplotusingwhateverfunctionsitfindswithinitself(inthiscase,thegraphicalfunctionhist(),whichdrawsahistogram).
AswealreadycoveredinChapter1,BeginningRandShiny,reactiveoutputschangewhentheirinputschange.Therunif(n)functionproducesnrandomnumbersbetween0and1(withdefaultarguments).Aswecanseeinthiscase,nisgivenbyinput$sampleSize.InputsareaccessedverysimplyinShinyinthisformat;youcanseethatwenamedtheinputsampleSizewithinthesliderInput()function,whichplacestheselectedvaluefromthewidgetininput$sampleSize(namingitmyInputplacesthevalueininput$myInput).
Therefore,runif()generatesrandomnumbersinthequantityofinput$sampleSize,hist()plotsthemwithahistogram,andrenderPlot({})tellsShinythattheoutputwithinisreactiveandshouldbeupdatedwheneveritsinputs(inthiscase,justinput$sampleSize)change.
Thefinalresultwilllooklikethefollowingscreenshot:
That'sit!YoumadeyourfirstShinyapplication.It'sthateasy.Now,let'sconsiderbuildingfullyfledgedapplications,startingwithaminimalexampleandbuildingupfromthere.
AminimalexampleofafullShinyapplicationThefirstthingtonoteisthatShinyprogramsaretheeasiesttobuildandunderstandusingtwoscripts,whicharekeptwithinthesamefolder.Theyshouldbenamedserver.Randui.R.
Theui.RoftheminimalexampleTheui.RfileisadescriptionoftheUI,andisoftentheshortestandsimplestpartofaShinyapplication.Inthefollowingcode,notetheuseofthe#character,whichmarkslinesofcodeascommentsthatwillnotberun,butwhichareforthebenefitofthehumansproducingthecode:
fluidPage(#Line1
titlePanel("Minimalapplication"),#Line2
sidebarLayout(#Line3
sidebarPanel(#Line4
textInput(inputId="comment",#Line5
label="Saysomething?",#Line6
value=""#Line7
)),#Line8
mainPanel(#Line9
h3("Thisisyousayingit"),#Line10
textOutput("textDisplay")#Line11
)
)
)
Thefollowinglistisanexplanationofeachline:
Line1:FlexiblelayoutfunctionLine2:TitleLine3:Standardinputsonthesidebar;outputsinthemainarealayoutLine4:ThesidebarlayoutfunctionLine5:Givethenameoftheinputelement;thiswillbepassedtoserver.RLine6:ThedisplaylabelforthevariableLine7:TheinitialvalueLine9:TheoutputpanelLine10:ThetitledrawnwiththeHTMLhelperfunctionLine11:TheoutputtextwiththeID,textDisplay,asdefinedinserver.R
TorunaShinyprogramonyourlocalmachine,youjustneedtodothefollowing:
1. Makesurethatserver.Randui.Rareinthesamefolder2. MakethisfolderR'sworkingdirectory(usingthesetwd()command—for
example,setwd("~/shinyFiles/minimalExample"),orwiththeSession>Setworkingdirectorymenuoption)
3. LoadtheShinypackagewiththelibrary(shiny)command4. TyperunApp()intheconsole(or,inRstudio,clickRunappjustabovethe
codewindow)
UsingrunApp()withthenameofadirectorywithinworksjustaswell—forexample,runApp("~/shinyFiles/minimalExample").SoinsteadofsettingtheworkingdirectorytothelocationofyourapplicationandthenusingrunApp()separately,thewholethingcansimplybecarriedoutinoneinstruction,passingrunApp()inthenameofthedirectorydirectly.Justrememberthatitisadirectoryandnotafilethatyouneedtopointto.
Thefirstinstruction,fluidPage(...,tellsShinythatweareusingafluidpagelayout.ThisisaveryflexiblelayoutfunctionwhosefunctionalitywewillexplorefurtherinChapter4,MasteringShiny'sUIFunctions.Next,thetitleoftheapplicationisdefinedverysimplyusingthetitlePanel()function.Thenfollowsthemainlayoutinstruction;inthiscase,wearegoingtousethesimplestUIlayout,sidebarLayout(),whichplacesinputsontheleft(orright,optionally)andthemainoutputsectioninthemiddle.AlloftheUIelementsaredefinedwithinthesidebarLayout()function.
ThenexttwoinstructionsperformthemainUIsetup,withsidebarPanel()settinguptheapplicationcontrolsandmainPanel()settinguptheoutputarea.ThesidebarPanel()phrasewillusuallycontainalloftheinputwidgets;inthiscase,thereisonlyone:textInput().ThetextInput()widgetisasimplewidgetthatcollectstextfromatextboxthatuserscaninteractwithusingthekeyboard.Theargumentsareprettytypicalamongmostofthewidgets,andareasfollows:
inputId:Thisargumentnamesthevariable,soitcanbereferredtointheserver.Rfilelabel:Thisargumentgivesalabeltoattachtotheinput,sousersknowwhatitdoesvalue:Thisargumentgivestheinitialvaluetothewidgetwhenitissetup;allthewidgetshavesensibledefaultsforthisargument—inthiscase,itisablankstring,""
Whenyoustartout,itcanbeagoodideatospelloutthedefaultargumentsinyourcodeuntilyougetusedtowhichfunctioncontainswhicharguments.Italsomakesyourcodemorereadableandremindsyouwhatthereturnvalueofthe
functionis(forexample,value=TRUEwouldsuggestaBooleanreturn).
ThefinalfunctionismainPanel(),whichsetsuptheoutputwindow.YoucanseethatIusedoneoftheHTMLhelperfunctionstomakealittletitle,h3("...").Therearemanyofthesehelperfunctionsincluded,andtheyareincrediblyusefulforsituationswhereyoueitherdon'twanttodotoomuchstylingwithHTMLandCSSyourselfordon'tknowhow.Let'sjuststopveryquicklytolookatafewexamples.
AnoteonHTMLhelperfunctionsThereareseveralHTMLhelperfunctionsthataredesignedtogenerateHTMLtogostraightonthepage;type?pintheconsoleforthecompletelist.ThesefunctionsallowyoutomarkuptextinHTMLusingRcode—forexample,h3("Heading3")willproduce<h3>Heading3</h3>,p("Paragraph")willproduce<p>Paragraph</p>,andsoon.
TheHTMLtagsthatwillbeavailableusingthisfunctioninclude<br>,<code>,<div>,<em>,<h1>,<img>,<p>,<pre>,and<span>.Evenmoretagsareavailablethroughtheuseofthetags()function.ThereismoreonShinyandHTMLinChapter3,IntegratingShinywithHTML,andafulllistoftagsandotherhelpisavailableinthedocumentationatshiny.rstudio.com/articles/html-tags.html.
ThefinishedinterfaceTheotherelementthatgoesinmainPanel()isanareatohandlereactivetextthatisgeneratedwithintheserver.Rfile—thatis,acalltotextOutput()withthenameoftheoutputasdefinedinserver.R—inthiscase,textDisplay.
Thefinishedinterfacelookssimilartothefollowingscreenshot:
Ifyou'regettingalittlebitlost,don'tworry.Basically,Shinyisjustsettingupaframeworkofnamedinputandoutputelements;theinputelementsaredefinedinui.Randprocessedbyserver.R,whichthensendsthembacktoui.R,whichknowswheretheyallgoandwhattypesofoutputtheyare.
Theserver.RoftheminimalexampleLet'snowlookatserver.R,whereitshouldallbecomeclear.Lookatthefollowingcode:
function(input,output){#serverisdefinedhereoutput$textDisplay=renderText({#assigntooutput$textDisplay
paste0("Yousaid'",input$comment,#fromtextinput
"'.Thereare",nchar(input$comment),
"charactersinthis.")
})
}
Wedefinethereactivecomponentsoftheapplicationwithinfunction(input,output){...}.Onthewhole,twotypesofthingsgoinhere.Reactiveobjects(forexample,data)aredefined,whicharethenpassedaroundasneeded(forexample,todifferentoutputinstructions),andoutputsaredefined,suchasgraphs.Thissimpleexamplecontainsonlythelatter.We'llseeanexampleofthefirsttypeinthenextexample.
Anoutputelementisdefinednextwithoutput$textDsiplay=renderText({..}).Thisinstructiondoestwobasicthings.First,itgivestheoutputaname(textDisplay)sothatitcanbereferencedinui.R(youcanseeitinthelastpartofui.R).Second,ittellsShinythatthecontentcontainedwithinisreactive(thatis,itwillbeupdatedwhenitsinputschange)andittakestheformoftext.WewillcoveradvancedconceptsinreactiveprogrammingwithShinyinalaterchapter.TherearemanyexcellentillustrationsofreactiveprogrammingattheShinytutorialpages,availableatrstudio.github.io/shiny/tutorial/#reactivity-overview.
Theactualprocessingisverysimpleinthisexample.Inputsarereadfromui.Rbytheuseofinput$...,sotheelementnamedinui.Rascomment(goandhavealookatui.Rnowtofindit)isreferencedwithinput$comment.
Thewholecommandusespaste0()tolinkstringswithnospaces(equivalenttopaste(...,sep="")),picksupthetexttheuserinputtedwithinput$comment,andprintsit,alongwiththenumberofcharacterswithinit(nchar())andsomeexplanatorytext.
That'sit!YourfirstShinyapplicationisready.Thefullcodecanbefoundhere,h
ttps://gist.github.com/ChrisBeeley/4202605cf2e64b4f609e.Usingtheseverysimplebuildingblocks,youcanactuallymakesomereallyusefulandengagingapplications.
TheprogramstructureSincethefirsteditionofthisbook,asignificantchangehastakenplacewithregardstohowShinyapplicationsarestructured.Anewfeaturehasbeenadded,givingustheabilitytoplacethemallwithinonecodefile.Thisismostusefulwhenbuildingsmalldemonstrationsorexamplesforotherusers,whocanjustpastethewholecodefileintotheconsoleandhavetheapplicationrunautomatically.Inordertomakeuseofthisfunctionality,justcombinethecodefromserver.Randui.R,asshowninthefollowingexample:
library(shiny)
server<-function(input,output){
#contentsofserver.Rfile
}
ui<-fluidPage(#orotherlayoutfunction
#contentsofui.Rfile
)
shinyApp(ui=ui,server=server)
Thisisusefulneitherforlargeapplications,norforthepurposesofexplainingthefunctionsofparticularpartsofcodewithinthisbook,soweshallignoreitfromnowon.Justbeawarethatit'spossible;youmaywellcomeacrossitonforums,andyoumaywishtocontributesomesmallexamplesyourself.
AnoptionalexerciseIfyouwanttohaveapracticebeforewemoveon,taketheexistingcodeandmodifyitsothattheoutputisaplotofauser-definednumberofobservations,withthetextasthetitleoftheplot.Theplotcallshouldlooklikethefollowing:
hist(rnorm(XXXX),main="YYYY")
Intheprecedinglineofcode,XXXXisanumbertakenfromafunctioninui.Rthatyouwilladd(sliderInput()ornumericInput())andYYYYisthetextoutputwealreadyusedintheminimalexample.YouwillalsoneedtomakeuseofrenderPlot();type?renderPlotintheconsoleformoredetails.
Sofarinthischapter,wehavelookedataminimalexampleandlearnedaboutthebasiccommandsthatgointheserver.Randui.Rfiles.Thinkingaboutwhatwe'vedoneintermsofreactivity,theui.Rfiledefinesareactivevalue,input$comment.Theserver.Rfiledefinesareactiveexpression,renderText().Itdependsoninput$comment.
NotethatthisdependenceisdefinedautomaticallybyShiny.TherenderText()expressionusesanoutputfrominput$comment,soShinyautomaticallyconnectsthem.Wheneverinput$commentchanges,renderText()willautomaticallyrunwiththenewvalue.TheoptionalexercisegavetworeactivevaluestotherenderPlot()call,andso,whenevereitherchanges,renderPlot()willbererun.Intherestofthischapter,wewilllookatanapplicationthatusessomeslightlymoreadvancedreactivityconcepts,andbytheendofthebook,wewillhavecoveredallthecapabilitiesthatShinyoffersandwhentousethem.
EmbeddingapplicationsindocumentsTobrieflyreturntothesubjectofinteractivedocuments,itisworthnotingthatitispossibletoembedentireShinyapplicationswithininteractivedocumentsratherthanhavingtheratherstripped-downfunctionalitythatweembeddedwithinadocumentearlierinthechapter.Justincludealinktothedirectorythatholdstheapplication,likethis:
```{r,echo=FALSE}
shinyAppDir(
"~/myApps/thisApplication",
)
```
Formoreinformationaboutembedding,type?shinyAppintheconsole.
WidgettypesBeforewemoveontoamoreadvancedapplication,let'shavealookatthemainwidgetsthatyouwillmakeuseofwithinShiny.I'vebuiltaShinyapplicationthatwillshowyouwhattheyalllooklike,aswellastheiroutputsandthetypeofdatatheyreturn.Torunit,justenterthefollowingcommand:
>library(shiny)
runGist(6571951)
Thisisoneoftheseveralbuilt-infunctionsofShinythatallowyoutoruncodehostedontheinternet.DetailsaboutsharingyourowncreationsinotherwaysarediscussedinChapter9,Persistence,Storage,andSharing.Thefinishedapplicationlookslikethefollowingscreenshot:
Youcanseethefunctionnames(checkboxGroupInputandcheckboxInput)asnumberedentriesontheleft-handsideofthepanel;formoredetails,justtype?checkboxGroupInputintotheconsole.
Ifyou'recuriousaboutthecode,it'savailableatgist.github.com/ChrisBeeley/6571951.
TheGapminderapplicationNowthatwe'vegotthebasics,let'sbuildafullapplication.Beforeweproceed,notethatwewillneedtoinstallafewpackages—tidyverse,gapminder,leaflet,andggmap.EachcanbeinstalledfromCRAN(theofficialRpackagerepository)usingthecodephrasesinstall.packages("tidyverse"),install.packages("gapminder"),andsoon.Wewillnotinstallggmapthisway,though.Atthetimeofwriting,thereisabugintheCRANversion.We'llinstallthedevversioninstead,asshowninthefollowingcode:
install.packages("devtools")
library(devtools)
devtools::install_github("dkahle/ggmap")
Theapplicationisprettysimpletogetusstarted,butitillustratesseveralimportantmethodsandprinciplesinShiny.Itfeaturestabbedoutput,whichallowstheusertoselectdifferentinputsorgroups,whichareeachkeptonaseparatetab.ItfeaturesthestandardShinylayout—thesidebarlayout—withinputsattheleftandoutputsinthemainsection.Thethreetabsgiveatextualsummary,alinegraphshowinglifeexpectancyovertime,andamapwithcirclesscaledtothelifeexpectancyineachcountry.Theapplicationlookslikethefollowingscreenshot:
TheUIIfyoucan,downloadandrunthecodeanddatafromgithub.com/ChrisBeeley/gapminder(thedatagoesinthesamefolderasthecode)soyoucangetanideaofwhateverythingdoes.Ifyouwanttoruntheprogramwithoutcopyingtheactualdataandcodetoyourcomputer(copyingdataandcodeispreferablesothatyoucanplaywithit),justuseanotherfunctiontoshareandrunapplications(wewilldiscussthisinChapter5,EasyJavaScriptandCustomJavaScriptFunctions),asshowninthefollowingcode:
runGitHub("gapminder","ChrisBeeley")
AsinmanyShinyapplications,ui.Risbyfarthesimplerofthetwocodefiles,andisasfollows:
library(shiny)
library(leaflet)
fluidPage(
titlePanel("Gapminder"),
Thefirstthingthatwedoisloadtheleaflet()package.Theleaflet()packageisusedfordrawinginteractivemaps.We'lldiscussitindetaillater.Fornow,justnotethatitisnecessarytoloaditintheUIinordertoaccessaspecialoutputfunctionthatdoesn'texistwithinShiny—leafletOutput().ThewholeinterfaceiswrappedinfluidPage().Thisistrueofmost,butnotall,Shinyapplications.WewilllookindetailatthebasictypesofShinypageandsetupinChapter4,MasteringShiny'sUIFunctions.Itperformsthebasicsetupofthepage,makingitresponsiveandensuringthatitlooksgoodondifferentlysizedbrowsers.ThereismoredetailonthisfunctioninChapter4,MasteringShiny'sUIFunctions.ThetitlePanel()functionisusedtogivetheapplicationanicebigtitle.
ThenextsectionshowsthesetupofthestandardlayoutofaShinyapplication.Simplified,itlookslikethefollowingcode,withsidebarPanel()definingtheinputsontheleftandmainPanel()theinputsontheright,allwrappedinsidebarLayout():
sidebarLayout(
sidebarPanel(
#inputcontrolsinhere
),
mainPanel(
#outputshere
)
Donotethatkeepingtheinputsontheleftandtheoutputsontherightisthetypicallayout,butShinyistotallyflexible.Youcankeepsomeinputsontherightifyouwish,orevenoutputsontheleft.ThisisjusttheusualsetupthatyouwillseeinasimpleShinyapplication.
WithinsidebarPanel(),wefindtwoinputs,sliderInput()andcheckboxInput(),asshowninthefollowingcode:
sliderInput(inputId="year",
label="Yearsincluded",
min=1952,
max=2007,
value=c(1952,2007),
sep="",
step=5
),
checkboxInput("linear",label="Addtrendline?",value=FALSE)
ThesliderInput()functionallowsyourusertoselectanumber,orarange,usingaslider.Inthiscase,theywillbeselectingarange,therangeofyearsthattheyareinterestedinforthetextsummaryandthetimeseriesgraph.Wehavealreadyseenthefirsttwoarguments,andtheywillbecomeveryfamiliartoyou—inputId
givingtheinputanamesoitcanbereferredtoasinput$name(input$yearinthiscase)andlabel,whichgivesthecontrolanicefriendlylabelfortheusertoreadsotheycanunderstandtheapplication.Youcanseetheotherarguments,givingaminimum,amaximum,andaninitialvalue(inthiscase,two,todefinearange).Wecanoptionallydefinetheseparatortoseparatethethousandsinthenumbers—inthiscase,settingittonothinginordertostoptheyearsappearingas1,952,2,007,andsoon.Notetheuseofstep=5.Thisgivesthenumericgapbetweeneachnotchontheslider.IfitisleftasNULL,thenShinywillpicksomethingsensible.Weuse5herebecausetheGapminderdataissplitintofive-yearchunks.
ThecheckboxInput()functionverysimplygivesyouatickboxthatreturnsTRUEwhentickedandFALSEwhenunticked.Thisexampleincludesallthepossiblearguments,whichgivesitanameandlabelandselectstheinitialvalue.
Thisconcludestheinputs.Let'slookattheoutputpanel,asshowninthefollowingcode:
),#endofsidebarPanel()
mainPanel(
tabsetPanel(
tabPanel("Summary",textOutput("summary")),
tabPanel("Trend",plotOutput("trend")),
tabPanel("Map",leafletOutput("map"),
p("Mapdataisfromthemostrecentyearintheselectedrange;
radiusofcirclesisscaledtolifeexpectancy"))
)
)
ProbablythemostunfamiliarpartofthiscodeistheuseoftabsetPanel().Thisallowsmultipleframesofoutputtobeshownonthescreenandselectedbytheuser,asiscommoninGUIsthatsupporttabbedframes.Notethatprocessingisonlycarriedoutforthecurrentlyselectedtab;invisibletabsarenotupdatedbehindthescenes,butratherwhentheyaremadeactive.Thisisusefultoknowwheresomeoralltabsrequiresignificantdataprocessing.Thesetupisverysimple,withacalltotabsetPanel()containingseveralcallstotabPanel()inwhicheachofthetabsisdefinedwithaheadingandapieceofoutputasdefinedinserver.R.Asyoucansee,therearethreetypesofoutputgivenbythreedifferentoutputfunctions—text,shownbytextOutput();aplot,shownbyplotOutput();andamapproducedwiththeleafletpackageandshownwithleafletOutput()(moreonwhichlater).
DataprocessingAsyouwritemoreandmorecomplexprograms,it'stheserver.Rfilethatwillbecomethelargestbecausethisiswhereallthedataprocessingandoutputgoes,andisevenwheresomeofthefunctionsthathandleadvancedUIfeatureslive.Let'slookatthechunksinorderandtalkabouttheworkcarriedoutineachsection.
Thefirstchunkofcodelookslikethefollowing:
library(tidyverse)
library(gapminder)
library(leaflet)
library(ggmap)
Youcanseethepackagesthatweneedbeingloadedatthetop.Weloadthetidyverseforaccesstothingssuchasdplyrandggplot,formungingdataandplotting,respectively.Thegapminderpackageisusedtoloadasubsetofthegapminderdata.IthasbeenverykindlymungedandplacedonlineintheformofaCRANpackagebyJennyBryan.Wewilltalkmoreaboutthedatathatitmakesavailablelater.Theleafletpackage—which,aswementionedbefore,isusedfordrawingmaps—isloadednext.Finally,theggmappackageisloaded.Thiswillbeusedtoconvertcountrynamestolatitudeandlongitudeforplottingbyleaflet.
Thenextthingthatwedoismungeor,ifwealreadymungedit,loadthedata,asshowninthefollowingcode:
if(!file.exists("geocodedData.Rdata")){
mapData=gapminder%>%
mutate(country2=as.character(country))%>%
group_by(country)%>%
slice(1)%>%
mutate_geocode(country2,source="dsk")%>%
select(-country2)
mapData=left_join(gapminder,mapData)%>%
group_by(country)%>%
fill(lon)%>%
fill(lat)
save(mapData,file="geocodedData.Rdata")
}else{
load("geocodedData.Rdata")
}
I'mnotgoingtodistractfromShinybytalkingtoomuchaboutthiscode.Essentially,itcheckstoseewhetherthedataisalreadyintherelevantdirectory(whichitwillbeafteryou'verunitonce).Ifitis,itjustloadsthedata.Ifnot,itpreparesitbyproducingasmallerdatasetwithoneinstanceofeachcountryandgeocodingit(thatis,turningitintolatitudeandlongitude),thencombiningitwiththewholedataset(toputalltheyearsforeachcountrybackin),andfillingintheresultingmissinggeocodingsusingtheincrediblyusefulfill()functionofdplyr.Havingdoneallthat,itsavesthedatafornexttime(sincequeryingtheAPIforall142countriestakesquitealongtime).Don'tworrytoomuchifyoucan'tfollowthiscodeatthemoment;it'sreallytherejusttogetthedataontoyourcomputerinasimpleway.
It'sworthnotingthatdatainstructions—andindeedanyothercode—thatappearabovefunction(input,output){}areexecutedoncewhentheapplicationstartsandthenserveallinstancesoftheShinyapplication.Thismakesnodifferencewhenyou'redevelopingonalocalmachine,sincethecodewillstartfresheverytimeyourunit,butonceyoumoveyourcodetoaserver,itcanmakeabigdifference,sincethecodeisonlyrunonceforalltheusersandapplicationinstancesyouhave(untilyourestarttheShinyserver,whichmanagesconnectionstoyourapplicationsonaserver,ofcourse).Therefore,anyintensivedataorprocessingcallsarebestkeptinthissectiontoavoidyourusershavingtowaitalongtimefortheirapplicationtoload.
ReactiveobjectsTherestofthiscodeiswrappedinfunction(input,output){}.Thisisthereactivepartofyourapplication.Wewilltalkinmoredetailaboutreactiveprogramminglaterinthebook;fornow,let'sjustsaythatreactiveprogrammingisatypeofprogrammingwherewhentheinputschange,theoutputschange.
Thenextpieceofcodelookslikethis:
theData=reactive({
mapData%>%
filter(year>=input$year)
})
Thiscodedefinesareactiveobject.Upuntilnow,theserver.Rfilehasjustcontainedalistofoutputcommandsthatproducetheoutput,readytofilltheallocatedspacesinui.R.Here,we'reworkingalittledifferently.Sometimes,youwanttoprepareareactivedatasetonceandthenpassitaroundtheprogramasneeded.
Thismightbebecauseyouhavetabbedoutputwindowsthatusethesamedataset(asinthiscase),andyoudon'twanttowriteandmaintaincodethatpreparesthedataaccordingtothevaluesofreactiveinputswithinallthreefunctions.Thereareothertimeswhenyouwanttocontroltheprocessingofdatabecauseitistimeintensiveoritmightmakeanonlinequery(suchasinthecaseofaliveapplicationthatqueriesdataliveinresponsetoreactiveinputs).Thewaythatyoucantakemorecontroloverdataprocessingfromreactiveinputs,ratherthandistributingitthroughyouroutputcode,istousereactiveobjects.Areactiveobject,likeareactivefunction,changeswhenitsinputchanges.Unlikeareactivefunction,itdoesn'tdoanything,butisjustadataobject(dataframe,number,list,andsoon)thatcanbeaccessedbyotherfunctions.Crucially,whenitruns,itsoutputiscached.Thismeansthataslongasitsinputsdon'tchange,itwillnotrerunifitiscalledonagainbyadifferentpartofyourapplication.Thispreventsyourapplicationfromrunningthesamedataprocessingtasksrepeatedly.
Inthiscase,thedataprocessingisverysmall,sowe'renotreallysavinganytime
usingreactiveobjects;however,itisstillgoodpracticetousethembecause,aswejustmentioned,itmeansthatyouonlyhaveonedatafunctiontomaintainratherthanseveralscatteredbetweentheoutputs.
Let'shavealookatanexample:
theData=reactive({
mapData%>%
filter(year>=input$year[1],year<=input$year[2])
})
Thisfunction,verysimply,filtersthedatasothatitcontainsonlydatathatwasloggedbetweentheyearsselectedinsliderInput().Thefirstthingtonoteisthat,unlikepreviousexamples,wearenotmakingacall,suchasoutput$lineGraph<-renderPlot({...})oroutput$summaryText<-renderText({...}).Instead,wearemarkingwhateverisinsidethecallreactivebyenclosingitinreactive({...})andassigningit,verysimply,totheData.ThisgeneratesareactiveobjectnamedtheData.Thiscanbeaccessedbycallingthisfunction—thatis,byrunningtheData()(forthewholedataframe),ortheData()$variableName(foravariable),ortheData()[,2:10](forthesecondtothetenthvariable).NotethebracketsaftertheData.Moreinformationonreactiveobjects,whentousethem,andlotsofadviceaboutmanagingandcontrollingreactivityanddataprocessinginyourapplicationisgiveninChapter8,CodePatternsinShinyApplications.
OutputsFinally,wewilllookathowtheoutputsaredefined.Let'slookfirstatthecodethatproducesthefirsttabofoutput,whichisthetextualsummaryofthedata.
TextsummaryThetextsummaryfunctioniswrappedinrenderText().ThisletsShinyknowthatthisfunctionreturnstext,asshowninthefollowingcode:
output$summary=renderText({
paste0(input$year[2]-input$year[1],"yearsareselected.Thereare",
length(unique(theData()$country)),"countriesinthedatasetmeasuredat",
length(unique(theData()$year)),"occasions.")
})
Rememberthatthisoutputispickedupinui.RwiththetextOutput()function,whichtakesthe"summary"identifier,which,asyoucansee,isthenamegivenbytheoutput$summary=renderText({...})function.Thefunctionverysimplypastestogethersometextandsomevaluestakenfromtheapplication.
TrendgraphsThenextpartofthecodespecifiesthegraphofthetrendinlifeexpectancyusingggplot2.Let'slookateachpieceofcode:
#trend
output$trend=renderPlot({
thePlot=theData()%>%
group_by(continent,year)%>%
summarise(meanLife=mean(lifeExp))%>%
ggplot(aes(x=year,y=meanLife,group=continent,colour=continent))+
geom_line()+ggtitle("Graphtoshowlifeexpectancybycontinentovertime")
if(input$linear){
thePlot=thePlot+geom_smooth(method="lm")
}
print(thePlot)
})
Thefirstlinedefinestheoutputasareactiveplot.Thesecondinstructionuseschaineddplyrinstructions,aswesawinChapter1,BeginningRandShiny,firsttogroupthedatabycontinentandyear,andthentocalculatethemeanlifeexpectancyinthegroupsthatresult(inAfricaineachyear,inAmericaineachyear,andsoon).Thisisthensentontoaggplotinstructionforthegivenyearonthexaxis,meanlifeexpectancyontheyaxis,andthegroupings/colorsdefinedbythecontinent.NotethatbyassigningittothePlot,wedonotprintit,butmerelybegintobuilditup.
Thenextsectiontestsforthevalueofthesmoothingcheckbox,input$linear,andifitisTRUE,aregressionlineisaddedtotheplot.Therequirementtoprint()ggplotgraphicshasbeendroppedfromShiny(inthefirsteditionofthisbook,itwasnecessaryforyoutoprint()eachgraphic).Thisgraphicwillneedtobeprintedinanyenvironment,eventheconsole,becauseithasnotbeencalledwithggplot(),butmerelyassignedtothePlot.Normally,whenusingggplot()directlyinaShinysession,therewillbenoneedtoprint()theplot.
AmapusingleafletFinally,weaddamaptothelasttabusingtheleafletpackage.TheleafletpackageisanRinterfacetotheexcellentJavaScriptleafletpackage,whichcanbeusedtobuildawidevarietyofmapsfrommanydifferentsourcesandannotatethemwithdatainanumberofdifferentways.Formoredetailsontheleafletpackage,visitrstudio.github.io/leaflet/.Thecodeisrelativelysimpleandlookslikethefollowing:
output$map=renderLeaflet({#1
mapData%>%#2
filter(year==input$year[2])%>%#3
leaflet()%>%#4
addTiles()%>%#5
setView(lng=0,lat=0,zoom=2)%>%#6
addCircles(lng=~lon,lat=~lat,weight=1,#7
radius=~lifeExp*5000,#8
popup=~paste(country,lifeExp))#9
})
It'snotessentialthatyouunderstandthiscodeatthispoint,andwewillbemakinguseoftheleafletpackageintherestofthebook,butlet'slookatitlinebylinetogetanideaofhowitworks:
Line1:Thisdefinesoutput$mapascontainingaleafletmapsothatShinyknowshowtohandletheoutput.Line2:ThistellsthefunctiontousethemapDatathatwealreadyloadedrightatthetopofthefile(not,inthiscase,thereactivedatareturnedbymapData()).Line3:Thisfiltersthedatasothatonlythemostrecentdatagivenintheselectioncanbeselected.Line4:ThistellsRthatwewanttousealeaflet.Line5:Thisdrawsthebackgroundtothemap—lotsofdifferentmapsareavailable.See?addTilesformorehelponthisfunction.Line6:Thisdefineswhichbitofthemapwearelookingatandhowzoomedinweare.Thiswillfocusonlatitude0andlongitude0,withthemapzoomedmostofthewayout.Lines7to9:Theseaddtheactualdatapoints;here,youcanseethatwegivethelatitudeandlongitudeasone-sidedequations,reducetheweight(penwidth),andgivetheformulatodeterminetheradiusofthecircles(life
expectancywitha5,000scalersoitshowsattherightsize).Finally,wedefinethepopup,whichiswhatappearswhenyouclickeachcircle(inthiscase,thenameofthecountryandtheactuallifeexpectancy).
AdvancedlayoutfeaturesInthischapter,wehavecoveredthemostsimpleofthelayoutfeaturesinShinywiththehelpofthesidebarLayout(),mainPanel(),andtabsetPanel()functions.Inlaterchapters,wewillbuildlargerandmorecomplexapplications,includingdashboards,andmakeuseofmoreadvancedlayoutfeatures.Itisworthpausingherebrieflytotakeaquicklookattheothertypesoflayoutthatareavailablesothatyoucanthinkaboutthebestwaytoimplementyourownapplicationaswegothroughthenextcoupleofchapters.
ThereareessentiallytwomorebroadtypesoflayoutfunctionthatyoucanuseinShiny.ThefirstusesthelayoutfeaturesofBootstrapandallowsyoutopreciselydefinethelayoutofyourapplicationusingagridlayout.Essentially,BootstrapasksyoutodefinetheUIasaseriesofrows.Eachrowcanbefurthersubdividedintocolumnsofvaryingwidths.
Eachsetofcolumnsonarowhaswidthsthataddupto12.Inthisway,youcanquiteeasilyspecify,forexample,thefirstrowasconsistingofonecolumnofwidth12,andthenthesecondrowasconsistingoftwocolumns,oneofwidth2andoneofwidth10.Thiscreatesaheaderpanelacrossthetopofthescreen,andthenathinoneandathickonetwocolumnsbelow,whichyouarelikelytoputUIandoutputelementsin,respectively.Awholevarietyoflayoutsispossible,andnestingandoffsettingthecolumnsarebothpossible,whichmeansthatwiththerightcode,youcanbuildanygrid-basedlayoutyoucanthinkof.Wewilllookatthecodetoimplementcustomlayoutsinthiswayinmoredetailinlaterchapters.
Ifyoudon'twanttosetupyourUIinthatmuchdetail,Shinyprovideslotsofotherlayoutsthatcanbeveryeasilycalledtolayoutyourapplicationinaparticularway.ThesefunctionsincludenavbarPage(),navList(),verticalLayout(),andsplitLayout().WewilllookatallofthelayoutfunctionsinChapter4,MasteringShiny'sUIFunctions,butit'sworthnotingherethattherearelotsofdifferentwaystocontrolthelayoutofaShinyapplication.
SummaryInthischapter,wehavecoveredalotofground.We'veseenthatShinyapplicationsaregenerallymadeupoftwofiles:server.Randui.R.You'velearnedwhateachpartofthecodedoes,includingsettingupui.Rwiththepositionandtypeofinputsandoutputs,andsettingupserver.Rwiththedataprocessingfunctions,outputs,andanyreactiveobjectsthatarerequired.
Theoptionalexerciseshavegivenyouachancetoexperimentwiththecodefilesinthischapter,varyingtheoutputtypes,usingdifferentwidgets,andreviewingandadjustingtheirreturnvaluesasappropriate.You'velearnedaboutthedefaultlayoutinShiny,sidebarLayout(),aswellastheuseofmainPanel()andtabsetPanel().
You'vealsolearnedaboutreactiveobjectsandwhenyoumightusethem.There'smoreonfinelycontrollingreactivitylaterinthebook.
Inthenextchapter,you'regoingtolearnhowtointegrateShinywithyourowncontentusingHTMLandCSS.
IntegratingShinywithHTMLSo,webuiltourownapplicationtoexploretheGapminderdata.YoulearnedaboutthebasicsetupofaShinyapplicationandsawalotofthewidgets.Itwillbeimportanttorememberthisbasicstructurebecausewearegoingtocoveralotofdifferentterritoriesinthischapterand,asaconsequence,wewon'thaveasingleapplicationattheend,aswedidinthepreviouschapter,butlotsofbitsandpiecesthatyoucanusetostartbuildingyourowncontent.
Buildingoneapplicationwithallofthesedifferentconceptswouldcreateseveralpagesofcode,anditwouldbedifficulttounderstandwhichpartdoeswhat.Asyougothroughthechapter,youmightwanttorebuildtheGapminderapplication,oranotheroneofyourown(ifyouhaveone),usingeachoftheconcepts.Ifyoudothis,youwillhaveabeautifullystyledandinteractiveapplicationbytheendthatyoureallyunderstand.Or,youmightliketojustbrowsethroughandpickoutthethingsthatyouareparticularlyinterestedin;youshouldbeabletounderstandeachsectiononitsown.Let'sgetstartednow.
Inthischapter,wearegoingtocoverthefollowingareas:
AddingHTMLtonativeShinyapplicationsCustomizingShinyapplications,orwholewebpages,usingHTMLStylingyourShinyapplicationusingCSSIncorporatingShinycontentwithinanotherwebpageHTMLtemplates
RunningtheapplicationsandcodeForconvenience,Ihavegatheredtogetheralltheapplicationsinthischapter.Thelinktotheliveversions,aswellasthesourcecodeanddata,canbefoundonmywebsiteatchrisbeeley.net/website.Ifyoucan,runtheliveversionfirstandthenbrowsethecodeasyougothrougheachexample.
ShinyandHTMLItmightseemquiteintimidatingtocustomizetheHTMLinaShinyapplication,andyoumayfeelthatbygoingunderthehood,itwouldbeeasytobreaktheapplicationorruinthestyling.YoumaynotwanttobotherrewritingeverywidgetandoutputinHTMLjusttomakeoneminorchangetotheinterface.
Infact,Shinyisveryaccommodating,andyouwillfindthatitwillquitehappilyacceptamixofShinycodeandHTMLcodeproducedbyyouusingShinyhelperfunctionsandtherawHTML,alsowrittenbyyou.So,youcanstylejustonebuttonorcompletelybuildtheinterfacefromscratchandintegrateitwithsomeothercontent.I'llshowyouallofthesemethodsandgivesomehintsaboutthetypeofthingsyoumightliketodowiththem.Let'sstartsimplebyincludingsomecustomHTMLinanotherwisevanillaShinyapplication.
CustomHTMLlinksinShinyWewillnowmakealittletoyapplicationthatshowsthelifeexpectancyovertimeforthreedifferentcountries,andthatgivesalinktotheWikipediapageforeach.It'snotreallydesignedtolookgoodorbeuseful,butrathertoillustrateseveralthingsthatarepossiblewhenyouwishtoaddsmallamountsofHTMLtoanativeShinyapplication,ratherthanbuildingfromscratchorworkingwithHTMLtemplates(moreonwhichlater).
We'regoingtobuildanapplicationthatallowsyoutoselectfromacoupleofdifferentcountriesandviewthelifeexpectancyovertimeinthatcountry.AcustomHTMLbuttonshowstheWikipediapagefortheselectedcountry.
ui.RLet'stakealookattheui.Rfilefirst:
fluidPage(
tags$head(HTML("<linkhref='http://fonts.googleapis.com/css?family=Jura'
rel='stylesheet'type='text/css'>")),
h2("CustomHTML",style="font-family:'Jura';
color:green;font-size:64px;"),
sidebarLayout(
sidebarPanel(
radioButtons("country","Country",
c("Afghanistan","Bahrain","Cambodia"))
),
mainPanel(
h3("Timeseries"),
HTML("<p><em>Lifeexpectancy</em>overtime</p>"),
plotOutput("plotDisplay"),
htmlOutput("outputLink")
)
)
)
There'saquickmethodofstylingtextinlineinthisexample.First,let'sfetchafontfromGoogleFonts,whichyoucanseebysimplyusingthetags$functiontogenerateanHTML<head>andthenplacingthelinkinside,asshowninthefollowingcode:
tags$head(HTML("<link
href='http://fonts.googleapis.com/css?family=Jura'
rel='stylesheet'type='text/css'>"))
Nowthatwehavethefontavailable,it'sasimplematterofplacingitwithintheapplication.ThetitlePage()argument,whichisoftenusedatthetopofaShinyapplication,hasbeenremovedandreplacedwithh2()becauseh2()willallowyoutoinsertinlinestylingstraightintothefunction,whereastitlePage()willnot.Moreover,althoughtheacceptedargumentsaredifferent,theactualHTMLgeneratedbytitlePage()andh2()isthesame.Totestthisyourself,gototheconsoleandtypetitlePage("Test")andthentryusingh2("Test").
Inbothcases,thesamethingisreturned—<h2>Test</h2>.Thisisareallyuseful
wayoflearningmoreaboutShinyandhelpingyoutodebugmorecomplexapplicationsthatmakeuseofShinyfunctions,aswellasHTMLandCSS.Sometimes,itcanbenecessarytoruntheapplicationandtheninspecttheHTMLusingtheinspectsourcefunctionavailableinmostwebbrowsers(Chrome,Explorer,Firefox,Safari,andsoon).Runningthefunctiondirectlyintheconsoleisalotquickerandlessconfusing.Havingdonethis,it'sasimplematterofpassingstylinginformationintoh2(),asyouwouldpassinlinestylingintoafontinHTML:
style="font-family:'Jura';color:green;font-size:64px;"
AlsoincludedistheHTML()function,whichmarkstextstringsasHTML,preventingtheHTMLfromescaping,whichwouldotherwiserenderthisonthescreenverbatim.TheothernewpartofthisfileisthehtmlOutput()function.This,inasimilarwaytotheHTML()function,preventsHTMLfromescapingandallowsyoutouseyourownmarkup,butthistimefortextpassedfromserver.R.Here'sthefinalinterface:
server.RTheserver.Rinthiscaseisquitesimple.First,weloadthetidyverseandthedata(thedatawecreatedinthepreviouschapter),asshowninthefollowingcode:
library(tidyverse)
load("geocodedData.Rdata")
Then,wedefinethereactivepartoftheapplication,asshowninthefollowingcode:
function(input,output){
output$plotDisplay<-renderPlot({
gapminder%>%
filter(country==input$country)%>%
ggplot(aes(x=year,y=lifeExp))+
geom_line()
})
output$outputLink<-renderText({
link="https://en.wikipedia.org/wiki/"
paste0('<formaction="',link,input$country,'">
<inputtype="submit"value="GotoWikipedia">
</form>')
})
}
Thefirstpartofthecodeshouldholdnosurprises,beingafairlysimplelinegraphproducedusingggplot2.Youcanseethefamiliarfilter()functionbeingusedtorestricttheseriestodatafromonecountry.ThesecondpartofthecodeusesrenderText(),whichwehaveusedbeforetoshowtextwithinanoutput.TheonlydifferencehereisthatwearegoingtogenerateourownHTML.Youwillrecallfromtheui.RthatthisoutputneedstobewrappedinhtmlOutput(),nottextOutput(),topreservetheHTML(whichwouldotherwisebeautomaticallyescapedinShiny).
That'sanicegentleintroductiontostartingtomixHTMLintoyourShinyapplications.Now,let'sgototheotherextremeandlookatbuildingyourinterfaceentirelyinHTML.
AminimalHTMLinterfaceNowthatwehavedippedourtoesintoHTML,let'sbuilda(nearly)minimalexampleofaninterfaceentirelyinHTML.TouseyourownHTMLinaShinyapplication,createtheserver.Rfileasyounormallywould.Then,insteadofaui.Rfile,createafoldernamedwwwandplaceafilenamedindex.htmlinsidethisfolder.Thisiswhereyouwilldefineyourinterface.
index.htmlLet'slookateachchunkofindex.htmlinturn,asshowninthefollowingcode:
<html>
<head>
<title>HTMLminimalexample</title>
<scriptsrc="shared/jquery.js"
type="text/javascript"></script>
<scriptsrc="shared/shiny.js"type="text/javascript"></script>
<linkrel="stylesheet"type="text/css"
href="shared/shiny.css"/>
<styletype="text/css">
body{
background-color:#ecf1ef;
}
#navigation{
position:absolute;
width:300px;
}
#centerdoc{
max-width:600px;
margin-left:350px;
border-left:1pxsolid#c6ec8c;
padding-left:20px;
}
</style>
</head>
The<head>sectioncontainssomeimportantsetupcodeforShiny,loadingtheJavaScriptandjQueryscripts,whichmakeitwork,aswellasastylesheetforShiny.YouwillneedtoaddsomeCSSofyourown,unlessyouwanteveryelementoftheinterfaceandoutputtobedisplayedasabigjumbleatthebottomofthescreen,makingthewholethinglookveryugly.Forsimplicity,I'veaddedsomeverybasicCSSinthe<head>section;youcould,ofcourse,useaseparateCSSfileandaddalinktoit,justasshiny.cssisreferenced.
ThebodyoftheHTMLcontainsalltheinputandoutputelementsthatyouwanttouse,aswellasanyothercontentthatyouwantonthepage.Inthiscase,I'vemixedupaShinyinterfacewithapictureofmycatsbecausenowebpageiscompletewithoutapictureofacat!Havealookatthefollowingcode:
<body>
<h1>MinimalHTMLUI</h1>
<divid="navigation">
<p>
<label>Titleforgraph:</label><br/>
<textareaname="comment"rows="4"
cols="30">Myfirstgraph</textarea>
</p>
<divclass="attr-colshiny-input-radiogroup"id="graph">
<p>
<label>Whatsortofgraphwouldyoulike?</label><br>
<inputtype="radio"name="graph"value="1"
title="Straightline"checked>Linear<br>
<inputtype="radio"name="graph"value="2"
title="Curve">Quadratic<br>
</p>
</div>
<label>Here'sapictureofmycats</label><br/>
<imgsrc="cat.jpg"alt="Mycats"width="300"height="300">
</div>
<divid="centerdoc">
<divid="textDisplay"class="shiny-text-output"></div>
<br/>
<divid="plotDisplay"class="shiny-plot-output"
style="width:80%;height:400px"></div>
</div>
</body>
</html>
Therearethreemainelements:atitleandtwo<div>sections,onefortheinputsandonefortheoutput.TheUIisdefinedwithinthenavigation<div>,whichisleftaligned.RecreatingShinywidgetsinHTMLisprettysimple,andyoucanalsouseHTMLelementsthatarenotgiveninShiny.InsteadofreplacingthetextInput()widgetwith<inputtype="text">(whichisequivalenttoit),Ihaveinsteadused<textarea>,whichallowsmorecontroloverthesizeandshapeoftheinputarea.
TheradioButtons()widgetcanberecreatedwith<inputtype="radio">.Youcanseethatbothgetanameattribute,whichisreferencedintheserver.Rfileasinput$name(inthiscase,input$commentandinput$graph).Youwillnotethatthereisanother<div>aroundtheradiobuttondefinition,<divclass="attr-colshiny-input-radiogroup"id="graph">.ThisisanecessaryextrawhenusingShinywithHTML,asdiscussedatgoo.gl/Lrx9GB.AnotheradvantageofusingyourownHTMListhatyoucanaddtooltips;Ihaveaddedthesetotheradiobuttonsusingthetitleattribute.
Theoutputregionissetupwithtwo<div>tags:onethatisnamedtextDisplayandpicksupoutput$textDisplay,asdefinedinserver.R,andtheotherthatisnamedplotDisplayandpicksupoutput$plotDisplayfromtheserver.Rfile.Inyourowncode,youwillneedtospecifytheclass(asshowninthepreviousexample)aseithershiny-text-output(fortext),shiny-plot-output(forplots),orshiny-html-output(fortablesoranythingelsethatRwilloutputasHTML).Youwillneedtospecifythe
heightofplots(inpx,cm,andsoon),andcanoptionallyspecifythewidtheitherinabsoluteorrelative(%)terms.
Justtodemonstratethatyoucanthrowanythingintherethatyoulike,there'sapictureofmycatsunderneaththeUI.Youwill,ofcourse,havesomethingabitmoresophisticatedinmind.Addmore<div>sections,links,pictures,andwhateveryoulike.
server.RLet'stakeaquicklookattheserver.Rfile,showninthefollowingcode:
function(input,output){
output$textDisplay<-renderText({
paste0("Title:'",input$comment,
"'.Thereare",nchar(input$comment),
"charactersinthis."
)
})
output$plotDisplay<-renderPlot({
par(bg="#ecf1ef")#setthebackgroundcolor
plot(poly(1:100,as.numeric(input$graph)),type="l",
ylab="y",xlab="x")
})
}
Texthandlingisdoneasbefore.You'llnotethattherenderPlot()functionbeginsbysettingthebackgroundcolortothesameasthepageitself(par(bg="#ecf1ef");formoregraphicaloptionsinR,see?par).Youdon'thavetodothis,butthegraph'sbackgroundwillbevisibleasabigwhitesquareifyoudon't.
Theactualplotitselfusesthepoly()commandtoproduceasetofnumbersfromalinearorquadraticfunctionaccordingtotheuserinput(thatis,input$graph).Notetheuseofas.numeric()tocoercethevaluewegetfromtheradiobuttondefinitioninindex.htmlfromastringtoanumber.
ThisisacommonsourceoferrorswhenusingShinycode,andyoumustremembertokeeptrackofhowvariablesarestored,whetheraslists,strings,orothervariabletypes,andeithercoercetheminplace(asdonehere),orcoercethemallinonegousingareactivefunction.
Thelatteroptioncanbeagoodideatomakeyourcodelessfiddlyandbuggybecauseitremovestheneedtokeeptrackofvariabletypesineverysinglefunctionyouwrite.ThereismoreaboutdefiningyourownreactivefunctionsandpassingdataaroundaShinyinstanceinthenextchapter.Thetype="l"argumentreturnsalinegraph,andthexlabandylabargumentsgivelabelstothexandyaxes.
Thefollowingscreenshotshowsthefinishedarticle:
Finishedarticle
IncludingaShinyapponawebpageThesimplestwaytomixShinyandanexistingwebpageisbyusinganiframe.JustlikewiththeRMarkdowndocumentinthepreviouschapter,itallowsyoutoincorporateanentireShinyapplicationintoanexistingdocument,exceptinthiscase,thedocumentisawebpageratherthanaMarkdowndocument.Theonlyrestrictionisthatyouwillneedtohosttheapplicationsomewhereontheinternetsoyoucanpointtoit.Onceyou'vedonethat,it'sassimpleasjustpointingtotheapplication,likeso:
<iframesrc="https://chrisbeeley.net/shinyapps/shinybook3rdedition/chapter2/widgettypes/"frameborder="0"width="950"height="800"></iframe>
HTMLtemplatesThedevelopersofShinythemselvesnotedthatalthoughithasalwaysbeenpossibletoproduceentireShinyapplicationsinHTML,itisveryraretofindanyexampleswheresomebodyhasdoneso.ThisisprobablybecauseofShiny'sabilitytoproduceattractiveapplicationsusingpureRcode(anditsabilitytoincorporatesnippetsofHTML),aswellastherelativecomplexityofwritingthewholeinterfaceyourself.Tomakethingssimpler,Shiny0.13addedtheabilitytouseHTMLtemplates.UsinganHTMLtemplate,youcanveryeasilymixtogetherHTMLandShinycode.Therearetwomainwaysofdoingthis:firstlybydefiningthecodeinlinewithintheHTML,andsecondlybydefiningitwiththeui.Rfile.Inbothcases,youwillneedthreefiles—aserver.Rfile,aui.Rfile,andanotherfile,atthesamedirectorylevel(notinawww/folder),whichwillbeHTMLandwhichyoucannameanythingyoulike.Let'scallourstemplate.html.
InlinetemplatecodeLet'slookfirstattheslightlysimplermethod,whichisincludingthecodeinlinewithintheHTMLtemplate.We'lltakesomeelementsoftheGapminderapplicationinthepreviouschapterandincorporatethemintoanHTMLtemplate.Theserver.Rfileisunchanged,althoughthisversionisalittleshorterthanthepreviousonebecausewehavetakenoutsomeoftheoutputs.Forthesakeofbrevity,we'llagainloadthedatathatwegeneratedinthepreviouschapter.Ifyouwanttoseehowtogeneratethisdata,havealookbackatthepreviouschapter.
server.RToremindyouoftheserver.Rfile,andtoshowyouwhichbitswehaveretained,let'slookattheserver.Rfilefirst,showninthefollowingcode:
library(tidyverse)
library(gapminder)
load("geocodedData.Rdata")
function(input,output){
theData=reactive({
mapData%>%
filter(year>=input$year[1])
})
output$trend=renderPlot({
thePlot=theData()%>%
group_by(continent,year)%>%
summarise(meanLife=mean(lifeExp))%>%
ggplot(aes(x=year,y=meanLife,group=continent,
colour=continent))+geom_line()+
ggtitle("Graphtoshowlifeexpectancybycontinentovertime")
if(input$linear){
thePlot=thePlot+geom_smooth(method="lm")
}
print(thePlot)
})
}
Therearenosurprisesinthiscode,really;itisjustacut-downversionofthecodefromthepreviouschapter.Youcanseeareactivefunctiontobringbackthedata(theData()),andacalltorenderPlot(),whichproducesalinegraphusingggplot().
ui.Randtemplate.htmlTheui.Risincrediblysimple,andlookslikethis:
htmlTemplate("template.html")
That'sit!YouarejusttellingShinywhattheHTMLfileiscalled.Rememberthatwecalledittemplate.html.Simple.Now,therealworkisdonewiththeHTMLfile.
Let'slookattheHTMLfile'svarioussections.First,thehead:
<html>
<head>
{{headContent()}}
{{bootstrapLib()}}
</head>
Thefirstthingtonoteishowthefilewillbewritten.Essentially,itwillbeHTML,interspersedwithShinycodewrappedindoublecurlybraces.So,thisfirstsectionopensthehtmltagandtheheadtagandthenrunstwofunctions,whicharegiveninsidedoublecurlybraces.YoualwaysneedtoincludeheadContent(),whichplacestheboilerplatecodenecessarytomakethepagework,justaswesawwiththeminimalHTMLwebpagethatwelookedatearlierinthechapter.SomeShinyfunctionsrequireyoutoincludebootstrapLib(),whichloadsvariousBootstrapcomponents.It'snotreallyworthworryingaboutwhetheryouareusingthosecomponentsornot,sinceyoumightaddthemlater,orincludethematfirstandthentakethemaway,andsoon.Justincludeiteverytimeandthenyouwon'tneedtoworry.
Next,wedefinethebodyoftheapplication,asshowninthefollowingcode:
<body>
<h1>MinimalHTMLUI</h1>
<divclass="container-fluid">
<divclass="row">
<divclass="col-sm-4">
<h3>Controlpanel</h3>
{{sliderInput(inputId="year",
label="Yearsincluded",
min=1952,
max=2007,
value=c(1952,2007),
sep="",
step=5)}}
{{checkboxInput("linear",label="Addtrendline?",
value=FALSE)}}
</div>
<divclass="col-sm-8">
{{plotOutput("trend")}}
<p>Formoreinformationabout<strong>Shiny</strong>lookatthe
<ahref="http://shiny.rstudio.com/articles/">documentation.</a></p>
<hr>
<p>Ifyouwishtowritesomecodeyoumayliketousethepre()functionlikethis:</p>
<pre>checkboxInput("linear",label="Addtrendline?",value=FALSE)</pre>
</div>
</div>
</div>
</body>
</html>
Asyoucansee,theShinycodeisincludedjustasyouwouldseeitinanynormalShinyapplication(indeed,thesearethefunctionsfromChapter2,ShinyFirstSteps),butineachcase,theyarewrappedwithdoublecurlybraces.It'sassimpleasthat.Itispossibletopassvaluesintothetemplatefromtheui.R,likethis:
htmlTemplate("template.html",customStep=10)
ThisvaluecannowbeaccessedfromwithintheShinycodeinthetemplate,likeso:
{{sliderInput(inputId="year",
label="Yearsincluded",
min=1952,
max=2007,
value=c(1952,2007),
sep="",
step=customStep)}}
Definingcodeintheui.RfileTheothermethodofdefininganHTMLtemplateisverysimilar,exceptinthiscase,theShinycodeisdefinedintheui.Rfile.Theserver.Rcodeisunchanged.
ui.RLet'sfirstlookattheui.Rfile,showninthefollowingcode:
htmlTemplate(
"template.html",
slider=sliderInput(inputId="year",
label="Yearsincluded",
min=1952,
max=2007,
value=c(1952,2007),
sep="",
step=5),
checkbox=checkboxInput("linear",label="Addtrendline?",value=FALSE),
thePlot=plotOutput("trend")
)
Asyoucansee,inthiscase,wearedefiningtheinputsintheui.Rfile.ThisisjustlikepassingthevalueofcustomStepinthepreviousexample,exceptthatinthiscasewearepassingwholewidgets,notjustvalues.Thesevaluesarenowreferredtoverysimplyinthetemplate.html,likeso:
...preamble
<divclass="container-fluid">
<divclass="row">
<divclass="col-sm-4">
<h3>Controlpanel</h3>
{{slider}}
{{checkbox}}
</div>
<divclass="col-sm-8">
{{thePlot}}
<p>Formoreinformationabout<strong>Shiny</strong>lookatthe
<ahref="http://shiny.rstudio.com/articles/">documentation.</a></p>
<hr>
...restofHTML
Themethodsareverysimilar.DefininginlineismoreappropriatewhenyourShinycodeisrelativelybrief,asitwasinthisexample.IfyouhavelargeamountsofShinyUIcode,youwillprobablyprefertodefineitinui.RtoavoidclutteringyourHTMLfilewithRcode.
TakeastepbackandrewindWecoveredquitealotofmaterialinthissection,soit'sworthspendingamomentreviewingwhatwecovered.We'velookedataddingHTMLtoanapplicationthatisotherwisewritteninpureShiny.We'velookedatstylingapplicationswithCSS,aswellasbuildingentireinterfaceswithHTMLusingHTMLtemplates,orbymakingthewholethingfromscratch.
ExerciseIfyouhaven'talreadybeentempted,nowisdefinitelyagoodtimetohaveagoatbuildingyourownapplicationwithyourowndata.ThenextchaptercoversadvancedtopicsinShinyUIdesign,andthoughyouarewelcometoplowon,alittlepracticalexperiencewiththefunctionswillstandyouingoodsteadforthenextchapter.Ifyou'reinterestedinsharingyourcreationsrightaway,feelfreetojumptoChapter9,PersistentStorageandSharingShinyApplications.
HowyougoaboutbuildingyourfirstapplicationwillverymuchdependonyourpreviousexperienceandwhatyouwanttoachievewithShiny,butaswitheverythinginlife,itisbettertostartsimple.Startwiththeminimalexamplegiveninthepreviouschapterandputinsomedatathat'srelevanttoyou.Shinyapplicationscanbehardtodebug(comparedwithinteractiveRsessions,atleast),soinyourearlyforays,keepthingsverysimple.
Forexample,insteadofdrawingagraph,startwithasimplerenderText()callandjustprintthefirstfewvaluesofavariable.ThiswillatleastletyouknowthatyourdataisloadingokayandthattheserverandUIarecommunicatingproperly.AlwaysmakesurethatanycodeyouwriteinR(graphs,tables,datamanagement,andsoon)worksinaplaininteractivesessionbeforeyouputitintoaShinyapplication!
DebuggingProbablythemosthelpfulandsimpledebuggingtechniqueistousecat()toprinttotheRconsole.Therearetwomainreasonswhyyoushoulddothis:
Thefirstistoputinlittlemessagestoyourself—forexample,cat("Thisbranchofcodeexecuted").ThesecondistoprintthepropertiesofRobjectsifyouarehavingproblemsrelatingtodatastructure,size,ortype.Thecat(str(x))phraseisparticularlyuseful,andwillprintalittlesummaryofanykindofRobject,whetheritisalist,adataframe,anumericvector,oranythingelse.
TheotherusefulmethodisastandardmethodofdebugginginR,browser(),whichcanbeputanywhereinyourcode.Assoonasitisexecuted,ithaltstheapplicationandentersdebugmode(see?browser).ThereismoreondebuggingShinyapplicationsinChapter8,CodePatternsinShinyApplications.
Onceyouhavetheapplicationworking,youcanstarttoaddcustomHTMLusingShiny'sbuilt-infunctionsorrewriteui.Rintoindex.html.ThechoiceherereallydependsonhowmuchHTMLyouwanttoinclude.AlthoughintheoryyoucancreateverylargeHTMLinterfacesinShinyusing.htmlfilesreferencedbytheincludeHTML()command,youwillendupwitharatherconfusinglistofmarkupsscatteredacrossdifferentfiles.
Bootstrap3andShinySincethefirsteditionofthisbook,ShinyhasmigratedfromBootstrap2toBootstrap3.Therearenowmanyfunctions—bothwithinShinyandinitspackages(suchasshinydashboardandshinythemes,bothavailableonCRAN)—thatenhancethewayyoucanstyleyourapplicationsstraightfromShiny.Thereismoreonadvancedlayoutfunctions,Bootstrap3,andthepackagestohelpyoutostyleyourapplicationsinChapter4,MasteringShiny'sUIFunctions.
SummaryThischapterhasincorporatedquiteapileoftoolsintoyourShinytoolbox.YoulearnedhowtousecustomHTMLstraightfromaminimalui.RUIsetupandhowtobuildthewholethingfromscratchusingHTMLandCSS.
Inthenextchapter,youaregoingtolearnmoreaboutbuildingaUIwithShiny.Shinyincludesalotofdifferentlayoutfunctions,whichwillallbedescribed,andwewillalsolookatmakingnicetables,usingprogressbars,andmodals,aswellasmakingyourUIreactive.
MasteringShiny'sUIFunctionsSofarinthisbook,we'vemasteredthebasicsofShinybybuildingourownGapminderdata-explorerapplication,andwe'velookedathowtostyleandextendShinyapplicationsusingHTMLandCSS.Inthischapter,wearegoingtoextendourtoolkitbylearningaboutmoreofShiny'sUIfunctions.Theseallowyoutotakecontrolofthefinedetailsofthewayyourapplicationlooksandbehaves.
Inordertodothis,we'regoingtochangethewaytheUIworksintheGapminderapplicationtomakeitmoreintuitiveandwell-featured.ThefinishedcodeanddataforthisadvancedGapminderapplicationcanbefoundathttps://chrisbeeley.net/website/.
Inthischapter,wewillcoverthefollowingtopics:
UsingShiny'smanylayoutfunctionseffectivelyLearninghowtoshowandhidepartsoftheinterfaceChangingtheinterfacereactivelyProducingbeautifultableswiththeDataTableslibraryShowingprogressbarstousersinlong-runningfunctionsShowingmessageswithmodals
Shiny'slayoutfunctionsTherearequitealotofdifferentlayoutfunctionsinShiny,andunderstandingwhattheyalldocanhelpyoutobuildtheinterfacethatyouwanteasily.It'spossibletocombinealotofthem,too,sowe'llreviewtheformandfunctionofeach,andthenshowalargerexampleattheendthatcombinesthemtogether.TherearethreemaintypesoflayoutfunctionthatShinyoffers.ThefirstiswhatIwouldcallsimplelayoutfunctions.Theseproduceastraightforwardkindoflayout,withoutmuchinthewayofstyling,anditisthesefunctionsthatcanoftenbecombinedtogether.ThenextkindiswhatIwouldcallcomplete.Thisisalayoutfunctionthatoffersalittlestylingandissuitablefordefininganentireapplication.Itwouldthereforeoftenbeusedonitsownwithoutanyothertypeoflayoutfunction.ThelastkindIwouldcallthedo-it-yourselfanditreferstoafunctionthatdoesn'treallydomuchatall,butratherjustdefinesthespaceinwhichyouwillplaceyourwidgets.Thisfunction,ofcourse,isverysuitableforcombiningwithotherfunctionstoproducethesetupyouwant.Let'srevieweachtypeinturn.
SimpleThroughoutthesecodeexamples,we'regoingtohaveaverysimpleserver.Rfile,whichwillnotchange.Itsimplydrawsatableandnothingelse.Wewillthereforealsobreakthehabitoftherestofthisbookandusethesingleapp.Rfilestructurefortheseexamples,sincetheyaresosmall.Withthatinmind,let'slookatthefirstapplication,whichwillbeflowLayout():
server=function(input,output){
output$table=renderTable({
head(iris)
})
}
ui=flowLayout(
sliderInput("slider","Slider",min=1,max=100,value=50),
textInput("text","Text"),
tableOutput("table")
)
shinyApp(ui,server)
InflowLayout,elementsareorderedlefttoright,toptobottom.Resizingthewindowcausestheelementstoreorderthemselvessotheyfit,lefttoright,toptobottom.Downloadthecodeexampleandtryitforyourself.Throughoutthissection,it'sagoodideatodownloadtheexamplesandtrythemout.
ThenextexampleisverticalLayout().Inthisandsubsequentcodeexamples,wewillnotbotherwiththeserverandshinyApp()codesincetheywillneverchange.Wewilljustlookatthevalueforuiineachcase:
ui=verticalLayout(
sliderInput("slider","Slider",min=1,max=100,value=50),
textInput("text","Text"),
tableOutput("table")
)
Asthenamesuggests,itmerelyarrangeselementsvertically.Asmanyasyousupply.Hereitisinaction:
ThefinalsimplefunctionissplitLayout().Ittakesasmanyelementsasyougiveitandarrangesthemonthepagelefttoright.Bydefault,eachisgiventhesamewidthbutyoucanoptionallysetthewidthforeachmanually.Hereisanexample:
ui=splitLayout(
cellWidths=c("20%","20%","60%"),
sliderInput("slider","Slider",min=1,max=100,value=50),
textInput("text","Text"),
tableOutput("table")
)
Inthiscase,wehavesetthewidthstobetteraccommodatethetable(which,asyoucansee,isgiven60%ofthewidth).It'ssuperficiallysimilartoflowLayout(),inthatifyoulayoutanapplicationwithboth,theymaylooksimilaratfirst,butthetwocriticaldifferencesarethatsplitLayout()allowsyoutodefinethewidthofeachwidget,andthatifyouresizethewindow,flowLayout(truetoitsname)willflowontothenextrow,whereassplitLayout()willjustcrampeverythingup,as
showninthefollowingscreenshot:
SplitLayout
CompleteNow,wecometothecompletesetupfunctions,thosethatcanbeusedindividuallytomakeawell-styledapplication.First,ofcourse,wehavetheoldfavorite,sidebarLayout().Many,manyShinyapplicationsarewrittenusingthislayoutanditisanattractiveandfunctionalsetup,withawellpanelthathighlightsthecontrolsandalargeareaforoneormoreoutputs.We'vealreadyseenthissetup,butasaquickreviewofthebasicstructurewithoutalltheusualclutter,let'stakealookattheuicode:
ui=fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("slider","Slider",min=1,max=100,value=50),
textInput("text","Text")),
mainPanel(tableOutput("table"))
)
)
It'sworthnotingthatthislayoutrequirestheuseoffluidPage()tosetitupcorrectly.Thisfunctionisrequiredforthisandanotherlayoutfunction,whichwewillcovershortly,aswellasbeingusefulinitsownright(whichwewillalsocovershortly).
TheothertwocompletelayoutfunctionsarenavbarPOAge()andnavlistPanel().Theyworksimilarlyinthatbothprovidebuttonsforyoutopagethroughsetsofinputandoutput.Let'slookateach.Hereisthesamesetofinputandoutput,puttogetherasanavbar:
Navbar
Thisisoneoftheselectedpages,Inputs.ThetableisviewableontheTablepage,accessedbyclickingtheTablebuttononthebaratthetop.ThisisobviouslynotparticularlygoodUIdesign,it'sjustdonetoillustratehowthefunctionswork.
Thecodeisverysimpleandjustconsistsoftabpanels,likeyouwouldfindintabsetPanel():
ui=navbarPage("Navbardemo",
tabPanel("Inputs",
sliderInput("slider","Slider",
min=1,max=100,value=50),
textInput("text","Text")),
tabPanel("Table",tableOutput("table"))
)
Navlistsworkinaprettysimilarway,exceptthepagesforthebuttonsaregatheredoverontheleft,asshowninthefollowingscreenshot:
Thecodehereisverysimilar,exceptinthiscase,aswiththesidebarLayout()function,acalltofluidPage()isnecessary:
ui=fluidPage(
navlistPanel("Navlistdemo",
tabPanel("Inputs",
sliderInput("slider","Slider",
min=1,max=100,value=50),
textInput("text","Text")),
tabPanel("Table",tableOutput("table"))
)
)
DoityourselfWealreadysawfluidPage()beingusedtosetuptheenvironmentforotherlayoutfunctionstowork.ButthisfunctioncanalsobeusedtobuildaUIfromscratch,usingthefluidRow()function.UsingfluidRow()withinfluidPage()allowsyoutoimplementthestandardbootstrapgridlayout,asdescribedatw3schools.com/bootstrap/bootstrap_grid_system.asp.Essentially,theinterfaceisbuiltuprowbyrowusingthefluidRow()function.Eachrowcanbedividedintosectionsofarbitrarywidthusingthecolumn()function.Thecolumnfunctiontakesanumericargumentspecifyinghowwideitshouldbe.Thetotalofalloftheseargumentsinagivenrowshouldalwaysbe12.So,forexample,arowmightconsistofcolumnsofwidth3and9,andthefollowingrow,perhapswidthsof4,4,and4.Inthiscase,averysimpleimplementationusingfluidRow()mightlooklikethis:
ui=fluidPage(
fluidRow(
column(width=4,
sliderInput("slider","Slider",min=1,max=100,value=50),
textInput("text","Text")),
column(width=8,
tableOutput("table")
)
)
)
CombininglayoutfunctionsTofinish,let'slookathowtousedifferentlayoutfunctionstogethertoachieveasimpleandflexiblesetupforyourapplication.Therearelotsofdifferentwaysofdoingthis;thisexampleisdesignedjusttoshowyouthatyoucanuseawidevarietyoffunctionstowritequick,simplecode.Intruth,anydesigncanbemadeusingthegridsystem,givenenoughtimeandeffort,butitissometimessimplerjusttouseabuilt-infunction,asyoucansee.Inthisexample,I'veaddedacoupleofgraphstofleshouttheinterfaceabit,butwewon'tbotherlookingatthecodeforthem.They'rejustcreatedusingrenderPlot({plot(runif(10,runif(10))})andassignedtooutput$graph1andoutput$graph2.ThefinalUIcodelookslikethis:
ui=fluidPage(
fluidRow(
column(width=4,
sliderInput("slider","Slider",min=1,max=100,value=50)),
column(width=8,
tableOutput("table")
)
),
splitLayout(
plotOutput("graph1"),plotOutput("graph2")
),
verticalLayout(
textInput("text","Text"),
p("Moredetailshere"),
a(href="https://shiny.rstudio.com/tutorial/","Shinydocumentation")
)
)
Asyoucansee,youcanfreelymixtogethertheselayoutfunctionstotakeadvantageofthebenefitsofeach;fluidRow()togiveprecisecontroloverthewidthofthecolumns,splitLayout()forsimplicity,andverticalLayout()tostackthewidgetsontopofeachother.Thefinalapplicationlookslikethefollowingscreenshot:
StreamliningtheUIbyhidingelementsThisisasimplefunctionthatyouarecertainlygoingtoneedifyoubuildevenamoderatelycomplexapplication.Thoseofyouwhohavebeendoingextracreditexercisesand/orexperimentingwithyourownapplicationswillprobablyhavealreadywishedforthisor,indeed,havealreadyfoundit.
conditionalPanel()allowsyoutoshoworhideUIelementsbasedonotherselectionswithintheUI.Thefunctiontakesacondition(inJavaScript,buttheformandsyntaxwillbefamiliarfrommanylanguages)andaUIelement,anddisplaystheUIonlywhentheconditionistrue.We'regoingtoenhancetheGapminderapplicationtoshowtheoptiontoaddatrendlinetothegraphonlywhenthegraphisshown.Inordertodothis,we'regoingtohavetoknowwhichofthetabpanelsiscurrentlyselected,andinordertodothatwe'regoingtohavetogivethemaname.Let'sdothatnow.
NamingtabPanelelementsInordertoallowtestingforwhichtabiscurrentlyselected,we'regoingtohavetofirstgivethetabsofthetabbedoutputnames.Thisisdoneasfollows(withthenewcodeinbold):
tabsetPanel(id="theTabs",
tabPanel("Summary",textOutput("summary"),
value="summary"),
tabPanel("Trend",plotOutput("trend"),
value="trend"),
tabPanel("Map",leafletOutput("map"),
p("Mapdataisfromthemostrecentyearintheselectedrange"),
value="map")
)
Asyoucansee,thewholepanelisgivenanID(theTabs)andtheneachtabPanelisalsogivenaname(summary,trend,map).Theyarereferredtointheserver.Rfileverysimplyasinput$theTabs.
Finally,wecanmakeourchangestoui.RtoremovepartsoftheUIbasedontabselection:
conditionalPanel(
condition="input.theTabs=='trend'",
checkboxInput("linear",label="Addtrendline?",
value=FALSE)
),
Asyoucansee,theconditionappearsveryR/Shiny-like,exceptwiththe.operatorfamiliartoJavaScriptusersinplaceof$.ThisisaverysimplebutpowerfulwayofmakingsurethatyourUIisnotclutteredwithirrelevantmaterial.
BeautifultableswithDataTableLaterversionsofShinyaddedsupporttodrawtablesusingthewonderfulDataTablejQuerylibrary.Thiswillenableyouruserstosearchandsortthroughlargetablesveryeasily.ToseeDataTableinaction,visitthehomepageathttp://datatables.net/orruntheapplicationfeaturedinthischapter.
Thepackagecanbeinstalledusinginstall.packages("DT")andneedstobeloadedinthepreambletotheserver.Randui.Rfilewithlibrary(DT).Oncethisisdone,usingthepackageisquitestraightforward.Therearetwofunctions:oneinserver.R(renderDataTable)andoneinui.R(dataTableOutput).Theyareusedasfollows:
###server.R
output$countryTable=renderDataTable({
mapData%>%
filter(year==2007)%>%
select(-c(lon,lat))
})
###ui.R
tabPanel("Table",dataTableOutput("countryTable"),
value="table")
AnythingthatreturnsadataframeoramatrixcanbeusedwithinrenderDataTable().Outofthebox,itwillproduceattractive,searchable,pageabletables.Inthiscase,thetablelookslikethis:
InpreviousversionsofShiny,therewerenamespaceconflictsbetweenthedatatablefunctionsandthestandardfunctions,withShinydrawingastandard
tablewithsomeofthesamefunctionnamesastheDTpackage.ThesituationnowisthatShinywillproducetheseJavaScriptdatatablesoutofthebox,butwithoutenablingalloftheoptionsofthefullDTpackage(inparticular,client-sideprocessing).It'sworthwhile,then,tousetheDTpackageandloaditinserver.Randui.R.Let'scustomizethetableusingtheoptionsthatareavailablewiththeDTpackage.Thecodeisasfollows:
datatable(
mapData%>%
filter(year==2007)%>%
select(-c(lon,lat)),
colnames=c("Country","Continent","Year","Lifeexpectancy",
"Population","GPDpercapita"),
caption="Countrydetails",filter="top",
options=list(
pageLength=15,
lengthMenu=c(10,20,50))
)
Thefirstthingtonoticeisthatweneedtowrapthefunctionindatatable().TherenderDataTable()functionwillautomaticallyapplythattoanydataframeormatrixreturnedbyit,butinordertoaddoptionsweneedtoexplicitlyaddit.Youcanseethecolumnnamesbeingmadefriendlierwiththecolnamesargument,acaption,andacolumnfilterbeingaddedatthetopusingfilter='top'.Lastly,therearesomeoptionsthatareaddedfromwithinacalltooptions=list(...),inthiscasechangingthesizeofthetableandtheoptionsforrownumbersgivenforthetable(making10,20,or50rowstheoptionsavailable).Therearemany,manyoptionsavailable,soreadthedocumentationformoredetails:rstudio.github.io/DT/.
ReactiveuserinterfacesAnothertrickyouwilldefinitelywantupyoursleeveatsomepointisareactiveuserinterface.ThisenablesyoutochangeyourUI(forexample,thenumberorcontentofradiobuttons)basedonreactivefunctions.
Forexample,consideranapplicationthatIwroterelatedtosurveyresponsesacrossabroadrangeofhealthservicesindifferentareas.Theservicesarerelatedtoeachotherinquiteacomplexhierarchy,andovertime,differentareasandservicesrespond(orceasetoexist,ormerge,orchangetheirname),whichmeansthatforeachtimeperiodtheusermightbeinterestedin,therewouldbeatotallydifferentsetofareasandservices.Theonlysensiblesolutiontothisproblemistohavetheusertellyouwhichareaanddaterangetheyareinterestedinandthengivethembackthecorrectlistofservicesthathavesurveyresponseswithinthatareaanddaterange.
Theexamplewe'regoingtolookatisalittlesimplerthanthis,justtokeepfromgettingboggeddownintoomuchdetail,buttheprincipleisexactlythesameandyoushouldnotfindthisideatoodifficulttoadapttoyourownUI.Wearegoingtomakeaselector,whichallowstheusertopickoneoftheyearswithintheyearrangethattheyhaveselectedandhavethatyearplottedonthemap,insteadofbeingstuckwiththemostrecentyear.Thechoicewillbeconstrainedbythefactthatthedatawasonlyrecordedeverysevenyears.So,forexample,iftheuserselects1960to1990,theselectorboxwillcontainonlytheyears1962,1967,1972,1977,1982,and1987.
Thereactiveuserinterfaceexample–server.RWhenyouaremakingareactiveuserinterface,thebigdifferenceisthatinsteadofwritingyourUIdefinitioninyourui.Rfile,youplaceitinserver.RandwrapitinrenderUI().Then,allyoudoispointtoitfromyourui.Rfile.
Let'stakealookattherelevantbitoftheserver.Rfile:
output$yearSelectorUI=renderUI({
selectedYears=unique(mapData$year)
selectInput("yearSelector","Selectyear",
selectedYears)
})
Thefirstlinetakesthereactivedatasetthatcontainsonlythedatabetweenthedatesselectedbytheuserandgivesalltheuniquevaluesoftheyearwithinit.Thesecondlineisawidgettypethatwehavenotusedyetthatgeneratesacombobox.Theusualidandlabelargumentsaregiven,followedbythevaluesthatthecomboboxcantake.Thisistakenfromthevariabledefinedinthefirstline.Theoutputitself,thatis,thebitdefinedasoutput$something=renderUI({...}),canbegivenanynameyoulike.ItwillsimplybecalledbywhateverthatnameisinuiOutputwithinui.R,asweshallseeinamoment.Notethattheactualinputthatwearecreating,thatis,thethingthatwillbecalledbyinput$something,hasthenamegivenintheselectInput()function.So,inthiscasetheinputwillbecalledinput$yearSelectorandnotinput$yearSelectorUI.Toavoidconfusion,IhaveadoptedanamingconventionformyuseofrenderUI,whereIgivetheoutput$thesamenameasselectInput(),orwhicheverotherfunctionitis,butwithUIattheend.ThishelpsmenottomixthemupandmeansIcanalwaysremembertheotherifIknowthefirstone.Let'slookatthecorrespondingentryintheui.Rnow.
Thereactiveuserinterfaceexample–ui.RTheui.Rfilemerelyneedstopointtothereactivedefinition,asshowninthefollowinglineofcode(justaddittothelistofwidgetswithinsidebarPanel()):
uiOutput("yearSelectorUI")
Youcannowpointtothevalueofthewidgetintheusualway,asinput$yearSelector.
TherearemoreadvancedthingsyoucandowithareactiveUIusingtheinsertUI()andremoveUI()functions,whichallowyoutoinsertarbitrarycontrolsorsetsofcontrolsandremovecontrols,respectively.Althoughtheyarequitesimpletoactuallyproduce,makinguseofthemisquitetrickybecauseoftheneedtokeeptrackofthenamesgiventoeachinput,aswellaswhichhavebeenaddedandremovedsofar.Asaconsequence,wewillnotlookatanexampleinthisbook;seethedocumentationforasimpleexampleandyoumaywishtobearinmindthatitispossibleshouldyoueverneeditinalargeapplication.
ProgressbarsItisquitecommoninShinyapplications,andinanalyticsgenerally,tohavecomputationsordata-fetchesthattakealongtime.Sometimes,itwillbenecessaryfortheusertowaitforsometimebeforetheiroutputisreturned.Incasessuchasthis,itisagoodpracticetodotwothings:toinformtheuserthattheserverisprocessingtherequestandhasnotsimplycrashedorotherwisefailed,andtogivetheusersomeideaofhowmuchtimehaselapsedsincetheyrequestedtheoutputandhowmuchtimetheyhaveremainingtowait.
ThisisachievedverysimplyinShinyusingthewithProgress()function.Thisfunctiondefaultstomeasuringprogressonascalefrom0to1andproducesaloadingbaratthetopoftheapplicationwiththeinformationfromthemessageanddetailargumentsoftheloadingfunction.
YoucanseeinthefollowingcodethatthewithProgressfunctionisusedtowrapafunction(inthiscase,thefunctionthatdrawsthemap),withmessageanddetailargumentsdescribingwhathashappenedandaninitialvalueof0(value=0,thatis,noprogressyet):
withProgress(message='Pleasewait',
detail='Drawingmap...',value=0,{
...functioncode...
})
Asthecodeissteppedthrough,thevalueofprogresscansteadilybeincreasedfrom0to1(forexample,inafor()loop)usingthefollowing:
incProgress(1/3)
Thethirdtimethisiscalled,thevalueofprogresswillbe1,whichindicatesthatthefunctionhascompleted(althoughothervaluesofprogresscanbeselectedwherenecessary;see?withProgess()).Tosummarize,thefinishedcodelooksasfollows:
withProgress(message='Pleasewait',
detail='Drawingmap...',value=0,{
...functioncode...
incProgress(1/3)
...functioncode...
incProgress(1/3)
...functioncode...
incProgress(1/3)
...functioncode...
})
It'sassimpleasthat.Again,takealookattheapplicationtoseeitinaction.Ifyouneedtogiveyourusermoredetailedinformationabouthowfartheyarethroughtheprocessing,theincProgress()functionalsotakesmessageanddetailarguments,whichmeansyoucanchangetheinformationbeinggivenbywithProgress()asyoustepthroughthefunction.Forexample,youmaywishtowriteincProgress(1/3,detail="Summarizingdata"),perhapsincProgress(1/3,message="Generatinggraph"),andsoon.
ProgressbarwithshinycssloadersTheshinycssloaderspackagemakesiteveneasiertoallowyourusertoseethatanoutputisloading.ItisavailableonCRANandsocanbeinstalledwiththis:
install.packages(“shinycssloaders”)
Outputsincludinggraphsandtableswillnowshowananimatedbusyiconwhiletheyload.ThecodeisassimpleaswrappinganoutputinwithSpinner(),orevenjustpipingittowithSpinner():
withSpinner(plotOutput(“myplot”))
Or,youcanusethefollowing:
plotOutput(“myplot”)%>%
withSpinner()
ModalsModalsareaUIelementfromBootstrapandarepop-upmessagesthatcantellyourusermoreaboutwhattheapplicationisdoing.Theycanbeusefultogiveauserwarnings,ortoallowtheusertorequestmoreinformationaboutanoutputiftheywishtoknowmore.Youcanwriteamodalverysimply,usingtwofunctionsintheserver.Rfile.ThemodalDialogfunctionproducesthemodal,andtheshowModalfunctionshowsit.Youcanusethemtogetherjustlikethis:
showModal(modalDialog(
title="Warning",
"Thisisawarning"
))
Thiswillgiveusasimpledialogwithatitleandamainsection:
Butwecandomoreinterestingthingsthanthiswithmodaldialogs.Therearetwowaysthatwecanexpandtheirfunctionality.First,becausethemodalDialog()functionwillacceptanyShinyUIelements,notjusttext,youcanaddHTMLelementssuchashorizontalrule,withhr(),andeveninputandoutput.Andsecond,youcangeneratethemodaldialogusingthemodalDialog()functionelsewhere,andmerelypasstheoutputtoshowModal().Theapplicationdoesn'treallydoanythingsensible,butit'sjustforillustration.We'regoingtohaveabuttonthatlaunchesamodaldialog.Themodaldialogwillcontainanactionbuttonandaninstructionnottopressit.Iftheuserdoespressit,afunctionelsewheredecideswhetherwe'realldoomedortheygotawaywithitthistime.Let'shavealookatthecode.We'llstartveryquicklywiththeonelinethatweaddtotheui.Rfiletoshowthebuttonthatmakesthemodaldialog:
actionButton("showModal","Launchloyaltytest")
Thisisthefirsttimethatwe'veseenanactionbutton.ActionbuttonsaresimplyHTMLcontrolsthatstartoffwithavalueof0andincrementby1eachtimethey'repressed.Onedoesn'tnormallyusethisvalue,althoughsometimesitcanbeusefultoknowhowmanytimesthebuttonwaspressed.Instead,theactionbuttonisusedbecausereactivefunctionscanformdependenciesonit,meaningtheywillrunwhenthebuttonispressed.Includingacalltoanactionbutton(byjustwritinginput$showModalononelineofthecode)willcauseanyreactivefunction,whetheritbeanoutputorotherwise,torerunwhenthebuttonispressed.Thereisalsoaspecialfunction,observeEvent({}),thatreactstoeventssuchasbuttonpushes,whichwe'regoingtousenow.ForadetaileddescriptionofhowtouseobserveEvent({})anditscousin,observe({}),seeChapter8,CodePatternsinShinyApplications.Fornow,it'senoughtoknowthatobserveEvent(input$showModal,{...})willruneverytimesomeonepushestheshowmodalbutton.
Havingsetupthebuttontolaunchit,let'sproducethefirstmodal:
observeEvent(input$showModal,{
showModal(modalDialog(
title="Loyaltytest",
actionButton("dontPress","Don'tpressthis")
))
})
Asyoucansee,straightawaywe'vemadethingsmoreinterestingbyincludingaShinyfunctioninsidethemodalratherthanjustsometext.Now,we'regoingtosetupanothermodalifsomeonepressestheactionbuttoninthefirst:
observeEvent(input$dontPress,{
showModal(testOutcome(sample(c(TRUE,FALSE),1)))
})
YoucanseetheshowModal()functionthatacceptsamodaldialogobjectandtheobserveEvent()function,againreactingtoabuttonpush.Butthistime,we'regeneratingthemodalwithafunction,testOutcome().Youcanseealsothatwe'repassingavaluetotestOutcome(),eitherTRUEorFALSE,chosenrandomly.Thisletsthefunctionknowwhethertheuserhasdoomedusallorgotawaywithitthistime.Let'slookatthefunction:
testOutcome=function(chance){
modalDialog(title="Outcome",
ifelse(chance,"You'vedoomedusall!",
"Yougotawaywithitthistime!"))
}
Asyoucansee,thefunctionisverysimpleandcreatesamodaldialogthatdecides,basedonthevaluefedtoit,whetherwe'realldoomed.Hopefully,thatshouldgiveyouagoodgroundinginhowwecanusefunctionstocreatemodals.Ifyou'recomfortablewiththis,exploretheexamplesinthedocumentation;theyuseadvancedconceptsinShiny,suchasreactivevalues(coveredinChapter8,CodePatternsinShinyApplications).
AlternativeShinydesignsAtthetimeofwriting,thereisquiteanexcitingnewdevelopmentintheworldofShinyUI,whichisthatdevelopersarestartingtoprovideShinyinterfacestoUIframeworksotherthanBootstrap.Thefirstexampleofthis,whichwillhopefullyleadtomanymore,isanimplementationoftheMaterialdesignframeworkforShiny.MaterialdesignistheveryflatdesigncreatedbyGooglein2014andfamiliartoanyuseroftheAndroidoperatingsystem.Hereisanexample:
TheShinypackageitselfisbasedontheopensourceimplementationofMaterialdesign,MaterializeCSS(materializecss.com/),andiscalledshinymaterial.ItisavailableonCRAN(cran.r-project.org/web/packages/shinymaterial/index.html).ThepackageitselffeaturesdifferentfunctionsfromvanillaShiny,material_radio_button()andmaterial_modal(),forexample,buttheprinciplesarethesameandanyShinydevelopershouldfinditeasytousethedifferentfunctionstogiveatotallydifferentfeeltotheirapplication.
SummaryInthischapter,welookedatdifferentwaystomakeyourapplicationgood-lookingandeasytouse.Welookedattherangeoflayoutfunctionsandhowbesttousethem,andwelookedatmakinginputthatshows,hides,andchangesitscontentsinresponsetothestateoftheapplication.Wealsolookedatmakingandcustomizingdatatables,andshowingprogressbarsandmessagestoyouruser,aswellasatotallynewwaythatShinyapplicationscanlookbasedontheMaterialdesignframeworkfromGoogle.
ThenextchapterisallaboutusingJavaScript,allthewayfromusingJavaScriptfromRcodewiththeshinyjspackagetoproducingacomplexapplicationthatusesJavaScripttopassmessagesbackandforthbetweentheShinyserverandtheclient.
EasyJavaScriptandCustomJavaScriptFunctionsWithShiny,JavaScript,andjQuery,youcanbuildprettymuchanythingyoucanthinkof;moreover,ShinyandjQuerywilldoalotoftheheavylifting,whichmeansthatfairlyminimalamountsofcodewillberequired.Inthischapter,wewillcover:
UsingJavaScripttoreadandwritetotheDOMUsingJavaScripttosendmessagesbetweenclientandserverEasyJavaScriptwiththeshinyjspackageUsingyourownJavaScriptwithextendShinyjsListeningforeventswithJavaScriptUsingJavaScriptlibrarieswithhtmlwidgets
JavaScriptandShinyTheconnectionbetweenJavaScriptandShinyisanotherreasontorecommendRStudioasanIDEbecauseitperformsbeautifulsyntaxhighlightingonJavaScriptstraightoutofthebox(although,clearly,othertexteditorsandIDEsmaydothisorbeeasilyconfiguredtodoso).
Beforeweproceed,it'sworthreviewingthedifferencebetweenserverandclient-sidecodeandwhatthey'reusedfor.JavaScriptgainedpopularityasaclient-sidelanguage,whichranonwebbrowsersandaddedinteractivitytowebsitesthatwouldotherwisebestaticHTMLandCSSfiles,whichweredownloadedfromservers.
Ithasfoundincreasinguseontheserverside(forexample,withNode.js),butwearegoingtouseitontheclientsideandsowillnotconsiderthisanyfurther.So,inthiscase,JavaScriptisrunningontheclientside.Theserversideinthiscase,ofcourse,isR,andspecificallythecommandsaretobefoundintheserver.Rfile.ShinyandJavaScript(and,byextension,jQuery)can,asserverandclientrespectively,passthingsbackandforthbetweenthemselvesasyouwish.
Also,it'sworthnotingthattherearetwowaysforShinyandJavaScripttointeractwitheachother.
Thefirstisperhapsthesimplestandwillbeconsideredfirst.BecauseShinyandJavaScriptcanbothreadandwritetothewebpage(thatis,totheDocumentObjectModel(DOM),itisquitesimpleforthemtointeractwitheachotheronthewebpage.TheDOMisawayoforganizingobjectsinHTML,XHTML,andXMLdocuments,inwhichelementsarereferencedwithinatreestructure.
Adetaileddiscussioniswelloutsidethescopeofthisbook;sufficetosaythatifyouaregoingtouseJavaScriptwithShinyorHTML,youwillneedtolearnabouttheDOMandhowtogetandsetattributeswithinit.
ThesecondwayinwhichShinyandJavaScriptcaninteractiswhenit'seasierorbettertosendmessagesdirectlybetweentheserverandclient.Althoughintheory,youcouldusethe<inputtype="hidden">tagofHTMLandpassmessageson
theDOMwithoutshowingthemtotheuser,itwilloftenbeeasiertocutoutthemiddlemanandsendtheinformationdirectly,particularlywhenthemessageiscomplicated(alargeJSONobject,forinstance).
Wewilllookatsendingmessagesdirectlyafterthefirstexample.
First,asawarm-up,wewilllookatusingJavaScripttoreadtheDOMgeneratedbyShinyandperformsomeclient-sideprocessing,beforewritingthechangesbacktotheDOM.
Example1–readingandwritingtheDOMInthisexample,we'regoingtofindoutsomethingaboutthestateoftheapplicationfromtheDOM,grabitwithJavaScript,andwriteitbacktotheDOM.We'lllookatui.RtoputtogetherthepageandthenlookattheJavaScript.Theserver.Rfileisunchanged,sowewillnotdiscussithere.
ui.RWe'regoingtousetheGapminderapplicationwe'vebeenlookingatthroughoutthebook,butaddalittleJavaScriptmagic.We'regoingtoaddabuttonthat,whenclicked,writesthecurrentlyselectedyearsonthesummarytexttab.Let'stakealookatwhatwe'readdingtotheui.Rfile,overinthesidebarPanel()function:
tags$input(type="button",
id="append",
value="Addcurrentinputvalues",
onClick="buttonClick()"),
includeHTML("appendText.js")
Asyoucansee,weusethetags$xxx()functionthatwesawinChapter3,IntegratingShinywithHTML,inordertogenerateabuttonthatwillrunaJavaScriptactionwhenit'sclicked.ItgeneratesthefollowingHTML:
<inputtype="button"id="append"value="Addcurrentinputvalues"
onclick="buttonClick()">
Theotherfunction,whichwealsosawinChapter3,IntegratingShinywithHTML,istheincludeHTML()function.AsdescribedinChapter3,IntegratingShinywithHTML,thisfunctionallowsyoutoincludeHTMLfromafileratherthanclutteringupyourui.Rwithit.JustlikeHTML(),itpreventsShinyfromescapinganyHTMLwithinit,whichisthedefaultbehavior.Inthiscase,wearelinkingtoaJavaScriptfilecalledappendText.js.Now,wejustneedtoaddanelementtowhichtheJavaScriptcanwrite,inmainPanel():
tabPanel("Summary",textOutput("summary"),p(id="selection","Values"))
WenowhaveaparagraphelementwithanIDofselectiontowhichwecanwrite.server.Risunchanged,andsonowlet'slookattheJavaScript.
appendText.jsTheJavaScriptisverysimpleandlookslikethis:
<scripttype="text/javascript">
functionbuttonClick(){
varelem=document.getElementById('selection');
elem.innerHTML=document.getElementById('year').value;
}
</script>
YoucanseethatwegrabtheelementwiththeIDofselectionandwritethevalueoftheyeartoit.Youwill,Iamsure,wishtoproducesomethingalittlemoresophisticatedthanthis!Nowwehavethebasics,thesecondexampleisabitmorecomplex.Inthisexample,wewillbepassingmessagesdirectlybetweenserver(R)andclient(JavaScript).
Example2–sendingmessagesbetweenclientandserverInthisexample,wearegoingtousetheDOMandmessagestopassinformationbetweentheclientandserver.Theuserwillbeabletoselectanumberusingaslider.Theserverreadsthisinputandthenpicksarandomnumberbetween1andtheuser-suppliednumber.ThisnumberiswrittenbacktothescreenaswellasbeingsentinamessagetoJavaScript.
JavaScriptreceivesthisnumberandproducesadrop-downselectorthatallowstheusertoselectavaluebetween1andtherandomnumberthattheserverpicked.Everytimetheuserselectsadifferentvaluefromthisdropdown,JavaScriptwilldeciderandomlywhetheritthinksthatShinyrules!orwhether,infact,JavaScriptrules!.Thisissentasamessagetotheserver,whichpicksitupandwritesittothescreen.Clearly,thisisnotofmuchuseasarealapplication,butitshoulddemonstratetoyoutheprinciplesofsendingandreceivingmessagesandreadingandwritingtotheDOM.
Itisdefinitelyworthhavingalookattheapplicationlive.Aswithalltheapplications,itcanberunstraightfrommywebsite(chrisbeeley.net/website)wherethesourcecodecanalsobedownloaded.Hereistheapplicationinaction:
Asyoucanseeinthepreviousscreenshot,theuserhaspicked7,theserverhaspicked6outoftherangeofnumbersfrom1to7,JavaScripthasbuiltadrop-downmenuusingthatnumberofoptions,andhasalsodecidedinthiscasethatJavaScriptrules.DonotethatthisapplicationcouldquiteeasilybewritteninpureShiny,and,likemanyexamplesinthisbook,isprovidedforillustrationonly.Itisworthkeepingitsimple,soyoucaneasilyseehoweverythingfitstogetherwithoutworryingaboutunderstandingeverythingJavaScriptisdoing.
Inthiscase,theui.Randserver.Rfilesarebothprettysimpleandshouldbefairlyself-explanatory.MostofthecodeisintheJavaScriptfile.Let'squicklylookattheui.Randserver.Rfilesfirst.
ui.RThecoderunsasfollows:
fluidPage(
#flexiblelayoutfunction
h4(HTML("Thinkofanumber:</br>DoesShinyor</br>JavaScript
rule?")),
sidebarLayout(
sidebarPanel(
#sidebarconfiguration
sliderInput("pickNumber","Pickanumber",
min=1,max=10,value=5),
tags$div(id="output")#tags$XXforholdingdropdown
),
mainPanel(
includeHTML("dropdownDepend.js"),#includeJSfile
textOutput("randomNumber"),
hr(),
textOutput("theMessage")
)
)
)
Theuseofh4(HTML("XXX"))allowsustoshrinkthetitlealittleandaddsomeHTMLlinebreaks(avoidingHTMLescapingwiththeHTMLfunctionasbefore).tags$div(...)producesa<div>elementinwhichtoplacethedrop-downmenu,whichJavaScriptwillbuild.ThemainPanel()calljustcontainsareferencetotheJavaScriptfilethatwillrunonthepage,aplacetoputtherandomnumbertheserverwillpick,ahorizontalline,andamessagefromJavaScriptregardingwhetherJavaScriptorShinyrules.
server.RTheserver.Rfilerunsasfollows:
function(input,output,session){
output$randomNumber=renderText({
theNumber=sample(1:input$pickNumber,1)
session$sendCustomMessage(type='sendMessage',
message=theNumber)
return(theNumber)
})
output$theMessage=renderText({
return(input$JsMessage)
})
}
Thefirstthingtonotehereistheuseoffunction(input,output,session){...}insteadofthefunction(input,output){...}thatweareusedtoseeing.TheadditionofasessionargumentaddsaconsiderableamountoffunctionalitytoShinyapplications.Inthiscase,itallowsustosendmessagestoJavaScript.Thereismoreonthefunctionalityofthesessionargumentinthenextchapter,Chapter6,Dashboards.
Thefirstfunctionherecarriesouttwotasks.First,ittakesthenumberthattheuserselectedonthesliderandpicksarandomnumberbetween1andthatnumber.ItsendsthatnumberstraighttoJavaScriptusingthesession$sendCustomMessagefunction(whichthesessionargumentwementionedpreviouslyenables).ThesendCustomMessage()functionisdefinedwithinShiny;itisplacedaftersession$inordertotieittothesessiondefinedintheshinyServer(function(input,output,session){...})function.Finally,itreturnsthenumbertoShiny,justlikeinastandardapplication,readytobeplacedintheoutputslot,whichui.Rsetsup.
ThesecondfunctionreceivestheJavaScriptmessage.It'sveryeasytoaccess,theShinyfunctionwithinJavaScriptwritesittothestandardinput$xxxvariablename,whichweareusedtoseeingthroughoutthebook.Asisnowplain,alotoftheworkinthisapplicationisbeingdonewithintheJavaScriptfile.Let'stakealook.
dropdownDepend.jsThereisquitealotofcodeinthissectiondoingquitealotofdifferentthings,sowe'llstepthrougheachchunkinturn:
<scripttype="text/javascript">
//Shinyfunctiontoreceivemessages
Shiny.addCustomMessageHandler("sendMessage",
function(message){
//callthisbeforemodifyingtheDOM
Shiny.unbindAll();
Thefirstpartcarriesouttwofunctions;Shiny.addCustomMessageHandler("sendMessage",function(message){...})registersthemessage-handlerwithShiny.The"sendMessage"namewasdefinedintheserver.Rfunctioninthesession$sendCustomMessage(type='sendMessage',message=theNumber)call.Thisisthefirststepinreceivingandprocessingmessagesfromtheserver.
ThesecondpartbeginstheprocessofreadingandwritingtotheDOM.WheneveryouaregoingtomodifytheDOMinJavaScript,youshouldcallShiny.unbindAll()firstandthenShiny.bindAll()attheend;wewillcomeacrossthelatterfunctionlaterinthissection:
/*deletethedropdownifitalready
existswhichitwillthesecond
timethisfunctioniscalled*/
//getthedropdownandassigntoelement
varelement=document.getElementById('mySelect');
//ifitalreadyexistsdeleteit
if(element!==null){
element.parentNode.removeChild(element);
}
Inthissection,wechecktoseewhetherthedropdownhasalreadybeendrawn(whichitwillbethesecondtimethisfunctioniscalled),andifithas,wedeleteitinordertoredrawitwiththenewnumberofoptions:
//Createemptyarraytostoretheoptions
vartheNumbers=[];
//addascendingnumbersuptothe
//valueofthemessage
for(vari=1;i<=message;i++){
theNumbers.push(i);
}
//grabthedivreadytowritetoit
vartheDiv=document.getElementById("output");
Now,wecreateanarrayandfillitwiththenumbersfrom1tothevalueofmessage,whichistherandomnumberthattheserverpickedandpassedtothisfunction:
//createanewdropdown
varselectList=document.createElement("select");
//giveitanameandwriteittothediv
selectList.setAttribute("id","mySelect");
theDiv.appendChild(selectList);
//addtheoptions
for(varn=0;n<theNumbers.length;n++){
varoption=document.createElement("option");
option.setAttribute("value",theNumbers[n]);
option.text=theNumbers[n];
selectList.appendChild(option);
}
Next,wecreatethedrop-downlistandaddtheoptionstoitusingthearrayofnumberscreatedimmediatelybefore:
//addanonchangefunctiontocallshinyRules
//everytimethisinputchanges
selectList.onchange=shinyRules;
//addclasstostylenicelyinBootstrap
selectList.className+="form-control";
//callthiswhenyou'vefinishedmodifyingtheDOM
Shiny.bindAll();
}
);
Finally,weaddononchangepropertytothedropdownsothatitwillcalltheshinyRules()functioneverytimeitischanged,addtheform-controlclasstorenderthedropdownnicelyinBootstrap,andcalltheShiny.bindAll()functionnecessarywhenwehavefinishedwritingtheDOM:
shinyRules=function(){
//definetextarrayandpickrandomelement
vartextArray=['JavaScriptRules!','ShinyRules!'];
varrandomNumber=Math.floor(Math.random()*textArray.length);
//wheneverthisinputchangessendamessagetotheserver
Shiny.onInputChange("JsMessage",textArray[randomNumber]);
}
</script>
ThislastpieceofcodedefinestheshinyRules()function,which,asintheprecedingcode,willbecalledeachtimethedropdownischanged.Itsetsupatextarray,picksarandomelementfromit,andthenusestheShiny.onInputChange(...)functiontosendthiselementtotheserver.Asyoucansee,thefunctiontakestwoargumentsinthiscase:"JsMessage"andtextArray[randomNumber].
Thefirstoftheseargumentsgivesthemessageaname,soitcanbepickedupbytheserver.Rfile.Thisisthepartoftheserver.Rfilethatwesawbeforethatreadsinput$JsMessage,sotheinputisaccessedusingthestandardShinynotationofinput$xxxthatweareusedtoseeing.Ifyougobacktolookattheserver.Rfile,youcanseeacalltorenderText()thatreturnsinput$JsMessage,readytobewrittenstraighttotheoutputpaneloftheinterface.
ShinyjsWe'vealreadyseenthataslongasyouknowJavaScript,usingJavaScriptisprettyeasyinShiny.Theshinyjspackage,availableonCRAN,actuallymakesiteasytouseextrabitsofJavaScriptinyourapplicationbywritingpureRcode.Installitwithinstall.packages("shinyjs")andlet'stakealook.
ShinyjsgivesyouaccesstoquiteafewnicelittleJavaScripttricks,soseethedocumentationformore,butwe'regoingtohavealookatafewwiththehelpoftheGapminderapplication.Thefirstistheabilitytodisableandenablecontrols.Thiscanbeusefultohelpyourusersunderstandhowanapplicationworks.Forexample,inthegapminderapplication,theyearcontroldoesnotdoanythingwhenthemaptabisselected,sincethedatausedisalwaysthemostrecentdataanyway.Wecanhidethecontrol,aswesawinthepreviouschapter,Chapter4,MasteringShiny'sUIFunctions,butsometimeswemayprefertograyoutanddisablethecontrol.
Inordertouseshinyjs,weneedtocalllibrary(shinyjs)inboththeserver.Randui.Rfiles.WealsoneedtoadduseShinyjs()anywherewithinfluidPage()inui.R.Withthisdone,enablinganddisablingcontrolsisverysimpleusingtheenable()anddisable()functionsfromShinyjs.WehavenamedthetabPaneltheTabsandthemaptabmapandthereforewecantestwhetherinput$theTabsisequalto"map".Now,it'sverysimpletoturnthecontrolonoroff:
observe({
if(input$theTabs=="map"){
disable("year")
}else{
enable("year")
}
})
Anothersimpletrickischangingtheformattingoftextortables.WecandothisverysimplyusingthetoggleClass()function,whichaddsandtakesawayaCSSclassto/fromadiv.Allweneedtodoiswrapthetextoutputfromthegapminderui.Rindiv,andgiveitamemorableid("theText"):
div(id="theText",textOutput("summary"))
DefinetheCSSintheheadoftheHTMLusingtags$head():
fluidPage(
tags$head(
tags$style(HTML(".redText{
color:red;
}"
))
),
...)
Addabutton:
checkboxInput("redText","Redtext?")
Andnow,applytothenameddiv("theText")thenamedclass("redText")whenthenamedbutton(input$redText)ispressed:
observe({
toggleClass("theText","redText",input$redText)
})
Wecanalsoallowtheusertoresetsomeorallofthecontrols.Thiscanbeusefuliftheycannotrememberthedefaultsandtheywishtorestoredefaultvaluesforthecontrolswithoutrestartingtheapplication.Thisfunctionrequiresonlyadiv.Wewillplaceadivaroundtheyearslidertoallowtheusertorestoretheirdefaultseasily:
div(id="yearPanel",
sliderInput("year",
"Yearsincluded",
min=1952,
max=2007,
value=c(1952,2007),
sep=""
)
),
Now,wejustneedanactionbuttonfortheusertopress:
actionButton("reset","Resetyear")
Now,weuseobserveEvent()tolistenforthebuttonpush,andreset()toresetthevalueofthenameddiv:
observeEvent(input$reset,{
reset("yearPanel")
})
Thelastexamplewe'regoingtoshowusestheoneventfunction,whichwillrunanypieceofRcodeinresponsetoanevent.Itwillrespondtothefollowingevents:click,dblclick,hover,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,keydown,keypress,andkeyup.We'regoingtolistenforthehoverevent,whichreferstowhenthemousepointerisabovesomething.Inordertodoso,wemerelyspecifythetypeofevent,theIDofthecontrol,andthentheRfunctionwewishtorun:
onevent("hover","year",
html("controlList",
input$year,add=FALSE))
Thisfunctionlistensforahoverovertheyearcontrol,andthenexecutesthegivenfunction.ThefunctionlooksforanelementwithanIDof"controlList",andaddsthevalueoftheinputyear.Theadd=FALSEargumentisgivensothepreviousentryisoverwritteneachtime.WeaddtheelementtothetabPanelgraph:
tabPanel("Trend",value="graph",plotOutput("trend"),
p(id="controlList")),
Now,theapplicationhasalittlereadoutofthecurrentlyselectedyearsunderneaththegraph,whichupdatestothenewvaluewheneveryouhoverovertheyearcontrol:
Gapminder
ExtendshinyjsTheshinyjspackagecandomuchmorethanjustgiveyouaccesstocannedJavaScript,usefulthoughthatis.UsingtheextendShinyjs()function,youcanveryeasilyreadRinputandsendittoJavaScript.ItissimplertouseiftheV8packageisinstalled.Ifyoucannotinstallthispackage,thereisaworkaround;consultthedocumentation.
We'regoingtoimprovetheapplicationthatwejustproduced(itwillstillbejustasuseless,ofcourse!)byallowingtheusertochangethesizeandthecolorofthetext.Thefinishedapplicationlooksasfollows:
Finishedapplication
ui.RLet'sstartwiththethingstoaddtotheui.R.Onceagain,weaddaplaceforthetexttolivetothegraphtab:
tabPanel("Trend",plotOutput("trend"),
h3("Userselectionhistory"),
p(id="selection",""))
Then,weaddabuttontoaddthetextandsomecontrolstothesidebar:
actionButton("buttonClick","Addinputs"),
selectInput("color","Textcolour",
c("Red"="red",
"Blue"="blue",
"Black"="black")),
selectInput("size","Textsize",
c("Extremelysmall"="xx-small",
"Verysmall"="x-small",
"Small"="small",
"Medium"="medium",
"Large"="large",
"Extralarge"="x-large",
"Supersize"="xx-large"))
ThelastadditionisareferencetotheJavaScriptfilethatwillmakeeverythinghappen.WeaddthiswiththeextendShinyjs()function.PutitunderneathuseShinyjs(),soyoudon'tloseit:
useShinyjs(),
extendShinyjs(script="appendText.js")
Let'slooknowattheserver.Rcode.
server.RThecodehereisprettysimple:
observeEvent(input$buttonClick,{
js$buttonClick(input$color,input$size)
})
We'veseentheobserveEvent()functionbefore;itsimplylistensfortheinput$buttonClickactionbutton.Thejs()functionisdoingallthework;itsendsitsargumentstotheshinyjs.buttonClickfunctionwithintheJavaScriptfile(whichwewilllookatnext).Ingeneral,js$foosendsitsargumentstoshinyjs.foointhedefinedJavaScript.
JavaScriptLastly,let'slookattheJavaScript.Aspreviouslymentioned,shinyjs.buttonClickisafunctionthatcanaccesstheargumentsofjs$buttonClick().Withthatinmind,let'slookatthecode:
shinyjs.buttonClick=function(params){
//boilerplatecode
vardefaultParams={
color:"black",
size:"medium"
};
params=shinyjs.getParams(params,defaultParams);
//restofcode
varelem=document.getElementById('selection');
elem.innerHTML=document.getElementById('year').value;
elem.style.color=params.color;
elem.style.fontSize=params.size;
}
Notethatitisnotnecessarytoincludethe<scripttype="text/javascript">...</script>scripttagaroundtheJavaScriptinthisfile.Thefirstpartisboilerplatecode,andit'sdesignedtohandletheuseofnamedandunnamedlistswithintheRargumentsofthefunctioncall.BothareacceptedbyextendShinyjs(),sothiscodeensuresthattheyplaynicelyasJavaScript.Italsoallowsyoutoadddefaultvalues.YoucanseetheparametersfromRbeingreadintotheparamsvariablewiththeshinyjs.getParamsfunction.
Therestofthecodeisverysimple,merelygrabbingtheselectionelement,addingtexttoit,andchangingthecolorandsizeusingparams.colorandparams.size,bothbroughtthroughfromRandplacedasnamedelementswithinparams.Youwillrecalltheinput$colorandinput$sizevaluesbeingpassedasargumentswithinthejs$buttonClick()function.
Andthat'sitforshinyjs.Averysimple,powerfulwayofusingeithercannedJavaScriptorincorporatingyourownJavaScriptandreadingRinputveryeasily.
RespondingtoeventsinJavaScriptYoucanlistenforallkindsofeventsinShinyapplications,eitherfromthewindoworfromindividualelements,andrunJavaScriptcodewhentheyoccur.Forexample,youcanlistenforwhenShinymakestheinitialconnectionwiththeclient,andyoucanlistenforwhenShinyisbusyoridle.Youcanlistenforwhenaninputchanges,oranoutputrecalculates.Forafulllist,seethedocumentationatshiny.rstudio.com/articles/js-events.html.Asanexample,we'regoingtomakeaverysimpleprogramthattakesalongtimetodrawagraphandgivesyoualittlealertboxwhenitisfinished.Theprogramissosimplethatwewillusethesingle-fileapp.Rformat.Here,itisreproducedinitsentirety:
ui<-fluidPage(
titlePanel("JavaScriptEvents"),
sidebarLayout(
sidebarPanel(
actionButton("redraw","Redrawplot")
),
mainPanel(
plotOutput("testPlot"),
includeHTML("events.js")
)))
server<-function(input,output){
output$testPlot<-renderPlot({
input$redraw
Sys.sleep(5)
plot(1:10)
})
}
shinyApp(ui=ui,server=server)
Weconstructanactionbuttontorefreshthegraph,setupaverysimpleplot,andestablishadependencybetweentheplotandtheactionbuttonsoitrefresheswhentheactionbuttonispressed.TheJavaScriptfileisaddedusingtheincludeHTML()function,aswesawearlierinthechapter.
TheJavaScriptitselfisverysimple:
<scripttype="text/javascript">
$(document).on('shiny:idle',function(event){
alert('Finished!');
});
</script>
Youcanseethatthefunctionlistensforshiny:idle(whichistriggeredwhenShinyhasfinishedwhatitisdoing)anddisplaysanalerttotheuser.Ifwewished,wecouldjustlistenforthatspecificgraphbeingredrawn;thisisachievedsimplyasfollows:
$('#testPlot').on('shiny:recalculated',function(event){
alert("Finished");
});
YoucanaccessawholerangeofJavaScriptplottinglibrariesstraightfromR,andthereforefromShiny,usingthehtmlWidgetspackage.Let'shavealookatsomeofthethingsthatareavailable.
htmlwidgetsThehtmlwidgetspackageallowspackagedeveloperstoveryeasilyproducebindingsbetweenJavaScriptvisualizationlibrariesandR.IfyouwishtomakeuseofthehtmlwidgetspackagetoproduceabindingtoyourownfavoriteJavaScriptlibrary,itisarelativelysimpleprocess,thedetailsofwhichcanbefoundathtmlwidgets.org/develop_intro.html.Wewillnotlookattheprocessofproducingyourownbindingsbecausemanypopularlibrariesareavailable,andthereareplentyinthischapterthatdemonstratetheuseofexistinglibraries.Moreover,itrequirescompetencewithJavaScript,whichisnotassumedinthisbook.SufficetosaythatthehtmlwidgetspackagemakesiteasytouseJavaScriptvisualizationlibrariesfromR,includingRMarkdowndocumentsandShinyapplications.
We'vealreadyseenleafletinthisbook.Thispackagemakesuseofthehtmlwidgetspackage.Inthischapter,wewillhavealookatsomeotherusefulpackagesthatmakeuseofhtmlwidgets.Forthesakeofspace,wewilllookonlyatthefunctionalityandsomeexampleapplications,ratherthangoingthroughallofthecode.Theapplicationpicturedistheoneusedinthepreviouseditionofthisbookandallthecodeforthischaptercanbefoundatchrisbeeley.net/website/shinybookV2.html.
Wewilllookatthefollowing:
dygraphsrChartsd3heatmapthreejs
DygraphsThedygraphslibraryinJavaScript(http://dygraphs.com/)isdesignedtoshowtimeseriesandtrenddata.Itsupportszoom,pan,andmouseover,andevensupportsmobiledevicesbyofferingpinchtozoom.ThedygraphsRpackageprovidesahandyinterfacetomanyofthefunctionsofthedygraphslibrary.Itcanbeinstalledusinginstall.packages("dygraphs").
Formoreinformationaboutthedygraphspackage,seethefollowinglink,http://rstudio.github.io/dygraphs/.
Let'stakealookatanexamplegraph:
GoogleAnalytics
Thereareacoupleofthingsyouneedtomakeanoteofonthisgraph.First,youcanseethemouseovereffectatthetop-rightofthegraph,wherethedateandvaluesofNHSusersandOtherarelisted.Second,thisgraphhasbeensmoothed
usingarollingaverage.Thenumberofpointstobeaveragedisspecifiedinthewidgetontheleft-handsideofthepage(Selectrollperiod)andisgivenbydefaultinthesmallsquareboxatthebottom-leftofthegraph.Third,thegrayboxatthebottomwiththeselectoroneithersidecanbeusedtoselectdaterangesonthegraph.ThiscanbeusefulinaShinyapplicationasawayofkeepingthedaterangeofallthedataconstantsbutallowingtheusertozoominonthegraphastheychoose.Asyoucansee,thetextunderneaththegraphmakesthisdistinctionclear,reportingthenumberofdaysinthewholedataset(933)aswellasthenumberselectedonthegraphitself(578).Makingagraphuser-friendlyandinteractiveisextremelyeasyusingthedygraphspackage.
rChartsThesupportinrChartsfordifferentJavaScriptlibrariesisverybroad,andtherearemanypossibilitiesforproducingbeautiful,interactivegraphics.Here,wewilltakealookatjustoneandperusethedocumentationatramnathv.github.io/rCharts/toseethedifferentlibrariesandgraphssupportedbythepackage.
TherChartspackageisnotavailableonCRANbutcanbeinstalledveryeasilyusingthefollowingcode:
install.packages("devtools")require(devtools)install_github("ramnathv/rCharts")
Let'snowtakealookatoneofthemanyplotoutputspossiblewiththispackage:
Plotoutputs
Thisplotisaclusteredbarchartoftheselectedinput(Averagesession/Users/Sessions),showingthenumbersoneachdayandinfivedifferentcountries.ThankstothemagicofD3,theplotisinteractiveoutofthebox.Itsupportsmouseover,asshowninthescreenshot,withJapan'sresultsforWednesdayhighlighted.TheplotcanbechangedfromGroupedtoStacked(thatis,differentdaysgroupedhorizontallyorvertically),andthedaysoftheweekcanbehiddenandshownbyclickingonthematthetopinthelegendofthegraph.
d3heatmapThed3heatmappackageusesvanillaD3andproducesinteractiveheatmaps.Itcanbeinstalledusinginstall.packages("d3heatmap").
Formoreinformationaboutthed3heatmappackage,seethefollowinglink,http://htmlwidgets.org/showcase_d3heatmap.html.
Hereisanexample:
Heatmap
Mouseovergivestheindividualvalueswithintheheatmap.
threejsThethreejspackage,asdiscussedatthebeginningofthechapter,canbeusedtoproduce3Dscatterplotsorplotsonglobemappings(includingEarthandcelestialbodies).Itcanbeinstalledusinginstall.packages("threejs").Inordertoproducetheglobeoutputthatisshowninthescreenshot,itisnecessarytoalsoinstallmapsusinginstall.packages("maps").Anexampleisshownhere:
Globe
Thevisualizationiscompletelyinteractiveandcanbespunwithamousedrag.
SummaryInthischapter,weusedJavaScripttoreadandwritetheDOM,andsendmessagesbackandforthwiththeserver.Weexploredhowtogetthemostoutoftheshinyjspackage,aswellashowtolistenforeventswithJavaScript.Lastly,wetalkedaboutthehtmlwidgetspackage,andshowedsomeexamplesofsomeofthegraphicsyoucanproduceusinghtmlwidgets-enabledRpackages.
Inthenextchapter,wewilllookathowtobuilddashboardsinShiny.
DashboardsThischapterisallaboutlayingoutyourShinyapplications.InChapter4,MasteringShiny'sUIFunctions,wealreadylookedatdoingitbyhand,usingHTMLorCSS,andwealreadysawhowtolayoutapplicationsusingtheBootstrapgridsystem.Shiny(anditsassociatedpackages)includesloadsoffunctionsthatallowyoutolayoutyourapplicationsbeautifullyandsimply.Thischaptertakesthecodeandapplicationsyouhavealreadyseenandchangesthemfromtheveryplain,vanilla-lookinglayoutthatthedefaultstylingreturnstoslick,customizablelayouts,culminatinginafull-featureddashboard.
Inthischapter,wewilldothefollowing:
MakeadashboardveryeasilyusingtheflexdashboardtemplateAddiconstoapplicationsFurtherexploreShinyusingtheBootstrapgridsystemtolayoutapplicationsBuildadashboardtohelpyourusersaccessalltheinformationtheyneedfromwithinthesameintuitiveinterface
ApplicationsinthischapterInordertobetterunderstandthelayoutfunctionsinparticular,we'renotgoingtoaddanyfunctionalityinthischapter.We'llstartoffwiththevanillaGapminderapplicationwehavebeenusingthroughoutandwe'llapplydifferenttypesoflayouttoit,soyoucanseehowtheywork.Asweprogress,wewilladdoneortwoextrafeatures.However,wewillmainlyfocusonlookingatthesameapplicationbutwithdifferenttypesoflayoutfunctionsappliedtoit.
Itishighlyrecommendedthatyoudownloadandrunallthecodeinthischapter,soyoucangetabettersenseofhowtheapplicationswork,aswellasseeingtheserver.Rcodeineachcase,whichwon'tberepeatedforeachapplication.Ifyou'renotinfrontofacomputerwhilereadingthissection,hopefullythereareenoughscreenshotsandexplanatorymaterialtokeepyougoinguntilyoucanseetheapplicationsinactionforyourself.
FlexdashboardsFlexdashboardsareasimpleRMarkdowntemplateandmakelayingoutdashboards(withorwithoutinteractivityfromShiny)verysimple.Formoreinformationaboutflexdashboards,gotormarkdown.rstudio.com/flexdashboard/.CreatingaflexdashboardinRStudioisincrediblyeasy.First,installthepackagewithinstall.packages("flexdashboard").Then,justselectfromFile|NewFile|RMarkdown...|FromTemplate|FlexDashboard|OK:
AswithmostRStudiodocuments,itcomesprefilledwiththeboilerplatecodetomakethestructureofadashboard.Youcanseethestructurestraightawayifyoulike,byclickingKnitinRStudio(orbycallingrmarkdown::render("yourFileName.Rmd")).
Theboilerplatedashboardlookslikethis:
Let'smakeaverysimplestaticdashboardfirsttolearnmoreaboutit,andthenwe'lllookatsomemoreadvancedlayoutfeaturesandaddsomeinteractiveShinyelements.Hereisthefinisheddashboard:
I'veusedthedefaultlayoutthatisreturnedwhenyouselectaflexdashboardtemplate,whichisstructuredverysimplylikethis,bymodifyingtheheadings:
Column{data-width=650}
-----------------------------------------------------------------------
###Lifeexpectancyovertime
```{r}
*Code*
```
Column{data-width=350}
-----------------------------------------------------------------------
###Lifeexpectancy
```{r}
*Code*
```
###GDPpercapita
```{r}
*Code*
```
Asyoucansee,thedashboardislaidoutincolumns,withthewidthdefinedin{}bracketsateachcolumndefinition.Chunksaredefinedasyou'dexpectinanyRMarkdowndocument,with###asaheadingand```{r}...```wrappingthecode.Flexdashboardwillneatlyarrangetheoutputforyouincolumns.
Simple!Now,let'slookatdoingsomethingabitdifferentwiththelayout,and
we'lladdsomeShinyatthesametime.ToturnaflexdashboardintoaShinydashboard,justaddruntime:shinytotheoptionsatthetop(thispartofthedocumentiscalledtheYAMLheader).Whilewe'rehere,let'salsolayouttheapplicationwithrows,ratherthancolumns.Simplychangeorientation:columnstoorientation:rows.
So,nowyourheadershouldlooklikethefollowing:
---
title:"Shinygapminder"
runtime:shiny
output:
flexdashboard::flex_dashboard:
orientation:rows
vertical_layout:fill
---
Let'snowdefineacolumnwithasidebarandplaceourcontrolsinitasnormal,asfollows:
```{rsetup,include=FALSE}
library(flexdashboard)
library(leaflet)
```
Column{.sidebar}
-----------------------------------------------------------------------
```{r}
sliderInput("year",
"Yearsincluded",
min=1952,
max=2007,
value=c(1952,2007),
sep="",
step=5
)
checkboxInput("linear",label="Addtrendline?",value=FALSE)
```
Andnow,wecanaccessthosevaluesasnormalintheoutput.Oneoftheoutputvaluesisdefinedasnormal,exceptasarowratherthanacolumn:
Row
-----------------------------------------------------------------------
###Lifeexpectancyovertime
```{r}
renderPlot({
thePlot=mapData%>%
filter(year>=input$year[1],year<=input$year[2])%>%
group_by(continent,year)%>%
summarise(meanLife=mean(lifeExp))%>%
ggplot(aes(x=year,y=meanLife,group=continent,colour=continent))+
geom_line()
if(input$linear){
thePlot=thePlot+geom_smooth(method="lm")
}
print(thePlot)
})
```
Notethat,inthiscase,wecan'tdefineareactiveobjecttofiltertheyearvalues,aswedidbefore,sowefilterbydatewithineachoutputitem(whichisbadcodingpracticeinanormalShinyapplication,ofcourse).We'llputtheotheroutputitemsinatabset,asfollows:
Row{.tabset}
-----------------------------------------------------------------------
###Lifeexpectancy
```{r}
renderLeaflet({
mapData%>%
filter(year==input$year[2])%>%
leaflet()%>%
addTiles()%>%
setView(lng=0,lat=0,zoom=2)%>%
addCircles(lng=~lon,lat=~lat,weight=1,
radius=~lifeExp*5000,
popup=~paste(country,lifeExp))
})
```
###GDPpercapita
```{r}
renderLeaflet({
mapData%>%
filter(year==input$year[2])%>%
leaflet()%>%
addTiles()%>%
setView(lng=0,lat=0,zoom=2)%>%
addCircles(lng=~lon,lat=~lat,weight=1,
radius=~log(gdpPercap)*25000,
popup=~paste(country,gdpPercap))
})
```
Andthat'sit.Prettysimple.Thefinishedarticlelookslikethis:
It'saprettysmart-lookingdashboard,consideringhowsimpleitistocode.Therearelotsmoreoptionsforflexdashboards,includingtheuseofShinymodules(coveredinChapter8,CodePatternsinShinyApplications)aswellasdifferentmethodsoflayingoutflexdashboards.Visitthelinkgivenpreviouslyformoreinformation.Fortherestofthischapter,wewillbelookingatfullShinyapplications.
SidebarapplicationwithextrastylingAsawarmup,we'llsticktoasimplelayoutwithasidebarfornow.Asweprogressthroughthischapter,we'llchangethelayoutandmakeitmorelikeamoderndashboard.Becausewe'renotdoingtoomuchinthewayoffeaturesorlayoutinthisfirstapplication,we'lladdafewvisualbellsandwhistles.SomeofthemwillbedroppedinlaterversionsoftheapplicationjusttostopthecodeandUIfrombecomingtoocluttered,butyoucanofcoursewriteanapplicationyourselfwithalloftheUIelementsinifyouwishto.Allofthecodeanddatafortheapplicationsinthischapterisavailableatchrisbeeley.net/website/.
AddingiconstoyourUIAswegothroughthevariousUIelementslater,we'regoingtosprinkleafewiconsthroughout,justtogivethepageabitmorevisualinterest.Iconscancomefromtwoiconlibraries,locatedatfontawesome.io/icons/andgetbootstrap.com/components/#glyphicons.Theycanbeaddedsimplyusingtheicon()commandwiththenameoftherequiredicongivenasastring.
Forexample,icon("user")willbydefaultreturniconsfromtheFontAwesomelibrary,andtousetheglyphicons,simplyaddlib="glyphicon"asfollows:
icon=icon("user",lib="glyphicon")
TheycanbeaddeddirectlytoyourUIoronbuttons(includingthebuttonsatthetopoftabpanels).Fromthefullcodeofthisapplication,youcanseethatwehavereplacedtheboringhorizontalrule,whichseparatedourinputwidgets,withaspinningLinuxpenguin(because,woo!Linux!)usingclass="fa-spin".TheclassargumentcomesfromtheuseofCSSclassestovarythecharacteristicsofFontAwesomeicons.Theexamplesaregivenatfontawesome.github.io/Font-Awesome/examples/.YoucanalterthesizeofFontAwesomeiconswithclass="fa-lg"(onethirdlarger),class="fa-2x"(twiceaslarge),class="fa-3x",anduptoclass="fa-5x".Puttingthemtogether,wegetthefollowing:
icon("linux",class="fa-spinfa-3x")
Now,let'slookatstylingyourapplicationveryeasilyusingshinythemes.
UsingshinythemesLet'sgivethewholeapplicationalickofpaintusingtheshinythemespackage.Ifyouhaven'talreadydoneso,installitwithinstall.packages("shinythemes").Thedocumentation(includingalistoftheavailablethemes)canbefoundatrstudio.github.io/shinythemes/.LoadthepackageandpassathemeintofluidPage():
library(shinythemes)
fluidPage(
theme=shinytheme("darkly"),
...restofUI...
Ifyouwanttochooseyourthemeinteractively,insteadaddthemeSelector()toyourUIdefinition,andalittleinteractivechooserwillappearonyourapp.Onceyou'rehappywithit,usethepreviouscodeformattomakethatthedefaultchoiceinyourapp:
library(shinythemes)
fluidPage(
themeSelector(),
...restofUI...
Enjoyyournewthemeandconsultthedocumentationfortheotheravailablethemesandtheappearanceofeach.
Andhere'sthefinishedapplication,showingthespinningpenguinandtheuserandcalendaricons,aswellasthethemechooserandoneofthethemes:
UsingthegridlayoutInthenextversionoftheapplication,we'regoingtousethefluidRow()functiontoapplyacustomlayouttotheUI.ThisfunctionallowsyoutoimplementthestandardBootstrapgridlayout,aswesawinChapter4,MasteringShiny'sUIFunctions.
Thewidthofthescreenisgivenas12units,andyoucanpassthecolumn()functionsofarbitrarysizeintoafluidRow()instructiontodefineagroupofwidthsaddingupto12.Inthissimpleexample,wewillhavethreecolumnsinthefirstrowandthenoneinthesecondrow.Thefinishedapplicationlookslikethis:
ui.RLet'slookattheui.Rfilenecessarytoachievethis.Theserver.Rfileremainsthesameasinthepreviousexample.We'lltakebreaksaswestepthroughthecodetounderstandwhat'shappening.We'lldefinethetitlealittledifferentlyhere,justtomakethingsabitdifferent.Theactualtitleitselfgoesintheh2()HMTLhelperfunction,andweusestandardinlinemarkuptoselectthefont,color,andsize,asshown.ThetitleisgivenasanargumenttothefluidPage()function;thisgivesthebrowserwindowitstitle(it'sautomaticallysetbytitlePanel(),butwe'renotusingthathere):
fluidPage(
title="Gapminder",
h2("Gapminderexplorer",
style="font-family:'Impact';color:purple;font-size:32px;"),
Now,weaddinarowofUIelementsusingthefluidRow()functionanddefinetwoequalcolumnswithinthisrow.NoticetheuseofwellPanel()aroundsliderInput().Thisplacestheinputinsideapanelandgivesitabetter,moredefinedappearancenexttotheotherUIelements,aswellasfillingthehorizontalwidthofthecolumn:
fluidRow(
column(6,
wellPanel(
sliderInput("year",
"Yearsincluded",
min=1952,
max=2007,
value=c(1952,2007),
sep="",
step=5
))
),
column(6,
p("Mapdataisfromthemostrecentyearintheselectedrange",
style="text-align:center;"))
Next,weaddahorizontalruletobreakthescreenupabitandthetwooutputitems,boththemselvesplacedwithinfluidRow():
hr(),
fluidRow(
column(6,plotOutput("trend")),
column(6,leafletOutput("map"))
),
Andaddonemorefluidrow,toplaceacheckboxwhereyoucanchooseifyouwantatrendlineandthetextualsummary,bothplacedheresotheyareunderneaththerelevantgraph:
fluidRow(
column(6,
checkboxInput("linear",label="Addtrendline?",
value=FALSE),
textOutput("summary")
)
)
FulldashboardThiswillbethemostfull-featuredapplicationinthischapterandhasthelongestserver.Rfile.
Ideally,downloadtheapplicationcodeavailableatchrisbeeley.net/website/andrunitonyourmachine,orvisitahostedversion,alsoonthesite.Thereisquiteabitofnewfunctionalityinthisapplication,soit'sagoodideatoexploreitnow.
Ifyoucan'tdothis,therefollowsabriefoutlineofthenewfunctionalityintheapplication:
Notificationsinthetop-rightoftheinterfaceLargefriendlyicons(informationboxes)forkeyfigureswithicons(calendar,person,piechart,Shinyversion,andsoon)Agauge
Thefinisheddashboardlookslikethis:
We'llstartbylookingatthecodeforeachoftheseadditionsandthenmoveontolookathowthewholeUIisputtogetherusingtheshinydashboardpackage.
NotificationsTheabilitytocreatenotificationsispartofalargeramountoffunctionalitywithinshinydashboard,whichallowsyoutocreatemessages,tasks,andnotificationsintheheaderofyourdashboard.Formoredetails,visitrstudio.github.io/shinydashboard/structure.html.
Inthisexample,we'lljustaddnotifications.Thecodeisverysimilartotheothertwotypesofcontent.StaticnotificationscanbeproducedwiththenotificationItem()functionasfollows(withtheoptionalstatusandcolorargumentsnotusedhere):
notificationItem(text="3userstoday",icon("users"))
Inordertoproducecontentdynamically,weneedtodoabitmorework.Ontheserver.Rside,thecodeisasfollows.Itallowsthenotificationcontenttoberendereddynamicallyandcalledintheui.RfilewithdropdownMenuOutput("notifications"):
output$notifications<-renderMenu({
countries=length(unique(theData()$country))
continents=length(unique(theData()$continent))
notifData=data.frame("number"=c(countries,continents),
"text"=c("countries","continents"),
"icon"=c("flag","globe"))
notifs<-apply(notifData,1,function(row){
notificationItem(text=paste0(row[["number"]],row[["text"]]),
icon=icon(row[["icon"]]))
})
dropdownMenu(type="notifications",.list=notifs)
})
Thevaluesneedfirsttobeplacedwithinadataframe,asshown.Thedynamicelementisgivenbycountriesandcontinents,definedasthenumberofcountriesandcontinents,respectively.Textdescribingwhatthevaluerepresentsandaniconarealsoaddedtothedataframe.Thefinalnotifsvalueisproducedusingafunctionthat'sgivenasanexampleinthehelpfiles,asyoucanseeititerateovertherowsofthedataframeandchangethenotificationsintothecorrectvalue.Asyoucansee,thedataframethatweproducedisprocessedrow-wisewiththe
appropriatevaluesfortextandiconbeingproduced(text="142countries",icon="flag",andtext="5continents",icon="globe").Finally,theyaregiventodropdownMenu()astheargumentto.list.Noticethatwealsodefinethetypeastype="notifications".
Havingdoneallthis,theactualnotificationsarepassedintotheheaderintheui.Rfilequitesimply,likethis:
header<-dashboardHeader(title="Gapminder",
dropdownMenuOutput("notifications"))
Thislooksalittledifferenttotheusualstructurethatwehaveencountered,whichisbecause,unlikestandardShinyinterfaces,Shinydashboardsareconstructedfromthreeseparatesections:dashboardHeader(),dashboardSidebar(),anddashboardBody().WewilllookindetailattheconstructionofaShinydashboardinthesectiononui.Rlater.
InfoboxesWehavealreadyseenhowtouseiconsearlierinthischapter,butshinydashboardmakesanicefeatureofitbyexpandingandcoloringiconstodrawattentiontokeypiecesofinformation.Aninfoboxcanbedrawnstaticallyasfollows:
infoBox(width=3,"Shinyversion","1.1.0",
icon=icon("desktop"))
Asyoucansee,thewidthcanbeset(usingthe12spanrulefromthestandardBootstrapfunctionswesawearlierinthischapter)withatitle(Shinyversion)andvalue(1.1.0)(althoughyoumayoftenwishtopassanumber).ThisfunctionisplacedwithindashboardBody()intheui.Rfile.Formoreinformationontheargumentsofthisfunction,type?infoBoxintotheconsole.
Althoughyoumaysometimeswishtohardcodeinfoboxesinthisway(toshowversionnumbersofanapplication,asinthiscase),inthemajorityofcases,youaregoingtoproducethiscontentdynamically.Inthiscase,youwillasalwaysneedtodosomepreparationonserver.Rfirst.Hereisthecodeforthefirstinfobox:
output$infoYears<-renderInfoBox({
infoBox(
"Years",input$year[2]-input$year[1],
icon=icon("calendar",lib="font-awesome"),
color="blue",
fill=ifelse(input$year[2]<2007,
TRUE,FALSE)
)
})
Thefirsticonisthenumberofdayswithinthespecifiedrange.Thefirstargumentgivestheiconatitle,Years,andthesecondgivesitavalue(thenumberofyears,calculatedbysubtractingthefirstyearfromthesecond).Youcanalsoselectthecoloroftheboxandwhethertheright-handportion(whichcontainsthetext,asopposedtotheicon)isfilled(asolidcolor)ornot.
Asyoucansee,herewearedecidingthefillofthevalueportionoftheicondynamically.Whenthelargestyearintherangeislowerthan2007,theiconwillbefilled.Ifitisn't,itwon't.See?ifelseforhelpwithifelse().
Theotherdynamicinfoboxesaresetupinthesameway,asfollows:
output$infoLifeExp<-renderInfoBox({
infoLifeExp=theData()%>%
filter(year==2007)%>%
group_by(continent)%>%
filter(continent==input$continent)%>%
pull(lifeExp)%>%
mean()%>%
round(0)
infoBox(
"LifeExp.(2007)",infoLifeExp,
icon=icon("user"),
color="purple",
fill=ifelse(infoLifeExp>60,
TRUE,FALSE)
)
})
output$infoGdpPercap<-renderInfoBox({
infoGDP=theData()%>%
filter(year==2007)%>%
group_by(continent)%>%
filter(continent==input$continent)%>%
pull(gdpPercap)%>%
mean()%>%
round(0)
infoBox("GDPpercapita",
infoGDP,
icon=icon("usd"),
color="green",
fill=ifelse(infoGDP>5000,
TRUE,FALSE)
)
})
ui.RTheui.Rfiletodisplaydynamicinfoboxesissimilartothefunctionwealreadysawtodisplaystaticinfoboxes,exceptnowthefunctionisinfoBoxOutput().Puttingallfourinfoboxestogether,wenowgetthefollowing:
fluidRow(
infoBoxOutput(width=3,"infoYears"),
infoBoxOutput(width=3,"infoLifeExp"),
infoBoxOutput(width=3,"infoGdpPercap"),
infoBox(width=3,"Shinyversion","1.1.0",
icon=icon("desktop")))
Aselsewhere,ineachcaseinfoBoxOutput()isgivenastring(infoYears),whichreferstothenameofthecorrespondingoutputelement(output$infoYears).
GoogleChartsgaugeThegaugewithGDPpercapitaisfromtheexcellentGoogleChartsAPI.Moreinformationonthiscanbefoundatdevelopers.google.com/chart/.Fortunatelyforus,thereisanRpackagetointerfacewithGoogleCharts,sothereisnoneedtogetourhandsdirtywithadifferentAPI.ThepackageisonCRANandcanbeinstalledwithinstall.packages("googleVis").
Installandloaditnow.Thecodeisasfollows:
output$gauge=renderGvis({
infoGDP=theData()%>%
filter(year==2007)%>%
group_by(continent)%>%
filter(continent==input$continent)%>%
pull(gdpPercap)%>%
mean()%>%
round(0)
df=data.frame(Label="GDP",Value=infoGDP)
gvisGauge(df,
options=list(min=0,max=50000,
greenFrom=5000,greenTo=50000,
yellowFrom=5000,yellowTo=25000,
redFrom=0,redTo=5000))
})
Adataframeisproduced,withthefirstcolumnbeingthelabelforthegauge,andthesecondthevalueofthegauge.Ifyourequiremorethanonegauge,simplyincludemultiplerows.Inthiscase,wewilljustuseonerow.Thegaugeisdrawnverysimplybypassingthedataframeandalistofoptions,whicharefairlyself-explanatory,givingtheminimumandmaximumforthegauge,aswellasthelimitswherethegaugeisgreen,yellow,andred,ifdesired.Thegaugeisdrawnverysimplyinui.RusinghtmlOutput("gauge").
ResizingtheGooglechartSofar,sosimple.However,thereisaproblem!Googlevisualizationcharts,unlikenativeRvisualizations,arenotautomaticallyresizedwhenthebrowserwindowchanges.We'regoingtofixthisproblemverysimply,usingsomevaluesthatwecanextractfromthesessionargumentoffunction(input,output,session){...}.Shinymakeslotsofthingsavailableinthisvariable,andoneofthemostusefuloftheseissession$clientData.Thistellsyoulotsofthingsaboutyouruser'sbrowser,suchasthepixelratio,aswellastheheightandwidthofindividualoutputelementswithintheapplication.Formoreontheusesofthesessionargument,seeshiny.rstudio.com/reference/shiny/1.0.2/session.html.Inourcase,allweneedtodoisestablishadependencyonsomethingwithinthisclientdata,whichwillchangewheneverthebrowserwindowresizes.
Inthiscase,output_trend_widthisperfect.Thisisthewidthofthelineplotshowinglifeexpectancyovertime.Thesevariablesarenamed,forexample,output$clientData$output_nameOfOutput_width.Whenthebrowserisresized,thispropertywillchange.We'renotreallyworriedaboutheightbecausethereisn'tanythingtobumpagainstthegaugebelowit,onlytotheleftandright.Thecodetodrawthegaugethereforebecomesasfollows:
output$gauge<-renderGvis({
#dependenceonsizeofplotstodetectaresize
session$clientData$output_trend_width
[...asbefore...]
})
Changingthewidthofthebrowserwindowwillnowredrawthegauge,whichwillmakeittherightsizeagain.
ui.RHavingexaminedallthenewelements,wecanhavealookathowShinydashboardsareputtogether.Aswasbrieflymentionedpreviously,Shinydashboardsarecomposedofthreepieces;dashboardHeader(),dashboardSidebar(),anddashboardBody().Theycanbeputtogetherlikethis:
dashboardPage(
dashboardHeader([...]),
dashboardSidebar([...]),
dashboardBody([...])
)
Or,theycanbeputtogetherlikethis:
#producecomponents
header<-dashboardHeader([...])
sidebar<-dashboardSidebar([...])
body<-dashboardBody([...])
#assemble
dashboardPage(header,sidebar,body)
Personally,Ihaveastrongpreferenceforthelatterformat,sinceitseemstometomakethecodesimplerandeasiertoread(nottomentionwithlessindentation),butyoumayprefertheotherway.
Wealreadysawtheheaderpartoftheapplicationpreviously;thisisreproducedinthefollowingcodeforconvenience.Notealsothat,unlikeinmanyShinyapplications,itisnecessarytoloadseveralpackagesintheui.Rfilebecausetherearespecialfunctionswithinthosepackages,whichgetcalledwiththefile(forexample,dashboardHeader(),leafletOutput(),andothers).Thetopoftheui.Rfilethereforelooksasfollows:
library(shinydashboard)
library(leaflet)
header=dashboardHeader(title="Gapminder",
dropdownMenuOutput("notifications"))
Thesidebarcancontaininputwidgets,asistypicalinShinyapplications,butalsobuttonstoselectdifferenttabsofthedashboard,eachofwhichcanbesetuptohavedifferentoutputelementsonit.Inthiscase,wehavetwotabs:themainonecontainsthegraphsandiconswehavespentmostofthissectiondiscussing,
andthemaptabcontainingtheinteractiveleafletmap.
Thecodeisasfollows:
sidebar<-dashboardSidebar(
sidebarMenu(
menuItem("Dashboard",tabName="dashboard",
icon=icon("dashboard")),
menuItem("Map",icon=icon("globe"),tabName="map",
badgeLabel="beta",badgeColor="red"),
Thefirsttwoitemsaretabbuttonsthatwillallowustopresentdifferentsetsofoutputelementstousers.Eachisgivenatitle(DashboardandMap),aname(dashboardandmaps),andanicon(dashboardandglobe).Theseconditemcangiveusersextrainformationaboutthetab—inthisexample,showingthattheoutputelementsonthattabarestillinbeta,usingthebadgeLabelandbadgeColorarguments,givingaredbetainthiscase.
Therestofthesidebarsetupisfamiliarfrompreviousincarnationsofthisapplication,asfollows:
menuItem("Map",icon=icon("globe"),tabName="map",
badgeLabel="beta",badgeColor="red"),
sliderInput("year",
"Yearsincluded",
min=1952,
max=2007,
value=c(1952,2007),
sep="",
step=5
),
selectInput("continent","Selectcontinent",
choices=c("Africa","Americas","Asia",
"Europe","Oceania"))
I'veaddeda"continent"selector,whichtheinfoboxesandgaugerespondto,showingtheaveragelifeexpectancyandGDPpercapitafortheselectedcontinent.Finally,dashboardBodyissetupusingatabItems(tabItem(),tabItem())structure:
body=dashboardBody(
tabItems(
tabItem(tabName="dashboard",
fluidRow(
infoBoxOutput(width=3,"infoYears"),
infoBoxOutput(width=3,"infoLifeExp"),
infoBoxOutput(width=3,"infoGdpPercap"),
infoBox(width=3,"Shinyversion","1.1.0",
icon=icon("desktop"))),
fluidRow(
box(width=10,plotOutput("trend"),
checkboxInput("linear",
label="Addtrendline?",
value=FALSE)),
box(width=2,htmlOutput("gauge"))
)
),
tabItem(tabName="map",
box(width=12,leafletOutput("map"),
p("Mapdataisfromthemostrecentyearintheselectedrange"))
)
)
)
Eachtabitemwillbepassedintohere;inthiscase,wehavetwo,aswesawinthepreviouscode-dashboardandmap.Nowyoucanputanythingyoulikeinit.Inthiscase,wehaveafluidrowwithfourinfoboxesofwidthof3(thetotalwidthbeing12,ofcourse).Thefirstthreearethedynamicinfoboxesthatwesetupintheserver.Rfile,andthethirdisastaticversion.Thecodeforthestaticversion,too,isfeaturedearlierinthissection.Wefinishthetabitemwithanotherfluidrow.Notetheuseofthebox()functionthatdrawswhiteboxesaroundtheelementsofadashboard.
Wefinishwiththefinaltab,map,which,ascanbeseen,containsjustoneboxwiththemapinandacommentaboutthedatainthemap.
SummaryInthischapter,weexploredmanydifferentwaysoflayingoutthesameapplications.WelookedatbothstaticandShiny-basedinteractiveflexdashboards.Startingwiththestandardsidebarlayout,welookedataddingiconsandusingtheshinythemespackagetoquicklystyleavanillaapplication.Weexploredthefunctionalityoftheshinydashboardpackage,whichallowsyoutoproducetabbedoutputsections;addintasks,notifications,andmessagesforyourusers;showlargefriendlyiconswithkeyinformationon;andprovideanattractiveandprofessional-lookingdefaultappearance.
Thekeytomakingthemostofthematerialinthischapter,aswellasofShinygenerally,istorememberthatyoucancombinethetoolsthatShinygivesyouinalotofdifferentways,dependingonyourneedsandyourskillset.There'snothingtostopyoufromusingtheshinydashboardpackagewithsomehighlycustomizedHTMLinoneofthetabsifyouneedtoverypreciselybuildaparticularkindofinterface.Somedevelopers,whohavemoreexperiencewithJavaScriptthanwithR,mayprefertoworkwiththeGoogleChartsAPIinJavaScriptanduseShinyasjustadataworkhorse,servingdataorstatisticsstraighttotheJavaScriptfunction.Thinkaboutwhatyouneed,andtherewillusuallybeacoupleofwaystoachieveit.Thesolutionyoupickwillbebasedpartlyonproducinganapplicationthatissimple,clean,andeasytomaintainanddebug.
Inthenextchapter,wearegoingtolookatgettingthemostoutofShinybyusingcustomURLstrings,producinginteractiveplots,addingpasswords,downloadinganduploadingdata,andmore.
PowerShinyInthischapter,wearegoingtolearnmanypowerfulfeaturesofShiny.Wewillstartwithanimatingplots.Afterthat,wewilldiscusshowtoreadclientinformationandgetrequestsinShiny.Nowadays,tellingastoryaboutthedataininteractiveandreportingwaysisindemand.So,wewillgothroughgraphicsandreport-generation,andhowtodownloadthemusingknitr.Downloadinganduploadingisalsoaninterestingpartofanyapplication,whichwewillseeusingsomeexamples.Bookmarkingthestateoftheappisanadd-ontoregeneratetheoutputontheapp.Wewillseeademonstrationofthefastdevelopmentofappsusingwidgetsandgadgets.Attheendofthechapter,wewilllookathowtoauthenticatetheappusingapassword.
Inthischapter,wewillcoverthefollowingtopics:
AnimationReadingclientinformationandGETrequestsinShinyCustominterfacesfromGETstringsDownloadinggraphicsandreportsDownloadablereportswithknitrDownloadinganduploadingdataBookmarkingInteractiveplotsInteractingwithtablesLinkinginteractivewidgetsShinygadgetsAddingapassword
AnimationAnimationissurprisinglyeasy.ThesliderInput()function,whichprovidesanHTMLwidgetthatallowsustoselectanumberalongaline,hasanoptionalanimationfunctionthatwillincrementavariablebyasetamounteverytimeaspecifiedunitoftimeelapses.Thisallowsyoutoveryeasilyproduceagraphicthatisanimated.
Inthefollowingexample,wearegoingtolookatthemonthlygraphandplotalineartrendlinethroughthefirst20%ofthedata(0-20%ofthedata).Then,wearegoingtoincrementthepercentagevaluethatselectstheportionofthedataby5%andplotalinearthroughthatportionofdata(5-25%ofthedata).Then,incrementby10-30%andplotanotherline,andsoon.
Thesliderinputissetupasfollows,withanID,label,minimumvalue,maximumvalue,initialvalue,stepbetweenvalues,andtheanimationoptions,givingthedelayinmillisecondsandwhethertheanimationshouldloop:
sliderInput("animation","Trendovertime",
min=0,max=80,value=0,step=5,
animate=animationOptions(interval=1000,
loop=TRUE))
Havingsetthisup,theanimatedgraphcodeisprettysimple,lookingverymuchlikethemonthlygraphdataexceptwiththelinearsmoothbasedonasubsetofthedatainsteadofthewholedataset.Thegraphissetupasbeforeandthenasubsetofthedataisproducedonwhichthelinearsmoothcanbebased:
groupByDate<-group_by(passData(),YearMonth,networkDomain)%>%
summarise(meanSession=mean(sessionDuration,na.rm=TRUE),
users=sum(users),
newUsers=sum(newUsers),sessions=sum(sessions))
groupByDate$Date<-as.Date(paste0(groupByDate$YearMonth,"01"),
format="%Y%m%d")
smoothData<-groupByDate[groupByDate$Date%in%
quantile(groupByDate$Date,
input$animation/100,
type=1):quantile(groupByDate$Date,
(input$animation+20)/100,
type=1),]
Wewon'tgettoodistractedbythiscode,butessentially,itteststoseewhichofthewholedaterangefallsinarangedefinedbypercentagequantilesbasedonthesliderInput()values.Takealookat?quantileformoreinformation.
Finally,thelinearsmoothisdrawnwithanextradataargumenttotellggplot2tobasethelineonlyonthesmallersmoothDataobjectandnotthewholerange:
ggplot(groupByDate,aes_string(x="Date",
y=input$outputRequired,
group="networkDomain",
colour="networkDomain"))+geom_line()+
geom_smooth(data=smoothData,
method="lm",colour="black")
Notbadforafewlinesofcode.Wehavebothggplot2andShinytothankforhoweasythisis.
ReadingclientinformationandGETrequestsinShinyShinyincludessomeveryusefulfunctionalitythatallowsyoutoreadinformationfromaclient'swebbrowser,suchasinformationfromtheURL(includingGETsearchrequests)andthesizeofplotsinpixels.
Allyouneedtodo,asbefore,isrunshinyServer()withasessionargument.Thiscauses,amongotherthings,anobjecttobecreatedthatholdsinformationaboutaclient'ssession,namedsession$clientData.
Theexactcontentofthisobjectwilldependonwhatisopenonthescreen.Thefollowingobjectswillalwaysexist:
url_hostname#hostname,e.g.localhostorchrisbeeley.net
url_pathname=#path,e.g./or/shiny
url_port=#portnumber(8100forlocalhost,canoptionally
#changewhenhosting,seechapter5)
url_protocol=#highlylikelytobehttp:
url_search=#thetextafterthe"?"intheURL.Inthe
following
#examplethiswillread"?person=NHS&smooth=yes".
Differentoutputtypeswillyielddifferentinformation.Plotswillgivethefollowinginformation,amongotherreturnvalues:
output_myplot_height=#inpixels
output_myplot_width=#inpixels
Therearemanyapplicationstowhichthisinformationcanbeput,suchasgivingdifferentUIsordefaultsettingstousersfromdifferentdomains,orconfiguringgraphsandotheroutputbasedontheirsize(forexample,foruserswhoareusingmobiledevicesor32"monitors).We'regoingtolookatperhapsthemostobviousandpowerfuluseofclientdata:thesearchstring.
CustominterfacesfromGETstringsInthisexample,we'regoingtoproduceURLsthatallowShinytoconfigureitselfwhentheuserlandsonthepagetosavethemfromhavingtosetuptheirpreferenceseachtime.Wewillmakeuseoftwovariables:onespecifiesthatauserisonlyinterestedindatafromtheNHSnetworkdomainandtheotherspecifiesthattheuserwantsasmoothinglinepresentontheirtrendgraph.Userswhorequestasmoothinglinewillalsobetakenstraighttothetrendlinetab.
AswellastheworkwiththeGETquery,theonlyextrabitwewillneedhereisafunctiontochangetheselectedpanelfromtabsetPanel().Thisisdone,unsurprisingly,usingupdateTabsetPanel().
CateringtothesedifferentneedsisveryeasilydonebycreatingURLsthatencodethepreferencesandgivingthemtothedifferentusers.Tosimplifythecode,wewillpretendthat,iftheyarepassedatall,thecorrectnumberofsearchtermsisalwayspassedinthecorrectorder.ThisisareasonableassumptionifyouwritetheURLsyourself.Inareal-worldexample,theURLsaremostlikelygoingtobegeneratedprogrammaticallyfromaUI.Correctlyparsingthemisnottoochallenging,butitisnotreallythefocusofthediscussionhere.ThefollowingarethetwoURLswewillgiveout:
feedbacksite.nhs.uk/shiny?person=NHS&smooth=yesfeedbacksite.nhs.uk/shiny?person=other&smooth=no
Asinthepreviousexample,thecodeiswrappedinobserve(),andthefirstportionofthecodereturnsthesearchtermsfromtheURLasanamedlist:
observe({
searchString<-parseQueryString(session$clientData$url_search)
...
Havingdonethis,wecanthencheckthatsearchStringexists(incaseotheruserslandfromthedefaultURL)and,ifitdoes,wecanchangethesettingsaccordingly.TheupdateTabsetPanel()commandusesalotoftheconceptswealreadysawwhenwereadthetabthatwasselected.Thefunctiontakesasessionargument,aninputIdargument(thenameofthepanel),andaselectedargument
(thenameofthetab):
#updateinputsaccordingtoquerystring
if(length(searchString)>0){#ifthesearchStringexists
#dealwithfirstquerywhichindicatestheaudience
if(searchString[[1]]=="nhs"){#forNHSusersdothefollowing
updateCheckboxGroupInput(session,"domainShow",
choices=list("NHSusers"="nhs.uk",
"Other"="Other"),selected=c("nhs.uk"))
}
#dotheywantasmooth?
if(searchString[[2]]=="yes"){
updateTabsetPanel(session,"theTabs",selected="trend")
updateCheckboxInput(session,inputId="smooth",
value=TRUE)
}
}
})
Thisisclearlyaverypowerfulwaytomaketheexperiencebetterforyouruserscompletelytransparently.Youmaywishtospendabitoftimesettingupawebinterfaceinwhateverlanguageyoulike(PHP,JavaScript,andsoon)andcorrectlyparsingtheURLsthatyougeneratewithinShiny.Ifyouneedtohandlevaryinglengthsandnamesoflists,youwillneedafewextracommands:
names(theList):Givesyouthenameofeachreturnvaluelength(unlist(theList)):Tellsyouhowlongthelistis
DownloadinggraphicsandreportsTheoptiontodownloadgraphicsandreportscanbeaddedeasilyusingdownloadHandler().Essentially,downloadHandler()hastwoargumentsthatbothcontainfunctions:onetodefinethepathtowhichthedownloadshouldgo,andonethatdefineswhatistobedownloaded.
Thefirstthingweneedtodoistakeanyfunctionsthatareusedeitherinthedownloadgraphicrequestorthereportandmakethemreactivefunctions,whichcanbecalledfromanywhereratherthaninstructionstodrawagraphwithinacalltorenderPlot().Theeffectofthis,ofcourse,isthatweonlyhaveonefunctiontowriteandmaintainratherthanoneinsidethedownloadgraphicfunction,oneinsidethedownloadreportfunction,andsoon.Thisisachievedverysimply:
trendGraph<-reactive({
...restoffunctionthatwasinsiderenderPlot
})
Thegraphcannoweasilybeprintedwithinthetrendtab:
output$trend<-renderPlot({
trendGraph()
})
We'llgothroughthefollowingcodefromserver.Rstepbystep:
output$downloadData.trend<-downloadHandler(
filename<-function(){
paste("Trend_plot",Sys.Date(),".png",sep="")
},
Thisisthefilenamefunction,andasyoucansee,itproducesfilenameTrend_plot_XX_.pngwhereXXisthecurrentdate:
content<-function(file){
png(file,width=980,height=400,
units="px",pointsize=12,
bg="white",res=NA)
trend.plot<-trendGraph()
print(trend.plot)
dev.off()
},
Thisisthecontentfunction,andasyoucansee,itopensapngdevice(?png),calls
areactivefunctionnamedmyTrend(),whichdrawsthegraph,printstothedevice,andcloseswithacalltodev.off().YoucansetupthetrendGraph()functionsimply;inthiscase,itisjustlikethefunctionthatdrawsthegraphitself,exceptinsteadofbeingwrappedinrenderPlot()toindicatethatitisaShinyoutputitisjustdefinedasareactivefunction.
Finally,thefollowingisgiventotellShinywhattypeoffiletoexpect:
contentType='image/png')
Addingthedownloadbuttontotheui.Rfileissimple;thedownloadButton()functiontakesthenameofthedownloadhandlerasdefinedinserver.Randalabelforthebutton:
tabPanel("Trend",plotOutput("trend"),
downloadButton("downloadData.trend","Downloadgraphic")
Asyoucansee,Ihaveaddedthebuttonunderneaththegraph,sousersknowwhattheyaredownloading.
DownloadablereportswithknitrThissamefunctioncanveryeasilyallowyouruserstoproducecustomreportsinHTML,pdf,orMSWordreadytobedownloadedtotheirmachines,usingtheknitrpackage(http://yihui.name/knitr/).knitrisauser-contributedpackagethatallowsreportstobegenerateddynamicallyfromamixtureofastaticreportformatsinterleavedwiththeoutputfromofR.
So,forexample,titlesandtextcanbefixed,buteachtimethereportisrun,adifferentoutputwillbeproducedwithinthedocumentdependingonthestateofthedatawhentheoutputisgenerated.knitrcanbeusedwiththeRMarkdownformat.HereisthesimpleRMarkdowndocumentwithintheGoogleAnalyticsapplication:
#Summaryreport
##Textsummary
Thisreportsummarisesdatabetween`rstrftime(input$dateRange[1],
format="%d%B%Y")`and`rstrftime(input$dateRange[2],
format="%d%B%Y")`.
##Trendgraph
```{rfig.width=7,fig.height=6,echo=FALSE}
trendGraph()
```
Ascanbeseen,thedocumentisamixofstaticheadingsandtext,inlineRoutput(givenas`r"print("somthing")`),andgraphicaloutput.ThetrendGraph()function,ofcourse,isthesametrendGraph()functionthatwesawinthedownloadgraphicscode.
Thecodetodownloadthereportisasfollows(withtheRMarkdowndocumentinthesamefolderasserver.RandnamedReport.Rmd):
output$downloadDoc<-
downloadHandler(filename="Report.html",
content=function(file){
knit2html("Report.Rmd",envir=environment())
#copydocumentto'file'
file.copy("Report.html",file,
overwrite=TRUE)
}
)
Addingabuttontodownloadthegraphisthesameasforthedownloadinggraphfunction;thefollowingshouldbeplacedinui.RwithinthesidebarPanel()function:
downloadButton("downloadDoc","Downloadreport")
DownloadinganduploadingdataDownloadingdataisdoneinasimilarfashion,whichlookslikethefollowingdownloadHandler()call:
output$downloadData<-downloadHandler(
filename=function(){
"myData.csv"
}
content=function(file){
write.csv(passData(),file)
}
)
UploadingdataisachievedusingthefileInput()function.Inthefollowingexample,wewillassumethattheuserwishestouploadacomma-separatedspreadsheet(.csv)file.Thebuttonisaddedtoui.Rinthefollowingmanner:
fileInput("uploadFile","UploadyourownCSVfile")
Thisbuttonallowsausertoselecttheirown.csvfile,anditalsomakesavarietyofobjectsbasedontheID(inthiscase,input$uploadFile$)availablefromserver.R.Themostusefulisinput$uploadFile$datapath,whichisapathtothefileitselfandcanbeturnedintoadataframeusingread.csv():
userData<-read.csv(input$uploadFile$datapath)
Thereareotherbitsofinformationaboutthefileavailable.Takealookat?fileInputformoredetails.
BookmarkingInternetusersarefamiliarwiththetermbookmarking.Ifwearesurfingasiteandthinkit'ssomethingimportantthatwe'dliketovisitagain,weusuallybookmarkit.HowaboutthebookmarkingaShinyapp?AstheShinyapphasdatarepresentationandinput/outputinteractions,savingthisstateusingbookmarkingandthenreproducingitwillrequiresomecodingexpertise.
Inthissection,wearegoingtofocusonhowtobookmarkthestateofaShinyapp.WewillseecodingmodificationsinthebasicstructureoftheShinyappneededtoincorporatethebookmarkingfeature.Wewillexplorebookmarkingastate,andsomeexceptionalcasesandhowtodealwiththem.
BookmarkingstateThestateofanapplicationcanbedefinedasthestateofinputandoutputatagiventime.InShinyapps,thestateoftheinputandoutputcanbetheinputintheinputboxandtheoutputintheoutputboxoragraphataparticulartime.Here,ouraimistosaveastateatagiventimeandrestoreit.AsofnowwehavelearnedaboutthestructureofShinyapps.Shinyappscanbeasinglefile(UIandServerfiletogether)ormultiplefiles(UI,Severorglobalfilesseparately).Andtherecanbeappswheretheflowofinputandoutputcanbestraightforwardornotstraightforward.Astraightforwardcontrolflowofanappcanbeconsideredastheappthathasadeterministicoutputforaninputandvice-versa.Inthissection,wewilllearntoprogramthebookmarkingfeaturesforthestateofappswithastraightforwardcontrolflow.
Themethodsofbookmarkingcanbeclassifiedintotwocategories:
BookmarkingbyEncodingthestateintoaURLBookmarkingbysavingthestatetotheserver
Inboththemethods,thebasicconceptistopreservethestateoftheapplicationataparticulartime.Thedifferenceliesinhowtosavethestate.Let'sdiscussboththemethodsindetail.
EncodingthestateintoaURLInthismethodofbookmarkingthestate,thestateoftheappisembeddedintoaURL.Aswehavediscussed,thestateoftheapplicationmeanstheinputandoutputvaluesataparticulartime.So,theinputandoutputvaluesareembeddedintheappforatimeinstance.TheinputandoutputvaluescanbeseenintheURLitself.AswearefamiliarwiththecodingofShinyapps,thedifferenceswithbookmarkingarethattheUIhastobereturnedasavalueofafunctionwithasingleargumentandenableBookmarking()hastobecalled.WhenwediscussedthedifferentstylesofcodingaShinyappinsinglefileormultiplefiles,welearnedweneedtounderstandhowtoimplementtherequirements-implementingUIwithasingleargumentfunctionandusingenableBookmarking().
Single-fileapplicationSingle-fileappshaveUIandservercodeinthesamefile.Forsuchacodingstyle,theappskeletoncanbegivenasfollows:
ui<-function(request){#UIcode}
server<-function(input,output,session){#ServerCode}
shinyApp(ui,server,enableBookmarking="url")
WecanseethattheUIcodeisreturnedasafunctionratherthanfluidpageoranyotherUIelement.Andinthelastlineofcode,theenableBookmarkingargumentissetto"url".TherestofthecodeissimilartoShinyapps.Let'sdevelopanappwiththeprecedingskeletonthatfindsthesquareofanumber.StartingwithUI,thereisatextInput()boxforgettingtheinput,verbatimTextOutput()forshowingoutput,andbookmarkButton()forbookmarking:
ui<-function(request){
fluidPage(
textInput("inptxt","EnterNumber"),
verbatimTextOutput("outsquare"),
bookmarkButton())
}
Intheservercode,wehavetocalculatethesquareoftheinputtednumberandsenditfordisplay.WecanseeinthefollowingcodethatrenderText()isfirstconvertingtheinputtedtextintoanumberandthencalculatingthesquare:
server<-function(input,output,session){
output$outsquare<-renderText({
(as.numeric(input$inptxt)*as.numeric(input$inptxt))
})}
Inthelast,wehavetocallshinyApp()inwhichwearepassingtheenableBookmarkingargumentas"url":
shinyApp(ui,server,enableBookmarking="url")
Let'shavealookatoutputofthecode:
Wecanseethatintheapp,theinputis2andtheoutputvalueis4.ThebookmarkwindowshowsthelinkedURLwithsomevaluesthatareencodingtheinput/output,http://127.0.0.1:4255/?_inputs_&inptxt=%222%22.
SinglefileShinyapplicationscanalsobereturnedbyafunction.Let'srecreatethesquareapplicationusingfunction:
App<-function(){
ui<-function(request){
fluidPage(
textInput("inptxt","EnterNumber"),
verbatimTextOutput("outsquare"),
bookmarkButton()
)
}
server<-function(input,output,session){
output$outsquare<-renderText({
(as.numeric(input$inptxt)*as.numeric(input$inptxt))
})
}
shinyApp(ui,server,enableBookmarking="url")
}
Toruntheapp,copythecodeintoanRScriptfile,selectallcode,andrunit.Afterthat,wecanexecutethecodeusingApp()inconsoleorRScript.enableBookmark()canalsobesetatthebeginningoftheappcode.
Now,wearereadytodevelopourapplicationformultiplefiles.
Multiple-fileapplicationInShinyapplicationswhereUIandservercodesarekeptinseparatefiles,enableBookmarking()hastobekeptintheglobal.rfile.Therestofthethingsarethesame.Forredevelopingthesquared-numberappinamultiple-filepattern,wehavetocopytheUIcodeandpasteitintotheui.rfileandservercodeintheserver.rfilewithenableBookmarking(store="url")intheglobal.rfile.Wewillgetthesameoutputaswegotinthesingle-fileimplementation.
BookmarkingbysavingthestatetotheserverInthismethod,theonlymodificationintermsofcodingistochangetheenableBookmarkingargumenttotheserver.Let'srecreatethesquare-calculationapplicationusingsavingstatetotheserver:
ui<-function(request){
fluidPage(
textInput("inptxt","EnterNumber"),
verbatimTextOutput("outsquare"),
bookmarkButton()
)
}
server<-function(input,output,session){
output$outsquare<-renderText({
(as.numeric(input$inptxt)*as.numeric(input$inptxt))
})
}
shinyApp(ui,server,enableBookmarking="server")#savingtotheserver
Howdoesitmakeadifferenceinprocessing?SavingstatetotheservermethodsavesthestateoftheapplicationontheserverinsteadofembeddingthestatesintoaURL.Shinyprovidestwoversionsofservers.OneistheopensourceShinyserver,andtheotherisShinyserverpro.InShinyserver,thestateoftheapplicationissavedinthe/var/lib/shiny-server/bookmarkssubdirectory.RStudioConnectalsostoresthebookmarkstatein/var/lib/rstudio_connect/bookmarks.WeusuallyexperimentwithourapplicationsondesktopversionsofRstudio.Insuchcases,thestateissavedintheshiny_bookmarks/subdirectory.
Theadvantageofusingsavingstateintheserveristhatfilescanalsobebookmarked,whichisnotpossiblewithbookmarkingwithencoding.LargefilesarenotsupportedbybookmarkingwithaURL.Tosavestateontheserver,thehostingenvironmentmustbesupported.
Asofnowwehavediscussedbookmarkingstatefortheappshavingstraightforwardflow.Butifappsdon'thavestraightforwardflows,weneedtotakesomeextrameasurestodealwithsuchsituations.Inasimilarway,ifappshaverandom-numbergenerationandusethattogenerateoutput,thenthesemethodologieswon'twork.
Forsuchapplicationswithcomplexstate,useofcallbackfunctionsisneeded.ShinyhasprovidedonBookmark(),andonRestore()toachievecallbackfunctions.Thesecallbackfunctionstakeoneargument,namedstate.Wecanusestate$valuetosaveandretrievevaluestobebookmarked.
Let'sdevelopanapplicationthatgeneratesarandomnumberandaddsittotheinputtednumber.HereistheUIcodeforgettingtheinputnumberusingtextInput().IthasoneactionButton()fortriggeringtheeventoftheaddition,andbookmarkButton()forbookmarking:
ui<-function(request){
fluidPage(
sidebarPanel(
textInput("txt","Number"),
actionButton("add","Add"),
bookmarkButton()
),
mainPanel(
h4("SumofRandomNumberandInputedNumber:",textOutput("result"))
)
)
}
Intheservercode,wecanseethattheresultvariableisinitiatedtozeroandarandom-numbergenerator,runif(),hasbeenused.onBookmark()isusedtosavetheresultvalueinthestate$valuesobject.AndonRestore()isusingthesamestateobjecttorestorethebookmarkedvalue:
server<-function(input,output,session){
vals<-reactiveValues(result=0)
#Savevaluesinstate$valuesforbookmark
onBookmark(function(state){
state$values$currentresult<-vals$result
})
#Readvaluesfromstate$valueswhenwerestore
onRestore(function(state){
vals$result<-state$values$currentresult
})
#Excludetheaddbuttonfrombookmarking
setBookmarkExclude("add")
observeEvent(input$add,{
vals$result<-vals$result+floor(runif(1))+as.numeric(input$txt)
})
output$result<-renderText({
vals$result
})
}
shinyApp(ui,server,enableBookmarking="url")
So,wehavelearnedhowtobookmarktheShinyappstateinasimple,straightforwardflowaswellasincomplexstates.WealsolearnedinwhichconditionsencodingwithaURListobeusedandwhenasaveontheservercanbeadvantageous.
InteractiveplotsToexplorecomplexpatternsindata,simplyputtingdataintoastaticgraphisn'tparticularlyuseful.Inthebigdataera,interactiveplotsdemandalotoftime.Rhascomeupwithveryelegantinteractiveoptions:
clickdouble-clickhoverbrushzoomin/outselectionofarea
plotOutput(),likefunction,providesoptionstoenablethesepropertiestomakeplotsmoreinteractive.Let'sexploreplotOutput()anduseitinourapp.HereisthesyntaxforplotOutput():
plotOutput(outputID,width,height,click=NULL,dbclick=NULL,hover=NULL,hoverDelay=NULL,hoverDelayType=NULL,brush=NULL,clickId=NULL,inline=FALSE)
Let'sdevelopanapplicationthatexplorestheirisdatasetandusessomeinteractivefeaturesofplotting:
library(shiny)
shinyApp(
#---------------Uistart--------------------------------
ui=basicPage(fluidRow(
column(width=4,
plotOutput("plot",height=300,
click="plot_click",
hover=hoverOpts(id="plot_hover",delayType="throttle"),
brush=brushOpts(id="plot_brush")
),
h4("Clickedpoints"),
tableOutput("plot_clickedpoints")
),
column(width=4,verbatimTextOutput("plot_hoverinfo")
)
)),
server=function(input,output,session){
output$plot<-renderPlot({
plot(iris$Sepal.Length,iris$Sepal.Width)
})
output$plot_clickedpoints<-renderTable({
#Forbasegraphics,weneedtospecifycolumns,thoughforggplot2,
#it'susuallynotnecessary.
res<-nearPoints(iris,input$plot_click,"Sepal.Length","Sepal.Width")
if(nrow(res)==0)
return()
res
})
output$plot_hoverinfo<-renderPrint({
cat("Hover(throttled):\n")
str(input$plot_hover)
})
}
)
Inthiscode,wecanseethatplotOutput()hastheclickandhoverproperties.Theclickpropertyspecifiesthatitwilldetectaclickeventontheplot.hoverwilldetectthepointer'sposition.
Inthefollowingoutput,thepositionofpointeriscapturedanddetailsaredisplayedinthehoverwindowanddataforthepointisinthetable:
ggplotandplotly,likeRlibraries,arefullofverygoodinteractiveplottingfunctions.ThesefunctionsuseD3.jstoproduceinteractiveplotsandgraphs.Aswehavealreadydiscussedggplot2,wewillnotelaborateonithere.
plotlyisalsoveryeasytouse.Ifplotlyisnotinstalledonyoursystem,youcaninstallitusinginstall.package("plotly").Let'slookatanexamplewithplotly:
library(plotly)
plot_ly(iris,x=iris$Sepal.Length,y=iris$Sepal.Width,text=paste("iris$Sepal.Width:",iris$Sepal.Width),
mode="markers",color=iris$Sepal.Width,size=iris$Sepal.Width)
Inthefirstlineofcode,theplotlylibraryisinvokedwhichfacilitatestheuseofplot_ly().Inplot_ly(),thefirstargumentisthenameofthedataset,thesecondisthexaxis,andthethirdistheyaxis.Theoutputcanbeseeninthefollowingscreenshot;bydefault,itsupportshoverandclickevents:
InteractivetablesIntheprecedingexampleapplication,wesawhowtomakeourplotinteractive.Nowit'stimetomaketablesinteractive.Interactiveplotsmeanwecanhavefreedominselectingrows/columns/cellsandshortingcolumns.
DTisapackagewithDTlibrarythathelpsustomaketablesmoreinteractive.Let'sseeasmallclient-sideexampleofDataTableswithShiny:
library(shiny)
library(DT)
shinyApp(
ui=fluidPage(DTOutput('tbl')),
server=function(input,output){
output$tbl=renderDT(
iris,options=list(lengthChange=FALSE)
)
})
Intheprecedingcode,DTOutput()isforoutputtingthetableonUI,andontheserversidewehaveusedrenderDT()tosendthedataintotheUIfromtheirisdataset.Theoutputcanbeseenhere:
Wecanseeintheprecedingscreenshotthatwecanselectrowsusingamousepointer.Inthisway,interactivefunctionalitycanbeaddedtotables.
DTsupportsbothserver-sideandclient-sideprocessing;thedefaultisserver-side,butclient-sidecanbesetbycallingDT::renderDT()withaserver=FALSEargument.Ifthedatasetisrelativelysmall,useserver=FALSE,otherwiseitwillbetooslowtorenderthetableinthewebbrowser,andthetablewillnotbeveryresponsive,foralargedataset.Examplecode:
DT::renderDataTable(iris,server=FALSE)
Formoredetailshttps://rstudio.github.io/DT/linkcanbefollowed.
RowselectionRowselectionisthedefaultindatatable(...,).Wecantoggletheselectionbyclickingontherow.Itcanalsobedisabledbysettingthedatatable(...,selection='none')property.The'selection'modecanalsobesetto'single'or'multiple'.Bydefault,themultipleselectionmodeisset.
ColumnselectionTochangethedefaultmoderowselectiontocolumn,weneedtosetthetargetascolumnindatatableasdatatable(...,selection=list(target='column')).Toselectrowandcolumnsimultaneously,thetargetcanbesetas"row+column".
CellSelectionToenablecellselection,thevalueoftargetmustbesettocell.
LinkinginteractivewidgetsShinyhasanumberofwidgetsthatcanbelinkedinourapplications.Forreferringtothewidgetgallerygotohttps://shiny.rstudio.com/gallery/widget-gallery.html.Here,youcanfindwidgetswithsampleoutputandaseecodebutton.Havealookatasnapshotofthesite:
Forbetterunderstanding,wewillbuildanapplicationwithaShinydashboardandintegratewidgetsintoourapp.Let'sdevelopourappstepbystep:
1. Developabasicdashboard.2. CopythecodeintoanRScriptfile.3. Iftheshinydashboardpackageisnotinstalled,installit.4. Executethecodebyclickingontherunappbutton:
##app.R##
library(shiny)
library(shinydashboard)
ui<-dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody()
)
server<-function(input,output){}
shinyApp(ui,server)
Hereistheoutput:
Now,let'sintegratethewidgetintoourbasicdashboard:
1. Gotohttps://shiny.rstudio.com/gallery/widget-gallery.html.2. Let'sintegratetheFileinputwidgetintoourapp.SoclickonFileinputto
seethecode.3. CopythecodefromtheUIfileandpasteitintothedashboardbody()dashboard
UIfiles:
dashboardBody(
fileInput("file",label=h3("Fileinput")),
hr(),
fluidRow(column(4,
verbatimTextOutput("value")))
)
4. CopythecodefortheserverfileoftheFileinputwidgetandpasteintheservercodeofourdashboard:
server<-function(input,output){
output$value<-renderPrint({
str(input$file)
})
}
5. Ourfile-uploadmanagerappisready.Thecompletecodecanbeseenhere:
##app.R##
library(shiny)
library(shinydashboard)
ui<-dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(fileInput("file",label=h3("Fileinput")),
hr(),
fluidRow(column(4,verbatimTextOutput("value"))))
)
server<-function(input,output){
output$value<-renderPrint({
str(input$file)
})
}
shinyApp(ui,server)
Hereistheoutput:
Fromtheprecedingscreenshot,wecanseethattheFileinputwidgethasbeenintegratedintoourapplication,andafterclickingontheBrowse...button,anyfilecanbeuploaded.
ShinygadgetsSofar,wehaveseenhowtopresentdatawithShinyappsfortheenduser.Nowit'stimetodevelopgadgetsforRcoders.ThemaindifferencebetweenShinyappsandShinygadgetsisthatappsaredeployedonservers,suchasshinyapps.iooradeployableserver,butgadgetsaretobecalledfromthecodefromRScript.Atthesametime,ShinygadgetscanberegisteredwithRStudioasadd-ins.
Shinygadgetshavebeendevelopedtomakerepeatabletaskseasilydoable,suchasimportingdataintherightformats,cleaningdata,manipulating,orvisualization.Nowlet'sseehowtowriteShinygadgets:
library(shiny)
library(miniUI)
newGadget<-function(inputVal1,inputVal2){
ui<-miniPage(
gadgetTitleBar("NewGadget"),#titleofGadget
miniContentPanel(
#layout,inputs,outputs
)
)
server<-function(input,output,session){
#Definereactiveexpressions,outputs,etc.
#WhentheDonebuttonisclicked,returnavalue
observeEvent(input$done,{
returnValue<-...
stopApp(returnValue)
})
}
runGadget(ui,server)
}
Fromthegadgetsskeleton,wecanseethatitissimilartoShinyapps.ThepackagingoftheUIandserverlogicisdonedifferentlyingadgets.Shinyappsaregenerallykeptintheapp'sdirectorywiththeserverandUIfiles,butShinygadgetsaredefinedinsidearegularfunction.
Aswecanseefromtheskeleton,westillhavetheUIandserverconstruct,butthey'reallwrappedinaregularfunction.Andhencethefunction'sinputValue1andinputValue2argumentscanbeaccessedlocally,andreturnvaluesfromtheserver
constructcanbethereturnvalueofthegadget.
Againinskeleton,UIhasminiPageinsteadoffluidpage,andminiContentPanelinplaceofsidelayout.Thisisbecausethegadget'soutputwillopeninaseparateRStudioViewerpaneinsteadofawebpage.TheMiniUIconstructisusedtomakeoptimaluseofavailablespace.
Let'sdevelopaShinygadgettoplotthedatapassedasanargument,detecttheclickevent,andshowthedata:
library(shiny)
library(miniUI)
library(ggplot2)
click_gadget<-function(data,xvar,yvar){
ui<-miniPage(
gadgetTitleBar("Dragtoselectpoints"),
miniContentPanel(
plotOutput("plot",height="100%",click="plot_click")
)
)
server<-function(input,output,session){
#Rendertheplot
output$plot<-renderPlot({
#Plotthedatawithx/yvarsindicatedbythecaller.
ggplot(data,aes_string(xvar,yvar))+geom_point()
})
#HandletheDonebuttonbeingpressed.
observeEvent(input$done,{
#Returnthebrushedpoints.See?shiny::brushedPoints.
stopApp(clickOpts(data,input$plot_click))
})
}
runGadget(ui,server)
}
Toexecutethiscode,firstselectalltheprecedingcodeinRScriptandexecuteit,thenrunclick_gadget(iris,"Sepal.Length","Sepal.Width").Here,theirisdatasethasbeenused,butitcanbechangedtoanydataset:
Atthesametime,iftheplotisclickedandtheDonebuttonispressed,wecanseethedataoftheinputteddataset.
AddingapasswordSofar,wehavelearnedhowtodevelopaShinyapplication.Sincetheapplicationisexposingsomuchdatatotheoutsideworld,itneedstobeprotectedbyunauthorizedmeans.Forthat,wecanprovidepasswordauthentication.Shinyprovidesacontrolforachievingthistask,calledpasswordInput(https://shiny.rstudio.com/reference/shiny/latest/passwordInput.html):
passwordInput(inputId,label,value="",width=NULL,placeholder=NULL)
Let'sseeanexamplewithpasswordInput:
ui<-fluidPage(
passwordInput("password","Password:"),
actionButton("go","Go"),
verbatimTextOutput("value")
)
server<-function(input,output){
output$value<-renderText({
req(input$go)
isolate(input$password)
})
}
Hereistheoutput:
Intheprecedingapplication,theUIsectionhaspasswordInput(),whichconvertstheenteredtextintodotsandtheinputtedvalueisstoredinapasswordvariablethatcanfurtherbeusedinreactiveprocesses.ThecodecanbecopiedandpastedintoRScriptandexecutedasasimpleShinyapplication.
SummaryInthischapter,welearnedaboutRShiny'sadvancedfeatures.Animation,graphics,reportdevelopment,andusingknitrarewonderfulprofessional-levelpresentationfeatures;usingthese,anybodycansharetheirresultsandtellthestoryaboutthedatainhand.WealsodiscussedanddemonstratedthefeatureofbookmarkingtheShinyapp'sstatewithdifferentlevelsofcomplexity.Interactiveplotsandtablesarenewwaystoplaywithdataandfindingnewwaysofdynamicpresentation.Widgetsarelikecookbooksandcanbeusedtospeeduptheapp-developmentprocess.WecannowalsocodeforRusersanddevelopersbydevelopinggadgets.Addingapasswordtoourappcangiveusextracontrolonrestrictingthedataaccessedbyendusers.Inthischapter,westudiedmostofthelatestadvancedfeaturesofpresentation,codesharing,andauthentication.
CodePatternsinShinyApplicationsAsyourShinyapplicationsbecomelargerandmorecomplex,itisimportanttowriteclear,readable,andmaintainablecode.Insomecases,itisalsonecessarytomodularizeyourcode.ThischaptercoversvariousaspectsofwritinganddebuggingShinyapplications,functions,andmodules.WewillalsolookatfeaturesthatrelatetothecontrolofreactivitywithinyourapplicationandhowtospeedupShinyapplications.
Inthischapter,wewillcoverthefollowingtopics:
ReactivityinRShinyControllingspecificinputwiththeisolate()functionRunningreactivefunctionsovertimeEventhandlingusingobserveEventFunctionsandModulesShinytestDebuggingHandlingerrors(includingvalidate()andreq())ProfilingRcodeDebounceandthrottle
ReactivityinRShinyRShinyusesareactiveprogrammingcodingpattern,whichmakesapplicationsresponsive.Inreactiveprogramming,therearebasicallythreeelements:
ReactiveSourceReactiveEndpointReactiveConductor
ThecombinationoftheseelementsiswhatmakesaShinyappmoreresponsive.Let'shavealookinmoredetailathowtheywork:
ReactiveSource:ThereactivesourceistheinputobjectingtheShinyapp.IttakestheinputfromtheuserandkeepsitintheInputobject.TheInputobjectisassociatedwithanyUIelement,withwhichtheuserinteractsandprovidesinput.Itcanbeaninputbox,anactionbutton,aninteractivetableorplot,oranyotherUIcomponent.Forexample,ifwetakeinputfromatextboxnamedtxtboxintheUIcode,wecansaythatinput$txtboxisthereactivesource.
ReactiveEndpoint:ThereactiveendpointistheoutputobjectinaShinyapp.ItacceptsavalueandputsthatintheoutputcomponentsoftheUI.TheoutputcomponentscanbeanyUIelementthatisusedtodisplaythedatainanyformat.Theoutputobjectisoutput.Forexample,ifwewanttoputthedataintoaplotnamedhist_plotintheUIfile,thenoutput$hist_plotwillbethereactiveendpoint.
Asimpleapplicationcancontainonlythesetwoelements:thesourceandtheendpoint.Let'stakealookatanexampleofanapplicationthatusuallyappearswheneverwestartanewShinyapplication:
library(shiny)
#DefineUIforapplicationthatdrawsahistogram
ui<-fluidPage(
#Applicationtitle
titlePanel("OldFaithfulGeyserData"),
#Sidebarwithasliderinputfornumberofbins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Numberofbins:",
min=1,
max=50,
value=30)
),
#Showaplotofthegenerateddistribution
mainPanel(
plotOutput("distPlot")
)
)
)
#Defineserverlogicrequiredtodrawahistogram
server<-function(input,output){
output$distPlot<-renderPlot({
#generatebinsbasedoninput$binsfromui.R
x<-faithful[,2]
bins<-seq(min(x),max(x),length.out=input$bins+1)
#drawthehistogramwiththespecifiednumberofbins
hist(x,breaks=bins,col='darkgray',border='white')
})
}
#Runtheapplication
shinyApp(ui=ui,server=server)
Yougetthefollowingoutput:
IntheUIsectionoftheprecedingcode,sliderInputistheinputcomponentnamedbins.Intheservercodesection,input$binsisthereactivesource.Similarly,plotOutput("distPlot")istheoutputcomponentontheUI.Asthevalueofinput$binschanges,thisplotalsochanges.Thisishowreactivityworks.Intheserver
section,output$distPlotisthereactiveendpoint.Thisisrepresentedhere:
However,withmorecomplexapps,thereactivesourceandendpointscanformdifferentcombinations.Imaginethatagraphistobebuiltusingtwoinputvalues.Inthiscase,theflowdiagramwouldbeasfollows:
ReactiveConductor:Sofar,wehaveseenthatthesourceisdirectlyconnectedtotheendpoint.Theremaybesituationswherethereisamiddleelementbetweenthesetwo.Thismiddleelementiscalledareactiveconductor.
Reactiveconductorsareusuallyusedtoencapsulateslowoperationsorcatchdata.FromShiny'sofficialwebsite,https://shiny.rstudio.com/articles/reactivity-overview.htmltocalculateanumberandinverseofitintheFibonaccisequenceforthe
nthelementofasingleapplication,whichcanbeverytime-consuming.Thissituationisshowninthecode:
#CalculatenthnumberinFibonaccisequence
fib<-function(n)ifelse(n<3,1,fib(n-1)+fib(n-2))
server<-function(input,output){
output$nthValue<-renderText({fib(as.numeric(input$n))})
output$nthValueInv<-renderText({1/fib(as.numeric(input$n))})
}
Theflowdiagramwouldbeasfollows:
Insuchsituations,amiddlelayertocatchtheresultscanbeaddedtoreducethetimetakenbythisprocess.Thatiscalledareactiveconductor.Theprecedingcodecanbemodifiedusingareactiveconductor,asfollows:
fib<-function(n)ifelse(n<3,1,fib(n-1)+fib(n-2))
server<-function(input,output){
currentFib<-reactive({fib(as.numeric(input$n))})
output$nthValue<-renderText({currentFib()})
output$nthValueInv<-renderText({1/currentFib()})
}
Nowtheflowdiagramwilllookasfollows:
Upuntilnow,wehavediscussedthreeelements:thereactivesource,thereactiveconductor,andthereactiveendpoints.Thesearethegeneraltermsusedtodescribereactiveprogrammingelements.InRShiny,thereactivesourceisknownasthereactivevalue,thereactiveendpointistheobserver,andthereactiveconductoristhereactiveexpression.Therearetwofundamentaldifferencesbetweentheobserverandreactiveconductor'sfunctionality.Thefirstisthattheobserverrespondstoeventflushingbutreactiveexpressionsdon't.Thesecondisthatreactiveexpressionscanreturnvaluesbuttheobservercan't.
AcloserlookatreactivitySofar,wehavediscussedreactiveprogrammingandhowShinyimplementsitsfeatures.We'llnowtakeacloserlookathowthisworks.RShinyusesapullmechanisminsteadofapushmechanism.Thismeanswhenevertheoutputsensesthatthereisachangeininput,itpullsthenewvaluesfromtheinput.Inpushmechanisms,outputvaluesarepushedforeveryinputchange.Pullmechanismsarealsoknownaslazy-evaluationmechanisms.Thissimplymeansthattheyarenotlikeinputtooutputelectricitytransfers,butmorelikepigeon-carriermethods.
Shinyimplementsreactivitywithtwospecialobjectclasses:
Reactivevalues,which,aswehaveseen,canbeprintedasinput$valueObserver,whichcanbeprintedasoutput$output_val
Wheneveranobserverusesareactivevalue,itcreatesareactivecontextwiththevalue.Thecontextcanbeanyexpressionthatisrunifthereisanychangeinthevalue.Here,theexpressionreferstocallback.Ifmultipleobserversareusingthesamevalue,thatsinglevaluecanholdmultiplecontexts:
Fromtheprecedingdiagram,seehowcontextworksbylookingatthebasiccommunicationbetweenreactivevaluesandobservers.Supposethattheinputvaluechangesto60,sowegetinput$=60.Inthiscase,acontextwithanewvaluewillbecreated.Theobserversensesthatnewvalueandaskstheserverforcallback.Theserverfindsthecontextwiththenewvalueandflushesittotheobserver:
Inagraphicalrepresentation,thereactivesourcecanonlybeaparentandthesourceendcanonlybeachild,whereasaconductorcanbeaparentandachild.
Controllingspecificinputwiththeisolate()functionTheremaybesituationswhentheobserverorconductorwantstoreadthevalueofanexpressionbutavoiddependency.Supposewewanttoimportsomedataorperformsomecalculations,butonlyafterabuttonhasbeenclicked.
Let'stakealookatthisexample:
library(shiny)
#DefineUIforapplicationthatdrawsahistogram
ui<-fluidPage(
#Applicationtitle
titlePanel("OldFaithfulGeyserData"),
#Sidebarwithasliderinputfornumberofbins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Numberofbins:",
min=1,
max=50,
value=30)
),
#Showaplotofthegenerateddistribution
mainPanel(
actionButton("goButton","Go!"),
plotOutput("distPlot")
)
)
)
#Defineserverlogicrequiredtodrawahistogram
Intheserverfile,wecanseethatoutput$distPlotisdependentoninput$goButton.Wheneverthebuttonisclicked,theplotgetsexecuted.However,whenwewrapinput$binsinisolate(),thistellsShinythattheobserverorreactiveexpressionshouldnotbedependentonanyreactiveobject:
server<-function(input,output){
output$distPlot<-renderPlot({
#generatebinsbasedoninput$binsfromui.R
x<-faithful[,2]
#bins<-seq(min(x),max(x),length.out=input$bins+1)
#Takeadependencyoninput$goButton
input$goButton
dist<-isolate(bins<-seq(min(x),max(x),length.out=input$bins+1))
hist(dist,breaks=bins,col='darkgray',border='white')
#drawthehistogramwiththespecifiednumberofbins
#hist(x,breaks=bins,col='darkgray',border='white')
})
}
#Runtheapplication
shinyApp(ui=ui,server=server)
Theflowgraphlooksasfollows:
Inthecode,wecanpreventtheplotbeingshownthefirsttimewithouttheclickbuttonbeingpressedbyapplyingconditionstoit.Inisolate(),notonlyreactivebutalsoreactiveexpressionscanbeincluded.Itisalsopossibletoincludemultiplestatementsinanisolateblock.
Runningreactivefunctionsovertime(executionscheduling)ThecoreoftheShinyapplicationisthereactiveengine,whichtellsShinywhentoexecuteeachcomponent.Wearenowfamiliarwiththereactiveflowofvariouscomponents.Reactiveobservershaveaflagthatindicateswhethertheyhavebeeninvalidated.Wheneverthevaluesoftheinputobjectchange,allofthedescendantsinthatgraphareinvalidated.Suchinvalidatedobserversarealsocalleddirtyorclean.Alongwiththis,thearrowsintheflowdiagramthathavebeenfollowedareremoved.
Let'sdiscussanexampleofasinglereactivesourceandendpoint:
server<-function(input,output){
output$Plot_output<-renderPlot({
hist(rnorm(input$User_input))
})
}
Theflowdiagramisasfollows:
Assoonastheinputvalueschange,allthedescendantsareinvalidatedandaflusheventistriggered:
Thisisrepresentedinthefollowingdiagram,inwhichtheoutputobjectisshownindarkgrey.Whenthishappens,alltheinvalidatedobserversarere-executed.Iftheoutputobjectre-executes,itaccessesthereactivevalue.Thismakestheoutputobjectdependentontheinput:
Fromtheprecedinggraph,wecanseethatoutput$Plot_outisrequestingforinput.Onceitgetsinput,theinvalidatingflagisclearedandtheoutputisplacedontheUI:
Event-handlingusingobserveEventandeventReactiveSofar,wehavelookedathowreactivityworksinShinyapplications.Wehavelearnedhowtouseobserve()andisolate().Wewillnowlookinmoredetailatevent-handling.Aneventcanbedefinedasareactivevalueoranexpressionthattriggersothercalculations.Forexample,sometimeswewantsomeactionstohappenonlyaftertheactionbuttonisclicked.
Wehavealreadylearnedtohandleeventsusingobserve()andisolate().Therearetwomoremethods:
observeEvent
eventReactive
Thesetwoprovidestraightforwardwaysofhandlingevents:
observeEvent:Ifwewanttoperformanactioninresponsetoanevent,observeEventisuseful.Thesyntaxisasfollows:
observeEvent(eventExpr,handlerExpr,event.env=parent.frame(),
event.quoted=FALSE,handler.env=parent.frame(),
handler.quoted=FALSE,label=NULL,suspended=FALSE,priority=
0,
domain=getDefaultReactiveDomain(),autoDestroy=TRUE,
ignoreNULL=TRUE,ignoreInit=FALSE,once=FALSE)
Thefirstargumentistheeventtoberespondedtoandthesecondisthefunctiontobecalledwhentheeventoccurs.TherestoftheparametersareoptionalandanexplanationcanbeseeninnextsectionofeventReactive.
eventReactive:eventReactiveisusedtocalculateavaluethatonlygetsupdatedwhenaresponsetoaneventisneeded.Thesyntaxisgivenhere:
eventReactive(eventExpr,valueExpr,event.env=parent.frame(),
event.quoted=FALSE,value.env=parent.frame(),value.quoted=
FALSE,
label=NULL,domain=getDefaultReactiveDomain(),ignoreNULL=
TRUE,
ignoreInit=FALSE)
Here,thefirstargumentistheeventtobedetectedandthesecondisthevalueexpression.Therestoftheargumentsareexplainedhere:
event.env:Theparentenvironmentfortheeventexpression.Bydefault,theparentenvironmentisthecallingenvironment.event.quoted:AlogicalexpressiontotellwhethereventExpisquoted.handler.envandhandler.quoted:Thesameasevent.envandevent.quoted,withrespecttothehandler.Thelabelisthenamegiventotheobserverorthereactive.Suspended:Alogicalexpressionforidentifyingwhethertheobserverissuspended.Priority:Avaluethatidentifiesthepriorityoftheobserver.autoDestroy:Anotherlogicalexpression.Ifitistrue,theobserverwillbedestroyedafterthedomainhasended.ignoreNULL:Alogicalparameteraswell.Ifitistrue,theactionistobetriggeredforthevalueisNULL.ignoreInit:Falsebydefault.IfitisTRUE,andobserveEventisfirstcreatedorinitialized,handlerExpr(secondargument)isignored.valueExpr:AnexpressionthatreturnsthevalueofeventReactive.value.envis:TheparentenvironmentforvalueExpr.value.quoted:CheckswhethervalueExprisquotedornot.
library(shiny):
shinyApp(
ui=fluidPage(
column(4,
numericInput("x","Value",1),
actionButton("button","Show")
),
column(8,tableOutput("table"))
),
Intheprecedingcode,thereisoneinputboxandanactionbuttonontheUI.Thefollowingisthecodeoftheserversection.Here,wecanseethatobserveEventishandlingtheevent,showingtheinputvalues,andeventReactiveiscalculatingtheheadrowsfromtheirisdatasetswithagivenvalueofx:
server=function(input,output){
observeEvent(input$button,{
cat("Showing",input$x,"rows\n")
})
df<-eventReactive(input$button,{
head(iris,input$x)
})
output$table<-renderTable({
df()
})
}
)
Youwillgetthefollowingoutput:
FunctionsandmodulesWehavenowgonethroughagoodnumberofShinyapplicationsthatarefocusedondifferenttopics.Imaginethatwenowhavetodevelopafully-fledgedenterpriseapplicationwithvariousfunctionalitiesasaShinyapplication.Astheapplicationgrows,sowillthenumberoflinesofcodeanditscomplexity.Insituationssuchasthese,applicationscanbecomeunmanageable.Wemayalsoberewritingthesamekindofcoderepeatedly,therebyincreasingthedevelopmenttime.Additionally,variablesneedtobescoped,whichlimitsthereusabilityofnames.Toeliminatetheseobstacles,wecanusemodularization.
Modularizationisnothingbuttheuseofseparatingmodulesaccordingtotheirfunctionality.Modulescanbedefinedasasetofinstructionsthatarewrittentoaccomplishatask.Rprogrammingisdevelopedaroundmodules.Werepeatedlycallfunctionsforalmostanytaskwewanttoperform.Thismeansthatwedon'thavetowritemorecode,asisrequiredinprogramminglanguagessuchasC,C++,orJava.WecanusethisprogrammingparadigmwithShinyaswell.
Sofar,wehavecodedUIandserverfunctionsintheShinyapplication,withanumberofcomponentsandinputandoutputelements.Wewillnowtrytomodifytheseinasimplercodingpatternwithmodules.
Wecanorganizethecodeintotwosections,basedontwofunctions:
ForcreatingUIelementsForloadingserverlogic
Wealsohavetosticktosomenamingconventionssothatthecodecanbeidentifiedeasily.TheUImodulescanbesuffixedwithInput,Output,orUI.Beforegoingintotoomuchdetail,let'sstartbyobservingthesimpleapplicationcodethatisavailablewheneverwestarttheShinyapplication,RStudio.Thisisgivenhere:
#
#ThisisaShinywebapplication.Youcanruntheapplicationbyclicking
#the'RunApp'buttonabove.
#
#FindoutmoreaboutbuildingapplicationswithShinyhere:
#
#http://shiny.rstudio.com/
#
library(shiny)
#DefineUIforapplicationthatdrawsahistogram
ui<-fluidPage(
#Applicationtitle
titlePanel("OldFaithfulGeyserData"),
#Sidebarwithasliderinputfornumberofbins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Numberofbins:",
min=1,
max=50,
value=30)
),
#Showaplotofthegenerateddistribution
mainPanel(
plotOutput("distPlot")
)
)
)
#Defineserverlogicrequiredtodrawahistogram
server<-function(input,output){
output$distPlot<-renderPlot({
#generatebinsbasedoninput$binsfromui.R
x<-faithful[,2]
bins<-seq(min(x),max(x),length.out=input$bins+1)
#drawthehistogramwiththespecifiednumberofbins
hist(x,breaks=bins,col='darkgray',border='white')
})
}
#Runtheapplication
shinyApp(ui=ui,server=server)
WecanseethatthiscodefromRStudioissimplywrittenwithoutmodularization.Let'sdiscussindetailhowwecanmodularizetheUIandServercodeofthisapplication:
UICodemodularization:Intheprecedingcode,oneoftheelementsoftheUIsectionissliderInput.Atthemoment,thereisonlyonesliderInputbox,buttheremightbemore,meaningwewouldhavetowritethesamecodeagainandagainifwedidn'tusemodularization.Let'sdevelopamoduleforthis.FortheUImodule,weneedtogiveanametothefunction,suchasSliderbarInput.WealsoneedtogiveanIDtoeachinputelementthatweareusing.Inthefunctionargument,therefore,wewilladdoneIDparameter,whichwillbeusedtocreateanamespaceusingNS().Rightnow,
weonlyhaveoneinputelement,butinthefuturewemaywanttoaddmultipleelements.WewillusetagList().TheUImodulewillnowlookasfollows:
SliderbarInput<-function(id){
ns<-NS(id)
tagList(
sliderInput(ns("bins"),
"Numberofbins:",
min=1,
max=50,
value=30)
)
}
WehavenowseparatedtheinputcodefromtheUIcode.WehavetocallthiscodefrominsidetheUIwheneverwewanttoputinasliderbar.Inthefollowingcode,wecanseethatSliderbarInput("Simplesliderbar")hasbeencalled.Thiswillcalltotheprecedingcode."Simplesliderbar"istheIDgiventosliderInput:
ui<-fluidPage(
#Applicationtitle
titlePanel("OldFaithfulGeyserData"),
#Sidebarwithasliderinputfornumberofbins
sidebarLayout(
sidebarPanel(
SliderbarInput("Simplesliderbar")#calltofunction
),
#Showaplotofthegenerateddistribution
mainPanel(
plotOutput("distPlot")
)
)
)
ServerCodemodularization:Intheserversectionoftheoriginalcode,wearedevelopingacalculationtorenderaplot.Wewilldevelopamoduletoperformthistask.Ourfirsttaskistogiveanametothefunction,suchasSliderbar.Intheservermodule,thefunctionhastohavethreearguments:Input,Output,andSession.Therestcanbetheinputparameterstheuserwantstoadd.Afterthat,wewillwriteallthecalculationcodeinthisfunction.Ournewmodulewilllookasfollows:
Sliderbar<-function(input,output,session){
#generatebinsbasedoninput$binsfromui.R
x<-faithful[,2]
bins<-seq(min(x),max(x),length.out=input$bins+1)
#drawthehistogramwiththespecifiednumberofbins
hist(x,breaks=bins,col='darkgray',border='white')
}
WenowhavetocalltheprecedingcodefromtheusingcallModule()servercode.Here,wewillpassthefunctiontobecalledandthelabelorIDoftheinputelement.Theservercodewilllookasfollows:
server<-function(input,output){
output$distPlot<-renderPlot({
callModule(Sliderbar,"Simplesliderbar")
#SimplesliderbariscomingfromUI
})
}
Bydoingthis,wehaverecreatedtheapplication.However,wehavetodecidewherewecandefineourmodules.Oneoptionistokeepourmodulesinthepreambleofasinglefileapplicationorinafilethatissourcedinthepreambleofasinglefile.Anotheroptionistoputthemodulesinaglobal.rfile,orafilethatissourcedintheglobalfile.Alternatively,wecanwrapamoduleinapackagethatisloadedbytheapplication.
Theadvantageofusingmodularizationisthatitmakesthecodereusable.
ShinytestLet'ssaythattheShinyapplicationwehavedevelopedrunswellonourmachineforsomeinput,butforothersitdoesnotgivethedesiredoutputoritgetsstucksomewhereandthrowserrors.Inthesoftwaredevelopmentprocess,testingisoneofthemostimportanttasks.AShinyapplicationmightstopworkingduetoanyofthefollowingreasons:
TheversionofShinyandtheversionofthepackagesmaydifferAmodificationintheapplicationcodeleadstothewronginputforanyotherreactivecodeThedataformatmayhavechanged
Theremaybecountlessreasonsthatcauseustocomeacrosserrorsandcausetheapplicationtostopworking.Doingtestingmanuallycanbetime-consumingandinefficientbecauseyouhavetoconsiderawiderangeofusecases.Forthisreason,Shinyhastheshinytestpackageforautomatictesting.Itcanbeinstalledasfollows(https://www.rdocumentation.org/packages/devtools/versions/1.13.6/topics/install_github):
library(devtools)
install_github("rstudio/shinytest")
Tocarryoutatest,followthesesteps:
1. RunrecordTest()tolaunchtheappinatestrecorder(https://rstudio.github.io/shinytest/reference/recordTest.html):
library(shinytest)
#Launchthetargetapp(replacewiththecorrectpath)
recordTest("path/to/app")
Inthetestrecorder,wecanfindalistofrecordedevents.Theseeventsareinteractionsmadebytheuser.Wecanalsotakesnapshotsofthestateoftheappbyclickingontakesnapontheright-handsidewindowoftherecorder.
2. Quitthetestrecorderandfindthetestscriptinthe.Rfileinthetest/
subdirectory.Thisholdscodelikethefollowing:
app<-ShinyDriver$new("..")
app$snapshotInit("mytest")
app$snapshot()
app$setInputs(checkGroup=c("1","2"))
app$setInputs(checkGroup=c("1","2","3"))
app$setInputs(action="click")
app$snapshot()
Here,wetrythepossibleusecaseinputandfiguringerrors.Foramorein-depthunderstandingoftheworkingsoftheshinytestpackage,refertohttps://rstudio.github.io/shinytest/articles/shinytest.html.
DebuggingDebuggingaShinyappisnotthesameasdebuggingcodeinotherprogramminglanguages,suchasC,C++,orJava,wherethecontrolflowlookslinear.BecauseRShinyisreactive,itrunsonawebserveraswellasaShinyframework.Thatmakesithardertodebug.
Ifwegetanerrororanundesirableoutput,wecanapplybreakpointsatthesuspectedlineofcode.Thiscanbedonebyclickingontheleftsideofthecodewherethelinenumberisgiven,whichmakesareddotappear:
Afterrunningthecode,Shinywillstopexecutionatthebreakpointandwecanstepintothecodeandtakealookatthecurrentvariablevalues.SettingabreakpointispossiblewithRStudio.
Sometimes,applyingabreakpointdoesn'twork,sowehavetochangetheinputandobservetheoutput.Wecanthenapplyabreakpointagaintotrytodiagnosetheproblem.Wecanenabletheshowcasemodeandseewhichpartofthecodeisexecuting:
shiny::runApp(display.mode="showcase")
Toexaminethereactive,wecanenableshiny.reclogandobservethereactivelogs:
options(shiny.reactlog=TRUE)
Thiswillgiveusanideaabouthowthereactivesareworking.Formoredetaileddebugging,wecanapplyaprintstatementinsuspectedplacestounderstandtheflowoftheapplicationandthebehaviorofthecode.Inordertotracetheleveloftheclientorserverarchitecture,weneedtosettheshiny.tracemodetoon.ThiswillprovideatraceoftheappintheJSONfile.
Thebrowser()statementcanalsobeusedfordebugging.Itactsasbreakpointandcanbeaddedanywhereinthecode.
Debuggingwiththeprecedingmethodologiesissufficienttorunanapplication,buttherearemanyothermethods,whichareoutsidethescopeofthisbook.Afterdebugging,thenextstepistohandletheerrorsthataredetected.
Handlingerrors(includingvalidate()andreq())Areyougettinganerrormessageinredonyourscreen?Areyouoryouruserabletounderstandthatmessage?Theanswerstosuchquestionscanbevalidationerrorsusingvalidate()andreq().
ValidateThisisdesignedtoleadtheuserthroughtheUIoftheShinyapp.Imaginethatwehaveasituationwhereweneedtoplotagraphbasedonsomevaluesofalistboxanditthrowsuparedmessage.Thislooksstressfultotheuser.Weneedtoprovideaprecautionarymessagetomakesurethereaderentersavalidinput.validateisawayofcheckingtheinputandprovidingamessage,usingneed.Inthefollowingcode,wecanseethatvalidateischeckingforaphonenumber.Iftheinputisanemptystring,itwillgiveamessage:
server<-function(input,output){
Phon_Number<-reactive({
validate(
need(input$PN!="","ProvidePhonenumber")
)
get(input$data,'package:datasets')
})
Handlingmissinginputwithreq()Sometimes,wemightseeanerrorwhenourgraphneedstobeupdateddynamicallybecausethedataisunavailablewhiletheapplicationisloading.Itisalsopossiblethatthedataisnotavailableforacertaininputandtheelementtriestorendertheoutput.Inthissituation,wewouldseeanerrormessage.
Tohandlethis,wecanusereq().req()istheshortformofrequire.Thisisusefulforcheckingpreconditions.Wheneverweneedtowriteareactiveelement,wecanencloseitinreq().Thiswillmakesurethattheoutputishalteduntilthevaluesareavailable.
ProfilingRcodeInthebigdataera,wherewemighthavepetabytesofdata,maintainingtheperformanceofapplicationsiscrucial.Theapplicationperformancemightgodownbecauseofdata-loadingoperationsorcalculations.Todetectsuchoperations,wehaveapackagecalledprofvis.Thistellsushowmuchtimealineofcodetakestoexecute.Installthepackageasfollows:
install.packages("profvis")
RStudiohasprovidedsupportfortheprofvispackage.Let'stakealookathowtouseit:
library(profvis)
profvis({
plot(iris$Sepal.Length,iris$Sepal.Width)
plot(cars$speed,cars$dist)
})
profvisenclosesthecodetobeanalyzed.Inthefollowingscreenshot,wecanseetheoutput.Foreachlineofcode,wecanseethememoryusagewiththetimetaken:
DebounceandthrottleDebounceandthrottleareusedtoslowdownareactiveexpression.Forexample,supposeweareusingtheinvalidationcheckforareactiveexpressionanderrorindicationsarepromptedunnecessarily.Wecanusedebounceandthrottletomakeexpressionssuchastheseslowdownandwaitforintermediateexpressionstocompletetheircalculations.Thesyntaxesofbothoftheseareasfollows:
debounce(r,millis,priority=100,domain=getDefaultReactiveDomain())
throttle(r,millis,priority=100,domain=getDefaultReactiveDomain())
Here,risthereactiveexpressionthatinvalidatestoooften.millisisthetimewindowusedbydebounce/throttle,andprioritysetstheobserver'spriority.Forexample,ifwewanttoadddebouncetoanexpression,wecandoitasfollows:
plot_iris<-plot(iris$Sepal.Length,iris$Sepal.Width))%>%debounce(1000)
Formoredetailvisitehttps://shiny.rstudio.com/reference/shiny/1.0.0/debounce.html.Letshaveanexample
##OnlyrunexamplesininteractiveRsessions
if(interactive()){
options(device.ask.default=FALSE)
library(shiny)
library(magrittr)
ui<-fluidPage(
plotOutput("plot",click=clickOpts("hover")),
helpText("Quicklyclickontheplotabove,whilewatchingtheresulttablebelow:"),
tableOutput("result")
)
server<-function(input,output,session){
hover<-reactive({
if(is.null(input$hover))
list(x=NA,y=NA)
else
input$hover
})
hover_d<-hover%>%debounce(1000)
hover_t<-hover%>%throttle(1000)
output$plot<-renderPlot({
plot(iris)
})
output$result<-renderTable({
data.frame(
mode=c("raw","throttle","debounce"),
x=c(hover()$x,hover_t()$x,hover_d()$x),
y=c(hover()$y,hover_t()$y,hover_d()$y)
)
})
}
shinyApp(ui,server)
}
Youwillgetthefollowingoutput:
Resulttable
SummaryInthischapter,welearnedaboutthereactivityofRShiny.Welearnedindetailhowreactivesource,endpoint,andconductorwork,andhowtocontrolspecificinputwithisolate().Wehavealsoscheduledthereactiveoffunctions.Afterthat,weusedobserveEventandeventReactiveforevent-handling,andlearnedhowtomakeourcodemoremodular.Afterdevelopingourapplication,wecarriedouttestingusingShinytest,debugging,anderror-handling.Ontopofallthis,wediscussedhowtoimprovetheperformanceofourShinycodeusingprofiling.
PersistentStorageandSharingShinyApplicationsHavingmadeallofthosewonderfullyintuitiveandpowerfulapplications,youarequitenaturallygoingtowanttoshowthemoff.YoumaywishtosharethemwithcolleaguesormembersoftheworldwideRcommunity.Youmaywishtosharethemwithindividualsinyourdepartmentorfieldwho,whilenotRusers,canhandlealittlebitofefforttogetanapplicationworking.Oryoumaywishtosharethemtransparentlyandfreelywiththewholeworldbyhostingthemonaserver.Shinyoffersquitealotofapproachestosharingapplications,andyou'llbegladtohearthateventhemostcomplexshouldnotbetootaxing,withtherighthardwareandOSonyourserver.
Inthischapter,wewilltakealookatthefollowing:
SharingoverGitHubAnintroductiontoGitusingGitandGitHubwithinRStudioSharingapplicationsusingGitSharingusing.zipand.tarShinyapps.io
ShinyServerRunningShinyinAWSandGoogleCloudScoping,loading,andreusingdatainShinyapplicationsTemporarydatainput/outputPermanentdatafunctionsDatabasesSQLinjectionDatabaseswiththepoolpackage
ThereareafewwaysofsharingwithRusersrunningtheShinypackagewithinRsummarizedinthefollowingsections.
SharingoverGitHubByfar,theeasiestwaytoshareyourcreationswithfellowRusersisoverGitHub(github.com).Ofcourse,otherRuserscanalsousealltheothermethodsinthischapter,butthisisprobablythemostfrictionlessmethod(shortofhostingtheapplication)forbothyouandtheenduser.
AnintroductiontoGitYouwillnodoubthaveheardofGit(git-scm.com—theversion-controlsystemthathascollaborativesharingfeaturesatGitHub),evenifyouhaveneverusedit.Gitisaversion-controlsystemthatcanbeusedlocallyonyourcomputer,orinordertogetthebestoutofit,theversion-controlrepositoryonyourcomputercanbesyncedonlineatGitHub.HostingofopensourcecodeatGitHubisfree,andtherearepaidoptionsforclosedsourcecode.Ifyouhaven'talreadyusedaversioncontrol,thisisanexcellentreasontostart.Itisalittleintimidatingfornewcomers,butovertime,theresourcesandtutorialsonthesitehaveimprovedandperhapsonedayofhead-scratchingawaitsyou.TrustmethatonedayIwillbepaidbackhundredfold.TheProGitbookcanbedownloadedforfreefromtheGitsiteatgit-scm.com/book/en/v2.Thereisalsoawonderfulinteractivetutorial(try.github.io)ontheGitsite.Asadie-hardLinuxenthusiast,itpainsmetoadmitit,butIactuallyfoundlearningonWindowseasierbecausetheyprovideawonderfulGUItogetyoustarted(alsoonOSX).ThisdoesnotmeanthatyouneedtouseWindowsorshouldsticktoWindows;IhappilydroppedtheGUIandwenttotheterminalinLinuxonceI'dfoundmyfeetabit.
It'salsoworthnotingthattherearesomegreatGUIsforLinuxaswell,soyoucancheckyourpackage-managementsystem.Ididn'tfindanythatsupportedbeginnerssowellastheofficialWindowsorOSXversions,though.GithasalistofGUIsatgit-scm.com/downloads/guis.NotethatsomeofthesesupportGitHubandotherssupportGititself.ThelistincludestheWindows,OSX,andLinuxGUIs.
Finally,RStudioitselfactuallysupportsGitandGitHub,andonceyou'veinstalledGitandsetupyouraccount,youcanprettymuchrunthewholeshowfromwithinRStudioitself.
UsingGitandGitHubwithinRstudioToinstallGit,simplygototheURLmentionedearlieranddownloadthe.exefileforWindows,oronUbuntu,runthefollowingcommand:
sudoapt-getinstallgit
ForotherflavorsofLinux,checkthepackage-managementsystem.HavinginstalledGit,younowneedtosetupanewprojectwithinRStudio.Aversion-controlwithGit(orSVN,adifferentversion-controlsystem,whichwewillnotconsiderhere)isonlypossiblewhenweuseaprojectwithinRStudio.
ProjectsinRStudio(h3)UsingprojectsinRStudioisagoodwaytoorganizeyourwork.EachprojecthasitsownworkingdirectorywithaseparateRsession,workspace,consoleinputhistory,andopeneditortabs(amongotherthings).Eachtimeaprojectisopened,eachofthesewillbesettothevaluecurrentlyassociatedwiththeproject,ineffectlaunchinganewRsession,loadingthedataandconsolehistorysincethelasttimetheprojectwasused(iftheyareselectedasthedefaultbehaviororindividuallyforthisproject),settingtheworkingdirectorytotheoneassociatedwiththeproject,andsoon.Thisallowsyoutoswitchinandoutofdifferentprojectseitherasyouworkorwhenyoupickupworkthenextday.Tosetupanewproject,gotoFile|NewProjectinRStudio.SelecteitherNewDirectoryifthisisacompletelynewsetofcodeandfilesthatyouwanttocreateanewfolderfor,orExistingDirectoryifyouhavealreadystartedandjustwanttopointtheprojecttoadirectorythatyouhavealreadycreated.Onceyouhaveaprojectsetup,gotoTools|Versioncontrol|ProjectSetup....Thefollowingmenuwillappear:
MakesurethattheGit/SVNtabontheleft-handsideofthepageisselectedandusetheversion-controlsystemcontrolontheright-handsideofthepagetoselectGit/SVN,ifyouprefer,butthiswillonlyappearifyouhaveinstalledit,andthiswillnotbecoveredinthischapter.YoumayneedtoreopentheprojectatthispointbygoingtoFile|Recentprojects.Youwillneedtoconfiguretheremoteconnectionbetweenyourlocal.gitrepositoryandtheGitHubaccountyourself.GotoyourGitHubaccount,andgotoRepositories|New.Giveitanameanddescription,andselectCreaterepository.Havingdonethis,someinstructionswillappearonthescreenthatwillhelpyoutosetupaconnectionbetweenthisremoterepositoryandthelocalversiononyourmachine.Atthetimeofwriting,thesimplestwayofdoingthisisthethirdboxdown.Keeptheseinstructionsasyouwillneedthemlater,butfornow,weneedtoconfigureRStudioalittlefurther.GototheTools|Globaloptions,andselecttheGit/SVNtab.Thefollowingmenuwillappear:
CheckwhethertheGitexecutableissetupcorrectlyinthefirstline.IfyoualreadyhaveanSSHRSAkey,itshouldbedisplayedinthebottomline.Ifnot,clickonCreateRSAKey,andyouwillbeguidedtocreateone.IfyouhavenotpreviouslypairedyourRSAkeywithyourGitHubaccount(whichyouwouldnothavedoneifthisisyourfirstexperiencewithGitHub),clickonViewpublickeyabovethethirdline,andthencopytheresultingtextintoyourGitHubaccountbygoingtoyouraccountsettings.Thiscanbeachievedbyclickingon
youruserportraitatthetop-rightcorneroftheGitHubwebpage.Next,clickonSettings,andthenclickontheleft-handsideofthescreenandselectSSHkeys,andfinally,clickonAddSSHkey,pasteyourkey,andclickonAddkey.Havingdonethis,youwillneedtocommityourcodetoGit,thatis,tothelocalcopyonyourmachine.ThisisveryeasyinRStudio.SelecttheGittabintheEnvironmentpaneinRStudio(bydefault,it'sthetop-righttabonthescreen),asshowninthefollowingscreenshot:
Selecttheelementsthatyouwanttocommitbyclickingontheboxestothefarleftofthescreenshot.ThiswillbeanythingthatyouwanttocommittoGitforyourfirstsubmissionandanythingthathaschangedforsubsequentsubmissions.ClickonCommitinthemenubar,whichisvisibleinthescreenshot.Youwillbepromptedtoreviewthechangesinanewwindowaswellasinstructedtowriteacommitmessageinthetop-rightcornerofthiswindow.Youcannotcommitwithoutamessage.Foryourfirstcommit,youmightliketowriteFirstcommitofbetaversion,andthenforsubsequentcommits,youmightmakecommentssuchasFixedjQuerybugandAddeddashboardelements,dependingonthechangesyouhavemade.Finally,topushtoGitHubforthefirsttime,selectMore|ShellintheGittab.Thiswillopenaterminalwindow.RemembertheterminalcommandsthattheGitHubwebpagegaveuswhenwesetupthenewrepositoryandthetwo-linerItoldyoutokeeptrackof?Thisiswhereweneedthem.Linebyline,copy
thetwo-linerfromthewebpage.ThiswillsetuptheconnectionbetweenRStudioandGitHub.Fromnowon,youcanpushyourcode(thatis,uploadittoGitHub)bycommittingandclickingonthePushbuttonintheGittabmenubar.ThisisaverybriefintroductiontoGit,GitHub,andRStudio,andisdesignedtogetyoustarted.Thereismuchmoretolearnabouthowtousethesetoolsefficiently,andyouareadvisedtotakealookattheonlinedocumentationforallthreeinordertolearnhowtomakethisprocessevensimplerandmorepowerful.
SharingapplicationsusingGitWeneedtoconsultthewebsitesmentionedearlierformoredetailsofeachofthesesteps.Onceyou'vesetyourGitversioncontrolandpairedwithanonlinerepositoryatGitHub,youcanveryeasilyshareyourcreationswithanyonerunningtheRandShinypackageusingtherunGitHub()command,whichtakesthenameoftherepositoryandtheusernameasmandatoryarguments:
runGitHub("GoogleAnalytics2ndEdition","ChrisBeeley")
Codeanddataarebothautomaticallydownloadedandrun.IfyouareusingRStudioandwanttolaunchyourownexternalbrowser,asopposedtousingtheonethatisbuilt-in,youneedtoaddlaunch.browser=TRUE.Ifyoudon'twantorneedversioncontrol,anddon'tneeddatatobeincludedinthedownload,asimpleroptionistouseGist,whichisalsohostedatGitHubatgist.github.com.UsingGistissimplyamatterofvisitingtheURL,settingupanaccount,pastingyourcodeintoit,andgivingtheserver.Randui.Rfilesthecorrectfilenames.YouwillthenhaveaURL,usingwhichyoucanshowyourcodetoothers.RunningthiscodefromtheShinypackageisjustamatterofusingrunGist()withtheURLorevenusingtheuniquenumericidentifierfromtheURL.
library(shiny):
runGist("https://gist.github.com/ChrisBeeley/a2f1d88dfedcd2e1cb59")
runGist("a2f1d88dfedcd2e1cb59")
Sharingusing.zipand.tarProbablythenextmostfrictionlessmethodofdistributingaShinyapplicationtoRusersisbyhostingeithera.zipor.tarfileofyourapplication,eitherovertheweborFTP.Youwillneedsomewheretohostthefile,andthenuserscanruntheapplicationusingrunUrl(),asfollows:
runUrl("http://www.myserver/shinyapps/myshinyapp.zip")
NotethatthisURLisnotreal.Youneedtoreplaceitwiththeaddressofyourownfile.
Ofcourse,youcandistributea.zipfileanywayyoulike—yourusersneedtoonlyunzipandthenuserunApp()fromwithinthedirectory,justasyoudowhentestingtheapplication.YoucanemailthefileanddistributeitonaUSBdriveforanymethodthatyouchoose.Thedisadvantagesofthismethodarethatyourusershavetounzipthefilethemselves(althoughthisisunlikelytoconfusemanyRusers),andanychangesmadetotheapplicationwillalsoneedtobedistributedmanually.
SharingwiththeworldInmostcases,anyseriousworkthatyoudowithShinywillatsomepointneedtobesharedwithanonR-user,whetherit'sanontechnicalcolleagueinyourdepartmentorthewholeoftheinternet.Inthiscase,abitmoreofthelegworkfallstoyou,butyoushouldstillbepleasantlysurprisedabouthowsimpletheprocessis.Therearetwooptionshere:setupyourownserverorgetapaidaccountwithRStudiotodoitforyou.
Shinyapps.ioShinyapps.ioisRStudio'spaidhostingforShinyapplications.Atthetimeofwriting,thereisatieredpricingstructure,dependingonthenumberofapplicationsyouwishtodeploy,whetheryouneedtheauthenticationofusers,thenumberofhoursyourapplicationswillrunpermonth,andsoon.Youcansetupafreeaccountthat,atthetimeofwriting,allowsfiveapplicationsand25hoursofapplicationruntimeamonth.Thisiswelcome;however,itisonlysuitableforverysmall-scaleuse;asingletweetofyourapplicationonthe#rstatshashtagislikelytobringenoughtraffictoyoursitetouseallofthe25hoursinaveryshorttime.Indeed,Ihavebeenlinkedtomanyshinyapps.ioapplications,whichindicatethattheaccounthasexceededtheallocatedhoursthismonthand,therefore,doesnotwork.
Bewarned:ifyouwanttheworldtoseeyourapplication,youeitherneedtogetapaidaccountorrunyourownserver(whichisexplainedlater).Usingthisservicedoes,ofcourse,entailcopyingyourcodeand/ordatatoathirdparty,soifthisisaproblemforyou,youwillneedtotakealookathostingyourselfonaserver.
IfyouareusingRStudio,itisveryeasytogetanapplicationonshinyapps.WheneveryouhaveaShinyapplication(thatis,aserver.Rorui.Rfile)open,youwillfindalittlePublishiconintheupper-rightcorneroftheeditor,asshowninthefollowingscreenshot:
Youwillbepromptedtoinstallvariousthings,dependingonyourOSandtheconfigurationofyourRinstallation.OnLinux,youwillprobablysaveyourselfabitofconfigurationifyouinstallthedevelopmentversionofR(r-base-devonUbuntu,availablethroughtheRmetapackageonFedora;forotherdistributionsoroperatingsystems,refertotherelevantdocumentation).
Foralloperatingsystems,youwillbepromptedtoinstallvariousRpackages.UsersofLinuxmayhaveproblemsconfiguringsomeofthesepackages;youmayneedtoinstalllibcurl-devandopenssl-dev(ortheirequivalentforyourdistro).InWindows,inmyexperience,thewholeoperation,rightfromthevanillainstallationofR,iscompletelyseamlessandeverythingwillbeinstalledandconfiguredcorrectly.
Then,youwillbepromptedtogotoyourshinyapps.ioaccountandlogin,whereyoucanauthenticateRStudio.YoucannowpublishitstraightfromRStudio:
1. Pressthebuttonhighlightedintheprecedingscreenshot,selectthefilestobeuploaded(forexample,thecodeanddatafiles),andclickonPublish
2. Itwilllaunchabrowserforyou,soyoucanseetheapplicationforyourselfandcopythelinkthatneedstobeshared
3. Ifyouforgetthelink,justlogintoshinyapps.io—thelinkisavailablefromyourlistofapplicationsinthemenu
Shinyapps.iowithoutRStudioIt'snotnecessarytouseRStudiotouseshinyapps.io,it'sjustabiteasier.Youneedtofollowthesesteps:
1. Ifyou'rehappierinanotherIDE,youjustneedtoensurethatyouhavethelatestversionofdevtoolsinstalled:
install.packages('devtools')
2. Installshinyapps:
install_github('rstudio/shinyapps')
3. Loadshinyapps:
library(shinyapps)
4. Logintoyourshinyapps.ioaccount,copytheauthorizetokencommandfromthetokensmenu(tokenmarkedwithXshere),andrunitinyourRsession(notethatthisonlyhastobedoneonceoneachcomputer):
shinyapps::setAccountInfo(name='chrisbeeley',
token='XXXXXXXXXXXXXXXXXXXXXXXX',
secret='XXXXXXXXXXXXXXXXXXXX')
5. Setyourworkingdirectorytothefolderthatholdsyourapplication:
setwd("~/myShinyApp")
6. Deploy:
deployApp()
MoredetailsareavailableonRStudio'spagesatshiny.rstudio.com/articles/shinyapps.html.
ShinyserverIfyouwanttohosttheapplicationsyourself,ShinyServerisavailableforLinux.Again,therearepaidandfreeoptions.ShinyServeristotallyfreeandopensource,whichisagreatcredittoRstudio.Thepaidversionhasanumberofbenefits.Themainonesaretheprovisionofsupportandextrafeatures,particularlyauthentication(LDAP/PAM/GoogleaccountsandrunningoverSSLtoencryptdatatoandfromtheserver).ItalsoallowsyoutousemultipleRprocessestoserveoneapplication,supportsmultipleversionsofRonthesameserver,andprovidesanadmindashboardthathelpsserveradministratorstomonitorserverload,concurrentconnections,andsoon.
BinariesareavailableforUbuntu,RedHat,CentOS,andSUSEEnterprise;forotherdistributions,itispossibletobuildfromthesource.Thefreeversionis,inmyexperience,stableandwell-featured.Installationdetailscanbefoundatrstudio.com/products/shiny/download-server/.
Followtheinstructionsmentionedpreviouslytoinstall,andusingthedefaultconfiguration,youshouldbeabletonavigatetoatestShinyapplicationbygoingtochrisbeeley.net:3838/shiny/01_hello/inawebbrowser(replacethedomainwithyourownURL).InorderforShinyServertowork,youneedtoopentherelevantport(inthiscase,thedefaultconfiguration,3838)onyourfirewall.Bydefault,applicationsarerunfromfileslocatedwithin/srv/shiny-server.Youcanincludedirectorieswithinthisfoldertoorganizeyourapplications.Theadministrator'sguide,whichislinkedtoandfromthedownloadpage,includesdetailsofhowtoconfigureShinyServer.
YoumaywishtochangetheportthroughwhichShinyservercommunicates(again,openingthisportonyourfirewall),changethelocationofapplicationfiles,oraddseverallocationstoapplicationfiles,orsomethingelseentirely.Thecompletedetailsareavailableinthedocumentation.InstallationonUbuntuisembarrassinglyeasy;evenwithmylimitedknowledgeofrunningLinuxservers,Ihaditupandrunningonmypersonalserverinlessthananhour.It'srunquitehappilyeversince.Mileagewithotherdistributionswillvary,althoughjudgingfromforumandblogposts,peoplehavesuccessfullyrunitonavarietyofdistributions.Dependingonwhatyouaredoingwithyourapplication,onething
tobecarefulofisdirectoryownershipandpermissions.
Forexample,oneofmyapplicationsproducesPDFfilesfordownload.ThisrequiresmakingShinytheownerofthedirectorywithintheapplicationfolder,whichhousesthetemporaryfilesthatareproducedfordownloadandmakingthedirectorywritable.Inacorporateenvironment,youmayalsofindthattheportShinyusesisblockedbythefirewall—changingtoadifferentportissimplyamatterofeditingtheconfigurationfileasdetailedontheShinyserverwebpagegivenpreviously.IfyouareinacorporateenvironmentrunningWindows,it'sworthnotingthattheopenversionrunsfineonanUbuntuservervirtualizedonWindows,inmyexperience.Icouldn'tspeakforthepaidversion,andI'msureRStudiowouldbehappytoadviseyouifyouwerethinkingaboutpayingforalicense.
RunningShinyapponAmazonAWSWehavethoroughlydiscussedhowtodevelopShinyapps.Nowit'stimetodiscussdeployingtheShinyapponAmazonAWS.Thedeploymentoftheapplicationisnecessarytomakeitavailableforusers.TodeployitonAmazonAWS,wehavetofollowthesesteps:
1. RegisterwithAmazonAWSandoptforEC2.Amazonprovidesafreesubscriptionforwhichtheuserhastoprovidecreditcarddetails.Suchschemesaresubjecttochangeaspercompanypolicy.
2. Logintoyouraccount,gotoAWSservice,andthentocomputeandfindEC2.ClickonEC2andadashboardwillappear.ClickonLaunchInstanceanditwilldiverttochooseAmazonMachineImage(AMI).SupposewehavechosentheUbuntuserver.Nowit'stimetochoosetheinstancetype,whichcanbet2.small,t2.micro,t2.large,andmore.Clickonthelaunchbuttonandanotherwindowwillcomeforreviewlaunch.Reviewandclickonlaunch.
WecanalsoconfiguresecuritybyConfigureSecurityGroup.InthisSSHrowcanbechangetoMYIP.Clickonaddrule,andaddacustomTCPrule.Underportrange,changeitto3838,whichistheportforShinyserver.
Afterthis,clickonreviewandlaunchandyouwillgetadialogueboxforgeneratingaprivatekey.Here,wehavetogiveanametothekeyandclicktodownloadthekeypair.Afterthat,wewillgetthe.pemfile.Saveitsecurely.ClickonlaunchandgettheEC2instancerunningsuccessfully.CopytheIPaddressavailableundertheDNS(IPv4).
3. AccesstheEC2thatwecreated.Downloadputty,andconvertthe.pemfileintoppk.ClickonPuttygen,thenFiletab,andloadtheprivatekey.Navigateto.pemandimportit.Savetheprivatekeywithanametoyourdesiredlocation.Afterdoingallthis,wehaveour.ppkfile.Openputty,andinthehostnamebox,entertheIPaddressoftheEC2instance.NavigatetoAuthfor
authenticationandimporttheppkkeythatwecreated.ClickonOpenandenteryourcredentials.
4. DownloadWinSCP,typetheEC2IP,andclickonadvance.UnderSSHAuthentication,entertheprivatekeyandclickonOk.EnternameasIPaddressandusernameandpassword.ClickYesinthedialogueboxthatappears.
5. Gototheroot.Gotothepromptandtypesudo-i,thenwewillgetthe#symbol.Runthefollowingcommands:
sudoapt-getupdate
sudoapt-getinstallr-base
sudoapt-getinstallr-base-dev
sudosu - -c"R-e\"install.packages('shiny',repos='http://cran.rstudio.com/')\""
ToinstallShinyserver,runthefollowingcommand:
wgethttps://download3.rstudio.org/ubuntu-12.04/x86_64/shiny-
server-1.4.4.807-amd64.deb
sudodpkg-ishiny-server-1.4.4.807-amd64.deb
6. Wecanseethefoldershiny-serverinthe/srv/shiny-server/path.Executethefollowingcommands:
sudochmod777/srv/shiny-server
sudomkdir/srv/shiny-server/Myapp_components
Intheprecedingcommand,theMyapp_componentsfolderiscreatedtosavethevariousshinyappcomponents,suchasserver.Randui.R.
7. AswehavealreadyinstalledandconfiguredwinSCPandcreatedtheMyapp_componentsfolder,wearereadytosendthecomponentstotheEC2instance.Configuretheshiny-server.conffile,whichislocatedin/etc/shiny-server/.Executethefollowingcommand:
sudochmod777/etc/shiny-server
Nowwecancopyconfigfileontothelocalsystem,editit,andsaveitback.
8. GototheAmazonconsoleandfindtherunningEC2instance.CopythepublicDNS,ec2-34-215-115-68.us-west-2.compute.amazonaws.com,appenditwith
3838/Myapp_components,andpastethisinyourbrowser'sURLbar.PressEnterandyourappshouldstartrunning.
Scoping,loading,andreusingdatainShinyapplicationsAlthoughloadingandreusingdatainShinyapplicationsiscoveredinthischapter,becauseitislikelythatthesefeatureswouldbeusedinashinyapps.io-hostedapplication,muchofitalsoappliestolocally-runShinyapplications.Ifyouuseshinyapps.io,itwillexpectyourapplicationtobeportable,thatis,toavoiddependenceonwritingpermanentchangestothelocalfilesystem.Thisisbecausetheapplicationmightbemovedtoanotherserverforload-balancingpurposes,renderingchangestothepreviouslocalfilesysteminaccessible.Youcanwritetemporaryfileswhileauserisconnectedtotheapplication(forexample,iftheuseruploadstheirowndata,thiscanbesavedtemporarily),butanychangesmadewillbelostwhentheuserexits.
DependingontheenvironmentinwhichyouarerunningandthetaskyouarecarryingoutwithyourShinyapplication,itisusuallyagoodpracticeinmostcasestomakeallShinyapplicationsportable.Bymakingtheapplicationportable,youcannotonlyseamlesslyswitchtoshinyapps.io(evenifitisjusttoshareabetaversionwithcolleaguesusingafreeaccount),butitalsomeansthattheapplicationisportableacrossothercontexts;forexample,ifyoudistributeitviaa.zipfile,changeyourcomputer,ormigratetheserveronwhichyourunShinyServer.Itisimportant,therefore,tounderstandthescopingofdatawithinShinyapplicationsaswellasthemeansofgettingdatainandout,bothtemporarilyandpermanently.
Temporarydatainput/outputTherearethreelevelsofscopingwithinthetemporary,portablepartofaShinyapplication:
Thelowestlevelisdatathatisread-onlywithineachindividualinstanceofaShinyapplication.Thislevelisquiteusefulifauserwantsafreshcopyofdataeachtimetheyvisittheapplication(ifdataneedstobeevenfresherthanthis,itcanbeplacedinareactivefunction).AnydataloadedaftertheshinyServer()functionwillbescopedlikethis.Notethatthisdataisonlyavailablefromtheserver.Rfileandnotfromtheui.Rfile,whichisloadedfirst.Thenextlevelupisdatathatisavailabletoallinstancesoftheapplication(again,justwithinserver.R).Thiscanbeusefulifthereisaverylargedatasetthatneedstobeloadedorthatneedssignificantprocessing;thiscanthenbedonethefirsttimetheapplicationspinsup,sousersdonothavetowaitforit.AnydataloadedbeforetheshinyServer()functionwillbescopedinthisway.Lastly,itispossibletomakedataavailabletoui.Randserver.Racrossallfunctionsbyloadingitinafilecalledglobal.R.Itisn'tveryoftenthatyouwouldwanttodothis;Ineverhave,butyoumayfinditusefulifyouwishtoconfigureyourUIusingdatabutdon'twantorneedtheextracodeandprocessingtimeadynamicUIwouldnecessitate.
RememberthatitisveryeasytogetdatainandoutofShinysessions(thatis,temporarily)usingthefileInput()anddownloadHandler()functions.
PersistentdatastorageIndatascienceproductdevelopment,oneofthemostimportantstepsistobringdatafromvarioussourcesandkeepitonstoragesystems.Mostly,data-storagemanagementfordatascienceprojectsisdonewithadatawarehouse.Nowadays,varioustechnologieshavebeendevelopedtostoreandprocessvarioustypesofdata,whichcanbestructured,semi-structured,orunstructured.UsingHadoop,HDFS,Hive,MongoDB,SQLite,orMySQL-liketoolsandtechnologiesarecoupleduptodevelopanecosystemtomakeforeasyavailabilityandfastprocessingofdata.
Innormalsoftware,thedatasourcesareusuallyRDBMSandmeanttodealwithonlinetransactionalrequirements.Butindatascienceprojects,thescenariosarequitedifferent.Here,generallyhistoricaldataisusedtopresentgraphsorgeneratereports.AndsinceShinyisalsoconsideredatooltopresentdata,itislesslikelytoexpectthatitsappswillacceptdatafromusersallthetime.Butthiscasealsoneedstobeconsidered.Inthissection,wewilldiscussthepersistentdata-storageoptionsavailablewithshinyappsandinwhatsituationstheyaregoingtobeused.
Asofnow,whateverShinyappswehavedevelopedareusinginbuiltdata.Suchdataiskeptlocallywheretheappresides.Forverysmalldata,thistechniquecanworkforsometime.Butasthedatagrows,theappwillbeunmanageable.Insuchscenarios,weneedtolookforotheroptionsthatcanhandlethegrowingdemandofdata.Today'sworldhassurpassedthebigdatalimits.So,whataretheoptionsavailable?
Let'sdiscusssomeofthepersistentdata-storageoptionsavailable:
Localfilesystem:Thisisaveryeasywaytostoredatawheretheappresides.Ifwehaveconfiguredtheshinyserverin-houseforthedeploymentoftheapp,wecantakesomememorytosavethedataacceptedfromuserorgeneratedduringtheexecution.Withthismethod,datacanbesavedandaccessedveryfast.Supposewewanttosavedataintoadataframeandkeepaddingdataintothistable.Wecanfollowsomeeasystepsand
developourapp:
1. WithanewRscriptfile,createadataframe:
NewDF<-data.frame("Sn","Name","Age")
Aftertheexecutionoftheprecedingcode,NewDFdataframewillbereadytouse.
NowifwewanttoadddatatothisNewDF,wehavetowritethefollowingsimplecode:
#insertdatainfirstrow.
NewDF[1,]<-c(1,"Rahul",25)
#insertdatainsecondrow
NewDF[2,]<-c(2,"Sumit",26)
2. IntheNewDF[1,]code,thefirstpartofthesubscripthastherownumberandthesecondcolumnnumber.NewDF[1,]isreferringtothefirstrowandallthecolumns.Similarly,NewDF[2,]referstothesecondrowandallthecolumns.Inthisway,datacanbeadded,modified,anddeletedwiththehelpoftheshinyapp.Andatthesametime,dataisresidingonapersistentstorage.Thisisoneofthemostimportantmethods,becausewhicheverecosystemisfollowedfordata-warehousing,somepartofthedatahastobekeptonShinylocalstorageforprocessing.
Dropbox:Dropboxisaverypopularstoragesystemavailableremotely.Itsupportsavarietyoffileformats.rdrop2isapackagetoaccessDropboxfromR.Itprovidesfunctionsforlistingfiles,copying,moving,anddeleting.ToaccessDropbox,wehavetofirstcreateanaccount.Thebasicsubscriptionisfreeandprovides2GBspaceforuse.Pleasevisithttps://www.dropbox.com/individualforabasicsubscription.TousedatafromDropbox,weneedtogothroughtheauthenticationprocessfirst:
library(rdrop2)
drop_auth()
Afterexecutingtheprecedingcode,itwilllaunchthebrowserandaskforyourDropboxaccountdetails.Wehavetologintoouraccount.Oncethisprocessisdone,closethebrowserandcompletetheauthenticationfromR.Thecredentialscanbeautomatically
cachedandusedinfuture.Wecanalsosavethetokenusingthefollowingcode:
token<-drop_auth()
saveRDS(token,file="token.rds")
Wecanalsoretrieveouraccountinformationusingthedrop_acc()%>%data.frame()command.MostofthecommandstoaccessDropboxfromRstartwithdrop_likedrop_acc().Fordirectory-listing,drop_dir()canbeused.IfyouwanttouploadafiletoDropbox,wecanusethefollowingcode:
write.csv(newFile,"newfile.csv")
drop_upload("newfile.csv")
Inasimilarway,drop_create(),drop_download(),drop_delete(),drop_move(),anddrop_copy()canalsobeused.AllthesediscussedfunctionscanbeusedwithRShinyapps.
AmazonAWSS3:Apopularplatformforfilehosting.Itkeepsfilesinbuckets.aws.s3isthepackageprovidedwithRtoaccessAWSS3.Theaws.s3packageisnotavailableonCRAN.Thefollowingcodecanbeuseforinstallation:
#lateststableversion
install.packages("aws.s3",repos=c("cloudyr"=
"http://cloudyr.github.io/drat"))
#onwindowsyoumayneed:
install.packages("aws.s3",repos=c("cloudyr"=
"http://cloudyr.github.io/drat"),INSTALL_opts="--no-multiarch")
Tousethispackage,weneedtocreateanaccountwithAmazonAWSforS3.Afreeaccountcanalsobecreatedwithlimitedaccessforoneyear.Oncewegetregistered,wecangeneratekeypairsontheIAMManagementwindowwiththeheadingaccesskey.Youcanalsovisithttps://github.com/cloudyr/aws.signature/formoredetaileddescriptionsofthecredentials.ItcanalsobedonethroughRusingthefollowingcode:
Sys.setenv("AWS_ACCESS_KEY_ID"="mykey",
"AWS_SECRET_ACCESS_KEY"="mysecretkey",
"AWS_DEFAULT_REGION"="us-east-1",
"AWS_SESSION_TOKEN"="mytoken")
Aftersettingcredentials,wecanaccessbucketsusingthefollowingcode:
"library("aws.s3")
bucketlist()
Wecanalsoaccesspublicly-listedbuckets:
bucketlist()get_bucket(bucket='1000genomes')
TogetalistofalltheobjectsintheprivateS3bucket,thefollowingcodeisuseful:
#specifykeys
get_bucket(
bucket='my_bucket',
key=YOUR_AWS_ACCESS_KEY,
secret=YOUR_AWS_SECRET_ACCESS_KEY
)
#specifykeysasenvironmentvariables
Sys.setenv("AWS_ACCESS_KEY_ID"="mykey",
"AWS_SECRET_ACCESS_KEY"="mysecretkey")
get_bucket("my_bucket")
Theaws.s3packagehasmanyfunctions.Someofthemareasfollows:
bucketlist():Providesadataframeofusers'buckets.get_bucket():Providesalistanddataframe,respectively,ofobjectsinagivenbucket.get_bucket_df():Providesalistanddataframe,respectively,ofobjectsinagivenbucket.object_exists():Providesalogicaloutputforwhetheranobjectexists.s3read_using():ProvidesagenericinterfaceforreadingfromS3objectsusingauser-definedfunction.get_object():ReturnsarawvectorrepresentationofanS3object.s3connection():ProvidesabinaryreadableconnectiontostreamanS3objectintoR.SQLite:Alightweightdatabaseengineavailablepublicly.Itsupportsstructureddata-management.Wecancreatetablesandperformupdate,deletion,andinsertionoperationsonit.Italsosupportstransactionmanagement.
ToembedSQLiteintoR,theRSQLitepackageneedstobeinstalled.ItisavailableonCRAN:
install.packages("RSQLite")
AconnectiontoSQLitecanbecreatedusingthefollowingcommand:
library(DBI)
con<-dbConnect(RSQLite::SQLite(),":memory:")
dbListTables(con)
Usingtheprecedingconnectionobject,con,wecanwritethedatasetinSQLite,asshowninthefollowingsnippet:
dbWriteTable(con,"iris",iris)
dbListTables(con)
Tofetchthecolumnsofthetable:
dbListFields(con,"iris")
Andtofetchtheentiretable:
dbReadTable(con,"iris")
Afterusingoftheconnection,wemustcloseitusingdbDisconnect(con):
MySQL:MySQLisaverypopularRDBMSdatabase-managementtoolavailablepublicly.ItissimilartoSQLitebutmorepowerful.Itcanbehostedlocallyorremotely.WecanusetheRMySQLpackagetointeractwithMySQLandR.ThepackagecanbedownloadedfromCRAN:
install.packages("RMySQL")
ToinstallintheLinuxplatformorOSX,youneedtheMariaDBConnector.RMySQLactsasaninterfacebetweenRandMySQL:
library(RMySQL)
library(DBI)
con<-dbConnect(RMySQL::MySQL(),group="my-db")
Inthefollowingcode,dbConnect()isthefunctiontoestablishaconnectionwiththeMySQLdatabasefromR.TheconobjectcannowbeusedtoperformvariousoperationsonDatabase.Let'sseesomeofthem:
dbListTables(con)
Theprecedingcodeliststhetablesinthedatabase.TowriteatablefromRintoMySQL,usethefollowingcode:
dbWriteTable(con,"iris",iris)
dbListTables(con)
Wecanalsofetchtheentiretablefromthedatabaseasfollows:
#Youcanfetchallresults:
res<-dbSendQuery(con,"SELECT*FROMiris")
dbFetch(res)
Todisconnecttheconnection,usethefollowingcode.
dbDisconnect(con)
GoogleSheets:Googlesheetsisalsoagoodoptionforstoringdata.Itispersistentandaccessiblefromanywhere.Googlesheetscanbeaccessedusingatitle,key,orURL.Datacanbeeasilyextractedoredited.Wecancreate,delete,rename,copy,anduploadGooglesheets.Wecanalsouploadlocally-generatedspreadsheetsintoGooglesheetsandviceversa.
TouseGooglesheetsfromR,wecanusethegooglesheetspackage.Itisdevelopedtobeusedwiththe%>%pipeoperator.Itusesthedplyrpackageinternally.ItcanbedownloadedfromCRAN:
install.packages("googlesheets")
Withthispackage,ademosheetforpracticeisavailable,namedGapminder.Let'susesomecodetoplaywiththissheet:
gs_gap()%>%
gs_copy(to="Gapminder")
Thiscodeisforthecopyingofthesheet.Toviewthesheetinthebrowser:
gap%>%gs_browse()
gap%>%gs_browse(ws="Europe")
Toreadallthedataintheworksheet,wecanusethefollowingcode:
africa<-gs_read(gap)
glimpse(africa)
Africa
Wecanalsotargetspecificcells:
gap%>%gs_read(ws=2,range="A1:D8")
Wecanalsocreatenewspreadsheets:
giris_ss<-gs_new("iris",input=head(iris,3),trim=TRUE)
WecanexploremoreaboutgooglesheetsandRathttps://github.com/jennybc/googlesheets#install-googlesheets.
DatabaseusingDplyr,DBI,andPOOLInthissection,wewilllearntousethedplyrpackagetoaccessdatafromdatabasesources.WewillalsoseehowtohookuptoanexternaldatabaseusingtheDBIpackage.ThePoolpackageisalsoanimportanttopictomanageconnectionsandpreventleakstomanageperformance.
dplyr:Apopulardata-manipulationpackageforinternalandexternaldatabases.ItinternallyworksasSQL.Itprovidesavarietyoffunctionsfordatamanipulation:
filter()
select()
arrange()
rename()
distinct()
mutate()
transmute()
summarise()
sample_n()
sample_frac()
Let'sseeanexampleusingsomeofthesefunctionswiththeirisdataset:
library(dplyr)
iris%>%filter(Sepal.Length>4&Sepal.Length<5)
Intheprecedingcode,thefilterfunctionhasbeenusedtofiltertherowsoftheirisdataset,whichhasvaluesbetween4and5.Wecanalsoselectonlycertaincolumns,usingselect():
iris%>%select(Sepal.Length)
Forselectingdistinctvalues,wecanusedistinct():
iris%>%distinct()
Wecanalsousethesefunctionsincombination.Thecodeshownhereisfirstfilteringthedataandthenapplyingselect():
iris%>%filter(Sepal.Length>4&Sepal.Length<5)%>%select(Sepal.Length)
Inthisway,wecangetrideofusingRandSQLseparatelyusingdplyr.
DBI:AcommoninterfacebetweenRandDBMS.Italsoprovidesavarietyoffunctionsforsupportingthefollowingfunctions:
ConnectinganddisconnectingtotheDBMSCreatingstatementsandexecutingintheDBMSExtractingresultsfromstatementsException-handlingTransactionmanagement
dbGetQuery()isaconvenientwayofexecutingquerieswithaconnectionobject.Ittakestwoargumentsimportantlyconnectiontodatabaseandquery:
dbGetQuery(conn,"SELECT*FROMCityLIMIT5;")
Pool:WiththeDBIpackage,therearesomeproblemswithconnection-managementandperformance.Wehavetocreateanddestroyconnectionsasandwhennecessary.Otherwise,therewillbeanaccumulationofleakedconnections.Thisleakedconnectionholdsresourcesthatmusthavebeenfreed.Becauseofthisapp,theperformancegoesdown.Fordealingwithsuchproblems,poolpackageisavailable.Itprovidesanextralayerofabstractionwhileestablishingaconnection.Usingthispackage,wecancreateanobjectwithareferencetothedatabase.Thisobjectiscalledpool.Thismakesusavoiddirectlyfetchingthedatabase.Also,thispoolobjectcanholdanumberofconnectionstothedatabase.Wheneverwearequeryingthedatabase,weareactuallyqueryingtothepoolthatholdstherunningandwaitingconnections.
Thepoolcanprovideuswithidleconnectionsthatwerepreviouslyfetchedorwithanewone.Oncetheconnectionisended,thegarbage-collectionprocessmakesfreeallresourcesheldbytheconnection.So,inthecontextoftheShinyapp,wedon'thavetoworryaboutendingtheconnection.
ThepoolpackageisavailableonGitHubandcanbedownloadedasfollows:
devtools::install_github("rstudio/pool")
Let'sseetheskeletonofaShinyappwithpool:
library(shiny)
library(DBI)
library(pool)
pool<-dbPool(
drv=RMySQL::MySQL(),
dbname="Database_Name",
host="Host_link",
username="username",
password="password"
)
ui<-fluidPage(
#--------Uistuff----------------------
)
server<-function(input,output,session){
#----ServerStuff-------------------
}
shinyApp(ui,server)
WithsqlInterpolate(),wecancreateaquerythatcanbetheinputtodbGetQuery().InsqlInterpolate,wecanseethreeparametersinthefunction:pool,sql,andid.poolistheobjectandsqlistheSQLquery.IDcanbeanyinput.dbGetQuery()willhavetwoparameters.Thepoolobjectandqueryarecreatedusinginterpolate:
query<-sqlInterpolate(pool,sql,id=input$ID)
dbGetQuery(pool,query)
Thisappskeletonisforasingle-fileapp.Foramulti-fileapp,itcanbeputonthetopoftheserveranduifileorinaglobalfile.
SQLInjectionSQLInjectionisakindofattackdonebyaddingSQLquirestotheURLoftheapplication.SuchqueriesexecuteontheDBMSwithouthavinglegitimateaccesstoit.Suchattacksarepossibleiftherearesomebranchesintothecode.Let'sseesomecodetounderstanditbetter:
dbGetQuery(conn,paste0("SELECT*FROMCityLIMIT",input$nrows,";"))
Aswecanseeintheprecedingcode,input$nrowshasbeenputdirectlyintothequery.Ifanattackergotaccesstothisinput$nrows,theycouldinjectanySQLstatementintoit.Inthiscase,thesolutioncanbetopreventanattackerfrompassingvectors.So,thecodecanbemodifiedasfollows:
dbGetQuery(conn,paste0("SELECT*FROMCityLIMIT",as.integer(input$nrows)[1],";"))
Theinputisconvertedintoanintegerfirst.So,ifanattackerputssomeSQLintoit,itwillgetconvertedintoanintegerandloseitsmeaning.Thisisaneasyexample.Let'sdiscussamorecomplexsituation:
query<-paste0("SELECT*FROMCityWHEREID='",input$ID,"';")
Inthiscode,ifanattackergetsaccesstoinput$IDandmanagestomodifythevalue,theycangetthedataofasinglecity.Forexample,input$IDchangedto5meansthatthedataofthecitywithID=5willbeinvoked.Butiftheyaretryingtogetinformationofallthecities,theycantryOR1=1ORandcangetdataofallthecity.Suchinputmakestheconditionalwaystrue.ThesolutiontothisattackistousesqlInterpolate().ItcanbeusedtointerpolatethevaluesintoaSQLstring,whichpreventsaSQLinjectionattack:
sql<-"SELECT*FROMCityWHEREID=?id;"
query<-sqlInterpolate(conn,sql,id=input$ID)
Intheprecedingcode,ifwechangetheinputto'OR1=1OR',itwillbeconvertedintoSELECT*FROMCityWHEREID='''OR1=1OR''',whereithasaddedextra('').ThiswillgiveblanktableoutputandpreventsSQLinjection.Instead,ifweuseinput$ID=6,itwillchangethequerytoSELECT*FROMCityWHEREID='6',whichisavalidID.SotheoutputwillbethedataofthecitywithID=6.
Thisprocessofcheckingandconvertinguserinputintosafevaluesiscalledsanitization.WemustalwaystakecaretosanitizetheuserinputtopreventSQLinjectionattacks.
SummaryInthischapter,welearnedaboutseveralmethodstoshareyourShinyapplicationswiththeworld.ThisprocessisveryeasywithfellowusersofR,andalittleharderwiththewholeinternet;howeveryoudoit,I'msureyou'llagreethatitwasrelativelypainlessandworththeeffort.WediscussedhowtouseGitandGitHub(andGist),andhowtousethemtoshareyourcodeandapplicationswithotherRusers.WealsolookedatdistributingShinyapplicationsmanuallyoroverFTPtoRusersusingthe.zipand.tarfiles.Wecoveredhostingsolutionstoshareyourapplicationwiththewholeinternet,includingShinyapps,ShinyServer,andAmazonAWS.Wewentthroughthepersistentdata-storageoptionsandhowtousethedplyr,DBI,andPoolpackages.WealsosawhowtopreventSQLinjectionattacks.
OtherBooksYouMayEnjoyIfyouenjoyedthisbook,youmaybeinterestedintheseotherbooksbyPackt:
RDataVisualizationRecipesVitorBianchiLanzetta
ISBN:9781788398312
GettoknowvariousdatavisualizationlibrariesavailableinRtorepresentdataGenerateelegantcodestocraftgraphicsusingggplot2,ggvisandplotlyAddelements,text,animation,andcolorstoyourplottomakesenseofdataDeepenyourknowledgebyaddingbar-charts,scatterplots,andtimeseriesplotsusingggplot2BuildinteractivedashboardsusingShiny.ColorspecificmapregionsbasedonthevaluesofavariableinyourdataframeCreatehigh-qualityjournal-publishablescatterplotsCreateanddesignvariousthree-dimensionalandmultivariateplots
DataAnalysiswithR-SecondEditionTonyFischetti
ISBN:9781788393720
GainathoroughunderstandingofstatisticalreasoningandsamplingtheoryEmployhypothesistestingtodrawinferencesfromyourdataLearnBayesianmethodsforestimatingparametersTrainregression,classification,andtimeseriesmodelsHandlemissingdatagracefullyusingmultipleimputationIdentifyandmanageproblematicdatapointsLearnhowtoscaleyouranalysestolargerdatawithRcpp,data.table,dplyr,andparallelizationPutbestpracticesintoeffecttomakeyourjobeasierandfacilitatereproducibility
Leaveareview-letotherreadersknowwhatyouthinkPleaseshareyourthoughtsonthisbookwithothersbyleavingareviewonthesitethatyouboughtitfrom.IfyoupurchasedthebookfromAmazon,pleaseleaveusanhonestreviewonthisbook'sAmazonpage.Thisisvitalsothatotherpotentialreaderscanseeanduseyourunbiasedopiniontomakepurchasingdecisions,wecanunderstandwhatourcustomersthinkaboutourproducts,andourauthorscanseeyourfeedbackonthetitlethattheyhaveworkedwithPackttocreate.Itwillonlytakeafewminutesofyourtime,butisvaluabletootherpotentialcustomers,ourauthors,andPackt.Thankyou!