TowardsDataScience 2023 博客中文翻译(三百七十一)

Python计算圆周率及数据科学相关技术探讨

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

为什么你(几乎)不能在家中用 Python 计算圆周率到一亿位

原文:towardsdatascience.com/why-you-almost-cant-calculate-pi-to-a-billion-digits-in-python-at-home-4262a4de9f80

这比你想象的要困难

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Bex T.

·发表在数据科学的前沿 ·阅读时间 9 分钟·2023 年 10 月 9 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由我使用 Midjourney 生成。

介绍

2022 年 6 月 9 日,谷歌设立了计算圆周率最多位数的新世界纪录——100 万亿位!这一重大成就得益于在谷歌云上运行的 y-cruncher 程序。该程序计算了整整 157 天、23 小时、31 分钟和 7.651 秒。

如果一亿是 10 万倍小于 100 万亿,那么运行时间会相应减少吗?换句话说,是不是只需要 136 秒?

但 136 秒实在太过雄心勃勃。家用电脑远不如谷歌云的强大环境。那么,像 24 小时这样更合理的运行时间如何?

结果发现,在 24 小时内计算即使是一亿位的圆周率也是一个巨大的梦想。本文通过 Python 证据解释了原因。

首先,math.pi有什么问题?

import math

print(math.pi)
3.141592653589793

math.pi的精度为 15 位。虽然这不是很多,但足以进行科学中的最高精度计算。

例如,NASA 的喷气推进实验室(JPL)使用 15 位数字的圆周率来在行星间导航。为了给你一个概念,这种精度足以计算一个半径为 150 亿英里的圆的周长。结果是 940 亿英里的周长误差不超过你小指的宽度。想一想吧!

那么,为什么还要纠结于一亿位,更不用说 100 万亿位了?

好吧,给你一个极客的答案:“因为这实在太酷了!”

如果我们增加小数点精度会怎样?

在我们使用大招(算法!)之前,如果我们在 Python 中增加近似π的小数点精度呢?那会简单得多。

对于这种近似,我们将使用拉马努 jan 的π公式。全球的数学家都用它来极其准确地近似π。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由我制作

from decimal import Decimal
from math import sqrt

def pi():
    return Decimal(9801) / (Decimal(1103) * Decimal(sqrt(8)))

pi()
Decimal('3.141592730013305445560728363')

我们使用简单的算术达到了 16 位的精度。让我们将其提高到 1000 位并测量运行时间:

# Increase float precision to 1000
from decimal import getcontext

getcontext().prec = 1000
%%time

approximation = pi()
CPU times: user 14 µs, sys: 2 µs, total: 16 µs
Wall time: 16.2 µs

仅仅花了不到一秒的时间就获得了 1000 位的精度!让我们大胆地将运行时间增加到 100 万:

getcontext().prec = 1_000_000
%%time

approximation = pi()
CPU times: user 3.18 ms, sys: 0 ns, total: 3.18 ms
Wall time: 3.18 ms

仅仅三毫秒——发生了什么?看起来我们可以在瞬间计算出十亿位数字。我们来试试:

# One billion digit precision
getcontext().prec = 1_000_000_000
%%time

approximation = pi()
CPU times: user 3.04 s, sys: 301 ms, total: 3.34 s
Wall time: 3.32 s

甚至不到 10 秒!approximation现在包含了十亿位的圆周率,仅计算了……等等!我们没有将前几十位数字与验证过的圆周率数字进行比较,以确保近似值的准确性。

所以,让我们检查一下:

# I got this from the Internet
verified_pi = Decimal(3.1415926535897932384626433832795028841971)
rounded_pi = round(approximation, 50)

print(verified_pi)
print(rounded_pi)
3.141592653589793115997963468544185161590576171875
3.14159273001330544556072836229812077562268642913720

嗯,看起来我们的近似值仅准确到第 7 位。真让人失望!

那么,刚刚发生了什么问题?

为什么我们得到如此糟糕的结果?拉曼努金的公式不正确吗?当然不是!G.H. 哈代本人认为发现拉曼努金的才华是他最大的数学贡献。

问题在于现代计算机存储浮点数和表示它们的方式。我不会深入探讨这个问题,但建议你参考这个维基百科页面这个出色的 StackOverflow 讨论

基本上,我们面临的是以下难题的更复杂版本:

0.1 + 0.1 + 0.10.30000000000000004

由于精度的限制,上述错误可能会积累,特别是对于像圆周率这样的无理数。即使是一个小的舍入误差,也可能在多次操作中积累,完全改变最终结果。

这让我们意识到:使用比例和简单数学的公式无法快速准确地计算圆周率的位数。

大牌或者计算机简史

阿基米德通过增加多边形的边数来近似圆周率,内切和外切于圆。但自那时以来,世界已经走了很长一段路。这个两千年前的方法简单,但它不是为计算机设计的。对不起,阿基米德。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

GIF 由我使用 Wolfram 制作。

[## 阿基米德的圆周率近似 - Wolfram 演示项目

显示了一个单位圆。一个正多边形被内切。另一个正多边形被……

demonstrations.wolfram.com](https://demonstrations.wolfram.com/ArchimedesApproximationOfPi/?source=post_page-----4262a4de9f80--------------------------------)

当然,牛顿在他的时代也有所贡献,并且贡献了他的收敛级数来近似圆周率。但他只能计算出 14 位数字。要计算更多位,他需要活得更久,因为每增加一位需要更多的级数项。

然后,拉马努金用他的公式迅速收敛并计算π。我们之前使用的比例是一个简化。对于 20 世纪中期的计算机来说,他的方法的效率几乎是光速。

到 20 世纪末,科学家们使用了更多基于快速收敛系列的算法。例如,Gauss-Legendre 算法的不同变体在 1982 年从 16500 位到 2002 年的 240 亿位之间打破了六项世界纪录。但这些算法对于富有的科学家来说才适用——它们需要大量的计算资源,特别是内存。

所以,21 世纪初,Spigot 算法开始兴起。这些算法可以从左到右顺序计算超越数的位数。这意味着它们需要显著较小的内存,因为它们不需要记住之前的位数来计算下一位。但它们的缺点是比收敛系列要慢。

那么,Google 使用了什么?

由于 Google 可以投入(浪费?)他们想要的资源来创下世界纪录,他们选择了Chudnovsky 算法。这个系列基于拉马努金的公式。它已经创造了七项世界纪录用于计算π。这些记录从 2009 年到 2022 年设立,所有记录都在万亿位数级别。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Chudnovsky 算法。图片来自维基百科。维基媒体共享资源。

在介绍中,我提到 Google 使用了一个名为 y-cruncher 的特殊程序。这个程序可以计算像π这样的万亿位数字。它的理想环境是多核系统,因为它是一个多线程软件。

而且 Google 提供了一个理想的环境!

为了进行 100 万亿位数字的实验,计算引擎具有这些疯狂的规格:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来自 Google 的记录公告。

当然,硬件并不是成就成功的唯一因素。要了解更多关于软件架构和细节,请访问Google Cloud页面。

尝试使用 Chudnovsky

为了感受算法的效果,让我们在我的 PC 上尝试 Chudnovsky 来计算前 10k 位。我正在运行一台配有 32GB RAM 的 AMD Ryzen 9 3200x 12 核 CPU 的普通电脑。

首先,让我们编写一个计时器装饰器,可以添加到任何π计算函数中:

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        execution_time = end_time - start_time
        print(f"Execution time: {execution_time} seconds")
        return result

    return wrapper

现在,让我们将 Chudnovsky 放入一个函数中。你不必阅读它,因为这是一个相当复杂的函数(我自己也不理解)。它唯一的作用是近似计算π的n_digits位,并将结果写入文本文件中。

import decimal

@timer
def chudnovsky_to_file(n_digits):
    decimal.getcontext().prec = n_digits + 1
    C = 426880 * decimal.Decimal(10005).sqrt()
    K = 6.0
    M = 1.0
    X = 1
    L = 13591409
    S = L

    for i in range(1, n_digits):
        M = M * (K**3 - 16 * K) / ((i + 1) ** 3)
        L += 545140134
        X *= -262537412640768000
        S += decimal.Decimal(M * L) / X

    pi = C / S

    with open("pi_digits.txt", "w") as file:
        file.write(str(pi) + "\n")

让我们尝试计算前 1000 位:

n_digits = 1000

chudnovsky_to_file(n_digits)
Execution time: 1.8556151390075684 seconds

尽管函数运行时间不到两秒,但它的准确度仅为 13 位小数。为什么?

首先,函数严重未优化。chudnovsky_to_file是一个基础的 Chudnovsky,没有添加任何华丽的技巧或调料。在这里添加它们会破坏 Medium 界面。

此外,原生 Python 在处理大整数或长精度浮点数的某些操作时并不充分准备。例如,导致上述函数问题的是函数体第二行的平方根函数。

要在 Python 中加速 Chudnovsky,请阅读这篇优秀(但痛苦详细)的文章。它实际上在不到 10 分钟内计算了 10 亿位数字。

对于我们来说,让我们尝试一种或多种易于理解且无需过多修改的算法。

尝试 Spigot 算法

除了需要较低的内存资源外,Spigot 算法还有一个独特的优点,那就是可以无限期运行。这意味着,只要你有时间,你可以计算任意精度的超越常数。

这篇优秀的文章列出了许多 Python 中的 Spigot 算法,并按效率顺序排列。对于这篇文章,我们将选择 Gosper 的验证系列:

def gospers_pi():
    q, r, t, n, i = 1, 0, 1, 8, 1
    while True:
        if n == (q * (675 * i - 216) + 125 * r) // (125 * t):
            yield n
            q, r = 10 * q, 10 * r - 10 * n * t
        else:
            q, r, t, i = (
                i * (2 * i - 1) * q,
                3 * (3 * i + 1) * (3 * i + 2) * ((5 * i - 2) * q + r),
                3 * (3 * i + 1) * (3 * i + 2) * t,
                i + 1,
            )
        n = (q * (27 * i - 12) + 5 * r) // (5 * t)

gospers_pi是一个生成器,所以我们将把它包装在另一个函数中,该函数运行到n_digits并将数字写入文本文件:

@timer
def gospers_to_file(n_digits):

    with open("pi_digits.txt", "w") as file:
        pi = gospers_pi()
        for n in range(n_digits + 1):
            if n == 0:  # Put the dot after 3
                file.write(str(next(pi)) + ".")
            else:
                file.write(str(next(pi)))

现在,让我们试试前 1000 位:

n_digits = 1000

gospers_to_file(n_digits)
Execution time: 0.012832880020141602 seconds

这花了不到一秒钟,并且在将最后几位数字与验证过的 1000 位π数字进行比较时,我们可以看到该算法既快速又准确。

现在,我将让函数运行 1 百万位数字,同时对这篇文章进行一些编辑。

结论

当我开始 1 百万位数字实验时,我意识到一个重大问题——我应该每隔 10k 位左右设置一个里程碑检查!

已经过了好几个小时。文章已编辑完成。但函数似乎不会很快停止。我只知道自从开始以来我的 RAM 使用量增加了 1 GB。我会在函数完成(如果有的话)后更新结果。

这仅仅展示了这一点:计算π的位数需要极大的耐心和毅力。从在互联网上寻找合适的算法到呆呆地等待程序退出(并希望最后的数字与互联网上验证的结果相匹配)。

本文还讨论了一些 Python 的明显问题。它对高精度数学的支持有限,而且速度像乌龟一样慢。但如果你足够耐心,比如计算 10 亿位数字的文章,你将获得回报。

感谢阅读!

好了,它终于在一夜之间完成了。

n_digits = 1_000_000

gospers_to_file(n_digits)
Execution time: 20107.50238442421 seconds

2 万秒或接近 6 小时。我检查了最后四位数字与 piday.org 上验证的百万位π数字,结果完全正确!

你可以从这里下载我的百万位数字(文件在 GitHub 上小于 1 MB)。

感谢你坚持到最后!

为什么你需要知识图谱,以及如何构建它

原文:towardsdatascience.com/why-you-need-a-knowledgegraph-and-how-to-build-it-ac4f35cb75b7?source=collection_archive---------0-----------------------#2023-08-09

从关系数据库迁移到图数据库的指南

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Stan Pugsley

·

关注 发表在 Towards Data Science ·7 分钟阅读·2023 年 8 月 9 日

TLDR: 知识图谱通过图数据库组织事件、人物、资源和文档,以进行高级分析。本文将解释知识图谱的目的,并向你展示如何将关系数据模型转换为图模型,将数据加载到图数据库中,并编写一些示例图查询。

为什么需要知识图谱?

关系数据库非常适合创建列表,但在管理各种实体的网络方面却很糟糕。你是否尝试过用关系数据库完成以下任务?

  • 分析一个医疗护理事件,当一个患者与许多人、地点和程序互动时

  • 在涉及到金融欺诈时,通过供应商、客户和交易类型的网络找到模式

  • 优化供应链的依赖关系和互联元素

这些都是事件、人员和资源网络的示例,这些网络给使用关系数据库的 SQL 分析师带来了巨大的头痛。随着网络规模的增加,关系数据库的速度会呈指数级下降,而图数据库则具有相对线性的关系。如果你在管理一个活动和事物的网络或网络,一个图数据库是正确的选择。未来,我们应该期待看到企业数据组采用关系数据库进行孤立分析,以及知识图谱处理跨功能的复杂网络过程的组合。

基于图数据库技术的知识图谱,是为了处理多样的过程和实体网络而构建的。在知识图谱中,你会有代表人员、事件、地点、资源、文档等的节点。你还有表示节点之间链接的关系(边)。这些关系在数据库中物理存储,并具有名称和方向。并非所有的图数据库都是知识图谱。要被视为知识图谱,设计必须嵌入业务 语义模型,通过清晰的业务名称反映在多个业务功能跨越的节点集合中。你实际上是在创建一个无缝的网络,连接所有交互的业务部分,并使用业务语义将数据紧密地与其代表的过程联系起来。这可以作为未来生成型 LLM 模型使用的基础

为了在知识图谱中展示多样的数据集,我们来看一个关于供应链物流的简单示例。业务流程可能被建模为如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

供应链图数据库模型。图像由作者提供。

该模型可以扩展到包括业务流程的任何相关部分:客户退货、发票、原材料、制造过程、员工,甚至客户评论。没有预定义的模式,因此模型可以在任何方向或深度上扩展。

从关系模型到维度模型再到图模型

现在,让我们通过将一个典型的关系数据库模型转换为图模型的过程,使用电子商务供应商的场景。假设该供应商正在进行一系列数字营销活动,在其网站上接收订单,并向客户发货。关系模型可能如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

电子商务关系数据库模型。图像由作者提供。

如果我们将其转换为数据仓库中的维度模型,该模型可能如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

电子商务维度模型(数据仓库)。图片由作者提供。

请注意,事实表集中于事件,而维度表表示将业务实体的所有属性组合成一个表。这种以事件为中心的设计提供了更快的查询时间,但也带来了其他问题。每个事件都是一个独立的事实表,从一个事件到相关事件的连接很难看出。当这些关系被分割在多个事实表之间时,没有简单的方法来理解维度实体(如产品)与另一个维度中的实体(如承运人)之间共享的所有事件的关系。维度模型专注于一次一个事件,但掩盖了不同事件之间的连接。

图形模型通过将过程建模为如下方式,解决了显示实体间相互关系的问题:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

电子商务图形数据库模型。图片由作者提供。

首次查看,这个图形模型与关系模型的相似性多于与维度模型的相似性,但它可以用于与数据仓库相同的分析目的。注意每个关系都有名称和方向。而且,可以在任何节点之间创建关系——事件与事件、人际与人际、文档与事件等。图形查询还允许你以 SQL 无法实现的方式遍历图形。

例如,你可以收集与关键事件相关的任何节点,并研究其出现模式。层次结构得到保留,每个层级可以单独引用,这不同于非规范化的维度表。最重要的是,图形在建模业务中的任何事件或实体时更具灵活性,不需要遵循严格的模式约束。图形设计旨在匹配业务的语义模型。

提取、转换和加载(ETL)

现在让我们看一个示例关系数据库表,并创建一些示例脚本以提取、转换和加载数据到图形数据库中。在这篇文章中,我将使用由Neo4j提供的 Cypher 语言,Neo4j 是最受欢迎的商业图形数据库。但这些概念也适用于其他变体的图形查询语言(GQL)。我们将使用以下示例产品表:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

产品表。图片由作者提供。

使用这个查询,我们可以提取过去 24 小时内更新的新产品:

SELECT product_id,
  product_name,
  cost_usd,
  product_status
FROM Product
WHERE last_updated_date > current_date -1;

我们可以将这些结果提取到一个名为“df”的Python Pandas数据框中,打开图形数据库连接,然后使用此脚本将数据框合并到图形中。

UNWIND $df as row
MERGE INTO (p:Product {product_id: row.product_id})
SET p.product_name = row.product_name,
  p.cost_usd = row.cost_usd,
  p.product_status= row.product_status,
  p.last_updated_date = datetime();

第一行引用了一个参数“df”,这是来自 Pandas 的数据框。我们将合并到节点类型“Product”中,该节点通过别名“P”进行引用。然后,“product_id”部分用于绑定到节点中的唯一标识符。之后,Merge 语句看起来类似于 SQL 中的合并。

在使用如上所述的合并语句创建每个节点后,我们创建关系。关系可以在同一脚本中创建,也可以在后处理脚本中使用像这样的合并命令创建:

MATCH (p:Product), (o:Order)
WHERE p.product_id = o.order_id
MERGE (o)-[:CONTAINS]->(p);

Match 语句看起来像 Oracle 中的传统连接用法,在 Match 之后声明两个节点类型,然后在 Where 子句中进行连接。

图模型上的查询

假设我们已经构建了图,现在想要查询它。我们可以使用类似这样的查询来查看来自亚利桑那州的广告组推动的订单。

MATCH (ag:AdGroup)<-[:BELONGS_TO]-(a:Ad)-[:DRIVES]->(o:Order)<-[:PLACES]-(c:Customer)
WHERE c.state = 'AZ'
RETURN ag.group_name,
  COUNT(o) as order_count

这个查询将返回广告组名称和订单数量,筛选自亚利桑那州。请注意,Cypher 中不需要 Group By 子句,与 SQL 不同。从该查询中,我们将收到以下示例输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图查询的示例结果。图片由作者提供。

这个例子可能看起来微不足道,因为你可以很容易地在关系数据库或数据仓库中使用订单事实表创建类似的查询。但让我们考虑一个更复杂的查询。假设你想查看从广告活动启动到可归因的交付被接收所需的时间。在数据仓库中,这个查询将跨越事实表(不是一项简单的任务)并且需要大量资源。在关系数据库中,这个查询将涉及一系列长连接。在图数据库中,这个查询看起来如下:

MATCH (cp:Campaign) )<-[:BELONGS_TO]-(ag:AdGroup)<-[:BELONGS_TO]-(a:Ad)
MATCH (a)-[:DRIVES]->(o:Order)<-[:FULFILLS]-(d:Delivery)
RETURN cp.campaign_name,
  cp.start_date as campaign_launch_date,
  MAX(d.receive_date) as last_delivery_date

