【数据挖掘】如何保证数据一致性?

news/2025/3/14 20:06:27/

 

一、说明

我曾经在网络分析服务公司担任数据分析师。此类系统可帮助网站收集和分析客户行为数据。 不言而喻,数据是网络分析服务最宝贵的价值。我的主要目标之一是监控数据质量。

为了确保数据一切正常,我们需要关注两件事:

  • 没有丢失或重复的事件 - >事件和会话数在预期范围内。
  • 数据是正确的 - >每个参数的值分布保持不变,另一个版本尚未开始将所有浏览器记录为 Safari 或完全停止跟踪购买。

        今天,我想告诉大家我处理这项复杂任务的经历。作为奖励,我将展示 ClickHouse 数组函数的示例。

摄影:Luke Chesser on Unsplash

二、什么是网络分析?

        网络分析系统会记录有关网站上事件的大量信息,例如,客户使用的浏览器和操作系统,他们访问了哪些URL,他们在网站上花费了多少时间,甚至他们添加到购物车并购买了哪些产品。所有这些数据都可用于报告(了解有多少客户访问了该网站)或分析(了解痛点并改善客户体验)。您可以在维基百科上找到有关网络分析的更多详细信息。

        我们将使用ClickHouse的匿名网络分析数据。描述如何加载它的指南可以在这里找到。

        让我们看一下数据。 是会话的唯一标识符,而其他参数是此会话的特征。 看起来像数字变量,但它们是浏览器和操作系统的编码名称。存储这些值(如数字),然后在应用程序级别解码值要高效得多。这种优化非常重要,如果您正在处理大数据,可以为您节省 TB 级。VisitIDUserAgentOS

SELECTVisitID,StartDate,UTCStartTime,Duration,PageViews,StartURLDomain,IsMobile,UserAgent,OS
FROM datasets.visits_v1
FINAL
LIMIT 10┌─────────────VisitID─┬──StartDate─┬────────UTCStartTime─┬─Duration─┬─PageViews─┬─StartURLDomain─────────┬─IsMobile─┬─UserAgent─┬──OS─┐
│ 6949594573706600954 │ 2014-03-17 │ 2014-03-17 11:38:42 │        0 │         1 │ gruzomoy.sumtel.com.ua │        0 │         7 │   2 │
│ 7763399689682887827 │ 2014-03-17 │ 2014-03-17 18:22:20 │       24 │         3 │ gruzomoy.sumtel.com.ua │        0 │         2 │   2 │
│ 9153706821504089082 │ 2014-03-17 │ 2014-03-17 09:41:09 │      415 │         9 │ gruzomoy.sumtel.com.ua │        0 │         7 │  35 │
│ 5747643029332244007 │ 2014-03-17 │ 2014-03-17 04:46:08 │       19 │         1 │ gruzomoy.sumtel.com.ua │        0 │         2 │ 238 │
│ 5868920473837897470 │ 2014-03-17 │ 2014-03-17 10:10:31 │       11 │         1 │ gruzomoy.sumtel.com.ua │        0 │         3 │  35 │
│ 6587050697748196290 │ 2014-03-17 │ 2014-03-17 09:06:47 │       18 │         2 │ gruzomoy.sumtel.com.ua │        0 │       120 │  35 │
│ 8872348705743297525 │ 2014-03-17 │ 2014-03-17 06:40:43 │      190 │         6 │ gruzomoy.sumtel.com.ua │        0 │         5 │ 238 │
│ 8890846394730359529 │ 2014-03-17 │ 2014-03-17 02:27:19 │        0 │         1 │ gruzomoy.sumtel.com.ua │        0 │        57 │  35 │
│ 7429587367586011403 │ 2014-03-17 │ 2014-03-17 01:13:14 │        0 │         1 │ gruzomoy.sumtel.com.ua │        1 │         1 │  12 │
│ 5195928066127503662 │ 2014-03-17 │ 2014-03-17 01:43:02 │     1926 │         3 │ gruzomoy.sumtel.com.ua │        0 │         2 │  35 │
└─────────────────────┴────────────┴─────────────────────┴──────────┴───────────┴────────────────────────┴──────────┴───────────┴─────┘

        您可能会注意到我在表名后指定了修饰符。我这样做是为了确保数据完全合并,并且每个会话只得到一行。final

        在ClickHouse引擎中经常使用,因为它允许使用而不是(文档中通常的更多详细信息)。使用这种方法,您可以在更新的情况下每个会话有几行,然后系统在后台将其合并。使用修饰符,我们强制了这个过程。CollapsingMergeTreeinsertsupdatesfinal

        我们可以执行两个简单的查询来查看差异。

