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,里面可以装看板、文字、数字、图形、表格等元素,取决于前后端接口传输的是什么。
3.1.2 前后端接口
shiny作为一款交互式平台开发框架,与用r markdown开发静态html最主要的区别就在于交互性,即是用户可以通过前端界面做一些操作,然后转化成逻辑条件,传给服务端进行计算,然后服务端将交互计算的输出结果再传回前端展示。所以这个流程和代码里都会涉及到input
和output
部分。
举一个简单的例子,对于一张交互式表格,当用户在一个下拉框中选择了某一个维度的值进行切片,则交互流程如下图:
在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的可视化包,如highcharter
、rCharts
、Recharts
等。但大道至简(一招鲜吃遍天),我们用万能的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. 参考资料
- shinyWidgets:数据平台前端交互控件集
- Shiny Gallery
- dashboardthemes
- shinyWidgets