我使用了一个示例查询路径,但用户可以采取多种路径来回答不同的业务问题。在查询中,请注意从 Campaign 到 Delivery 的路径通过 Order 和 Delivery 之间的关系。此外,为了提高可读性,我将路径拆分为两部分,第二行以 Ad 的别名开始。查询的输出结果如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图查询的示例结果。图片由作者提供。

结论

我们已经查看了一些将电子商务业务流程从关系模型转换为图模型的示例步骤,但在这篇文章中无法涵盖所有设计原则。希望你已经看到,图数据库要求的技术水平与关系数据库大致相同,并且迁移并不是一个巨大障碍。

最大的挑战是让你的大脑摆脱传统的关系建模技术,转而以语义或业务建模的方式进行思考。如果你发现图形技术有潜在应用,试着通过一个概念验证项目进行尝试。知识图谱的分析可能性远远超出了你在二维表格中能做到的!

所有图片均由作者提供

为什么你需要在 Python 中使用装饰器来编写 DRY 代码

原文:towardsdatascience.com/why-you-need-to-write-dry-code-with-decorators-in-python-3930ea23f569

数据

使用装饰器来查看你的数据在 Pandas 处理管道中的变化

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Byron Dolon

· 发表在 Towards Data Science · 9 分钟阅读 · 2023 年 5 月 19 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

装饰是关键 — 图片由 Adi Goldstein 提供,来源于 Unsplash

DRY 代码是高效编程的关键之一。

DRY 代表“不要重复自己”,无论是为个人项目编写代码还是为大规模代码库编写代码,这一点都很重要。这意味着你应该拥抱 DRY 代码的概念,创建模块化的解决方案,而不是将逻辑从一个文件复制到另一个文件,或从一个函数复制到另一个函数。

在 Python 中使用装饰器可以帮助实现这一点。简而言之,装饰器允许你操作现有函数并增加其功能。装饰器可以在许多不同的场景中派上用场,但在本文中,我们将探讨如何使用装饰器来改进 Pandas 数据处理管道。

装饰器可以帮助我们理解数据处理管道中每个阶段的情况,这对于处理大规模数据时非常有用。你可以使用它们来跟踪数据的变化,随着对初始 DataFrame 应用不同的转换,这可能会变得难以管理。

随意在你自己的笔记本中跟进。你可以从 Kaggle 这里 下载数据集,CC0 1.0 通用公共领域献身许可下免费使用。

如果你打算跟进,请确保运行以下代码来导入所有必要的库:

import logging
import functools
import time
from datetime import datetime

logging.basicConfig(level=logging.INFO)

Pandas 中的标准数据处理管道

在深入讨论装饰器之前,让我们先看看 Pandas 中现有的数据处理管道,并快速了解它的功能。

FILE_PATH = "Updated_sales.csv"
CHUNK_SIZE = 1000

def read_raw_data(file_path: str, chunk_size: int=1000) -> DataFrame:
    csv_reader = pd.read_csv(file_path, chunksize=chunk_size)
    processed_chunks = []

    for chunk in csv_reader:
        chunk = chunk.loc[chunk["Order ID"] != "Order ID"].dropna()
        processed_chunks.append(chunk)

    return pd.concat(processed_chunks, axis=0)

def split_purchase_address(df_to_process: DataFrame) -> DataFrame:
    df_address_split = df_to_process["Purchase Address"].str.split(",", n=3, expand=True)
    df_address_split.columns = ["Street Name", "City", "State and Postal Code"]

    df_state_postal_split = (
        df_address_split["State and Postal Code"]
        .str.strip()
        .str.split(" ", n=2, expand=True)
    )
    df_state_postal_split.columns = ["State Code", "Postal Code"]

    return pd.concat([df_to_process, df_address_split, df_state_postal_split], axis=1)

def extract_product_pack_information(df_to_process: DataFrame) -> DataFrame:
    df_to_process["Pack Information"] = df_to_process["Product"].str.extract(r'.*\((.*)\).*').fillna("Not Pack")

    return df_to_process

def one_hot_encode_product_column(df_to_process: DataFrame) -> DataFrame:    
    return pd.get_dummies(df_to_process, columns=["Product"])

def process_raw_data():
    df = read_raw_data(file_path=FILE_PATH, chunk_size=CHUNK_SIZE)

    return (
        df
        .pipe(split_purchase_address)
        .pipe(extract_product_pack_information)
        .pipe(one_hot_encode_product_column)
    )

final_df = process_raw_data()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

初始管道的输出 — 作者提供的图片

在这个管道中,本质上有一个“main”函数,它调用几个其他的读取和处理函数,以输出一个准备好用于训练机器学习模型的 DataFrame。这是一个只有三个预处理步骤的简单示例,但你可以通过查看process_raw_data函数来了解它是如何工作的,具体如下:

  1. 使用自定义read_raw_data函数按指定的块大小读取原始数据;

  2. 在结果 DataFrame 对象上调用 Pandas 的pipe方法以实现方法链,这涉及将自定义数据预处理函数按顺序传递给pipe方法。

在这种情况下,我们有三个主要任务要处理我们的数据,这些任务被封装在三个函数中:split_purchase_addressextract_product_pack_informationone_hot_encode_product_columns。所有这些函数以不同的方式转换初始 DataFrame,并使原始数据在进行机器学习时真正可用。

然而,如果在管道的每个连续步骤中很难看到发生了什么。因为我写了代码,我知道每次调用pipe方法时 DataFrame 正在发生什么。但如果基础数据发生变化或其他人想知道发生了什么,返回一些以日志形式呈现的数据会很有用!

实现日志有很多不同的方法,但让我们看看 Python 中的装饰器如何帮助我们了解数据的情况。

什么是装饰器?

因此,我们的目标是理解 DataFrame 在管道的多个阶段处理时发生了什么。解决方案是为我们定义的函数添加一个包装层,以便增加额外的功能,返回有关 DataFrame 的信息等。做到这一点意味着我们需要定义装饰器函数。

这比在每个函数中添加相同类型的日志行更高效。特别是在大规模处理时,想象一下需要在十几个不同的数据处理函数或散布在几个不同文件中的函数中复制粘贴相同的三条日志消息。

从事“元编程”要高效得多,其中我们定义装饰器函数,其目标是操控现有代码。理想情况下,装饰器函数应该做的就是接受一个函数作为输入,并运行该函数并返回它通常的结果,但在函数上添加一些额外的功能。

定义你可以用来处理数据的装饰器

记录函数的执行时间

对于装饰器的初步示例,让我们从简单地记录每个函数的开始时间以及计算函数运行时间开始。

def log_execution_and_time(function):
    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        logging.info(f"{datetime.fromtimestamp(start_time)} - Start execution of: {function.__name__}")
        result = function(*args, **kwargs)
        end_time = time.time()
        logging.info(f"{datetime.fromtimestamp(end_time)} - Ended execution of: {function.__name__}")
        logging.info(f"Duration of {function.__name__}: {round(end_time - start_time, 2)} seconds")
        return result
    return wrapper

有很多内容需要详细了解,所以让我们看看这里发生了什么。我们首先定义了我们的装饰器函数log_execution_and_time。由于装饰器应该接受一个函数作为输入,我们还定义了一个function参数。

如果我们在不使用functools.wraps这一行的情况下使用函数,我们将无法保留现有函数的元数据并在日志中使用它。总是记得在你的装饰器中包含这一行,否则它们将不会按你想要的方式行为。例如,原函数名将不会在包装器中保留(例如,装饰器无法识别read_raw_data函数名是read_raw_data)。你可以在Python 文档中查看一个简单的例子。

接下来,我们定义包装器来实际实现我们想要的附加功能。在这种情况下,我们想要跟踪函数运行所需的时间。这样做很简单:我们只需使用time库将当前时间分配给start_timeend_time变量,然后取这两者之间的差值以获得函数的持续时间。你会看到在我们定义这些变量的中间,我们运行了函数。执行此操作的行是:

result = function(*args, **kwargs)

这就像我们之前读取原始数据时所使用的语法一样:

df = read_raw_data(file_path=FILE_PATH, chunk_size=CHUNK_SIZE)

wrapperfunction中定义argskwargs参数的原因是为了允许我们通过装饰器传递的函数接受任意数量的参数和关键字参数。

在这个函数中,我们还使用logging.info语法写了一些日志行。我们还使用function.__name__来提取日志消息中的实际函数名称。

最后,在包装器函数内部,我们返回先前定义的result,这是原函数执行的结果。在我们的数据处理管道中,这将始终是一个 Pandas DataFrame。在我们的装饰器函数中,我们返回wrapper函数,其中包括我们对现有函数的附加功能。

记录 DataFrame 元数据

既然我们已经了解了装饰器的工作原理,让我们来看看如何在日志中返回关于 DataFrame 的元数据的信息。

def log_dataframe_metadata(function):
    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        result = function(*args, **kwargs)
        logging.info(f"{function.__name__} - result DataFrame number of rows: {len(result)}")
        logging.info(f"{function.__name__} - result DataFrame number of columns: {len(result.columns)}")
        for column, memory in result.memory_usage(deep=True).iteritems():
            logging.info(f"{function.__name__} - {column} memory usage: {memory}")
        return result
    return wrapper

装饰器语法与之前相同。相反,我们首先返回函数的结果,然后基于结果 DataFrame 定义日志消息。在这里,你可以看到你可以使用装饰器对函数的结果执行操作。

在这种情况下,我们想要查看 DataFrame 中的三项内容:

  1. 行数:len(result)

  2. 列数:len(result.columns)

  3. 每列的内存使用量:result.memory_usage(deep=True)

在这里,记住 result 在这种情况下始终是一个 Pandas DataFrame,这意味着我们可以在其上调用 Pandas 方法。根据你定义的函数来转换 DataFrame,这些方法中的每一个都可能会很有用。

例如,如果你向 DataFrame 添加了新列,你可以通过检查列数来验证列是否已添加。此外,如果你只是添加了额外的列,DataFrame 中不应添加任何行,你也可以对其进行合理性检查。

最后,监控每一列的内存使用情况是有用的,以确保你在一些预定义的限制范围内。为了方便,我们的日志装饰器处理 result.memory_usage 的输出,这是一系列包含每列信息的数据,因此我们可以遍历此系列,并以易于阅读的方式返回每列使用的内存。

向我们的管道添加装饰器

现在我们已经定义了装饰器,将它们应用到我们的管道中非常简单。我们之前的初始代码可以像这样修改,以添加我们在装饰器函数中定义的功能。

@log_execution_and_time
@log_dataframe_metadata
def read_raw_data(file_path: str, chunk_size: int=1000) -> DataFrame:
    csv_reader = pd.read_csv(file_path, chunksize=chunk_size)
    processed_chunks = []

    for chunk in csv_reader:
        chunk = chunk.loc[chunk["Order ID"] != "Order ID"].dropna()
        processed_chunks.append(chunk)

    return pd.concat(processed_chunks, axis=0)

