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

0. 前言

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

产品全型如下图所示:


数据产品监控平台

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标题,右侧有一个显示/隐藏按钮;然后控件都是从上到下排列。

导航栏

2.2.2 核心指标看板

核心指标看板

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

2.2.3 核心指标趋势

核心指标趋势

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

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

报表&核心用户排行分布

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

2.2.5 访问明细追踪

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


访问明细追踪

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,里面可以装看板、文字、数字、图形、表格等元素,取决于前后端接口传输的是什么。

ui的架构
3.1.2 前后端接口

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

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

交互流程

在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显著高于其他人,没错这个人就是我),想观察哪些用户不活跃,这些不活跃用户间有多大程序的差异,在下面的图中并不能一目了然。

整体核心用户排行

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


长尾用户分布

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. 参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容