基于shinydashboard开发的数据产品监控平台

0. 前言

这是本人第3个基于shinydashboard开发的数据产品(前2个见基于R shinydashboard的道路交通可视化案例、比特币智能对冲策略平台);相对于第1篇的炫技、第2篇的神叨,这一个具备相对最为完整的产品逻辑、最为简洁(不为了show什么而做什么),同时也是最为实用的产品。此文从产品、数据、开发的角度来总结一下经验。

产品全型如下图所示:


基于shinydashboard开发的数据产品监控平台_第1张图片
数据产品监控平台

1. 背景

对于产品而言,数据是既是其脉象,也是其指南;对于数据产品而言亦然。数据产品模块(如报表)不能仅仅只是交付后就完事,还必须要作后续数据监控,至少有以下几点意义:

  • 了解当前的业务重点,业务人员主要在关注什么数据
  • 了解当前业务的变化趋势,哪些业务数据访问增长,哪些减少
  • 了解每一条业务线报表的核心用户,这些人决定了这些业务线的数据需求
  • 数据产品收益评价,哪些有较高的价值,哪些较小。如果收益较小,是数据产品的问题还是数据需求本身的问题?

2. 产品设计

数据来自于后端同学的日志数据,尽管他们有现成的平台可监控,但一方面非结构数据的查询并不是太简单方便,另一方面自己想要的一些逻辑计算并没有现成的模块可直接查询——考虑成本和收益,主要是我一个人在使用,不便占用开发资源——所以就自己写一个平台。

2.1 指标体系

日志数据就4个字段(日期、用户、报表、访问量),所以翻来覆去也就那几点排列组合的指标。

类型 字段 口径 备注
维度 日期(visit_date) 衍生日、周、月粒度数据
报表(report_id)
用户(visit_user)
指标 PV SUM(visit_num)
UV COUNT(DISTINCT CASE WHEN visit_num >0 THEN visit_user END)
访问报表数 COUNT(DISTINCT CASE WHEN visit_num >0 THEN report_id END)
表均PV PV/访问报表数 单位报表的查询深度
表均UV UV/访问报表数 单位报表服务广度
人均PV PV/UV 单位用户的查询深度

2.2 产品模块

在这个平台中,核心的数据模块主要有以下模块:

2.2.1 导航栏

dashboard必备元素。shinydashboard默认的导航栏布局左侧,左上方为dashboard标题,右侧有一个显示/隐藏按钮;然后控件都是从上到下排列。

基于shinydashboard开发的数据产品监控平台_第2张图片
导航栏

2.2.2 核心指标看板

基于shinydashboard开发的数据产品监控平台_第3张图片
核心指标看板

根据筛选条件计算的核心指标,以看板(big number)的形式一眼对报表访问现状有一个非常直观的认识。

2.2.3 核心指标趋势

基于shinydashboard开发的数据产品监控平台_第4张图片
核心指标趋势

核心指标的序列波动趋势,作整体监控用,如果核心指标有明显的非周期性波动趋势,意味着需要找业务方聊聊了。

2.2.4 报表&核心用户排行分布

基于shinydashboard开发的数据产品监控平台_第5张图片
报表&核心用户排行分布

无论是业务数据报表,还是核心用户,一定是符合二八定律的:真正的核心始终只是一小部分。通过排行分布分析,可以直接观察到哪些业务报表,哪些用户是真正的核心,从而为需求管理(哪些业务线的需求须尽力支持,哪些则可以往后拖)、需求挖掘(应该找哪些人聊业务和数据,挖掘抽象出产品化需求)提供指引。

2.2.5 访问明细追踪

报表访问记录明细,可以通过搜索,快速追踪某一个报表或者某一个用户的访问明细,作定向观察。


基于shinydashboard开发的数据产品监控平台_第6张图片
访问明细追踪

3. shinydashboard开发仪表盘

shinydashboard是一套为数据分析师快速搭建数据产品的框架,与常规的数据平台不同,它可以不需前后端分别开发以及联调的过程,一个人(通过各种调包)就能快速搭建出一个MVP出来(比如这个平台的开发调试只用了一个周末);同时与开发语言相对,亦能充分发挥R统计建模的优势。

shiny分2部分:ui.R(对应前端结构)和server.R(对应后端逻辑)。ui.R里定义前端部分,包括页面结构、交互效果、读取后端接口展示内容;server.R里则主要进行数据连接、数据处理、数据建模与计算、数据可视化,然后将结果传输给前端展现。

3.1 前端ui.R

