037 《Boost.Histogram 权威指南》
🌟🌟🌟本文案由Gemini 2.0 Flash Thinking Experimental 01-21创作,用来辅助学习知识。🌟🌟🌟
书籍大纲
▮▮▮▮ 1. chapter 1: 初识直方图 (Introduction to Histograms)
▮▮▮▮▮▮▮ 1.1 什么是直方图?(What is a Histogram?)
▮▮▮▮▮▮▮ 1.2 为什么要使用直方图?(Why Use Histograms?)
▮▮▮▮▮▮▮ 1.3 直方图的基本概念 (Basic Concepts of Histograms)
▮▮▮▮▮▮▮▮▮▮▮ 1.3.1 轴 (Axis)
▮▮▮▮▮▮▮▮▮▮▮ 1.3.2 箱 (Bin)
▮▮▮▮▮▮▮▮▮▮▮ 1.3.3 计数 (Count)
▮▮▮▮▮▮▮ 1.4 直方图的类型 (Types of Histograms)
▮▮▮▮▮▮▮▮▮▮▮ 1.4.1 一维直方图 (1D Histograms)
▮▮▮▮▮▮▮▮▮▮▮ 1.4.2 多维直方图 (Multidimensional Histograms)
▮▮▮▮ 2. chapter 2: Boost.Histogram 快速入门 (Quick Start to Boost.Histogram)
▮▮▮▮▮▮▮ 2.1 Boost.Histogram 库简介 (Introduction to Boost.Histogram Library)
▮▮▮▮▮▮▮ 2.2 环境搭建与安装 (Environment Setup and Installation)
▮▮▮▮▮▮▮ 2.3 第一个 Boost.Histogram 程序 (Your First Boost.Histogram Program)
▮▮▮▮▮▮▮ 2.4 创建和填充直方图 (Creating and Filling Histograms)
▮▮▮▮▮▮▮ 2.5 访问直方图数据 (Accessing Histogram Data)
▮▮▮▮ 3. chapter 3: 深入理解轴 (Deep Dive into Axes)
▮▮▮▮▮▮▮ 3.1 规则轴 (Regular Axis)
▮▮▮▮▮▮▮ 3.2 可变轴 (Variable Axis)
▮▮▮▮▮▮▮ 3.3 整数轴 (Integer Axis)
▮▮▮▮▮▮▮ 3.4 分类轴 (Category Axis)
▮▮▮▮▮▮▮ 3.5 自定义轴 (Custom Axis)
▮▮▮▮▮▮▮ 3.6 轴的配置与选项 (Axis Configuration and Options)
▮▮▮▮ 4. chapter 4: 存储与累加器 (Storage and Accumulators)
▮▮▮▮▮▮▮ 4.1 存储类型 (Storage Types)
▮▮▮▮▮▮▮▮▮▮▮ 4.1.1 整数存储 (Integer Storage)
▮▮▮▮▮▮▮▮▮▮▮ 4.1.2 无限制存储 (Unlimited Storage)
▮▮▮▮▮▮▮▮▮▮▮ 4.1.3 权重存储 (Weight Storage)
▮▮▮▮▮▮▮ 4.2 累加器 (Accumulators)
▮▮▮▮▮▮▮▮▮▮▮ 4.2.1 默认累加器 (Default Accumulator)
▮▮▮▮▮▮▮▮▮▮▮ 4.2.2 权重累加器 (Weight Accumulator)
▮▮▮▮▮▮▮▮▮▮▮ 4.2.3 自定义累加器 (Custom Accumulators)
▮▮▮▮▮▮▮ 4.3 选择合适的存储与累加器 (Choosing the Right Storage and Accumulator)
▮▮▮▮ 5. chapter 5: 实战代码:Boost.Histogram 的应用 (Practical Code: Applications of Boost.Histogram)
▮▮▮▮▮▮▮ 5.1 数据分析案例 (Data Analysis Case Studies)
▮▮▮▮▮▮▮▮▮▮▮ 5.1.1 一维数据分布分析 (1D Data Distribution Analysis)
▮▮▮▮▮▮▮▮▮▮▮ 5.1.2 多维数据关联性分析 (Multidimensional Data Correlation Analysis)
▮▮▮▮▮▮▮ 5.2 性能监控与指标统计 (Performance Monitoring and Metric Statistics)
▮▮▮▮▮▮▮ 5.3 模拟与实验数据处理 (Simulation and Experimental Data Processing)
▮▮▮▮▮▮▮ 5.4 与其他库的集成应用 (Integration with Other Libraries)
▮▮▮▮ 6. chapter 6: 高级应用技巧 (Advanced Application Techniques)
▮▮▮▮▮▮▮ 6.1 直方图的变换与操作 (Histogram Transformations and Operations)
▮▮▮▮▮▮▮▮▮▮▮ 6.1.1 重采样 (Resampling)
▮▮▮▮▮▮▮▮▮▮▮ 6.1.2 重分箱 (Rebinning)
▮▮▮▮▮▮▮▮▮▮▮ 6.1.3 投影 (Projection)
▮▮▮▮▮▮▮ 6.2 直方图的算术运算 (Arithmetic Operations on Histograms)
▮▮▮▮▮▮▮ 6.3 直方图的序列化与持久化 (Serialization and Persistence of Histograms)
▮▮▮▮▮▮▮ 6.4 自定义直方图行为 (Customizing Histogram Behavior)
▮▮▮▮ 7. chapter 7: Boost.Histogram API 全面解析 (Comprehensive API Analysis of Boost.Histogram)
▮▮▮▮▮▮▮ 7.1 核心类:histogram (Core Class: histogram)
▮▮▮▮▮▮▮ 7.2 轴类:axis (Axis Classes: axis)
▮▮▮▮▮▮▮ 7.3 累加器相关 API (Accumulator Related APIs)
▮▮▮▮▮▮▮ 7.4 自由函数与工具函数 (Free Functions and Utility Functions)
▮▮▮▮▮▮▮ 7.5 API 使用的最佳实践 (Best Practices for API Usage)
▮▮▮▮ 8. chapter 8: Boost.Histogram 与生态系统 (Boost.Histogram and Ecosystem)
▮▮▮▮▮▮▮ 8.1 与 Boost 其他库的集成 (Integration with Other Boost Libraries)
▮▮▮▮▮▮▮ 8.2 Boost.Histogram 在现代 C++ 开发中的角色 (Role of Boost.Histogram in Modern C++ Development)
▮▮▮▮▮▮▮ 8.3 未来发展趋势与展望 (Future Trends and Prospects)
1. chapter 1: 初识直方图 (Introduction to Histograms)
1.1 什么是直方图?(What is a Histogram?)
直方图(Histogram),是一种强大的数据可视化和分析工具,广泛应用于统计学、图像处理、数据挖掘等多个领域。它以图形化的方式,直观地展示数据的分布情况,帮助我们快速理解数据的整体特征和潜在规律。
简单来说,直方图就像是数据的“频率分布图”。它将数据按照一定的规则划分为若干个区间(或者称为箱,Bin),然后统计每个区间内数据出现的频次(或者称为计数,Count),最后用柱状图的形式展现出来,柱子的高度就代表了该区间内数据的频次。
为了更好地理解直方图,我们可以将其与日常生活中的例子进行类比:
① 学生成绩分布:假设一个班级的数学考试成绩出来了,为了了解成绩的整体分布情况,老师可以将成绩划分为几个分数段,例如:[0, 60), [60, 70), [70, 80), [80, 90), [90, 100]。然后统计每个分数段的学生人数。如果用直方图来表示,横轴就是分数段(区间),纵轴就是学生人数(频次),每个柱子就代表了对应分数段的学生人数。这样一看,就能清晰地了解班级整体的数学成绩分布,例如,是集中在中间分数段,还是两极分化严重。
② 城市空气质量分析: 环保部门需要监测城市空气质量,PM2.5 是一个重要的指标。 假设我们收集了一年中每天的 PM2.5 数据。为了分析 PM2.5 的分布情况,我们可以将 PM2.5 浓度值划分为不同的区间,例如:[0, 35) (优), [35, 75) (良), [75, 115) (轻度污染), [115, 150) (中度污染), [150, 250) (重度污染), [250+) (严重污染)。然后统计每天 PM2.5 值落在每个区间的天数。通过直方图,我们可以直观地看到一年中空气质量为优、良、污染等不同等级的天数分布,从而评估城市整体的空气质量状况。
③ 图像像素亮度分布: 在数字图像处理中,图像可以看作是由像素点组成的矩阵,每个像素点都有一个亮度值(通常在 0-255 之间)。为了分析图像的亮度分布,我们可以将亮度值划分为若干个区间,然后统计每个区间内像素点的数量。直方图可以帮助我们了解图像的整体亮度分布情况,例如,图像是偏亮、偏暗,还是亮度分布均匀。这在图像增强、图像分割等领域都有重要的应用。
总而言之,直方图是一种用于汇总离散或连续数据分布的有效工具。它通过将数据分组到箱中并显示每个箱的计数,帮助我们理解数据的集中趋势、离散程度、以及是否存在异常值或模式。 无论你是初学者还是经验丰富的工程师,掌握直方图的概念和应用都至关重要,它将是你数据分析工具箱中的一把利器。
1.2 为什么要使用直方图?(Why Use Histograms?)
直方图之所以在数据分析和可视化领域如此受欢迎,是因为它具有以下显著的优点和广泛的应用场景:
① 直观展示数据分布: 这是直方图最核心的功能。相比于原始数据表格,直方图能够以图形化的方式,一目了然地展示数据的分布形态。我们可以快速判断数据是集中分布还是均匀分布,是正态分布、偏态分布还是其他分布类型。这种直观性对于快速理解数据特征至关重要。
② 发现数据中的模式和趋势: 直方图可以帮助我们发现数据中隐藏的模式和趋势。例如,通过观察直方图的形状,我们可以识别出数据是否具有峰值(众数),峰值的位置和数量,以及数据分布的对称性、偏斜性等。这些模式和趋势往往蕴含着重要的信息,可以帮助我们进行更深入的分析和决策。
③ 识别异常值和离群点: 直方图可以帮助我们快速识别数据中的异常值(Outlier)和离群点。异常值是指明显偏离数据集中趋势的数值。在直方图中,异常值通常会显示为远离主体分布的孤立柱状。识别异常值对于数据清洗、质量控制以及异常检测等应用非常重要。
④ 比较不同数据集的分布: 我们可以使用直方图来比较不同数据集的分布情况。例如,我们可以将不同时间段、不同组别或者不同条件下的数据绘制成直方图,然后进行对比分析。通过比较直方图的形状、中心位置、离散程度等,我们可以发现不同数据集之间的差异和联系。
⑤ 作为进一步分析的基础: 直方图常常作为数据分析的第一步。通过直方图,我们可以对数据的整体分布有一个初步的了解,从而为后续更深入的统计分析、建模预测等工作奠定基础。例如,在机器学习中,我们可以先绘制特征数据的直方图,了解特征的分布情况,再选择合适的特征工程和模型算法。
⑥ 广泛的应用场景: 直方图的应用场景非常广泛,几乎涉及到所有需要分析数据分布的领域:
⚝ 统计学: 描述性统计分析、概率分布估计、假设检验等。
⚝ 数据分析: 数据探索性分析(EDA)、数据质量评估、特征工程等。
⚝ 图像处理: 图像增强、图像分割、图像检索等。
⚝ 信号处理: 信号特征提取、信号分类、频谱分析等。
⚝ 金融分析: 股票价格分布分析、风险评估、交易策略优化等。
⚝ 生物信息学: 基因表达数据分析、蛋白质组学数据分析等。
⚝ 性能监控: 系统性能指标监控、网络流量分析、日志分析等。
⚝ 质量控制: 产品质量检测、生产过程监控、缺陷分析等。
总之,直方图是一种简单而强大的数据分析工具,它能够帮助我们快速理解数据分布、发现数据模式、识别异常值,并为后续更深入的分析和应用提供基础。 掌握直方图的使用,将极大地提升我们处理和分析数据的能力。
1.3 直方图的基本概念 (Basic Concepts of Histograms)
要深入理解和有效使用直方图,我们需要掌握其几个核心概念:轴 (Axis),箱 (Bin),和 计数 (Count)。 这三个概念是构建和解读直方图的基础。
1.3.1 轴 (Axis)
轴(Axis)是直方图的骨架,它定义了数据的取值范围和划分方式。 在直方图中,轴通常是水平的,用于表示数据的不同取值区间。
① 维度 (Dimension): 直方图可以有一个或多个轴。 一维直方图(1D Histogram)只有一个轴,用于展示单个变量的数据分布,例如,学生成绩分布直方图只有一个轴——“成绩”。 多维直方图(Multidimensional Histogram)则有多个轴,用于展示多个变量的联合分布,例如,我们可以用二维直方图同时展示学生的身高和体重分布。 Boost.Histogram 库支持创建任意维度的直方图,为我们分析复杂数据提供了强大的工具。
② 轴的类型 (Axis Type): 轴的类型决定了数据如何被划分到不同的箱中。 常见的轴类型包括:
⚝ 规则轴 (Regular Axis): 规则轴将数据范围均匀地划分为若干个等宽的区间(箱)。 这是最常见的轴类型,适用于数据分布相对均匀的情况。 例如,将成绩范围 [0, 100] 划分为 10 个等宽的区间,每个区间宽度为 10。
⚝ 可变轴 (Variable Axis): 可变轴允许我们自定义箱的边界,箱的宽度可以不相等。 这适用于数据分布不均匀,或者需要关注特定数据区间的情况。 例如,在分析收入分布时,我们可以设置更窄的箱来关注低收入和高收入人群的分布细节,而对于中等收入人群,可以使用更宽的箱。
⚝ 整数轴 (Integer Axis): 整数轴专门用于处理整数类型的数据。 它可以确保每个箱都对应一个或多个连续的整数值。 例如,统计文章中单词长度的分布,单词长度是整数,可以使用整数轴。
⚝ 分类轴 (Category Axis): 分类轴用于处理离散的类别数据,例如,城市名称、产品类型、颜色等等。 每个箱对应一个特定的类别标签,而不是数值区间。 例如,统计不同城市的销售额分布,可以使用分类轴,每个箱代表一个城市。
⚝ 圆形轴 (Circular Axis): 圆形轴用于表示周期性或角度数据,例如,时间(一天中的小时、一年中的月份)、角度(方位角、旋转角度)等。 圆形轴的首尾是相连的,没有明显的起点和终点。
③ 轴的配置 (Axis Configuration): 除了类型,轴还可以进行各种配置,例如:
⚝ 名称 (Name): 为轴指定一个名称,用于在图表和分析结果中标识轴的含义。
⚝ 标签 (Label): 为轴添加标签,用于在图表上显示轴的单位或描述信息。
⚝ 范围 (Range): 定义轴的数据取值范围,超出范围的数据可以被忽略或单独处理(例如,放入溢出箱)。
⚝ 箱的数量 (Number of Bins): 对于规则轴和整数轴,需要指定箱的数量,决定了数据被划分成多少个区间。 箱的数量会影响直方图的精度和展示效果,需要根据具体情况进行选择。
理解轴的概念及其各种类型和配置,是创建有效直方图的关键。 选择合适的轴类型和配置,能够更好地反映数据的特征,并满足不同的分析需求。 Boost.Histogram 库提供了丰富的轴类型和配置选项,为我们灵活地构建各种直方图提供了强大的支持。
1.3.2 箱 (Bin)
箱(Bin),也称为区间(Interval)或柱(Bar),是直方图的基本单元。 它代表了轴上的一个数据区间,用于统计落入该区间内的数据点的数量。 直方图的柱状图就是由一个个相邻的箱组成的。
① 箱的边界 (Bin Boundaries): 每个箱都有明确的起始边界和结束边界,定义了该箱所包含的数据范围。 对于规则轴,箱的边界是均匀分布的;对于可变轴,箱的边界可以自定义。 箱的边界决定了数据点被划分到哪个箱中。
② 箱的宽度 (Bin Width): 箱的宽度是指箱的起始边界和结束边界之间的距离。 对于规则轴,所有箱的宽度是相等的;对于可变轴,箱的宽度可以不相等。 箱的宽度会影响直方图的形状和分辨率。 较窄的箱宽度可以展示更精细的数据分布细节,但也可能引入更多的噪声;较宽的箱宽度可以平滑数据分布,但可能会丢失一些细节信息。
③ 箱的索引 (Bin Index): 在程序中,为了方便访问和操作箱,通常会为每个箱分配一个索引。 索引通常从 0 开始,依次递增。 通过箱的索引,我们可以快速定位到特定的箱,并获取其计数等信息。
④ 溢出箱 (Overflow and Underflow Bins): 有时,数据中可能会出现超出轴的定义范围的值。 为了处理这些超出范围的数据,直方图通常会提供溢出箱(Overflow Bin)和 下溢箱(Underflow Bin)。 溢出箱用于统计大于轴的最大值的数据点,下溢箱用于统计小于轴的最小值的数据点。 溢出箱和下溢箱可以帮助我们了解数据中超出范围值的分布情况,以及数据范围设置是否合理。
⑤ 箱的内容 (Bin Content): 箱的内容主要包括计数(Count)和可能的累加值(Accumulated Value)。 计数表示落入该箱内的数据点的数量。 累加值则取决于所使用的累加器(Accumulator),可以是数据的总和、平均值、方差等等。 默认情况下,直方图只统计计数。
箱是直方图的核心组成部分,它将连续的数据离散化为一个个可统计的区间。 合理设置箱的边界和宽度,选择合适的溢出箱处理方式,以及理解箱的内容,对于构建和解读直方图至关重要。 Boost.Histogram 库提供了灵活的箱配置选项,以及丰富的累加器支持,可以满足各种复杂的数据分析需求。
1.3.3 计数 (Count)
计数(Count),也称为频次(Frequency),是直方图箱的核心数据。 它表示落入每个箱内的数据点的数量。 直方图的柱状高度正是由计数决定的,柱子越高,表示该箱内的数据点越多,数据在该区间内出现的频率越高。
① 计数的类型 (Count Type): 计数的类型可以是整数,也可以是浮点数。 默认情况下,计数是整数类型,表示数据点的绝对数量。 在某些情况下,我们可能需要使用权重(Weight)来统计计数。 例如,在处理抽样数据时,为了反映总体分布,我们需要根据抽样权重来调整计数。 这时,计数可以是浮点数类型,表示加权计数。 Boost.Histogram 库支持使用权重进行计数,可以处理各种复杂的加权数据分析场景。
② 计数的累加 (Count Accumulation): 当我们向直方图填充数据时,每当一个数据点落入某个箱内,该箱的计数就会增加。 这个过程称为计数的累加。 累加的方式可以是简单的加 1,也可以是加权重值。 Boost.Histogram 库提供了灵活的累加器机制,可以自定义计数的累加方式,例如,可以累加数据的平方和、最小值、最大值等等。
③ 计数的访问 (Count Access): 我们可以通过箱的索引来访问每个箱的计数。 通过访问计数,我们可以获取每个箱内的数据点数量,从而了解数据的分布情况。 Boost.Histogram 库提供了方便的 API 来访问直方图的计数数据,例如,可以使用 at()
方法根据箱的索引获取计数。
④ 计数的归一化 (Count Normalization): 在某些情况下,为了方便比较不同数据集的分布,或者为了将计数转换为概率密度,我们需要对计数进行归一化处理。 归一化是指将计数缩放到一个特定的范围,例如,[0, 1] 或者总和为 1。 常见的归一化方法包括:
⚝ 总数归一化 (Total Count Normalization): 将每个箱的计数除以所有箱的总计数,使得所有箱的计数总和为 1。 归一化后的计数可以理解为数据点落入该箱的概率。
⚝ 密度归一化 (Density Normalization): 将每个箱的计数除以箱的宽度和总数据点数量,得到概率密度。 密度归一化可以消除箱宽度不一致对直方图形状的影响,更准确地反映数据的分布密度。
计数是直方图最直接的输出结果,它反映了数据在不同区间内的分布频率。 理解计数的类型、累加方式、访问方法以及归一化处理,对于正确解读直方图,并从中提取有价值的信息至关重要。 Boost.Histogram 库提供了灵活的计数处理机制,以及丰富的归一化选项,可以满足各种数据分析和可视化需求。
1.4 直方图的类型 (Types of Histograms)
根据轴的维度数量,直方图可以分为一维直方图(1D Histograms)和 多维直方图(Multidimensional Histograms)。 维度数量决定了直方图可以展示的数据变量的个数,以及数据的复杂程度。
1.4.1 一维直方图 (1D Histograms)
一维直方图是最简单、最常见的直方图类型。 它只有一个轴,用于展示单个变量的数据分布。 我们在前面例子中提到的学生成绩分布直方图、城市空气质量 PM2.5 分布直方图、图像像素亮度分布直方图等,都是一维直方图。
① 应用场景: 一维直方图主要用于分析单个变量的分布特征,例如:
⚝ 了解数据的中心趋势: 通过观察直方图的峰值位置,可以了解数据的集中趋势,例如,平均值、中位数、众数等。
⚝ 评估数据的离散程度: 通过观察直方图的宽度和形状,可以评估数据的离散程度,例如,标准差、方差、四分位数间距等。
⚝ 判断数据的分布类型: 通过比较直方图的形状与常见的概率分布形状(例如,正态分布、均匀分布、指数分布等),可以初步判断数据的分布类型。
⚝ 检测异常值: 通过观察直方图中远离主体分布的孤立柱状,可以检测数据中的异常值。
② 可视化方式: 一维直方图通常以柱状图的形式进行可视化。 横轴表示数据变量的取值范围(轴),纵轴表示计数(频次)。 每个柱子的高度代表了对应数据区间内的数据点数量。
③ Boost.Histogram 中的实现: 在 Boost.Histogram 库中,创建一维直方图非常简单。 我们只需要指定一个轴即可。 例如,创建一个规则轴的一维直方图:
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个规则轴,范围从 0 到 10,划分为 10 个箱
8
auto axis = bh::axis::regular(10, 0.0, 10.0, "value");
9
10
// 创建一个一维直方图,使用规则轴
11
auto hist = bh::make_histogram(axis);
12
13
// 填充一些数据
14
hist(1.5);
15
hist(2.5);
16
hist(2.5);
17
hist(3.5);
18
hist(4.5);
19
hist(4.5);
20
hist(4.5);
21
hist(5.5);
22
hist(6.5);
23
hist(7.5);
24
hist(8.5);
25
hist(9.5);
26
27
// 打印直方图的计数
28
for (auto x : hist) {
29
std::cout << "bin[" << x.bin().idx() << "] count = " << *x << std::endl;
30
}
31
32
return 0;
33
}
这段代码创建了一个一维直方图,使用了一个规则轴,范围从 0 到 10,划分为 10 个箱。 然后填充了一些示例数据,并打印了每个箱的计数。 运行这段代码,你将看到每个箱的计数结果,这就是一个简单的一维直方图。
一维直方图是数据分析的基础工具,掌握其概念和使用方法,是学习更复杂直方图类型的前提。 Boost.Histogram 库提供了简洁易用的 API,可以方便地创建和操作一维直方图,进行各种数据分析和可视化任务。
1.4.2 多维直方图 (Multidimensional Histograms)
多维直方图,顾名思义,是具有多个轴的直方图。 它可以展示两个或多个变量的联合分布情况。 例如,我们可以使用二维直方图同时展示身高和体重的分布,使用三维直方图同时展示温度、湿度和风速的分布。 多维直方图可以帮助我们分析变量之间的关联性和相互作用。
① 应用场景: 多维直方图主要用于分析多个变量之间的关系,例如:
⚝ 变量之间的相关性分析: 通过观察多维直方图的形状和分布模式,可以分析变量之间是否存在相关性,例如,正相关、负相关、不相关等。
⚝ 多变量数据模式识别: 多维直方图可以帮助我们发现多变量数据中的复杂模式,例如,聚类、异常值、条件分布等。
⚝ 高维数据降维可视化: 对于高维数据,我们可以通过投影或者切片等方式,将高维直方图降维到二维或三维进行可视化,从而探索高维数据的结构。
⚝ 特征工程: 在机器学习中,可以使用多维直方图分析特征之间的交互作用,为特征选择和特征组合提供依据。
② 可视化方式: 多维直方图的可视化比一维直方图更复杂。
⚝ 二维直方图: 二维直方图可以使用热图(Heatmap)或者等高线图(Contour Plot)进行可视化。 热图用颜色深浅表示计数的大小,颜色越深,计数越高。 等高线图用等高线表示计数相等的区域。
⚝ 三维直方图: 三维直方图可以使用三维柱状图或者体绘制(Volume Rendering)进行可视化。 三维柱状图在三维空间中绘制柱子,柱子的高度表示计数。 体绘制则将三维直方图看作一个三维密度场,用颜色和透明度表示密度大小。
⚝ 更高维度直方图: 对于更高维度的直方图(维度大于 3),直接可视化比较困难。 通常需要使用降维技术(例如,投影、切片)或者交互式可视化工具进行探索。
③ Boost.Histogram 中的实现: 在 Boost.Histogram 库中,创建多维直方图也非常方便。 我们只需要指定多个轴即可。 例如,创建一个二维直方图:
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
#include <random>
4
5
namespace bh = boost::histogram;
6
7
int main() {
8
// 创建两个规则轴
9
auto axis_x = bh::axis::regular(10, 0.0, 10.0, "x");
10
auto axis_y = bh::axis::regular(10, 0.0, 10.0, "y");
11
12
// 创建一个二维直方图,使用两个规则轴
13
auto hist2d = bh::make_histogram(axis_x, axis_y);
14
15
// 填充一些二维数据 (例如,随机生成)
16
std::random_device rd;
17
std::mt19937 gen(rd());
18
std::uniform_real_distribution<> dis(0.0, 10.0);
19
20
for (int i = 0; i < 1000; ++i) {
21
hist2d(dis(gen), dis(gen));
22
}
23
24
// 遍历二维直方图的箱
25
for (auto x : hist2d) {
26
std::cout << "bin[" << x.bin().idx(0) << ", " << x.bin().idx(1) << "] count = " << *x << std::endl;
27
}
28
29
return 0;
30
}
这段代码创建了一个二维直方图,使用了两个规则轴,分别表示 x 轴和 y 轴。 然后随机生成了 1000 个二维数据点,并填充到直方图中。 最后遍历并打印了每个箱的计数。 这就是一个简单的二维直方图的例子。
多维直方图是分析多变量数据的强大工具,它可以帮助我们发现变量之间的复杂关系和模式。 Boost.Histogram 库支持创建任意维度的直方图,并提供了丰富的 API 来操作和分析多维直方图数据。 随着数据复杂度的增加,多维直方图的应用将越来越广泛。
END_OF_CHAPTER
2. chapter 2: Boost.Histogram 快速入门 (Quick Start to Boost.Histogram)
2.1 Boost.Histogram 库简介 (Introduction to Boost.Histogram Library)
Boost.Histogram 库是 C++ Boost 程序库家族中的一员,专门用于创建和操作直方图 📊。它提供了一套强大而灵活的工具,可以高效地处理各种数据分析和统计任务。无论您是初学者还是经验丰富的专家,Boost.Histogram 都能满足您在直方图方面的需求。
Boost.Histogram 的核心优势:
① 现代化设计:Boost.Histogram 采用现代 C++ (C++14 及以上) 设计理念,充分利用了模板元编程、constexpr 等特性,提供了类型安全、高性能的接口。这意味着您可以在编译时获得更强的类型检查,并在运行时获得更高的效率。
② 灵活性和可扩展性:库的设计高度模块化,核心组件包括轴 (Axis)、存储 (Storage) 和累加器 (Accumulator),它们可以自由组合和定制。这种设计允许用户根据具体需求创建各种类型的直方图,从简单的一维直方图到复杂的多维直方图,从基本的计数直方图到带权重的直方图,都能轻松应对。
③ 高性能:Boost.Histogram 经过精心优化,在性能方面表现出色。它采用了高效的数据结构和算法,可以快速地填充和访问直方图数据,即使处理大规模数据集也能保持高效。这对于需要进行实时数据分析或高性能计算的应用场景至关重要。
④ 易用性:尽管功能强大,Boost.Histogram 仍然非常易于使用。库提供了简洁明了的 API,使得创建、填充和操作直方图变得直观而简单。即使是初学者也能快速上手,并利用其强大的功能。
⑤ 与其他 Boost 库的良好集成:作为 Boost 库家族的一员,Boost.Histogram 可以与其他 Boost 库无缝集成,例如 Boost.Accumulators (累加器库), Boost.Serialization (序列化库) 等。这为构建更复杂的数据分析应用提供了便利。
Boost.Histogram 的应用场景:
Boost.Histogram 在数据分析、科学计算、性能监控、机器学习等领域都有广泛的应用:
⚝ 数据可视化:直方图是数据可视化的重要工具,Boost.Histogram 可以帮助您快速生成用于数据可视化的直方图数据。
⚝ 数据分析:在统计分析中,直方图用于展示数据的分布情况,Boost.Histogram 提供了强大的功能来分析各种类型的数据分布。
⚝ 性能监控:在软件和硬件性能监控中,直方图可以用来统计事件发生的频率和分布,例如请求延迟、CPU 使用率等。
⚝ 机器学习:在机器学习中,直方图可以作为特征工程的一部分,用于提取数据特征。
⚝ 科学计算:在物理、化学、生物等科学领域,直方图常用于分析实验数据和模拟数据。
本章概要:
本章将引导您快速入门 Boost.Histogram 库。我们将从环境搭建开始,逐步介绍如何创建、填充和访问直方图数据。通过本章的学习,您将能够编写您的第一个 Boost.Histogram 程序,并对直方图的基本概念和操作有一个初步的了解。为后续深入学习 Boost.Histogram 的高级特性和应用打下坚实的基础。
2.2 环境搭建与安装 (Environment Setup and Installation)
在使用 Boost.Histogram 之前,您需要先搭建好开发环境并安装 Boost 库。Boost 是一个跨平台的 C++ 程序库集合,支持多种操作系统和编译器。
安装 Boost 库
Boost 库的安装方式取决于您的操作系统和所使用的包管理器。以下介绍几种常见的安装方法:
① 使用包管理器 (Package Manager):
大多数 Linux 发行版和 macOS 都提供了包管理器,例如 apt
, yum
, brew
等,可以方便地安装 Boost 库。
⚝ Debian/Ubuntu:
打开终端,执行以下命令安装 Boost.Histogram 以及其他常用的 Boost 组件:
1
sudo apt-get update
2
sudo apt-get install libboost-all-dev
⚝ CentOS/RHEL:
打开终端,执行以下命令安装 Boost.Histogram 以及其他常用的 Boost 组件:
1
sudo yum update
2
sudo yum install boost-devel
⚝ macOS (Homebrew):
如果您的 macOS 系统安装了 Homebrew,可以使用以下命令安装 Boost:
1
brew install boost
如果您只需要 Boost.Histogram 库,可以尝试安装特定的包(如果包管理器提供的话)。例如,在某些系统中,可能会有 libboost-histogram-dev
或类似的包。但是,安装完整的 libboost-all-dev
或 boost-devel
通常是最方便的方式,因为它包含了 Boost 库的所有组件,方便您后续使用其他 Boost 库。
② 从源代码编译安装:
如果您希望使用特定版本的 Boost 库,或者您的操作系统没有提供预编译的 Boost 包,您可以从 Boost 官网下载源代码进行编译安装。
下载 Boost 源代码:
访问 Boost 官网,下载最新版本的 Boost 源代码压缩包。解压源代码:
将下载的压缩包解压到您希望安装 Boost 的目录,例如/usr/local/boost
或~/boost
.编译和安装:
打开终端,进入解压后的 Boost 源代码根目录。执行以下命令进行编译和安装:
1
./bootstrap.sh
2
./b2 install --prefix=/usr/local
▮▮▮▮⚝ ./bootstrap.sh
:用于生成 b2
构建工具。
▮▮▮▮⚝ ./b2 install --prefix=/usr/local
:使用 b2
构建工具编译并安装 Boost 库到 /usr/local
目录。您可以根据需要修改 --prefix
指定的安装路径。
注意: 编译 Boost 库可能需要一些时间,具体时间取决于您的计算机性能和选择的编译选项。
③ 验证安装:
安装完成后,您可以通过编写一个简单的程序来验证 Boost.Histogram 是否安装成功。创建一个名为 histogram_test.cpp
的文件,内容如下:
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
int main() {
5
namespace bh = boost::histogram;
6
7
// 创建一个一维直方图,轴的范围是 0 到 10,分为 10 个箱子
8
auto hist = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
9
10
// 填充一些数据
11
hist(1.5);
12
hist(2.5);
13
hist(3.5);
14
15
// 打印直方图的箱子计数
16
for (auto bin : hist) {
17
std::cout << "bin count: " << bin << std::endl;
18
}
19
20
return 0;
21
}
使用 C++ 编译器 (例如 g++) 编译并运行该程序:
1
g++ histogram_test.cpp -o histogram_test -std=c++14 -lboost_histogram
2
./histogram_test
注意:
⚝ -std=c++14
:指定使用 C++14 标准编译,Boost.Histogram 需要 C++14 或更高版本。
⚝ -lboost_histogram
:链接 Boost.Histogram 库。如果您的 Boost 库安装在非标准路径,可能需要使用 -L
和 -I
选项指定库文件和头文件路径。例如,如果 Boost 安装在 /usr/local
,您可能需要添加 -L/usr/local/lib -I/usr/local/include
。
如果程序成功编译并运行,并且输出了直方图的箱子计数,则说明 Boost.Histogram 库已经成功安装并可以使用了。
2.3 第一个 Boost.Histogram 程序 (Your First Boost.Histogram Program)
让我们从一个简单的 "Hello, Histogram!" 程序开始,体验 Boost.Histogram 的基本用法。这个程序将创建一个一维直方图,并填充一些数据,最后打印直方图的计数。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
int main() {
5
namespace bh = boost::histogram;
6
7
// ① 创建一个一维直方图
8
auto hist = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
9
10
// ② 填充数据
11
hist(1.5);
12
hist(2.5);
13
hist(1.5);
14
hist(4.5);
15
hist(7.5);
16
hist(1.5);
17
hist(2.5);
18
hist(9.5);
19
20
// ③ 访问和打印直方图数据
21
std::cout << "Histogram bin counts:" << std::endl;
22
for (auto bin : hist) {
23
std::cout << "bin: [" << bin.bin().lower() << ", " << bin.bin().upper() << ") count: " << *bin << std::endl;
24
}
25
26
return 0;
27
}
代码解析:
① 创建直方图:
1
auto hist = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
⚝ bh::make_histogram(...)
: 是一个工厂函数,用于创建直方图对象。
⚝ bh::axis::regular(10, 0.0, 10.0)
: 创建一个规则轴 (Regular Axis)。
▮▮▮▮⚝ 10
:表示将轴的范围划分为 10 个箱 (Bin)。
▮▮▮▮⚝ 0.0
:表示轴的起始值。
▮▮▮▮⚝ 10.0
:表示轴的结束值。
▮▮▮▮⚝ 因此,这个规则轴定义了一个范围从 0.0 到 10.0,均匀划分为 10 个箱子的轴。每个箱子的宽度为 \((10.0 - 0.0) / 10 = 1.0\)。
② 填充数据:
1
hist(1.5);
2
hist(2.5);
3
// ...
4
hist(9.5);
⚝ hist(value)
: 使用 ()
运算符向直方图填充数据。每次调用 hist(value)
,Boost.Histogram 会根据 value
找到对应的箱子,并将该箱子的计数增加 1。
③ 访问和打印直方图数据:
1
std::cout << "Histogram bin counts:" << std::endl;
2
for (auto bin : hist) {
3
std::cout << "bin: [" << bin.bin().lower() << ", " << bin.bin().upper() << ") count: " << *bin << std::endl;
4
}
⚝ for (auto bin : hist)
: 使用范围 for 循环遍历直方图的箱迭代器 (Bin Iterator)。bin
是一个迭代器对象,指向直方图的每个箱子。
⚝ bin.bin().lower()
: 获取当前箱子的下边界值。
⚝ bin.bin().upper()
: 获取当前箱子的上边界值。
⚝ *bin
: 解引用迭代器 bin
,获取当前箱子的计数 (Count)。
编译和运行:
使用与 2.2 节相同的编译命令编译并运行程序:
1
g++ histogram_test.cpp -o histogram_test -std=c++14 -lboost_histogram
2
./histogram_test
预期输出:
1
Histogram bin counts:
2
bin: [0, 1) count: 0
3
bin: [1, 2) count: 3
4
bin: [2, 3) count: 2
5
bin: [3, 4) count: 0
6
bin: [4, 5) count: 1
7
bin: [5, 6) count: 0
8
bin: [6, 7) count: 0
9
bin: [7, 8) count: 1
10
bin: [8, 9) count: 0
11
bin: [9, 10) count: 1
输出结果显示了每个箱子的范围和计数。例如,箱子 [1, 2)
的计数为 3,因为我们填充了三个值 1.5。
这个简单的例子展示了 Boost.Histogram 的基本工作流程:创建直方图、填充数据和访问数据。在接下来的章节中,我们将深入探讨直方图的各个方面,包括轴的类型、存储方式、高级操作等。
2.4 创建和填充直方图 (Creating and Filling Histograms)
创建和填充直方图是使用 Boost.Histogram 的核心步骤。本节将详细介绍如何创建不同类型的直方图,并演示多种填充数据的方法。
创建直方图
Boost.Histogram 提供了 bh::make_histogram()
工厂函数来创建直方图。make_histogram()
接受一个或多个轴对象 (Axis Object) 作为参数,用于定义直方图的维度和箱子。
① 创建一维直方图:
1
namespace bh = boost::histogram;
2
3
// 创建一个一维直方图,使用规则轴
4
auto hist1d_regular = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
5
6
// 创建一个一维直方图,使用可变轴
7
auto hist1d_variable = bh::make_histogram(bh::axis::variable({0.0, 1.0, 3.0, 6.0, 10.0}));
8
9
// 创建一个一维直方图,使用整数轴
10
auto hist1d_integer = bh::make_histogram(bh::axis::integer(0, 10));
11
12
// 创建一个一维直方图,使用分类轴
13
auto hist1d_category = bh::make_histogram(bh::axis::category({"A", "B", "C", "D"}));
⚝ 规则轴 (Regular Axis): bh::axis::regular(num_bins, min, max)
,将指定范围 [min, max)
均匀划分为 num_bins
个箱子。
⚝ 可变轴 (Variable Axis): bh::axis::variable({edges})
,使用用户自定义的箱子边界 edges
创建轴。边界值必须是升序排列。
⚝ 整数轴 (Integer Axis): bh::axis::integer(min, max)
,表示整数范围 [min, max)
的轴。每个整数值对应一个箱子。
⚝ 分类轴 (Category Axis): bh::axis::category({categories})
,用于表示离散的类别。每个类别对应一个箱子。
② 创建多维直方图:
要创建多维直方图,只需在 bh::make_histogram()
中传入多个轴对象即可。
1
// 创建一个二维直方图,使用两个规则轴
2
auto hist2d_regular = bh::make_histogram(
3
bh::axis::regular(10, 0.0, 10.0), // x 轴
4
bh::axis::regular(5, -5.0, 5.0) // y 轴
5
);
6
7
// 创建一个三维直方图,混合使用不同类型的轴
8
auto hist3d_mixed = bh::make_histogram(
9
bh::axis::regular(10, 0.0, 10.0), // x 轴
10
bh::axis::variable({-1.0, 0.0, 1.0}), // y 轴
11
bh::axis::category({"X", "Y", "Z"}) // z 轴
12
);
填充直方图
创建直方图后,可以使用 ()
运算符或 fill()
方法填充数据。
① 使用 ()
运算符填充:
对于一维直方图,直接传入一个数值即可:
1
hist1d_regular(3.5);
2
hist1d_variable(4.2);
3
hist1d_integer(5);
4
hist1d_category("B");
对于多维直方图,需要传入与维度数量相匹配的多个数值,按照轴的顺序传入:
1
hist2d_regular(2.5, 1.0); // 填充 (x=2.5, y=1.0)
2
hist3d_mixed(7.8, 0.5, "Y"); // 填充 (x=7.8, y=0.5, z="Y")
② 使用 fill()
方法填充:
fill()
方法与 ()
运算符的功能相同,只是语法略有不同。
1
hist1d_regular.fill(3.5);
2
hist2d_regular.fill(2.5, 1.0);
3
hist3d_mixed.fill(7.8, 0.5, "Y");
③ 填充多个数据点:
()
运算符和 fill()
方法每次只能填充一个数据点。如果需要一次性填充多个数据点,可以使用循环结构。
1
std::vector<double> data1d = {1.1, 2.3, 4.5, 6.7, 8.9, 9.1};
2
for (double value : data1d) {
3
hist1d_regular(value);
4
}
5
6
std::vector<std::tuple<double, double>> data2d = {
7
{1.0, 2.0},
8
{3.0, 4.0},
9
{5.0, 6.0}
10
};
11
for (const auto& point : data2d) {
12
hist2d_regular(std::get<0>(point), std::get<1>(point));
13
}
④ 带权重的填充:
在某些情况下,数据点可能带有权重。Boost.Histogram 支持带权重的填充。您需要使用权重存储 (Weight Storage) 或用户自定义存储 (Custom Storage) 的直方图,并使用 weight()
修饰符来指定权重。关于存储和权重的详细内容将在后续章节介绍。
1
// 创建一个使用权重存储的一维直方图
2
auto hist1d_weight = bh::make_histogram(
3
bh::axis::regular(10, 0.0, 10.0),
4
bh::storage::weight() // 使用权重存储
5
);
6
7
hist1d_weight(3.5); // 默认权重为 1.0
8
hist1d_weight(4.5, bh::weight(2.0)); // 指定权重为 2.0
9
hist1d_weight(5.5, bh::weight(0.5)); // 指定权重为 0.5
总结:
本节介绍了如何使用 bh::make_histogram()
创建不同类型和维度的直方图,以及如何使用 ()
运算符和 fill()
方法填充数据。掌握这些基本操作是使用 Boost.Histogram 的基础。在下一节,我们将学习如何访问直方图中存储的数据。
2.5 访问直方图数据 (Accessing Histogram Data)
填充数据后,我们需要访问直方图中的数据进行分析和处理。Boost.Histogram 提供了多种方式来访问直方图数据,包括箱迭代器 (Bin Iterator)、索引访问 (Index Access) 和点查找 (Point Lookup)。
① 箱迭代器 (Bin Iterator):
箱迭代器是最常用也是最灵活的访问直方图数据的方式。通过箱迭代器,您可以遍历直方图的所有箱子,并获取每个箱子的信息,例如箱子的边界和计数。
1
namespace bh = boost::histogram;
2
3
auto hist = bh::make_histogram(bh::axis::regular(5, 0.0, 5.0));
4
hist(0.5);
5
hist(1.5);
6
hist(1.5);
7
hist(2.5);
8
hist(3.5);
9
hist(4.5);
10
hist(4.5);
11
hist(4.5);
12
13
std::cout << "Using bin iterator:" << std::endl;
14
for (auto bin : hist) {
15
std::cout << "bin: [" << bin.bin().lower() << ", " << bin.bin().upper() << ") count: " << *bin << std::endl;
16
}
⚝ for (auto bin : hist)
: 范围 for 循环遍历直方图 hist
的箱迭代器。
⚝ bin.bin().lower()
: 获取当前箱子的下边界。
⚝ bin.bin().upper()
: 获取当前箱子的上边界。
⚝ *bin
: 解引用迭代器 bin
,获取当前箱子的计数。
对于多维直方图,箱迭代器会遍历所有维度的箱子组合。
1
auto hist2d = bh::make_histogram(
2
bh::axis::regular(2, 0.0, 2.0),
3
bh::axis::regular(2, 0.0, 2.0)
4
);
5
hist2d(0.5, 0.5);
6
hist2d(1.5, 1.5);
7
8
std::cout << "\nUsing bin iterator for 2D histogram:" << std::endl;
9
for (auto bin : hist2d) {
10
std::cout << "bin (x): [" << bin.bin(0).lower() << ", " << bin.bin(0).upper() << ") "
11
<< "bin (y): [" << bin.bin(1).lower() << ", " << bin.bin(1).upper() << ") "
12
<< "count: " << *bin << std::endl;
13
}
⚝ bin.bin(0)
: 获取第一个轴 (x 轴) 的箱子对象。
⚝ bin.bin(1)
: 获取第二个轴 (y 轴) 的箱子对象。
⚝ bin.bin(axis_index)
: 对于 N 维直方图,可以使用 bin.bin(axis_index)
获取指定轴索引 axis_index
的箱子对象。
② 索引访问 (Index Access):
您可以使用箱子的索引来直接访问直方图的计数。每个轴的箱子都有一个从 0 开始的索引。
1
std::cout << "\nUsing index access:" << std::endl;
2
for (int i = 0; i < hist.axis().bins(); ++i) {
3
std::cout << "bin index: " << i << " count: " << hist.at(i) << std::endl;
4
}
⚝ hist.axis().bins()
: 获取直方图轴的箱子数量。
⚝ hist.at(index)
: 使用箱子索引 index
访问计数。索引范围从 0 到 hist.axis().bins() - 1
。
对于多维直方图,需要提供多个索引,每个索引对应一个轴。
1
std::cout << "\nUsing index access for 2D histogram:" << std::endl;
2
for (int i = 0; i < hist2d.axis(0).bins(); ++i) {
3
for (int j = 0; j < hist2d.axis(1).bins(); ++j) {
4
std::cout << "bin index (x, y): (" << i << ", " << j << ") count: " << hist2d.at(i, j) << std::endl;
5
}
6
}
⚝ hist2d.axis(0).bins()
: 获取第一个轴 (x 轴) 的箱子数量。
⚝ hist2d.axis(1).bins()
: 获取第二个轴 (y 轴) 的箱子数量。
⚝ hist2d.at(index_x, index_y)
: 使用箱子索引 index_x
和 index_y
访问二维直方图的计数。
③ 点查找 (Point Lookup):
点查找允许您根据给定的数据点值,找到该点所属的箱子的计数。
1
std::cout << "\nUsing point lookup:" << std::endl;
2
std::cout << "count at 1.5: " << hist.at(1.5) << std::endl;
3
std::cout << "count at 4.8: " << hist.at(4.8) << std::endl; // 超出最后一个箱子的上边界,返回 0
⚝ hist.at(value)
: 使用数据点值 value
进行点查找,返回包含该点的箱子的计数。如果 value
超出轴的范围,则返回 0。
对于多维直方图,需要提供多个数据点值。
1
std::cout << "\nUsing point lookup for 2D histogram:" << std::endl;
2
std::cout << "count at (0.5, 0.5): " << hist2d.at(0.5, 0.5) << std::endl;
3
std::cout << "count at (1.8, 1.8): " << hist2d.at(1.8, 1.8) << std::endl; // 超出轴范围,返回 0
④ 获取总计数 (Total Count):
您可以使用 sum()
方法获取直方图的总计数,即所有箱子的计数之和。
1
std::cout << "\nTotal count: " << hist.sum() << std::endl;
2
std::cout << "Total count (2D): " << hist2d.sum() << std::endl;
总结:
本节介绍了访问 Boost.Histogram 数据的三种主要方式:箱迭代器、索引访问和点查找。箱迭代器提供了最灵活的遍历方式,索引访问允许您通过索引快速访问特定箱子的计数,点查找则可以根据数据点值直接获取计数。根据不同的应用场景,您可以选择合适的访问方式来获取直方图数据。掌握这些数据访问方法是进行后续数据分析和处理的关键。
END_OF_CHAPTER
3. chapter 3: 深入理解轴 (Deep Dive into Axes)
3.1 规则轴 (Regular Axis)
规则轴(Regular Axis)是 Boost.Histogram 中最常用和最基础的轴类型之一。它将一个连续的数值范围均匀地分割成多个等宽的箱(bin),非常适合表示均匀分布或近似均匀分布的数据。规则轴以其简洁性和高效性,在数据可视化、统计分析以及性能监控等领域中得到广泛应用。
规则轴的定义与特点
规则轴的核心在于其均匀分割的特性。这意味着在指定的数值范围内,所有箱的宽度都是相等的。定义一个规则轴,通常需要指定以下几个关键参数:
① 箱的数量 (Number of bins):决定了数据范围将被划分成多少个箱子。箱的数量越多,直方图的精度越高,但同时也会增加内存消耗和计算成本。
② 数值范围 (Range):定义了轴的起始值和结束值,数据将在这个范围内被统计和分析。超出范围的数据可以被特殊处理,例如被放入溢出箱(overflow bin)或欠流箱(underflow bin),这将在后续章节中详细介绍。
规则轴的主要特点包括:
⚝ 均匀性:所有箱宽度相等,易于理解和解释。
⚝ 高效性:由于箱宽固定,计算箱索引非常快速。
⚝ 适用性广:适用于各种均匀或近似均匀分布的数据。
创建规则轴
在 Boost.Histogram 中,可以使用 boost::histogram::axis::regular
类来创建规则轴。其构造函数通常接受三个参数:箱的数量,数值范围的起始值,以及数值范围的结束值。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个拥有 10 个箱子,范围从 0.0 到 10.0 的规则轴
8
auto regular_axis = bh::axis::regular(10, 0.0, 10.0, "value");
9
10
// 打印轴的属性
11
std::cout << "轴类型: " << regular_axis.options() << std::endl;
12
std::cout << "箱数: " << regular_axis.bins() << std::endl;
13
std::cout << "下界: " << regular_axis.lower() << std::endl;
14
std::cout << "上界: " << regular_axis.upper() << std::endl;
15
std::cout << "轴标签: " << regular_axis.metadata() << std::endl;
16
17
return 0;
18
}
代码解析:
⚝ bh::axis::regular(10, 0.0, 10.0, "value")
: 这行代码创建了一个规则轴。
▮▮▮▮⚝ 10
:指定了箱的数量为 10。
▮▮▮▮⚝ 0.0
:指定了数值范围的起始值为 0.0。
▮▮▮▮⚝ 10.0
:指定了数值范围的结束值为 10.0。
▮▮▮▮⚝ "value"
:为轴指定了一个标签 "value",这在后续的数据可视化和分析中非常有用。
⚝ regular_axis.options()
: 获取轴的选项,例如是否包含下溢出箱和上溢出箱。
⚝ regular_axis.bins()
: 获取轴的箱数量。
⚝ regular_axis.lower()
: 获取轴的数值范围下界。
⚝ regular_axis.upper()
: 获取轴的数值范围上界。
⚝ regular_axis.metadata()
: 获取轴的元数据,这里是轴的标签。
规则轴的应用场景
规则轴适用于多种场景,尤其是在处理连续型数据时:
① 数据分布可视化:当需要展示数据的分布情况,例如测量值的分布、温度分布等,规则轴可以清晰地展示数据在各个数值区间的频率。
② 性能监控:在性能监控系统中,可以使用规则轴来统计请求的响应时间分布、CPU 使用率分布等,帮助分析系统性能瓶颈。
③ 科学实验数据分析:在物理、化学、生物等科学实验中,规则轴常用于分析实验数据的分布特征,例如粒子能量分布、反应速率分布等。
示例:分析随机数分布
假设我们生成了一组服从正态分布的随机数,并希望使用规则轴来分析其分布情况。
1
#include <boost/histogram.hpp>
2
#include <random>
3
#include <iostream>
4
5
namespace bh = boost::histogram;
6
7
int main() {
8
// 创建一个规则轴,拥有 50 个箱子,范围从 -5.0 到 5.0
9
auto axis = bh::axis::regular(50, -5.0, 5.0, "x");
10
11
// 创建一个一维直方图,使用规则轴
12
auto hist = bh::make_histogram(axis);
13
14
// 创建一个正态分布随机数生成器
15
std::random_device rd;
16
std::mt19937 gen(rd());
17
std::normal_distribution<> distrib(0.0, 2.0); // 均值为 0.0,标准差为 2.0 的正态分布
18
19
// 填充直方图
20
for (int i = 0; i < 10000; ++i) {
21
hist(distrib(gen));
22
}
23
24
// 打印直方图的箱计数 (部分)
25
for (auto bin : hist.axis_ref(0)) {
26
std::cout << "箱区间: [" << bin.lower() << ", " << bin.upper() << "), 计数: " << bin.value() << std::endl;
27
}
28
29
return 0;
30
}
代码解析:
⚝ auto axis = bh::axis::regular(50, -5.0, 5.0, "x");
: 创建了一个拥有 50 个箱子,范围从 -5.0 到 5.0 的规则轴,用于统计 x 轴的数据。
⚝ auto hist = bh::make_histogram(axis);
: 使用规则轴创建了一个一维直方图。
⚝ std::normal_distribution<> distrib(0.0, 2.0);
: 创建了一个均值为 0.0,标准差为 2.0 的正态分布随机数生成器。
⚝ 循环 for (int i = 0; i < 10000; ++i)
: 生成 10000 个随机数并填充到直方图中。
⚝ 循环 for (auto bin : hist.axis_ref(0))
: 遍历直方图的箱子,并打印每个箱子的区间范围和计数。
通过运行这段代码,我们可以观察到生成的直方图近似于正态分布的形状,中间箱子的计数较高,两端箱子的计数较低,这符合正态分布的特性。
规则轴的局限性
尽管规则轴非常实用,但也存在一些局限性:
① 不适用于非均匀分布数据:当数据分布极度不均匀时,使用规则轴可能会导致信息丢失。例如,如果数据主要集中在某个小范围内,而使用等宽的箱子,则大部分箱子可能为空,无法有效展示数据的细节。
② 边界效应:对于某些特定的数据分布,规则轴的箱边界可能会人为地分割数据,导致分析结果出现偏差。
在处理非均匀分布数据或需要更精细控制箱边界的情况下,可以考虑使用其他类型的轴,例如可变轴(variable axis)或对数轴(log axis),这些将在后续章节中介绍。
3.2 可变轴 (Variable Axis)
可变轴(Variable Axis),也称为不规则轴(Irregular Axis),是一种箱宽度可以不相等的轴类型。与规则轴的均匀分割不同,可变轴允许用户自定义箱的边界,从而更灵活地适应各种数据分布,尤其是在处理非均匀分布数据或需要关注特定数据区间时,可变轴显得尤为重要。
可变轴的定义与特点
可变轴的核心在于其自定义箱边界的特性。用户可以根据数据的特点和分析需求,自由地设置每个箱子的起始和结束位置。定义一个可变轴,需要提供一个单调递增的边界值序列。这些边界值定义了各个箱子的范围。如果有 \(n\) 个边界值,则会创建 \(n-1\) 个箱子。
可变轴的主要特点包括:
⚝ 灵活性:箱宽度可变,能够适应各种数据分布。
⚝ 精确性:可以根据数据特点精细划分箱子,捕捉数据细节。
⚝ 适用性广:尤其适用于非均匀分布数据和需要关注特定区间的场景。
创建可变轴
在 Boost.Histogram 中,可以使用 boost::histogram::axis::variable
类来创建可变轴。其构造函数接受一个边界值数组作为参数。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
#include <vector>
4
5
namespace bh = boost::histogram;
6
7
int main() {
8
// 定义箱边界值,注意边界值需要单调递增
9
std::vector<double> bin_edges = {0.0, 1.0, 2.5, 5.0, 10.0};
10
11
// 创建一个可变轴,使用定义的边界值
12
auto variable_axis = bh::axis::variable(bin_edges, "value");
13
14
// 打印轴的属性
15
std::cout << "轴类型: " << variable_axis.options() << std::endl;
16
std::cout << "箱数: " << variable_axis.bins() << std::endl;
17
std::cout << "轴标签: " << variable_axis.metadata() << std::endl;
18
19
// 打印每个箱子的边界
20
for (auto bin : variable_axis) {
21
std::cout << "箱区间: [" << bin.lower() << ", " << bin.upper() << ")" << std::endl;
22
}
23
24
return 0;
25
}
代码解析:
⚝ std::vector<double> bin_edges = {0.0, 1.0, 2.5, 5.0, 10.0};
: 定义了一个 std::vector<double>
类型的变量 bin_edges
,存储了箱子的边界值。这些值必须是单调递增的。在这个例子中,我们定义了 4 个箱子,边界分别为 [0.0, 1.0), [1.0, 2.5), [2.5, 5.0), [5.0, 10.0)。
⚝ bh::axis::variable(bin_edges, "value")
: 使用 bin_edges
创建了一个可变轴,并设置轴标签为 "value"。
⚝ 循环 for (auto bin : variable_axis)
: 遍历可变轴的每个箱子,并打印其下界和上界。
可变轴的应用场景
可变轴在以下场景中尤其有用:
① 非均匀分布数据可视化:当数据分布高度偏斜或在某些区间内数据密度远高于其他区间时,使用可变轴可以更有效地展示数据的分布细节。例如,在收入分布分析中,收入较低的区间可能需要更细的箱子,而收入较高的区间可以使用较宽的箱子。
② 关注特定数据区间:在某些研究中,我们可能只对数据的特定区间感兴趣。例如,在环境监测中,我们可能更关注污染物浓度超过某个阈值的样本数量。可变轴可以让我们在感兴趣的区间设置更细的箱子,提高分析精度。
③ 处理对数尺度数据:对于一些呈指数增长或衰减的数据,例如地震震级、声音强度等,使用对数尺度的可变轴可以更好地展示数据的分布特征。虽然 Boost.Histogram 没有直接提供对数轴,但可以通过自定义可变轴的边界值来实现对数轴的效果。
示例:分析城市人口分布
假设我们想要分析一个国家城市人口的分布情况,人口分布通常是非均匀的,大城市人口远多于小城市。使用可变轴可以更好地展示这种分布特征。
1
#include <boost/histogram.hpp>
2
#include <vector>
3
#include <iostream>
4
5
namespace bh = boost::histogram;
6
7
int main() {
8
// 城市人口数据 (假设,单位:百万)
9
std::vector<double> city_populations = {
10
0.5, 0.8, 1.2, 1.5, 2.0, 2.8, 3.5, 4.2, 5.0, 8.0, 15.0, 25.0
11
};
12
13
// 定义可变轴的边界值,根据人口分布特点设置
14
std::vector<double> population_bins = {0.0, 1.0, 2.0, 5.0, 10.0, 30.0};
15
auto axis = bh::axis::variable(population_bins, "Population (Millions)");
16
17
// 创建一维直方图
18
auto hist = bh::make_histogram(axis);
19
20
// 填充直方图
21
for (double population : city_populations) {
22
hist(population);
23
}
24
25
// 打印直方图结果
26
for (auto bin : hist.axis_ref(0)) {
27
std::cout << "人口区间: [" << bin.lower() << ", " << bin.upper() << "), 城市数量: " << bin.value() << std::endl;
28
}
29
30
return 0;
31
}
代码解析:
⚝ std::vector<double> city_populations = { ... };
: 模拟了一组城市人口数据。
⚝ std::vector<double> population_bins = {0.0, 1.0, 2.0, 5.0, 10.0, 30.0};
: 定义了可变轴的边界值。注意,箱子的宽度是不等的,例如 [0, 1), [1, 2), [2, 5), [5, 10), [10, 30)。 这样设置可以更细致地展示小城市和中等城市的人口分布,同时也能涵盖大城市的人口范围。
⚝ 循环 for (double population : city_populations)
: 遍历城市人口数据,并填充到直方图中。
⚝ 循环 for (auto bin : hist.axis_ref(0))
: 打印每个人口区间的城市数量。
通过使用可变轴,我们可以更清晰地看到不同人口规模的城市数量分布,这对于理解城市发展和人口结构非常有帮助。
可变轴的注意事项
使用可变轴时,需要注意以下几点:
① 边界值单调递增: 边界值必须是单调递增的,否则会导致未定义的行为。
② 边界值选择: 合理选择边界值非常重要,需要根据数据的分布特点和分析目标来确定。边界值设置不当可能会导致信息丢失或误导性结果。
③ 箱宽解释: 由于箱宽度不相等,在解释直方图时需要注意箱宽的影响。通常需要结合箱宽度来理解频率或密度信息。
3.3 整数轴 (Integer Axis)
整数轴(Integer Axis)是专门用于处理整数数据的轴类型。它针对整数值进行了优化,在表示离散整数数据时更加高效和直观。与规则轴和可变轴处理连续数值范围不同,整数轴直接对应于整数值,每个箱子代表一个或多个连续的整数值。
整数轴的定义与特点
整数轴的核心在于其离散整数的特性。它将一个整数范围分割成多个箱子,每个箱子覆盖一个或多个连续的整数值。定义一个整数轴,通常需要指定以下参数:
① 整数范围 (Integer Range): 定义了轴的起始整数值和结束整数值。
② 箱的类型 (Binning Strategy): 可以选择不同的箱类型,例如每个箱子代表一个整数值,或者多个整数值。Boost.Histogram 的整数轴默认每个箱子代表一个整数值。
整数轴的主要特点包括:
⚝ 精确性:精确表示整数数据,避免浮点数精度问题。
⚝ 高效性:针对整数数据优化,索引计算快速。
⚝ 直观性:箱子直接对应整数值,易于理解和解释。
⚝ 适用性:适用于计数、ID、索引等离散整数数据。
创建整数轴
在 Boost.Histogram 中,可以使用 boost::histogram::axis::integer
类来创建整数轴。其构造函数通常接受两个参数:整数范围的起始值和结束值。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个整数轴,范围从 0 到 10 (包含 0 和 10)
8
auto integer_axis = bh::axis::integer(0, 10, "index");
9
10
// 打印轴的属性
11
std::cout << "轴类型: " << integer_axis.options() << std::endl;
12
std::cout << "箱数: " << integer_axis.bins() << std::endl;
13
std::cout << "下界: " << integer_axis.lower() << std::endl;
14
std::cout << "上界: " << integer_axis.upper() << std::endl;
15
std::cout << "轴标签: " << integer_axis.metadata() << std::endl;
16
17
// 打印每个箱子的整数值
18
for (auto bin : integer_axis) {
19
std::cout << "箱值: " << bin.value() << std::endl; // 整数轴的 bin 直接返回整数值
20
}
21
22
return 0;
23
}
代码解析:
⚝ bh::axis::integer(0, 10, "index")
: 创建了一个整数轴,范围从 0 到 10。注意,整数轴的范围是闭区间,即包含起始值和结束值。因此,这个轴会创建 11 个箱子,分别对应整数 0, 1, 2, ..., 10。
⚝ integer_axis.options()
、integer_axis.bins()
、integer_axis.lower()
、integer_axis.upper()
、integer_axis.metadata()
: 与规则轴类似,用于获取轴的属性。
⚝ 循环 for (auto bin : integer_axis)
: 遍历整数轴的每个箱子。
⚝ bin.value()
: 对于整数轴,bin.value()
直接返回箱子代表的整数值。
整数轴的应用场景
整数轴非常适合处理各种离散整数数据:
① 计数统计:例如,统计不同年龄段的人数、不同商品购买次数、不同错误代码出现的频率等。
② 索引或 ID 分布:分析用户 ID 分布、产品 ID 分布、事件类型 ID 分布等。
③ 离散事件模拟:在离散事件模拟中,可以使用整数轴来统计事件发生的次数或频率。
④ 图像处理:在图像处理中,像素的索引通常是整数,可以使用整数轴来分析像素值的分布。
示例:统计骰子点数分布
假设我们模拟掷骰子 10000 次,并使用整数轴来统计每个点数出现的次数。
1
#include <boost/histogram.hpp>
2
#include <random>
3
#include <iostream>
4
5
namespace bh = boost::histogram;
6
7
int main() {
8
// 创建一个整数轴,范围从 1 到 6 (骰子点数)
9
auto axis = bh::axis::integer(1, 6, "dice_value");
10
11
// 创建一维直方图
12
auto hist = bh::make_histogram(axis);
13
14
// 创建一个均匀分布随机数生成器,模拟骰子
15
std::random_device rd;
16
std::mt19937 gen(rd());
17
std::uniform_int_distribution<> distrib(1, 6); // 均匀分布在 [1, 6] 之间
18
19
// 模拟掷骰子 10000 次并填充直方图
20
for (int i = 0; i < 10000; ++i) {
21
hist(distrib(gen));
22
}
23
24
// 打印直方图结果
25
for (auto bin : hist.axis_ref(0)) {
26
std::cout << "点数: " << bin.value() << ", 次数: " << bin.count() << std::endl;
27
}
28
29
return 0;
30
}
代码解析:
⚝ auto axis = bh::axis::integer(1, 6, "dice_value");
: 创建了一个整数轴,范围从 1 到 6,对应骰子的 6 个点数。
⚝ std::uniform_int_distribution<> distrib(1, 6);
: 创建了一个均匀分布的整数随机数生成器,模拟骰子的随机性。
⚝ 循环 for (int i = 0; i < 10000; ++i)
: 模拟掷骰子 10000 次,并将每次的点数填充到直方图中。
⚝ 循环 for (auto bin : hist.axis_ref(0))
: 遍历整数轴的每个箱子,并打印点数和出现的次数。
运行这段代码,我们可以看到每个点数出现的次数大致相等,符合均匀分布的预期。整数轴在这种离散整数数据的统计分析中非常方便和直观。
整数轴的扩展:步长 (Step)
整数轴还支持设置步长(step),允许每个箱子代表多个连续的整数值。例如,可以设置步长为 2,则箱子会覆盖 [0, 1], [2, 3], [4, 5] 等整数范围。虽然 Boost.Histogram 的 axis::integer
类本身没有直接提供步长选项,但可以通过自定义轴或者结合其他轴类型来实现类似的效果。在大多数情况下,默认的步长为 1 的整数轴已经足够满足需求。
整数轴的局限性
整数轴主要适用于离散整数数据。如果数据是连续的或非整数的,则不适合使用整数轴。在这种情况下,应该选择规则轴或可变轴等更合适的轴类型。
3.4 分类轴 (Category Axis)
分类轴(Category Axis),也称为标签轴(Label Axis)或枚举轴(Enum Axis),是一种用于处理非数值型、离散分类数据的轴类型。与之前介绍的数值轴不同,分类轴的箱子不代表数值范围,而是代表不同的类别或标签。分类轴非常适合处理诸如产品类型、国家、颜色、状态等分类数据。
分类轴的定义与特点
分类轴的核心在于其类别标签的特性。每个箱子都与一个唯一的类别标签关联,数据根据其所属的类别被分配到相应的箱子中。定义一个分类轴,需要提供一个类别标签列表。
分类轴的主要特点包括:
⚝ 非数值性:处理非数值型分类数据。
⚝ 标签化:每个箱子都有明确的类别标签,易于理解和解释。
⚝ 离散性:适用于离散的分类数据。
⚝ 适用性:适用于产品分类、地理区域、状态类型等分类数据的统计和分析。
创建分类轴
在 Boost.Histogram 中,可以使用 boost::histogram::axis::category
类来创建分类轴。其构造函数接受一个类别标签列表作为参数。类别标签可以是字符串、枚举类型或其他可以转换为字符串的类型。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
#include <vector>
4
#include <string>
5
6
namespace bh = boost::histogram;
7
8
int main() {
9
// 定义类别标签列表
10
std::vector<std::string> categories = {"苹果", "香蕉", "橙子", "葡萄"};
11
12
// 创建一个分类轴,使用定义的类别标签
13
auto category_axis = bh::axis::category(categories, "水果类型");
14
15
// 打印轴的属性
16
std::cout << "轴类型: " << category_axis.options() << std::endl;
17
std::cout << "箱数: " << category_axis.bins() << std::endl;
18
std::cout << "轴标签: " << category_axis.metadata() << std::endl;
19
20
// 打印每个箱子的类别标签
21
for (auto bin : category_axis) {
22
std::cout << "类别: " << bin.value() << std::endl; // 分类轴的 bin 直接返回类别标签
23
}
24
25
return 0;
26
}
代码解析:
⚝ std::vector<std::string> categories = {"苹果", "香蕉", "橙子", "葡萄"};
: 定义了一个 std::vector<std::string>
类型的变量 categories
,存储了类别标签列表。
⚝ bh::axis::category(categories, "水果类型")
: 使用 categories
创建了一个分类轴,并设置轴标签为 "水果类型"。
⚝ 循环 for (auto bin : category_axis)
: 遍历分类轴的每个箱子。
⚝ bin.value()
: 对于分类轴,bin.value()
直接返回箱子对应的类别标签。
分类轴的应用场景
分类轴广泛应用于各种需要处理分类数据的场景:
① 产品销售统计:统计不同产品类别的销售额、销量等。
② 用户行为分析:分析用户在不同平台、不同设备上的行为分布。
③ 地理区域分析:统计不同国家、不同城市的数据指标。
④ 问卷调查分析:分析不同选项的选择频率。
⑤ 日志分析:统计不同类型的日志事件数量。
示例:统计不同水果的销量
假设我们有一组水果销售数据,包含水果名称和销量,我们希望使用分类轴来统计每种水果的销量。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
#include <vector>
4
#include <string>
5
#include <map>
6
7
namespace bh = boost::histogram;
8
9
int main() {
10
// 水果销量数据 (水果名称 -> 销量)
11
std::map<std::string, int> fruit_sales = {
12
{"苹果", 150},
13
{"香蕉", 200},
14
{"橙子", 120},
15
{"葡萄", 180},
16
{"苹果", 50}, // 再次销售苹果
17
{"香蕉", 80} // 再次销售香蕉
18
};
19
20
// 定义分类轴的类别标签
21
std::vector<std::string> categories = {"苹果", "香蕉", "橙子", "葡萄"};
22
auto axis = bh::axis::category(categories, "水果类型");
23
24
// 创建一维直方图
25
auto hist = bh::make_histogram(axis);
26
27
// 填充直方图
28
for (const auto& pair : fruit_sales) {
29
hist(pair.first, bh::weight(pair.second)); // 使用权重累加销量
30
}
31
32
// 打印直方图结果
33
for (auto bin : hist.axis_ref(0)) {
34
std::cout << "水果: " << bin.value() << ", 销量: " << bin.value() << " (计数: " << bin.count() << ")" << std::endl; // bin.value() 返回类别标签, bin.count() 返回计数,这里计数不直接代表销量,需要使用权重累加器
35
}
36
37
// 使用权重累加器来获取销量
38
for (auto bin : hist.axis_ref(0)) {
39
std::cout << "水果: " << bin.value() << ", 销量: " << bin.weight() << std::endl; // bin.weight() 返回权重累加器的值,即销量
40
}
41
42
43
return 0;
44
}
代码解析:
⚝ std::map<std::string, int> fruit_sales = { ... };
: 模拟了水果销售数据,使用 std::map
存储水果名称和销量。
⚝ std::vector<std::string> categories = {"苹果", "香蕉", "橙子", "葡萄"};
: 定义了分类轴的类别标签,与水果名称对应。
⚝ bh::axis::category(categories, "水果类型")
: 创建分类轴。
⚝ 循环 for (const auto& pair : fruit_sales)
: 遍历水果销售数据。
⚝ hist(pair.first, bh::weight(pair.second));
: 填充直方图。这里 pair.first
是水果名称(类别标签),pair.second
是销量。由于我们需要统计销量,而不是简单的计数,所以使用了 bh::weight(pair.second)
来指定权重。这样,每次填充时,对应类别的箱子的权重累加器会增加相应的销量值。
⚝ 循环 for (auto bin : hist.axis_ref(0))
: 遍历分类轴的每个箱子。
⚝ bin.weight()
: 使用 bin.weight()
获取权重累加器的值,即每种水果的总销量。
通过使用分类轴和权重累加器,我们可以方便地统计和分析不同类别的销售数据。
分类轴的注意事项
① 类别标签唯一性: 分类轴的类别标签应该是唯一的,重复的标签会导致数据分配到同一个箱子。
② 标签类型: 类别标签可以是字符串、枚举类型或其他可以转换为字符串的类型。选择合适的标签类型可以提高代码的可读性和维护性。
③ 数据类型匹配: 填充直方图时,提供的数据类型需要与分类轴的类别标签类型匹配。
3.5 自定义轴 (Custom Axis)
自定义轴(Custom Axis)为 Boost.Histogram 提供了极大的灵活性。当内置的轴类型(规则轴、可变轴、整数轴、分类轴)无法满足特定需求时,用户可以创建完全自定义的轴类型。自定义轴允许用户定义箱子的边界、索引计算方式、甚至轴的行为,从而应对各种复杂的数据分析场景。
自定义轴的定义与实现
要创建一个自定义轴,需要继承 boost::histogram::axis::axis
基类,并实现一些必要的成员函数。最核心的函数是 index(value)
,它负责将输入值映射到箱子的索引。此外,还需要定义轴的箱类型、选项、元数据等。
一个基本的自定义轴通常需要实现以下几个部分:
① 轴的选项 (Options): 定义轴的选项类型,通常使用 struct options_type
。可以继承自 boost::histogram::axis::options_base
,并添加自定义选项。
② 轴的箱类型 (Bin Type): 定义轴的箱类型,通常使用 using bin_type = ...
。箱类型需要提供 lower()
、upper()
和 value()
等方法来获取箱的边界和值。
③ 构造函数 (Constructor): 实现轴的构造函数,初始化轴的状态,例如箱边界、选项、元数据等。
④ index(value)
函数: 实现核心的 index(value)
函数,将输入值映射到箱子的索引。索引值必须是整数,范围从 0 到 bins() - 1
。对于超出范围的值,可以返回特殊索引值,例如 boost::histogram::axis::uoflow
(上溢出) 或 boost::histogram::axis::overflow
(下溢出)。
⑤ bins()
函数: 返回轴的箱数量。
⑥ metadata()
函数 和 metadata(value)
函数: 获取和设置轴的元数据,例如轴的标签。
⑦ 其他可选函数: 例如 value(index)
函数,用于根据箱索引获取箱的代表值;bin(index)
函数,返回箱对象;等等。
自定义轴的示例:对数轴 (Log Axis)
虽然 Boost.Histogram 没有直接提供对数轴,但我们可以通过自定义轴来实现一个简单的对数轴。对数轴的箱子在对数尺度上是均匀的,适用于展示呈指数分布的数据。
1
#include <boost/histogram.hpp>
2
#include <cmath>
3
#include <iostream>
4
#include <vector>
5
#include <limits>
6
7
namespace bh = boost::histogram;
8
9
// 自定义对数轴
10
struct log_axis : public bh::axis::axis<log_axis> {
11
struct options_type : bh::axis::options_base {};
12
using bin_type = bh::axis::bin::interval_bin; // 使用 interval_bin 作为箱类型
13
14
// 构造函数
15
log_axis(int nbins, double base, double min_val, double max_val, std::string label = "")
16
: nbins_(nbins), base_(base), min_val_(min_val), max_val_(max_val), label_(std::move(label)) {
17
if (min_val_ <= 0 || max_val_ <= 0 || min_val_ >= max_val_ || base_ <= 1) {
18
throw std::invalid_argument("Invalid log axis parameters");
19
}
20
log_min_ = std::log(min_val_) / std::log(base_);
21
log_max_ = std::log(max_val_) / std::log(base_);
22
bin_width_ = (log_max_ - log_min_) / nbins_;
23
}
24
25
// 索引函数:将值映射到箱索引
26
[[nodiscard]] auto index(double val) const noexcept {
27
if (val < min_val_) return bh::axis::uoflow;
28
if (val >= max_val_) return bh::axis::overflow;
29
if (val <= 0) return bh::axis::uoflow; // 对数轴不接受非正值
30
31
double log_val = std::log(val) / std::log(base_);
32
int bin_index = static_cast<int>((log_val - log_min_) / bin_width_);
33
if (bin_index < 0) bin_index = 0; // 确保索引在有效范围内
34
if (bin_index >= nbins_) bin_index = nbins_ - 1;
35
return bin_index;
36
}
37
38
[[nodiscard]] auto bins() const noexcept { return nbins_; }
39
[[nodiscard]] auto metadata() const noexcept { return label_; }
40
[[nodiscard]] auto metadata(std::string label) noexcept { label_ = std::move(label); }
41
[[nodiscard]] auto options() const noexcept { return options_type{}; }
42
43
// 箱对象
44
[[nodiscard]] auto bin(int index) const noexcept {
45
if (index < 0) return bin_type(std::numeric_limits<double>::lowest(), min_val_); // 下溢出箱
46
if (index >= nbins_) return bin_type(max_val_, std::numeric_limits<double>::max()); // 上溢出箱
47
double log_lower = log_min_ + index * bin_width_;
48
double log_upper = log_lower + bin_width_;
49
return bin_type(std::pow(base_, log_lower), std::pow(base_, log_upper));
50
}
51
52
53
private:
54
int nbins_;
55
double base_;
56
double min_val_;
57
double max_val_;
58
std::string label_;
59
double log_min_;
60
double log_max_;
61
double bin_width_;
62
};
63
64
int main() {
65
// 创建一个自定义对数轴
66
auto axis = log_axis(10, 10.0, 1.0, 100.0, "log_value");
67
68
// 创建一维直方图
69
auto hist = bh::make_histogram(axis);
70
71
// 填充一些数据
72
for (double val : {0.5, 1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0, 150.0}) {
73
hist(val);
74
}
75
76
// 打印直方图结果
77
for (auto bin : hist.axis_ref(0)) {
78
std::cout << "箱区间: [" << bin.lower() << ", " << bin.upper() << "), 计数: " << bin.value() << std::endl;
79
}
80
81
return 0;
82
}
代码解析:
⚝ struct log_axis : public bh::axis::axis<log_axis> { ... };
: 定义了自定义轴 log_axis
,继承自 bh::axis::axis<log_axis>
。
⚝ struct options_type : bh::axis::options_base {};
: 定义了轴的选项类型,这里没有添加额外的选项,直接继承自 options_base
。
⚝ using bin_type = bh::axis::bin::interval_bin;
: 指定箱类型为 interval_bin
,表示区间箱。
⚝ log_axis(int nbins, double base, double min_val, double max_val, std::string label = "")
: 构造函数,接受箱数量、对数底数、数值范围、轴标签等参数,并计算对数范围和箱宽度。
⚝ index(double val)
: 核心的索引函数。
▮▮▮▮⚝ 首先处理下溢出、上溢出和非正值的情况。
▮▮▮▮⚝ 将输入值 val
取对数,并计算其在对数尺度上的位置。
▮▮▮▮⚝ 根据箱宽度计算箱索引。
▮▮▮▮⚝ 返回箱索引,或溢出/下溢出索引。
⚝ bins()
, metadata()
, metadata(std::string label)
, options()
: 标准的轴接口函数,返回箱数量、元数据和选项。
⚝ bin(int index)
: 返回箱对象。根据箱索引计算箱的对数范围,并将其转换回原始数值范围。
这个示例展示了如何创建一个简单的对数轴。用户可以根据自己的需求,自定义更复杂的轴类型,例如多峰轴、周期轴等。
自定义轴的应用场景
自定义轴适用于各种需要特殊轴行为的场景:
① 对数尺度数据:如上例所示,创建对数轴来处理对数分布的数据。
② 非线性尺度数据:例如平方根尺度、指数尺度等。
③ 周期性数据:例如角度轴,可以处理周期性数据,如角度分布。
④ 多峰分布数据:自定义轴可以根据数据的峰值位置设置箱边界,更好地展示多峰分布的特征。
⑤ 特殊数据类型:处理非数值型但又不是简单分类的数据,例如时间序列数据、地理位置数据等。
自定义轴的注意事项
① 正确实现 index(value)
函数: index(value)
函数是自定义轴的核心,必须正确实现,确保将输入值正确映射到箱索引。
② 箱类型选择: 根据轴的特性选择合适的箱类型。Boost.Histogram 提供了多种内置箱类型,例如 interval_bin
、integer_bin
、category_bin
等,也可以自定义箱类型。
③ 性能考虑: 自定义轴的 index(value)
函数可能会被频繁调用,需要考虑性能优化,避免复杂的计算影响直方图的填充速度。
④ 测试与验证: 自定义轴需要充分的测试和验证,确保其行为符合预期,并且能够正确处理各种输入数据。
3.6 轴的配置与选项 (Axis Configuration and Options)
Boost.Histogram 的轴提供了丰富的配置选项,允许用户根据具体需求定制轴的行为。这些配置选项主要通过轴的构造函数参数和选项类来设置。合理的配置轴选项可以提高直方图的精度、效率和适用性。
常用轴选项
不同的轴类型有不同的配置选项,但一些通用的选项适用于多种轴类型,例如规则轴、可变轴和整数轴。
① 下溢出箱 (Underflow Bin) 和 上溢出箱 (Overflow Bin):
⚝ 作用: 用于统计超出轴数值范围的数据。下溢出箱统计小于轴最小边界值的数据,上溢出箱统计大于等于轴最大边界值的数据(对于规则轴和可变轴)或大于轴最大整数值的数据(对于整数轴)。
⚝ 配置: 可以通过轴的选项来启用或禁用下溢出箱和上溢出箱。默认情况下,规则轴和可变轴启用溢出箱,整数轴默认禁用溢出箱。
⚝ 选项类: boost::histogram::axis::option::underflow
和 boost::histogram::axis::option::overflow
。
② 增长 (Growth):
⚝ 作用: 允许轴在填充数据时自动增长箱的数量和范围。当数据超出当前轴的范围时,轴会自动扩展以容纳新数据。
⚝ 配置: 可以通过轴的选项来启用增长。增长选项通常只适用于一维直方图的最后一个轴。
⚝ 选项类: boost::histogram::axis::option::growth
。
③ 循环 (Circular):
⚝ 作用: 将轴设置为循环轴,例如角度轴。循环轴的首尾相连,超出轴最大值的数值会循环回到轴的起始位置。
⚝ 配置: 可以通过轴的选项来启用循环。
⚝ 选项类: boost::histogram::axis::option::circular
。
④ metadata (元数据):
⚝ 作用: 为轴添加元数据,例如轴的标签、单位、描述等。元数据可以是字符串或其他类型,用于在后续的数据分析和可视化中提供额外的信息。
⚝ 配置: 可以通过轴的构造函数参数或 metadata()
函数来设置元数据。
配置选项的示例
示例 1:启用溢出箱和设置轴标签
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个规则轴,启用下溢出箱和上溢出箱,并设置轴标签
8
auto axis = bh::axis::regular(
9
10, 0.0, 10.0, "value",
10
bh::axis::option::underflow | bh::axis::option::overflow // 显式启用溢出箱 (默认已启用,这里仅为示例)
11
);
12
13
// 创建一维直方图
14
auto hist = bh::make_histogram(axis);
15
16
// 填充一些数据,包含超出范围的值
17
for (double val : {-1.0, 0.5, 5.0, 10.0, 11.0}) {
18
hist(val);
19
}
20
21
// 打印溢出箱和正常箱的计数
22
std::cout << "下溢出箱计数: " << hist.at(-1) << std::endl; // 下溢出箱索引为 -1
23
std::cout << "正常箱计数 (箱 5): " << hist.at(5) << std::endl; // 索引 5 对应范围 [5, 6)
24
std::cout << "上溢出箱计数: " << hist.at(10) << std::endl; // 上溢出箱索引为 10 (箱数为 10 的规则轴)
25
26
return 0;
27
}
代码解析:
⚝ bh::axis::regular(10, 0.0, 10.0, "value", bh::axis::option::underflow | bh::axis::option::overflow)
: 创建规则轴时,通过最后一个参数 bh::axis::option::underflow | bh::axis::option::overflow
显式启用下溢出箱和上溢出箱。虽然规则轴默认已启用溢出箱,但这里为了演示如何使用选项。
⚝ hist.at(-1)
: 访问下溢出箱的计数。下溢出箱的索引通常为 -1。
⚝ hist.at(5)
: 访问索引为 5 的正常箱的计数。
⚝ hist.at(10)
: 访问上溢出箱的计数。对于箱数为 \(N\) 的轴,上溢出箱的索引通常为 \(N\)。
示例 2:使用增长轴
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个规则轴,启用增长
8
auto axis = bh::axis::regular(
9
10, 0.0, 10.0, "value",
10
bh::axis::option::growth // 启用增长
11
);
12
13
// 创建一维直方图
14
auto hist = bh::make_histogram(axis);
15
16
// 填充超出初始范围的数据
17
for (double val : {1.0, 5.0, 15.0, 20.0}) {
18
hist(val);
19
}
20
21
// 打印轴的箱数和上界,观察是否增长
22
std::cout << "增长后的箱数: " << hist.axis_ref(0).bins() << std::endl;
23
std::cout << "增长后的上界: " << hist.axis_ref(0).upper() << std::endl;
24
25
// 打印直方图结果 (部分)
26
for (auto bin : hist.axis_ref(0)) {
27
std::cout << "箱区间: [" << bin.lower() << ", " << bin.upper() << "), 计数: " << bin.value() << std::endl;
28
}
29
30
31
return 0;
32
}
代码解析:
⚝ bh::axis::regular(10, 0.0, 10.0, "value", bh::axis::option::growth)
: 创建规则轴时,通过 bh::axis::option::growth
启用增长选项。
⚝ 填充数据 15.0
和 20.0
,这些值超出了初始范围 [0.0, 10.0)。
⚝ hist.axis_ref(0).bins()
和 hist.axis_ref(0).upper()
: 打印增长后的箱数和上界。可以看到,轴的箱数和范围已经自动扩展,以容纳超出范围的数据。
轴选项的组合
轴的选项可以使用位运算符 |
进行组合,例如同时启用下溢出箱和上溢出箱:
1
bh::axis::option::underflow | bh::axis::option::overflow
不同的选项组合可以实现更灵活的轴配置,满足各种复杂的数据分析需求. 用户可以根据具体的应用场景,选择合适的轴类型和配置选项,以获得最佳的分析效果。
END_OF_CHAPTER
4. chapter 4: 存储与累加器 (Storage and Accumulators)
4.1 存储类型 (Storage Types)
在 Boost.Histogram 库中,存储(Storage) 负责管理直方图的 计数(Count) 或 权重(Weight) 数据。存储的选择直接影响直方图的内存占用、性能以及可以执行的操作类型。Boost.Histogram 提供了多种内置的存储类型,以满足不同的应用需求。本节将详细介绍几种常用的存储类型。
4.1.1 整数存储 (Integer Storage)
整数存储(Integer Storage) 是最基本的存储类型,它使用整数来存储每个 箱(Bin) 的计数。这是默认的存储类型,适用于大多数简单的直方图应用场景,特别是当您只需要统计事件发生的频次时。
特点:
① 内存效率高:整数存储通常占用较少的内存,尤其是在计数范围不是非常大的情况下。
② 性能良好:整数运算通常比浮点运算更快,因此整数存储在填充和访问速度方面具有优势。
③ 功能限制:整数存储只能存储整数计数,无法处理带权重的直方图或需要更高精度累加的场景。
适用场景:
① 简单的计数统计,例如统计网站访问次数、程序执行次数等。
② 数据量较大,但对内存占用有较高要求的场景。
③ 性能敏感的应用,需要快速的填充和访问速度。
代码示例:
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个使用整数存储的一维直方图,轴的范围是 [0, 10),分为 10 个箱
8
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
9
10
// 填充一些数据
11
h(1.5);
12
h(2.5);
13
h(2.5);
14
h(3.5);
15
16
// 访问直方图数据并打印
17
for (auto bin : h) {
18
std::cout << "bin[" << bin.idx() << "] count = " << *bin << std::endl;
19
}
20
21
return 0;
22
}
代码解释:
① bh::make_histogram(bh::axis::regular(10, 0.0, 10.0))
创建了一个一维直方图,使用了默认的整数存储类型。
② h(1.5); h(2.5); h(2.5); h(3.5);
使用 ()
运算符填充直方图,每次调用都会将对应箱的计数加一。
③ for (auto bin : h)
遍历直方图的每个箱。
④ *bin
解引用箱迭代器,返回当前箱的计数值。
输出结果:
1
bin[0] count = 0
2
bin[1] count = 1
3
bin[2] count = 2
4
bin[3] count = 1
5
bin[4] count = 0
6
bin[5] count = 0
7
bin[6] count = 0
8
bin[7] count = 0
9
bin[8] count = 0
10
bin[9] count = 0
从输出结果可以看出,箱 1、2、3 的计数分别为 1、2、1,与填充的数据相符。默认的整数存储类型适用于简单的计数场景,且易于理解和使用。
4.1.2 无限制存储 (Unlimited Storage)
无限制存储(Unlimited Storage) 使用可以动态增长的整数类型来存储计数,例如 std::vector<std::size_t>
。这种存储类型可以避免整数溢出的问题,适用于计数可能非常大的场景。
特点:
① 避免溢出:可以存储任意大的整数计数,不会发生溢出。
② 动态增长:内存占用会随着计数的增长而增加,但不会预先分配过多的内存。
③ 性能略低:相比于固定大小的整数存储,无限制存储在某些情况下性能会稍慢,因为可能涉及到动态内存分配和管理。
适用场景:
① 计数可能非常大,超出固定大小整数类型范围的场景。
② 需要保证计数绝对准确,避免溢出的场景。
③ 对内存占用不是非常敏感,但需要处理大规模数据的场景。
代码示例:
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个使用无限制存储的一维直方图
8
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0), bh::storage::unlimited());
9
10
// 填充大量数据,模拟计数增长
11
for (std::size_t i = 0; i < 1000000; ++i) {
12
h(5.5);
13
}
14
15
// 访问直方图数据并打印箱 5 的计数
16
for (auto bin : h) {
17
if (bin.idx() == 5) {
18
std::cout << "bin[5] count = " << *bin << std::endl;
19
break;
20
}
21
}
22
23
return 0;
24
}
代码解释:
① bh::make_histogram(bh::axis::regular(10, 0.0, 10.0), bh::storage::unlimited())
创建了一个一维直方图,并显式指定使用 bh::storage::unlimited()
无限制存储类型。
② for (std::size_t i = 0; i < 1000000; ++i) { h(5.5); }
循环填充 100 万次数据到箱 5,模拟计数增长。
③ 代码遍历直方图,找到箱 5 并打印其计数。
输出结果:
1
bin[5] count = 1000000
从输出结果可以看出,即使填充了 100 万次数据,箱 5 的计数仍然准确,没有发生溢出。无限制存储类型确保了在大规模计数场景下的数据准确性。
4.1.3 权重存储 (Weight Storage)
权重存储(Weight Storage) 不仅存储计数,还允许为每次填充指定一个 权重(Weight) 值。这在需要对事件进行加权统计的场景中非常有用。权重存储通常使用浮点数来存储累加的权重值,以支持更精确的计算。
特点:
① 支持权重:可以为每次填充指定权重,实现加权统计。
② 更高精度:通常使用浮点数存储权重,可以进行更精确的累加和计算。
③ 更多功能:权重存储可以支持更复杂的统计分析,例如计算加权平均值、加权标准差等。
④ 内存占用增加:相比于整数存储,权重存储通常占用更多的内存,因为需要存储额外的权重信息。
适用场景:
① 需要进行加权统计的场景,例如问卷调查中不同样本的权重不同、物理实验中不同事件的权重不同等。
② 需要更高精度累加的场景,例如金融数据分析、科学计算等。
③ 需要进行更复杂统计分析的场景。
代码示例:
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个使用权重存储的一维直方图
8
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0), bh::storage::weight());
9
10
// 填充带权重的数据
11
h(1.5, bh::weight(2.0)); // 数据 1.5,权重 2.0
12
h(2.5, bh::weight(1.5)); // 数据 2.5,权重 1.5
13
h(2.5, bh::weight(2.5)); // 数据 2.5,权重 2.5
14
h(3.5, bh::weight(1.0)); // 数据 3.5,权重 1.0
15
16
// 访问直方图数据并打印
17
for (auto bin : h) {
18
std::cout << "bin[" << bin.idx() << "] value = " << bin->value()
19
<< ", variance = " << bin->variance() << std::endl;
20
}
21
22
return 0;
23
}
代码解释:
① bh::make_histogram(bh::axis::regular(10, 0.0, 10.0), bh::storage::weight())
创建了一个一维直方图,并显式指定使用 bh::storage::weight()
权重存储类型。
② h(1.5, bh::weight(2.0));
使用 bh::weight(value)
指定填充数据的权重。
③ bin->value()
返回当前箱的权重累加值。
④ bin->variance()
返回当前箱的权重平方和,可以用于计算方差等统计量。
输出结果:
1
bin[0] value = 0, variance = 0
2
bin[1] value = 2, variance = 4
3
bin[2] value = 4, variance = 8.5
4
bin[3] value = 1, variance = 1
5
bin[4] value = 0, variance = 0
6
bin[5] value = 0, variance = 0
7
bin[6] value = 0, variance = 0
8
bin[7] value = 0, variance = 0
9
bin[8] value = 0, variance = 0
10
bin[9] value = 0, variance = 0
从输出结果可以看出,箱 1 的权重累加值为 2.0,箱 2 的权重累加值为 1.5 + 2.5 = 4.0,箱 3 的权重累加值为 1.0,与填充的带权重数据相符。权重存储类型为直方图提供了更强大的统计分析能力。
4.2 累加器 (Accumulators)
累加器(Accumulator) 是 Boost.Histogram 库中用于更新 存储(Storage) 中箱值的对象。当我们调用直方图的 ()
运算符填充数据时,实际上是使用累加器来更新对应箱的存储值。Boost.Histogram 提供了多种内置的累加器,以支持不同的更新策略和统计需求。
4.2.1 默认累加器 (Default Accumulator)
默认累加器(Default Accumulator) 是最简单的累加器,它只是将箱的计数加一。当我们使用默认的整数存储或无限制存储,并且不显式指定累加器时,Boost.Histogram 会使用默认累加器。
特点:
① 简单高效:默认累加器操作简单,性能高效。
② 适用广泛:适用于大多数简单的计数统计场景。
③ 功能有限:只能进行简单的计数加一操作,无法进行更复杂的累加或统计。
使用场景:
① 简单的计数统计,不需要权重或更复杂的累加操作。
② 与整数存储或无限制存储配合使用。
代码示例:
实际上,在 4.1.1 节和 4.1.2 节的代码示例中,我们已经使用了默认累加器,因为我们没有显式指定累加器类型。以下代码再次展示默认累加器的使用:
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个使用默认累加器的一维直方图(默认存储为整数存储)
8
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
9
10
// 使用默认累加器填充数据(实际上是隐式使用)
11
h(1.5); // 默认累加器将箱 1 的计数加一
12
h(2.5); // 默认累加器将箱 2 的计数加一
13
14
// 访问直方图数据并打印
15
for (auto bin : h) {
16
std::cout << "bin[" << bin.idx() << "] count = " << *bin << std::endl;
17
}
18
19
return 0;
20
}
代码解释:
① auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
创建直方图时,没有显式指定存储类型和累加器类型,因此使用了默认的整数存储和默认累加器。
② h(1.5); h(2.5);
填充数据时,隐式使用了默认累加器,将对应箱的计数加一。
输出结果:
1
bin[0] count = 0
2
bin[1] count = 1
3
bin[2] count = 1
4
bin[3] count = 0
5
bin[4] count = 0
6
bin[5] count = 0
7
bin[6] count = 0
8
bin[7] count = 0
9
bin[8] count = 0
10
bin[9] count = 0
默认累加器简单易用,适用于基本的计数统计需求。
4.2.2 权重累加器 (Weight Accumulator)
权重累加器(Weight Accumulator) 用于与 权重存储(Weight Storage) 配合使用,它可以累加权重值,并同时维护权重平方和,用于计算方差等统计量。当我们使用权重存储,并且不显式指定累加器时,Boost.Histogram 会使用权重累加器。
特点:
① 支持权重累加:可以累加权重值,实现加权统计。
② 维护权重平方和:可以用于计算方差等统计量。
③ 功能强大:为权重存储提供了完整的累加和统计功能。
使用场景:
① 需要进行加权统计的场景。
② 需要计算方差等统计量的场景。
③ 与权重存储配合使用。
代码示例:
实际上,在 4.1.3 节的代码示例中,我们已经使用了权重累加器,因为我们使用了权重存储,并且没有显式指定累加器类型。以下代码再次展示权重累加器的使用,并显式指定累加器类型,以加深理解:
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个使用权重存储和权重累加器的一维直方图
8
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0),
9
bh::storage::weight(),
10
bh::accumulator::weight_sum()); // 显式指定权重累加器
11
12
// 使用权重累加器填充带权重的数据
13
h(1.5, bh::weight(2.0));
14
h(2.5, bh::weight(1.5));
15
h(2.5, bh::weight(2.5));
16
h(3.5, bh::weight(1.0));
17
18
// 访问直方图数据并打印
19
for (auto bin : h) {
20
std::cout << "bin[" << bin.idx() << "] value = " << bin->value()
21
<< ", variance = " << bin->variance() << std::endl;
22
}
23
24
return 0;
25
}
代码解释:
① bh::make_histogram(bh::axis::regular(10, 0.0, 10.0), bh::storage::weight(), bh::accumulator::weight_sum())
创建直方图时,显式指定了权重存储 bh::storage::weight()
和权重累加器 bh::accumulator::weight_sum()
。
② h(1.5, bh::weight(2.0));
填充带权重的数据,权重累加器负责累加权重值和权重平方和。
输出结果:
输出结果与 4.1.3 节的示例相同,证明了权重累加器的正确使用。权重累加器是进行加权统计和相关分析的重要工具。
4.2.3 自定义累加器 (Custom Accumulators)
除了内置的累加器,Boost.Histogram 还允许用户创建 自定义累加器(Custom Accumulators),以满足更特殊或更复杂的统计需求。自定义累加器需要用户自己定义累加逻辑和存储结构。
特点:
① 高度灵活:可以根据需求自定义累加逻辑和存储结构。
② 功能强大:可以实现各种复杂的统计功能。
③ 开发成本较高:需要用户编写代码来实现累加器逻辑。
适用场景:
① 需要实现内置累加器无法满足的特殊统计功能的场景。
② 需要高度定制化的累加和存储行为的场景。
③ 高级用户,对 Boost.Histogram 库有深入理解。
创建自定义累加器的步骤:
① 定义累加器结构体或类:需要包含存储累加值的成员变量,以及必要的成员函数,例如构造函数、operator()
累加函数、value()
获取累加值函数等。
② 实现累加逻辑:在 operator()
函数中实现自定义的累加逻辑。
③ 在创建直方图时指定自定义累加器类型:使用 bh::make_histogram
函数的参数指定自定义累加器类型。
代码示例:
以下示例创建一个简单的自定义累加器,用于统计数据的最小值和最大值。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
#include <limits>
4
#include <algorithm>
5
6
namespace bh = boost::histogram;
7
8
// 自定义累加器:MinMaxAccumulator
9
struct MinMaxAccumulator {
10
double min_val = std::numeric_limits<double>::max();
11
double max_val = std::numeric_limits<double>::lowest();
12
13
// 累加函数
14
void operator()(double value) {
15
min_val = std::min(min_val, value);
16
max_val = std::max(max_val, value);
17
}
18
19
// 获取最小值
20
double min() const { return min_val; }
21
22
// 获取最大值
23
double max() const { return max_val; }
24
};
25
26
namespace boost::histogram::accumulator {
27
// 注册自定义累加器,使其可以像内置累加器一样使用
28
template <>
29
struct use_default_traits<MinMaxAccumulator> : std::true_type {};
30
}
31
32
int main() {
33
// 创建一个使用自定义累加器的一维直方图
34
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0),
35
bh::storage::default_storage<MinMaxAccumulator>()); // 使用 default_storage 包装自定义累加器
36
37
// 填充数据
38
h(1.5);
39
h(5.0);
40
h(2.5);
41
h(8.0);
42
h(3.0);
43
44
// 访问直方图数据并打印每个箱的最小值和最大值
45
for (auto bin : h) {
46
auto& acc = bin.value(); // 获取自定义累加器对象
47
std::cout << "bin[" << bin.idx() << "] min = " << acc.min()
48
<< ", max = " << acc.max() << std::endl;
49
}
50
51
return 0;
52
}
代码解释:
① struct MinMaxAccumulator
定义了自定义累加器结构体,包含 min_val
和 max_val
成员变量,以及 operator()
累加函数、min()
和 max()
获取函数。
② operator()(double value)
函数实现了累加逻辑,更新最小值和最大值。
③ namespace boost::histogram::accumulator { ... }
中的代码注册了自定义累加器,使其可以像内置累加器一样使用。
④ bh::make_histogram(..., bh::storage::default_storage<MinMaxAccumulator>())
创建直方图时,使用 bh::storage::default_storage<MinMaxAccumulator>()
包装自定义累加器类型,并作为存储类型传递给 make_histogram
函数。
⑤ auto& acc = bin.value();
获取箱的自定义累加器对象。
⑥ acc.min()
和 acc.max()
获取每个箱的最小值和最大值。
输出结果:
1
bin[0] min = inf, max = -inf
2
bin[1] min = 1.5, max = 1.5
3
bin[2] min = 2.5, max = 3
4
bin[3] min = inf, max = -inf
5
bin[4] min = 5, max = 5
6
bin[5] min = inf, max = -inf
7
bin[6] min = inf, max = -inf
8
bin[7] min = 8, max = 8
9
bin[8] min = inf, max = -inf
10
bin[9] min = inf, max = -inf
从输出结果可以看出,自定义累加器成功统计了每个箱内数据的最小值和最大值。自定义累加器为 Boost.Histogram 提供了极大的灵活性,可以满足各种复杂的统计需求。
4.3 选择合适的存储与累加器 (Choosing the Right Storage and Accumulator)
选择合适的 存储(Storage) 和 累加器(Accumulator) 是使用 Boost.Histogram 库的关键步骤。不同的存储和累加器组合会影响直方图的内存占用、性能、功能和精度。以下是一些选择建议:
根据应用场景选择:
① 简单计数统计:
▮▮▮▮⚝ 存储类型:整数存储(Integer Storage) 或 无限制存储(Unlimited Storage)。
▮▮▮▮⚝ 累加器:默认累加器(Default Accumulator)。
▮▮▮▮⚝ 理由:整数存储内存效率高,性能好;默认累加器简单高效。如果计数可能非常大,选择无限制存储以避免溢出。
② 加权统计:
▮▮▮▮⚝ 存储类型:权重存储(Weight Storage)。
▮▮▮▮⚝ 累加器:权重累加器(Weight Accumulator)。
▮▮▮▮⚝ 理由:权重存储和权重累加器专门用于加权统计,可以精确累加权重值,并计算方差等统计量。
③ 需要更高精度:
▮▮▮▮⚝ 存储类型:权重存储(Weight Storage)。
▮▮▮▮⚝ 累加器:权重累加器(Weight Accumulator) 或 自定义累加器(Custom Accumulators),例如使用更高精度的浮点数类型进行累加。
▮▮▮▮⚝ 理由:权重存储使用浮点数存储权重,可以提供更高的精度。自定义累加器可以进一步定制精度需求。
④ 特殊统计需求:
▮▮▮▮⚝ 存储类型:默认存储(Default Storage) 结合 自定义累加器(Custom Accumulators)。
▮▮▮▮⚝ 累加器:自定义累加器(Custom Accumulators)。
▮▮▮▮⚝ 理由:自定义累加器可以实现各种特殊的统计功能,例如计算均值、中位数、众数、百分位数等。默认存储可以作为自定义累加器的基础存储类型。
根据性能和内存占用选择:
① 内存敏感型应用:
▮▮▮▮⚝ 存储类型:整数存储(Integer Storage)。
▮▮▮▮⚝ 理由:整数存储内存占用最小,尤其是在计数范围较小的情况下。
② 性能敏感型应用:
▮▮▮▮⚝ 存储类型:整数存储(Integer Storage)。
▮▮▮▮⚝ 累加器:默认累加器(Default Accumulator)。
▮▮▮▮⚝ 理由:整数运算通常比浮点运算更快,默认累加器操作简单,性能高效。
③ 大规模数据处理:
▮▮▮▮⚝ 存储类型:无限制存储(Unlimited Storage) 或 权重存储(Weight Storage)。
▮▮▮▮⚝ 理由:无限制存储可以处理计数非常大的情况,权重存储可以处理需要加权统计的大规模数据。需要根据具体情况权衡内存占用和性能。
总结:
选择合适的存储和累加器需要根据具体的应用场景、统计需求、性能要求和内存限制进行权衡。对于简单的计数统计,整数存储和默认累加器通常是最佳选择。对于加权统计或需要更高精度的场景,权重存储和权重累加器是必要的。对于特殊的统计需求,自定义累加器提供了强大的灵活性。
在实际应用中,建议先从简单的存储和累加器类型开始,例如整数存储和默认累加器。如果发现功能或性能不足,再逐步尝试更复杂的存储和累加器类型,例如权重存储、权重累加器或自定义累加器。通过实践和测试,找到最适合自己应用场景的存储和累加器组合。
END_OF_CHAPTER
5. chapter 5: 实战代码:Boost.Histogram 的应用 (Practical Code: Applications of Boost.Histogram)
5.1 数据分析案例 (Data Analysis Case Studies)
数据分析是直方图最常见的应用场景之一。Boost.Histogram 提供了强大的工具来创建和操作直方图,从而有效地分析各种类型的数据分布和关联性。本节将通过几个实际案例,展示如何使用 Boost.Histogram 进行数据分析。
5.1.1 一维数据分布分析 (1D Data Distribution Analysis)
一维直方图 (1D Histogram) 是分析单变量数据分布的有力工具。它可以帮助我们理解数据的集中趋势、离散程度、偏态和峰度等特征。例如,我们可以使用一维直方图来分析用户年龄分布、产品价格分布、网站访问时长分布等。
案例描述:假设我们有一份包含用户年龄的数据集,我们希望分析用户的年龄分布情况,例如,哪个年龄段的用户最多,年龄分布是否均匀等。
代码实现:
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
#include <vector>
4
#include <numeric>
5
6
namespace bh = boost::histogram;
7
8
int main() {
9
// ① 准备用户年龄数据
10
std::vector<int> ages = {22, 25, 30, 35, 22, 28, 40, 45, 22, 31, 38, 27, 29, 33, 39, 26, 32, 37, 41, 24};
11
12
// ② 创建一维直方图,使用规则轴 (regular axis),将年龄范围划分为若干个箱 (bin)
13
auto hist = bh::make_histogram(bh::axis::regular(10, 20, 50, "Age")); // 10 个箱,范围从 20 到 50,轴标签为 "Age"
14
15
// ③ 填充直方图
16
for (int age : ages) {
17
hist(age);
18
}
19
20
// ④ 遍历直方图并输出结果
21
std::cout << "Age Distribution Histogram:\n";
22
for (auto bin : hist.axis(0)) {
23
std::cout << " [" << bin.lower() << ", " << bin.upper() << "): " << hist[{bin}] << "\n";
24
}
25
26
// ⑤ 计算一些统计指标,例如均值 (mean) 和标准差 (standard deviation) (作为扩展练习)
27
double sum_of_ages = std::accumulate(ages.begin(), ages.end(), 0.0);
28
double mean_age = sum_of_ages / ages.size();
29
std::cout << "\nMean Age: " << mean_age << "\n";
30
31
double sum_sq_diff = 0.0;
32
for (int age : ages) {
33
sum_sq_diff += (age - mean_age) * (age - mean_age);
34
}
35
double std_dev_age = std::sqrt(sum_sq_diff / ages.size());
36
std::cout << "Standard Deviation of Age: " << std_dev_age << "\n";
37
38
39
return 0;
40
}
代码解释:
① 我们首先准备了一组用户年龄数据 ages
,这可以是实际应用中从数据库或文件中读取的数据。
② 使用 bh::make_histogram
创建了一个一维直方图。bh::axis::regular(10, 20, 50, "Age")
定义了一个规则轴,它将 20 到 50 岁的年龄范围均匀地划分为 10 个箱 (bin)。最后一个参数 "Age"
是轴的标签,用于输出和可视化。
③ 使用 for
循环遍历 ages
向量,并使用 hist(age)
将每个年龄值填充到直方图中。Boost.Histogram 会自动根据年龄值找到对应的箱 (bin) 并增加计数 (count)。
④ 我们遍历直方图的轴 (axis) 和箱 (bin),并输出每个箱 (bin) 的范围和计数 (count)。hist[{bin}]
用于访问特定箱 (bin) 的计数 (count)。
⑤ 作为扩展练习,代码还计算了年龄的均值 (mean) 和标准差 (standard deviation),这些统计指标可以进一步帮助我们理解年龄分布的特征。
运行结果 (示例):
1
Age Distribution Histogram:
2
[20, 23): 3
3
[23, 26): 3
4
[26, 29): 3
5
[29, 32): 3
6
[32, 35): 2
7
[35, 38): 2
8
[38, 41): 2
9
[41, 44): 1
10
[44, 47): 1
11
[47, 50): 0
12
13
Mean Age: 31.2
14
Standard Deviation of Age: 6.84255
结果分析:
从直方图的输出结果可以看出,年龄分布主要集中在 20-35 岁之间,其中 20-23, 23-26, 26-29, 29-32 这四个年龄段的用户数量最多,均为 3 人。随着年龄的增长,用户数量逐渐减少。均值年龄为 31.2 岁,标准差为 6.84 岁,表明年龄分布相对集中,但也有一定的离散性。
可视化:
为了更直观地展示年龄分布,我们可以将直方图数据导出到文件,并使用数据可视化工具(如 Python 的 Matplotlib, Seaborn,或者 R 的 ggplot2)绘制成柱状图。这将使我们能够更清晰地观察年龄分布的形状和特征。
5.1.2 多维数据关联性分析 (Multidimensional Data Correlation Analysis)
多维直方图 (Multidimensional Histogram) 可以用于分析多个变量之间的关联性。例如,我们可以使用二维直方图 (2D Histogram) 分析用户年龄和收入之间的关系,或者使用三维直方图 (3D Histogram) 分析温度、湿度和降雨量之间的关系。
案例描述:假设我们有一份包含用户年龄和收入的数据集,我们希望分析用户的年龄和收入之间是否存在关联性,例如,年龄是否会影响收入水平。
代码实现:
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
#include <vector>
4
#include <tuple>
5
6
namespace bh = boost::histogram;
7
8
int main() {
9
// ① 准备用户年龄和收入数据 (使用 tuple 存储)
10
std::vector<std::tuple<int, double>> user_data = {
11
{25, 55000.0}, {30, 65000.0}, {22, 48000.0}, {35, 75000.0}, {28, 60000.0},
12
{40, 90000.0}, {27, 58000.0}, {32, 70000.0}, {45, 110000.0}, {38, 85000.0},
13
{24, 52000.0}, {31, 68000.0}, {33, 72000.0}, {39, 88000.0}, {26, 56000.0}
14
};
15
16
// ② 创建二维直方图,分别使用规则轴 (regular axis) 定义年龄和收入轴
17
auto hist = bh::make_histogram(
18
bh::axis::regular(5, 20, 50, "Age"), // 年龄轴:5 个箱,范围 20-50
19
bh::axis::regular(5, 40000, 120000, "Income") // 收入轴:5 个箱,范围 40000-120000
20
);
21
22
// ③ 填充直方图
23
for (const auto& data : user_data) {
24
hist(std::get<0>(data), std::get<1>(data)); // 使用 std::get 获取 tuple 中的年龄和收入
25
}
26
27
// ④ 遍历直方图并输出结果 (二维直方图需要嵌套循环遍历)
28
std::cout << "Age vs. Income Distribution Histogram:\n";
29
for (auto age_bin : hist.axis(0)) {
30
for (auto income_bin : hist.axis(1)) {
31
std::cout << " Age[" << age_bin.lower() << ", " << age_bin.upper() << ") x Income[" << income_bin.lower() << ", " << income_bin.upper() << "): " << hist[{age_bin, income_bin}] << "\n";
32
}
33
}
34
35
return 0;
36
}
代码解释:
① 我们准备了一组用户年龄和收入数据 user_data
,使用 std::tuple<int, double>
存储每条数据,其中第一个元素是年龄,第二个元素是收入。
② 使用 bh::make_histogram
创建了一个二维直方图。第一个 bh::axis::regular
定义了年龄轴,第二个 bh::axis::regular
定义了收入轴。
③ 使用 for
循环遍历 user_data
向量,并使用 hist(std::get<0>(data), std::get<1>(data))
将每条数据的年龄和收入值填充到直方图中。
④ 遍历二维直方图需要使用嵌套循环,分别遍历年龄轴和收入轴的箱 (bin)。hist[{age_bin, income_bin}]
用于访问特定箱 (bin) 的计数 (count)。
运行结果 (示例):
1
Age vs. Income Distribution Histogram:
2
Age[20, 26) x Income[40000, 56000): 3
3
Age[20, 26) x Income[56000, 72000): 2
4
Age[20, 26) x Income[72000, 88000): 0
5
Age[20, 26) x Income[88000, 104000): 0
6
Age[20, 26) x Income[104000, 120000): 0
7
Age[26, 32) x Income[40000, 56000): 0
8
Age[26, 32) x Income[56000, 72000): 5
9
Age[26, 32) x Income[72000, 88000): 2
10
Age[26, 32) x Income[88000, 104000): 0
11
Age[26, 32) x Income[104000, 120000): 0
12
Age[32, 38) x Income[40000, 56000): 0
13
Age[32, 38) x Income[56000, 72000): 0
14
Age[32, 38) x Income[72000, 88000): 3
15
Age[32, 38) x Income[88000, 104000): 1
16
Age[32, 38) x Income[104000, 120000): 0
17
Age[38, 44) x Income[40000, 56000): 0
18
Age[38, 44) x Income[56000, 72000): 0
19
Age[38, 44) x Income[72000, 88000): 0
20
Age[38, 44) x Income[88000, 104000): 2
21
Age[38, 44) x Income[104000, 120000): 1
22
Age[44, 50) x Income[40000, 56000): 0
23
Age[44, 50) x Income[56000, 72000): 0
24
Age[44, 50) x Income[72000, 88000): 0
25
Age[44, 50) x Income[88000, 104000): 0
26
Age[44, 50) x Income[104000, 120000): 1
结果分析:
从二维直方图的输出结果可以看出,年龄和收入之间存在一定的关联性。例如,在年龄段 [26, 32) 和收入段 [56000, 72000) 的用户数量最多,为 5 人。随着年龄和收入的增长,用户数量的分布也发生变化。为了更清晰地分析关联性,我们可以将二维直方图可视化为热图 (heatmap) 或三维曲面图。
可视化:
类似于一维直方图,我们可以将二维直方图数据导出,并使用可视化工具绘制热图或三维柱状图。热图可以直观地展示不同年龄和收入组合的用户数量,颜色深浅代表数量多少,从而帮助我们分析年龄和收入之间的关联模式。
5.2 性能监控与指标统计 (Performance Monitoring and Metric Statistics)
直方图在性能监控和指标统计方面也扮演着重要角色。它可以帮助我们收集和分析系统性能数据,例如请求延迟 (request latency)、响应时间 (response time)、资源利用率 (resource utilization) 等。通过直方图,我们可以了解性能指标的分布情况,识别性能瓶颈,并进行性能优化。
案例描述:假设我们正在监控一个 Web 服务的请求延迟。我们希望使用直方图来统计请求延迟的分布情况,例如,延迟主要集中在哪个范围,是否存在长尾延迟 (long-tail latency) 等。
代码实现:
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
#include <vector>
4
#include <random>
5
#include <chrono>
6
7
namespace bh = boost::histogram;
8
9
int main() {
10
// ① 模拟 Web 服务请求延迟数据 (使用随机数模拟)
11
std::random_device rd;
12
std::mt19937 gen(rd());
13
std::exponential_distribution<> distrib(0.1); // 指数分布模拟请求延迟
14
15
std::vector<double> latencies;
16
for (int i = 0; i < 1000; ++i) {
17
latencies.push_back(distrib(gen) * 10); // 延迟放大 10 倍,单位毫秒 (ms)
18
}
19
20
// ② 创建直方图,使用对数轴 (log axis) 或可变轴 (variable axis) 更适合延迟数据
21
auto hist = bh::make_histogram(
22
bh::axis::variable({0.1, 1, 10, 100, 1000}, "Latency (ms)") // 可变轴,箱边界自定义
23
);
24
25
// ③ 填充直方图
26
for (double latency : latencies) {
27
hist(latency);
28
}
29
30
// ④ 遍历直方图并输出结果
31
std::cout << "Request Latency Distribution Histogram:\n";
32
for (auto bin : hist.axis(0)) {
33
std::cout << " [" << bin.lower() << ", " << bin.upper() << "): " << hist[{bin}] << "\n";
34
}
35
36
// ⑤ 计算分位数 (quantile) (例如 P50, P90, P99) (作为扩展练习)
37
auto calculate_quantile = [&](double quantile) {
38
double count_sum = 0;
39
double total_count = bh::indexed(hist).size(); // 获取总计数
40
for (auto bin : hist.axis(0)) {
41
count_sum += hist[{bin}];
42
if (count_sum / total_count >= quantile) {
43
return bin.upper(); // 返回分位数对应的箱 (bin) 的上边界
44
}
45
}
46
return hist.axis(0).back().upper(); // 极端情况,返回最后一个箱 (bin) 的上边界
47
};
48
49
std::cout << "\nP50 Latency: " << calculate_quantile(0.50) << " ms\n";
50
std::cout << "P90 Latency: " << calculate_quantile(0.90) << " ms\n";
51
std::cout << "P99 Latency: " << calculate_quantile(0.99) << " ms\n";
52
53
54
return 0;
55
}
代码解释:
① 我们使用随机数模拟 Web 服务请求延迟数据 latencies
,这里使用了指数分布来模拟延迟,因为实际的请求延迟通常符合指数分布或对数正态分布。
② 创建直方图时,我们使用了 bh::axis::variable
定义了一个可变轴。对于延迟数据,使用可变轴或对数轴 (log axis) 通常更合适,因为延迟数据范围可能很广,且分布不均匀。可变轴可以自定义箱 (bin) 的边界,更灵活地适应数据分布。
③ 填充直方图的过程与之前案例类似。
④ 遍历直方图并输出结果。
⑤ 作为扩展练习,代码实现了计算分位数 (quantile) 的函数 calculate_quantile
。分位数是性能监控中常用的指标,例如 P50 (中位数)、P90、P99 等,它们可以帮助我们了解延迟分布的不同百分位点的值。
运行结果 (示例):
1
Request Latency Distribution Histogram:
2
[0.1, 1): 91
3
[1, 10): 625
4
[10, 100): 271
5
[100, 1000): 13
6
[1000, inf): 0
7
8
P50 Latency: 8.42249 ms
9
P90 Latency: 24.9727 ms
10
P99 Latency: 51.7875 ms
结果分析:
从直方图和分位数的输出结果可以看出,请求延迟主要集中在 1-10 毫秒 (ms) 范围内,占比最多。P50 延迟约为 8.4 毫秒,P90 延迟约为 25 毫秒,P99 延迟约为 52 毫秒。这些分位数指标可以帮助我们评估服务的性能水平,例如,99% 的请求延迟在 52 毫秒以内。如果 P99 延迟过高,可能需要进一步分析和优化系统性能。
应用场景扩展:
除了请求延迟,直方图还可以用于监控和统计其他性能指标,例如:
⚝ CPU 利用率 (CPU Utilization):统计 CPU 利用率在不同区间的分布情况,例如,CPU 利用率在 0-20%, 20-40%, ..., 80-100% 的时间占比。
⚝ 内存使用量 (Memory Usage):统计内存使用量在不同区间的分布情况,例如,内存使用量在 0-1GB, 1-2GB, ..., 7-8GB 的时间占比。
⚝ 磁盘 I/O 延迟 (Disk I/O Latency):统计磁盘 I/O 操作的延迟分布,类似于请求延迟的分析。
⚝ 网络带宽利用率 (Network Bandwidth Utilization):统计网络带宽的利用率分布。
通过对这些性能指标进行直方图统计和分析,我们可以全面了解系统的性能状况,及时发现和解决性能问题。
5.3 模拟与实验数据处理 (Simulation and Experimental Data Processing)
在科学研究和工程领域,模拟 (simulation) 和实验 (experiment) 是常用的方法。Boost.Histogram 可以用于处理模拟和实验产生的大量数据,进行数据分析和结果可视化。
案例描述:假设我们进行了一项物理实验,测量了某种粒子的能量分布。我们希望使用直方图来分析实验数据,得到粒子的能量分布谱。
代码实现:
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
#include <vector>
4
#include <random>
5
6
namespace bh = boost::histogram;
7
8
int main() {
9
// ① 模拟实验数据:粒子能量数据 (MeV)
10
std::random_device rd;
11
std::mt19937 gen(rd());
12
std::normal_distribution<> distrib(50.0, 10.0); // 正态分布模拟粒子能量
13
14
std::vector<double> energies;
15
for (int i = 0; i < 10000; ++i) {
16
energies.push_back(distrib(gen));
17
}
18
19
// ② 创建直方图,使用规则轴 (regular axis) 定义能量轴
20
auto hist = bh::make_histogram(
21
bh::axis::regular(100, 0, 100, "Energy (MeV)") // 100 个箱,范围 0-100 MeV
22
);
23
24
// ③ 填充直方图
25
for (double energy : energies) {
26
hist(energy);
27
}
28
29
// ④ 遍历直方图并输出结果 (可以输出到文件,用于后续绘图)
30
std::cout << "Particle Energy Spectrum Histogram:\n";
31
for (auto bin : hist.axis(0)) {
32
std::cout << " [" << bin.lower() << ", " << bin.upper() << "): " << hist[{bin}] << "\n";
33
}
34
35
// ⑤ 归一化直方图 (normalization) (可选,用于显示概率密度分布)
36
auto normalized_hist = hist;
37
normalized_hist /= bh::sum(hist); // 除以总计数进行归一化
38
39
std::cout << "\nNormalized Particle Energy Spectrum Histogram:\n";
40
for (auto bin : normalized_hist.axis(0)) {
41
std::cout << " [" << bin.lower() << ", " << bin.upper() << "): " << normalized_hist[{bin}] << "\n";
42
}
43
44
45
return 0;
46
}
代码解释:
① 我们使用随机数模拟实验数据 energies
,这里使用了正态分布来模拟粒子能量分布,实际实验数据可能来自传感器或其他测量设备。
② 创建直方图时,我们使用了规则轴 (regular axis) 定义能量轴,将能量范围 0-100 MeV 划分为 100 个箱 (bin)。
③ 填充直方图的过程与之前案例类似。
④ 遍历直方图并输出结果。为了后续绘图,可以将直方图数据输出到文件,例如 CSV 格式。
⑤ 代码还展示了如何对直方图进行归一化 (normalization)。归一化后的直方图可以表示概率密度分布 (probability density distribution),更方便进行比较和分析。归一化操作通过将直方图除以总计数 bh::sum(hist)
实现。
运行结果 (示例,部分输出):
1
Particle Energy Spectrum Histogram:
2
[0, 1): 0
3
[1, 2): 0
4
[2, 3): 0
5
...
6
[45, 46): 78
7
[46, 47): 112
8
[47, 48): 145
9
[48, 49): 178
10
[49, 50): 215
11
[50, 51): 230
12
[51, 52): 195
13
[52, 53): 158
14
[53, 54): 120
15
[54, 55): 95
16
...
17
[97, 98): 0
18
[98, 99): 0
19
[99, 100): 0
20
21
Normalized Particle Energy Spectrum Histogram:
22
[0, 1): 0
23
[1, 2): 0
24
[2, 3): 0
25
...
26
[45, 46): 0.0078
27
[46, 47): 0.0112
28
[47, 48): 0.0145
29
[48, 49): 0.0178
30
[49, 50): 0.0215
31
[50, 51): 0.023
32
[51, 52): 0.0195
33
[52, 53): 0.0158
34
[53, 54): 0.012
35
[54, 55): 0.0095
36
...
37
[97, 98): 0
38
[98, 99): 0
39
[99, 100): 0
结果分析:
从直方图的输出结果可以看出,粒子能量分布大致呈正态分布形状,能量峰值在 50 MeV 附近。归一化后的直方图显示了能量的概率密度分布,可以更方便地与其他分布进行比较。
应用场景扩展:
直方图在模拟和实验数据处理中应用广泛,例如:
⚝ 物理学实验:粒子物理实验中的能量谱、动量谱分析;天文学观测中的星系红移分布、光度分布分析。
⚝ 生物学实验:细胞大小分布、基因表达水平分布分析。
⚝ 工程模拟:有限元分析 (Finite Element Analysis, FEA) 中的应力分布、温度分布分析;流体动力学模拟 (Computational Fluid Dynamics, CFD) 中的速度分布、压力分布分析。
⚝ 金融建模:股票收益率分布、风险因子分布分析。
5.4 与其他库的集成应用 (Integration with Other Libraries)
Boost.Histogram 可以与其他 C++ 库集成使用,扩展其功能和应用范围。例如,可以与 Boost.Python 集成,将直方图功能暴露给 Python;可以与数据可视化库(如 ROOT, Gnuplot, Matplotlib-cpp)集成,实现直方图的可视化。
案例描述:展示 Boost.Histogram 与 Boost.Python 的集成,将 C++ 中创建的直方图传递给 Python 环境进行分析和可视化。
代码概念示例 (C++ 部分,需要 Boost.Python 环境):
1
#include <boost/histogram.hpp>
2
#include <boost/python.hpp>
3
#include <vector>
4
5
namespace bh = boost::histogram;
6
namespace python = boost::python;
7
8
// ① C++ 函数:创建并填充直方图
9
bh::histogram<bh::axis::regular> create_histogram(const python::list& data_list) {
10
auto hist = bh::make_histogram(bh::axis::regular(10, 0, 10));
11
for (int i = 0; i < python::len(data_list); ++i) {
12
hist(python::extract<double>(data_list[i]));
13
}
14
return hist;
15
}
16
17
// ② Boost.Python 模块定义
18
BOOST_PYTHON_MODULE(histogram_module) // 模块名
19
{
20
python::def("create_histogram", create_histogram); // 暴露 create_histogram 函数
21
22
// 可以考虑暴露 histogram 类本身,但此处简化示例,只传递数据
23
python::def("get_histogram_data", [](const bh::histogram<bh::axis::regular>& hist) {
24
python::list data;
25
for (auto bin : hist.axis(0)) {
26
data.append(python::make_tuple(bin.lower(), bin.upper(), hist[{bin}]));
27
}
28
return data;
29
});
30
}
代码解释 (C++ 部分):
① create_histogram
函数接收一个 Python 列表 data_list
,将其转换为 C++ 的 std::vector<double>
,然后创建并填充一个 Boost.Histogram 直方图。
② BOOST_PYTHON_MODULE
定义了一个 Boost.Python 模块 histogram_module
,将 create_histogram
函数暴露给 Python 环境。
③ get_histogram_data
函数用于从 C++ 直方图中提取数据,并将其转换为 Python 列表返回,方便 Python 端进行处理。
Python 代码示例 (Python 部分,假设已编译生成 histogram_module.so
或 histogram_module.pyd
):
1
import histogram_module
2
import matplotlib.pyplot as plt
3
4
# ① 准备 Python 数据
5
data = [1.5, 2.3, 4.7, 5.1, 6.8, 7.2, 8.5, 9.1, 3.4, 5.9]
6
7
# ② 调用 C++ 函数创建直方图
8
hist_cpp = histogram_module.create_histogram(data)
9
10
# ③ 从 C++ 获取直方图数据
11
hist_data = histogram_module.get_histogram_data(hist_cpp)
12
13
# ④ 使用 Matplotlib 可视化直方图
14
bin_centers = [(bin_lower + bin_upper) / 2 for bin_lower, bin_upper, count in hist_data]
15
counts = [count for bin_lower, bin_upper, count in hist_data]
16
17
plt.bar(bin_centers, counts, width=1.0) # 假设箱宽度为 1
18
plt.xlabel("Value")
19
plt.ylabel("Count")
20
plt.title("Histogram from Boost.Histogram (via Boost.Python)")
21
plt.grid(True)
22
plt.show()
代码解释 (Python 部分):
① 导入编译好的 Boost.Python 模块 histogram_module
和 Matplotlib 库。
② 准备 Python 数据 data
。
③ 调用 C++ 模块中的 create_histogram
函数,将 Python 数据传递给 C++,创建 Boost.Histogram 直方图。
④ 调用 get_histogram_data
函数从 C++ 获取直方图数据,并使用 Matplotlib 绘制柱状图。
集成应用扩展:
⚝ 与 ROOT 集成:ROOT 是高能物理领域常用的数据分析和可视化框架,Boost.Histogram 可以与 ROOT 集成,利用 ROOT 的强大可视化功能绘制高质量的直方图。
⚝ 与 Gnuplot 集成:Gnuplot 是一款跨平台命令行驱动的绘图工具,可以将 Boost.Histogram 数据导出为 Gnuplot 可以识别的格式,使用 Gnuplot 进行绘图。
⚝ 与 Matplotlib-cpp 集成:Matplotlib-cpp 是一个 C++ 封装的 Matplotlib 库,可以直接在 C++ 代码中使用 Matplotlib 进行绘图,方便 Boost.Histogram 的可视化。
⚝ 与其他 Boost 库集成:例如,与 Boost.Serialization 集成,实现直方图的序列化和持久化;与 Boost.Asio 集成,在异步网络应用中进行性能监控和统计。
通过与其他库的集成,Boost.Histogram 可以更好地融入到现有的 C++ 生态系统中,发挥更大的作用。
END_OF_CHAPTER
6. chapter 6: 高级应用技巧 (Advanced Application Techniques)
6.1 直方图的变换与操作 (Histogram Transformations and Operations)
直方图不仅仅是一个静态的数据结构,它还可以进行各种变换和操作,以满足不同的分析需求。Boost.Histogram 提供了丰富的工具来实现这些高级操作,包括重采样(Resampling)、重分箱(Rebinning)和投影(Projection)等。本节将深入探讨这些变换与操作,并通过实战代码展示如何在实际应用中灵活运用它们。
6.1.1 重采样 (Resampling)
重采样(Resampling)是指改变直方图的采样率或分辨率的过程。在某些情况下,原始直方图可能具有过高或过低的分辨率,不便于分析或与其他数据进行比较。重采样可以帮助我们调整直方图的分辨率,使其更适合当前的任务。
① 上采样(Upsampling):增加直方图的分辨率,即增加箱(bin)的数量。上采样通常通过插值算法来实现,例如线性插值或样条插值。Boost.Histogram 本身不直接提供插值功能,但可以通过访问直方图数据,然后使用其他库(如 Boost.Interp 或自定义算法)进行插值,再创建新的直方图。
② 下采样(Downsampling):降低直方图的分辨率,即减少箱的数量。下采样通常通过合并相邻的箱来实现。重分箱(Rebinning)是下采样的一种常见形式,将在下一小节详细介绍。
实战代码:下采样示例
假设我们有一个一维直方图,箱数较多,为了简化分析,我们希望将其下采样,减少箱数。以下代码展示了如何手动实现一个简单的下采样,将每两个相邻的箱合并为一个箱。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
#include <vector>
4
5
namespace bh = boost::histogram;
6
7
int main() {
8
// 创建一个原始直方图
9
auto h_original = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
10
for (int i = 0; i < 10; ++i) {
11
h_original.fill(i + 0.5);
12
}
13
14
// 创建一个新的直方图,箱数减半
15
auto h_resampled = bh::make_histogram(bh::axis::regular(5, 0.0, 10.0));
16
17
// 手动下采样:合并原始直方图中每两个相邻的箱
18
for (int i = 0; i < 5; ++i) {
19
h_resampled[i] = h_original[2 * i] + h_original[2 * i + 1];
20
}
21
22
// 打印原始直方图和重采样后的直方图
23
std::cout << "Original Histogram:\n";
24
for (int i = 0; i < 10; ++i) {
25
std::cout << "bin " << i << ": " << h_original[i] << "\n";
26
}
27
28
std::cout << "\nResampled Histogram (Downsampled):\n";
29
for (int i = 0; i < 5; ++i) {
30
std::cout << "bin " << i << ": " << h_resampled[i] << "\n";
31
}
32
33
return 0;
34
}
代码解释:
⚝ 我们首先创建了一个具有 10 个箱的原始直方图 h_original
,并填充了一些数据。
⚝ 然后,我们创建了一个新的直方图 h_resampled
,其箱数为原始直方图的一半(5 个箱)。
⚝ 通过遍历新直方图的箱索引,我们将原始直方图中每两个相邻箱的计数累加到新直方图的对应箱中,实现了简单的下采样。
高级应用:
重采样在信号处理、图像处理和时间序列分析等领域有广泛应用。例如,在音频信号处理中,可能需要将音频信号从一个采样率转换为另一个采样率。在直方图应用中,重采样可以用于:
⚝ 数据可视化:降低高分辨率直方图的分辨率,使其在屏幕上更易于显示和理解。
⚝ 数据压缩:通过下采样减少直方图的数据量,节省存储空间。
⚝ 特征提取:在某些机器学习任务中,重采样可以作为一种特征工程手段,提取不同分辨率下的数据特征。
6.1.2 重分箱 (Rebinning)
重分箱(Rebinning)是一种特殊的下采样操作,它通过重新定义箱的边界来合并或分割箱。与简单的下采样相比,重分箱更加灵活,可以根据具体需求调整箱的宽度和位置。
① 合并箱(Merging Bins):将多个相邻的箱合并为一个箱。这通常用于降低直方图的分辨率,或者将一些统计量较小的箱合并,以提高统计的稳健性。
② 分割箱(Splitting Bins):将一个箱分割成多个更小的箱。这在某些情况下可以提高直方图的分辨率,但需要注意的是,分割箱并不能增加原始数据的信息量,只是在视觉上或计算上可能更精细。Boost.Histogram 本身不直接支持分割箱,通常需要通过插值或重新填充数据来实现类似效果。
Boost.Histogram 的重分箱实现
Boost.Histogram 提供了 bh::algorithm::reduce_axes
函数,可以用于实现重分箱。reduce_axes
函数允许用户指定新的轴配置,从而实现箱的合并。
实战代码:使用 reduce_axes
进行重分箱
1
#include <boost/histogram.hpp>
2
#include <boost/histogram/algorithm/reduce_axes.hpp>
3
#include <iostream>
4
5
namespace bh = boost::histogram;
6
namespace bha = boost::histogram::algorithm;
7
8
int main() {
9
// 创建一个原始直方图
10
auto h_original = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
11
for (int i = 0; i < 10; ++i) {
12
h_original.fill(i + 0.5);
13
}
14
15
// 使用 reduce_axes 进行重分箱,将每两个箱合并为一个
16
auto h_rebinned = bha::reduce_axes(h_original, bha::rebin(2, 0)); // rebin(factor, axis_index)
17
18
// 打印原始直方图和重分箱后的直方图
19
std::cout << "Original Histogram:\n";
20
for (int i = 0; i < 10; ++i) {
21
std::cout << "bin " << i << ": " << h_original[i] << "\n";
22
}
23
24
std::cout << "\nRebinned Histogram:\n";
25
for (int i = 0; i < 5; ++i) {
26
std::cout << "bin " << i << ": " << h_rebinned[i] << "\n";
27
}
28
29
return 0;
30
}
代码解释:
⚝ 我们使用 bh::algorithm::reduce_axes
函数对原始直方图 h_original
进行了重分箱。
⚝ bha::rebin(2, 0)
指定了重分箱的操作:2
表示合并因子,即将每 2 个箱合并为一个箱;0
表示操作的轴索引(对于一维直方图,轴索引为 0)。
⚝ reduce_axes
函数返回一个新的直方图 h_rebinned
,其箱数是原始直方图的一半,每个箱的计数是原始直方图中对应合并箱的计数之和。
高级应用:
重分箱在数据分析和可视化中非常有用:
⚝ 平滑数据:通过合并箱,可以平滑直方图的形状,减少噪声的影响,更清晰地显示数据的整体分布趋势。
⚝ 调整分辨率:根据分析需求,灵活调整直方图的分辨率,例如,在数据探索阶段可以使用较低的分辨率快速查看数据分布,在精细分析阶段可以使用较高的分辨率。
⚝ 兼容不同分辨率的数据:当需要比较或合并来自不同来源、具有不同分辨率的直方图时,可以通过重分箱将它们调整到相同的分辨率。
6.1.3 投影 (Projection)
投影(Projection)是指从多维直方图中提取低维直方图的过程。对于多维数据,我们有时需要单独分析某个或某几个维度的数据分布,而忽略其他维度的影响。投影操作可以将多维直方图“降维”,得到我们感兴趣的低维直方图。
Boost.Histogram 的投影实现
Boost.Histogram 提供了 project
方法来实现直方图的投影。project
方法接受轴索引作为参数,指定要保留的维度,返回一个低维的直方图。
实战代码:多维直方图的投影
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个二维直方图
8
auto h2d = bh::make_histogram(
9
bh::axis::regular(5, 0.0, 5.0, "x"),
10
bh::axis::regular(5, 0.0, 5.0, "y")
11
);
12
13
// 填充二维直方图
14
for (int i = 0; i < 5; ++i) {
15
for (int j = 0; j < 5; ++j) {
16
h2d.fill(i + 0.5, j + 0.5);
17
}
18
}
19
20
// 投影到 x 轴 (轴索引 0)
21
auto h_x_projected = h2d.project(0);
22
23
// 投影到 y 轴 (轴索引 1)
24
auto h_y_projected = h2d.project(1);
25
26
// 打印原始二维直方图 (简化表示)
27
std::cout << "Original 2D Histogram (projection to x and y):\n";
28
std::cout << "x-projection:\n";
29
for (int i = 0; i < 5; ++i) {
30
std::cout << "bin " << i << ": " << h_x_projected[i] << "\n";
31
}
32
std::cout << "y-projection:\n";
33
for (int i = 0; i < 5; ++i) {
34
std::cout << "bin " << i << ": " << h_y_projected[i] << "\n";
35
}
36
37
38
return 0;
39
}
代码解释:
⚝ 我们创建了一个二维直方图 h2d
,具有 x 轴和 y 轴。
⚝ h2d.project(0)
将二维直方图投影到 x 轴,返回一个一维直方图 h_x_projected
,其中每个箱的计数是原始二维直方图在对应 x 轴箱上的所有 y 轴箱的计数之和。
⚝ h2d.project(1)
同理,将二维直方图投影到 y 轴,返回一维直方图 h_y_projected
。
高级应用:
投影在多维数据分析中至关重要:
⚝ 边缘分布分析:通过投影,可以得到多维数据在每个维度上的边缘分布(Marginal Distribution),即单独考虑每个维度的数据分布情况。
⚝ 特征选择:在特征工程中,可以通过分析各个维度的边缘分布,评估特征的重要性,为特征选择提供依据。
⚝ 数据可视化:将高维数据投影到低维空间(例如二维或一维),便于可视化和理解高维数据的结构。
⚝ 条件分布分析:结合切片(Slice)操作(将在后续章节或更高级的主题中讨论),可以分析在某些维度取特定值时,其他维度的数据分布情况,即条件分布(Conditional Distribution)。
6.2 直方图的算术运算 (Arithmetic Operations on Histograms)
Boost.Histogram 支持直方图之间的基本算术运算,包括加法、减法、乘法和除法。这些运算可以用于合并、比较和归一化直方图。
① 加法 (+
):将两个直方图对应箱的计数相加。加法运算通常用于合并来自不同数据集或不同时间段的直方图。
② 减法 (-
):将两个直方图对应箱的计数相减。减法运算可以用于比较两个直方图的差异,或者从一个直方图中去除另一个直方图的影响。
③ 乘法 (*
):将直方图的每个箱的计数乘以一个标量值,或者将两个直方图对应箱的计数相乘(箱对箱乘法,element-wise multiplication)。标量乘法用于缩放直方图的计数,箱对箱乘法在某些高级统计分析中可能用到。
④ 除法 (/
):将直方图的每个箱的计数除以一个标量值,或者将两个直方图对应箱的计数相除(箱对箱除法)。标量除法用于归一化直方图,箱对箱除法可以用于计算比率直方图。
运算的条件
进行直方图算术运算时,需要注意以下条件:
⚝ 轴的兼容性:参与运算的直方图必须具有相同的轴配置,即轴的数量、类型、边界和标签必须完全一致。否则,运算结果可能没有意义或导致错误。
⚝ 存储类型:直方图的存储类型需要支持相应的算术运算。例如,权重存储的直方图可以进行加减乘除运算,但整数存储的直方图在除法运算时需要注意精度问题。
实战代码:直方图的算术运算
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建两个直方图,轴配置相同
8
auto h1 = bh::make_histogram(bh::axis::regular(5, 0.0, 5.0));
9
auto h2 = bh::make_histogram(bh::axis::regular(5, 0.0, 5.0));
10
11
// 填充直方图
12
for (int i = 0; i < 5; ++i) {
13
h1.fill(i + 0.5);
14
h2.fill(5.0 - (i + 0.5)); // 填充相反的数据分布
15
}
16
17
// 加法运算
18
auto h_sum = h1 + h2;
19
20
// 减法运算
21
auto h_diff = h1 - h2;
22
23
// 标量乘法
24
auto h_scaled = h1 * 2.0;
25
26
// 标量除法
27
auto h_normalized = h1 / h1.sum(); // 除以总计数进行归一化
28
29
// 打印结果 (简化表示)
30
std::cout << "Histogram h1:\n";
31
for (int i = 0; i < 5; ++i) {
32
std::cout << "bin " << i << ": " << h1[i] << "\n";
33
}
34
std::cout << "\nHistogram h2:\n";
35
for (int i = 0; i < 5; ++i) {
36
std::cout << "bin " << i << ": " << h2[i] << "\n";
37
}
38
std::cout << "\nHistogram sum (h1 + h2):\n";
39
for (int i = 0; i < 5; ++i) {
40
std::cout << "bin " << i << ": " << h_sum[i] << "\n";
41
}
42
std::cout << "\nHistogram difference (h1 - h2):\n";
43
for (int i = 0; i < 5; ++i) {
44
std::cout << "bin " << i << ": " << h_diff[i] << "\n";
45
}
46
std::cout << "\nHistogram scaled (h1 * 2.0):\n";
47
for (int i = 0; i < 5; ++i) {
48
std::cout << "bin " << i << ": " << h_scaled[i] << "\n";
49
}
50
std::cout << "\nHistogram normalized (h1 / sum(h1)):\n";
51
for (int i = 0; i < 5; ++i) {
52
std::cout << "bin " << i << ": " << h_normalized[i] << "\n";
53
}
54
55
56
return 0;
57
}
高级应用:
直方图的算术运算在多种场景下非常有用:
⚝ 背景扣除:在实验数据分析中,可以使用减法运算从信号直方图中扣除背景噪声直方图,得到纯信号的分布。
⚝ 差异分析:通过减法运算比较不同组别或不同条件下的数据分布差异。
⚝ 归一化:使用除法运算将直方图归一化,例如,转换为概率密度分布,便于比较不同样本量的直方图。
⚝ 模型验证:在模拟与实验对比中,可以使用算术运算结合卡方检验等统计方法,评估模拟结果与实验数据的吻合程度。
6.3 直方图的序列化与持久化 (Serialization and Persistence of Histograms)
在实际应用中,我们经常需要将直方图保存到磁盘,以便后续分析或共享。Boost.Histogram 提供了序列化(Serialization)和持久化(Persistence)的支持,可以将直方图对象转换为字节流进行存储,并在需要时重新加载。
Boost.Serialization 库
Boost.Histogram 可以与 Boost.Serialization 库无缝集成,实现直方图的序列化和反序列化。Boost.Serialization 是一个强大的 C++ 序列化库,支持多种序列化格式,如二进制、文本和 XML。
序列化步骤
① 包含头文件:包含 Boost.Serialization 和 Boost.Histogram 的头文件。
② 注册类:对于自定义的轴类型或累加器类型,需要在序列化之前进行注册。对于 Boost.Histogram 提供的标准轴和累加器,通常不需要显式注册。
③ 序列化代码:使用 Boost.Serialization 提供的 boost::archive::binary_oarchive
(二进制输出)或其他类型的 archive 对象,将直方图对象写入输出流。
④ 反序列化代码:使用 boost::archive::binary_iarchive
(二进制输入)或其他类型的 archive 对象,从输入流读取字节流,重建直方图对象。
实战代码:直方图的序列化与反序列化
1
#include <boost/histogram.hpp>
2
#include <boost/archive/binary_oarchive.hpp>
3
#include <boost/archive/binary_iarchive.hpp>
4
#include <fstream>
5
#include <iostream>
6
7
namespace bh = boost::histogram;
8
9
int main() {
10
// 创建一个直方图
11
auto h_original = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
12
for (int i = 0; i < 10; ++i) {
13
h_original.fill(i + 0.5);
14
}
15
16
// 序列化到文件
17
{
18
std::ofstream ofs("histogram.bin", std::ios::binary);
19
boost::archive::binary_oarchive oa(ofs);
20
oa << h_original; // 序列化直方图对象
21
}
22
23
// 从文件反序列化
24
bh::histogram<bh::axis::regular<>, bh::storage::default_storage> h_loaded; // 注意:需要指定直方图类型
25
{
26
std::ifstream ifs("histogram.bin", std::ios::binary);
27
boost::archive::binary_iarchive ia(ifs);
28
ia >> h_loaded; // 反序列化到直方图对象
29
}
30
31
// 验证反序列化结果
32
std::cout << "Original Histogram:\n";
33
for (int i = 0; i < 10; ++i) {
34
std::cout << "bin " << i << ": " << h_original[i] << "\n";
35
}
36
37
std::cout << "\nLoaded Histogram from File:\n";
38
for (int i = 0; i < 10; ++i) {
39
std::cout << "bin " << i << ": " << h_loaded[i] << "\n";
40
}
41
42
return 0;
43
}
代码解释:
⚝ 我们包含了 <boost/archive/binary_oarchive.hpp>
和 <boost/archive/binary_iarchive.hpp>
头文件,以及 <fstream>
用于文件操作。
⚝ 在序列化部分,我们创建了一个 std::ofstream
对象用于二进制文件输出,并创建 boost::archive::binary_oarchive
对象 oa
与输出流关联。使用 oa << h_original;
将直方图对象 h_original
序列化到文件 "histogram.bin"。
⚝ 在反序列化部分,我们创建了一个 std::ifstream
对象用于二进制文件输入,并创建 boost::archive::binary_iarchive
对象 ia
与输入流关联。使用 ia >> h_loaded;
从文件 "histogram.bin" 反序列化数据,重建直方图对象 h_loaded
。注意,反序列化时需要预先声明直方图对象的类型,确保类型与序列化时的类型一致。
⚝ 最后,我们打印原始直方图和反序列化后的直方图,验证数据是否一致。
高级应用:
直方图的序列化与持久化在以下场景中非常重要:
⚝ 数据存档:将实验数据或分析结果以直方图的形式保存,长期存储和管理。
⚝ 数据共享:将直方图数据共享给其他研究人员或团队,方便数据交换和协作。
⚝ 断点续算:在长时间运行的程序中,可以将中间结果(直方图)序列化到磁盘,程序中断后可以从断点恢复,避免重复计算。
⚝ 离线分析:在数据采集阶段,可以将数据实时填充到直方图中,然后将直方图序列化到磁盘,在离线状态下进行后续分析和可视化。
其他序列化方法
除了 Boost.Serialization,还可以使用其他序列化库或方法来持久化 Boost.Histogram 对象,例如:
⚝ JSON 格式:将直方图数据转换为 JSON 格式进行存储。可以使用 Boost.JSON 或其他 JSON 库来实现。JSON 格式具有良好的跨平台性和可读性,适合于 Web 应用或与其他语言的数据交换。
⚝ HDF5 格式:使用 HDF5 (Hierarchical Data Format version 5) 库存储直方图数据。HDF5 是一种高性能、高容量的数据存储格式,适合于存储大规模科学数据。
⚝ 自定义格式:根据具体需求,可以设计自定义的二进制或文本格式来存储直方图数据。
选择合适的序列化方法取决于应用场景、数据量、性能要求和与其他系统的兼容性等因素。
6.4 自定义直方图行为 (Customizing Histogram Behavior)
Boost.Histogram 提供了丰富的扩展点,允许用户自定义直方图的行为,以满足特定的需求。自定义主要体现在以下几个方面:
① 自定义轴类型 (Custom Axis Types):用户可以创建自己的轴类型,例如,非均匀间隔轴、循环轴或基于特定物理模型的轴。自定义轴类型需要实现轴接口,并提供箱索引计算、箱边界获取等方法。
② 自定义累加器 (Custom Accumulators):用户可以创建自己的累加器类型,用于存储和处理箱的统计量。例如,除了默认的计数累加器,还可以实现均值累加器、方差累加器、中位数累加器等。自定义累加器需要满足累加器概念的要求,并提供累加、合并等操作。
③ 自定义存储 (Custom Storage):用户可以创建自己的存储类型,用于管理直方图的箱数据。例如,可以使用稀疏矩阵存储来优化高维稀疏直方图的内存使用,或者使用分布式存储来处理大规模直方图。自定义存储需要实现存储接口,并提供数据访问、修改等方法.
④ 自定义行为扩展 (Custom Behavior Extensions):通过 Boost.Histogram 的扩展机制,用户可以添加自定义的行为,例如,自定义填充规则、自定义数据处理流程或自定义输出格式。
自定义轴类型示例 (概念)
假设我们需要创建一个循环轴(Cyclic Axis),例如,表示角度的轴,其范围为 \( [0, 360) \) 度,并且在 360 度和 0 度之间是连续的。我们可以自定义一个 cyclic_axis
类,继承自 bh::axis::axis_base
,并实现必要的接口。
1
// 伪代码,仅为概念演示
2
class cyclic_axis : public bh::axis::axis_base {
3
public:
4
cyclic_axis(int bins, double min, double max) : ... {}
5
6
// 实现轴接口方法,例如:
7
int index(double val) const override {
8
// 计算循环轴的箱索引
9
...
10
}
11
12
double bin_lower(int index) const override {
13
// 获取箱的下边界
14
...
15
}
16
17
double bin_upper(int index) const override {
18
// 获取箱的上边界
19
...
20
}
21
22
// ... 其他方法 ...
23
};
自定义累加器类型示例 (概念)
假设我们需要创建一个均值累加器(Mean Accumulator),用于在每个箱中存储数据的均值。我们可以自定义一个 mean_accumulator
类,实现累加器概念。
1
// 伪代码,仅为概念演示
2
class mean_accumulator {
3
public:
4
mean_accumulator() : sum_(0.0), count_(0) {}
5
6
void operator()(double val) {
7
sum_ += val;
8
count_ += 1;
9
}
10
11
mean_accumulator& operator+=(const mean_accumulator& other) {
12
sum_ += other.sum_;
13
count_ += other.count_;
14
return *this;
15
}
16
17
double value() const {
18
if (count_ == 0) return 0.0;
19
return sum_ / count_;
20
}
21
22
private:
23
double sum_;
24
int count_;
25
};
高级应用:
自定义直方图行为为 Boost.Histogram 提供了极大的灵活性和可扩展性,使其能够适应各种复杂的应用场景:
⚝ 特定领域的数据分析:针对物理学、工程学、金融学等特定领域的数据特点,定制轴类型和累加器,更有效地分析和表示领域数据。
⚝ 性能优化:通过自定义存储类型,优化大规模直方图的内存使用和计算性能。
⚝ 算法集成:将自定义的行为扩展与特定的算法或分析流程集成,构建定制化的数据分析工具。
⚝ 教学与研究:自定义功能为教学和研究提供了平台,可以探索新的直方图应用和算法。
自定义 Boost.Histogram 的行为需要深入理解库的架构和接口,并具备一定的 C++ 高级编程技能。但一旦掌握,将能够充分发挥 Boost.Histogram 的潜力,解决更复杂、更具挑战性的数据分析问题。
END_OF_CHAPTER
7. chapter 7: Boost.Histogram API 全面解析 (Comprehensive API Analysis of Boost.Histogram)
7.1 核心类:histogram (Core Class: histogram)
histogram
类是 Boost.Histogram 库的核心 (core)和灵魂 (soul),它代表了直方图这一数据结构的抽象 (abstraction)和实现 (implementation)。理解 histogram
类的 API 是掌握 Boost.Histogram 的关键。本节将深入剖析 histogram
类的构造函数 (constructors)、成员函数 (member functions) 以及其在直方图操作中的核心作用。
histogram
类是一个模板类 (template class),其定义通常如下所示:
1
template <typename AxesTuple, typename Storage = mp_dynamic_bin>
2
class histogram;
① 模板参数 (Template Parameters):
⚝ AxesTuple
:这是一个轴的元组 (tuple of axes),定义了直方图的维度和每个维度上的分箱规则。它可以是 std::tuple
或 boost::tuple
,包含一个或多个轴对象。轴对象决定了直方图的维度、范围和分箱方式。例如,一维直方图的 AxesTuple
可能只包含一个轴,而二维直方图则包含两个轴。
⚝ Storage
:这是一个存储策略 (storage policy),决定了直方图如何存储计数 (counts) 和其他累加值。默认的存储策略是 mp_dynamic_bin
,它使用动态内存分配来存储计数,可以有效地处理稀疏直方图。Boost.Histogram 提供了多种存储策略,以适应不同的内存和性能需求。
② 构造函数 (Constructors):
histogram
类提供了多种构造函数,允许用户以灵活的方式创建直方图。
⚝ 默认构造函数 (Default Constructor):
1
histogram() = delete;
默认构造函数被删除,这意味着你不能 (cannot)创建一个没有轴的 histogram
对象。直方图必须至少有一个轴来定义其维度。
⚝ 带轴元组的构造函数 (Constructor with Axes Tuple):
1
histogram(const AxesTuple& axes);
2
histogram(AxesTuple&& axes);
这是最常用的构造函数。它接受一个轴元组 axes
,用于初始化直方图的轴。你可以传递一个 const
引用或右值引用。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个一维直方图,使用规则轴,范围从 0 到 10,分为 10 个箱子
8
auto h1 = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
9
10
// 创建一个二维直方图,使用两个规则轴
11
auto h2 = bh::make_histogram(bh::axis::regular(5, 0.0, 5.0),
12
bh::axis::regular(5, 0.0, 5.0));
13
14
std::cout << "h1 dimension: " << h1.rank() << std::endl; // 输出:h1 dimension: 1
15
std::cout << "h2 dimension: " << h2.rank() << std::endl; // 输出:h2 dimension: 2
16
17
return 0;
18
}
在上面的例子中,bh::make_histogram
是一个辅助函数 (helper function),用于简化 histogram
对象的创建。它接受轴对象作为参数,并返回一个 histogram
对象。h1
是一个一维直方图,h2
是一个二维直方图。rank()
成员函数返回直方图的维度。
⚝ 拷贝构造函数 (Copy Constructor) 和 移动构造函数 (Move Constructor):
1
histogram(const histogram& other);
2
histogram(histogram&& other) noexcept;
histogram
类支持拷贝构造 (copy construction)和移动构造 (move construction),允许你复制或移动直方图对象。移动构造通常更高效,因为它避免了深拷贝。
③ 成员函数 (Member Functions):
histogram
类提供了丰富的成员函数,用于操作和访问直方图的数据。
⚝ rank()
:
1
std::size_t rank() const noexcept;
返回直方图的维度 (dimension),即轴的数量。
1
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0),
2
bh::axis::regular(5, 0.0, 5.0),
3
bh::axis::category({"A", "B", "C"}));
4
std::cout << "histogram dimension: " << h.rank() << std::endl; // 输出:histogram dimension: 3
⚝ axis(i)
:
1
const axis::variant& axis(std::size_t i) const;
返回索引为 i
的轴的常量引用 (const reference)。索引 i
从 0 开始。返回类型 axis::variant
是一个变体类型 (variant type),可以容纳不同类型的轴对象。
1
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0),
2
bh::axis::category({"A", "B", "C"}));
3
std::cout << "axis 0 type: " << h.axis(0).type_name() << std::endl; // 输出:axis 0 type: regular_axis
4
std::cout << "axis 1 type: " << h.axis(1).type_name() << std::endl; // 输出:axis 1 type: category_axis
type_name()
是 axis::variant
的成员函数,用于获取轴类型的名称。
⚝ fill(args...)
:
1
template <typename... Args>
2
void fill(Args&&... args);
3
4
template <typename... Args>
5
void fill(weight_t w, Args&&... args);
fill()
函数用于填充 (fill)直方图。它接受与直方图维度数量相同的参数,每个参数对应一个轴的观测值 (observation value)。第一个重载版本使用默认权重 1,第二个重载版本允许你指定权重 (weight) w
。
1
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
2
h.fill(3.5); // 使用默认权重 1 填充值 3.5
3
h.fill(7.2, bh::weight(2.0)); // 使用权重 2.0 填充值 7.2
bh::weight(w)
用于创建一个权重对象 (weight object),传递给 fill()
函数以指定权重。
⚝ at(indices...)
:
1
template <typename... Indices>
2
storage_type& at(Indices... indices);
3
4
template <typename... Indices>
5
const storage_type& at(Indices... indices) const;
at()
函数用于访问 (access)直方图的箱子 (bin)。它接受与直方图维度数量相同的索引参数,每个索引对应一个轴的箱子索引。索引从 0 开始。返回类型 storage_type&
或 const storage_type&
是对存储在指定箱子中的累加器 (accumulator)的引用。
1
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
2
h.fill(3.5);
3
h.fill(3.5);
4
h.fill(7.2);
5
6
std::cout << "bin index 3 count: " << h.at(3) << std::endl; // 输出:bin index 3 count: 2
7
std::cout << "bin index 7 count: " << h.at(7) << std::endl; // 输出:bin index 7 count: 1
在上面的例子中,值 3.5 落入索引为 3 的箱子(假设轴范围是 [0, 10],分为 10 个箱子),值 7.2 落入索引为 7 的箱子。at(3)
返回索引为 3 的箱子的累加器引用,其值表示该箱子的计数。
⚝ operator[](indices...)
:
1
template <typename... Indices>
2
const storage_type& operator[](Indices... indices) const;
operator[]
提供了类似数组的访问方式 (array-like access),用于访问直方图的箱子。它与 at()
函数类似,但只能用于常量直方图 (const histogram),并且只返回常量引用。
1
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
2
h.fill(3.5);
3
const auto& const_h = h;
4
std::cout << "bin index 3 count (const): " << const_h[3] << std::endl; // 输出:bin index 3 count (const): 1
⚝ reset()
:
1
void reset();
reset()
函数用于重置 (reset)直方图的所有计数器。它将所有箱子的累加器值设置为默认值 (default value),通常为 0。直方图的轴配置保持不变。
1
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
2
h.fill(3.5);
3
std::cout << "bin index 3 count before reset: " << h.at(3) << std::endl; // 输出:bin index 3 count before reset: 1
4
h.reset();
5
std::cout << "bin index 3 count after reset: " << h.at(3) << std::endl; // 输出:bin index 3 count after reset: 0
⚝ sum()
:
1
value_type sum() const noexcept;
sum()
函数返回直方图所有箱子的计数总和 (sum of counts)。对于使用权重累加器的直方图,它返回权重总和 (sum of weights)。
1
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
2
h.fill(1.0);
3
h.fill(2.0);
4
h.fill(3.0, bh::weight(2.0));
5
std::cout << "sum of counts/weights: " << h.sum() << std::endl; // 输出:sum of counts/weights: 4
⚝ 迭代器 (Iterators):
histogram
类提供了迭代器 (iterators),用于遍历直方图的箱子。
⚝ indexed()
:
1
auto indexed() const;
1
`indexed()` 返回一个**范围对象 (range object)**,用于迭代直方图的箱子,并同时提供**箱子的索引 (bin indices)**和**累加器值 (accumulator value)**。
1
auto h = bh::make_histogram(bh::axis::regular(3, 0.0, 3.0));
2
h.fill(0.5);
3
h.fill(1.5);
4
h.fill(2.5);
5
6
for (auto x : h.indexed()) {
7
std::cout << "indices: " << x.indices() << ", value: " << *x << std::endl;
8
}
9
// 输出:
10
// indices: [0], value: 1
11
// indices: [1], value: 1
12
// indices: [2], value: 1
13
// indices: [3], value: 0 (overflow bin)
14
// indices: [-1], value: 0 (underflow bin)
1
`x.indices()` 返回一个**索引元组 (tuple of indices)**,`*x` 解引用迭代器得到**累加器对象 (accumulator object)**。
⚝ value()
:
1
auto value() const;
1
`value()` 返回一个**范围对象 (range object)**,用于迭代直方图的箱子,只提供**累加器值 (accumulator value)**,不提供箱子索引。
1
auto h = bh::make_histogram(bh::axis::regular(3, 0.0, 3.0));
2
h.fill(0.5);
3
h.fill(1.5);
4
h.fill(2.5);
5
6
for (auto val : h.value()) {
7
std::cout << "value: " << val << std::endl;
8
}
9
// 输出:
10
// value: 1
11
// value: 1
12
// value: 1
13
// value: 0
14
// value: 0
④ 总结 (Summary):
histogram
类是 Boost.Histogram 库的核心,提供了创建、填充、访问和操作直方图的完整 API。理解其构造函数、fill()
、at()
、reset()
、sum()
和迭代器等核心成员函数,是使用 Boost.Histogram 进行数据分析和统计的基础。在后续章节中,我们将结合具体的轴类型和存储策略,进一步深入探讨 histogram
类的应用。
7.2 轴类:axis (Axis Classes: axis)
轴 (axis) 是直方图的骨架 (backbone),它定义了直方图的维度 (dimension)、范围 (range) 和 分箱规则 (binning rules)。Boost.Histogram 提供了多种预定义的轴类型,以满足不同的数据分析需求,同时也支持用户自定义轴类型。本节将详细介绍 Boost.Histogram 中常用的轴类,包括规则轴 (regular axis)、可变轴 (variable axis)、整数轴 (integer axis)、分类轴 (category axis) 和 自定义轴 (custom axis)。
① 轴的通用概念 (General Concepts of Axes):
所有轴类都共享一些通用概念 (common concepts)和 API 接口 (API interfaces)。
⚝ 箱子索引 (Bin Indexing):轴将数据值映射到箱子索引 (bin index)。箱子索引通常是整数 (integer),从 0 开始,到 bins() - 1
结束,其中 bins()
返回轴的箱子数量。除了常规箱子,轴通常还提供溢出箱 (overflow bin) 和 欠流箱 (underflow bin),用于存储超出轴范围的数据。溢出箱的索引通常是 bins()
,欠流箱的索引通常是 -1
。
⚝ 轴范围 (Axis Range):轴定义了数据的有效范围。对于连续轴 (continuous axis)(如规则轴、可变轴),轴范围由最小值 (minimum value) 和 最大值 (maximum value) 确定。对于离散轴 (discrete axis)(如整数轴、分类轴),轴范围由离散值的集合 (set of discrete values) 确定。
⚝ 轴类型 (Axis Type):Boost.Histogram 使用 axis::variant
来统一管理不同类型的轴对象。axis::variant
是一个变体类型 (variant type),可以容纳不同类型的轴对象,例如 regular_axis
, variable_axis
等。这使得 API 设计更加灵活和通用。
② 规则轴:regular_axis
(Regular Axis):
规则轴 (regular axis) 是最常用的轴类型之一。它将一个连续范围 (continuous range) 均匀地划分为固定数量的箱子 (fixed number of bins)。
⚝ 构造函数 (Constructors):
1
regular_axis(unsigned bins, double lower, double upper, const char* name = nullptr);
2
regular_axis(unsigned bins, double lower, double upper, const metadata_type& metadata);
▮▮▮▮⚝ bins
:箱子数量,必须是正整数 (positive integer)。
▮▮▮▮⚝ lower
:轴的下界 (lower bound)。
▮▮▮▮⚝ upper
:轴的上界 (upper bound)。
▮▮▮▮⚝ name
:轴的名称 (name),可选,用于标识轴。
▮▮▮▮⚝ metadata
:轴的元数据 (metadata),可选,可以是任何类型,用于存储轴的附加信息。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个规则轴,10 个箱子,范围 [0, 10]
8
auto axis1 = bh::axis::regular(10, 0.0, 10.0);
9
10
// 创建一个规则轴,20 个箱子,范围 [-5, 5],并命名为 "x"
11
auto axis2 = bh::axis::regular(20, -5.0, 5.0, "x");
12
13
std::cout << "axis1 bins: " << axis1.bins() << std::endl; // 输出:axis1 bins: 10
14
std::cout << "axis1 lower bound: " << axis1.lower() << std::endl; // 输出:axis1 lower bound: 0
15
std::cout << "axis1 upper bound: " << axis1.upper() << std::endl; // 输出:axis1 upper bound: 10
16
std::cout << "axis2 name: " << axis2.name() << std::endl; // 输出:axis2 name: x
17
18
return 0;
19
}
⚝ 常用成员函数 (Common Member Functions):
▮▮▮▮⚝ bins()
:返回箱子数量。
▮▮▮▮⚝ lower()
:返回轴的下界。
▮▮▮▮⚝ upper()
:返回轴的上界。
▮▮▮▮⚝ bin(value)
:返回给定值 value
所属的箱子索引 (bin index)。如果值超出轴范围,则返回溢出箱或欠流箱的索引。
▮▮▮▮⚝ value(index)
:返回给定箱子索引 index
的中心值 (center value)。
▮▮▮▮⚝ width(index)
:返回给定箱子索引 index
的宽度 (width)。
1
auto axis = bh::axis::regular(5, 0.0, 5.0);
2
std::cout << "value 2.5 bin index: " << axis.bin(2.5) << std::endl; // 输出:value 2.5 bin index: 2
3
std::cout << "bin index 2 center value: " << axis.value(2) << std::endl; // 输出:bin index 2 center value: 2.5
4
std::cout << "bin index 2 width: " << axis.width(2) << std::endl; // 输出:bin index 2 width: 1
③ 可变轴:variable_axis
(Variable Axis):
可变轴 (variable axis) 允许用户自定义不均匀的箱子边界 (non-uniform bin boundaries)。箱子边界由一个递增的数值序列 (increasing sequence of numbers) 指定。
⚝ 构造函数 (Constructors):
1
variable_axis(std::vector<double> edges, const char* name = nullptr);
2
variable_axis(std::vector<double> edges, const metadata_type& metadata);
3
variable_axis(std::initializer_list<double> edges, const char* name = nullptr);
4
variable_axis(std::initializer_list<double> edges, const metadata_type& metadata);
▮▮▮▮⚝ edges
:一个向量 (vector) 或 初始化列表 (initializer list),包含箱子的边界值 (edge values)。边界值必须是递增的 (increasing)。箱子的数量是 edges.size() - 1
。
▮▮▮▮⚝ name
:轴的名称,可选。
▮▮▮▮⚝ metadata
:轴的元数据,可选。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
#include <vector>
4
5
namespace bh = boost::histogram;
6
7
int main() {
8
// 创建一个可变轴,边界为 {0, 1, 3, 6},定义了 3 个箱子:[0, 1), [1, 3), [3, 6)
9
auto axis1 = bh::axis::variable({0.0, 1.0, 3.0, 6.0});
10
11
// 创建一个可变轴,边界为 {1, 2, 4, 8, 16},并命名为 "energy"
12
std::vector<double> edges = {1.0, 2.0, 4.0, 8.0, 16.0};
13
auto axis2 = bh::axis::variable(edges, "energy");
14
15
std::cout << "axis1 bins: " << axis1.bins() << std::endl; // 输出:axis1 bins: 3
16
std::cout << "axis2 bins: " << axis2.bins() << std::endl; // 输出:axis2 bins: 4
17
std::cout << "axis2 name: " << axis2.name() << std::endl; // 输出:axis2 name: energy
18
19
return 0;
20
}
⚝ 常用成员函数 (Common Member Functions):
▮▮▮▮⚝ bins()
:返回箱子数量。
▮▮▮▮⚝ edges()
:返回箱子边界值的常量引用 (const reference)。
▮▮▮▮⚝ bin(value)
:返回给定值 value
所属的箱子索引。
▮▮▮▮⚝ value(index)
:返回给定箱子索引 index
的中心值 (center value)(通常是箱子边界的平均值)。
▮▮▮▮⚝ width(index)
:返回给定箱子索引 index
的宽度 (width)(箱子边界之差)。
1
auto axis = bh::axis::variable({0.0, 1.0, 3.0, 6.0});
2
std::cout << "value 2.0 bin index: " << axis.bin(2.0) << std::endl; // 输出:value 2.0 bin index: 1
3
std::cout << "bin index 1 center value: " << axis.value(1) << std::endl; // 输出:bin index 1 center value: 2
4
std::cout << "bin index 1 width: " << axis.width(1) << std::endl; // 输出:bin index 1 width: 2
④ 整数轴:integer_axis
(Integer Axis):
整数轴 (integer axis) 用于表示整数值 (integer values) 的直方图。它将一个整数范围 (integer range) 内的每个整数值作为一个独立的箱子。
⚝ 构造函数 (Constructors):
1
integer_axis(int lower, int upper, const char* name = nullptr);
2
integer_axis(int lower, int upper, const metadata_type& metadata);
▮▮▮▮⚝ lower
:轴的下界 (lower bound),包含在范围内。
▮▮▮▮⚝ upper
:轴的上界 (upper bound),不包含在范围内。箱子的范围是 [lower, upper)
。
▮▮▮▮⚝ name
:轴的名称,可选。
▮▮▮▮⚝ metadata
:轴的元数据,可选。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
// 创建一个整数轴,范围 [0, 5),包含整数 0, 1, 2, 3, 4
8
auto axis1 = bh::axis::integer(0, 5);
9
10
// 创建一个整数轴,范围 [-2, 3),并命名为 "id"
11
auto axis2 = bh::axis::integer(-2, 3, "id");
12
13
std::cout << "axis1 bins: " << axis1.bins() << std::endl; // 输出:axis1 bins: 5
14
std::cout << "axis1 lower bound: " << axis1.lower() << std::endl; // 输出:axis1 lower bound: 0
15
std::cout << "axis1 upper bound: " << axis1.upper() << std::endl; // 输出:axis1 upper bound: 5
16
std::cout << "axis2 name: " << axis2.name() << std::endl; // 输出:axis2 name: id
17
18
return 0;
19
}
⚝ 常用成员函数 (Common Member Functions):
▮▮▮▮⚝ bins()
:返回箱子数量,等于 upper - lower
。
▮▮▮▮⚝ lower()
:返回轴的下界。
▮▮▮▮⚝ upper()
:返回轴的上界。
▮▮▮▮⚝ bin(value)
:返回给定整数值 value
所属的箱子索引。
▮▮▮▮⚝ value(index)
:返回给定箱子索引 index
对应的整数值。
▮▮▮▮⚝ width(index)
:对于整数轴,箱子宽度始终为 1。
1
auto axis = bh::axis::integer(0, 5);
2
std::cout << "value 3 bin index: " << axis.bin(3) << std::endl; // 输出:value 3 bin index: 3
3
std::cout << "bin index 3 value: " << axis.value(3) << std::endl; // 输出:bin index 3 value: 3
4
std::cout << "bin index 3 width: " << axis.width(3) << std::endl; // 输出:bin index 3 width: 1
⑤ 分类轴:category_axis
(Category Axis):
分类轴 (category axis) 用于表示离散的类别 (discrete categories) 或 标签 (labels)。每个类别对应一个独立的箱子。
⚝ 构造函数 (Constructors):
1
category_axis(std::vector<std::string> categories, const char* name = nullptr);
2
category_axis(std::vector<std::string> categories, const metadata_type& metadata);
3
category_axis(std::initializer_list<std::string> categories, const char* name = nullptr);
4
category_axis(std::initializer_list<std::string> categories, const metadata_type& metadata);
▮▮▮▮⚝ categories
:一个字符串向量 (vector of strings) 或 字符串初始化列表 (initializer list of strings),包含类别的名称。
▮▮▮▮⚝ name
:轴的名称,可选。
▮▮▮▮⚝ metadata
:轴的元数据,可选。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
#include <vector>
4
#include <string>
5
6
namespace bh = boost::histogram;
7
8
int main() {
9
// 创建一个分类轴,类别为 {"A", "B", "C"}
10
auto axis1 = bh::axis::category({"A", "B", "C"});
11
12
// 创建一个分类轴,类别为 {"apple", "banana", "cherry"},并命名为 "fruit"
13
std::vector<std::string> categories = {"apple", "banana", "cherry"};
14
auto axis2 = bh::axis::category(categories, "fruit");
15
16
std::cout << "axis1 bins: " << axis1.bins() << std::endl; // 输出:axis1 bins: 3
17
std::cout << "axis2 name: " << axis2.name() << std::endl; // 输出:axis2 name: fruit
18
19
return 0;
20
}
⚝ 常用成员函数 (Common Member Functions):
▮▮▮▮⚝ bins()
:返回类别数量。
▮▮▮▮⚝ categories()
:返回类别名称的常量引用 (const reference)。
▮▮▮▮⚝ bin(category_name)
:返回给定类别名称 category_name
所属的箱子索引。
▮▮▮▮⚝ value(index)
:返回给定箱子索引 index
对应的类别名称。
▮▮▮▮⚝ width(index)
:对于分类轴,箱子宽度没有实际意义,通常返回 1。
1
auto axis = bh::axis::category({"A", "B", "C"});
2
std::cout << "category 'B' bin index: " << axis.bin("B") << std::endl; // 输出:category 'B' bin index: 1
3
std::cout << "bin index 1 category: " << axis.value(1) << std::endl; // 输出:bin index 1 category: B
4
std::cout << "bin index 1 width: " << axis.width(1) << std::endl; // 输出:bin index 1 width: 1
⑥ 自定义轴:custom_axis
(Custom Axis):
自定义轴 (custom axis) 允许用户完全自定义轴的行为 (customize axis behavior)。用户需要提供一个自定义的箱子查找函数 (custom bin lookup function),该函数将数据值映射到箱子索引。自定义轴提供了最大的灵活性,可以满足各种特殊的分箱需求。关于自定义轴的详细内容将在后续章节中深入探讨。
⑦ 轴的配置与选项 (Axis Configuration and Options):
除了基本的轴类型,Boost.Histogram 还提供了一些配置选项 (configuration options),用于进一步定制轴的行为。这些选项通常通过修饰器 (decorators) 或 适配器 (adapters) 来实现,例如:
⚝ options::underflow_overflown
:使轴同时启用欠流箱 (underflow bin) 和 溢出箱 (overflow bin)。(默认行为)
⚝ options::underflow
:仅启用欠流箱 (underflow bin)。
⚝ options::overflow
:仅启用溢出箱 (overflow bin)。
⚝ options::none
:禁用欠流箱 (underflow bin) 和 溢出箱 (overflow bin)。
这些选项可以作为额外的参数 (additional arguments) 传递给轴的构造函数。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
namespace o = boost::histogram::options;
6
7
int main() {
8
// 创建一个规则轴,禁用欠流箱和溢出箱
9
auto axis1 = bh::axis::regular(10, 0.0, 10.0, o::none);
10
11
// 创建一个规则轴,仅启用溢出箱
12
auto axis2 = bh::axis::regular(10, 0.0, 10.0, o::overflow);
13
14
std::cout << "axis1 has underflow bin: " << axis1.options().underflow() << std::endl; // 输出:axis1 has underflow bin: false
15
std::cout << "axis1 has overflow bin: " << axis1.options().overflow() << std::endl; // 输出:axis1 has overflow bin: false
16
std::cout << "axis2 has underflow bin: " << axis2.options().underflow() << std::endl; // 输出:axis2 has underflow bin: false
17
std::cout << "axis2 has overflow bin: " << axis2.options().overflow() << std::endl; // 输出:axis2 has overflow bin: true
18
19
return 0;
20
}
⑧ 总结 (Summary):
轴是 Boost.Histogram 的核心组成部分,负责定义直方图的维度和分箱规则。Boost.Histogram 提供了多种轴类型,包括规则轴、可变轴、整数轴和分类轴,以满足不同的数据分析需求。用户可以根据数据的特点和分析目标选择合适的轴类型。理解各种轴类型的构造函数、成员函数和配置选项,是灵活运用 Boost.Histogram 的关键。在后续章节中,我们将结合实战代码,演示如何使用不同类型的轴来创建和操作直方图。
7.3 累加器相关 API (Accumulator Related APIs)
累加器 (accumulator) 负责存储 (store) 和 累积 (accumulate) 落在直方图箱子中的数据。Boost.Histogram 提供了多种预定义的累加器类型,以支持不同的数据统计需求,同时也允许用户自定义累加器类型。本节将详细介绍 Boost.Histogram 中常用的累加器类型和相关的 API。
① 累加器的基本概念 (Basic Concepts of Accumulators):
⚝ 计数 (Count):最基本的累加器是计数器 (counter),它简单地统计 (count) 落入每个箱子的数据点的数量。默认情况下,Boost.Histogram 使用整数计数器 (integer counter) 作为累加器。
⚝ 权重 (Weight):在某些情况下,数据点可能具有权重 (weight)。例如,在模拟实验中,每个事件可能具有不同的权重,表示其重要性或概率。为了处理带权重的数据,Boost.Histogram 提供了权重累加器 (weight accumulator),它可以累积数据点的权重总和,并同时跟踪权重的平方和,用于计算误差 (error)。
⚝ 自定义累加器 (Custom Accumulators):Boost.Histogram 允许用户自定义累加器类型 (custom accumulator types),以实现更复杂的数据统计功能。例如,用户可以创建累加器来计算箱子内数据的平均值 (average)、标准差 (standard deviation)、最小值 (minimum)、最大值 (maximum) 等统计量。
② 默认累加器 (Default Accumulator):
默认情况下,Boost.Histogram 使用整数存储 (integer storage) 和 默认累加器 (default accumulator)。默认累加器就是一个简单的整数计数器 (integer counter),类型为 std::int64_t
。当你使用 histogram<AxesTuple>
创建直方图时,默认使用整数存储和默认累加器。
⚝ API 接口 (API Interface):默认累加器类型是 std::int64_t
,它支持基本的算术运算 (arithmetic operations),例如 +=
(加法赋值)、+
(加法)、=
(赋值) 等。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
8
h.fill(1.5);
9
h.fill(1.5);
10
h.fill(2.5);
11
12
auto count1 = h.at(1); // 获取索引为 1 的箱子的累加器值
13
auto count2 = h.at(2); // 获取索引为 2 的箱子的累加器值
14
15
std::cout << "bin 1 count: " << count1 << std::endl; // 输出:bin 1 count: 2
16
std::cout << "bin 2 count: " << count2 << std::endl; // 输出:bin 2 count: 1
17
18
return 0;
19
}
在上面的例子中,h.at(1)
和 h.at(2)
返回的是 std::int64_t
类型的累加器值,可以直接作为整数使用。
③ 权重累加器:weight_counter<T>
(Weight Accumulator):
权重累加器 (weight accumulator) 用于处理带权重的数据。Boost.Histogram 提供了 weight_counter<T>
模板类,其中 T
是权重值的类型,通常是 double
或 float
。weight_counter<T>
内部存储权重总和 (sum of weights) 和 权重平方和 (sum of squared weights)。
⚝ 构造函数 (Constructors):
1
weight_counter() noexcept;
2
weight_counter(double value) noexcept;
3
weight_counter(double value, double variance) noexcept;
▮▮▮▮⚝ 默认构造函数创建一个权重累加器,权重总和和平方和都初始化为 0。
▮▮▮▮⚝ weight_counter(value)
创建一个权重累加器,权重总和初始化为 value
,平方和初始化为 value * value
。
▮▮▮▮⚝ weight_counter(value, variance)
创建一个权重累加器,权重总和初始化为 value
,平方和初始化为 variance
。
⚝ API 接口 (API Interface):weight_counter<T>
提供了以下成员函数:
▮▮▮▮⚝ sample()
:返回权重总和 (sum of weights)。
▮▮▮▮⚝ variance()
:返回权重平方和 (sum of squared weights)。
▮▮▮▮⚝ value()
:返回权重总和 (sum of weights)。(与 sample()
相同)
▮▮▮▮⚝ operator+= (weight_counter<T> other)
:将另一个权重累加器 other
的值累加到当前累加器。
▮▮▮▮⚝ operator+= (weight_t w)
:将权重 w
累加到当前累加器。
▮▮▮▮⚝ operator double()
:隐式转换为 double
类型,返回权重总和。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0),
8
bh::storage::weight()); // 使用权重存储
9
10
h.fill(1.5, bh::weight(2.0));
11
h.fill(1.5, bh::weight(3.0));
12
h.fill(2.5, bh::weight(1.5));
13
14
auto weight_acc1 = h.at(1); // 获取索引为 1 的箱子的权重累加器
15
auto weight_acc2 = h.at(2); // 获取索引为 2 的箱子的权重累加器
16
17
std::cout << "bin 1 weight sum: " << weight_acc1.value() << std::endl; // 输出:bin 1 weight sum: 5
18
std::cout << "bin 1 weight variance: " << weight_acc1.variance() << std::endl; // 输出:bin 1 weight variance: 13
19
std::cout << "bin 2 weight sum: " << weight_acc2.value() << std::endl; // 输出:bin 2 weight sum: 1.5
20
21
return 0;
22
}
在上面的例子中,我们使用 bh::storage::weight()
指定直方图使用权重存储 (weight storage)。h.at(1)
和 h.at(2)
返回的是 bh::weight_counter<double>
类型的权重累加器对象。我们可以使用 value()
和 variance()
成员函数获取权重总和和平方和。
④ 自定义累加器 (Custom Accumulators):
Boost.Histogram 允许用户自定义累加器类型 (custom accumulator types),以满足更复杂的数据统计需求。要创建自定义累加器,你需要定义一个类 (class) 或 结构体 (struct),并实现一些必要的操作 (necessary operations),例如 operator+=
(加法赋值)、默认构造函数 (default constructor)、拷贝构造函数 (copy constructor) 等。
例如,我们可以创建一个自定义累加器来计算箱子内数据的平均值 (average):
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
struct MeanAccumulator {
7
double sum = 0.0;
8
std::size_t count = 0;
9
10
MeanAccumulator() = default;
11
MeanAccumulator(const MeanAccumulator&) = default;
12
MeanAccumulator& operator=(const MeanAccumulator&) = default;
13
14
MeanAccumulator& operator+=(double value) {
15
sum += value;
16
++count;
17
return *this;
18
}
19
20
double value() const {
21
return count == 0 ? 0.0 : sum / count;
22
}
23
};
24
25
namespace boost::histogram {
26
template <>
27
struct is_accumulator<MeanAccumulator> : std::true_type {};
28
} // namespace boost::histogram
29
30
31
int main() {
32
using namespace boost::histogram;
33
auto h = make_histogram(axis::regular(10, 0.0, 10.0),
34
storage::unlimited<MeanAccumulator>()); // 使用 unlimited 存储和自定义累加器
35
36
h.fill(1.0);
37
h.fill(2.0);
38
h.fill(3.0);
39
h.fill(4.0);
40
41
auto mean_acc = h.at(0); // 获取索引为 0 的箱子的自定义累加器
42
std::cout << "bin 0 mean value: " << mean_acc.value() << std::endl; // 输出:bin 0 mean value: 2.5
43
44
return 0;
45
}
在上面的例子中,我们定义了一个 MeanAccumulator
结构体,用于计算平均值。我们重载了 operator+=
来累加数据值,并提供了 value()
函数来获取平均值。我们使用 bh::storage::unlimited<MeanAccumulator>()
指定直方图使用 unlimited 存储 (unlimited storage) 和我们的自定义累加器类型。
注意 (Note):为了让 Boost.Histogram 识别自定义累加器类型,我们需要特化 (specialize) boost::histogram::is_accumulator
模板类。
⑤ 选择合适的存储与累加器 (Choosing the Right Storage and Accumulator):
选择合适的存储类型 (storage type) 和 累加器类型 (accumulator type) 取决于你的数据分析需求 (data analysis needs) 和 性能要求 (performance requirements)。
⚝ 整数存储 (Integer Storage) 和 默认累加器 (Default Accumulator):适用于计数直方图 (counting histograms),即只需要统计数据点数量的场景。内存效率高 (memory efficient),性能好 (good performance)。
⚝ 权重存储 (Weight Storage) 和 权重累加器 (Weight Accumulator):适用于带权重的数据 (weighted data) 分析。可以计算权重总和和误差。内存占用稍高 (slightly higher memory footprint),性能略有下降 (slightly lower performance)。
⚝ Unlimited 存储 (Unlimited Storage) 和 自定义累加器 (Custom Accumulators):适用于复杂的数据统计 (complex data statistics) 需求,例如计算平均值、标准差等。灵活性高 (high flexibility),但内存占用和性能取决于自定义累加器的实现 (memory footprint and performance depend on the implementation of custom accumulators)。
⑥ 总结 (Summary):
累加器是 Boost.Histogram 中负责数据累积和统计的核心组件。Boost.Histogram 提供了默认累加器、权重累加器和自定义累加器等多种选择,以满足不同的数据分析需求。理解各种累加器类型的 API 和适用场景,并根据实际需求选择合适的累加器,是高效使用 Boost.Histogram 的关键。在后续章节中,我们将结合实战案例,进一步演示如何使用不同类型的累加器来构建和分析直方图。
7.4 自由函数与工具函数 (Free Functions and Utility Functions)
除了 histogram
类和轴类提供的成员函数外,Boost.Histogram 还提供了一系列自由函数 (free functions) 和 工具函数 (utility functions),用于执行各种直方图操作,例如直方图变换 (histogram transformations)、算术运算 (arithmetic operations)、序列化 (serialization) 等。这些函数通常在 boost::histogram
命名空间下定义。本节将介绍一些常用的自由函数和工具函数。
① 直方图变换与操作相关函数 (Functions for Histogram Transformations and Operations):
⚝ project(histogram& h, axis_indices...)
:投影 (projection) 函数,用于降低直方图的维度 (reduce histogram dimension)。它接受一个直方图 h
和要保留的轴索引 (axis indices) 作为参数,返回一个新的低维直方图,其中只包含指定的轴。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
auto h2d = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0, "x"),
8
bh::axis::regular(5, 0.0, 5.0, "y"));
9
h2d.fill(2.5, 3.5);
10
h2d.fill(3.5, 1.5);
11
12
// 投影到 x 轴,保留第 0 轴 (x 轴)
13
auto h1dx = bh::project(h2d, 0);
14
// 投影到 y 轴,保留第 1 轴 (y 轴)
15
auto h1dy = bh::project(h2d, 1);
16
17
std::cout << "h2d dimension: " << h2d.rank() << std::endl; // 输出:h2d dimension: 2
18
std::cout << "h1dx dimension: " << h1dx.rank() << std::endl; // 输出:h1dx dimension: 1
19
std::cout << "h1dy dimension: " << h1dy.rank() << std::endl; // 输出:h1dy dimension: 1
20
std::cout << "h1dx sum: " << h1dx.sum() << std::endl; // 输出:h1dx sum: 2
21
std::cout << "h1dy sum: " << h1dy.sum() << std::endl; // 输出:h1dy sum: 2
22
23
return 0;
24
}
bh::project(h2d, 0)
返回一个新的直方图 h1dx
,它是 h2d
在 x 轴上的投影,即对 y 轴求和。bh::project(h2d, 1)
类似,投影到 y 轴。
⚝ rebin(axis& a, unsigned factor)
:重分箱 (rebinning) 函数,用于合并轴的箱子 (merge bins of an axis)。它接受一个轴 a
和一个因子 (factor) factor
作为参数,返回一个新的轴,其中每 factor
个原始箱子合并为一个新的箱子。重分箱操作通常用于平滑直方图 (smooth histograms) 或 降低直方图分辨率 (reduce histogram resolution)。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
auto axis_orig = bh::axis::regular(10, 0.0, 10.0);
8
auto h_orig = bh::make_histogram(axis_orig);
9
for (int i = 0; i < 10; ++i) {
10
h_orig.fill(i + 0.5);
11
}
12
13
// 对轴进行 2 倍重分箱,每 2 个箱子合并为一个
14
auto axis_rebin = bh::axis::rebin(axis_orig, 2);
15
auto h_rebin = bh::make_histogram(axis_rebin);
16
// 将原始直方图的数据填充到重分箱后的直方图中
17
for (auto x : h_orig.indexed()) {
18
h_rebin.at(x.indices()[0]) += *x;
19
}
20
21
std::cout << "original axis bins: " << axis_orig.bins() << std::endl; // 输出:original axis bins: 10
22
std::cout << "rebin axis bins: " << axis_rebin.bins() << std::endl; // 输出:rebin axis bins: 5
23
std::cout << "original histogram sum: " << h_orig.sum() << std::endl; // 输出:original histogram sum: 10
24
std::cout << "rebin histogram sum: " << h_rebin.sum() << std::endl; // 输出:rebin histogram sum: 10
25
26
return 0;
27
}
bh::axis::rebin(axis_orig, 2)
返回一个新的轴 axis_rebin
,它是 axis_orig
重分箱后的轴。注意,rebin
函数只操作轴,不直接操作直方图的数据。你需要手动将原始直方图的数据累加到重分箱后的直方图中。
② 直方图算术运算相关函数 (Functions for Arithmetic Operations on Histograms):
Boost.Histogram 支持直方图之间的算术运算 (arithmetic operations),例如加法 (addition)、减法 (subtraction)、乘法 (multiplication)、除法 (division) 等。这些运算通常是逐箱子 (bin-wise) 进行的。
⚝ h1 + h2
:直方图加法,返回一个新的直方图,其箱子值是 h1
和 h2
对应箱子值的和 (sum)。
⚝ h1 - h2
:直方图减法,返回一个新的直方图,其箱子值是 h1
和 h2
对应箱子值的差 (difference)。
⚝ h1 * h2
:直方图乘法,返回一个新的直方图,其箱子值是 h1
和 h2
对应箱子值的乘积 (product)。
⚝ h1 / h2
:直方图除法,返回一个新的直方图,其箱子值是 h1
和 h2
对应箱子值的商 (quotient)。
⚝ h * scalar
或 scalar * h
:直方图与标量乘法,返回一个新的直方图,其箱子值是原始直方图箱子值与标量的乘积 (product)。
⚝ h / scalar
:直方图与标量除法,返回一个新的直方图,其箱子值是原始直方图箱子值与标量的商 (quotient)。
注意 (Note):进行直方图算术运算时,参与运算的直方图必须具有相同的轴配置 (same axis configuration),即维度、轴类型、箱子数量、轴范围等必须完全相同。否则,运算结果可能是未定义的。
1
#include <boost/histogram.hpp>
2
#include <iostream>
3
4
namespace bh = boost::histogram;
5
6
int main() {
7
auto h1 = bh::make_histogram(bh::axis::regular(5, 0.0, 5.0));
8
auto h2 = bh::make_histogram(bh::axis::regular(5, 0.0, 5.0));
9
10
h1.fill(1.5);
11
h1.fill(2.5);
12
h2.fill(2.5);
13
h2.fill(3.5);
14
h2.fill(3.5);
15
16
auto h_sum = h1 + h2; // 直方图加法
17
auto h_diff = h1 - h2; // 直方图减法
18
auto h_mul = h1 * h2; // 直方图乘法
19
auto h_div = h1 / h2; // 直方图除法
20
auto h_scale = h1 * 2.0; // 直方图与标量乘法
21
22
std::cout << "h_sum sum: " << h_sum.sum() << std::endl; // 输出:h_sum sum: 5
23
std::cout << "h_diff sum: " << h_diff.sum() << std::endl; // 输出:h_diff sum: -1
24
std::cout << "h_mul sum: " << h_mul.sum() << std::endl; // 输出:h_mul sum: 2
25
std::cout << "h_div sum: " << h_div.sum() << std::endl; // 输出:h_div sum: inf (除以 0 得到无穷大)
26
std::cout << "h_scale sum: " << h_scale.sum() << std::endl; // 输出:h_scale sum: 4
27
28
return 0;
29
}
③ 直方图序列化与持久化相关函数 (Functions for Serialization and Persistence of Histograms):
Boost.Histogram 提供了序列化 (serialization) 和 反序列化 (deserialization) 的支持,可以将直方图对象保存到文件 (save to file) 或 从文件加载 (load from file)。Boost.Histogram 使用 Boost.Serialization 库来实现序列化功能。
⚝ save(filename, histogram)
:将直方图 histogram
保存到文件 (save to file) filename
。
⚝ load(filename)
:从文件 filename
加载直方图 (load histogram),返回加载的直方图对象。
注意 (Note):使用序列化功能需要链接 Boost.Serialization 库 (link Boost.Serialization library)。
1
#include <boost/histogram.hpp>
2
#include <boost/histogram/serialization.hpp> // 引入序列化头文件
3
#include <fstream>
4
#include <iostream>
5
6
namespace bh = boost::histogram;
7
8
int main() {
9
auto h = bh::make_histogram(bh::axis::regular(5, 0.0, 5.0));
10
h.fill(1.5);
11
h.fill(2.5);
12
13
const char* filename = "histogram.bin";
14
15
// 保存直方图到文件
16
{
17
std::ofstream ofs(filename, std::ios::binary);
18
bh::save(ofs, h);
19
}
20
21
// 从文件加载直方图
22
bh::histogram<std::tuple<bh::axis::regular>> h_loaded;
23
{
24
std::ifstream ifs(filename, std::ios::binary);
25
h_loaded = bh::load(ifs);
26
}
27
28
std::cout << "original histogram sum: " << h.sum() << std::endl; // 输出:original histogram sum: 2
29
std::cout << "loaded histogram sum: " << h_loaded.sum() << std::endl; // 输出:loaded histogram sum: 2
30
31
return 0;
32
}
上面的例子演示了如何使用 bh::save()
和 bh::load()
函数将直方图对象序列化到文件和从文件反序列化。
④ 其他工具函数 (Other Utility Functions):
Boost.Histogram 还提供了一些其他的工具函数,例如:
⚝ make_histogram(axes...)
:辅助函数 (helper function),用于简化 histogram
对象的创建。它接受轴对象作为参数,并返回一个 histogram
对象。我们在前面的例子中已经多次使用过 make_histogram
。
⚝ weight(value)
:权重辅助函数 (weight helper function),用于创建一个权重对象 (weight object),可以传递给 histogram::fill()
函数以指定权重。
1
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0));
2
h.fill(3.5, bh::weight(2.0)); // 使用权重 2.0 填充值 3.5
⚝ indexed(histogram)
、value(histogram)
:返回迭代器范围对象 (iterator range objects),用于遍历直方图的箱子,我们在 7.1 节中已经介绍过。
⑤ 总结 (Summary):
Boost.Histogram 提供的自由函数和工具函数极大地扩展了直方图的功能,涵盖了直方图变换、算术运算、序列化等多个方面。这些函数与 histogram
类和轴类一起,构成了 Boost.Histogram 库完整而强大的 API 体系。熟练掌握这些自由函数和工具函数,可以更加高效地使用 Boost.Histogram 进行数据分析和处理。在后续章节中,我们将结合具体的应用场景,进一步演示如何使用这些函数来解决实际问题。
7.5 API 使用的最佳实践 (Best Practices for API Usage)
为了更有效地使用 Boost.Histogram API,并编写出高效 (efficient)、可维护 (maintainable) 和 易于理解 (easy-to-understand) 的代码,本节总结了一些 Boost.Histogram API 使用的最佳实践 (best practices)。
① 选择合适的轴类型 (Choose the Right Axis Type):
根据数据的特点和分析目标,选择最合适的轴类型。
⚝ 对于连续数据 (continuous data),如果需要均匀分箱 (uniform binning),使用 regular_axis
。
⚝ 对于连续数据 (continuous data),如果需要非均匀分箱 (non-uniform binning),使用 variable_axis
。
⚝ 对于整数数据 (integer data),使用 integer_axis
。
⚝ 对于离散类别数据 (discrete category data),使用 category_axis
。
⚝ 对于特殊的分箱需求 (special binning requirements),考虑使用 custom_axis
。
选择合适的轴类型可以提高直方图的表达能力 (expressiveness) 和 分析效率 (analysis efficiency)。
② 合理配置轴选项 (Configure Axis Options Appropriately):
根据需要配置轴的欠流箱 (underflow bin) 和 溢出箱 (overflow bin) 选项。
⚝ 默认情况下,轴同时启用欠流箱和溢出箱 (options::underflow_overflown
),适用于大多数情况。
⚝ 如果只需要捕获超出范围的数据 (capture out-of-range data),可以使用默认选项。
⚝ 如果确定数据不会超出范围 (data is guaranteed to be within range),或者不需要处理超出范围的数据 (out-of-range data is not relevant),可以禁用欠流箱和溢出箱 (options::none
),以节省内存 (save memory) 和 提高性能 (improve performance)。
③ 选择合适的存储与累加器 (Choose the Right Storage and Accumulator):
根据数据统计的需求选择合适的存储类型和累加器类型。
⚝ 如果只需要计数 (counting),使用整数存储 (integer storage) 和 默认累加器 (default accumulator)。
⚝ 如果需要处理带权重的数据 (weighted data),使用权重存储 (weight storage) 和 权重累加器 (weight accumulator)。
⚝ 如果需要进行复杂的统计计算 (complex statistical calculations),例如平均值、标准差等,使用 unlimited 存储 (unlimited storage) 和 自定义累加器 (custom accumulator)。
选择合适的存储和累加器类型可以平衡内存占用 (balance memory footprint)、性能 (performance) 和 功能需求 (functional requirements)。
④ 使用 make_histogram
简化直方图创建 (Use make_histogram
to Simplify Histogram Creation):
使用 bh::make_histogram(axes...)
辅助函数可以简化直方图的创建代码 (simplify histogram creation code),使代码更简洁易读。
1
// 使用 make_histogram 创建直方图 (推荐)
2
auto h = bh::make_histogram(bh::axis::regular(10, 0.0, 10.0),
3
bh::axis::category({"A", "B", "C"}));
4
5
// 不使用 make_histogram 创建直方图 (代码较冗长)
6
auto axes_tuple = std::make_tuple(bh::axis::regular(10, 0.0, 10.0),
7
bh::axis::category({"A", "B", "C"}));
8
bh::histogram<decltype(axes_tuple)> h_long(axes_tuple);
⑤ 使用迭代器高效遍历直方图数据 (Use Iterators to Efficiently Iterate over Histogram Data):
使用 histogram::indexed()
或 histogram::value()
迭代器可以高效地遍历直方图的箱子数据 (efficiently iterate over histogram bin data),避免手动索引访问,提高代码的可读性 (readability) 和 性能 (performance)。
1
auto h = bh::make_histogram(bh::axis::regular(3, 0.0, 3.0));
2
h.fill(0.5);
3
h.fill(1.5);
4
h.fill(2.5);
5
6
// 使用 indexed 迭代器遍历 (推荐)
7
for (auto x : h.indexed()) {
8
std::cout << "indices: " << x.indices() << ", value: " << *x << std::endl;
9
}
10
11
// 不使用迭代器,手动索引访问 (代码较繁琐,效率可能较低)
12
for (int i = 0; i < h.axis(0).bins(); ++i) {
13
std::cout << "index: " << i << ", value: " << h.at(i) << std::endl;
14
}
⑥ 合理使用直方图算术运算 (Use Histogram Arithmetic Operations Judiciously):
直方图算术运算提供了方便的直方图操作方式,但在使用时需要注意以下几点 (pay attention to the following points):
⚝ 确保参与运算的直方图具有相同的轴配置 (same axis configuration)。
⚝ 注意除法运算 (division operation) 可能导致除以零 (division by zero) 的情况,需要进行错误处理 (error handling) 或 数据检查 (data validation)。
⚝ 算术运算会创建新的直方图对象 (new histogram objects),如果频繁进行算术运算,可能会产生额外的内存开销 (additional memory overhead)。
⑦ 利用序列化实现直方图持久化 (Utilize Serialization for Histogram Persistence):
使用 Boost.Histogram 的序列化功能可以将直方图对象保存到磁盘 (save to disk),并在需要时加载回来 (load back),实现直方图的持久化 (persistence)。这对于长期存储 (long-term storage) 和 数据共享 (data sharing) 非常有用。
⑧ 编写清晰的注释和文档 (Write Clear Comments and Documentation):
为了提高代码的可维护性 (maintainability) 和 可理解性 (understandability),编写清晰的注释 (clear comments) 和 文档 (documentation) 非常重要。对于复杂的直方图配置、自定义累加器和特殊的操作,添加必要的注释,解释代码的目的 (purpose)、逻辑 (logic) 和 用法 (usage)。
⑨ 持续学习和实践 (Continuous Learning and Practice):
Boost.Histogram 库功能丰富,API 细节较多。通过阅读官方文档 (read official documentation)、学习示例代码 (study example code)、参与社区讨论 (participate in community discussions) 和 实际项目练习 (practice in real-world projects),不断学习和实践,才能更深入地理解和掌握 Boost.Histogram API,并将其应用到实际的数据分析工作中。
总结 (Summary):
遵循以上最佳实践,可以帮助你更有效地使用 Boost.Histogram API,编写出高质量的 C++ 直方图处理代码。最佳实践是一个持续改进 (continuous improvement) 的过程,随着你对 Boost.Histogram 库的理解不断深入,你会发现更多更有效的 API 使用技巧。
END_OF_CHAPTER
8. chapter 8: Boost.Histogram 与生态系统 (Boost.Histogram and Ecosystem)
8.1 与 Boost 其他库的集成 (Integration with Other Boost Libraries)
Boost 库集合是 C++ 社区的基石,提供了大量高质量、经过实践检验的库,极大地扩展了 C++ 标准库的功能。Boost.Histogram
作为 Boost 库家族的一员,自然能够与其他 Boost 库无缝集成,共同构建强大的 C++ 应用。这种集成不仅提升了 Boost.Histogram
的应用场景,也为开发者提供了更加灵活和高效的工具链。
① Boost.Asio: 网络编程和异步 I/O 是现代应用开发的重要组成部分。Boost.Asio
库提供了强大的异步 I/O 功能,可以用于处理网络数据流。在网络监控、数据采集等场景中,我们常常需要对网络数据进行实时分析和统计。Boost.Histogram
可以与 Boost.Asio
结合使用,实时收集和分析网络数据包的特征,例如数据包大小分布、协议类型分布等,从而实现网络流量监控、异常检测等功能。
1
#include <boost/asio.hpp>
2
#include <boost/histogram.hpp>
3
#include <iostream>
4
5
namespace asio = boost::asio;
6
namespace hist = boost::histogram;
7
8
int main() {
9
asio::io_context io_context;
10
asio::ip::udp::socket socket(io_context, asio::ip::udp::endpoint(asio::ip::udp::v4(), 0));
11
12
hist::histogram<hist::axis::regular<>> packet_size_hist(hist::axis::regular<>(10, 0, 1500, "Packet Size"));
13
14
std::array<char, 1500> recv_buf;
15
asio::ip::udp::endpoint remote_endpoint;
16
17
auto receive_handler = [&](const boost::system::error_code& error, std::size_t bytes_received) {
18
if (!error && bytes_received > 0) {
19
packet_size_hist(bytes_received);
20
std::cout << "Received " << bytes_received << " bytes" << std::endl;
21
socket.async_receive_from(asio::buffer(recv_buf), remote_endpoint, receive_handler); // Continue receiving
22
} else if (error) {
23
std::cerr << "Receive error: " << error.message() << std::endl;
24
}
25
};
26
27
socket.async_receive_from(asio::buffer(recv_buf), remote_endpoint, receive_handler);
28
io_context.run();
29
30
// 打印直方图统计结果 (简略)
31
for (auto bin : packet_size_hist) {
32
std::cout << "Bin [" << bin.bin().lower() << ", " << bin.bin().upper() << "): " << bin.value() << std::endl;
33
}
34
35
return 0;
36
}
上述代码示例展示了如何使用 Boost.Asio
接收 UDP 数据包,并使用 Boost.Histogram
实时统计接收到的数据包大小的分布。
② Boost.Serialization: 数据的持久化是许多应用场景的必要需求。Boost.Serialization
库提供了强大的序列化功能,可以将 C++ 对象转换为字节流,方便存储到磁盘或通过网络传输。Boost.Histogram
对象可以借助 Boost.Serialization
库进行序列化和反序列化,从而实现直方图数据的持久化存储和加载。这在需要保存分析结果、离线分析等场景中非常有用。
1
#include <boost/archive/binary_oarchive.hpp>
2
#include <boost/archive/binary_iarchive.hpp>
3
#include <boost/histogram.hpp>
4
#include <fstream>
5
#include <iostream>
6
7
namespace hist = boost::histogram;
8
9
int main() {
10
// 创建并填充直方图
11
hist::histogram<hist::axis::regular<>> hist1(hist::axis::regular<>(10, 0, 10));
12
for (int i = 0; i < 100; ++i) {
13
hist1(rand() % 10);
14
}
15
16
// 序列化到文件
17
{
18
std::ofstream ofs("histogram.bin", std::ios::binary);
19
boost::archive::binary_oarchive oa(ofs);
20
oa << hist1;
21
}
22
23
// 从文件反序列化
24
hist::histogram<hist::axis::regular<>> hist2;
25
{
26
std::ifstream ifs("histogram.bin", std::ios::binary);
27
boost::archive::binary_iarchive ia(ifs);
28
ia >> hist2;
29
}
30
31
// 验证反序列化后的直方图 (简略)
32
std::cout << "Histogram 1 sum: " << std::accumulate(hist1.begin(), hist1.end(), 0.0, [](double sum, auto bin){ return sum + bin.value(); }) << std::endl;
33
std::cout << "Histogram 2 sum: " << std::accumulate(hist2.begin(), hist2.end(), 0.0, [](double sum, auto bin){ return sum + bin.value(); }) << std::endl;
34
35
36
return 0;
37
}
上述代码示例展示了如何使用 Boost.Serialization
将 Boost.Histogram
对象序列化到二进制文件,并从文件中反序列化恢复直方图对象。
③ Boost.Python: Python 作为一种流行的脚本语言,在数据科学和机器学习领域应用广泛。Boost.Python
库允许 C++ 代码与 Python 代码进行互操作。通过 Boost.Python
,可以将 Boost.Histogram
库封装成 Python 模块,从而在 Python 中方便地创建、填充和分析直方图。这使得 Python 开发者可以利用 Boost.Histogram
的高性能和丰富功能,进行数据分析和可视化。
1
# python 代码示例 (假设已经构建了 Boost.Histogram 的 Python 模块)
2
import boost.histogram as bh
3
import numpy as np
4
import matplotlib.pyplot as plt
5
6
# 创建直方图
7
hist = bh.Histogram(bh.axis.Regular(10, 0, 10, name="x"))
8
9
# 填充数据
10
data = np.random.rand(100) * 10
11
hist.fill(data)
12
13
# 获取直方图数据
14
x = hist.axes[0].edges
15
y = hist.view()
16
17
# 绘制直方图
18
plt.stairs(y, x)
19
plt.xlabel("x")
20
plt.ylabel("counts")
21
plt.title("Histogram from Boost.Histogram (via Boost.Python)")
22
plt.show()
上述 Python 代码示例展示了如何使用封装后的 Boost.Histogram
Python 模块创建直方图,填充数据,并使用 matplotlib
库绘制直方图。
④ Boost.Test: 单元测试是保证代码质量的关键环节。Boost.Test
库提供了丰富的测试工具,可以用于编写和运行 C++ 单元测试。对于 Boost.Histogram
库的开发和使用,可以使用 Boost.Test
编写单元测试用例,验证直方图的各种功能是否正常工作,例如填充数据、访问数据、直方图运算等。
1
#define BOOST_TEST_MODULE HistogramTest
2
#include <boost/test/unit_test.hpp>
3
#include <boost/histogram.hpp>
4
5
namespace hist = boost::histogram;
6
7
BOOST_AUTO_TEST_CASE(FillAndCountTest) {
8
hist::histogram<hist::axis::regular<>> h(hist::axis::regular<>(10, 0, 10));
9
h(5.0);
10
h(5.5);
11
BOOST_TEST_EQ(h.at(5), 2); // 验证 bin 索引为 5 的计数是否为 2
12
}
13
14
BOOST_AUTO_TEST_CASE(ArithmeticOperationsTest) {
15
hist::histogram<hist::axis::regular<>> h1(hist::axis::regular<>(10, 0, 10));
16
hist::histogram<hist::axis::regular<>> h2(hist::axis::regular<>(10, 0, 10));
17
h1(1.0);
18
h2(2.0);
19
auto h3 = h1 + h2;
20
BOOST_TEST_EQ(h3.at(1), 1); // 验证直方图相加结果
21
BOOST_TEST_EQ(h3.at(2), 1);
22
}
上述代码示例展示了如何使用 Boost.Test
编写简单的单元测试用例,测试 Boost.Histogram
的填充和计数功能以及直方图的算术运算功能。
⑤ Boost.Math: 统计分析常常需要进行数学计算。Boost.Math
库提供了广泛的数学函数和工具,包括统计分布、特殊函数等。Boost.Histogram
可以与 Boost.Math
结合使用,对直方图数据进行更深入的统计分析,例如计算均值、标准差、分位数,进行拟合检验等。
1
#include <boost/histogram.hpp>
2
#include <boost/math/distributions/normal.hpp>
3
#include <iostream>
4
#include <numeric>
5
6
namespace hist = boost::histogram;
7
namespace bm = boost::math;
8
9
int main() {
10
// 创建并填充直方图
11
hist::histogram<hist::axis::regular<>> h(hist::axis::regular<>(100, -5, 5));
12
std::vector<double> data(10000);
13
std::random_device rd;
14
std::mt19937 gen(rd());
15
bm::normal_distribution<> nd(0.0, 1.0);
16
for (auto& x : data) {
17
x = nd(gen);
18
h(x);
19
}
20
21
// 计算均值 (近似)
22
double sum_val = 0.0;
23
double sum_weight = 0.0;
24
for (auto bin : h) {
25
sum_val += bin.bin().center() * bin.value();
26
sum_weight += bin.value();
27
}
28
double mean = sum_val / sum_weight;
29
std::cout << "Approximate Mean: " << mean << std::endl;
30
31
// 可以进一步使用 Boost.Math 进行更精确的统计分析,例如拟合正态分布等。
32
33
return 0;
34
}
上述代码示例展示了如何使用 Boost.Math
的正态分布生成随机数据,并使用 Boost.Histogram
统计数据分布,并近似计算均值。实际应用中可以结合 Boost.Math
提供的更丰富的统计工具进行深入分析。
除了上述列举的库之外,Boost.Histogram
还可以与 Boost.Filesystem
(读取文件数据), Boost.Range
(迭代和操作直方图数据), Boost.Variant
/Boost.Any
(处理多种数据类型), Boost.JSON
(JSON 序列化) 等库进行集成,以满足各种不同的应用需求。Boost 库之间的良好兼容性和协同工作能力,使得开发者能够构建更加强大和灵活的 C++ 应用。
8.2 Boost.Histogram 在现代 C++ 开发中的角色 (Role of Boost.Histogram in Modern C++ Development)
在现代 C++ 开发中,数据分析和处理变得越来越重要。无论是科学研究、金融分析、性能监控还是机器学习,都离不开对数据的有效组织和分析。Boost.Histogram
在这个背景下扮演着至关重要的角色,它为 C++ 开发者提供了一个高效、灵活且易于使用的直方图库,极大地简化了数据分布统计和分析的任务。
① 数据分析与可视化: 直方图是数据分析和可视化的基础工具。Boost.Histogram
提供了强大的直方图创建和操作功能,可以方便地对各种类型的数据进行统计分析,并为后续的可视化提供数据基础。在现代 C++ 应用中,无论是桌面应用、服务器端程序还是嵌入式系统,数据可视化都变得越来越重要,Boost.Histogram
可以作为数据可视化的重要数据源。
② 性能关键型应用: C++ 语言以其高性能而著称,常用于开发性能关键型应用。Boost.Histogram
库在设计时就考虑了性能因素,采用了高效的存储和算法,能够快速地填充和查询直方图数据。这使得 Boost.Histogram
非常适合应用于需要实时数据分析和处理的场景,例如高频交易系统、网络流量监控、实时性能分析等。
③ 简化复杂性: 手动实现直方图功能,特别是多维直方图和各种轴类型,是一项复杂且容易出错的任务。Boost.Histogram
库封装了直方图实现的复杂性,提供了简洁易用的 API,开发者可以专注于数据分析逻辑,而无需花费大量精力在直方图的底层实现上。这大大提高了开发效率,并降低了出错的风险。
④ 现代 C++ 特性: Boost.Histogram
库充分利用了现代 C++ 的特性,例如模板元编程、constexpr、move 语义等,实现了高度的灵活性和性能优化。库的设计风格也符合现代 C++ 的编程范式,例如使用 RAII (Resource Acquisition Is Initialization) 管理资源,使用 move 语义避免不必要的拷贝等。这使得 Boost.Histogram
库不仅功能强大,而且代码优雅、易于维护。
⑤ 跨平台兼容性: Boost 库以其良好的跨平台兼容性而闻名。Boost.Histogram
库也继承了这一优点,可以在多种操作系统和编译器上编译和运行。这使得使用 Boost.Histogram
开发的 C++ 应用能够方便地部署到不同的平台,提高了代码的可移植性。
⑥ 广泛的应用领域: Boost.Histogram
库的应用领域非常广泛,包括但不限于:
⚝ 科学计算: 物理学、天文学、生物学等领域的实验数据分析,模拟结果统计。
⚝ 金融分析: 股票价格分布分析、风险评估、交易策略回测。
⚝ 性能监控: 系统性能指标 (CPU 使用率、内存占用、网络延迟等) 统计和分析。
⚝ 机器学习: 特征工程、数据预处理、模型评估 (例如,绘制模型预测结果的直方图)。
⚝ 物联网 (IoT): 传感器数据分析、设备状态监控。
⚝ 图像处理: 图像像素值分布统计、颜色直方图。
总而言之,Boost.Histogram
在现代 C++ 开发中扮演着重要的角色,它为开发者提供了一个强大、高效、易用的直方图工具,可以广泛应用于各种数据分析和处理场景,提升 C++ 应用的开发效率和性能。
8.3 未来发展趋势与展望 (Future Trends and Prospects)
Boost.Histogram
作为一个活跃的开源项目,其未来发展充满了机遇和挑战。随着数据科学和 C++ 技术的不断发展,Boost.Histogram
也将持续演进,以满足新的需求和技术趋势。
① 性能优化: 随着数据规模的不断增大,对直方图库的性能要求也越来越高。未来的 Boost.Histogram
可能会在以下方面进行性能优化:
⚝ SIMD (Single Instruction, Multiple Data) 支持: 利用 SIMD 指令并行处理多个数据,提高填充和查询速度。
⚝ GPU 加速: 将直方图计算任务卸载到 GPU 上,利用 GPU 的并行计算能力加速数据处理。
⚝ 分布式计算: 支持分布式直方图计算,处理海量数据集。
⚝ 更高效的存储: 探索更紧凑、更高效的存储结构,降低内存占用,提高缓存命中率。
② 新功能扩展: 为了满足不断变化的应用需求,Boost.Histogram
可能会增加新的功能:
⚝ 更多轴类型: 例如,地理轴 (用于地理空间数据)、时间轴 (用于时间序列数据) 等。
⚝ 更丰富的累加器: 例如,支持更复杂的统计量计算,例如中位数、众数、偏度、峰度等。
⚝ 流式直方图: 支持流式数据处理,实时更新直方图。
⚝ 在线算法: 集成在线算法,例如在线均值、在线方差等,与直方图结合使用。
⚝ 更好的可视化集成: 提供更方便的接口,与其他可视化库 (例如 matplotlib
, plotly
, ggplot2
) 集成,简化直方图可视化流程。
③ 与其他库的深度集成: Boost.Histogram
将继续加强与其他 Boost 库以及第三方库的集成:
⚝ Boost.DataFrame: 与 Boost.DataFrame
库更紧密地集成,方便对 DataFrame 中的数据列进行直方图分析。
⚝ Arrow: 与 Apache Arrow 等列式数据存储格式集成,提高数据交换效率。
⚝ 机器学习库: 与机器学习库 (例如 TensorFlow
, PyTorch
的 C++ API, scikit-learn
的 C++ 版本) 集成,方便在机器学习流程中使用直方图进行特征工程和模型分析。
④ 标准化的推动: 随着 C++ 标准的不断发展,Boost.Histogram
有可能被吸纳到 C++ 标准库中,成为标准库的一部分。这将进一步提升 Boost.Histogram
的普及度和影响力,使其成为 C++ 开发者进行数据分析的标配工具。
⑤ 社区驱动的创新: Boost.Histogram
的发展离不开活跃的社区贡献。未来,社区将继续在功能扩展、性能优化、bug 修复等方面发挥重要作用。鼓励更多的开发者参与到 Boost.Histogram
的开发和维护中,共同推动库的进步和发展。
总而言之,Boost.Histogram
的未来发展前景广阔。随着数据分析需求的增长和 C++ 技术的进步,Boost.Histogram
将不断完善和创新,为 C++ 开发者提供更强大、更易用的直方图工具,在数据科学和现代 C++ 开发领域发挥越来越重要的作用。
END_OF_CHAPTER