SELECTuniqExact(VisitID) AS unique_sessions,sum(Sign) AS number_sessions, -- number of sessions after collapsingcount() AS rows
FROM datasets.visits_v1┌─unique_sessions─┬─number_sessions─┬────rows─┐
│         1676685 │         1676581 │ 1680609 │
└─────────────────┴─────────────────┴─────────┘SELECTuniqExact(VisitID) AS unique_sessions,sum(Sign) AS number_sessions,count() AS rows
FROM datasets.visits_v1
FINAL┌─unique_sessions─┬─number_sessions─┬────rows─┐
│         1676685 │         1676721 │ 1676721 │
└─────────────────┴─────────────────┴─────────┘

        使用在性能上有其自身的缺点。您可以在文档中找到有关它的更多信息。final

三、如何保证数据质量?

        验证没有丢失或重复的事件非常简单。你可以找到很多方法来检测时间序列数据中的异常,从朴素的方法(例如,与前一周相比,事件数在 +20% 或 -20% 以内)到 ML 与 Prophet 或 PyCaret 等库。

        数据一致性是一项比较棘手的任务。正如我之前提到的,网络分析服务跟踪有关客户在网站上行为的大量信息。它们记录了数百个参数,我们需要确保所有这些值看起来都有效。

        参数可以是数字(持续时间或看到的网页数量)或分类(浏览器或操作系统)。对于数值,我们可以使用统计标准来确保分布保持不变——例如,柯尔莫哥罗夫-斯米尔诺夫检验。

        因此,在研究了最佳实践之后,我唯一的问题是如何监控分类变量的一致性,是时候讨论它了。

四、分类变量

        让我们以浏览器为例。我们的数据中有 62 个浏览器的唯一值。

SELECT uniqExact(UserAgent) AS unique_browsers
FROM datasets.visits_v1┌─unique_browsers─┐
│              62 │
└─────────────────┘SELECTUserAgent,count() AS sessions,round((100. * sessions) / (SELECT count()FROM datasets.visits_v1FINAL), 2) AS sessions_share
FROM datasets.visits_v1
FINAL
GROUP BY 1
HAVING sessions_share >= 1
ORDER BY sessions_share DESC┌─UserAgent─┬─sessions─┬─sessions_share─┐
│         7 │   493225 │          29.42 │
│         2 │   236929 │          14.13 │
│         3 │   235439 │          14.04 │
│         4 │   196628 │          11.73 │
│       120 │   154012 │           9.19 │
│        50 │    86381 │           5.15 │
│        79 │    63082 │           3.76 │
│       121 │    50245 │              3 │
│         1 │    48688 │            2.9 │
│        42 │    21040 │           1.25 │
│         5 │    20399 │           1.22 │
│        71 │    19893 │           1.19 │
└───────────┴──────────┴────────────────┘

        我们可以将每个浏览器的共享作为数值变量单独监控,但在这种情况下,我们将监控一个字段的至少 12 个时间序列,.每个至少做过一次警报的人都知道,我们监视的变量越少越好。跟踪许多参数时,需要处理大量误报通知。UserAgent

        因此,我开始考虑一种可以显示分布之间差异的指标。这个想法是比较现在 () 和之前 () 的浏览器份额。我们可以根据粒度选择上一个周期:T2T1

  •         对于分钟数据——你可以看上一点,
  •         对于每日数据 - 值得查看一周前的一天,以考虑每周的季节性,
  •         对于月度数据 - 您可以查看一年前的数据。

        让我们看下面的例子。

        我的第一个想法是查看类似于机器学习中使用的L1规范的启发式指标(更多详细信息)。

        对于上面的例子,这个公式将给我们以下结果 — 10%。实际上,这个指标是有意义的——它显示了浏览器已更改的分发事件中的最小份额。

        之后,我和我的老板讨论了这个话题,他在数据科学方面有很多经验。他建议我看看Kullback-Leibler或Jensen-Shannon散度,因为这是计算概率分布之间距离的更有效的方法。

        如果您不记得这些指标或以前从未听说过它们,请不要担心,我站在你的立场上。所以我用谷歌搜索了公式(本文彻底解释了这些概念)和我们示例的计算值。

