介绍 PeekingDuck 计算机视觉
开源的最先进计算机视觉模型,代码行数极少
·
关注 发表在 Towards Data Science ·4 min read·2023 年 1 月 3 日
–
图片由 Vlad Tchompalov 提供,来源于 Unsplash。
介绍
计算机视觉项目可能非常令人畏惧,涉及各种工具和软件包,如 OpenCV、TensorFlow 和 PyTorch 等。除了需要熟悉所涉及的工具和 API,还需要正确组合各种软件包,以便整个计算机视觉管道能够正常工作。
例如,OpenCV 处理[H, W, C]
格式的 BGR 通道图像,而 TensorFlow 处理相同格式但使用 RGB 通道的图像,PyTorch 处理[C, H, W]
格式的 RGB 通道图像。由于这种不一致性,图像格式必须在不同库之间传递时不断修改。像这样的许多问题(以及其他问题!)会导致大量冗余代码,我们希望避免这种情况。
如果我们可以通过一个统一的管道来简化计算机视觉管道,那将会怎样呢?
-
开源且没有如 GPL-3.0 之类的限制,以减少成本。
-
模块化以适应各种用例。
-
采用最先进的技术以实现最大性能。
-
最小化以减少管道复杂性。
事实证明,所有这些问题在一定程度上都通过 PeekingDuck 得到解决,这是 AI Singapore 最近发布的一个计算机视觉软件包!
PeekingDuck
PeekingDuck 是一个计算机视觉框架,其特点是:
-
开源(Apache 2.0)— 无费用或限制。
-
模块化 — 混合搭配各种模块以解决不同的用例。
-
最先进的计算机视觉推理 — 强大的深度学习模型。
-
最小 — 实际上无需 Python 代码!
通过 pip 等包管理器安装 PeekingDuck 作为 Python 包后,可以直接从命令行/终端使用该包,便于与其他应用程序的直接集成。
安装 PeekingDuck
PeekingDuck 作为一个 Python 包进行安装:
pip install peekingduck
节点 — PeekingDuck 的基本构建块
使用 PeekingDuck,计算机视觉管道是通过基本构建块称为节点来构建的。每个节点处理不同的操作,通过混合不同的节点可以创建不同的管道。到目前为止,PeekingDuck 具有 6 种不同类型的节点:
-
输入 — 从实时摄像头或视频/图像文件中将图像数据输入到管道中。
-
增强 — 预处理图像数据。
-
模型 — 执行计算机视觉任务,如对象检测或姿态估计。
-
涉猎 — 后处理模型输出。
-
绘制 — 可视化模型输出,例如边界框。
-
输出 — 将模型输出保存到磁盘。
人员跟踪管道
使用 PeekingDuck 很简单!在本节中,我们将演示如何使用 PeekingDuck 创建一个人员跟踪管道!
初始化 PeekingDuck
第一步是在指定目录(在此案例中为person_tracking/
)中初始化 PeekingDuck。
mkdir person_tracking
cd person_tracking
peekingduck init
这将会在 person_tracking/
下创建一个名为 pipeline_config.yml
的配置文件以及一些其他源代码文件。为了让 PeekingDuck 按我们希望的方式运行,我们必须修改 pipeline_config.yml
。
在我们的例子中,pipeline_config.yml
应包含以下行:
nodes:
- input.visual:
source: venice-2-train.mp4
- model.jde
- dabble.statistics:
maximum: obj_attrs["ids"]
- draw.bbox
- draw.tag:
show: ["ids"]
- draw.legend:
show: ["cum_max"]
- output.media_writer:
output_dir: output/
我们为此任务使用以下 nodes
:
-
input.visual
— 指定加载图像数据的文件。我们使用的是一个由 MOT15 数据集的 Venice-2 图像 拼接而成的视频。 -
model.jde
— 指定要使用的模型。对于人物跟踪,我们使用 Joint Detection and Embedding (JDE) 模型。 -
dabble.statistics
— 根据模型的输出进行统计计算。在这种情况下,我们计算每帧检测到的最大 ID 数量。 -
draw.bbox
— 在每帧上绘制检测到的边界框。 -
draw.tag
— 为每个边界框绘制相应的标签。 -
draw.legend
— 绘制累计检测到的最大数量。 -
output.media_writer
— 将模型的预测结果输出到磁盘。
通过混合和匹配不同的节点,我们可以构建不同的管道来解决不同的计算机视觉应用场景。详细的节点列表可以在 PeekingDuck 的网站 上找到。
准备数据
下一步是准备数据。在我们的例子中,我们使用 OpenCV 将 MOT15 数据集的 Venice-2 图像 拼接成一个名为 venice-2-train.mp4
的视频文件,帧率为 30
,分辨率为 [1920, 1080]
。
import cv2
import os
w = cv2.VideoWriter("venice-2-train.mp4",
cv2.VideoWriter_fourcc(*"MP4V"),
30, [1920, 1080])
files = sorted(os.listdir("MOT15/train/Venice-2/img1"))
for f in files:
im = cv2.imread(os.path.join("MOT15/train/Venice-2/img1", f))
w.write(im)
w.release()
运行 PeekingDuck
在初始化了 PeekingDuck 和数据之后,剩下的就是从命令行运行管道:
peekingduck run
管道的输出将保存在 output/
下,如 pipeline_config.yml
中所指定,可以以视频或 .gif
图像的形式进行可视化。检测到的边界框已覆盖到每个跟踪人员上,并标注了每个相应的跟踪 ID。累计的最大跟踪 ID 数量也显示在每帧的左下角。
PeekingDuck 人物跟踪输出。图形由作者创建。原始图像是来自 MOT15 数据集的 Venice-2 图像。
请注意,除了准备数据外,我们在使用 PeekingDuck 进行人物跟踪时没有编写一行 Python 代码!
结论
计算机视觉已经取得了长足进展,我们现在可以使用许多出色的包,如 PeekingDuck。PeekingDuck 提供了开源的、模块化的最先进的计算机视觉模型,代码量最少,使得任何人都可以相对轻松地进行计算机视觉项目!
参考文献
介绍 PyCircular:一个用于圆形数据分析的 Python 库
圆形数据在分析和建模时可能会面临独特的挑战
·
关注 发表在 数据科学的未来 ·8 分钟阅读·2023 年 1 月 24 日
–
图片由 Patrick McManaman 提供,发布在 Unsplash
在这篇文章中,我介绍了PyCircular,一个专门用于圆形数据分析的 Python 库。作为其中一位作者,我很高兴与大家分享这个强大的工具,以帮助解决处理圆形数据时面临的挑战。
圆形数据,如表示角度、方向或时间戳的数据,在分析和建模时可能会带来独特的挑战。圆形数据的特性可能会导致在应用传统的线性和基于核的方法时遇到困难,因为这些方法不适合处理圆形数据的周期性特征。此外,计算均值和标准差时也可能出现问题,因为这些度量对圆形数据并不适用。
PyCircular 通过提供一组专门针对圆形数据的工具和功能来解决这些挑战。该库包括各种圆形统计方法,如分布、核函数和置信区间。此外,它还包括可视化工具,如圆形直方图和分布图,以帮助你更好地理解数据。
https://github.com/albahnsen/pycircular
本文的剩余部分将深入探讨处理圆形数据的独特挑战,并展示如何通过一系列示例使用 PyCircular 来应对这些挑战。你将看到 PyCircular 如何有效处理圆形数据的周期性特征,并计算有意义的集中趋势和离散度量。你还将学习如何利用库的可视化工具来更好地理解和解释圆形数据。通过阅读本文,你将对如何有效地使用 PyCircular 来分析和建模圆形数据有一个扎实的了解。
在训练机器学习模型时,我们必须拥有一个包含输入变量(特征)和相应输出变量(标签)的数据集。模型学习将特征映射到标签上,训练的目标是找到最佳的映射参数集。
在一些应用中,模型的特征包括用户、事务、登录等的描述信息。在这些场景中,大多数都有时间戳信息,可能是事件时间、星期几或月中的某一天。如果目标是根据过去的事件预测未来事件,你可以使用时间戳作为特征。例如,你可以将一天中的时间、星期几或一年中的月份作为特征,来预测交通量或能源消耗。
然而,处理机器学习问题中的时间的最佳方法将取决于你试图解决的具体问题和数据的结构。
让我们看看如何使用 PyCircular 来分析这种复杂行为。
首先,让我们安装库并加载一些示例合成数据。
!pip install pycircular
使用来自 load_transactions 的数据集,我们可以看到从 2020 年 1 月 1 日到 2020 年 7 月 29 日共有 349 个观察(交易)。
作者提供的图片
然后,绘制每次观察时间的直方图,我们看到大多数例子发生在下午 5 点到早上 7 点之间,中午发生的例子很少。此外,当将一天中的小时视为标量变量时,可能会出现一些问题。
-
一个问题是一天中的小时具有周期性,这意味着一天结束时的值(24:00)与一天开始时的值(00:00)相关。然而,当一天中的小时被视为标量变量时,这种周期性关系未被考虑,这可能导致不准确或误导性的结果。
-
另一个问题是一天中的小时往往与其他变量相关联,如星期几或季节。例如,工作日的高峰时段可能比周末更拥堵。然而,当一天中的小时被视为标量变量时,这些相关性未被考虑,可能导致偏见或误导性结果。
-
第三个问题是一天中的小时可能会受到季节、星期几或节假日等不同因素的影响。这些因素可能会对一天中的小时的行为和模式产生重大影响。因此,如果在使用一天中的小时作为标量变量时未考虑这些信息,可能会导致不准确的结论。
为了克服这些问题,一种解决方案是使用周期编码技术,如正弦和余弦编码,以融入数据的周期性特征。另一种解决方案是将其他相关变量(如星期几或季节)包括在模型中,以考虑潜在的相关性。此外,在分析数据时,考虑影响一天中的小时的不同因素也很重要。
对于我们的例子,首先通过使用圆形直方图来更好地理解我们的数据集。
作者提供的图片
然后,计算标量或算术均值
作者提供的图片
处理示例时间时,特别是分析如时间均值这样的特征时,一个问题是很容易犯下使用算术均值的错误。确实,算术均值不是平均时间的正确方法,因为如上图所示,它未考虑时间特征的周期性行为。例如,四个交易时间分别为 2:00、3:00、22:00 和 23:00 的算术均值是 12:30,这与实际情况不符,因为没有交易发生在那个时间附近。
我们可以通过将交易时间建模为周期变量来克服这一限制,特别是使用 von Mises 分布(Fisher, 1996)。von Mises 分布,也称为周期正态分布,是一种围绕圆周的包裹正态分布变量的分布。von Mises 分布的一个示例集(D)定义为:
其中
是周期均值和周期标准差。在这篇论文中,我们展示了它们的计算。
图像由作者提供
现在,计算了周期均值和标准差后,我们可以估计 von Mises 分布。
图像由作者提供
这种方法为事件时间的分布提供了良好的近似。然而,当使用仅有一个模式的统计分布时,如果分布与数据集不匹配,可能会很难准确建模。此外,如果数据集是多模态的(即有多个峰值),单一模式的分布将无法捕捉数据中的所有变化。这可能导致基于模型的预测或推断不准确。
通过使用 von Mises 核分布对数据进行建模可以克服这一问题。
克服使用仅有一个模式的统计分布问题的一种方法是使用基于核的方法,如核密度估计(KDE)。
KDE 是一种用于估计随机变量概率密度函数的非参数方法。它通过用平滑且对称的核函数(如 von Mises)替换每个数据点的点质量来工作。结果的 PDF 估计是以每个数据点为中心的核函数的总和。
通过使用核函数,KDE 可以平滑出任何单模态分布,并捕捉数据中的多个模式,使其成为建模多模态数据集的更灵活的方法。此外,核密度估计是非参数的,这意味着它不对数据的潜在分布做任何假设。
然而,值得注意的是,选择合适的核函数很重要,且在使用 KDE 时存在一些挑战,如带宽选择和维度诅咒。
图像由作者提供
总结而言,使用像 KDE 这样的基于核的方法与 von Mises 结合,可以通过允许对多模态数据集进行更灵活和稳健的建模,帮助克服使用仅有一个模式的统计分布的问题。
使用核函数创建新特征
最后,我们可以将核应用于新的观察值,并创建一个可以作为机器学习模型输入的新特征。
图片来源于作者
我们可以看到,在中午的观察值概率非常低(0.017),因为在训练核时,该时间没有任何观察值。
总之,这种方法使我们能够通过创建数据中时间信息的更强健的表示来有效处理时间戳。通过在特征工程中使用 von Mises 核,我们可以生成准确捕捉数据中时间模式细微差别的新特征。这种方法可以克服将日期视为标量变量的局限性,并提升机器学习模型的性能。
下一步
-
带宽参数(bw)的选择对于模型的性能至关重要,pycircular 库提供了一系列优化方法来为给定的数据集选择最佳的 bw。
-
要评估核的有效性,重要的是要进行准确性测试并与其他方法的结果进行比较。
-
虽然一天中的时间是一个重要的时间特征,但还需要调查其他时间变量如星期几和月份的天数如何影响模型的性能。
-
核可以用于机器学习模型,它可以作为特征工程过程的一部分进行集成,并应用于输入数据,以创建更好地捕捉数据中时间模式的新特征。
我将在后续的帖子中展示如何处理这些问题。
了解更多关于PyCircular的信息,请访问github.com/albahnsen/pycircular
介绍 PyDicom 及其类、方法和属性
PyDicom……超越像素数据
·
关注 发布于 Towards Data Science ·12 分钟阅读·2023 年 1 月 4 日
–
如果你是 Python 爱好者,并且正在处理医疗数据,特别是 DICOM 数据,你可能已经听说过很棒的 Python 包 PyDicom。在这篇文章中,你将学习这个包的基础知识。我们将讨论 PyDicom 的类(DataSet、DataElement、Sequence)以及一些你可以应用于 PyDicom 类的有用方法和属性。
照片由 Owen Beard 提供,来自 Unsplash
PyDicom 是一个用于处理 DICOM 文件(如医学图像、报告和放射治疗对象)的纯 Python 包。PyDicom 使读取和写入这些复杂文件变得简单,转换为自然的 Python 结构以便于操作。
DICOM(医学数字成像和通信)是医学成像系统的标准语言。当你听到“DICOM”这个词时,你可能会认为它只是医学图像。但 医疗数据科学家/分析师 可能会将其视为任何其他 医学数据集 的宝贵财富。因为它不仅仅是像素数据。DICOM 文件包含许多与患者、成像模态等相关的有用数据。
处理 DICOM 元数据并访问特定属性对任何人(包括数据科学家/分析师)在处理 DICOM 文件时都是有益的。幸运的是,PyDicom 提供了许多有用的函数、方法和属性来处理 DICOM 文件。
感谢 PyDicom 的创建者和贡献者
让我们开始编码
当我想探索一个新的 Python 包时,我通常会从使用该包的一般示例开始,然后逐渐深入。我的主要目的是学习如何提取多个 DICOM 文件的元数据,并将其转换为我可以用于分析和探索的数据集。
所以……让我们从一个常见的 PyDicom 示例开始我们的旅程。
我们将讨论以下内容:
读取 DICOM 文件的示例
PyDicom 的核心元素
PyDicom 数据集
*PyDicom 数据集的 * .keys() 方法
.values() 方法
.elements() 方法
.group_dataset() 方法
.dir() 方法
PyDicom 数据集的属性 pixel_array 属性
PyDicom 数据元素
PyDicom 序列
PyDicom 与 ImageIO
读取 DICOM 文件的示例
要使用 pydicom 读取 DICOM 文件,你可以按照以下步骤操作:
- 安装 pydicom:要使用 pydicom,你需要通过 pip 安装它。
# Install PyDicom Package
!pip install pydicom
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Collecting pydicom
Downloading pydicom-2.3.1-py3-none-any.whl (2.0 MB)
|████████████████████████████████| 2.0 MB 5.4 MB/s
Installing collected packages: pydicom
Successfully installed pydicom-2.3.1
- 导入 pydicom 库:在你的 Python 脚本中,你需要导入 pydicom 库以便使用它。
# Import the pacakage
import pydicom
网上有很多 DICOM 样本。你也可以使用你自己的 DICOM 文件。在这篇文章中,我选择了一个 数据集,包含一个患者的 99 切片胸部 CT 扫描。你可以在 Kaggle 上找到它。我将数据集保存到了我的 Drive 中,以便通过 GoogleColab 轻松访问。
注意: 有时,某些代码行的输出可能会过长和庞大。对此提前表示歉意!!!但这是一个很好的机会,让你感受一下 DICOM 文件在现实生活中的样子。
- 读取 DICOM 文件:要读取 DICOM 文件,你可以使用 dcmread 函数,它返回一个包含 DICOM 文件数据的 Dataset 对象。
# Reading a DICOM file from a specific path
dcm_data = pydicom.dcmread('/content/drive/MyDrive/Datasets/Kaggle/DICOM/dicom_lung/000000.dcm')
dcm_data
完整输出已作为 GitHub gist 添加,详细内容请参阅这个链接。 [作者提供的图片]
很明显,除了像素数据之外,还有很多 DICOM 元数据。但我们可以看到输出的模式,即 DICOM 属性是逐行写入的。每一行都有一个唯一的标签和其他核心的 DICOM 属性元素。让我们更详细地讨论它们。
PyDicom 中的核心元素
使用 .dcmread() 会封装一个 DataSet,即一个字典数据结构 {}。这个 DataSet 包含如下表示的键和值:
-
Keys: 包含你正在读取的 DICOM 文件中指定的属性的 DICOM 标签。示例键包括:
-
(0x0010, 0x0010) PatientName 属性。
-
(0x0028, 0x0010) 行数属性。
-
(0x7fe0, 0x0010) PixelData 属性。
标签的数字由两个十六进制数字组成,第一个数字表示组,第二个数字表示特定元素。因此,你可能会发现许多属性具有相同的第一个标签数字。
-
-
Values: 这个字典的值通常包含以下内容:
-
Tag: 元素的标签,例如 (0028, 0030)。
-
Keyword: 描述属性所指的内容。标签 (0028, 0030) 的关键字是“Pixel Spacing”。
VR: 这仅是两个字符,指的是元素的值表示(Value Representation),它描述了属性值的数据类型和格式。标签 (0028, 0030) 的 VR 是“DS”,即十进制字符串。你可以通过 Python 结构查看任何标签的 VR 以及它是如何表示的,详细信息请参考链接。
Value: 元素的实际值。它可以是整数、字符串、列表,甚至是Sequence,即一个属性的数据集。标签 (0028, 0030) 的值是一个包含两个浮点数的列表,分别表示沿行和列的物理距离(单位为毫米)。这个列表的一个示例是 [0.564453125, 0.564453125]。
-
dcmread() 函数的输出。[作者提供的图片]
现在我们理解了使用 PyDicom 读取 DICOM 文件需要直接操作主要类 DataSet。让我们更深入地讨论一下 DataSet 的内容。
PyDicom DataSet
DICOM DataSet 是 DICOM DataElements 的可变映射。DICOM DataSet 中的每个 DataElement,即字典中的值,都有一个唯一的标签,即字典的键,用于标识它。例如,“PatientName” 属性对应于 DICOM 标准中的标签 (0x0010, 0x0010),它标识了病人的名字数据元素。
PyDicom DataSet 类的内容。[作者提供的图片]
你可以通过多种方式访问特定的 DICOM 属性,例如:
# Extract the patient's name.
patient_name = dcm_data.PatientName
patient_name
'C3N-00247'
或者你可以通过唯一标签访问相同的属性 PatientName。例如,PatientName 属性的标签是 (0010, 0010)。注意这些数字是十六进制的,符合 DICOM 标准。
# Extract the patient's name using its unique DICOM tag (0010, 0010)
dcm_data[0x0010, 0x0010]
(0010, 0010) Patient's Name PN: 'C3N-00247'
注意,使用标签,即数据集字典的键,会返回字典的整个值,而不仅仅是属性的实际值。要仅获取实际值,你应该使用 .value 属性。
# Extract the patient's name using its unique DICOM tag (0010, 0010)
dcm_data[0x0010, 0x0010].value
'C3N-00247'
你可以决定是通过唯一标签提取特定属性,还是通过其关键词提取。现在让我们深入了解一下我们可以与 DataSet 类一起使用的一些有用的方法和属性。
PyDicom DataSet 的方法
.keys() 方法
使用**.keys()** 返回 DataSet 字典的键列表。当合并具有共同 DICOM 属性的多个 DICOM 文件的元数据时,这种方法可能会很有用。
# Extract the keys, the DICOM tags, that are in a DICOM file
dcm_data.keys()
.keys() 方法的输出。 [图片由作者提供]
.values() 方法
这种方法返回 DataSet 字典的值列表。这种形式有点笨重,不太适合阅读。但在某些情况下,这种方法可能对迭代值列表很有用。
# Extract the values, the DICOM attributes, that are in a DICOM file
dcm_data.values()
.values() 方法的部分输出。 [图片由作者提供]
.elements() 方法
使用 .elements() 方法可以获得 DataSet 的顶级元素。当你不需要 DICOM 文件中可能存在的 Sequences 的 DICOM 属性时,这个方法会很有用。注意下面输出图中 .elements() 方法使用时 Sequences 的表示方式。
# Extract the top-level elements of the Dataset Class
[*dcm_data.elements()]
.elements() 方法的完整输出已作为 GitHub gist 添加,查看 链接。 [图片由作者提供]
.group_dataset() 方法
如上所述,你可能会发现许多属性的第一个数字相同。这些标签,具有共同的第一个标签,通常描述一个共同的参数。例如,具有共同第一个标签 (0x0010) 的属性通常与病人相关。第一个标签为 (0x0028) 的属性描述图像像素属性。有时查看与特定参数相关的所有属性会很有帮助。使用方法 (.group_dataset) 返回一个仅包含某个组元素的 DataSet。
# Extract the attributes related to 0x0028 tag, these are related to ImagePixel
dcm_data.group_dataset(0x0028)
(0028, 0002) Samples per Pixel US: 1
(0028, 0004) Photometric Interpretation CS: 'MONOCHROME2'
(0028, 0010) Rows US: 512
(0028, 0011) Columns US: 512
(0028, 0030) Pixel Spacing DS: [0.564453125, 0.564453125]
(0028, 0100) Bits Allocated US: 16
(0028, 0101) Bits Stored US: 12
(0028, 0102) High Bit US: 11
(0028, 0103) Pixel Representation US: 0
(0028, 0106) Smallest Image Pixel Value US: 0
(0028, 0107) Largest Image Pixel Value US: 4095
(0028, 0303) Longitudinal Temporal Information M CS: 'MODIFIED'
(0028, 1050) Window Center DS: [-500, 40]
(0028, 1051) Window Width DS: [1500, 400]
(0028, 1052) Rescale Intercept DS: '-1024.0'
(0028, 1053) Rescale Slope DS: '1.0'
(0028, 1055) Window Center & Width Explanation LO: ['WINDOW1', 'WINDOW2']
.dir() 方法
返回 DataSet 中元素关键词的字母顺序列表。这是对你处理的元数据提供初步了解的好选择。
# An alphabetical list of the element keywords in the DICOM DataSet.
dcm_data.dir()
['AccessionNumber', 'AcquisitionDate', 'AcquisitionNumber', 'AcquisitionTime',
'BitsAllocated', 'BitsStored', 'BodyPartExamined', 'CTDIvol',
'ClinicalTrialTimePointDescription', 'ClinicalTrialTimePointID', 'Columns',
'ContentDate', 'ContentTime', 'ConvolutionKernel', 'DataCollectionDiameter',
'DateOfLastCalibration', 'DeidentificationMethod', 'DeidentificationMethodCodeSequence',
'DistanceSourceToDetector', 'DistanceSourceToPatient', 'EstimatedDoseSaving',
'EthnicGroup', 'Exposure', 'ExposureModulationType', 'ExposureTime',
'FilterType', 'FocalSpots', 'FrameOfReferenceUID', 'GantryDetectorTilt',
'GeneratorPower', 'HighBit', 'ImageComments', 'ImageOrientationPatient',
'ImagePositionPatient', 'ImageType', 'InstanceNumber', 'KVP', 'LargestImagePixelValue',
'LongitudinalTemporalInformationModified', 'Manufacturer', 'ManufacturerModelName',
'Modality', 'PatientAge', 'PatientBirthDate', 'PatientID', 'PatientIdentityRemoved',
'PatientName', 'PatientPosition', 'PatientSex', 'PerformedProcedureStepStartDate',
'PerformedProcedureStepStartTime', 'PhotometricInterpretation', 'PixelData',
'PixelRepresentation', 'PixelSpacing', 'PositionReferenceIndicator', 'ProtocolName',
'ReasonForStudy', 'ReconstructionDiameter', 'ReferencedImageSequence',
'ReferencedStudySequence', 'ReferringPhysicianName', 'RequestedProcedureDescription',
'RescaleIntercept', 'RescaleSlope', 'RotationDirection', 'Rows', 'SOPClassUID',
'SOPInstanceUID', 'SamplesPerPixel', 'SeriesDate', 'SeriesDescription',
'SeriesInstanceUID', 'SeriesNumber', 'SeriesTime', 'SingleCollimationWidth',
'SliceLocation', 'SliceThickness', 'SmallestImagePixelValue', 'SoftwareVersions',
'SourceImageSequence', 'SpecificCharacterSet', 'SpiralPitchFactor', 'StudyComments',
'StudyDate', 'StudyDescription', 'StudyID', 'StudyInstanceUID', 'StudyPriorityID',
'StudyStatusID', 'StudyTime', 'TableFeedPerRotation', 'TableHeight', 'TableSpeed',
'TimeOfLastCalibration', 'TotalCollimationWidth', 'WindowCenter', 'WindowCenterWidthExplanation',
'WindowWidth', 'XRayTubeCurrent']
特定属性集的过滤器可以作为参数实现到 .dir() 方法中。
# Extract all the attributes that have "Pixel" in its keywords
dcm_data.dir('Pixel')
['LargestImagePixelValue',
'PixelData',
'PixelRepresentation',
'PixelSpacing',
'SamplesPerPixel',
'SmallestImagePixelValue']
PyDicom DataSet 的属性
有一些属性可以与 DataSet 类一起使用。但我只会指出一个,最重要和最常用的属性,即pixel_array。
# Extract the image pixels
dcm_data.pixel_array
array([[356, 244, 201, ..., 190, 224, 76],
[309, 387, 370, ..., 122, 17, 6],
[334, 476, 486, ..., 29, 46, 47],
...,
[ 98, 188, 186, ..., 156, 86, 125],
[ 66, 138, 139, ..., 221, 74, 81],
[168, 173, 100, ..., 188, 135, 147]], dtype=uint16)
# Image representation
im = dcm_data.pixel_array
plt.imshow(im, cmap='gray')
plt.axis('off')
plt.title('Axial Slice of a Chest-CT')
plt.show()
[作者提供的图片]
PyDicom DataElement
正如我们上面提到的,DataSet 是一个 PyDicom 类,包含一组 DICOM 标签作为键,值作为 DICOM DataElements。DataElements 是在读取 DICOM 文件时可能会发现的属性值或元数据。能够访问特定信息对于你的工作或研究非常有用,而无需在如此庞大的文本数据中搜索。你可以使用属性在特定 DataElement 中访问特定信息。
PyDicom DataElement 类的内容及访问特定信息的方法。[作者提供的图片]
# Return the element's keyword (if known) as str
kwrds = dcm_data[0x0010, 0x0010].keyword
# Return the DICOM dictionary name for the element as str
name = dcm_data[0x0010, 0x0010].name
# Return the element's Value Representation
vr = dcm_data[0x0010, 0x0010].VR
# Return the element's value.
value = dcm_data[0x0010, 0x0010].value
print("The element's Keyword : ", kwrds)
print("The element's name : ", name)
print("The element's Value Representation : ", vr)
print("The element's value : ", value)
The element's Keyword : PatientName
The element's name : Patient's Name
The element's Value Representation : PN
The element's value : C3N-00247
PyDicom 序列
PyDicom DataSet 中的 DataElement 可能包含字符串、整数、列表,甚至是数据序列。该序列源自 Python 的列表,但它甚至可以是一个 DataSet。让我们看看如何访问特定序列中的参数。由于我们正在处理的文件信息量庞大,让我们使用 .dir(*filter) 方法。通过合适的过滤器参数,我们可以提取“序列”的名称。
# Extract all the attributes that have "Sequence" in its keywords
dcm_data.dir('Sequence')
['DeidentificationMethodCodeSequence',
'ReferencedImageSequence',
'ReferencedStudySequence',
'SourceImageSequence']
让我们尝试使用去标识化方法代码序列。
# Return a specific Sequence, "DeidentificationMethodCodeSequence"
dcm_data.DeidentificationMethodCodeSequence
<Sequence, length 8>
要打印出序列中的所有元素,我们可以使用索引。
# Extract the contents of a Sequence
dcm_data.DeidentificationMethodCodeSequence[:]
[(0008, 0100) Code Value SH: '113100'
(0008, 0102) Coding Scheme Designator SH: 'DCM'
(0008, 0104) Code Meaning LO: 'Basic Application Confidentiality Profile',
(0008, 0100) Code Value SH: '113101'
(0008, 0102) Coding Scheme Designator SH: 'DCM'
(0008, 0104) Code Meaning LO: 'Clean Pixel Data Option',
(0008, 0100) Code Value SH: '113104'
(0008, 0102) Coding Scheme Designator SH: 'DCM'
(0008, 0104) Code Meaning LO: 'Clean Structured Content Option',
(0008, 0100) Code Value SH: '113105'
(0008, 0102) Coding Scheme Designator SH: 'DCM'
(0008, 0104) Code Meaning LO: 'Clean Descriptors Option',
(0008, 0100) Code Value SH: '113107'
(0008, 0102) Coding Scheme Designator SH: 'DCM'
(0008, 0104) Code Meaning LO: 'Retain Longitudinal Temporal Information Modified Dates Option',
(0008, 0100) Code Value SH: '113108'
(0008, 0102) Coding Scheme Designator SH: 'DCM'
(0008, 0104) Code Meaning LO: 'Retain Patient Characteristics Option',
(0008, 0100) Code Value SH: '113109'
(0008, 0102) Coding Scheme Designator SH: 'DCM'
(0008, 0104) Code Meaning LO: 'Retain Device Identity Option',
(0008, 0100) Code Value SH: '113111'
(0008, 0102) Coding Scheme Designator SH: 'DCM'
(0008, 0104) Code Meaning LO: 'Retain Safe Private Option']
要访问序列中的特定信息,我们可以使用其索引。
# Return the info of specific line in a Sequence
dcm_data.DeidentificationMethodCodeSequence[0].CodeMeaning
'Basic Application Confidentiality Profile'
PyDicom 与 ImageIO
个人观点,我发现 PyDicom 是处理 DICOM 文件的最佳包。因为它专为 DICOM 设计,这一点很明显。它为程序员提供了更灵活的选项,特别是当用户想处理 DICOM 元数据而不仅仅是像素数据时。然而,另一方面,如果我只需要像素数据,我会更倾向于使用 ImageIO,因为它只提供处理像素数据所需的基本元数据。要了解更多关于如何使用 ImageIO 处理 DICOM 文件的信息,请查看这篇文章。
## 使用 ImageIO Python 包处理 DICOM
医学图像 == DICOM
towardsdatascience.com
结论:
-
PyDicom DataSet 是该包的主类,用户将直接处理它。
-
DataSet 是一个 Python 字典结构,包含 DICOM 标签作为键,DICOM 属性作为值。
-
我们介绍了如何使用 PyDicom 提供的一些重要方法和属性来处理 DataSet 类。
-
DataSet 的值基本上是一个 DataElement,另一个 PyDicom 类,它包含 DICOM 标签、关键字、VR 和每个 DICOM 属性的值。
感谢阅读…
推荐
- 欲了解更多关于 DICOM 的信息,请参阅这篇文章
目前用于临床护理的 DICOM 图像数量多达数十亿!
-
DICOM 元数据——大数据分析的有用资源:
这篇 文章 概述了通过结合患者访问和 DICOM 信息来表示数据的新方法,医学影像元数据的高级使用,辐射剂量分析和图像分割,以及用于特征工程的深度学习,以丰富数据。
-
PyDicom 文档: 我们仅仅触及了这个优秀包的表面,它的文档非常详尽。如果你想了解更多关于 PyDicom 的内容,请参考这个 链接 并尝试编写你在其中阅读到的代码。
参考文献
[1] PyDicom 文档, 用户指南, [访问日期:2022 年 12 月 25 日]
[2] PyDicom 文档, 元素 VR 和 Python 类型, [访问日期:2022 年 12 月 25 日]
[3] PyDicom 文档,API 参考, [访问日期:2022 年 12 月 25 日]
介绍 Python 的魔法方法
原文:
towardsdatascience.com/introducing-pythons-magic-methods-f443ed913703
PYTHON | PROGRAMMING
一份关于利用 dunder 函数的力量来提升编程水平的实用指南
·发表于 Towards Data Science ·阅读时长 7 分钟·2023 年 5 月 24 日
–
照片由 Matt Palmer 提供,来源于 Unsplash
Python 是一种很棒的编程语言,GitHub发现,它在 2022 年也是第二大热门语言。Python 最吸引人的优点是其强大的社区。似乎 Python 为你可能遇到的任何使用场景都有一个包。
Python 还有很多酷炫的特性,这些特性并不是常见的知识。如果你有兴趣了解更多,请随时查看我之前的相关文章。
通过这些酷炫的隐藏 Python 特性,将你的编码技能提升到新水平
towardsdatascience.com
在广阔而动态的 Python 编程世界中,存在一组独特的函数,这些函数通常被初学者忽视,但它们在语言的生态系统中具有重要意义。这些就是魔法方法(也称为 dunder 函数)。
魔法方法是一组在 Python 中预定义的方法,提供了特殊的语法特性。它们通过其前后有双下划线来轻松识别,例如 __init__
、__call__
、__len__
等等。
魔法方法在定义类的对象如何处理各种 Python 操作方面发挥了关键作用。实质上,它们允许自定义对象表现得类似于内置的 Python 类型。直接的好处是增强了 Python 的一致性和直观性。
在这篇文章中,我们将专注于并讨论强大的 Dunder 函数背后的魔法。我们将探讨它们的目的并讨论它们的使用。
无论你是 Python 新手还是经验丰富的程序员,本指南旨在提供对 Dunder 函数的全面理解,使你的 Python 编程体验更加高效和愉快。
记住,Python 的魔力不仅在于它的简单性和多样性,还在于像 Dunder 函数这样的强大功能。所以,让我们一起揭开魔法的面纱吧!
__init__
也许所有 Dunder 函数中最基本的是__init__
。这是 Python 在我们创建(或如名称所示,初始化)一个新对象时自动调用的魔法方法。
class Pizza:
def __init__(self, size, toppings):
self.size = size
self.toppings = toppings
# Now let's create a pizza
my_pizza = Pizza('large', ['pepperoni', 'mushrooms'])
print(my_pizza.size) # This will print: large
print(my_pizza.toppings) # This will print: ['pepperoni', 'mushrooms']
我们创建了一个名为Pizza
的类。我们将__init__
函数设置为接受初始化时指定的大小和配料参数,并将它们设置为自定义对象的属性。
在这里,self
用于表示类的实例。所以当我们说self.size = size
时,我们是在说:“嘿,这个 pizza 对象有一个属性 size,我想它是我在创建对象时提供的大小。”
__str__
和__repr__
__str__
这是 Python 的魔法方法,允许我们为自定义对象定义描述。它本质上是在回答以下问题:
“你会如何向朋友描述这个对象?”
当你使用str()
打印对象或将其转换为字符串时,Python 会检查你是否为该对象的类定义了__str__
方法。
如果你有,它会使用那个方法将对象转换为字符串。
我们可以扩展我们的 Pizza 示例,加入一个__str__
函数,如下所示:
class Pizza:
def __init__(self, size, toppings):
self.size = size
self.toppings = toppings
def __str__(self):
return f"A {self.size} pizza with {', '.join(self.toppings)}"
my_pizza = Pizza('large', ['pepperoni', 'mushrooms'])
print(my_pizza) # This will print: A large pizza with pepperoni, mushrooms
__repr__
__str__
函数更像是描述对象属性的非正式方式。另一方面,__repr__
用于提供一个更正式、详细和明确的自定义对象描述。
如果你在对象上调用repr()
,或者在控制台中输入对象的名称,Python 将查找__repr__
方法。
如果__str__
没有定义,Python 会使用__repr__
作为备份来打印对象或将其转换为字符串。因此,至少定义__repr__
通常是一个好主意,即使你不定义__str__
。
下面是我们可以为我们的 pizza 示例定义__repr__
的方法:
class Pizza:
def __init__(self, size, toppings):
self.size = size
self.toppings = toppings
def __repr__(self):
return f"Pizza('{self.size}', {self.toppings})"
my_pizza = Pizza('large', ['pepperoni', 'mushrooms'])
print(repr(my_pizza)) # This will print: Pizza('large', ['pepperoni', 'mushrooms'])
你看,__repr__
会给你一个可以作为 Python 命令运行的字符串,以重建 pizza 对象,而__str__
则提供了一个更易于理解的描述。希望这有助于你更好地理解这些魔法方法!
__add__
在 Python 中,我们都知道你可以使用+
运算符将数字相加,例如3 + 5
。
但是如果我们想添加一些自定义对象的实例呢?__add__
Dunder 函数允许我们做到这一点。它使我们能够定义+
运算符在自定义对象上的行为。
为了保持一致性,假设我们想要在我们的披萨示例中定义 +
行为。假设每当我们将两张或更多的披萨相加时,它将自动合并所有的配料。以下是它可能的实现方式:
class Pizza:
def __init__(self, size, toppings):
self.size = size
self.toppings = toppings
def __add__(self, other):
if not isinstance(other, Pizza):
raise TypeError("You can only add another Pizza!")
new_toppings = self.toppings + other.toppings
return Pizza(self.size, new_toppings)
# Let's create two pizzas
pizza1 = Pizza('large', ['pepperoni', 'mushrooms'])
pizza2 = Pizza('large', ['olives', 'pineapple'])
# And now let's "add" them
combined_pizza = pizza1 + pizza2
print(combined_pizza.toppings) # This will print: ['pepperoni', 'mushrooms', 'olives', 'pineapple']
类似于 __add__
双下划线方法,我们还可以定义其他算术函数,比如 __sub__
(用于使用 —
操作符进行减法)和 __mul__
(用于使用 *
操作符进行乘法)。
len
这个双下划线方法允许我们定义 len()
函数对于我们自定义对象应返回的内容。
Python 使用 len()
来获取数据结构的长度或大小,比如列表或字符串。
在我们的披萨类的上下文中,我们可以说披萨的“长度”是它拥有的配料数量。以下是我们可以实现这一点的方法:
class Pizza:
def __init__(self, size, toppings):
self.size = size
self.toppings = toppings
def __len__(self):
return len(self.toppings)
# Let's create a pizza
my_pizza = Pizza('large', ['pepperoni', 'mushrooms', 'olives'])
print(len(my_pizza)) # This will print: 3
在 __len__
方法中,我们仅返回 toppings
列表的长度。现在,len(my_pizza)
将告诉我们 my_pizza
上有多少个配料。
注意:
__len__
应该始终返回一个整数,并且预期是一个非负值。负配料只是怪异,不是吗?
iter
这个双下划线方法允许你的对象是可迭代的——也就是说,可以在 for
循环中使用。
为此,我们还需要定义 __next__
函数。这个函数用于定义返回迭代中的下一个值的行为。此外,它还应该在序列中没有更多项目时向可迭代对象发出信号。我们通常通过引发 StopIteration
异常来实现这一点。
对于我们的披萨示例,假设我们想要迭代配料。我们可以通过定义 __iter__
方法使我们的披萨类可迭代,如下所示:
class Pizza:
def __init__(self, size, toppings):
self.size = size
self.toppings = toppings
def __iter__(self):
self.n = 0
return self
def __next__(self):
if self.n < len(self.toppings):
result = self.toppings[self.n]
self.n += 1
return result
else:
raise StopIteration
# Let's create a pizza
my_pizza = Pizza('large', ['pepperoni', 'mushrooms', 'olives'])
# And now let's iterate over it
for topping in my_pizza:
print(topping)
在这种情况下,for
循环调用 __iter__
,它初始化一个计数器(self.n
)并返回披萨对象本身(self
)。
然后,for
循环调用 __next__
来依次获取每个配料。
当 __next__
返回了所有配料后,它会引发 StopIteration
异常,然后 for
循环现在知道没有更多的配料了,因此将停止迭代过程。
结论
总之,很明显,魔法方法确实是 Python 语言的核心。
它们构成了许多 Python 内置操作的基础,为我们开发者提供了必要的自由度,以便以与语言其余部分的用法一致的方式个性化我们对象的行为。
在本文中,我们讨论了如何利用双下划线函数,如 __init__
、__iter__
和 __len__
,来满足我们的需求。你可以在这里找到 Python 允许的所有双下划线方法的完整列表:
[## mathspp - 将你的 Python 🐍 提升到下一个水平 🚀
这是对 Python 中双下划线方法的介绍,帮助你理解它们是什么以及它们的作用。(如果…
你喜欢这篇文章吗?每月$5,你可以成为会员,解锁无限访问 Medium 的权限。你将直接支持我和你在 Medium 上喜爱的所有其他作家。非常感谢!
## 通过我的推荐链接加入 Medium - David Farrugia
获取对我所有⚡高级⚡内容和 Medium 上无限制的访问权限。通过买一杯咖啡支持我的工作…
想要联系我吗?
我非常想听听你对这个话题或关于 AI 和数据的任何想法。
如果你希望联系我,请发邮件至 davidfarrugia53@gmail.com。
介绍 Python 的 Parse:正则表达式的终极替代品
PYTHON TOOLBOX
使用最佳实践和实际案例来展示强大的文本解析库
·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 6 月 19 日
–
parse 库非常简单易用。照片由 Amanda Jones 提供,来源于 Unsplash
本文介绍了一个名为 parse
的 Python 库,用于快速、方便地解析和提取文本数据,是 Python 正则表达式的绝佳替代品。
并且涵盖了 parse
库的最佳实践和解析 nginx 日志文本的实际案例。
介绍
我有一个同事叫王。一天,他带着担忧的表情来到我面前,说他遇到了一个复杂的问题:他的老板要求他分析过去一个月的服务器日志,并提供访客流量统计。
我告诉他很简单,只需使用正则表达式。例如,要分析 nginx 日志,使用以下正则表达式,这很基础。
content:
192.168.0.2 - - [04/Jan/2019:16:06:38 +0800] "GET http://example.aliyundoc.com/_astats?application=&inf.name=eth0 HTTP/1.1" 200 273932
regular expression:
(?<ip>\d+\.\d+\.\d+\.\d+)( - - \[)(?<datetime>[\s\S]+)(?<t1>\][\s"]+)(?<request>[A-Z]+) (?<url>[\S]*) (?<protocol>[\S]+)["] (?<code>\d+) (?<sendbytes>\d+)
但王依然担心,学习正则表达式太复杂了。虽然网上有很多现成的例子可以学习,但他在解析不常见的文本格式时需要帮助。
而且,即使这次他能解决问题,如果老板在提交分析时要求更改解析规则呢?难道他不会再费很长时间吗?
是否有更简单、更方便的方法?
我考虑了一下,说,当然有。今天我们介绍的主角是 Python parse
库。
安装与设置
如在 parse GitHub 页面上所述,它使用 Python 的 format() 语法来解析文本,本质上作为 Python f-strings 的逆操作。
在开始使用 parse
之前,让我们看看如何安装这个库。
直接使用 pip 安装:
python -m pip install parse
使用 conda 安装可能更麻烦,因为 parse 不在默认的 conda 通道中,需要通过 conda-forge 安装:
conda install -c conda-forge parse
安装后,你可以在代码中使用 from parse import *
直接使用库的方法。
特性与用法
parse
API 类似于 Python 正则表达式,主要包括 parse
、search
和 findall
方法。基本用法可以从 parse 文档中学习。
模式格式
parse 格式与 Python 格式语法非常相似。你可以使用 {}
或 {field_name}
捕获匹配的文本。
例如,在以下文本中,如果我想获取个人资料 URL 和用户名,我可以这样写:
content:
Hello everyone, my Medium profile url is https://qtalen.medium.com,
and my username is @qtalen.
parse pattern:
Hello everyone, my Medium profile url is {profile},
and my username is {username}.
或者你想提取多个电话号码。电话号码的前面有不同格式的国家代码,且电话号码长度固定为 11 位。你可以这样写:
compiler = Parser("{country_code}{phone:11.11},")
content = "0085212345678901, +85212345678902, (852)12345678903,"
results = compiler.findall(content)
for result in results:
print(result)
或者如果你需要处理一段 HTML 标签中的文本,但该文本前后有不定长度的空白,你可以这样写:
content:
<div> Hello World </div>
pattern:
<div>{:^}</div>
在上面的代码中,{:11}
指宽度,意味着捕获至少 11 个字符,相当于正则表达式 (.{11,})?
。{:.11}
指精度,意味着最多捕获 11 个字符,相当于正则表达式 (.{,11})?
。因此,当结合使用时,它表示 (.{11, 11})?
。结果是:
捕获固定宽度的字符。图像由作者提供
parse 最强大的功能是处理时间文本,可以直接解析为 Python datetime 对象。例如,如果我们想解析 HTTP 日志中的时间:
content:
[04/Jan/2019:16:06:38 +0800]
pattern:
[{:th}]
获取结果
有两种获取结果的方法:
-
对于使用
{}
而没有字段名称的捕获方法,你可以直接使用result.fixed
将结果获取为元组。 -
对于使用
{field_name}
的捕获方法,你可以使用result.named
将结果获取为字典。
自定义类型转换
尽管使用 {field_name}
已经非常简单,但源代码显示 {field_name}
在内部被转换为 (?P<field_name>.+?)
。所以,parse
仍然使用正则表达式进行匹配。.+?
表示非贪婪模式下的一个或多个随机字符。
从 parse 格式到正则表达式的转换过程。图像由作者提供
然而,我们通常希望更精确地匹配。例如,文本“my email is xxx@xxx.com”,“my email is {email}”
可以捕获电子邮件。有时我们可能会得到脏数据,例如“my email is xxxx@xxxx”,我们不想抓取它。
是否有办法使用正则表达式进行更准确的匹配?
这时,with_pattern
装饰器就派上用场了。
例如,对于捕获电子邮件地址,我们可以这样编写:
@with_pattern(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b')
def email(text: str) -> str:
return text
compiler = Parser("my email address is {email:Email}", dict(Email=email))
legal_result = compiler.parse("my email address is xx@xxx.com") # legal email
illegal_result = compiler.parse("my email address is xx@xx") # illegal email
使用 with_pattern
装饰器,我们可以定义一个自定义字段类型,在这种情况下是 Email
,它将匹配文本中的电子邮件地址。我们还可以使用这种方法匹配其他复杂的模式。
现实世界的例子:解析 Nginx 日志
在了解解析的基本用法后,我们回到文章开头提到的王的困扰。让我们看看如果我们有过去一个月的服务器日志文件,如何解析日志。
注意: 我们选择了 NASA 的 HTTP 日志数据集 作为本实验的数据集,该数据集可以免费使用。
需要解析的文本片段如下:
文本片段是什么样的。作者截图
首先,我们需要预处理解析表达式。这样,在解析大型文件时,我们就不必为每一行文本编译正则表达式,从而提高性能。
接下来,parse_line
方法是这个例子的核心。它使用预处理的表达式来解析文本,如果有匹配则返回相应的匹配结果,如果没有则返回一个空字典。
然后,我们使用 read_file
方法逐行处理文本,使用生成器,这可以最小化内存使用。然而,由于磁盘的 4k 能力限制,这种方法可能无法保证性能。
由于我们需要对日志文件进行统计,我们必须使用 from_records
方法从匹配结果构造一个 DataFrame
。
最后,在 main
方法中,我们将所有方法整合在一起,并尝试计算不同 status_code
的出现次数:
王的困扰已经轻松解决。作者图片
就这样。王的困扰已经轻松解决。
使用 parse
库的最佳实践
尽管 parse
库非常简单,以至于我在文章中只有一点要写,但仍然有一些最佳实践需要遵循,就像正则表达式一样。
可读性和可维护性
为了高效捕获文本并保持表达式,建议始终使用 {field_name}
而不是 {}
。这样,你可以直接使用 result.named
获取键值结果。
推荐使用 Parser(pattern)
来预处理表达式,而不是 parse(pattern, text)
。
一方面,这可以提高性能。另一方面,当使用 Custom Type Conversions
时,你可以将 pattern
和 extra_type
保持在一起,这使得维护更容易。
大数据集的性能优化
如果你查看源代码,你会发现{}
和{field_name}
分别使用正则表达式(.+?)
和(?P<field_name>.+?)
进行捕获。这两个表达式都使用了非贪婪模式。所以当你使用with_pattern
编写自己的表达式时,也应该尝试使用非贪婪模式。
同时,在编写with_pattern
时,如果使用()
进行捕获分组,请使用regex_group_count
来指定具体的分组,如:[@with_pattern](http://twitter.com/with_pattern)(r’((\d+))’, regex_group_count=2)
。
最后,如果在with_pattern
中不需要分组,请使用(?:x)
代替。@with_pattern(r’(?:<input.*?>)(.*?)(?:</input>)’, regex_group_count=1)
表示你想要捕获输入标签之间的内容。输入标签不会被捕获。
结论
在这篇文章中,我改变了以往写长篇论文的方式。通过解决同事的问题,我简要介绍了parse
库的使用。希望你喜欢这种风格。
这篇文章没有涵盖官方网站上的详细使用方法,但介绍了一些最佳实践和基于我经验的性能优化方案。
同时,我详细解释了如何使用parse
库通过实际例子解析 nginx 日志。
正如新系列标题所示,除了提高代码执行速度和性能之外,使用各种工具提升工作效率也是性能提升的一部分。
这篇文章帮助数据科学家简化文本解析,将时间花在更重要的任务上。如果你对这篇文章有任何想法,请随时留言讨论。
我的上一系列文章是关于 Python 并发的,你可以在这里阅读:
Python 并发
查看列表10 篇故事 [## 通过我的推荐链接加入 Medium - 彭千
作为 Medium 会员,你的部分会费将分配给你阅读的作者,并且你可以完全访问每个故事……
medium.com](https://medium.com/@qtalen/membership?source=post_page-----3ae07e51b753--------------------------------)
本文最初发布于:www.dataleadsfuture.com/introducing-pythons-parse-the-ultimate-alternative-to-regular-expressions/
介绍 Quix Streams:一个开源的 Python Kafka 库
轻松生成和消费类似 Pandas 接口的时间序列数据流
·
关注 发表在 Towards Data Science · 7 分钟阅读 · 2023 年 3 月 6 日
–
作者提供的图片
你可能会好奇,为什么世界上还需要另一个用于 Kafka 的 Python 框架。毕竟,已有很多现有的库和框架可供选择,比如 kafka-python、Faust、PySpark 等等。
然而,Quix Streams 的重点是时间序列和遥测数据,因此其功能被优化用于遥测相关的用例。这可能是设备遥测(最初是对 Formula 1 赛车的传感器数据进行道路测试)或其他类型的遥测数据,如指标、日志和跟踪。
它还旨在帮助你充分利用 Apache Kafka 的水平扩展能力。如果你需要处理大量的数据流(例如,每秒 60,000 个数据点),这尤其重要。
尽管如此,你不必在 Formula 1 遥测上进行实时机器学习才能发现 Quix Streams 的有用之处——我希望它的简洁性和性能能让你们变得更高效,我也很期待看到你们发现的其他用例。
你可以使用 Quix Streams 做些什么
为了帮助你了解如何使用这个库,这里列出了核心功能和简化的代码示例,演示了它们的工作原理:
使用 Pandas DataFrames 更高效地生成数据
时间序列参数会同时发出,因此它们共享一个时间戳。独立处理这些数据是浪费的。该库使用了一个表格系统,可以原生地与 Pandas DataFrames 配合使用。每一行都有一个时间戳和用户定义的标签作为索引。
- 要了解如何使用该库与 Pandas 从 CSV 中直接流式传输数据的完整可运行示例,请参阅 这个 gist。
生成时间序列数据时无需担心序列化或反序列化
Quix Streams 使用不同的编解码器和优化来序列化和反序列化时间序列数据,以最小化负载,从而提高吞吐量并减少延迟。
- 以下示例演示了如何使用
add_value
方法将数据追加到流中:
利用内置缓冲区来优化时间序列数据窗口的处理操作
如果你以高频率发送数据,每条消息的处理成本可能很高。该库提供了内置的时间序列缓冲区用于生成和消费,允许在延迟和成本之间进行多种配置。
- 例如,你可以配置库,在收集到 100 个时间戳数据项或经过一定数量的毫秒后(使用数据中的时间戳,而不是消费者机器的时钟)从缓冲区释放一个数据包。
buffer.packet_size = 100
buffer.time_span_in_milliseconds = 100
- 然后你可以从缓冲区读取数据并使用
on_read
函数进行处理。
生产和消费不同类型的混合数据
该库允许你在同一时间戳下生成和消费不同类型的混合数据,如数字、字符串或二进制数据。
-
例如,你可以同时生成时间序列数据和大型二进制数据块。
-
通常,你会想将时间序列数据与二进制数据结合。在以下示例中,我们将公交车的车载摄像头与其 ECU 单元的遥测数据结合,以便在上下文中分析车载摄像头画面。
-
你还可以生成包含负载的事件:
-
例如,你可能需要监听时间序列或二进制流中的变化,并生成事件(如“超速”)。这些可能需要某种文件随事件消息一起发送(例如交易发票或带有照片证据的超速罚单)。这是一个关于测速摄像头的示例:
使用流上下文进行水平扩展
流上下文允许你将来自一个数据源的数据与补充元数据捆绑在同一范围内——这使工作负载可以通过多个副本进行水平扩展。
- 在以下示例中,
create_stream
函数用于创建一个名为 bus-123AAAV 的流,该流分配给特定消费者,并将按正确顺序接收消息:
利用内置的状态处理以增强鲁棒性
该库包括一个易于使用的状态存储,结合了 blob 存储和 Kubernetes 持久卷,确保快速恢复任何故障或中断。
要使用它,你可以创建 LocalFileStorage
的实例或使用我们的帮助类之一来管理状态,如 InMemoryStorage
。
这是一个对数据中选定列进行状态操作求和的示例:
其他性能和可用性增强
该库还包括许多其他增强功能,旨在简化与 Kafka 交互时配置和性能的管理过程:
-
无需模式注册表:该库不需要模式注册表来发送不同类型或参数的集合,这由协议内部处理。这意味着你可以在每个主题中发送多个模式。
-
消息拆分:Quix Streams 自动处理生产者端的大消息,必要时将其拆分。你不再需要担心 Kafka 消息限制。在消费者端,这些消息会自动合并。
-
消息代理配置:使用 Kafka 的最佳配置需要许多设置,而理想的配置需要时间。该库默认处理 Kafka 配置,但也支持自定义配置。
-
检查点:该库支持在从 Kafka 主题消费数据时进行手动或自动检查点。这使你能够通知消息代理你已经处理了某一点之前的消息(并且在未计划的重启情况下不会重复处理相同的消息)。
-
水平扩展:Quix Streams 使用流上下文功能处理水平扩展。你可以从一个副本扩展到多个副本,再缩减回一个副本,库确保数据负载始终在你的副本之间可靠地共享。
有关功能的详细概述,请参见库文档。
入门
要快速尝试 Quix Streams,您只需安装库并设置本地 Kafka 实例。
安装 Quix Streams
使用以下命令安装 Quix Streams:
python3 -m pip install quixstreams
- 要在配备 M1 或 M2 芯片的 Mac 上安装 Quix Streams,请参见此特殊安装指南:在 M1/M2 Mac 上安装 Quix Streams。
在本地安装 Kafka
该库需要利用消息代理来发送和接收数据。要在本地安装和测试 Kafka:
-
从Apache Kafka 下载页面下载 Apache Kafka 二进制文件。
-
将文件内容提取到方便的位置(即
kafka_dir
),然后使用以下命令启动 Kafka 服务:
Linux / macOS
<kafka_dir>/bin/zookeeper-server-start.sh config/zookeeper.properties
<kafka_dir>/bin/zookeeper-server-start.sh config/server.properties
Windows
<kafka_dir>\bin\windows\zookeeper-server-start.bat.\config\zookeeper.properties
<kafka_dir>\bin\windows\kafka-server-start.bat .\config\server.properties
以下示例将为您提供如何使用 Quix Streams 生产和消费数据的基本概念:
生产时间序列数据
这是一个如何使用 Python 将时间序列数据生产到 Kafka 主题的示例。
import quixstreams as qx
import time
import datetime
import math
# Connect to your kafka client
client = qx.KafkaStreamingClient('127.0.0.1:9092')
# Open the output topic which is where data will be streamed out to
# If the topic does not exist, it will be created
topic_producer = client.get_topic_producer(topic_id_or_name = "mytesttopic")
# Set stream ID or leave parameters empty to get stream ID generated.
stream = topic_producer.create_stream()
stream.properties.name = "Hello World Python stream"
# Add metadata about time series data you are about to send.
stream.timeseries.add_definition("ParameterA").set_range(-1.2, 1.2)
stream.timeseries.buffer.time_span_in_milliseconds = 100
print("Sending values for 30 seconds.")
for index in range(0, 3000):
stream.timeseries \
.buffer \
.add_timestamp(datetime.datetime.utcnow()) \
.add_value("ParameterA", math.sin(index / 200.0) + math.sin(index) / 5.0) \
.publish()
time.sleep(0.01)
print("Closing stream")
stream.close()
消费时间序列数据
这是一个如何使用 Python 从 Kafka 主题消费时间序列数据的示例:
import quixstreams as qx
import pandas as pd
# Connect to your kafka client
client = qx.KafkaStreamingClient('127.0.0.1:9092')
# get the topic consumer for a specific consumer group
topic_consumer = client.get_topic_consumer(topic_id_or_name = "mytesttopic",
consumer_group = "empty-destination")
def on_dataframe_received_handler(stream_consumer: qx.StreamConsumer, df: pd.DataFrame):
# do something with the data here
print(df)
def on_stream_received_handler(stream_consumer: qx.StreamConsumer):
# subscribe to new DataFrames being received
# if you aren't familiar with DataFrames there are other callbacks available
# refer to the docs here: https://docs.quix.io/sdk/subscribe.html
stream_consumer.timeseries.on_dataframe_received = on_dataframe_received_handler
# subscribe to new streams being received
topic_consumer.on_stream_received = on_stream_received_handler
print("Listening to streams. Press CTRL-C to exit.")
# Handle termination signals and provide a graceful exit
qx.App.run()
有关如何消费和生产时间序列和事件数据的完整文档,请查看文档。
下一步
这是 Quix Streams 的首次迭代,下一版本已经在开发中。
主要亮点是一个名为“流数据帧”的新特性,它简化了来自批处理环境的用户的有状态流处理。它消除了用户在内存中管理状态、更新滚动窗口、处理检查点和状态持久化以及在服务意外重启后管理状态恢复的需要。
通过引入熟悉的 Pandas DataFrames 接口,我和我的同事希望让流处理对新接触流数据的数据专业人士更具可及性。
以下示例展示了如何在流数据帧上执行滚动窗口计算:
请注意,这正是您在 Jupyter notebook 上对静态数据执行相同计算的方式——因此,对于习惯于批处理的人来说,学习起来会很容易。
你无需为流数据的有状态处理的复杂性而苦恼——这一切都会由库来管理。此外,虽然它仍然会像 Pandas 一样,但底层将使用二进制表——这相比传统的 Pandas DataFrames 带来了显著的性能提升。
要了解下一个版本何时准备好,请确保关注Quix Streams GitHub 仓库。
路线图也应由更广泛的数据社区的反馈和贡献来塑造:
-
如果你发现了 bug 或想要请求改进,请随时报告 GitHub 问题。
-
如果你有问题、需要帮助,或只是想了解更多关于这个库的信息,可以尝试在 Slack 社区“The Stream”(我帮忙进行管理)中发布消息,或查看文档。
-
如果你想改进这个库,请查看贡献指南。
介绍 Crystal Bar Chart:可视化序列差分聚类
了解 Crystal Bar Charts 并使用 Python 创建你自己的图表
·发表于 Towards Data Science ·阅读时间 12 分钟·2023 年 12 月 29 日
–
Crystal Bar Chart 作者Nick Gerend
介绍
有很多方法可以更好地理解一系列数据。无论是温度、考试成绩、食品价格、步数还是 UFO 目击事件,总能通过数据分析和可视化的视角学到关于这些事物的本质。
在接下来的内容中,我将描述一种新的数据可视化技术,它使用简单的基于阈值的聚类。我希望你会发现这是一种与传统可视化方法(如直方图和箱线图)相配的全新方法,从而对感兴趣的特征获得新的视角。
序列差分聚类
数值上
怎样才能脱颖而出?以单一数值属性为例,我们假设简单的阈值是将有序值进行比较并分组的标准。以以下序列为例:
[ 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , 55 , 89 , 144 ]
这当然是斐波那契数列的前 13 个数字。如果我们从第 3 个位置开始,计算每个值与前一个值之间的差,我们会得到相同的序列,只是向前移动了 2 步:
[ _ , _ , 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , 55 ]
现在,让我们设定一个阈值为 5,并假设在从左到右遍历序列时,任何大于 5 的数值都标志着一个新簇的开始。这将导致前 8 项(值为 0-13)被分到一起,而每个后续项由于所有剩余的差值都超过 5,因而每项都是其所在组的唯一项。
对于斐波那契数列,这种方法会产生可预测的结果,但在探索性数据分析中,可能会出现各种模式。我将描述如何使用一种新的数据可视化方法探索这些模式,首先从下面几个熟悉的图表类型开始比较。
使用直方图
让我们尝试将聚类策略应用于直方图,通过确定基于相同阈值的箱子数量并审查分离:
# pandas histogram
import pandas as pd
import numpy as np
data = {'value' : [0,1,1,2,3,5,8,13,21,34,55,89,144]}
df = pd.DataFrame(data=data)
data_range = df['value'].max() - df['value'].min()
num_bins = np.ceil(data_range/5).astype(int)
print(num_bins) # 29
df['value'].hist(bins=num_bins, color='w', edgecolor='black',
linewidth=1.2, grid=False, figsize=(7,1.5))
以 29 个箱子的形式呈现的斐波那契数列(0 到 144)的直方图,使用 Matplotlib 渲染
使用蜜蜂散点图
现在,让我们用蜜蜂散点图可视化每个单独的值,每个圆的直径等于阈值,以寻找间隙:
# vizmath (modified) beeswarm chart
from vizmath.beeswarm import swarm
import pandas as pd
from math import pi
data = {
'id' : [str(i) for i in range(1, 14)],
'value' : [0,1,1,2,3,5,8,13,21,34,55,89,144]
}
df = pd.DataFrame(data=data)
bs = swarm(df, 'id', 'value', None, size_override=pi*(5/2)**2)
bs.beeswarm_plot(color=False)
斐波那契数列(0 到 144)的蜜蜂散点图,直径为 5,使用 Matplotlib 渲染
水晶条形图
布局
为了介绍水晶条形图,让我们继续之前的例子,使用交替的灰色阴影来说明不同的集群(稍后我们将详细介绍):
# vizmath crystal bar chart
import pandas as pd
from vizmath.crystal_bar_chart import crystals
data = {
'id' : [str(i) for i in range(1, 14)],
'value' : [0,1,1,2,3,5,8,13,21,34,55,89,144]
}
df = pd.DataFrame(data=data)
cbc = crystals(df, 'id', 'value', 5, width_override=5, rotation=90)
cbc.cbc_plot(legend=False, alternate_color=True, color=False)
斐波那契数列(0 到 144)的水晶条形图,使用 Matplotlib 渲染
为了有趣,我们添加一个任意大小属性:
# vizmath crystal bar chart with added width property
import pandas as pd
from vizmath.crystal_bar_chart import crystals
data = {
'id' : [str(i) for i in range(1, 14)],
'value' : [0,1,1,2,3,5,8,13,21,34,55,89,144],
'size' : [5,13,8,7,6,8,13,5,11,4,9,12,6] # new size property
}
df = pd.DataFrame(data=data)
cbc = crystals(df, 'id', 'value', 5, width_field='size', rotation=90)
cbc.cbc_plot(legend=False, alternate_color=True, color=False)
斐波那契数列(0 到 144)的水晶条形图,带有任意属性‘大小’,使用 Matplotlib 渲染
现在让我们移动值,以观察图表如何适应新的原点:
# vizmath crystal bar chart with adjusted origin
import pandas as pd
from vizmath.crystal_bar_chart import crystals
data = {
'id' : [str(i) for i in range(1, 14)],
'value' : [0,1,1,2,3,5,8,13,21,34,55,89,144]
}
df = pd.DataFrame(data=data)
cbc = crystals(df, 'id', 'value', 5, width_override=5,
rotation=90, offset=21) # new offset
cbc.cbc_plot(legend=False, alternate_color=True, color=False)
斐波那契数列(0 到 144)的水晶条形图,调整偏移 = 21,使用 Matplotlib 渲染
灵感
在 2020 年 10 月,我和我的妻子在查看一个数据可视化挑战时,数据提供的是一个包含一些重复项的单一特征。
我最初的想法是制作一些看起来有三维效果的、类似水晶的东西,最终得到的是一种简单的水晶条形图版本。
早期的水晶条形图,由 Nick Gerend 于 2020 年 10 月制作,使用 Tableau 渲染
在 2022 年,我再次拿起了这个想法,并使用基于阈值的聚类(顺序差分聚类策略)对一组值进行分层,给定阈值,并使每个后续水晶堆叠在其偏移(移除一次)邻居旁边的中心轴周围形成一个集群的水晶(代表一个集群中的值)。
摩天大楼¹似乎是一个很好的数据集开始,并成为我对新算法的第一次测试:
“天际线”由 Nick Gerend(2022 年 4 月 10 日)
接下来,我将描述水晶条形图的关键特性。
水晶条形图算法
绘制 Crystal Bar Chart 是将晶体顶部面的接触点与原点对齐,以便侧面正确绘制,勾勒出每个值对应的完整晶体:
Crystal Bar Chart 由 Nick Gerend 构建
-
从一组值开始,并根据偏好(升序或降序)对其进行排序,例如:0.2, 1.5, 7.4, 9.4
-
使用期望的偏移量(例如 -1.7)调整值,以调整原点的位置
-
设置一个阈值(例如 3.5),并根据之前描述的顺序差异聚类对数据进行分组
-
遍历每个组(外层循环)及组内的每个值(内层循环),将初始值的晶体沿中心轴定向,并根据项目的范围更新下一个晶体的起始位置,该范围垂直于中心轴
-
对于每个后续值,交替左右放置晶体,以类似方式考虑晶体顶部面占据的范围,以调整下一个交替晶体的位置(不同的排序和垂直轴上的放置方法正在审查以备未来更新)
-
计算晶体每个面的点(左、右、顶部):
顶部面尺寸:由顺序差异聚类阈值(沿中心轴的高度)和根据大小属性的范围(垂直于中心轴的宽度)勾勒
左右面尺寸:根据视图中的侧面多边形绘制,确定为晶体顶部面和原点之间接触点的斜率(默认为 0,0)
-
当遇到新集群时,重置起始位置,并对集群中每个晶体重复面位置计算
-
如果需要,可以反转偏移量以将值重新映射到原始范围
Python 实现
我已经通过我的 vizmath 包在 PyPI 上提供了 Crystal Bar Chart 算法的 Python 实现。让我们使用上面的初始示例详细分析几个选项,并解释输入和输出:
import pandas as pd
from vizmath.crystal_bar_chart import crystals # pip install vizmath==0.0.14
# using the example data from above:
data = {
'id' : [str(i) for i in range(1, 14)],
'value' : [0,1,1,2,3,5,8,13,21,34,55,89,144],
'size' : [5,13,8,7,6,8,13,5,11,4,9,12,6]
}
df = pd.DataFrame(data=data)
# create a crystals object
# > df: DataFrame with 1 numerical column of data and an
# optional size column 'width_field'
# > id_field: required identifier (can be dummy values)
# > height_field: required value column
# > height_range: sequential differential clustering threshold
# > width_field = optional size column
# > bottom_up: False = descending, True = ascending
# > width_override: value constant to set the size value
# (overrides the width_field values)
# > offset: value to adjust the origin by
# > reset_origin: False = keeps offset, True: resets origin with offset
# > rotation: overall rotation around the center in degrees
cbc = crystals(df, 'id', 'value', 5, width_field='size', bottom_up = True,
width_override = None, offset=21, reset_origin=True, rotation=90)
#plot the Crystal Bar Chart
cbc.cbc_plot(legend=False, alternate_color=True, color=False)
Crystal Bar Chart 以反向顺序和偏移量及重置原点,使用 Matplotlib 渲染
以下是 Crystal Bar Chart 算法的输出:
-
id - 项目标识符
-
group - 由顺序差异聚类生成的项目集群:1 到 N
-
side - 晶体面的标识符:{0,1,2}
-
value - 项目的值(沿中心轴的晶体顶部的质心位置)
-
height - 顺序差异聚类阈值(晶体顶部面的高度,平行于中心轴)
-
width - 次要值 ≥ 0(晶体顶部面的宽度,垂直于中心轴)
-
x, y - 布局中点的笛卡尔二维坐标
-
path - 描述一个有序整数集的路径,该路径围绕一个多边形,同时与水晶条形图中的每个(x, y)点对应,对于每个水晶 id 和面:1 到 N。
# Cyrtsal Bar Chart DataFrame
cbc.o_crystal_bar_chart.df[['id', 'group', 'side',
'value', 'height', 'width', 'x', 'y', 'path']].head()
水晶条形图数据框
在未来的版本中,我将尝试结合不同的选项,将水晶沿垂直于中央轴的轴线放置,以便进行集群和价值比较。
Tableau Public 实现
在本节中,我将介绍如何在 Tableau Public (v 2023.3.0)中实现我的水晶条形图可视化以及一些有趣的交互功能。
为了配合水晶主题,我们从维基百科上获取一个关于钻石的数据集²。数据包含钻石的名称、未切割和已切割的重量、来源等信息。为了示例 purposes,我将数据限制为大于 200 克拉且未切割和已切割值只有一个切割记录的钻石。
import pandas as pd
# https://en.wikipedia.org/wiki/List_of_diamonds (as of 12/25/2023)
# filtered to enries with uncut and cut values with only 1 cut, >200 carats
diamonds = {
'Name' : [
'4 February Stone', 'Centenary Diamond', 'Cross of Asia',
'DeBeers Diamond', 'Earth Star Diamond', 'Golden Jubilee Diamond',
'Graff Lesedi La Rona', 'Great Mogul Diamond', 'Gruosi Diamond',
'Incomparable Diamond', 'Jubilee Diamond', 'Koh-i-Noor',
'Lesotho Brown', 'Lesotho Promise', 'Millennium Star',
'Premier Rose Diamond', 'Regent Diamond', 'Taylor-Burton Diamond',
'Tiffany Yellow Diamond'],
'Uncut' : [
404.2, 599, 280, 440, 248.9, 755.5, 1111, 780, 300.12,
890, 650.8, 793, 601, 603, 777, 353.9, 410, 241, 280],
'Cut' : [
163.41, 273.85, 79.12, 234.5, 111.59, 545.67, 302.37, 280, 115.34,
407.48, 245.3, 105.6, 71.73, 75, 203.04, 137, 140.64, 68, 128.54],
'Color' : [
'white', 'colorless', 'yellow', '-', 'brown', 'yellow-brown',
'colourless', '-', 'black', 'brownish-yellow', 'colorless',
'colorless', 'pale brown', 'colorless', 'colorless',
'colorless', 'white with pale blue', 'colorless', 'yellow'],
'Origin' : [
'Angola', 'South Africa', 'South Africa', 'South Africa',
'South Africa', 'South Africa', 'Botswana', 'India', 'India',
'Democratic Republic of Congo', 'South Africa', 'India', 'Lesotho',
'Lesotho', 'Democratic Republic of Congo', 'South Africa', 'India',
'South Africa', 'South Africa']
}
df = pd.DataFrame(data=diamonds)
接下来,我们将使用 vizmath 创建一个水晶条形图,并将绘图信息和原始数据输出到 csv 文件中:
from vizmath.crystal_bar_chart import crystals
cbc = crystals(df, 'Name', 'Uncut', 100, width_field='Cut') # calculate
cbc.o_crystal_bar_chart.dataframe_rescale(0, 5000, -2500, 2500) #rescale
cbc.to_csv('crystal_bar_chart') # crystal bar chart output
cbc.df.to_csv('data.csv') # original data
使用Text file选项将crystal_bar_char.csv文件导入 Tableau。然后使用Connections旁边的Add链接将data.csv添加到Files列表中。在右侧的阶段,双击crystal_bar_char.csv标签并将data.csv文件从Files拖到该阶段。选择Inner Join,在Data Source下拉菜单中选择***[Id]字段,data.csv下的[Name]***字段。
数据集准备好后,导航到Sheet 1,并创建我们将用于绘制图表的计算列:
[chart]: MAKEPOINT([Y],[X])
[chart_top]: 如果[Side] = 0,则 MAKEPOINT([Y],[X]),否则为 null end。
首先,将***[chart]拖到Marks下的Detail中以生成第一个地图层,并通过右击地图区域并选择Background Layer***s 调整这些选项。
-
取消所有Background Map Layers(Base、Land Cover 等)。
-
现在右击地图区域,选择Map Options并取消所有选项。
关闭Background Layers并继续以下步骤:
-
将***[Group]、[Id]和[Side]拖到Marks下的Detail***中。
-
将现在的SUM(Group)和SUM(Side)右击每一个并选择相应选项转换为Dimension和Discrete。
-
右击***[Group],选择Sort***,并选择Descending。
-
在Marks下拉菜单中选择Polygon(此时如果看起来有些奇怪也不用担心)。
-
将***[Path]拖到Marks下的Path***,右击现在的SUM(Path)并选择Dimension。
-
将***[Group]拖到Color***,并重复该过程,将其转换为Dimension和Discrete。
-
在Color下选择“Edit Colors…”,并配置为交替的灰度方案,0 和 2 组使用浅色,1 和 3 组使用深色。
-
点击确定,将不透明度调整为 95%,并在Color下选择黑色边框。
-
将***[chart_top]***拖入地图区域,弹出窗口将出现:Add a Marks Layer - 将该药丸拖入其中以创建新的地图层。
-
重复上述步骤,只不过现在将***[Origin]用于Color***,并根据需要调整颜色。
-
在Color下,选择黑色边框并将不透明度设置为 70%,然后右击图表右下角的空值药丸,选择Hide Indicator以隐藏空值标签。
现在图表部分已就位,应类似于以下内容:
让我们添加另一个图表以用于比较和交互。
-
使用底部面板上的第一个加号创建一个新的工作表,命名为Sheet 2。
-
将***[Group]拖到Columns并转换为Dimension和Discrete***。
-
将***[Name]拖到Columns***,将***[Uncut]拖到Rows***。
-
右键点击***[Uncut],在Measure下选择Maximum***。
-
将***[Origin]拖到Color***,将***[Cut]拖到Size***。
-
右键点击***[Cut],在Measure下选择Maximum***。
-
在顶部的Format菜单下,选择Shading…,并在Column Banding > Header下选择深灰色,Level勾选设置为第一个刻度,其他选项设置为None。
-
右键点击顶部的Group / Name,选择Hide Field Labels for Columns*,并根据需要调整图例名称和颜色。
Sheet 2准备好了,选择顶部菜单中的Entire View后应类似于以下内容:
最后,将两个工作表整合到仪表板中。创建仪表板并添加工作表后,在Dashboard顶部菜单中的Actions下设置一个操作。点击Add Action下拉菜单,选择Highlight。在Source Sheets中选择Sheet 2,在Target Sheets中选择Sheet 1。在Targeted Highlighting中选择Selected Fields,并选择***[Group]和[Name]字段。最后,在右侧的Run action on菜单中选择Hover选项,这样整个仪表板将在Sheet 2***上的每个条形和组上悬停时突出显示!
调整颜色并将所有内容以有序的方式排列后,以下是我们在 Tableau Public 中的新仪表板:
结论
在本文中,我概述了一种我称之为晶体条形图的新型数据可视化工具。这个工具对于将信息压缩到一个小空间非常有用,通过沿中央轴重叠的形状来表示一维数据,这些数据按顺序差异聚类分组,还可以选择沿垂直轴表示第二个数值属性。
晶体条形图的节省空间功能是对条形图和类似可视化工具的有效替代,它不需要较大的占用空间,并且与各种其他工具配合良好,用于在学术和专业工作中检查数据系列。
通过多种选项调整晶体状数据表示的特征,我希望你能发现这种可视化技术适用于不同的数据探索任务,并且是一种有趣的新方式来发现洞察!
参考文献
[1] 维基百科(CC BY-SA),“最高建筑物列表”(截至 2022 年 4 月 10 日)
[2] 维基百科(CC BY-SA),“钻石列表”(截至 2023 年 12 月 25 日)
相关文章
介绍多弦图:可视化复杂集合关系
了解多弦图,并使用 Python 创建你自己的图表
·发布于Towards Data Science ·阅读时间 13 分钟·2023 年 11 月 17 日
–
Nick Gerend 的多弦图
背景
前身
在数据可视化的领域中,表示复杂集合关系的发展经历了重要的里程碑,尤其是简单而有效的韦恩图、现代弦图和 UpSet 图的创建。
韦恩图
韦恩图由约翰·韦恩于 1880 年代提出¹,是集合论和逻辑学中的基础工具,以其简单性和有效性而著称,能够直观地表示不同集合之间的关系。韦恩图由重叠的圆圈组成,每个圆圈通常代表一个集合,圆圈之间的重叠部分表示这些集合共有的元素。
韦恩图
它们在教育、商业分析和逻辑推理中特别有用,因为它们提供了一种清晰直观的方式来展示集合的交集、差异和并集。它们将复杂关系简化为易于理解的视觉效果,使其成为解决问题、数据分析和决策过程中的宝贵工具。
弦图
现代弦图在近期历史上尤为引人注目,尤其是在 2007 年《纽约时报》刊登的一篇文章中提到马丁·克日文斯基²(“Circos”³可视化工具的主要贡献者)的工作。如今,弦图的特点是其圆形布局,带有弯曲的多边形弦连接圆周上的集合,每个弦展示两个集合之间或一个集合内的独立人群之间的关系。
弦图
这些图表特别有效地揭示了数据集中的隐藏模式和连接。关系的强度通常通过弦的厚度来表示,其他元素(如颜色和形状)可以用于显示方向性,使得弦图不仅视觉上引人注目,而且信息量极大。
UpSet Plot
UpSet Plot 由 Lex、Gehlenborg 等人于 2014 年介绍,成为可视化复杂集合交集的解决方案,克服了弦图和维恩图的一些固有缺点,能够可视化两个以上交集的集合关系。
UpSet Plot
它结合了矩阵的简洁性和条形图的定量表示,直接展示复杂的集合信息,这些信息可能超出了弦图仅限于成对集合关系的范围。
改进空间
虽然这三种方法各自以独特的方式提供信息,但每种方法都有一些明显的缺点:
-
维恩图由于多个重叠区域变得混乱,缺乏有效表示复杂关系的要素。
-
弦图局限于成对的集合关系,这大大限制了它们的应用。
-
UpSet Plot 的矩阵布局在集合组合增加时不容易扩展,因此可能难以立即洞察集合复杂性的某些方面或“全貌”。
多弦图
灵感
为了解决维恩图和弦图上述提到的挑战,我在 2021 年 6 月提出了一种算法,将弦图推广到适应三个或更多集合交互,并称之为“多弦图”(或简称 multichord)。作为开发的副产品,我还独立提出了 UpSet Plot,以测试这种新方法,后来才知道它已经是一个成熟的图表!
维恩图 > 弦图 > UpSet Plot > 多弦图
相对于其三种前身,这种新可视化提供了以下功能:
-
提供了一种准确而令人愉悦的视觉布局,相对于缺乏精确度的维恩图来说,适用于复杂的集合关系。
-
消除了弦图的成对限制,同时保持了在方向性、间距等方面的创造潜力。
-
通过提供对网络复杂性的即时视觉洞察,补充了 UpSet Plot 的离散信息,同时不会在矩阵和条形图编码中迷失(正如我之前提到的,它们也可以很好地配合使用!)
多弦图不仅拓宽了弦图的应用范围,还提供了对复杂网络的更细致理解,这是当今数据驱动世界中的重要需求。
数学、算法与布局
从数学原理开始,以下是多弦图构建中的基本项:
- 笛卡尔极坐标转换(CPC):首先在笛卡尔空间中计算位置,为了简化
# Input coordinates are in Cartesian, but represent Polar ingredients
# > 'r' is the Cartesian height offset in this case
# > the angle is calculated as a percentage along
# the straight line in Cartesian space relative to 2*pi
x = r * cos(angle)
y = r * sin(angle)
# For converting points, I created this helper function:
from math import cos, sin, pi
def polarize(x, max_x, y, y_offset = 0.):
angle = (2.*pi)*(((x)%(max_x))/(max_x))
angle_deg = angle * 180./pi
angle_rotated = (abs(angle_deg-360.)+90.) % 360\.
angle_new = angle_rotated * pi/180.
y += y_offset
x_out = (y)*cos(angle_new)
y_out = (y)*sin(angle_new)
return x_out, y_out
-
弦函数(CF):生成沿圆周两点之间路径的一组点,一些常见的实现包括:
-
直线(原始的!)
-
从圆圈中产生的弧,其中点是接触弦
-
Bezier 曲线(具有 3 个或更多点)
-
自定义函数(我为初始 Python 实现创建了一个)
-
# Here's a custom chord function I created for use in the Multi-Chord Diagram
# chord(x0, y0, x1, y1, x2, y2, points, h_override=0.)
# > x0, y0: the circle's center
# > x1, y1, x2, y2: two points on a circle
# > points: number of desired points along the chord path
# > h_override: height constraint on the chord's apex
from math import cos, sin, pi, sqrt, atan2
def LnToPntDst(x0, y0, x1, y1, x2, y2):
n = abs((y1-y2)*x0+(x2-x1)*y0+x1*y2-x2*y1)
d = sqrt((x2-x1)**2+(y2-y1)**2)
return n/d
def DistBtwTwoPnts(x1, y1, x2, y2):
return sqrt((x2-x1)**2+(y2-y1)**2)
def Rotate(x, y, angledeg, x_offset, y_offset):
xa = x*cos(angledeg*pi/180) + y*sin(angledeg*pi/180)
ya = -x*sin(angledeg*pi/180) + y*cos(angledeg*pi/180)
xa -= x_offset
ya -= y_offset
return xa, ya
def AngleByTwoPnts(x1, y1, x2, y2):
return atan2(x2-x1, y2-y1)*180/pi - 90
def chord(x0, y0, x1, y1, x2, y2, points, h_override=0.):
h = LnToPntDst(x0, y0, x1, y1, x2, y2)
w = DistBtwTwoPnts(x1, y1, x2, y2)
if h_override == 0.:
new_h = (1.-(h/w)/10.)*h
if new_h < h*0.01:
h = h*0.01
else:
h = new_h
else:
h = h*h_override
a = AngleByTwoPnts(x1, y1, x2, y2)
xr = []
yr = []
for i in range(points+1):
arc_percent = i/(points/2.)
if i > points/2.:
arc_percent = (points-i)/(points/2.)
if i == 0 or i == points:
arc = 0.
else:
arc = sqrt((h/2.)**2-((h/2.)-(h/2.)/((points)/2.)*i)**2.)
percent = arc/(h/2.)
y_1 = -percent*arc+(1-percent)*arc_percent
y_2 = percent*arc+(1-percent)*arc_percent
xr_1, yr_1 = Rotate(i/points*w, y_1, a, -x1, -y1)
xr_2, yr_2 = Rotate(i/points*w, y_2, a, -x1, -y1)
d1 = DistBtwTwoPnts(x0, y0, xr_1, yr_1)
d2 = DistBtwTwoPnts(x0, y0, xr_2, yr_2)
if d1 < d2:
xr.append(xr_1)
yr.append(yr_1)
else:
xr.append(xr_2)
yr.append(yr_2)
return list(zip(xr, yr))
- 重缩放函数(RF):为了方便将图形缩放到单位正方形
# 'x_o' is the original value
# 'x_min' is the current minimum of the values range
# 'x_max' is the current maximum of the values range
# 'x_r' is the rescaled value based on a new range, 'new_min' and 'new_max'
x_r = (new_max - new_min) * ((x_o - x_min)/(x_max - x_min)) + new_min
这是多弦图算法:
-
从集合组合及其大小(或从原始数据中计算)开始,例如:({a}, 25.5),({a, b}, 15),({a, c}, 14.4) 等
-
定义布局顺序和缓冲偏移量,用于间隔集合和嵌套集合组合,并提供可选的整体径向百分比填充
-
布局一个数字线,表示数据范围,并按照指定的顺序排列每个集合组合,用给定的偏移量分隔
-
向前和向后传播数字线的范围
-
通过从 3 条可用数字线中将最接近的弦端点分组来收集弦端点
-
定义用于绘图的点数,并继续计算每个部分中的点位置,利用(CPC)将直线部分和弦端点映射到圆形布局上,并利用(CF)为圆内的弦点
-
使用(CPC)添加外部集合组合和集合多边形,并使用(RF)将其重缩放到单位圆
这是一个使用我称之为多弧图的另一种可视化的笛卡尔布局:
多弧图展示了多弦图的笛卡尔布局
最简化的多弦图布局版本由以下组件构成:
-
表示独立集合人口或连接 2 个或更多集合的弦,根据存在的集合到集合关系组合的数量
-
一个中环,由集合内唯一集合组合分割成多个部分,表示“专属”集合组合大小
-
一个外环,被集合分割成多个部分,每个部分包含“包含”集合组合(表示集合大小,减去来自中间环的缓冲间距)
多弦图
Python 实现
我已通过我的 dataoutsider 包在 PyPI 上提供了该算法的初步 Python 实现。以下是使用示例:
from dataoutsider import multi_chord as mc
import pandas as pd
data = [['a', 56.5], ['a,b', 15], ['a,c', 14.4],
['a,b,d', 8.6], ['c,d', 13], ['d', 30.9],
['c,b', 10], ['b', 24.3], ['a,b,c,d', 17.2],
['b,e',5.6], ['c,d,e',17.8]]
df = pd.DataFrame(data, columns = ['group', 'value'])
df_mc = mc.multi_chord_on_groups_alias(df, percent=75)
mc.multi_chord_plot(df_mc, level = 3, transparency = 0.5)
通过 Matplotlib 的 dataoutsider 包中的多弦图
这是基础输出(df_mc):
来自 multi_chord_on_groups_alias 函数的输出
在下一节中,我将演示如何使用此输出创建一个互动可视化,以适应商业智能领域的专业人士。
Tableau Public 实现(包括 UpSet 图)
在本节中,我将提供一个在 Tableau Public (v 2023.3.0) 中实现我的多弦图的教程,并与不同组件进行交互。
首先导出多弦图数据(在上一节中创建),包括用于构建 UpSet 图的数据。我将 UpSet 图的数据方法称为multi_chord_venn,以致敬维恩图,并且因为当时我不知道 UpSet 图已经存在。
import os
df_mc.to_csv(os.path.dirname(__file__) +
'/multichord_diagram.csv', encoding='utf-8', index=False)
df_upset = mc.multi_chord_venn(df_mc).drop_duplicates()
df_upset.to_csv(os.path.dirname(__file__) +
'/upset_plot.csv', encoding='utf-8', index=False)
首先从multichord_diagram.csv开始。使用文本文件选项将文件导入 Tableau,导航到Sheet 1,并创建这些计算列:
[mc_map]:MAKEPOINT([Y], [X])
[mid_path]:int({fixed [Group]: avg(if [Type] = ‘element’ then [Path] else null end)} * 3/2)
[mc_label]:if [Type] = ‘element’ and [Path] = [mid_path] then MAKEPOINT([Y], [X]) else null end
首先将***[mc_map]拖到标记下的详细信息中,以生成第一个地图图层,并通过右键点击地图区域并选择背景图层***来调整这些选项:
-
取消选择所有背景地图图层(基础图层、土地覆盖等)
-
现在右键点击地图区域并选择地图选项,然后在面板中取消所有选项
关闭背景图层并继续以下步骤:
-
将***[Group]、[Type]和[Value]拖到标记下的详细信息***中
-
将***[Count]拖到标记下的详细信息***中
-
右键点击现在的SUM(Value) 并选择维度,再次右键点击并选择离散
-
对***SUM(Count)***重复该过程
-
再次右键点击Value并选择排序,然后选择降序,并将Value拖到标记中的药丸顶部
-
在标记下拉菜单中选择多边形(如果此时看起来很奇怪,不用担心)
-
将***[Path]拖到标记下的路径中,并重复将其转换为维度***的过程
-
在颜色下选择黑色边框颜色,将透明度调整为 80%,并选择编辑颜色以根据需要编辑颜色选项
现在应该可以看到多弦图的结构。让我们添加一些标签:
-
将***[mc_label]***拖入地图区域,会出现一个弹出窗口:添加标记图层 - 将药丸拖入此处以创建一个新的地图图层
-
将[Group]拖到此新地图图层中的标签下
-
在标记下拉菜单中选择圆形,点击标签,并选择这些选项:{对齐:水平居中,垂直居中}
-
在菜单仍然打开的情况下,点击文本 - 高亮文本框中的文本,将字体大小更改为 12,并点击确定
-
点击Color,选择白色并将透明度更改为 80%
-
最后,点击Size并将大小调整到第二个哈希
你会看到右下角有一个空值警告,你可以右键点击并选择Hide Indicator。此时你应该看到类似于这样的内容(包括你选择的颜色):
你会注意到集合按其大小顺时针递减排序,而弦按顺时针递增排序(算法的默认设置)。绘图顺序可以根据需要调整,就像我们之前对Value所做的那样。
现在让我们创建 UpSet 图。首先导入upset_plot.csv文件,选择Data选项卡并点击New Data Source。选择文本文件并导入数据。创建一个新工作表,点击底部面板上的第一个加号,并确保在右上角的Data下选择了新数据源。
现在添加这些计算列:
[count]:{fixed [Group]: sum(if [Group2] = ‘count’ then [Value2] else null end)}
[chord_magnitude]:if not isnull([count]) then [Value] else null end
[set_magnitude]:{fixed [Group2]: max(if [Group] = [Group2] then [Value] else null end)}
接着将***[Group2]拖到Filter中,仅选择集合(a, b, c, d, e)。将[Value2]添加到过滤器中,并仅过滤值为 1。最后,将[count]添加到过滤器中,并在右下角取消勾选Include Nulls Values***
继续按照这些步骤设置矩阵视图:
-
将***[chord_magnitude]拖动到Columns***,在其下拉菜单中选择Minimum作为其Measure,并选择Discrete
-
在这个标签旁双击以添加一个新标签并输入:‘|’
-
按 Enter 键将文本提交为新标签,并将***[Group]拖动到其旁边,然后是[count]***
-
将***[Group2]拖动到Rows***,从下拉菜单中选择Sort,并按以下条件排序:{Nested, Descending, set_magnitude, Maximum}
-
为***[chord_magnitude]***添加排序:{Nested, Descending, chord_magnitude, Minimum}
-
将***[Value2]拖动到Rows的最后位置,并将Measure设置为Maximum***
-
在Marks下拉菜单中,选择Circle
请注意,我发现当前版本在双轴模式下有一些错误,因此排序可能需要根据需要进行调整。现在我们进行一些格式化:
-
右键点击底部轴,选择Edit Axis,并将设置更改为:{Custom, Fixed start: 1, Fixed end: 1}
-
关闭,再次右键点击并取消勾选Show Header
-
在视图顶部,右键点击所有离散的Columns标题(视图标题中的任何位置),除了***[count],选择Rotate Label***(你也可以调整每个标题容器的大小,以更好地适应标签)
-
通过右键点击外部标题并选择Hide Field Labels for Columns/Rows来隐藏外部标题
现在我们将创建一个双轴图,以便在点之间绘制一些线条:
-
将***[Value2](再次)拖到Columns中的最后一个位置,并设置为Maximum***,如之前所述
-
从下拉菜单中选择Dual Axis,右键点击新轴,选择Synchronize Axis,然后通过取消选中每个轴上的Show Header来隐藏这两个轴
-
在新视图中,从Marks下拉菜单中选择Line,双击Marks内以创建一个新的药丸,并输入:1
-
按回车键,将这个药丸拖到Path
这是一个完成的视图,顶部的视图下拉选项设置为Entire View:
现在让我们为弦(特定于种群)和集合(包括种群)创建类似的视图。
这是弦图视图:
这是集合视图:
现在将它们添加到仪表板中,并在仪表板顶部菜单中的Actions下设置一个操作。点击Add Action下拉菜单并选择Highlight。在Targeted Highlighting下选择Selected Fields,并选择***[Group]和[Group2]字段。最后,在右侧的Run action on菜单下选择Hover***选项,现在整个仪表板将在悬停于集合和弦上时高亮显示!
多弦图和 Tableau Public 中的 UpSet 图交互
结论
在本文中,我简要介绍了应用于集合之间关系的可视化历史以及我称之为“多弦图”的内容,这是一种我开发的可视化工具,用于增强一些现有方法,以便快速洞察具有复杂集合关系的数据。近年来,我有很多机会利用这一工具进行个人项目和各种业务应用,希望它能为其他人提供一些新的功能!
我相信数据可视化可以帮助解决探索性数据分析、建模和讲故事中的挑战,并且这是艺术与科学的真正交汇点,可以让所有人享受。
如果你遇到任何有趣或专业的用例,请告诉我,谢谢阅读!
参考文献
[1] John Venn, “关于命题和推理的图示和机械表示” (1880), 哲学杂志与科学期刊
[2] David Constantine, “基因组的特写:物种逐个分析” (2007), 纽约时报
[3] Martin Krzywinski, 等,“Circos:比较基因组学的信息美学” (2009), 基因组研究
[4] Lex A, Gehlenborg N, Strobelt H, Vuillemot R, Pfister H. “UpSet: 交集集的可视化” (2014), IEEE 计算机图形学与可视化汇刊
四种项目相似性度量的介绍
原文:
towardsdatascience.com/introduction-of-four-types-of-item-similarity-measures-e0aea70da335
数据挖掘
介绍如何选择项目嵌入可用时的相似性度量
·发布于 Towards Data Science ·阅读时间 5 分钟·2023 年 2 月 17 日
–
图片由 James Yarema 提供,来源于 Unsplash
推荐算法在我们日常生活中无处不在,从我们 Youtube 首页上出现的视频顺序,到沃尔玛商品货架的排列。这些算法学习用户的潜在偏好,并向用户推荐最相关的项目。为实现这一点,我们需要用数值向量来描述用户和项目,以便算法能够测量用户相似性和项目相似性,从而进行进一步的推荐。
学习用户和项目的数值向量表示有两种常见的方法:基于内容的过滤和协同过滤。在基于内容的过滤中,通过根据项目的专家知识手动构建特征来学习项目的向量表示。以移动应用为例,每个在 Google Play 上的应用都有一个应用名称、一个类别和开发者提供的一些文本描述。然后可以应用专家知识从这些信息中提取特征,以构建应用的向量表示。基于内容的过滤的优势在于它不依赖于现有的用户-项目数据。相反,协同过滤不需要对项目的专家知识,完全依赖现有的用户-项目数据。当涉及到协同过滤时,数据本身会说话。只要我们拥有足够的用户-项目数据,就可以通过矩阵分解来提取用户和项目的表示。
在获得项目的数值向量表示后,下一任务是测量项目之间的相似度以进行推荐。在这篇文章中,我们将关注各种相似度度量,并讨论如何选择合适的相似度度量。同时,将提供 Spark 代码示例,因推荐算法通常应用于大数据。
言归正传,让我们开始吧!
1. 示例数据介绍
在这篇文章中,我们将通过分析大量用户的已安装应用列表来研究应用相似度。假设我们有 m 个用户,总共安装了 n 个应用,那么我们可以构建一个 m×n 的用户-项目矩阵。与协同过滤方法不同,协同过滤方法将学习用户和项目的低维表示,以进行实时推荐任务的快速计算,但在这个示例中,我们将直接使用每个应用的 m 维表示。协同过滤学习用户和项目的低维表示,以进行实时推荐任务的快速计算,但这里我们专注于演示如何在获得数值向量表示后计算相似度。因此,我们简化了获取项目向量的过程。
此外,为了演示目的,我们将仅使用一个包含 4 个用户和 4 个应用的虚拟示例,该示例可以通过以下代码生成:
生成已安装应用列表示例的代码
已安装应用列表示例
数据帧随后被转换为每个用户的安装列表行:
将已安装应用列表转换为 userid 作为键的代码
转换后的已安装应用列表结果
2. 各种相似度度量
各种相似度度量的计算依赖于两个常见的统计结果:应用对的余弦相似度和应用向量的欧几里得量级,这些可以计算如下:
生成应用对的余弦相似度和欧几里得量级的代码
应用对的余弦相似度和欧几里得量级结果
2.1 余弦相似度
余弦相似度衡量两个向量之间的角度。可以通过调用 Spark 的原生函数轻松计算。
余弦相似度公式
2.2 点积
点积是余弦相似度乘以两个向量的欧几里得范数。它可以理解为一个向量在另一个向量上的投影。一个向量的欧几里得范数衡量了该应用在用户中的受欢迎程度。当更多用户安装一个应用时,该应用的欧几里得范数将变大。因此,与余弦相似度不同,点积受两个应用的受欢迎程度的影响。一个受欢迎的应用和一个不受欢迎的应用的点积会很小,就像我们想象短向量在长向量上的投影,投影长度不会很长。因此,在余弦相似度的基础上,点积考虑了两个应用的受欢迎程度。
生成点积的代码
2.3 Jaccard 相似度
Jaccard 相似度是用户集合的交集除以安装两个应用的用户集合的并集。用户集合的交集衡量了两个应用的相似度,而用户集合的并集衡量了两个应用的多样性。从这个意义上讲,我们可以将 Jaccard 相似度视为一个以两个应用的多样性为标准化的度量。
Jaccard 相似度的说明
通过使用两个向量的欧几里得范数和计算出的点积,可以轻松计算 Jaccard 相似度:
生成 Jaccard 相似度的代码
2.4 条件概率提升
条件概率提升衡量了应用 B 的安装在多大程度上有助于应用 A 的安装。
条件概率提升的方程
通过使用两个向量的欧几里得范数,可以轻松计算条件概率。
计算条件概率提升的代码
映射回 app_id 后,可以得到最终结果。
合并四个相似度结果的代码
合并的相似度结果
总结
在这篇文章中,我们介绍了嵌入中的四种相似度度量,即余弦相似度、点积、Jaccard 相似度和条件概率提升。根据嵌入相似度的定义,我们可以选择使用最合适的度量。
Apache Iceberg 表介绍
原文:
towardsdatascience.com/introduction-to-apache-iceberg-tables-a791f1758009
选择 Apache Iceberg 作为数据湖的几个令人信服的理由
·发布于 Towards Data Science ·8 分钟阅读·2023 年 4 月 10 日
–
由 Annie Spratt 提供的照片,来源于 Unsplash
Apache Iceberg:这是什么?Apache Iceberg——它是新的数据湖文件格式吗?还是表格格式?它为何如此优秀?数据湖的时间旅行?
我将在这个故事中尝试回答所有这些问题。
事务一致的数据湖表以及时间点快照隔离是我们所需的一切。
选择表格格式是任何追求数据湖或数据网格策略的人都必须做出的关键选择。选择数据湖平台时需要考虑的一些重要功能包括模式演变支持、读写时间、可扩展性(数据处理是否可以 Hadoop 分割?)、压缩效率以及时间旅行等。
根据业务需求选择正确的文件和表格格式将决定你的数据平台的速度和成本效益。
Iceberg 是一种表格格式,引擎和文件格式无关。Iceberg 通常不是像Apache Hive这样的旧技术的开发。这是件好事,因为从旧技术中开发可能会有局限性。一个好的例子是模式如何随时间变化,而 Iceberg 可以像重命名列一样简单地处理这种变化。它被设计并证明在全球最苛刻的工作负载中大规模的数据湖平台上表现良好。
越来越多的数据工具开始引入冰山表的支持。
例如,我们可以在 AWS Athena(必须是引擎 3)中这样创建一个 Apache Iceberg 表:
谷歌的 BigQuery 现在也支持 Iceberg 表:
冰山表和数据平台类型
Netflix 最初创建了 Iceberg,最终它得到了 Apache 软件基金会的支持和捐赠。现在,Iceberg 独立开发,它是一个完全非盈利的开源项目,专注于处理复杂的数据平台架构。
它支持多种大数据文件格式,包括 Apache Avro、Apache Parquet 和 Apache ORC。
在设计数据平台时,将数据保存在数据湖中是最简单的解决方案之一。
相比于现代数据仓库解决方案,它需要的维护要少得多。
数据湖通常用于存储所有类型的数据——结构化和非结构化——无论大小。数据湖传统上与 Apache Hadoop 分布式文件系统(HDFS)连接。然而,组织越来越多地使用对象存储解决方案,如 Amazon S3、Google Cloud Storage 或 Microsoft Azure Data Lake Storage。
简而言之,数据湖通过集中管理数据来简化数据管理。
与此相反,当每次访问都通过一个单一系统(数据仓库)进行时,它简化了并发管理和更新,但限制了灵活性并增加了成本。我之前在这里写过:
它在多大程度上满足了您的业务需求?选择的困境。
towardsdatascience.com
一切都很棒,但在构建数据湖数据平台时,我们可能还会遇到其他一些问题,例如没有时间旅行、没有模式演变支持以及数据转换和定义的复杂性。
当我们构建数据湖数据平台时,外部表通常会带来与该架构相关的一整套缺点……但 Iceberg 有助于解决这些问题。
传统外部表的一些已知限制:
-
例如,我们不能通过 DML 语句修改它们,且数据一致性不能得到保证。话虽如此,如果在处理过程中底层数据发生变化,我们可能会得到不一致的结果。
-
现代数据仓库中通常有有限的并发查询数,例如 BigQuery 中为 4。
-
外部表不支持集群,也不允许从中导出数据。
-
不允许使用通配符引用表名。
-
没有时间旅行功能
根据我的经验,数据一致性是大规模分析所需的最重要的特性之一。Iceberg 解决了这个问题,现在多个引擎(如 Spark、Hive、Presto、Dremio 等)可以同时操作同一张表。
它还提供了许多其他出色的功能,例如回滚到先前的表版本(以悄悄解决问题),以及在处理海量数据时的高级数据过滤能力。
数据一致性和改进的处理效率
一个好的例子是,当 ETL 过程通过从存储中添加和删除文件来修改数据集时,另一个读取数据集的应用程序可能会处理数据集的部分或不一致的表示,从而产生不准确的结果。
Iceberg 通常通过利用大量清单(元数据)文件来缓解这些风险,以便在数据处理过程中进行快照。它将捕获模式并维护增量,包括文件信息和分区数据,以保证一致性和完全隔离。
Iceberg 还会自动以层次结构的方式排列快照元数据,这确保了快速高效的表格修改,无需重新定义所有数据集文件,从而在数据湖规模下实现最佳性能。
Iceberg 提供 SQL 命令,允许你合并新数据(MERGE)、更改旧行和删除特定行。
Iceberg 可以积极重建数据文件以提高读取性能,或者使用删除增量加快更新速度。
一个好的合并示例可以在这里找到:
//advanced-sql-techniques-for-beginners-211851a28488?source=post_page-----a791f1758009-------------------------------- ## 初学者的高级 SQL 技巧
在 1 到 10 的范围内,你的数据仓库技能有多好?
[towardsdatascience.com
另一个可以应用于上述 AWS Athena 表格的示例如下:
图片由作者提供
时间旅行功能
现代数据仓库允许你在数据中进行时间旅行,即我们可以转到特定的时间戳以获取表格中的特定数据状态。例如,在 Google Cloud BigQuery 中,我们可以运行以下 SQL 来实现:
SELECT *
FROM `mydataset.mytable`
FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR);
这也可能是数据湖平台的一个“不错的附加功能”。
如果你围绕文件设计数据架构,例如 Apache ORC 或 Apache Parquet,你将受益于实现的简便,但你也会遇到上述提到的问题。时间旅行不被支持。模式演变一直是个问题。当字段随时间变化时可能会出现问题。例如,AVRO 文件格式支持模式演变,我之前写过关于大数据文件格式优缺点的文章:
//big-data-file-formats-explained-275876dc1fc9?source=post_page-----a791f1758009-------------------------------- ## 大数据文件格式详解
Parquet 与 ORC 与 AVRO 与 JSON。该选择哪个以及如何使用它们?
[towardsdatascience.com
Iceberg 将整个历史记录保存在 Iceberg 表格格式中,没有存储系统依赖。
Iceberg 表格用户可以在任何 Iceberg 快照或历史时间点查询过去的状态,以获得一致的结果、进行比较或回滚以修复错误,因为历史状态是不可变的。
SELECT count(*) FROM mydatabase.user_transactions FOR TIMESTAMP AS OF TIMESTAMP '2023-01-01 00:00:00.000000 Z'
模式演进支持
数据湖文件以及它们的模式随着时间的推移可能会发生变化,这并不是秘密。现在在 Iceberg 表中添加一列不会返回“无效”数据。
列名和顺序可以更改。最棒的是,模式更新从不需要重建你的表。
ALTER TABLE mydatabase.user_transactions ALTER COLUMN total_cost_gbp AFTER user_id;
Iceberg 允许就地修改表,并确保正确性,即添加的新列永远不会从其他列中读取现有值。当数据量发生变化时,我们现在可以仅通过 SQL 修改分区布局或更改表模式,即使是嵌套结构。
traffic_source STRUCT<name STRING, medium STRING, source STRING>,
这就是 Iceberg 所谓的分区演进。
当你修改分区规范时,使用早期规范写入的现有数据不会受到影响。
每个分区版本的元数据被单独保存。Iceberg 不需要昂贵的操作,例如重写表数据或迁移到新表。
改进的分区
在 Iceberg 中,这被称为“隐藏分区”。传统的数据湖分区方式称为“Hive 分区布局”。
让我们考虑一个在 BigQuery 中具有 Hive 分区布局的外部表:
CREATE OR REPLACE EXTERNAL TABLE production.user_transactions (
payment_date date,
timestamp timestamp,
user_id int,
total_cost_usd float,
registration_date string
)
WITH PARTITION COLUMNS (
dt STRING, -- column order must match the external path
category STRING)
OPTIONS (
uris = ['gs://user-transactions/*'],
format = 'AVRO',
hive_partition_uri_prefix = 'gs://user-transactions'
);
在 Hive 中,分区必须是明确的,并且显示为列,因此 **user_transactions**
表将包含一个 **dt**
日期列。这意味着对我们表进行操作的所有 SQL 查询都必须除了 **timestamp**
过滤器外,还要有 **dt**
过滤器。
Hive 需要分区值。它不了解事务时间戳和我们表中的
dt
之间的联系。
相反,Iceberg 通过取列值并在必要时进行修改来生成分区值。Iceberg 负责将事务**timestamp
** 转换为 **dt**
并维护连接。Iceberg 可能会隐藏分区,因为它不需要用户维护分区列。
分区值总是被适当地创建并在可能的情况下用于加速查询。
dt
对生产者和消费者都是不可见的,查询不再依赖于我们表的实际物理布局。
它有助于避免在转换数据时出现分区错误。例如,使用错误的日期格式,导致不正确的分区,而不是失败:**20230101**
代替 **2023–01–01**
。这是 Hive 分区布局中一个众所周知且最常见的问题。 另一个 Iceberg 有助于解决的问题是,在 Hive 分区布局中,所有工作的查询都与表的分区方案相关,因此更改分区设置将会破坏查询。
结论
关系型数据库和数据仓库都支持原子事务和时间旅行,但它们以其专有的方式实现。Iceberg 作为一个 Apache 项目,是完全开源的,不依赖于任何特定的工具或数据湖引擎。
Iceberg 支持行业标准文件格式如 Parquet、ORC 和 Avro,并且与 Dremio、Spark、Hive 和 Presto 等关键数据湖引擎兼容。其广泛的社区合作生成新想法,并提供长期的帮助。它拥有各种活跃的社区,例如公共 Slack 频道,任何人都可以自由参与。它经过设计并证明在全球最大工作负载和环境中的数据湖平台上表现出色。
组织现在可以通过利用 Iceberg 充分发挥数据湖架构的潜力和优势。体验基于云存储的数据平台的成本效益,同时无需牺牲传统数据库和数据仓库解决方案的功能和能力。
它使我们的数据湖平台真正灵活且可靠
总结一下,Iceberg 表格格式的优点如下:
-
多个独立程序可以同时一致地处理相同的数据集。
-
改进的数据处理和数据可靠性(针对非常大的数据湖规模表格的更新更加高效)。
-
随着时间的推移,改进的模式处理。
-
ETL 管道大大简化(通过对数据湖中的数据进行操作,而不是在多个独立系统之间传输数据)。
推荐阅读:
1. cloud.google.com/bigquery/docs/time-travel
4. iceberg.apache.org/community/
5. cloud.google.com/bigquery/docs/iceberg-tables
6. medium.com/towards-data-science/data-platform-architecture-types-f255ac6e0b7
7. medium.com/towards-data-science/big-data-file-formats-explained-275876dc1fc9
8. iceberg.apache.org/docs/latest/evolution/
9. iceberg.apache.org/docs/latest/partitioning/#icebergs-hidden-partitioning
10. cloud.google.com/bigquery/docs/external-data-cloud-storage#sql