此产品中ui.R代码如下,除了调包和括号部分,核心代码不超过50行;R作为一门高级(调包)语言,其书写简洁的优势在这里体现得淋漓尽致。

library(RMySQL)
library(shiny)
require(shinydashboard)
library(shinyWidgets)
library(highcharter)
library(dplyr)
library(dashboardthemes)
library(highcharter)
library(stringr)
library(DT)
library(xts)
library(ggplot2)
library(plotly)
library(ggthemes)

ui <- fluidPage(
  dashboardPage(
    dashboardHeader(title = iconv("数据产品访问监控",to = "UTF-8"),titleWidth = 260),
    dashboardSidebar(
      dateRangeInput(label = iconv("日期范围",to = "UTF-8"),inputId = "date_range",start = Sys.Date()-30,end = Sys.Date(),max = Sys.Date(),language = "zh-CN"),
      sidebarMenu(
        menuItem(iconv("公共报表",to = "UTF-8"), tabName = "report", icon = icon("chart-area")),
        menuSubItem(icon = NULL,textInput(inputId = "report_search",label = iconv("搜索报表id或名称",to = "UTF-8")))
      ),width = 260),
    dashboardBody(
      shinyDashboardThemes(
        theme = "grey_light"
      ),
      tabItems(
        tabItem("report",
                fluidRow(
                  valueBoxOutput("total_report_num",width = 4),
                  valueBoxOutput("total_report_pv",width = 4),
                  valueBoxOutput("total_report_uv",width = 4),
                  valueBoxOutput("pv_per_report",width = 4),
                  valueBoxOutput("uv_per_report",width = 4),
                  valueBoxOutput("pv_per_uv",width = 4)
             ),
                fluidRow(
                  tabBox(width = 12,title = "报表访问核心指标趋势",
                    side = "right", 
                    selected = "报表PV",
                    tabPanel("人均PV", highchartOutput("report_pv_per_uv")),
                    tabPanel("表均UV", highchartOutput("report_uv_per_report")),
                    tabPanel("表均PV", highchartOutput("report_pv_per_report")),
                    tabPanel("报表数", highchartOutput("report_num")),
                    tabPanel("报表UV", highchartOutput("report_uv")),
                    tabPanel("报表PV", highchartOutput("report_pv"))
                  )
              ),
             fluidRow(
               tabBox(width = 12,title = "报表访问排行",
                      side = "right", 
                      selected = "PV排行",
                      tabPanel("核心用户排行",plotlyOutput("report_rank_user")),
                      tabPanel("人均PV排行", plotlyOutput("report_rank_pv_per_uv")),
                      tabPanel("UV排行", plotlyOutput("report_rank_uv")),
                      tabPanel("PV排行", plotlyOutput("report_rank_pv"))
               )),
              fluidRow(
                box(title = iconv("报表访问记录",to = "UTF-8"),dataTableOutput("report_visit_dataset"),collapsible = T,width = 12)
              )
        )   
      )
    )
  )
)
3.1.1 dashboard架构

对比一上面图中的产品布局,以及代码结构,可以显然发现ui.R的架构(事实上,这也是shinydashboard开发数据产品的一般架构)很简单:头部+导航栏+主体,标题在头部,抽屉菜单在导航栏中,控件既可以放在导航栏内也可以放在主体里;主体里可以设定主题(既可以调包,也可以自己连CSS;当然作为调包侠自然是拒绝自己造轮子),可以设置tab,可以赛box,里面可以装看板、文字、数字、图形、表格等元素,取决于前后端接口传输的是什么。

基于shinydashboard开发的数据产品监控平台_第7张图片
ui的架构
3.1.2 前后端接口

shiny作为一款交互式平台开发框架,与用r markdown开发静态html最主要的区别就在于交互性,即是用户可以通过前端界面做一些操作,然后转化成逻辑条件,传给服务端进行计算,然后服务端将交互计算的输出结果再传回前端展示。所以这个流程和代码里都会涉及到inputoutput部分。

举一个简单的例子,对于一张交互式表格,当用户在一个下拉框中选择了某一个维度的值进行切片,则交互流程如下图:

基于shinydashboard开发的数据产品监控平台_第8张图片
交互流程

在shiny中的伪代码则如下:

selectInput(inputId = "s", label, choices) # 前端下拉框
box(tableOutput(outputId = "tb")) # 前端表格
output_df <- df %>% filter(x == input$s) %>% reactive # 后端读前端传回的值
output$tb <- output_df() # 后端计算结果传给前端展示
3.1.3 数据可视化

数据可视化的代码其实是在server.R里,ui.R只调用可视化产出结果。不过数据可视化本来就属于前端领域,因此还是放在这里论述。

