使用R自动化工资监控

每个自重的办公室都会定期监控工资,以便在感兴趣的劳动力市场中导航。 但是,尽管任务是必要且重要的,但并不是每个人都愿意为此支付第三方服务费用。


在这种情况下,为了使HR无需定期手动对数百个职位空缺和简历进行分类,编写一次可以自己完成的小型应用程序会更有效率,并在输出处以美观的仪表板形式提供结果,其中包括表格,图形,过滤和上传数据的功能。 例如,这:



您可以在此处现场观看(甚至按一下按钮)。


在本文中,我将讨论如何编写这样的应用程序,以及在此过程中遇到的陷阱。


问题陈述


需要编写一个应用程序,该应用程序将收集hh.ru作业数据并恢复在圣彼得堡的特定职位(后端/前端/全栈开发人员,DevOps,QA,项目经理,系统分析师等)并给出期望薪水的最低,平均和最高值,并为每个专业的中,高级和高级专业人员提供薪资。


本来应该大约每六个月更新一次数据,但是不应该每月更新一次。


第一个原型


乍看之下,它是用纯净的光泽写成的,具有漂亮的引导程序布局,几乎看不到什么:简单,而且最重要的是-可理解。 该应用程序的主页包含最必要的内容:对于每个专业,均提供薪水和期望薪水的平均值(中级),还有最后一次数据更新的日期和“更新”按钮。 标题中的标签-根据所考虑的专业数量-包含具有完整收集的数据和图表的表。



如果用户发现数据没有被太长时间更新,则可以按相应专业的“更新”按钮。 申请离开 进入无意识 想了5分钟,员工离开喝咖啡。 他返回后,等待主页和相应选项卡上的更新数据。


自检问题:此原型有什么问题?

至少,为了更新所有九个专业的数据,用户需要单击每个图块上的“更新”按钮 -如此九次。


为什么不对所有内容都做一个“更新”按钮? 事实是-这是第二个问题-对于每个请求(“有关管理者的更新和处理数据”,“有关QA的更新和处理数据”等)花费5-10分钟 ,这本身是不允许的很久了 一次更新所有数据的请求将5分钟变成45分钟,甚至全部60分钟。 用户不能等待太多。


甚至有几个withProgress()函数都以这种方式包装了数据收集和处理过程,并使用户期望更有意义,这并没有节省太多的情况。


这个原型的第三个问题是,如果我们添加更多职业(那么,如果有的话),我们将面临这样一个事实,即标头中的位置结束


这三个原因足以让我完全重新考虑构建应用程序和UX的方法。 如果您发现更多内容,请随时发表评论。


该原型还具有以下优点:


  • 界面和业务逻辑的通用方法 :代替复制粘贴,我们将相同的片段移到带有参数的单独函数中。

例如,这是一个专业的“平铺”在主页上的显示方式:


代号
 tile <- function(title, midsal = NA, midsalres = NA, total.res = NA, total.vac = NA, updated = NA) { return( column(width = 4, h2(title), strong("  (middle):"), midsal, br(), strong("  (middle):"), midsalres, br(), strong(" :"), total.res, br(), strong(" : "), total.vac, br(), strong(" : "), updated, br(), br(), actionButton(inputId = paste0(tolower(prof), "Btn"), label = "Update", class = "btn-primary") ) ) } 

  • 通过inputId = paste0(, "Btn") 动态地形成 UI,直到代码中的标识符(inputId inputId = paste0(, "Btn") ,请参见上面的示例。 事实证明,这种方法极为方便,因为有必要用十几个控件进行初始化,再乘以专业人数。
  • 它起作用了:)

收集的数据存储在各种专业的.csv文件中( append = TRUE ),然后在启动应用程序时从那里读取数据。 当出现新数据时,会将它们添加到相应的文件中,然后重新计算平均值。


关于分隔符的几句话


一个重要的细微差别:csv文件的标准分隔符-逗号或分号-不太适合我们的情况,因为您经常可以找到空缺和履历,且标题为“ Shvets,Reaper,igrets(duda; html / css)”。 因此,我立即决定选择一些更具异国情调的东西,而我的选择落在了|上。