import numpy as npprev = np.array([0.7, 0.2, 0.1])
curr = np.array([0.6, 0.27, 0.13])def get_kl_divergence(prev, curr):kl = prev * np.log(prev / curr)return np.sum(kl)def get_js_divergence(prev, curr): mean = (prev + curr)/2return 0.5*(get_kl_divergence(prev, mean) + get_kl_divergence(curr, mean))kl = get_kl_divergence(prev, curr)
js = get_js_divergence(prev, curr)
print('KL divergence = %.4f, JS divergence = %.4f' % (kl, js))# KL divergence = 0.0216, JS divergence = 0.0055

        如您所见,我们计算的距离差异很大。所以现在我们(至少)有三种方法来计算之前和现在浏览器份额之间的差异,下一个问题是为我们的监控任务选择哪种方式。

五、获胜者是...

        估计不同方法性能的最佳方法是查看它们在现实生活中的表现。为此,我们可以模拟数据中的异常并比较效果。

        数据中有两种常见的异常情况:

  • 数据丢失:我们开始丢失来自其中一个浏览器的数据,并且所有其他浏览器的份额都在增加
  • 更改:当来自一个浏览器的流量开始标记为另一个浏览器时。例如,我们现在看到的 10% 的 Safari 事件是未定义的。

        我们可以获取实际的浏览器共享并模拟这些异常。为简单起见,我将把所有份额低于 5% 的浏览器分组到组中。browser = 0

WITH browsers AS(SELECTUserAgent,count() AS raw_sessions,(100. * count()) / (SELECT count()FROM datasets.visits_v1FINAL) AS raw_sessions_shareFROM datasets.visits_v1FINALGROUP BY 1)
SELECTif(raw_sessions_share >= 5, UserAgent, 0) AS browser,sum(raw_sessions) AS sessions,round(sum(raw_sessions_share), 2) AS sessions_share
FROM browsers
GROUP BY browser
ORDER BY sessions DESC┌─browser─┬─sessions─┬─sessions_share─┐
│       7 │   493225 │          29.42 │
│       0 │   274107 │          16.35 │
│       2 │   236929 │          14.13 │
│       3 │   235439 │          14.04 │
│       4 │   196628 │          11.73 │
│     120 │   154012 │           9.19 │
│      50 │    86381 │           5.15 │
└─────────┴──────────┴────────────────┘

        是时候模拟这两种情况了。您可以在 GitHub 上找到所有代码。对我们来说,最重要的参数是实际效果——丢失或改变的事件份额。理想情况下,我们希望我们的指标等于这种效果。

        作为模拟的结果,我们得到了两个图表,显示了事实效应和距离指标之间的相关性。

        图表中的每个点都显示一个模拟的结果 — 实际效果和相应的距离。

        您可以很容易地看到 L1 范数是我们任务的最佳指标,因为它最接近线。Kullback-Leibler和Jensen-Shannon的分歧很大,并且根据用例(哪个浏览器正在失去流量)具有不同的级别。distance = share of affected events

        此类指标不适合监控,因为您将无法指定一个阈值,以便在超过 5% 的流量受到影响时向您发出警报。此外,我们无法轻松解释这些指标,而 L1 范数准确地显示了异常的程度。