除了统计建模,数据可视化也是R强大的优势之一。R里也移植了许多优秀的javascript的可视化包,如highcharterrChartsRecharts等。但大道至简(一招鲜吃遍天),我们用万能的ggplot2+plotly即可:ggplot2保证图形结构,plotly使图形变得可交互。

plotly除了静态的ggplot2图形可hover显示数值外,还有强大的缩放功能。比如核心用户排行模块中,用户间有很明显的长尾效应(可以看到有一个人贡献的PV显著高于其他人,没错这个人就是我),想观察哪些用户不活跃,这些不活跃用户间有多大程序的差异,在下面的图中并不能一目了然。

基于shinydashboard开发的数据产品监控平台_第9张图片
整体核心用户排行

但我们通过局部缩放,就很方便观察这些长尾用户的分布。


基于shinydashboard开发的数据产品监控平台_第10张图片
长尾用户分布

3.2 后端server.R

3.2.1 交互对象

除了数据可视化部分,server.R里基本就是数据处理、统计和计算(熟练掌握R基本语法以及dplyr等数据处理包)——这是数据分析、数据开发的基本功,无需阐述。

但要注意的就是:需要区分对象是静态的还是交互的,凡是用到前端传回传值计算的变量,必须用reactive()转化成交互对象,后续调用时必须以函数形式调用()

比如上面一段伪代码,因为用到了前端传回的下拉框的交互值来计算,所以output_df必须要经过reactive()转化,后续赋值时,调用的也是output_df()

3.2.2 输出结果

后端结果必须通过以类似于render开头的接口才能传给前端,以下是上面的核心用户排行柱状图代码。report_rank_user是前端box里的outputId(对应上面ui.R代码里tabPanel("核心用户排行",plotlyOutput("report_rank_user"))那一行),renderPlotly是plotly包里将ggplot2图形转化交互图形后回调给前端的输出函数。

output$report_rank_user <- renderPlotly({
    df_report_rank_user <- df_report_visit_by_report() %>% group_by(visit_user) %>% summarise(visit_num = sum(visit_num)) %>% as.data.frame
    df_report_rank_user$visit_user <- factor(df_report_rank_user$visit_user,levels = df_report_rank_user$visit_user[order(df_report_rank_user$visit_num,decreasing = T)])
    
    bar_rank_user <- ggplot(data = df_report_rank_user,aes(x = visit_user, y = visit_num))+
        geom_bar(stat = "identity",fill = "steelblue")+
        labs(x = "",y = "pv")+
        theme_hc()+
        theme(axis.text.x = element_blank(),axis.ticks.x = element_blank(),plot.background = element_rect(fill = "transparent"),panel.background = element_rect(fill = "transparent"))
    ggplotly(bar_rank_user)
})

3.3 产品发布

shiny有2种部署方式:一种是在自己的服务器上部署,这样相对麻烦一点,不过还是强烈推荐这一种方式,因为第二种太坑;第二种就是通过Rstudio在shinyapps.io上传,这样能省掉搭建环境的成本,不过至少有3大坑:

  • shinyapps.io在国内访问速度奇慢无比,能不能打开,多久能打开得靠人品
  • 所有的中文必须以UTF-8编码转化,否则报错;当然为了彻底解决这个问题,无论是代码,还是数据里,最好压根就没有中文(R是一门对中文不友好的语言,这一点比Python3差得远)
  • 如果server.R里连接了数据库,则需要在数据库配制文件里增加rstudio服务器白名单,这样其实也很麻烦,而且还有数据安全问题

因为这个数据产品监控平台主要是我自己用,所以就不部署,每次只在本地上通过rstudio启动

4.产品价值

数据产品的价值在于降低数据获取、计算、展示的成本,提高快速数据分析,获取insight的效率——而这个数据产品是能达到这个目标的。

数据平台访问日志数据原来存储在开发人员的非结构化搜索引擎里,而非技术人员想要获取、分析这些数据,有着明显的学习和使用成本,因此这一部分数据尽管本身是有价值的,却没有得到充分利用。通过这样一个数据产品监控平台,则显著地降低了这样的成本,使我自己(当然如果部署了也包括别人)能快捷、方便地掌控数据产品使用情况,为数据产品生命周期管理,健康的新陈代谢提供了坚强的支撑和驱动。

5. 参考资料

  • shinyWidgets:数据平台前端交互控件集
  • Shiny Gallery
  • dashboardthemes
  • shinyWidgets

你可能感兴趣的:(基于shinydashboard开发的数据产品监控平台)