一切正常,直到下一次我开始使用时,我没有在包含货币的列中找到日期,然后各列向下移动,因此锁定了图表。 我开始理解。 事实证明,我的系统被一个漂亮的女孩弄坏了-“数据分析师|业务分析师”。 从那时起,我一直使用\x1B作为定界符,即ESC字符。 仍然不失望。


分配还是不分配?


在进行此项目时,assign函数对我来说是一个真正的发现:您可以动态生成变量的名称和其他日期框架, 酷了!


当然,我想将源数据保留在不同空缺的单独数据框中。 而且我不想写“ designer.vac = data.frame(...),analyser.vac = data.frame(...)”。 因此,启动应用程序时初始化这些对象的代码如下所示:


分配
 profs <- c("analyst", "designer", "developer", "devops", "manager", "qa") for (name in profs) { if (!exists(paste0(name, ".vac"))) assign(x = paste0(name, ".vac"), value = data.frame( URL = character() #    , id = numeric() # id  , Name = character() #   , City = character() , Published = character() , Currency = character() , From = numeric() # .    , To = numeric() # .  , Level = character() # jun/mid/sen , Salary = numeric() , stringsAsFactors = FALSE )) } 

但是我的喜悦并没有持续多久。 将来不再可能通过某个参数访问此类对象,并且这种强制性导致了代码重复。 同时,对象的数量呈指数增长,因此,很容易混淆对象和分配调用。


因此,我不得不使用另一种方法,最终变得更加简单: 使用列表。


初始化数据帧包? 容易!
 profs <- list( devops = "devops" , analyst = c("systems+analyst", "business+analyst") , dev.full = "full+stack+developer" , dev.back = "back+end+developer" , dev.front = "front+end+developer" , designer = "ux+ui+designer" , qa = "QA+tester" , manager = "project+manager" , content = c("mathematics+teacher", "physics+teacher") ) for (name in names(profs)) { proflist[[name]] <- data.frame( URL = character() #    , id = numeric() # id  , Name = character() #   , City = character() , Published = character() , Currency = character() , From = numeric() # .    , To = numeric() # .  , Level = character() # jun/mid/sen , Salary = numeric() , stringsAsFactors = FALSE ) } 

请注意,我没有像以前那样使用通常的带有职业名称的载体,而是使用了一个列表,该列表同时包含搜索查询,用于查找有关职位空缺和履历的数据。 因此,在调用求职功能时,我设法摆脱了丑陋的开关。


一口气从这些数据帧中渲染N个表和N个图形? 嗯...

而且,一般来说,并不难。 这是server.R的真空球形示例:


 lapply(seq_along(my.list.of.data.frames), function(x) { output[[paste0(names(my.list.of.data.frames)[x], ".dt")]] <- renderDataTable({ datatable(data = my.list.of.data.frames[[names(my.list.of.data.frames)[x]]]() , style = 'bootstrap', selection = 'none' , escape = FALSE) }) output[[paste0(names(my.list.of.data.frames)[x], ".plot")]] <- renderPlot( ggplot(na.omit(my.list.of.data.frames[[names(my.list.of.data.frames)[x]]]()), aes(...)) ) }) 

因此得出以下结论:列表是非常方便的事情,它使您可以减少代码量和处理代码的时间。 (因此,请不要分配。)


就在那一刻,当我因乔郑(Joe Cheng)关于仪表板的讨论而无法进行重构时,它来了...


重新思考


事实证明,在R中有一个特殊的程序包,为创建仪表板而进行了优化-Shinydashboard 。 它还使用引导程序,使组织带有简洁侧边栏的UI变得容易一些,该侧边栏可以完全隐藏而无需任何conditionalPanel() ,从而使用户可以专注于研究数据。


事实证明,如果HR每六个月检查一次数据,则不需要“更新”按钮。 一点都没有。 这不完全是一个“静态仪表板”,而是接近于此。 数据更新脚本可以与闪亮的应用程序完全分开实现,并使用标准Scheduler根据时间表运行它 窗户 您的操作系统。


这可以立即解决两个问题:漫长的等待(如果您定期在后台运行脚本,则用户甚至不会注意到其工作,而只会始终看到新鲜的数据)以及用户更新数据所需的冗余操作。 过去需要点击9次(每个专业一次),现在只需零次。 似乎我们已经在效率上有所收获,力求无限!


事实证明,应用程序不同部分中的代码执行的次数不同。 我不会对此进行详细介绍;如果您愿意,最好使自己熟悉报告中的直观说明。 我仅概述主要思想:操纵ggplot()中的数据,快速处理错误,并且可以带给应用程序高层的代码越多越好。 同时生产力有时会增长。


实际上,我对报告的了解越远,我越清楚地意识到我的第一个原型中的代码不是由风水组织的,而且从某个时候可以明显看出,该项目的重写比重构容易。 但是,如果投入了如此多的精力,如何离开自己的头脑呢?


死者不能死


-我从头开始思考并重写了项目,这次


  • 将用于收集职位空缺和恢复的整个代码(实际上是整个ETL过程)交付到一个单独的脚本中,该脚本可以独立于闪亮的应用程序运行,从而避免了用户的繁琐等待;
  • 使用了reactFileReader()从csv文件中读取预先收集的数据,从而确保了源数据在我的应用程序中的相关性,而无需重新启动和不必要的用户操作;
  • 摆脱了assign()的支持而使用列表,并在之前存在循环的地方积极使用lapply();
  • 另外,还使用了Shinydashboard重新设计了UI应用程序-无需担心屏幕空间不足;
  • 数次减少了应用程序的总容量(从1800行代码减少到360行代码)。

现在,该解决方案的工作方式如下。


  1. ETL脚本每月运行一次(这里是有关如何执行此操作的说明 ),并认真地遍历所有行业,收集有关空缺的原始数据并从hh恢复。
    此外,有关职位空缺的数据是通过网站的API获取的(我能够部分重用上一个项目中的代码),但是对于每个简历,我不得不使用rvest程序包解析网页,因为现在已经可以使用相应的API方法。 您可以猜测这如何影响脚本的速度。
  2. 将对收集的数据进行梳理-详细描述此过程,并在此处提供代码示例。 处理后的数据以hist / professional-hist-vac.csv和hist / professional-hist-res.csv的形式保存到磁盘中。 顺便说一句,这样的数据异常值可能会导致有趣的事情,请小心:)
    对于每个职业,脚本都会获取带有历史数据的增强文件,选择最相关的文件(从最近更新之日起不超过一个月的文件),并以data.res / professional-res-recent.csv和data.vac / professional格式生成新的csv文件。 -vac-recent.csv。 最终应用程序也可以使用此数据...
  3. ...,该文件在启动后读取简历和作业文件夹的内容(分别为data.res和data.vac),然后每小时检查一次文件更改。 就资源和执行速度而言,使用reactFileFileer()进行操作比使用invalidateLater()更为有效。 如果文件中有更改,则带有源数据的表将自动更新,并且平均值和图表将重新计算,因为它们依赖于reactValues(),也就是说,不需要其他代码即可处理这种情况。
  4. 在主页上,现在有一个表格,显示期望的最低,中位数和最大值,并针对每个找到的级别为每个专业提供的薪水(全部为传统知识)。 此外,您可以在标签上查看带有详细信息的图表,并以.xlsx格式上载数据(您永远不知道HR需要这些数字是什么)。

仅此而已。 事实证明,现在用户在我们的仪表板上唯一可用的按钮是“下载”按钮。 这是更好的选择:用户拥有的按钮越少,机会就越少 抛出未处理的异常 对他们感到困惑。


而不是结尾


今天,该应用程序仅收集和分析圣彼得堡的数据。 考虑到主要利益相关者都感到满意,并且最常见的反应是“很棒,但是莫斯科能做到吗?”,我认为该实验是成功的。


可以在此链接中 查看应用程序,所有源代码 (以及完成文件的示例)都在此处提供


顺便说一句,该应用程序称为Salary Monitor,缩写为Salmon-“ salmon”。


Source: https://habr.com/ru/post/zh-CN417991/


All Articles