六、L1范数计算

        现在我们知道什么指标将向我们显示数据的一致性,剩下的最后一个任务是在数据库中实现 L1 范数计算(在我们的例子中是 — ClickHouse)。

        我们可以为它使用广为人知的窗口函数。

with browsers as (selectUserAgent as param,multiIf(toStartOfHour(UTCStartTime) = '2014-03-18 12:00:00', 'previous',toStartOfHour(UTCStartTime) = '2014-03-18 13:00:00', 'current','other') as event_time,sum(Sign) as eventsfrom datasets.visits_v1where (StartDate = '2014-03-18')-- filter by partition key is a good practiceand (event_time != 'other')group by param, event_time)
selectsum(abs_diff)/2 as l1_norm
from(selectparam,sumIf(share, event_time = 'current') as curr_share,sumIf(share, event_time = 'previous') as prev_share,abs(curr_share - prev_share) as abs_difffrom(selectparam,event_time,events,sum(events) over (partition by event_time) as total_events,events/total_events as sharefrom browsers)group by param)┌─────────────l1_norm─┐
│ 0.01515028932687386 │
└─────────────────────┘

        ClickHouse有非常强大的数组函数,在支持窗口函数之前,我已经使用了很长时间。所以我想向你展示这个工具的强大功能。

with browsers as (selectUserAgent as param,multiIf(toStartOfHour(UTCStartTime) = '2014-03-18 12:00:00', 'previous',toStartOfHour(UTCStartTime) = '2014-03-18 13:00:00', 'current','other') as event_time,sum(Sign) as eventsfrom datasets.visits_v1where StartDate = '2014-03-18' -- filter by partition key is a good practiceand event_time != 'other'group by param, event_timeorder by event_time, param)
select l1_norm 
from(select-- aggregating all param values into arraysgroupArrayIf(param, event_time = 'current') as curr_params,groupArrayIf(param, event_time = 'previous') as prev_params,-- calculating params that are present in both time periods or only in one of themarrayIntersect(curr_params, prev_params) as both_params,arrayFilter(x -> not has(prev_params, x), curr_params) as only_curr_params,arrayFilter(x -> not has(curr_params, x), prev_params) as only_prev_params,-- aggregating all events into arraysgroupArrayIf(events, event_time = 'current') as curr_events,groupArrayIf(events, event_time = 'previous') as prev_events,-- calculating events sharesarrayMap(x -> x / arraySum(curr_events), curr_events) as curr_events_shares,arrayMap(x -> x / arraySum(prev_events), prev_events) as prev_events_shares,-- filtering shares for browsers that are present in both periodsarrayFilter(x, y -> has(both_params, y), curr_events_shares, curr_params) as both_curr_events_shares,arrayFilter(x, y -> has(both_params, y), prev_events_shares, prev_params) as both_prev_events_shares,-- filtering shares for browsers that are present only in one of periodsarrayFilter(x, y -> has(only_curr_params, y), curr_events_shares, curr_params) as only_curr_events_shares,arrayFilter(x, y -> has(only_prev_params, y), prev_events_shares, prev_params) as only_prev_events_shares,-- calculating the abs differences and l1 normarraySum(arrayMap(x, y -> abs(x - y), both_curr_events_shares, both_prev_events_shares)) as both_abs_diff,1/2*(both_abs_diff + arraySum(only_curr_events_shares) + arraySum(only_prev_events_shares)) as l1_normfrom browsers)┌─────────────l1_norm─┐
│ 0.01515028932687386 │
└─────────────────────┘

        这种方法对于具有pythonic思维的人来说可能很方便。凭借持久性和创造力,可以使用数组函数编写任何逻辑。

