tl;dr:第1部分解释了什么是解释算法,第2部分描述了一个开放源码的SQL数据模型。
在数据世界中,大多数报告都是从问多少开始的:“每周有多少新客户购买?”或者“这个群体每月的医疗费用是多少?”
最初的报告不可避免地会引发关于为什么的问题:“为什么我们上周看到的购买量减少了?”“为什么这一群体的医疗费用在增加?”
学术界对此有一个答案:为什么?问题:解释算法。解释算法查看数据集的列/属性,并识别高可能性解释(在数据库中称为“谓词”)。例如,算法可能会发现,在看到新营销活动的人群中,你得到的客户较少,或者你正在研究的群体的医疗成本在很大程度上可以归因于某个亚群体的昂贵治疗。
学术兴趣建立在真正的痛苦中。当记者、研究人员或组织问为什么时?,由此产生的数据分析主要用于通过查询发布特设分组,或不科学地创建数据透视表,试图对数据集进行切片和切分,以解释数据集随时间的变化。像Sisu这样的公司(由下面讨论的DIFF论文的作者之一Peter Bailis创建)是建立在这样一个前提之上的:数据消费者越来越问为什么?
你可以用解释性问题的形式重新表述许多不同的问题。这是一个我一直感兴趣的领域,尤其是因为它可能会帮助像记者和社会科学家这样的人更好地识别有趣的趋势。在《帮助记者的数据不同》(2015)一书中,我说:
如果有一个实用程序,在给定两个与模式对齐的数据集(例如,两个csv文件)的情况下,它会返回一个报告,说明它们在不同方面的差异,这将是一件好事。该实用工具可以对有趣的分组或聚合列进行提示,或者只是随机探索(分组、聚合)的成对组合,并通过各种度量对它们进行排序,比如与自己的组/跨组的最大偏差。
在那篇文章发表的时候,我还没有把对这样一个系统的渴望和研究界正在进行的积极工作联系起来。多亏了数据库研究人员,这种联系现在已经存在了!在本文中,我将首先介绍两种解释算法的方法,然后在我的datools库中介绍其中一种算法的开源实现。
2013年,尤金·吴(Eugene Wu)和山姆·马登(Sam Madden)引入了Scorpion,这一系统解释了为什么总数据(例如上周的客户数量)高于或低于其他示例数据。他们论文中的图1很好地解释了这个问题。他们想象一个用户在看一张图表,在这种情况下,是一组传感器的总温度,并突出显示一些异常值,问“与图表上的其他点相比,这些点为什么这么高?”
图中显示了用户如何在图表上突出显示异常值
蝎子有两个优点。首先,它是以总量为基础的:直到你看了一些每周或每月的统计数据后,你才注意到有什么不对劲,并寻找一个解释。其次,它适用于各种各样的聚合,并对最常见的聚合进行了优化(例如,总和、平均值、计数、标准差)。我相信在所有的解释算法中,Scorpion配对是问题最直观的措辞(“为什么这么高/低?”)最直观的体验(在可视化上突出可疑的结果)。
实现Scorpion的挑战在于,如前所述,它在存储数据的数据库之外进行处理。具体来说,Scorpion划分和合并数据子集以确定解释的方式需要决策树和聚类算法,这些算法通常在数据库1之外执行。它还特别适用于聚合,聚合通常是问题的来源,但不是问题出现的唯一地方。
这就是DIFF的用武之地。在2019,Firas Abuzaid、Peter Kraft、Sahaana Suri、Edward Gan、Eric Xu、Autul-ShanOy、Asvin Ananthanarayan、John Sheu、Py、Y、Y、Y和Mati ZHaARi引入了一种解释算法,称为DIFF的数据库操作符,可以用SQL表示。如果你这么想,下面是DIFF操作符的语法:
在本例中,DIFF操作符比较了一个应用程序本周的崩溃日志和上周的崩溃日志,并考虑了应用程序版本、设备和操作系统等列来进行解释。最有可能的解释是本周发生的事故比上周多20倍(风险率=20.0),并解释了本周75%的事故(支持率=75%)。
DIFF要求我们做一些心理体操来改变“为什么X这么高?”进入“这两个群体有何不同?”。它还要求用户对风险比率和支持率等统计数据保持头脑清醒。作为精神开销的交换,DIFF因其实用性而令人兴奋。正如示例所示,DIFF的作者设想它用SQL表示,这意味着它可以在大多数关系数据库上实现。虽然本文的一个贡献是对DIFF进行了专门而高效的实现,但它也可以作为一系列SQL GROUP BY/JOIN/WHERE操作符完全在数据库中实现。
如果你有一个关系数据库,喜欢SQL,并且想运行一个解释算法,DIFF是令人兴奋的,因为这三样东西就是你所需要的。亲爱的读者,幸运的是,我有一个关系数据库,喜欢SQL,想运行一个解释算法。
在过去几个月里,我一直将DIFF实现为一个精简的Python包装器,它生成计算两个与模式对齐的查询之间差异所需的SQL。实现这一点的核心,包括注释,需要不到300行代码。要查看该工具的完整示例,您可以查看这个Jupyter笔记本,但我将在下面展示一些片段,让您了解它是如何工作的。
首先,我们需要一个数据集。为此,我从Scorpion论文的实验中获得了灵感,其中一个实验依赖于我的研究生院顾问萨姆·马登(Sam Madden)和一些合作者从英特尔收集的传感器数据。使用Simon Willison优秀的sqlite utils库,我将数据加载到sqlite并检查它:
#检索并稍微转换数据集http://db.csail.mit.edu/labdata/data.txt.gzgunzip数据。txt。gz sed-i';1s/^/天时间_天纪元汽车旅馆温度湿度光电压\n/#39;数据txt头部数据。txt#在SQLitepip中获取它安装sqlite utilssqlite utils插入英特尔传感器。sqlite读取数据。txt--csv--sniff--detect typessqlite utils schema英特尔传感器。sqlite
创建表";阅读";([day]文本,[time_of_day]文本,[epoch]整数,[moteid]整数,[temperature]FLOAT,[湿度]FLOAT,[light]FLOAT,[voltage]FLOAT];
好啊因此,我们为每个传感器的读数设置了一行,包括发生的日期和时间,不同传感器的时间对齐读数的历元,一个moteid(传感器的ID,也称为mote),然后是传感器倾向于感测的类型:温度、湿度、光照和电压。
在Scorpion论文(第8.1节和第8.4节)中,用户注意到整个实验室中放置的各种传感器检测到过高的温度值(读取实验代码,这种情况发生在2004-03-01和2004-03-10之间)。一个自然的问题是为什么会发生这种情况。Scorpion算法发现moteid=15(ID为15的传感器)这几天过得不好。
我们能用DIFF复制这个结果吗?让我看看!DIFF实现是我一直在构建的一个名为datools的库的一部分,它是我用于各种数据分析的工具集合。让我们安装datools:
从sqlalchemy导入从datools创建引擎。解释从数据工具导入差异。模型从数据工具导入列。sqlalchemy_utils导入查询_结果_漂亮_打印引擎=创建_引擎(';sqlite:///intel-sensor.sqlite' ) 候选人=差异(引擎=引擎,测试关系=';从温度大于100和白天的读数中选择汽车旅馆、温度、湿度、光线、电压;从温度小于100和白天的读数中选择汽车旅馆、温度、湿度、光线、电压;从温度小于100和白天的读数中选择汽车旅馆、温度、湿度、光线、电压。);2004-03-01" 和day<"2004-03-10"' , 在_column_values={column(';moteid';),},在候选项中的候选项的列上,最小支持度=0.05,最小风险比率=2.0,最大顺序=1)打印(候选项)
哇!moteid=15是datools的顶级谓词。差异被认为是测试关系和控制关系之间的差异!在risk_ratio=404.83的情况下,我们了解到,传感器15出现在高温读数记录集中的可能性大约是低温读数记录集中的400倍。复制蝎子的结果,万岁!糟糕的传感器15!
让我们把这个电话打断一下,让我们了解一下发生了什么:
引擎:连接到某个数据库的SQLAlchemy引擎,在本例中是SQLite数据库。
test_关系:“test set”,这是一个查询,其中包含显示特定条件的记录。在我们的例子中,这是感兴趣的时期内的高温记录。这也可以是针对“医疗费用高的患者”或“购买的客户”的SQL查询
控制关系:“控制集”,这是一个查询,其中的记录没有显示特定的条件。在我们的例子中,是感兴趣时期的低温记录。这也可以是“医疗费用不高的患者”或“尚未购买的潜在客户”的SQL查询
On.CulnNoxValue:这些是要考虑的集合值列作为解释。在我们的例子中,我们考虑的是moteid列,所以我们可以确定一个特定的传感器有问题。
On.CulnNoLang:这些是你想考虑的范围值列。diff将这些列装入15个大小相等的桶中,这对{Column(';湿度';)、等连续变量非常有效,柱子(';灯';),列(';电压';),}。在本例中,我们不提供任何内容(稍后将详细说明原因),但在Jupyter笔记本中,您可以看到这一点。
min_support:解释应该解释的测试集的最小部分([0,1])。例如,min_support=0.05表示,如果一个解释不包括至少5%的测试集,我们就不想知道它。
_min:风险比率应涵盖的最小解释。例如,min_risk_ratio=2.0表示,如果一个解释在测试集中出现的可能性不是在对照集中出现的至少两倍,我们就不想知道它。
Max命令:一个联合解释要考虑多少列。例如,在Scorpion的论文中,作者发现,不只是传感器15(一栏解释),而且在某些光线和电压条件下(三栏解释),传感器15是异常读数的最佳解释。要分析三列解释,可以将max_order设置为3。不幸的是,希望是暂时的,而max_order是实现DIFF paper的参数datools最有趣、最有趣、最具挑战性的。diff目前只支持max_order=1。
一个精明的读者会注意到,在我的例子中,我通过让DIFF只考虑MuteID解释(OnthCulnNuxValue= {列(&39;MoTeID和α39;)})来哄骗我的例子中的结果。Scorpion的论文也考虑了其他栏目,但仍然从moteid获得了最强的信号。在Jupyter笔记本中,我们更深入地探讨了这一点,并遇到了一个用diff复制蝎子结果的问题。我在笔记本中提供了一些关于这一点的假设,但要获得更明智的意见,我们必须等到datools。diff支持最大订单>;1.
在我们开始用DIFF paper的算法复制Scorpion paper的发现之前,你应该知道这并不全是好事。幸运的是,我对改进数据工具同样感到兴奋。当我第一次写它的时候,我觉得下面的列表既是当前版本的限制,也是图书馆的路线图。如果你好奇的话,这个项目板会追踪我最积极的工作。
让diff不仅仅在SQLite上工作。diff生成SQL,我希望该SQL能在任何数据库上运行。这在很大程度上是一个改进测试工具以提供其他数据库和修复任何中断的问题。接下来我要针对的几个数据库是DuckDB、Postgres和Redshift,但如果你有兴趣在其他方面进行合作,我很乐意提供帮助。
支持最大订单>;1.DIFF论文的贡献之一是如何应对你在寻找多栏解释时遇到的组合爆炸。我想支持至少两列或三列的解释。
在更多数据集上使用diff。如果你有一个数据集(特别是一个公共数据集),你希望尝试这个,让我知道!
在实施高阶解释后,复制蝎子的差异分析。完整的JUJYTER笔记本显示,当我们要求它考虑比MyTID更多的列时,DIFF还不能复制蝎子的结果。笔记本提供了从“DIFF和Scorpion是不同的算法,有不同的权衡”到“为什么我们要考虑输出度量作为解释?”我认为在实施max_order>;1,这样我们就可以看到datools是如何工作的。diff处理更复杂的解释。
分享更多关于datools的信息。diff是datools软件包的一部分,但我没有告诉你太多关于datools的事情。关于SQL(尽管将继续存在)如何也有其粗糙的边缘,人们已经说了无数的话。datools会平滑其中一些粗糙的边缘。
Eugene Wu不仅向我介绍了解释算法的概念,还耐心地指导我完成各种论文的开头和结尾。Peter Bailis不仅表明对解释算法的需求得到了广泛的感受,而且还支持了与最新最先进的解决方案相比的情境化差异。我很感谢他们两人的反馈。
严格来说,不一定要在数据库之外运行更复杂的分析或机器学习算法。MADlib很好地说明了这一点,尽管在实践中,这种方法并没有像我希望的那样广泛。 ↩
例如,并不是每个数据库(我在看你们,SQLite和Redshift)都支持分组集和数据立方体之类的东西,但这些操作符对于让DIFF之类的工具在SQL中有效工作是至关重要的。datools提供的包装器,如果数据库支持分组集,将使用本机功能,但如果数据库不支持,则将使用次优功能。 ↩