@log_execution_and_time
@log_dataframe_metadata
def split_purchase_address(df_to_process: DataFrame) -> DataFrame:
    df_address_split = df_to_process["Purchase Address"].str.split(",", n=3, expand=True)
    df_address_split.columns = ["Street Name", "City", "State and Postal Code"]

    df_state_postal_split = (
        df_address_split["State and Postal Code"]
        .str.strip()
        .str.split(" ", n=2, expand=True)
    )
    df_state_postal_split.columns = ["State Code", "Postal Code"]

    return pd.concat([df_to_process, df_address_split, df_state_postal_split], axis=1)

@log_execution_and_time
@log_dataframe_metadata
def extract_product_pack_information(df_to_process: DataFrame) -> DataFrame:
    df_to_process["Pack Information"] = df_to_process["Product"].str.extract(r'.*\((.*)\).*').fillna("Not Pack")

    return df_to_process

@log_execution_and_time
@log_dataframe_metadata
def one_hot_encode_product_column(df_to_process: DataFrame) -> DataFrame:    
    return pd.get_dummies(df_to_process, columns=["Product"])

@log_execution_and_time
@log_dataframe_metadata
def process_raw_data():
    df = read_raw_data(file_path=FILE_PATH, chunk_size=CHUNK_SIZE)

    return (
        df
        .pipe(split_purchase_address)
        .pipe(extract_product_pack_information)
        .pipe(one_hot_encode_product_column)
    )

final_df = process_raw_data()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

带有日志装饰器的数据处理管道输出 — 作者图片

我们需要做的就是在函数定义的顶部添加 @log_execution_and_time@log_dataframe_metadata 以实现装饰器功能。然后,当我们以相同的方式运行代码时,我们会额外得到每个函数运行时的日志输出。

现在,用户可以看到 DataFrame 在处理管道中移动时发生了什么。结果 DataFrame 与之前完全相同。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最终的 DataFrame 没有改变 — 作者图片

就这些!这只是你可以用装饰器做的许多事情的开始。你可以通过更多的详细日志、向装饰器添加可选参数以使其在不同函数中表现不同,甚至让它们根据需要编辑每个 DataFrame 的结果(如删除空值)等来进一步改善你的数据处理管道。

感谢你抽出时间阅读这篇文章!如果你喜欢我的内容,我希望你通过下面的推荐链接注册 Medium。这将使我获得你月度订阅的一部分,并且你将获得仅限 Medium 会员的独家功能。如果你已经在关注我,感谢你的支持!

## 使用我的推荐链接加入 Medium — Byron Dolon

作为 Medium 会员,你的一部分会员费会流向你阅读的作者,你可以完全访问每一个故事……

byrondolon.medium.com

更多内容- 5 个实用的技巧供有志数据分析师参考 - 掌握电商数据分析 - 在 Pandas 数据框中检查子字符串 - 学习 Python 的 7 个最佳 GitHub 仓库 - 用 Pandas 理解数据的 5(又半)行代码

为什么作为数据科学家你应该考虑使用 Fortran

原文:towardsdatascience.com/why-you-should-consider-using-fortran-as-a-data-scientist-5511e05ef89

探讨 Fortran 可以为数据科学和机器学习带来的好处

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Egor Howell

·发表于Towards Data Science ·阅读时间 8 分钟·2023 年 5 月 16 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Federica Galli拍摄,来自Unsplash

背景

Python 被广泛认为是数据科学的金标准语言,与数据科学相关的所有包、文献和资源总是可以在 Python 中找到。这并不一定是坏事,因为这意味着对于你可能遇到的任何数据相关问题,已经有大量记录的解决方案。

然而,随着数据集的增大和模型复杂度的提升,也许是时候探索其他语言了。这时,老牌语言Fortran可能会再次流行。因此,今天的数据科学家们了解它并尝试实现一些解决方案是非常值得的。

什么是 Fortran?

Fortran,意为“公式翻译器”,是 1950 年代首个被广泛使用的编程语言。尽管其年代久远,它仍然是高性能计算语言,并且比 C 和 C++都要快

最初为科学家和工程师设计,以运行如流体力学和有机化学等领域的大规模模型和仿真,Fortran 至今仍被物理学家频繁使用。我在物理学本科期间也学过它!

它的专长在于建模和仿真,这对包括机器学习在内的众多领域至关重要。因此,Fortran 非常适合解决数据科学问题,因为这正是几十年前它被发明出来的目的。

优势与劣势

Fortran 相对于其他编程语言如 C++和 Python 有几个关键优势。以下是一些主要点:

  • 易于阅读:Fortran 是一种紧凑的语言,只有五种原生数据类型:INTEGER、REAL、COMPLEX、LOGICAL 和 CHARACTER。这种简洁性使其易于阅读和理解,特别是在科学应用中。

  • 高性能:Fortran 通常用于基准测试高性能计算机的速度。

  • 大型库:Fortran 有广泛的,主要用于科学目的。这些库为开发人员提供了大量的函数和工具,用于执行复杂的计算和模拟。

  • 历史数组支持:Fortran 从一开始就支持多维数组,这对于机器学习和数据科学如神经网络至关重要。

  • 为工程师和科学家设计:Fortran 专门为纯数值计算而构建,这与 C/C++和 Python 的通用用途不同。

然而,这并非全是美好如画。以下是 Fortran 的一些缺点:

  • 文本操作:不适合字符和文本处理,因此在自然语言处理方面并不理想。

  • Python 有更多的包:尽管 Fortran 有很多库,但远不及 Python 中的总数。

  • 小社区:Fortran 语言的关注度不如其他语言。这意味着它没有大量的 IDE 和插件支持,也没有很多的 Stack Overflow 回答!

  • 不适合许多应用程序:它显然是一种科学语言,所以不要试图用它来建立网站!

设置 Fortran

Homebrew

让我们快速了解一下如何在你的计算机上安装 Fortran。首先,你应该安装Homebrew (链接在这里),它是 MacOS 的一个包管理器。

要安装 Homebrew,只需运行其网站上的命令:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

你可以通过运行命令brew help来验证 Homebrew 是否已安装。如果没有错误,那么 Homebrew 已经成功安装在你的系统上。

GCC 编译器

由于 Fortran 是一个编译语言,我们需要一个能够编译 Fortran 源代码的编译器。不幸的是,MacOS 并不自带 Fortran 编译器,因此我们需要自行安装一个。

一个受欢迎的选项是 GCC(GNU 编译器集合)编译器,你可以通过 Homebrew 安装:brew install gcc。GCC 编译器是一个支持 C、Go 以及当然还有 Fortran 的编译器集合。GCC 组中的 Fortran 编译器叫做 gfortran,它可以编译所有主要版本的 Fortran,如 77、90、95、2003 和 2008。建议使用 .f90 扩展名来命名 Fortran 代码文件,尽管 关于这个话题有些讨论

为了验证 gfortran 和 GCC 是否成功安装,运行命令 which fortran。输出应该如下所示:

/opt/homebrew/bin/gfortran

gfortran 编译器迄今为止是最受欢迎的,然而还有其他几个编译器。可以在 这里 找到一个列表。

集成开发环境(IDE)与文本编辑器

一旦我们有了 Fortran 编译器,下一步是选择一个集成开发环境(IDE)或文本编辑器来编写 Fortran 源代码。这是个人偏好的问题,因为有许多选项可供选择。个人而言,我使用 PyCharm 并安装 Fortran 插件,因为我不喜欢使用多个 IDE。其他由 Fortran 网站 推荐的流行文本编辑器包括 Sublime TextNotepad++Emacs

运行程序

在我们进入第一个程序之前,重要的是要注意本文不会进行语法或命令的教程。这里的链接 是一个简短的指南,将涵盖所有基本语法。

以下是一个名为 example.f90 的简单程序:

GitHub Gist 由作者提供。

我们的编译过程如下:

gfortran -o example example.f90 

这个命令编译代码并创建一个名为 example 的可执行文件。你可以用任何你喜欢的名字替换 example。如果你没有使用 -o 标志指定名称,编译器将使用一个默认名称,通常对于大多数 Unix 系统来说是 a.out

运行 example 可执行文件的方法如下:

./example

./ 前缀用于指示可执行文件位于当前目录中。此命令的输出将如下所示:

 Hello world
           1

现在,让我们解决一个更“实际”的问题!

性能示例:背包问题

概述

背包问题 是一个著名的 组合优化 问题,其核心是:

一组项目,每个项目都有一个值和重量,必须被打包进一个背包中,以最大化总值,同时遵守背包的重量限制。

尽管问题听起来很简单,但解决方案的数量随着物品数量的增加而呈指数增长。因此,使得在超出一定数量的物品时,通过暴力破解方式解决不可解的

启发式 方法如遗传算法可以用来在合理的时间内找到一个“足够好”或“近似”的解决方案。如果你有兴趣了解如何使用遗传算法解决胶囊问题,可以查看我之前的帖子:

## 遗传算法与胶囊问题:初学者指南

通过实践遗传算法,逐步学习如何解决胶囊问题

pub.towardsai.net

胶囊问题在数据科学和运筹学中有广泛的应用,包括库存管理和供应链效率,因此高效解决这个问题对业务决策非常重要。

在这一部分,我们将看到 Fortran 如何通过纯暴力破解方式快速解决胶囊问题,并与 Python 进行比较。

注意:我们将专注于基本版本,即0–1 胶囊问题,其中每个物品要么完全放入背包,要么完全不放入背包。

Python

让我们从 Python 开始。

以下代码使用暴力破解方法解决 22 个物品的胶囊问题。每个物品在一个 22 元素的数组中编码为 0(不在)或 1(在)(每个元素代表一个物品)。由于每个物品只有 2 种可能的值,总的组合数量是2^(num_items)。我们利用itertools.product方法计算所有可能解决方案的笛卡尔积,然后进行迭代。

作者的 GitHub Gist。

这段代码的输出:

Items in best solution:
Item 1: weight=10, value=10
Item 6: weight=60, value=68
Item 7: weight=70, value=75
Item 8: weight=80, value=58
Item 17: weight=170, value=200
Item 19: weight=190, value=300
Item 21: weight=210, value=400
Total value: 1111
Time taken: 13.78832197189331 seconds

Fortran

现在,让我们用相同的变量来解决相同的问题,但用 Fortran 来实现。与 Python 不同,Fortran 没有执行排列和组合操作的包。

我们的方法是使用模运算运算符将迭代次数转换为二进制表示。例如,如果迭代次数是 6,那么 6 除以 2 的模是 0,这意味着第一个项目未被选择。然后我们将迭代次数除以 2 以将位向右移动,再次取模以获取下一个项目的二进制表示。这个过程对每个项目(即 22 次)重复,最终得到每一个可能的组合。

作者提供的 GitHub Gist。