七、警报和监控

        我们有两个查询,向我们显示浏览器在我们数据中的份额波动。可以使用此方法监视感兴趣的数据。

        剩下的唯一一点就是在警报阈值上与团队保持一致。我通常会查看历史数据和以前的异常情况,以获取一些初始级别,然后使用新信息不断调整它们:误报警报或错过的异常。

        此外,在实施监控的过程中,我遇到了一些细微差别,我想简要介绍一下:

  • 例如,数据中存在在监视中没有意义的参数,或者 ,因此请明智地选择要包含的参数。UserIDStartDate
  • 您可能具有高基数的参数。例如,在 Web 分析中,数据具有超过 600K 个唯一值。为其计算指标可能会消耗资源。因此,我建议要么将这些值(例如,采用域或 TLD)存储,要么仅监控顶级值并将其他值分组到单独的组“其他”中。StartURL
  • 您可以使用存储桶对数值使用相同的框架。
  • 在某些情况下,预计数据会发生重大变化。例如,如果您正在监视应用程序版本字段,则在每个版本发布后都会收到警报。此类事件有助于确保您的监控仍在:)

 


http://www.ppmy.cn/news/1039262.html

相关文章

Go 与 Rust

目录 1. Go 与 Rust 1. Go 与 Rust 一位挺 Rust 的网友说道: “我也为这个选择烦恼了很久。最终 Rust 胜出了。首先, 我感觉 Rust 更接近于以前 Pascal 时代的东西, 你可以控制一切; 其次, 如果 wasm 和相关技术大爆发, Rust 将是一个更安全的选择; 然后, 我们已经有了 Python…

pytorch_lightning报错 You requested gpu: [1],But your machine only has: [0]

pytorch_lightning报错 You requested gpu: [1],But your machine only has: [0] 问题及分析 报错图片如下: 分析 gpu:[1]指代的gpu的标号,如果笔记本中只包含一个GPU,一般序号为[0].所以无法找到程序指定的GPU。 解决方法 …

内网穿透实战应用——【通过cpolar分享本地电脑上有趣的照片:发布piwigo网页】

通过cpolar分享本地电脑上有趣的照片:发布piwigo网页 文章目录 通过cpolar分享本地电脑上有趣的照片:发布piwigo网页前言1. 设定一条内网穿透数据隧道2. 与piwigo网站绑定3. 在创建隧道界面填写关键信息4. 隧道创建完成 总结 前言 首先在本地电脑上部署…

做好以下几点,可以让我们延长周末体验感,好好放松!!!

工作以后常常容易感到疲于奔命,让我们找到适合自己方式,来让我们度过一个充实放松的周末! 方向一:分享你周末的时间规划 我们可以把每个月当做一个周期,制定一个简单的计划,如:第一周,锻炼身体…

浅谈时序:set_input_delay

1、set_input_delay的本质 set_input_delay是对模块input信号在模块外部延迟的约束,本质上EDA工具会根据约束调整设计内部的器件类型,摆放的位置以及优化内部组合逻辑保证满足约束要求。 约束指导原则:尽量照顾设计外部逻辑延时 set_input…

【代码随想录-Leetcode第六题:209. 长度最小的子数组】

209. 长度最小的子数组 题目思路代码实现 题目 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回…

leetcode 力扣刷题 两数/三数/四数之和 哈希表和双指针解题

两数/三数/四数之和 题目合集 哈希表求解1. 两数之和454. 四数相加Ⅱ 双指针求解15.三数之和18. 四数之和 这个博客是关于:找出数组中几个元素,使其之和等于题意给出的target 这一类题目的,但是各个题之间又有些差异,使得需要用不…

Jmeter 连接 MySQL 数据库脚本

1、创建线程组 2、创建 JDBC Connection Configuration 3、创建 JDBC Request 4、最终创建的目录 5、重点来了 5.1 在百度中下载个 MySQL-connector-Java-8.0.28.jar,放在 jmeter 的 bin 目录下 5.2 在测试计划中,将 jar 包添加到脚本中 5.3 输入参…