使用 linux [time](https://linuxize.com/post/linux-time-command/) 命令:

time gfortran -o brute brute_force.f90
time ./brute

输出:

 Items in best solution:
 Item:           1 Weight:          10 Value:          10
 Item:           6 Weight:          60 Value:          68
 Item:           7 Weight:          70 Value:          75
 Item:           8 Weight:          80 Value:          58
 Item:          17 Weight:         170 Value:         200
 Item:          19 Weight:         190 Value:         300
 Item:          21 Weight:         210 Value:         400
 Best value found:         1111
./brute  0.26s user 0.01s system 41% cpu 0.645 total

Fortran 代码速度约为 Python 的 21 倍!

比较

为了获得更直观的比较,我们可以将执行时间绘制为项目数量的函数:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由作者在 Python 中生成的图表。

Fortran 的性能超越了 Python!

尽管 Fortran 的计算时间会增加,但增长幅度远远小于 Python。这确实展示了 Fortran 在解决优化问题时的计算能力,而优化问题在许多数据科学领域至关重要。

总结与进一步思考

尽管 Python 一直是数据科学的首选,但像 Fortran 这样的语言仍然能提供显著的价值,特别是在处理优化问题时,因其固有的数值计算能力。它在解决背包问题的暴力破解中优于 Python,随着问题中项目的增加,性能差距进一步扩大。因此,作为数据科学家,如果你需要在计算能力上获得优势以解决业务和行业问题,你可能需要考虑将时间投入到 Fortran 中。

本文中使用的完整代码可以在我的 GitHub 上找到:

[## Medium-Articles/Optimisation/knapsack 在主分支 · egorhowell/Medium-Articles

你此时无法执行该操作。你在另一个标签页或窗口中登录。你在另一个标签页或窗口中注销…

github.com](https://github.com/egorhowell/Medium-Articles/tree/main/Optimisation/knapsack?source=post_page-----5511e05ef89--------------------------------)

另一个事项!

我有一个免费的通讯,Dishing the Data,我在其中分享每周成为更好数据科学家的技巧。

[## Dishing The Data | Egor Howell | Substack

如何成为更好的数据科学家。点击阅读《Dishing The Data》,作者Egor Howell,Substack 发表的…

newsletter.egorhowell.com

与我联系!

参考资料及进一步阅读

为什么你应该在地理空间开发中使用 DevContainers

原文:towardsdatascience.com/why-you-should-use-devcontainers-for-your-geospatial-development-600f42c7b7e1

了解使用 DevContainers 和 Codespaces 在跨平台和设备上进行无缝地理空间开发的优势

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Maurício Cordeiro

·发表于Towards Data Science ·阅读时间 5 分钟·2023 年 3 月 31 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图像由 Midjourney 创建。说明:“世界被电缆包围,在一个杂乱的环境中,赛博朋克,真实的 3D”

由于 Medium.com 政策的变动(自 2023 年 9 月实施),这篇文章现在可以在geocorner.net上免费获取:www.geocorner.net/post/why-you-should-use-devcontainers-for-your-geospatial-development

介绍

在我最近的一篇文章中,发表于 TDS(使用 Python 配置用于空间分析的最小 Docker 镜像),我展示了如何配置一个包含 GDAL 和 XArray 等基本地理空间分析工具的 Python Docker 镜像。管理包依赖可能很具挑战性,尤其是当涉及到专门的地理空间库时。在这方面,Docker 容器提供了一个优雅的解决方案,用于将系统部署到云服务器。但是,开发过程本身呢?在这篇文章中,我将探讨改变我对开发环境管理看法的关键因素,以及 DevContainers 和 Codespaces 如何改造地理空间开发工作流。

理由

1- 跨平台环境一致性的需求

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图像由 Midjourney 创建 - 说明:“一位程序员在使用不同年代的多台计算机工作”

我正在开发一个新的软件包,用于从巴西空间研究机构(INPE)提取气候数据。上周我不得不在以前的 Windows 笔记本电脑上运行这个软件包,以进行一些性能评估。这次经历突显了跨平台管理依赖项和配置的挑战。这个新软件包需要支持 GRIB2 数据格式,这在国家气象中心、天气机构和研究人员中很常见。然而,在 Windows 上安装必要的依赖项证明是麻烦的,最终导致我的整个开发环境崩溃。这让我花了一整天时间修复 Windows 环境,更糟糕的是,由于每个平台上可用的软件包版本不同,我不得不绕过代码中的语法差异。这次事件强调了拥有一致的开发环境以避免这些麻烦的重要性。

2- 地理空间软件包的复杂性

地理空间软件包本质上是科学软件。因此,它们通常起源于学术界,由研究人员或实习生开发,这些人可能缺乏适当的软件工程专业知识。这些问题在之前的一篇文章中已经讨论过:7 个原因说明科学软件设计不佳

因此,这些软件包常常缺乏支持、稳健性和适当的软件工程设计。这种复杂性在尝试设置和维护开发环境时会带来额外的障碍,特别是在将这些软件包与其他工具和库集成时。

更糟的是,这些软件包有些建立在较旧的软件和命令行工具之上,这些工具使用不同的语言编写,要使它们正常工作不仅需要安装 Python 软件包本身,还需要单独的安装程序和特定的环境配置。举一个例子,cfgrib 需要 eccodes 库来正确编码/解码 GRIB2 文件格式。直接取自 eccodes 文档:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来源:eccodes 文档 (confluence.ecmwf.int/display/ECC/ecCodes+installation)

3- 从任何设备访问代码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Firmbee.com 提供,在 Unsplash 上发布

另一个促使我重新考虑开发环境管理的情况是,当我需要从妻子的电脑访问我的代码时。我知道,这确实是个紧急情况,但有时当你没有笔记本电脑时这种情况是可能发生的。虽然我的所有代码都托管在 GitHub 上,但在她的机器上安装所有所需的开发工具来完成如此小的任务是不现实的。这种挫败感促使我探索 DevContainersCodespaces 作为潜在的解决方案。

解决方案

无缝开发的 DevContainers

DevContainers 是 Visual Studio Code 在 2019 年推出的一项功能,允许开发者在不同的机器之间标准化开发环境。通过利用 Docker 容器的强大功能,DevContainers 封装了依赖项和配置,确保无论本地系统如何都能提供一致的体验。

简而言之,开发者可以将 VSCode 连接到一个容器,并在利用容器化环境的同时保持高质量的开发体验。

GitHub Codespaces

使用 DevContainers,设置一个 GitHub Codespace 来进行你的项目变得轻而易举。GitHub Codespaces 提供了一个功能齐全的、基于云的 VSCode 环境,支持扩展和各种开发工具(图 1)。你甚至可以在其中运行 Jupyter notebooks、绘制地图,并从几乎任何带有浏览器的设备上访问它。

截至 2022 年 11 月 9 日,GitHub Codespaces 每月提供 120 小时的免费核心时间和 15GB 的存储空间。这不足以进行全职编码,但在主要开发机器不可用时,它是一个宝贵的资源,用于紧急编码情况。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1:运行在 GitHub Codespace 内的基于云的 VSCode。

结论

虽然 DevContainers 提供了许多优点,但也有一些潜在的缺点需要注意。一个这样的缺点是需要在开发机器上安装 Docker。对于不熟悉 Docker 的开发者来说,这一要求可能会引入学习曲线并增加设置过程的复杂性。

此外,运行 Docker 容器可能会消耗大量系统资源,这可能导致在硬件能力有限的机器上出现性能问题。在决定是否采用 DevContainers 进行地理空间开发时,重要的是要考虑这些缺点。

你呢?你是否曾经使用过 DevContainers 或 Codespaces?你遇到了什么其他的优点或缺点?请在评论中留下你的想法。

感谢阅读,下篇文章见。

保持联系

如果你喜欢这篇文章并希望支持我作为作家,考虑成为一名 Medium 会员。每月仅需 5 美元,我将从你的会员费中获得少量佣金,对你没有额外费用。或者你也可以随时 请我喝咖啡

[## 通过我的推荐链接加入 Medium - Maurício Cordeiro

阅读每一个来自Maurício Cordeiro的故事(以及 Medium 上的成千上万其他作者的故事)。你的会员费用将直接…

cordmaur.medium.com](http://cordmaur.medium.com/membership?source=post_page-----600f42c7b7e1--------------------------------)

为什么你的数据管道需要闭环反馈控制

原文:towardsdatascience.com/why-your-data-pipelines-need-closed-loop-feedback-control-76e28e3565f?source=collection_archive---------3-----------------------#2023-09-10

公司和云的复杂性现实要求新的控制和自主水平,以在规模上实现业务目标

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Jeff Chou

·

关注 发表在Towards Data Science ·9 分钟阅读·2023 年 9 月 10 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由Cosmin Paduraru提供

随着数据团队在云上规模化,数据平台团队需要确保他们负责的工作负载达到业务目标, 这是我们 Sync 的主要任务。在拥有数十名数据工程师和数百个生产作业的大规模环境下,由于技术和人为因素的种种原因,控制其性能变得不可行。

今天缺失的环节是建立一个 闭环反馈系统,它可以自动驱动管道基础设施向业务目标前进。这句话有些冗长,所以让我们深入探讨一下这个问题。

今天数据平台团队面临的问题

数据平台团队必须管理从管理层到工程师的基本不同的利益相关者。这两组团队往往有相互对立的目标,平台经理可能会受到双方的挤压。

我们与平台经理和数据工程师的许多实际对话通常是这样的:

“我们的 CEO 希望我降低云成本,并确保我们的服务水平协议得到满足,以保持客户满意。”

好的,那么问题是什么?

“问题是我实际上不能直接更改任何东西,我需要其他人帮助,这就是瓶颈。”

所以基本上,平台团队在实际实施改进时感到束手无策,面临巨大的摩擦。让我们深入探讨一下原因。

是什么阻碍了平台团队?

  • 数据团队超出了技术范围 — 调整集群或复杂配置(Databricks、Snowflake)是一项耗时的任务,数据团队更愿意集中精力在实际管道和 SQL 代码上。许多工程师没有相应的技能、支持结构,甚至不了解他们工作的成本。识别和解决根本原因问题也是一项令人生畏的任务,这阻碍了建立一个功能正常的管道。

  • 过多的抽象层 — 让我们聚焦于一个堆栈:Databricks 运行其自己版本的 Apache Spark,该 Spark 运行在云提供商的虚拟计算上(AWS、Azure、GCP),配有不同的网络选项和不同的存储选项(DBFS、S3、Blob),而且所有这些都可以在一年中的任何时候独立和随机更新。选项的数量令人不堪重负,平台人员难以确保一切都是最新和最优的。

  • 遗留代码 — 一个不幸的现实就是遗留代码。公司内部的团队往往会发生变化,人来人往,随着时间的推移,任何一个特定工作所需的知识可能会逐渐消失。这种现象使得调整或优化特定工作变得更加困难。

  • 变化令人恐惧 — 变革本能上是令人害怕的。如果一个生产任务正在进行,我们是否愿意冒险进行调整?老话说得好:“如果它没坏,就不要修。”这种恐惧往往是现实的,如果一个任务不是幂等的或存在其他下游影响,任务失败会带来真正的麻烦。这种心理障碍阻碍了对任务性能的改善尝试。

  • 在规模上,有太多作业 — 通常平台经理负责监督数百甚至数千个生产作业。未来公司的增长确保这个数字只会增加。鉴于上述所有情况,即使你有一个本地专家,逐个调整作业也是不切实际的。虽然这对少数几个高优先级的作业可能有效,但它使公司的大部分工作负载或多或少被忽视了。

显然,对于数据平台团队来说,要快速提高系统在规模上的效率是一场艰难的战斗。我们相信解决方案在于管道构建方式的范式转变。管道需要一个闭环控制系统,它不断推动管道朝着业务目标前进,而无需人工干预。让我们深入探讨一下。

闭环反馈控制对于管道意味着什么?

当前的管道属于“开环”系统,即作业只是运行而没有任何反馈。为了说明我说的内容,下面的图片展示了“作业 1”每天运行,每次运行的成本为$50。假设业务目标是将该作业的成本降至$30。那么,除非有人实际采取行动,否则成本将会在可预见的未来保持在$50,如成本与时间的图表所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

如果我们有一个系统,实际反馈作业的输出统计数据,以便第二天的部署可以得到改进,会是什么样的呢?它可能会像这样:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

你看到的是一个经典反馈循环,在这种情况下,期望的“设定点”是$30 的成本。由于这个任务每天运行,我们可以将实际成本的反馈发送到一个“更新配置”模块,该模块接收成本差异(在此例中为$20),从而对“作业 1”的配置进行更改。例如,“更新配置”模块可能会减少 Databricks 集群中的节点数量。

这在生产环境中看起来是什么样的?

实际上,这并不是一蹴而就的。“更新配置”模型现在负责调整基础设施,尝试将成本降低到$30。正如你所想的,随着时间的推移,系统将会改进,并最终达到期望的$30 成本,如下图所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

这一切听起来可能很美好,但你可能会疑惑,“这个神奇的‘更新配置’模块是什么?”这就是问题的关键。这个模块是一个数学模型,它可以输入一个数值目标差异,并输出一个基础设施配置或代码更改。

这不是一件容易的事情,并且会根据目标(例如成本 vs. 运行时间 vs. 利用率)而有所不同。这个模型必须基本预测基础设施变更对业务目标的影响 —— 这并不容易。

没有人可以预测未来

一个微妙的事情是,“更新配置”模型并不是 100%准确的。在第四个蓝点中,您实际上可以看到成本在某一点上上升。这是因为模型试图预测一次配置变更将降低成本,但由于没有什么能够 100%准确预测,有时它在局部上可能是错误的,因此成本可能在单次运行中上升,而系统正在“训练”。

但是,随着时间的推移,我们可以看到总成本实际上是在下降的。您可以将其视为一个智能的试错过程,因为以 100%准确度预测配置变更的影响是完全不可能的。

“那么,有什么关系?” — 设定任何目标并行动

上述方法是一种通用策略,而不仅仅是节省成本。上述的“设定点”只是数据平台人员设定的一个目标。它可以是任何可测量的目标,例如运行时间就是一个很好的例子。

假设我们希望一个工作在 1 小时(或 SLA)内完成。我们可以让上述系统调整配置,直到达到 SLA。或者更复杂一点,同时考虑成本和 SLA 目标?完全没有问题,系统可以优化以达到您在多个参数上的目标。除了成本和运行时间外,其他业务用例目标还包括:

  • 资源利用率: 独立于成本和运行时间,我是否正确地利用了我的资源?

  • 能源效率: 我是否尽可能少地消耗资源,以减少碳足迹?

  • 容错性: 我的工作实际上是否对故障具有弹性?这意味着我是否需要过度规格化它,以防我被抢占,或者以防没有可用的 SPOT 实例?

  • 可扩展性: 我的工作是否具备可扩展性?如果输入数据增加 10 倍,我的工作会崩溃吗?

  • 延迟: 我的工作是否达到了延迟目标?响应时间目标?

理论上,一个数据平台的人只需设定目标,然后一个自动系统可以迭代地改进基础设施,直到达到目标。没有人参与其中,没有工程师需要上手。平台团队只需设定他们从利益相关者那里收到的目标。听起来像是一个梦想。

到目前为止,我们一直比较抽象。让我们深入探讨一些具体的使用案例,希望能够引起人们的兴趣:

示例功能 #1:按业务目标对作业进行分组

假设您是一个数据平台经理,负责监督数百个生产作业的运行。目前,它们都有自己的成本和运行时间。下面的简单图表显示了一个卡通示例,基本上所有作业都随机分布在成本和运行时间图表上。

如果你想在大规模上降低成本怎么办?如果你想一次性改变许多作业的运行时间(或 SLA)怎么办?现在,你可能得请求工程师帮助你修改所有作业(祝好运)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

现在假设你对所有作业实施了上述闭环控制系统。你只需设定作业的高级业务目标(在此案例中为 SLA 运行时间要求),反馈控制系统将尽力找到实现这些目标的基础设施。最终状态将是这样的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

在这里,我们看到每个作业的颜色代表了不同的业务目标,依据 SLA 定义。幕后闭环反馈控制系统改变了集群/仓库的大小、各种配置,甚至调整了整个管道,以尽可能低的成本达到 SLA 运行时间目标。通常,较长的作业运行时间会导致更低的成本机会。

示例功能 #2:自愈作业

大多数数据平台的工作人员可以确认,他们的数据管道总是在变化。两个非常流行的场景是:数据规模随时间增长和代码变更。这两者都会导致成本和运行时间出现不稳定的行为。

以下插图展示了基本概念。我们从左到右走一遍示例:

  • 开始: 假设你有一个作业,并且随着时间的推移数据规模增长。通常情况下,你的集群保持不变,因此成本和运行时间都会增加。

  • 开始反馈: 随着时间的推移,运行时间接近 SLA 要求,反馈控制系统在绿色箭头处介入。此时,控制系统会调整集群,以保持在红色虚线下,同时最小化成本。

  • 代码变更: 在某个时点,开发者推送了一个新的代码更新,导致成本和运行时间的激增。反馈控制系统介入,并调整集群以更好地适应新的代码变更。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

希望这两个示例说明了闭环控制管道的潜在好处。当然,实际上还有许多细节被省略了,一些设计原则公司必须遵循。

一个重要的方面是配置能够在出现问题时恢复到之前的状态。此时,一个幂等的管道将非常理想,以防需要许多迭代。

结论

数据管道是复杂的系统,像其他复杂系统一样,它们需要反馈和控制来确保稳定的性能。这不仅有助于解决技术或业务问题,还能显著释放数据平台和工程团队的精力,使他们能专注于实际构建管道。

正如我们之前提到的,这在很大程度上依赖于“更新配置”模块的性能。这是反馈循环成功所需的关键情报。构建这个模块并非易事,也是目前主要的技术障碍。它可以是一个算法或机器学习模型,并利用历史数据。这是我们过去几年中一直在致力于的主要技术组件。

在我们下一篇文章中,我们将展示该系统在 Databricks Jobs 中的实际应用,让你相信我们讨论的内容是真实的!

对了解更多关于 Databricks 流水线的闭环控制感兴趣?请联系 Jeff Chou 和其他 Sync Team成员。

相关内容

为什么你的 RAG 在生产环境中不可靠

原文:towardsdatascience.com/why-your-rag-is-not-reliable-in-a-production-environment-9e6a73b3eddb

以及你应该如何正确调整它

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Ahmed Besbes

·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 10 月 12 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:Ivan JermakovUnsplash

随着 LLM 的兴起,检索增强生成(RAG)框架 也因能够在数据上构建问答系统而获得了广泛关注。

我们都见过那些聊天机器人与 PDF 或电子邮件对话的演示。

虽然这些系统确实令人印象深刻,但在生产环境中,如果不进行调整和实验,它们可能不够可靠。

在这篇文章中,我探讨了 RAG 框架背后的问题,并提出了一些提升其性能的技巧。这些技巧包括利用文档元数据和调整超参数。

这些发现基于我作为一名 ML 工程师的经验,我仍在学习这项技术并在制药行业构建 RAG。

事不宜迟,我们来看一看 🔍

如果你对提高构建 ML 系统的生产力感兴趣,可以随时订阅我的 通讯

我每周发送关于编程和系统设计的见解,帮助你更快地推出 AI 产品。

RAG 简要介绍 ⚙️

首先,我们要弄清楚基本概念。

这是 RAG 的工作原理。

它首先接受一个输入问题,并从外部数据库中检索相关文档。然后,它将这些片段作为上下文传递给提示,以帮助 LLM 生成一个 增强的 回答。

这基本上是说:

“嘿 LLM,这里是我的问题,还有一些文本片段帮助你理解问题。给我一个回答。”

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

不要被这个图表的简单性所欺骗。

事实上,RAG 隐藏了某些复杂性,并且涉及以下组件:

  • 解析不同格式的外部数据的加载器:PDF、网站、Doc 文件等。

  • 用于将原始数据分割成较小文本块的分割器

  • 用于将文本块转换为向量的嵌入模型

  • 用于存储和查询向量的向量数据库

  • 用于将问题和检索到的文档结合的提示

  • 用于生成答案的 LLM

如果你喜欢图示,这里有另一个给你。

稍微复杂一点,但它展示了索引和检索过程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者修改的图像

哎呀!

不过别担心,你仍然可以很快原型化你的 RAG。

LangChain这样的框架抽象了构建 RAG 涉及的大部分步骤,使得原型设计这些系统变得容易。

这有多简单?五行代码就搞定了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当然,这种代码片段的明显简化总是有问题的:根据你的使用情况,它们并不总是能按原样工作,需要非常仔细的调整。

它们作为快速启动的工具很棒,但绝对不是工业化应用的可靠解决方案。

RAG 的问题

如果你开始构建 RAG 系统而几乎没有调整,你可能会感到惊讶。

这是我在使用 LangChain 和构建 RAG 的前几周所注意到的情况。

1 — 检索到的文档不总是与问题相关。

如果你仔细查看数据库检索到的块,你有时会注意到它们并不完全与问题相关。它们与问题在某种程度上相似,但在许多情况下,它们并没有完全对准用户的请求。此外,这些块往往冗余且重叠,这使得生成的答案……重复。

这是一个小实验,展示了与查询(在嵌入空间)最相似的文档既不是最相关的,也不是最语义相似的(这真令人惊讶!)。

在这个例子中,模型忽略了句子的情感,并且对复数形式(添加“s”将相似度降低了 8%)也不够鲁棒。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2 — RAG 系统缺乏基本的世界知识 如果你指示 RAG 仅依赖外部数据库来提供答案,它可能会通过编造事实或拒绝回答简单问题来让你感到惊讶。

我曾经在与网球相关的 Reddit 帖子上构建过一个 RAG。虽然它成功回答了有关当时温布尔登公开赛的问题,但对网球规则的一般问题却无能为力。

我知道你不应该问超出系统数据范围的问题,但如果你部署了基于 RAG 的服务,你应该对用户提出的任何问题做好准备。

3 — 速度

根据你使用的数据库类型和 LLM 的大小,你的 RAG 可能会非常慢。这会降低用户体验。你应该在构建解决方案之前先做一些基准测试。

4 — 一个有损的过程

由于 RAG 将原始数据分割成块,嵌入这些块,并检索最相似的 K 个,固有的信息会逐渐丧失和提炼。

因此,保留外部文档中的所有信息并提供完美的答案几乎是不可能的。

记住这一点很重要:我们在这里处理的是很多近似值。

提升 RAG 性能的技巧

如果你盲目地将 RAG 接入数据,它可能会表现出奇怪的行为。

你可以通过应用一些技巧和最佳实践来解决这些问题。

👉 检查和清理数据

“垃圾进,垃圾出”的原则仍然适用于 RAG。

如果你提供的文档是噪声较大的(例如 HTML 标签!)、彼此不一致、令人困惑,甚至是重复的,那么生成的答案将会受到影响,并反映出这些缺陷。

在选择 RAG 将使用的数据时要小心。

例如,与其将固有较短的 FAQ 与长 PDF 合并,不如创建两个向量存储。

这里有一个快速的质量检查来评估数据质量:尝试回答一两个问题。如果你发现很难用现有的数据支持得到答案,那就不要指望 RAG 表现得更好。

👉 微调块大小、top_k 和块重叠

这些参数非常重要,因为分块是 RAG 系统的核心部分。(记住,我们从存储中检索到的文档是!)

这些参数影响检索结果的质量,从而影响生成的答案。

例如,小块大小可能无法捕捉到足够的上下文,而大块大小可能引入大量无关的噪声。

这里没有什么神奇的数字可以给你。最好的解决方案是进行实验并在测试集上验证这些超参数。(是的,构建测试集很重要!)

Pinecone 有一个关于分块策略的指南,值得阅读。

👉 利用文档元数据

有时候在从数据库检索到文档后,基于可用的元数据(例如日期)进行过滤是有用的。这提供了另一种过滤维度,而且不需要额外费用。

👉 调整你的系统提示以给 RAG 一个默认行为或特定指令。这是我在一个项目中使用的系统提示:

You're a helpful assistant that works for a big pharmaceutical company.

You'll be asked questions about diseases and treatments among other things.

Stick to the question, provide a comprehensive answer and don't invent facts.

Use the following pieces of context to answer the question at the end.

If you don't know the answer, just say that you don't know, don't try to make up an answer.

%CONTEXT%
{context}

%Question%
 {question}
Answer:

👉 转换输入查询

如果你表达得不够清楚,RAG 可能找不到它需要的相关文档来建立有用的上下文。解决这一问题的一种方法是通过 LLM重新表述查询,然后再试一次。

另一种解决方案可能是尝试HyDE方法,它接受查询,生成一个假设性回应,并在嵌入空间中使用这两者进行检索。

查询转换是一个令人兴奋的领域,它可能会改善 RAG 系统,因为 LLMs 往往在较小的查询上表现更好。

结论

今天就到这里!

希望你喜欢这篇文章,并从中学到了有关 RAG 的一些有用信息。

由于这项技术相对较新,许多优化技术将会出现,使这个框架更加可靠,并准备好用于工业化应用。

如果你也在构建 RAG,我很好奇你使用了哪些优化技术。请在评论中告诉我。

下次见👋!

训练中断会毁掉我的马拉松吗?

原文:towardsdatascience.com/will-a-training-break-ruin-my-marathon-465e94cb949e

大规模数据分析训练中断对马拉松准备的表现成本

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 barrysmyth

·发表在Towards Data Science ·10 min 阅读·2023 年 1 月 13 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片Anna Shvets拍摄。

TLDR;

  • 超过 50%的跑者在训练马拉松时会经历至少 7 天的跑步中断。更长时间的训练中断(≥7 天)也经常发生。但仍有希望,因为即使是长时间的中断也不一定会毁掉你的马拉松。

  • 然而,中断确实会带来成本,训练中断的完成时间比没有长时间训练中断时的完成时间长 2%–9%。

  • 完成时间的成本取决于中断的性质(其持续时间和时机)以及跑者的类型(男性与女性、年轻与年长、快速与慢速)。

  • 通过理解这些成本,跑者可以在训练中断后调整马拉松目标,以确保更好的比赛结果。

介绍

训练马拉松并不容易。在许多方面,它比实际跑马拉松更困难。事实上,许多报名并开始训练马拉松的跑者,最终未能到达起跑线,更不用说完成比赛了。作为那些开始马拉松之旅的人的百分比,未能参赛者的估计数从15%到高达50%不等。

值得注意的是,最近的都柏林马拉松(2022 年)中,几乎 10,000 名注册跑者未能如期出现。然而,这至少部分是由于疫情的影响,因为许多注册转移自取消的 2020 年和 2021 年的赛事。在‘正常’年份中,都柏林的缺席率可能接近20%

好消息是,如果你能够坚持度过漫长的训练月份,并且在马拉松起跑线上(相对)完好无损,那么你完成比赛的机会非常高。通常,只有 2-3%的参赛者会在完成前退出,尽管这通常取决于比赛当天的情况。例如,2018 年波士顿马拉松的恶劣寒冷天气导致了大量参赛者未能完成比赛,甚至包括精英选手在内,5%的男性和 3.8%的女性在比赛中途退出

跑者们经常在训练未能按计划进行时,a放弃他们的马拉松训练和比赛。有些人因伤病或生病离比赛日过近而无法及时恢复。其他人由于各种原因(如繁忙的生活方式、家庭责任、旅行等)而训练受到干扰,感到比赛准备被毁掉,因此选择退出。但情况真的如此吗?

  • 训练中断是否意味着比赛当天的灾难?

  • 如果不是灾难,那是否有证据表明训练中断会影响表现?

  • 如果确实有表现成本,那么这种成本如何依赖于中断的性质(中断的持续时间和训练期间的时机)或跑者的类型(性别、年龄、能力)?

在这篇文章中,我将总结我参与的一项近期研究的结果,该研究利用了一个非常大的马拉松跑者及其训练活动的数据集。艰苦的工作由Ciara Feely完成,她目前是都柏林大学的最后一年博士生,该研究最近由期刊Frontiers in Sports and Active Living发表,Frontiers in Sports and Active Living

数据集

这项研究基于 292,323 名跑者(每位跑者比赛日前最多 12 周)的 15,697,711 条跑步活动的数据集。这些跑者在 2014 年至 2017 年期间完成了 509,979 场马拉松,并将他们的训练和比赛数据上传到 Strava。Strava Inc.根据与作者的数据共享协议提供了这些数据的匿名版本。更多数据集细节可以在 这里 查阅,简而言之,我们注意到以下几点:

  • 男性和女性之间的比例为 80/20,男性的平均年龄约为 40 岁,而女性的平均年龄为 38 岁。

  • 男性跑者的平均马拉松完成时间稍低于 4 小时,而女性的平均完成时间略高于 4 小时 24 分钟。

  • 平均而言,跑者每周训练时间稍超过 3 天,并且在训练期间每周完成平均 40–41 公里。

重要的是,这个数据集只包括了参加了比赛日的跑者;即,它不包括那些开始马拉松训练但最终未能到达起跑线的跑者。因此,这项研究应视为对未严重到足以使跑者放弃马拉松的训练干扰的调查。

此外,我们专注于每个跑者在特定年份的最快马拉松成绩。我们这样做主要是为了避免包括那些作为其他比赛训练的一部分的马拉松。

在我们的分析中,我们还基于以下三个不同的跑者分组进行研究:

  1. 性别男性女性 跑者。

  2. 年龄年轻(≤40 岁)与 年长(>40 岁)跑者。

  3. 能力更快更慢 的跑者;更快的跑者在我们的数据集中拥有的 个人最佳(PB)马拉松时间少于 240 分钟。更快的跑者占数据集中所有跑者的 53%以上,并且主要(88%)是男性。

接下来,我们将比较这些分组的各种特征,探索训练干扰与完成时间之间的关系。

训练干扰的频率

训练干扰 是指一段时间——几天连续——没有任何记录的训练活动。由于每周训练期间的常规恢复日,预计会有一些短暂的训练干扰。实际上,由于我们的跑者每周跑步大约 3 次,因此我们会发现每周训练中有≤4 天的连续间隔,具体取决于训练日如何分布。然而,更长时间的训练间隔(≥7 天)应当较少出现,可能指示一些未预期的训练干扰。

较长的训练中断发生的频率如何?为探索这一点,我们计算了训练历史中包含“最长 训练中断” ≥7 天的跑者比例,并比较了这些比例在性别、年龄和能力上的差异,以及中断时机的差异;早期 中断发生在比赛日前 8-12 周,而晚期 中断发生在比赛日前 3-7 周。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

按性别(A)、年龄(B)、中断时机(C)和能力(D)划分的最长训练中断时长的跑者比例。转载自 Frontiers 根据 CC BY 许可证。

上面的图表(A-D)展示了不同训练中断时长(7-42+天,x 轴)对性别(A)、年龄(B)、中断时机(C)和跑者能力(D)的结果。大多数跑者在比赛当天经历了至少 7 天的训练中断,而更长时间的中断则越来越不常见。值得注意的是,10-20%的跑者经历了至少 14 天的中断,这可能被视为比单纯的忙碌日程更严重的中断,例如伤病或疾病。

图 A、B 和 D 表明,基于性别、年龄或能力,经历不同时长中断的跑者比例差异不大;男性、年轻跑者和更快的跑者的比例有所增加,但大多数情况下差异较小。

当我们考虑中断时机时,情况有所不同,见图 C。早期的中断比晚期中断更为常见(无论时长如何),这表明跑者在接近比赛日时,经历训练中断的可能性较低。一个解释可能是,随着比赛日的临近,跑者变得更加投入,不愿意牺牲训练,除非必须。然而,也必须承认,由于我们的数据集排除了未完成训练的跑者,这些跑者可能倾向于晚期中断。换句话说,经历晚期中断的跑者的退赛率可能高于早期中断的跑者。例如,晚期受伤可能没有足够的时间恢复,而如果受伤发生在训练早期则可能会有所不同。因此,这种分析可能低估了经历晚期中断的跑者的比例,因为至少有一些跑者可能未能到达起跑线。尽管如此,这种效应不太可能完全解释跑者经历早期和晚期中断之间的相对差异(35%-60%)。

训练中断的成本

尽管马拉松训练中出现较长时间的干扰(≥7 天)并不罕见,但好消息是,这些干扰不一定会对比赛日造成致命影响。大多数跑者经历过干扰,但大多数跑者仍能顺利到达起跑线。然而,很可能长时间的训练干扰会有一定代价。可以推测,错过两周的训练将会对表现产生影响,特别是当这两周的训练是在高峰期错过的,此时跑者正在适应一些非常长的跑步,以模拟比赛日的疲劳。

为了探索这一点,我们区分了两种类型的训练历史:

  1. 干扰的训练历史是指最长训练干扰时间为≥7 天的情况。

  2. 未干扰的训练历史是指最长训练干扰时间为<7 天的情况。

接下来,我们关注了那些至少跑过 2 场马拉松的跑者,其中一段训练历史受到干扰而另一段则未受干扰;这产生了一组包含 43,933 名跑者(83.39% 男性和 26.61% 女性)的数据,这些跑者都有受到干扰和未受干扰的训练历史。

然后,我们将干扰马拉松(与干扰训练相关的马拉松)的完成时间与相应的未干扰马拉松的完成时间逐一比较。我们根据这两个完成时间之间的相对差异估算了“干扰成本”。例如,如果一个干扰马拉松的完成时间为 244 分钟,而同一跑者最近的未干扰马拉松完成时间为 235 分钟,那么这个 9 分钟的减慢代表了一个 3.8%的干扰成本,当转换为未干扰完成时间的百分比时。

现在,还有其他几个因素可能解释这种时间差异——不同的路线、不同的天气条件、不同的训练方法(尽管有干扰)、比赛日的配速或补给变化、基础体能差异、调整后的恢复策略等——但预计这些因素在干扰和未干扰的训练中出现的可能性是相同的,因此通过我们使用的数据集的规模,这些因素将会被平均化

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

按性别(A)、年龄(B)、干扰时机(C)和能力(D)计算的训练干扰平均成本;摘自这里,根据CC BY 许可证发布。

训练干扰的成本在上面的图表中进行了总结,再次按最长训练干扰的持续时间(x 轴)进行分类,并通过比较训练历史中的性别(A)、性别(A)、年龄(B)、干扰时机(C)和跑者能力(D)进行分析。

正如我们可能预期的那样,较长的中断与更大的成本相关。在(A)中,对于给定的中断持续时间,男性的中断成本高于女性,这在基于 21–27 天的中断持续时间的统计上是显著的;顺便提一下,统计显著性是基于z-testt-test(适用时)(p<0.01),在图中用填充标记和实线表示(有关使用的统计方法的更多信息,请参见论文)。较快的跑者或较晚的中断也与比较慢的跑者或较早的中断更大的成本相关;年轻跑者经历的成本高于年长跑者,尽管超过 7–13 天的差异在统计上不显著。

在(A)中,我们可以看到,男性跑者在经历 21–27 天的训练中断后,完成时间增加了近 8%;对于同样受中断影响的女性跑者,成本略高于 4%。这种性别差异可能与男性跑者往往高估自己的能力以及他们跑得不够有纪律的比赛倾向有关,这可能会加重训练中断对比赛日的影响。

此外,最快和最慢跑者之间的成本差异最大。平均而言,较快的跑者的中断成本约为 5.5%(峰值接近 9%),而较慢跑者的平均成本不到 3%(峰值不到 4%)。其中一个原因可能是较快的跑者中男性占比更高(88%),正如上文所讨论的。

最后,在得出结论之前,值得注意的是,在类似的研究中,我们总是需要小心避免混淆因果关系和相关性。这里的重点是通过关联性来看相关性。较长的中断与较慢的完成时间相关,但是否导致较长的完成时间还需要进一步的研究。至少,更仔细地控制如赛道和天气条件、位置和补给策略、训练实践,或跑者从休息中恢复的速度等因素将是重要的,但这需要另一天的工作。

结论

马拉松训练并不总是顺利进行。一些跑者可能永远无法到达起跑线,但那些能够到达的跑者通常会经历较长时间的训练中断,有些人可能需要多周的时间来恢复疾病或伤害。

幸运的是,这样的中断并不一定会过早终结跑者的马拉松梦想。然而,长时间的训练中断确实会带来表现上的成本。证据表明,它们与完成时间的延长有关,可能会比正常情况下长 2%–9%,具体取决于中断的性质(持续时间和时机)以及跑者的类型(性别、年龄、能力)。

虽然确认干扰与表现成本相关并不令人惊讶,但量化这些成本是有用的。例如,它可以帮助跑者更好地规划即将到来的比赛日。当然,更好地理解训练干扰与完成时间之间的关系应该能帮助跑者调整他们的比赛目标,并在训练受到干扰时调整他们的配速。这将帮助他们确保从下一个马拉松中获得最大的收益。

ChatGPT 会取代数据科学工作吗?

原文:towardsdatascience.com/will-chatgpt-take-data-science-jobs-73e4fbb706b8

观点

数据科学的黄金时代是否终于结束了?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Natassha Selvaraj

·发表于Towards Data Science ·8 分钟阅读·2023 年 9 月 15 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:iStock

如果你正在阅读这篇文章,你可能已经在数据行业有一份工作,或者正在寻找进入这个领域的机会。

随着过去一年生成型人工智能领域的所有进展,你可能担心数据科学工作会被自动化取代。

一年前,我会嘲笑任何提及将我的数据科学工作自动化的可能性的人。

实际上,我甚至写了一篇完整文章来嘲笑 AI 能够取代数据科学家的想法——我的意思是,我们编写代码、构建机器学习模型、分析数据,并将复杂的信息传达给非技术相关方。

我们的工作很困难。这些技能需要多年的磨练。AI 可以提高数据团队的效率和协作,但不可能取代我们所做的实际工作。

然而,上述博客文章是在 ChatGPT 发布之前写的。

自那时起,我们见证了生成型 AI 领域的范式转变。

在这篇文章中,我将基于生成型 AI 领域的现有进展重新评估我对数据科学未来的立场。

基于我广泛的研究和行业专家的见解,我将呈现一系列观点,解释为什么 ChatGPT 可能会取代数据科学家,以及它可能不会取代的原因。

我将审视辩论的两个方面,并留给你,读者,去做出明智的决定,判断生成型 AI 是否会使数据科学家变得过时。

如果你更喜欢视频格式,请观看这个:

点击这里查看视频版本

糟糕的情况:为什么数据科学工作面临风险

1. ChatGPT 能快速编写代码

数据科学家大约花费40%-50%的时间在编写代码上。

不仅如此,ChatGPT 不仅能够编写代码,而且变得非常熟练,速度也非常快。

该聊天机器人已经在多个顶级公司通过了编码面试,可以将手绘草图转化为完全成熟的网站,并且能够从用户故事中构建数据库表

实际上,这家软件公司 CEO 表示,该模型将完成编码任务的时间从9 周缩短到仅仅几天

这是超过 20 倍的效率提升——这将显著减少组织需要雇佣的人员来完成编程任务。

2. ChatGPT 能够摄取和分析数据

现在,你可能在想,“编程只是数据科学家工作的一小部分。我们的工作涉及机器学习建模、统计分析以及向利益相关者提供见解。”

好吧,ChatGPT 也能做这些事情。

该模型的新代码解释器插件,现在更名为“高级数据分析”,允许你在 ChatGPT 界面内上传和分析数据。

这是一个高级数据分析插件根据纯文本要求构建客户细分模型的截图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

该模型能够预处理数据集,找到 K 均值聚类的最佳簇数,构建算法,并且根据模型的输出提供数据驱动的见解。

根据商业内幕的报道,编程和分析相关的角色是最容易被 AI 自动化取代的职位之一。

马克·穆罗,布鲁金斯学会的高级研究员,曾研究过 AI 对美国劳动力的影响,他声称这是因为 ChatGPT 可以比人类更快地产生代码,并且擅长分析数据和预测结果。

3. 架起人类与技术之间的桥梁

我合作过的许多非技术型利益相关者在阅读 Excel 表格中呈现的数据时感到困惑。这些都是忙碌的人,他们需要以简单、直接的语言呈现结果。

例如,营销团队会问诸如“哪些客户最有可能再次购买产品 X”这样的问题,并期望你在几页幻灯片中给出答案,突出显示驱动重复购买的预测指标。

作为一个语言模型,这样的使用场景正是 ChatGPT 的强项。

你可以问它以下问题:

I work in marketing and have no technical background whatsoever. 
Can you explain, based on this dataset, which of my customers are most likely
to purchase product X again and why?

ChatGPT 不会给你提供复杂的图表和计算,而是会准确告诉你有关客户数据集所需了解的内容,使你具备做出数据驱动的营销决策所需的知识。

ChatGPT 的对话能力,加上其技术能力,可以让数据科学和分析领域变得更加普及。

以前需要你对 Excel 或 Python 有很强掌握的任务,现在可以轻松使用像高级数据分析插件这样的工具完成。

此外,ChatGPT Enterprise刚刚发布,允许公司为其员工购买语言模型的订阅。

OpenAI 称这是“迄今为止最强大的 ChatGPT 版本”,因为它没有使用限制,性能提升至两倍快。它还提供无限访问高级数据分析插件的权限。

由于这个版本的 ChatGPT 符合 SOC2 标准,员工可以将公司专有的数据集直接上传到 ChatGPT 界面,而无需担心泄露敏感的公司信息。

积极的一面:为什么你的数据科学工作是安全的

好吧,之前部分的悲观言论可能让人有些沮丧,我几乎可以看到你们中的一些人对屏幕摇头,不同意我提出的观点。

但不用担心!

我们现在将深入探讨更令人安心的方面,并探讨为什么许多业内人士认为数据科学工作是安全的(至少现在是)。

1. ChatGPT 无法进行复杂的数据分析

数据科学家(和分析师)通常从多个来源收集数据,并使用各种工具从中提取洞察。

这项工作并不像使用高级数据分析插件将单一数据集上传到 ChatGPT 那么简单。

例如,我目前正在处理一个需要分析数千个 PDF 文件的项目。

这样的任务具有挑战性,因为数据量很大,每个 PDF 文档包含多种类型的信息,如表格和图像。

在这项任务中,ChatGPT 的帮助有限,因为它无法访问分析甚至一个文档所需的包:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

当然,它能够帮助我编写提取和分析数据所需的代码——但没有编程知识的人无法运行、验证或调试这些代码。

此外,大多数公司将其信息存储在 SQL 数据库中。

高级数据分析插件仅限于文件上传,这意味着它不能直接与公司数据库交互。

你仍然需要具备数据库管理和 SQL 查询必要技能的人来访问、解释和分析这些数据库中的数据。

2. ChatGPT 无法模拟人类决策

根据畅销书作者和顶尖商业及数据专家 Bernard Marr 的说法,即使是最先进的 LLMs也缺乏如批判性思维、战略规划和解决问题的能力。

这些模型对业务的内部运作没有洞察力,且缺乏领域专业知识。

如果你问它“为什么产品销售在过去 2 个月暴跌”,模型将缺乏关于你组织的必要背景信息来提供有根据的回答。

此外,人类分析师或数据科学家通常会接触到组织内的不同团队——我们询问他们目前面临的问题,深入了解业务问题,并提出解决策略。

另一方面,LLM 无法像人类一样进行协作性问题解决。

3. ChatGPT 会犯错误

最后,ChatGPT 易受幻觉的影响。它在编写代码、解释数据和生成见解时可能会出错。

根据 AI 生成的数据做出商业决策是一种大多数组织不愿意冒的风险。

仍然需要人类专家来验证 AI 模型生成的代码和输出。

实际上,我合作过的许多组织在做出决定之前通常有两种生成预测的方式——一个是内部数据科学团队,另一个是外部咨询公司。

由这两个实体生成的数字会被比较,并经常进行对账,以确保预测的一致性。

如果公司愿意花费数万美元聘请第三方咨询公司仅仅是为了在决定如何进行之前交叉验证他们的预测,你真的认为他们会用 AI 模型来替代数据科学家以节省成本吗?

如果你问我,这种可能性非常小。

在我看来,AI 生成的预测将作为一个基准——它可能成为组织验证其数据科学团队提供的见解的另一种方式。

我在数据科学领域工作。我该如何保护我的职业免受 AI 影响?

首先,你必须拥抱 AI。利用它来跟上行业趋势并学习新知识。根据这篇《福布斯》文章,假装 AI 不会显著改变你的工作方式只会带来更多的坏处而非好处。

相反,利用生成性 AI 模型来自动化工作中的某些部分,并利用效率提升来发展能使你在行业中脱颖而出的技能。

此外,Tina Huang,前 Meta 数据科学家,建议你建立多个收入来源,而不是仅仅依赖全职工作。

你可以开始提供自由职业数据科学服务,以获取被动收入,确保你的工作安全不依赖于单一雇主的决定。

最终,我个人认为,组织在招聘员工时看重的素质将会发生变化。传统上,职位要求非常重视技术技能——你在 Excel、编程或 Tableau 上的表现越好,被录用的可能性就越大。

我相信,重心将会慢慢转移 away from 工具和技术专长,因为生成性 AI 模型正在弥合这一领域的技能差距。当然,技术技能仍然有价值,但它只能带你走到一定程度。

相反,组织将开始看重沟通、创造力、领导力和决策能力等技能。

一个理解如何利用 AI 实现公司目标的人,将比仅具备技术专长的人对雇主更具价值。

生成式 AI 是否会取代数据分析师的需求?

原文:towardsdatascience.com/will-generative-ai-replace-the-need-for-data-analysts-6b6807599d00?source=collection_archive---------1-----------------------#2023-05-23

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者使用 Midjourney 创作的艺术作品

不会。但它重新定义数据分析师的角色。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 加伦·冈崎

·

关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 5 月 23 日

自 2022 年 11 月 ChatGPT 发布以来,关于数据分析师的角色是否最终会被生成式 AI(如 ChatGPT、Bard 和 Bing Chat 等大型语言模型)取代的猜测越来越多。这种猜测很大程度上源于这些大型语言模型(LLMs)编写代码的能力。

作为在数据分析领域工作大部分职业生涯的人,理解生成 AI 在我们领域的影响确实引起了我的兴趣。出于好奇,我花了相当多的时间评估生成 AI 在数据分析中的当前能力。

在这篇文章中,我总结并分享了我的发现,因为我相信生成 AI 将在未来的数据分析工作中发挥重要作用。此外,我认为数据分析师社区有必要理解它将对他们的领域以及整个商业环境产生深远的影响。

我们目前的状态

到目前为止,我们知道生成 AI 可以编写 SQL、Python 和 R 代码。我们还可以假设,随着不断的微调,它们生成的代码的效率将会随着时间的推移而提高。但这只是开始。

在 2023 年 3 月底,OpenAI 的 ChatGPT 发布了一个名为代码解释器的插件。如果你是目前少数几位可以访问 Alpha 版本的人之一,你可以将数据文件上传到其中,并调用 Python 进行回归分析和描述性分析,查找数据中的模式,甚至创建可视化。全部无需编写或了解一行 Python 代码! 受人尊敬的沃顿商学院教授伊桑·莫利克对此进行了很好的总结

所以,你看到了。能够加载、分析和呈现数据而不写一行代码。游戏结束了吗?还不那么快。

尽管这些能力令人难以置信,但 Code Interpreter 有一些显著的局限性,显示了生成 AI 在接管数据分析行业时可能面临的一些挑战。

首先,它需要上传一个表格。一个二维的 CSV 文件(目前限制为 100 MB)。除了大小限制之外,想象一下任务是构建一个包含您公司所有数据的表格……

我可能到此为止,但我们继续吧。

拿着你的一个表格,你现在还需要获得批准,将你公司所有数据的一个表格推送到公司防火墙之外的 LLM 中,而你对此没有控制权……

我们可以到此为止。

当前(稍后会详细讨论)的替代方案是您的公司建立自己的 LLM。虽然理论上可行,但训练和微调模型的复杂性、所需的专业知识以及巨大的成本,使得只有极少数公司能够使其成本效益显著。

但为了更好地理解,让我们退一步,假设你的公司在那个名单上。

但首先,让我们从一些视角开始。如果我们回顾到 2000 年代初商业智能工具的引入,这些工具的巨大价值在于它们能够让非技术的业务人员利用他们的领域知识,通过使他们能够选择、分析和呈现数据,而不需要编写一行代码。听起来熟悉吗?

提供用户友好的数据分析手段并不是什么新鲜事。它始终具有巨大的价值。确实,这是一个持续增长的数十亿美元行业。然而,这些工具没有领域知识就毫无用处。这适用于任何数据分析,无论使用什么工具。即使是生成性 AI 也是如此。没有领域知识,我们不知道该向数据提出什么问题。即使问题已经提供给我们,我们又如何解读我们的发现呢?

在我看来,数据分析工作的最大价值在于其回答临时问题的能力。不可预见的、关键任务的问题。复杂的、多层次的、非线性的类型问题。回答这些问题需要领域知识。

例如,为什么我们最畅销的产品销售额突然暴跌?我们的主要供应商刚刚倒闭,我们该怎么办?为什么我们客户流失率上个月翻了一番?这些不是可以遵循既定决策树的简单问题。

这些少数示例的共同点在于,它们需要对从未被问过的情境性问题给出即时答案。这才是关键。如果你理解生成性 AI 的构造,它在回答这种性质的问题上的无能为力确实是它完全取代数据分析师的致命弱点。

简要总结一下,生成性 AI 利用现有数据集来“训练”一个 LLM,以生成基于其所接收的训练数据的概率驱动答案。虽然你可以持续通过更精确的数据集来微调你的模型,但你如何训练你的模型应对从未被问过的多层次、情境性问题呢?

这就像你刚刚开始一份你尚不熟悉的行业数据分析师的新工作。在第一天,你被要求紧急回答上述问题之一。你会从哪里开始?你会提取什么数据?你怎么知道你需要考虑哪些所有潜在变量?即使你能以某种方式得出一个答案,你又如何知道它是否正确?

正因为如此,我不认为数据分析师的角色会完全被生成性 AI 取代。然而……生成性 AI 在当前状态下,已经在数据分析领域有许多用途,而且这些用途将随着功能的不断增加而不断扩展。

生成性 AI 在数据分析中的当前潜在用途

截至今天,生成式人工智能在数据分析领域的最佳应用是它既能编写代码,又能解释所写的代码(它做得相当好)。我个人用它来帮助我编写和理解 Python 代码。

对于那些希望进入数据分析领域的人,我强烈建议你利用生成式人工智能来帮助你学习编码。当我刚开始进入这个领域时,它将大大加快我的学习曲线。

在数据分析领域,还有另一个令人兴奋的发展:生成式人工智能推动了专用编码工具的发展。GitHub 发布了其Copilot产品,它可以在你编写代码时实时建议编码解决方案/改进!

在本文早些时候,我提到了公司在构建自己的 LLM 时可能面临的潜在障碍。现在可能有一个新的替代方案:Databricks 最近发布了一个Dolly开源 LLM。理论上,这可以解决成本(开源)和将数据推送到公司防火墙之外的问题。它是一个小规模的 LLM,更适合于专注的数据集。

我提到 Dolly,主要是作为生成式人工智能领域发展速度的一个例子,并提醒它可能对数据分析领域产生的影响。

正如我们已经看到的那样,人工智能的演变只会以光速继续发展。

结论

我毫不怀疑生成式人工智能将重塑数据分析中的工作流程。一般来说,重复性的任务甚至分析将在未来由生成式人工智能执行。我也可以看到编程成为一种商品,而不再是高度发展的技能。

基于上述观点,我相信未来典型的数据分析师将具备业务线级的领域知识,并结合生成式人工智能工具,以帮助他们更高效地利用时间。

最后,从个人角度来说,我鼓励每位读者拥抱生成式人工智能。了解它,并在个人和商业生活中使用它。随着新 API 和插件的不断出现,它的覆盖范围和能力将不断增长。

无论好坏。

我喜欢写关于数据分析领域影响深远的话题。如果你想关注我的未来文章,请在Medium上关注我。谢谢!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者使用 Midjourney 创作的艺术作品

窗口函数:数据工程师和数据科学家必知的内容

原文:towardsdatascience.com/window-functions-a-must-know-for-data-engineers-and-data-scientists-4dd3e4ad0d2

回到基础 | 揭开 SQL 窗口函数的神秘面纱

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Iffat Malik

·发表于 Towards Data Science ·阅读时间 13 分钟·2023 年 6 月 14 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据增长在过去几年中非常显著,尽管我们有各种工具和技术,但 SQL 仍然是大多数工具的核心。它是数据分析的基本语言之一,被各类公司广泛使用来解决与数据相关的挑战。

我始终相信,你不需要仅仅为了通过面试或解决特定问题而了解某些概念。如果你愿意深入学习这些概念及其底层架构,它将帮助你在任何工作中取得成功。窗口函数有点棘手,刚开始可能会让人感到有些害怕,但一旦掌握,它们会变得非常有趣。

如果你熟悉 SQL 聚合函数,理解窗口函数的概念会更容易。聚合函数对一组值进行计算并返回一个值;当与GROUP BY子句配合使用时,它为每个组返回一个值。你可以在这里阅读更多内容:

## SQL 聚合函数为你的下一个数据科学面试做准备

回到基础 | SQL 初学者的基础知识

towardsdatascience.com

在进一步讨论之前,让我向你介绍一下示例数据库。我们将使用来自一个虚拟汽车零售公司的数据,你可以在我的 GitHub Repo中找到源数据,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

什么是窗口函数?

窗口函数的传统定义是,

窗口函数在与当前行相关的一组表行上执行计算。

如果我将其拆解,窗口函数使我们能够对分区执行计算。分区只是用户定义的一组、子组或行桶,窗口函数将在其上执行计算。

它们也被广泛称为分析函数

为什么我们需要窗口函数?

如我们所知,聚合函数将来自多行的数据汇总为一行(如果与 GROUP BY 子句一起使用,则每组一行);而窗口函数也在一组行上执行计算,但与聚合函数不同的是,它们不会将结果集汇总为一行。相反,所有行保持其原始形式/身份,并且为每行添加计算后的行。

听起来有趣吧?让我们来分解一下。这是来自表 PRODUCTS 的示例数据,

--query PRODUCTS table
SELECT 
    * 
FROM 
    PRODUCTS 
LIMIT 10;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

比如,我们需要关于每个PRODUCTCATEGORY的平均购买价格的信息,

--average buy price for each product category
SELECT
    PRODUCTCATEGORY,
    FORMAT(AVG(BUYPRICE),2) AS AVERAGE_BUYPRICE
FROM
    PRODUCTS
GROUP BY PRODUCTCATEGORY;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

现在,仅这些信息可能用处不大。是的!你现在确实知道了每个PRODUCTCATEGORY的平均购买价格,但接下来呢?这些信息如何产生商业洞察?如果我想将每个产品的购买价格与特定PRODUCTCATEGORY的平均购买价格进行比较呢?让我重新表述一下我的新需求,

  1. 显示每个PRODUCTCATEGORY中的购买价格以及该PRODUCTCATEGORY的平均购买价格。

  2. PRODUCTCATEGORY分组排列结果集。

你能仅用普通的聚合函数实现这一点吗?上述要求希望显示一些信息原样(例如PRODUCTCATEGORYPRODUCTNAMEBUYPRICE),并且还希望新增一列显示每个PRODUCTCATEGORY的平均购买价格。这就是英雄般的窗口函数登场的时候,

--using window function
SELECT 
    PRODUCTCATEGORY,
    PRODUCTNAME,
    BUYPRICE,
    FORMAT(AVG(BUYPRICE) OVER (PARTITION BY PRODUCTCATEGORY),2) AS AVERAGE_BUYPRICE
FROM
    PRODUCTS; 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

GIF 由作者提供

在我们跳到常用的窗口函数之前,让我们了解一下基本的语法和配对的子句。

窗口函数的一般语法是,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

其中,

  • ***OVER()***子句定义了一组用户指定的行。窗口函数将仅在该特定集合上执行计算。

    它专门与窗口函数一起使用;然而,它也可以与聚合函数一起使用,就像我们在上面用*AVG()*函数一样,从而将其转变为窗口函数。

    如果你在*OVER()*中不提供任何内容,窗口函数将应用于整个结果集。

  • PARTITION BYOVER子句一起使用。它将查询结果集划分为多个分区,然后窗口函数应用于每个分区。

    这是可选的,如果你没有指定PARTITION BY 子句,那么函数会将所有行视为一个分区。

  • ORDER BY 子句用于在结果集的每个分区内按升序或降序排序。默认情况下是升序。

  • ROWS/RANGEFRAME子句的一部分,它定义了分区中的一个子集。

你可以在这里阅读关于窗口函数与聚合函数的详细比较以及窗口函数子句的内容,SQL 窗口函数的结构

现在我们已经熟悉了窗口函数的基本结构,让我们探索最常用的窗口函数,

ROW_NUMBER()

ROW_NUMBER() 给表或使用PARTITION BY 子句的分区中的每一行分配了一个顺序整数。常见的语法是,

ROW_NUMBER() OVER ([PARTITION BY clause] [ORDER BY clause])

这是来自PRODUCTS表的示例数据,它包含了汽车零售商提供的一系列产品数据。

--query PRODUCTS table
SELECT 
    * 
FROM 
    PRODUCTS 
LIMIT 10;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

让我们从基础开始,

--assigns a row number to each row in a table
SELECT 
    *,
    ROW_NUMBER() OVER() AS ROW_NUM
FROM 
    PRODUCTS;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

在这里,ROW_NUMBER() 给表PRODUCTS中的每一行分配了从 1 开始的顺序整数。让我们稍微升级一下,为每个PRODUCTCATEGORY添加行号,为此我们需要使用PARTITION BY 子句。

--row_number by productcategory
SELECT 
    *,
    ROW_NUMBER() OVER(PARTITION BY PRODUCTCATEGORY) AS ROW_NUM
FROM 
    PRODUCTS;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的 GIF

我们在PRODUCTS表中有 2 个不同的PRODUCTCATEGORY

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

ROW_NUMBER() 为每一行生成了一个顺序整数,PARTITION BY 子句将结果集按PRODUCTCATEGORY 划分成桶。因此,ROW_NUMBER() 结合OVERPARTITION BY 子句,为每个PRODUCTCATEGORY生成了唯一的数字序列。

现在,让我们也利用ORDER BY 子句。这也是入门/中级面试中最常被问到的问题之一。假设我们想知道每个PRODUCTCATEGORY中库存数量最高的前 3 个产品。

--top 3 products with the highest quantity in each product category
WITH PRODUCT_INVENTORY AS
(
SELECT
    PRODUCTCATEGORY,
    PRODUCTNAME,
    QUANTITYINSTOCK,
    ROW_NUMBER() OVER (PARTITION BY PRODUCTCATEGORY ORDER BY QUANTITYINSTOCK DESC) AS ROW_NUM
FROM
    PRODUCTS
)

SELECT
    PRODUCTCATEGORY,
    PRODUCTNAME,
    QUANTITYINSTOCK,
    ROW_NUM AS TOP_3_PRODUCTS
FROM 
    PRODUCT_INVENTORY
WHERE ROW_NUM <= 3;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

首先让我们将整个查询分成两部分,如下图所示;首先,我们在创建一个PRODUCT_INVENTORY。表数据将被划分为每个PRODUCTCATEGORY的分区/组,按每个产品类别的库存数量的降序排列。然后,ROW_NUMBER() 将为每个分区生成顺序整数号码。这里的有趣之处在于,每个PRODUCTCATEGORY中的行号会在每个分区中重置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

上面的查询将返回以下结果,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

现在我们查询的第二部分是相当简单的。它将利用这个结果集作为输入,并从每个 PRODUCTCATEGORY 中选取前 3 个产品,基于条件 ROW_NUM ≤ 3。最终结果如下,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

这将得到最终结果,如下所示,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

RANK()

如其名称所示,RANK() 为表中的每一行或每个分区中的每一行分配一个排名。一般语法是,

RANK() OVER ([PARTITION BY 子句] [ORDER BY 子句])

继续我们PRODUCTS表的例子,让我们根据库存数量以降序为产品分配一个排名,按PRODUCTCATEGORY进行分区。

--generate rank for each product category
SELECT
    PRODUCTCATEGORY,
    PRODUCTNAME,
    QUANTITYINSTOCK,
    RANK() OVER (PARTITION BY PRODUCTCATEGORY ORDER BY QUANTITYINSTOCK DESC) AS "RANK"
FROM
    PRODUCTS;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

我已经将结果集限制为演示目的。现在,不要将 ROW_NUMBER()RANK() 混淆。这两者的结果集可能看起来相似;然而,存在差异。ROW_NUMBER() 为表中的每一行或每个分区中的每一行分配唯一的顺序整数编号;而 RANK() 也为表中的每一行或每个分区中的每一行生成顺序整数编号,但对具有相同值的行分配相同的排名。

让我们通过一个例子来理解,以下是 CUSTOMERS 表的示例数据,

-- sample data from table customers
SELECT
    CUSTOMERID,
    CUSTOMERNAME,
    CREDITLIMIT
FROM 
    CUSTOMERS
LIMIT 10; 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

在以下演示中,我为 CUSTOMERS 表生成了 ROW_NUMBER()RANK(),按 CREDITLIMIT 降序排列。

--row_number() and rank() comparison
SELECT 
    CUSTOMERID,
    CUSTOMERNAME,
    CREDITLIMIT,
    ROW_NUMBER() OVER (ORDER BY CREDITLIMIT DESC) AS CREDIT_ROW_NUM,
    RANK() OVER (ORDER BY CREDITLIMIT DESC) AS CREDIT_RANK
FROM 
    CUSTOMERS;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

我已经将结果集限制为演示目的。绿色高亮的是 ROW_NUMBER(),蓝色高亮的是 RANK()

现在,如果你查看红色高亮的 3 条记录,这就是两个函数结果集不同的地方;ROW_NUMBER() 为所有行生成了唯一的顺序整数编号。而另一方面,RANK()CUSTOMERID 239321 分配了相同的排名 20,因为它们具有相同的信用额度,即 105000.00。不仅如此,对于下一个记录,即 CUSTOMERID 458,它跳过了排名 21 并分配了排名 22

DENSE_RANK()

现在如果你在想,为什么我们需要 DENSE_RANK(),如果我们已经有了 RANK()?正如我们在前面的例子中看到的,RANK() 为具有相同值的行生成相同的排名,然后跳过下一个连续排名(参见上面的图片)。

DENSE_RANK()RANK() 类似,只是有一个区别,它在对行进行排名时不会跳过任何排名。通用语法是,

DENSE_RANK() OVER ([PARTITION BY 子句] [ORDER BY 子句])

回到 CUSTOMERS 表,让我们比较 RANK()DENSE_RANK() 的结果集,

--dense_rank() and rank() comparison
SELECT 
    CUSTOMERID,
    CUSTOMERNAME,
    CREDITLIMIT,
    RANK() OVER (ORDER BY CREDITLIMIT DESC) AS CREDIT_RANK,
    DENSE_RANK() OVER (ORDER BY CREDITLIMIT DESC) AS CREDIT_DENSE_RANK
FROM 
    CUSTOMERS;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

RANK()(以蓝色突出显示)类似,DENSE_RANK()(以绿色突出显示)为 CUSTOMERID 239321 生成了相同的排名,而 DENSE_RANK()RANK() 不同的是,它没有跳过下一个连续数字,而是保持了序列,并将 21 排名分配给 CUSTOMERID 458

NTH_VALUE()

这与我们之前讨论的有点不同。NTH_VALUE() 将返回指定窗口中表达式的 Nth 行的值。常见的语法是,

NTH_VALUE(expression, N) OVER ([PARTITION BY clause] [ORDER BY clause] [ROW/RANGE clause])

‘N’ 必须是正整数值。如果数据不存在于 Nth 位置,函数将返回 NULL。在这里,如果你注意到,我们在语法中有一个额外的子句,即 ROW/RANGE 子句*。

RAW/RANGE 是窗口函数中 Frame Clause 的一部分,它定义了窗口分区内的子集。ROW/RANGE 根据当前行定义此子集的起点和终点,你以当前行的位置为基点,并以此为参考,在分区内定义你的框架。

  • ROWS - 这定义了框架的开始和结束,通过指定当前行之前或之后的行数。

  • RANGE - 与 ROWS 相反,RANGE 通过与当前行的值进行比较来定义分区内框架的值范围。

通用语法是,

{ROWS | RANGE} BETWEEN <frame_starting> AND <frame_ending>

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

每当你使用 ORDER BY 子句时,它将设置默认框架为,

{ROWS/RANGE} BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

如果没有 ORDER BY 子句,默认框架是,

{ROWS/RANGE} BETWEEN UNBOUNDED PRECEDINGUNBOUNDED FOLLOWING

这些内容现在可能看起来有些多,但无需记住语法及其含义,只需练习即可!你可以在这里阅读详细的 Frame Clause 和一系列示例,

[## SQL 窗口函数的结构]

回到基础 | SQL 初学者基础

towardsdatascience.com

现在假设,我们需要找出每个 PRODUCTCATEGORY 中的第二高买价的 PRODUCTNAME

--productname for each productcategory with 2nd highest buy price
SELECT
    PRODUCTNAME,
    PRODUCTCATEGORY,
    BUYPRICE,
    NTH_VALUE(PRODUCTNAME,2) OVER(PARTITION BY PRODUCTCATEGORY ORDER BY BUYPRICE DESC) AS SECOND_HIGHEST_BUYPRICE
FROM
    PRODUCTS;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

我们还有两个类似于 NTH_VALUE() 的值函数;FIRST_VALUE()LAST_VALUE()。顾名思义,它们分别从排序列表中返回最高(第一个)和最低(最后一个)值。通用语法是,

FIRST_VALUE(expression) OVER ([PARTITION BY clause] [ORDER BY clause] [ROW/RANGE clause])

LAST_VALUE(expression) OVER ([PARTITION BY clause] [ORDER BY clause] [ROW/RANGE clause])

类似于上述示例,现在你能找出每个PRODUCTCATEGORY中买价最高和最低的PRODUCTNAME吗?

NTILE()

有时会出现需要将分区内的行安排到一定数量的组或桶中的情况。NTILE() 就是用于这种目的,它将分区中有序的行分成特定数量的桶。每个桶都会分配一个从 1 开始的组号。它会尽量创建尽可能大小相等的组。对于每一行,NTILE() 函数返回一个组号,表示该行所属的组。

通用语法是,

NTILE(N) OVER ([PARTITION BY 子句] [ORDER BY 子句])

其中 ‘N’ 是定义要创建的组数的正整数。

比如,我们想要将PRODUCTCATEGORY -‘Cars’ 分类为高价、中价和低价的汽车列表。

--segregate the 'Cars' for high range, mid range and low range buy price
SELECT 
    PRODUCTNAME,
    BUYPRICE,
    NTILE(3) OVER (ORDER BY BUYPRICE DESC) AS BUYPRICE_BUCKETS
FROM 
    PRODUCTS
WHERE
    PRODUCTCATEGORY = 'Cars';

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

LAG() 和 LEAD()

我们经常遇到需要进行某种比较分析的场景。例如,将选定年份的销售与前一年或后一年进行比较。这种比较在处理时间序列数据和计算时间上的差异时非常有用。

LAG() 从当前行之前的行提取数据。如果没有前导行,则返回NULL。常见的语法是,

LAG(expression, offset) OVER ([PARTITION BY 子句] [ORDER BY 子句])

LEAD() 从当前行之后的行获取数据。如果没有后续行,则返回NULL。常见的语法是,

LEAD(expression, offset) OVER ([PARTITION BY 子句] [ORDER BY 子句])

其中offset是可选的,但使用时其值必须是 0 或正整数,

  • 当指定为 0 时,LAG()LEAD() 会对当前行评估表达式。

  • 如果省略,1 被认为是默认值,它会取当前行的直接前一行或后一行。

--yearly total sales for each product category
WITH YEARLY_SALES AS
(
SELECT
    PROD.PRODUCTCATEGORY,
    YEAR(ORDERDATE) AS SALES_YEAR,
    SUM(ORDET.QUANTITYORDERED * ORDET.COSTPERUNIT) AS TOTAL_SALES
FROM
    PRODUCTS PROD
INNER JOIN
    ORDERDETAILS ORDET
    ON PROD.PRODUCTID = ORDET.PRODUCTID
INNER JOIN
    ORDERS ORD
    ON ORDET.ORDERID = ORD.ORDERID
GROUP BY PRODUCTCATEGORY, SALES_YEAR  
)

SELECT
    PRODUCTCATEGORY,
    SALES_YEAR,
    LAG(TOTAL_SALES) OVER (PARTITION BY PRODUCTCATEGORY ORDER BY SALES_YEAR) AS LAG_PREVIOUS_YEAR,
    TOTAL_SALES,
    LEAD(TOTAL_SALES) OVER (PARTITION BY PRODUCTCATEGORY ORDER BY SALES_YEAR) AS LEAD_FOLLOWING_YEAR
FROM YEARLY_SALES;

在这里,我们首先使用CTE(公共表表达式)来获取每个PRODUCTCATEGORY按年度的总销售数据。然后,我们使用这些数据与LAG()LEAD() 结合,以获取按PRODUCTCATEGORY 分区且按SALES_YEAR 排序的前一年和后一年总销售数据。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

结论

窗口函数在你想以多种方式分析数据时非常有用。不同的 SQL 变体可能有略微不同的实现,因此查阅特定 SQL 变体的官方文档总是一个好主意。以下是一些资源,帮助你入门,

如果你记得某些东西非常清楚,你一定是练习得很好,

成为会员,阅读 Medium 上的所有故事.

快乐学习!

PostgreSQL 中的窗口函数

原文:towardsdatascience.com/window-functions-in-postgresql-788d2ad57c6b

任何数据从业者必须了解的知识

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 梅尔林·舍费尔

·发布于数据科学前沿 ·阅读时间 8 分钟·2023 年 1 月 6 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由斯蒂芬·道森提供,来源于Unsplash

你是否曾经发现自己编写了许多步骤或包含聚合函数的长 SQL 查询,以计算看似简单的指标?你是否曾经想过“是否可以通过不同的方式计算表中的内容,以简化操作”?那么,也许是时候看看窗口函数了。

窗口函数是 SQL 中一个方便且强大的特性,它允许你对多行进行类似于聚合函数的计算。但不同于可能会与 GROUP BY 一起使用的聚合函数,它们不会返回一组行的单一值,而是返回集合中每一行的值。我们来看看一个例子:

SELECT
  user_id,
  SUM(sales)
FROM transactions
GROUP BY 
  user_id;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SELECT
SUM(sales) OVER(
            PARTITION BY user_id
)
FROM transactions;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你为什么以及何时应该使用窗口函数?

窗口函数的一个优势是,它们允许你结合使用聚合值和非聚合值,因为行不会被合并在一起。这为在一步中计算许多内容提供了一种方法,否则需要多个步骤。

与多个子查询和自连接相比,窗口函数相对简单易用且易于阅读。它们减少了查询的复杂性和步骤数,从而使得后续维护变得更加容易。

这样,它们也可以帮助解决性能问题。例如,你可以使用窗口函数,而不是进行自连接或子查询。

重要的窗口函数

你可以将 SUM()和 COUNT()等函数用作窗口函数,但还有一些特殊函数仅作为窗口函数可用。

以下是一些 PostgreSQL 中最重要的窗口函数:

  1. RANK(): 返回每行在数据集中的排名。排名是根据行在数据集中的顺序决定的。排名为 1 的是第一行。排名相同的行会获得相同的排名,这可能导致排名中出现间隙。

  2. DENSE_RANK(): 类似于 RANK(),但如果行值相同,它不会跳过任何排名。例如,如果两行具有相同的值且被排序为第一行和第二行,它们的排名将是 1 和 1,接下来的是 2,而不是 1 和 1 和 3,正如 RANK() 的情况。

  3. ROW_NUMBER(): 返回数据集中每行的唯一编号。第一行的行号为 1,第二行的行号为 2,依此类推。

  4. NTILE(): 将行分成指定数量的组或“块”,并为每行分配一个块编号。例如,如果你指定 NTILE(3) 并且数据集中有 9 行,前 3 行将被分配块编号 1,接下来的 3 行将被分配块编号 2,最后 3 行将被分配块编号 3。这个函数可以用来计算分位数。

  5. 5. LAG() 和 LEAD(): 这些函数用于访问数据集中前一个或后一个行的值。例如,如果你有一组值,并且想将每一行的值与前一行的值进行比较,可以使用 LAG() 函数来访问前一个值。

让我们看一些示例!

RANK()、DENSE_RANK 和 ROW_NUMBER(): 使用这些函数时,你需要指定要排名的列名,以及决定数据集行顺序的 ORDER BY 子句。

例如,要按“销售”列中的值数量对表中的行进行排名,你可以使用以下查询:

SELECT
user_id,
RANK() OVER(
          ORDER BY COUNT(*)
),
DENSE_RANK() OVER(
          ORDER BY COUNT(*)
),
ROW_NUMBER() OVER(
          ORDER BY COUNT(*)
)
FROM transactions
GROUP BY user_id;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这个示例中,相应的排名函数将最高值的行分配排名 1,第二高值的行分配排名 2(或如果允许并列,则为 1),依此类推。

NTILE(): 使用 NTILE() 函数时,你需要指定要创建的块数以及决定数据集行顺序的 ORDER BY 子句。例如,要将表中的行分成 4 个块(四分位数),你可以使用以下查询:

SELECT
  user_id,
  sales,
  NTILE(4) OVER(
            ORDER BY sales)
FROM transactions;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如你所见,我们现在可以轻松确定前/后四分位数的销售额。我发现这个函数对于找出高于或低于某个百分比/分位数的值非常有用。

LAG()LEAD(): 使用 LAG() 或 LEAD() 函数时,你需要指定要访问的列名、移动的行数以及如果指定的行不存在时使用的默认值。例如,要访问“销售”列中前一行的值,你可以使用以下查询:

SELECT
  user_id,
  sale_year,
  sales,
  LAG(sales,1,0) OVER(
                  PARTITION BY user_id 
                  ORDER BY sale_year
  ) AS prev_year_sales
FROM transactions
ORDER BY 
  user_id, sale_year;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这里,LAG()函数将访问“sales”列中前一行的值,如果没有前一行,则返回 0(例如,如果当前行是集合中的第一行)。

OVER、PARTITION BY 和 ORDER BY

OVER 子句是 PostgreSQL 中许多窗口函数的可选语法部分。它指定决定函数操作的行集的标准。例如,如果你想使用窗口函数计算每年的平均“sales”值,你可以使用 OVER 子句将行集定义为所有具有相同“year”值的行。OVER 子句通常包括 PARTITION BY 和 ORDER BY 子句,这些子句用于将行分成组(或分区)并确定每个分区内的行顺序。

OVER 子句是 PostgreSQL 中窗口函数的一个强大特性,它允许你控制决定窗口函数操作的行集的标准。这使得你可以根据需要对行组或单行进行计算。

如果你需要对包含的行进行更精细的控制,可以使用OVER 子句中的行选择。行选择子句决定了窗口函数操作的集合中将包含哪些行。行选择子句可以用来指定包含的行数行的范围,根据当前行在集合中的位置来确定。如果你只想从集合的开始或结束部分包含某些行,或想排除计算中的某些行,这会很有用。以下是一些你可能经常遇到的例子:

  1. ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING:这个子句指定集合中的所有行都应包含在计算中。这是默认行为,如果没有指定行选择子句,你不会经常看到它,但这是最常用的行选择。

  2. ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING:这个子句指定从当前行到集合的末尾的所有行都应包含在计算中。

  3. ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:这个子句指定从集合开始到当前行的所有行都应包含在计算中。我发现这在累积计算中非常有用。

  4. ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING:这个子句指定当前行及其前后各一行应包含在计算中。你可以更改数值来调整计算窗口的大小和形状。

这里还有一些值得了解的内容:

  1. 窗口函数可以在查询的 SELECT、WHERE 和 HAVING 子句中使用。这允许你在查询的各个部分使用窗口函数的结果,甚至用它们来过滤查询返回的行。

  2. 窗口函数可以与聚合函数结合使用。例如,你可以使用窗口函数来计算每一行的排名,然后使用 SUM()等聚合函数来计算排名的总和。这在计算分数和百分比时非常有用。

  3. 窗口函数可以与 GROUP BY 子句一起使用。当与 GROUP BY 子句结合使用时,窗口函数将计算每一组行的值,而不是整个行集的值。

  4. 窗口函数可以与子查询和 CTE(公共表表达式)一起使用。这允许你对子查询/CTE 的结果进行计算,而不是对原始数据集进行计算。

  5. 在 PostgreSQL 中,有一个 WINDOW 关键字用于定义窗口函数的窗口帧。窗口帧是窗口函数操作的行集,由 OVER 子句和函数语法中包含的任何行选择子句决定。通过使用 WINDOW 关键字,你可以在单个查询中为多个窗口函数指定窗口帧,而不是为每个函数重复 OVER 和行选择子句。这可以使你的查询更具可读性,更易于维护。

SELECT
 year,
 sales,
 AVG(sales) OVER w1 ,
 SUM(sales) OVER w1 
FROM sales_table
WINDOW w1 AS (PARTITION BY year 
            ORDER BY month 
            ROWS BETWEEN UNBOUNDED PRECEDING 
            AND UNBOUNDED FOLLOWING
          );

总之,窗口函数是 PostgreSQL 中一种强大且多功能的工具,它允许你对行集合进行复杂的计算。无论你是需要计算排名、将行分组,还是访问其他行的值,窗口函数都能满足你的需求。

希望你学到了东西,并能利用窗口函数改进你的计算和分析!

-梅林

[## 通过我的推荐链接加入 Medium - 梅林·谢费尔

阅读梅林·谢费尔(Merlin Schäfer)和其他成千上万名作家的每个故事。你的会员费用直接支持……

ms101196.medium.com](https://ms101196.medium.com/membership?source=post_page-----788d2ad57c6b--------------------------------)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值