Press "Enter" to skip to content

Rustic Data 使用Plotters进行数据可视化 —— 第1部分

如何将原始数字转化为华丽的Rust图形的详细指南

Various Plotters Features (Image by author)

TLDR;

Plotters是一个流行的Rust库,用于创建数据可视化。它提供了各种工具和函数,帮助您创建高质量的图形图表和其他可视化效果。本文是一系列文章的第一部分,重点关注使用Plotters准备的可视化效果的美学方面。从更改颜色方案到添加注释,您将学习如何自定义Plotters的可视化效果的外观方面。

阅读完本文后,您将对如何使用Plotters库创建专业外观的可视化效果有着牢固的理解,这将吸引您的观众。在本文中,我们还将探索各种工具和方法来处理数据的使用,这时,Ndarray库将非常有用。因此,无论您是业余还是经验丰富的Rust程序员,如果您对使用Plotters创建富有信息美观的可视化效果感兴趣,阅读本文至关重要。

注意:本文假设您对Rust编程语言有基本的了解。

为本文编写了名为6-plotters-tutorial-part-1.ipynb的笔记本,可以在以下仓库中找到:

GitHub – wiseaidev/rust-data-analysis:Rust数据分析的最终课程。

Rust数据分析的最终课程。通过创建一个来为wiseaidev/rust-data-analysis做贡献…

github.com

目录(TOC)

∘ TLDR; ∘ 目录(TOC) ∘ 这篇文章适合谁? ∘ 什么是Plotters? ∘ Plotters的优势 ∘ 设置Plotters ∘ 单线图 ∘ 多线图 ∘ 网格、坐标轴和标签 ∘ 颜色和标记 ∘ 子图 ∘ 误差条 ∘ 散点图 ∘ 直方图 ∘ 结论 ∘ 结尾语 ∘ 资源

这篇文章适合谁?

Photo by Myriam Jessier on Unsplash

对于那些对在Rust中创建直观数据可视化感兴趣的人来说,本文是一个必读的文章。无论您是一位有经验的数据科学家还是刚刚起步的初学者,Rust中的Plotters库都可以帮助您创建令人着迷且引人注目的视觉效果,肯定会给您的观众留下深刻的印象。凭借对Rust编程的基本知识,您将轻松入门。

Plotters库在创建华丽有效可视化效果方面拥有巨大的能力,可以快速而轻松地完成——无论是个人项目还是专业项目。它是一个工具,让您能够生成能够有效传达复杂信息的高质量图形。

如果您希望进一步提升您的可视化技能,那么这篇文章将是您的最佳选择!清晰的解释结合有用的图表,使得跟随变得简单,而逐步说明确保快速进展,使用 Plotters crate 制作出令人叹为观止的可视化效果。

什么是 Plotters?

Photo by Stephen Phillips - Hostreviews.co.uk on Unsplash

Plotters 是一个强大而适应性强的 Rust crate,使您能够轻松地创建令人惊叹的可视化效果。它的多功能性允许创建各种图表,包括线图、散点图和直方图,同时在样式选项和自定义注释方面提供高度灵活性。

这个全能工具使开发人员能够定义所需的任何类型的可视化效果,使其成为数据分析任务中不可或缺的资产。一个值得注意的特点是它支持交互式界面,这使得生成静态图形以及轻松创建基于 web 的应用程序成为可能。这种能力有助于对数据集进行轻松探索,从而产生适用于机器学习或数据科学项目的各种图表类型。

此外,Plotters 与流行的开发环境(如 Jupyter Notebook)无缝集成,同时支持专门用于增强数据可视化体验的高级包,为什么这个包应该成为每个开发人员工具包的一部分提供了更多的理由!

无论您是刚刚开始您的旅程,还是已经在分析复杂数据集,Plotters 提供了无与伦比的适应性和用户友好性,真正值得在当今顶级工具中获得认可!

Plotters的优势

Photo by UX Indonesia on Unsplash

数据可视化是数据分析的关键方面,Plotters 库为简化该过程提供了几个优点。它与常见的数据分析 crate(如 Ndarray)的集成使得与熟悉的数据结构一起使用变得轻松。

使用这个开源工具的另一个值得注意的好处在于其具有成本效益;开发人员和分析师可以在没有任何费用或使用权利限制的情况下使用该库。此外,任何有兴趣为改进软件做出贡献的人都可以作为一个社区努力的一部分

此外,开源意味着全球各地的成员可以通过各种平台(如 stackoverflow等论坛)快速获得在线支持,从而实现高效的问题解决!

设置 Plotters

要充分利用 Plotters 的功能,确保我们正确设置环境至关重要。该库提供了广泛的图表类型,如折线图、散点图、直方图和饼图;但是,如果没有正确的设置,这些功能将无法访问。幸运的是,设置 Plotters 是一个简单的过程-只需在您的 Jupyter Notebook 中运行一个命令,然后开始使用!

:dep plotters = { version = "^0.3.5", default_features = false, features = ["evcxr", "all_series", "all_elements"] }

一旦导入到项目工作空间或笔记本会话中,Plotters 允许您探索其丰富的自定义选项,以满足您的特定需求-无论是简单还是复杂的图表。

单线图

A linear single-line plot (Image by author)

线图是 Plotters 库中的一种基本可视化工具,它允许我们用直线连接的数据点来表示数据。在本节中,我们将探索单线图的概念,即使用 LineSeries 结构来创建带有一条线的可视化效果。

LineSeries 结构体在 Plotters 中被广泛用于可视化数据,特别是在创建单线图时。这样的图表非常适合用于展示两个变量之间的相关性或者突出时间序列数据中的模式。

要通过 Plotters 创建一维图表,首先要导入该库,并使用其 draw_series 函数以及 LineSeries 结构体来绘制带有指定数据集的线状图。例如,如果我们想通过一个简单的图表来展示每月的销售数据,可以按照以下方式使用 draw_series 函数:

evcxr_figure((640, 240), |root| {    let mut chart = ChartBuilder::on(&root)        .build_cartesian_2d(0f32..5f32, 0f32..5f32)?;    let x_axis = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];    chart.draw_series(LineSeries::new(        x_axis.map(|x| (x, x)),        &RED,    ))?;    Ok(())}).style("width:100%")

在上面的代码中,我们有一个数组 x,代表了 x 和 y 的坐标。接下来,让我们看一个不同的示例,其中我们使用一个 Ndarray 数组来表示单线图的数据:

evcxr_figure((640, 240), |root| {    let mut chart = ChartBuilder::on(&root)        .build_cartesian_2d(0f32..7f32, 0f32..7f32)?;        let x_axis = Array::range(1., 7., 1.);        chart.draw_series(LineSeries::new(        x_axis.into_raw_vec().into_iter().map(|x| (x, x)),        &RED,    ))?;    Ok(())}).style("width:100%")

接下来,让我们来可视化一个由方程 y = f(x) = x³ 表示的二次曲线图。以下是相应的代码:

let points_coordinates: Vec<(f32, f32)> = {    let x_axis = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];    let quadratic: Vec<f32> = x_axis.iter().map(|x| i32::pow(*x as i32, 3) as f32).collect::<Vec<f32>>();    x_axis.into_iter().zip(quadratic).collect()};points_coordinates// Output// [(1.0, 1.0), (2.0, 8.0), (3.0, 27.0), (4.0, 64.0), (5.0, 125.0), (6.0, 216.0)]

现在,我们需要绘制这个向量,如下所示:

evcxr_figure((640, 240), |root| {    let mut chart = ChartBuilder::on(&root)        .build_cartesian_2d(0f32..7f32, 0f32..220f32)?;    chart.draw_series(LineSeries::new(        points_coordinates.iter().map(|(x, y)| (*x, *y)),        &RED,    ))?;    Ok(())}).style("width:100%")
A cubic function plot (Image by author)

总之,在 Plotters 中,线状图 提供了一种强大的方式来展示数据集中的 相关性趋势。通过使用 LineSeries 结构体,同时操作 x-valuesy-values 数组/向量,我们可以制作信息丰富且引人入胜的数据可视化。无论是深入研究科学研究结果还是分析业务指标,这些线状图都是探索数据集并与他人有效沟通其见解的不可或缺的工具。

多线图

多线图(作者提供的图片)

Plotters 提供了在单个输出中显示多个图形的出色能力,使我们能够同时在同一可视化中呈现多条曲线。这个显著的特性便于对数据集进行简便的比较和分析。为了更深入地理解这个概念,让我们来看一个示例:

evcxr_figure((640, 240), |root| {    let mut chart = ChartBuilder::on(&root)        .build_cartesian_2d(0f32..7f32, 0f32..220f32)?;    chart.draw_series(LineSeries::new(        linear_coordinates.iter().map(|(x, y)| (*x, *y)),        &RED,    ))?;    chart.draw_series(LineSeries::new(        quadratic_coordinates.iter().map(|(x, y)| (*x, *y)),        &GREEN,    ))?;    chart.draw_series(LineSeries::new(        cubic_coordinates.iter().map(|(x, y)| (*x, *y)),        &BLUE,    ))?;    Ok(())}).style("width:100%")

通过使用提供的代码片段,我们可以轻松生成多条曲线。这是通过多次调用draw_series函数,并使用数组中的x值和来自不同数学表达式的相应y值定义来实现的。执行此代码后,将显示一个综合图形,展示了所有这些绘制的曲线,以供观察。

让我们再看一个演示多线图的灵活性的示例。观察以下代码片段:

let points_coordinates: Vec<(f32, f32)> = {    let x_y_axes = array!([[1., 2., 3., 4.], [1., 2., 3., 4.]]);    let x_axis: Vec<f32> = x_y_axes.slice(s![0, 0, ..]).to_vec();    let y_axis: Vec<f32> = x_y_axes.slice(s![0, 1, ..]).to_vec();    x_axis.into_iter().zip(y_axis).collect()};// [(1.0, 1.0), (2.0, 2.0), (3.0, 3.0), (4.0, 4.0)]evcxr_figure((640, 240), |root| {    let mut chart = ChartBuilder::on(&root)        .build_cartesian_2d(0f32..5f32, 0f32..5f32)?;    chart.draw_series(LineSeries::new(        points_coordinates.iter().map(|(x, y)| (*x, *y)),        &RED,    ))?;    Ok(())}).style("width:100%")

这个代码片段涉及一个具有两个维度的Ndarray数组x,其中包含不同的数据集。每一行表示唯一的值。当在整个数组上调用draw_series函数时,Plotters会将其视为多条曲线,同时绘制。结果以并排显示两个数据集,便于以直观的方式比较和分析它们的模式、趋势或任何其他值得注意的特征,以便从中从视觉上得出有意义的结论而不需要太多的努力。

为了展示多线图的适应性,我们可以使用任意数据创建一个可视化表示。观察以下代码片段:

let random_samples: Vec<(f32, f32)> = {    let x_y_axes = Array::random((2, 5), Uniform::new(0., 1.));    let x_axis: Vec<f32> = x_y_axes.slice(s![0, ..]).to_vec();    let y_axis: Vec<f32> = x_y_axes.slice(s![0, ..]).to_vec();    x_axis.into_iter().zip(y_axis).collect()};random_samplesevcxr_figure((640, 240), |root| {    let mut chart = ChartBuilder::on(&root)        .build_cartesian_2d(0f32..1f32, 0f32..1f32)?;    chart.draw_series(LineSeries::new(        random_samples.iter().map(|(x, y)| (*x, *y)),        &RED,    ))?;    Ok(())}).style("width:100%")

在这段代码片段中,我们使用了Ndarray函数Array::random来创建一个二维数据数组,其中填充了任意值。每次使用这个方法时,它都会生成一个独特的数据点集合。通过打印出生成的数组,我们可以仔细检查这些随机数。调用draw_series函数将我们的数据集中的每一行作为单独的曲线展示在一个图表上。由于每次执行都会产生不同的随机输出,每个生成的图表都将是独特的,并且为您的可视化体验引入一些不确定性和多样性。

总结一下,在单个输出中可视化多个图表的能力是数据探索和分析的一项强大功能。无论是绘制不同的曲线,比较数据集,还是利用随机数据,多线图提供了对手头信息的全面视图。通过利用Plotters的功能并尝试不同的数据源,您可以创建有影响力的可视化效果,促进更好的理解和决策。

网格、坐标轴和标签

Plotters网格(作者提供的图片)

在数据可视化领域,呈现一个网格在绘图中至关重要。Plotters库通过启用mesh功能使我们能够实现这一目标。通过将语句chart.configure_mesh().draw()?;简单地加入到我们的代码中,我们可以提升绘图的视觉吸引力和清晰度。

evcxr_figure((640, 240), |root| {    let mut chart = ChartBuilder::on(&root)        .build_cartesian_2d(0f32..1f32, 0f32..1f32)?;    chart.configure_mesh().draw()?;    Ok(())}).style("width:100%")

这行代码ChartBuilder::on(&root).build_cartesian_2d(0f32..1f32, 0f32..1f32)?;允许我们手动设置x轴的范围从0到1,y轴的范围从0到1。通过指定这些范围,我们可以精确控制绘图中显示的区域,确保突出显示最相关的数据点。

为了增强绘图的清晰度和理解力,为坐标轴提供适当的标签和一个描述性的标题是至关重要的。让我们以以下代码片段为例:

evcxr_figure((640, 480), |root| {    let mut chart = ChartBuilder::on(&root)        .caption("Plot Demo", ("Arial", 20).into_font())        .x_label_area_size(50)        .y_label_area_size(50)        .build_cartesian_2d(0f32..1f32, 0f32..1f32)?;        chart.configure_mesh()        .x_desc("x = Array::range(1., 7., 0.1);")        .y_desc("y = f(x)")        .draw()?;        Ok(())}).style("width: 60%")
Plotters标签(作者提供的图片)

在这段代码中,我们添加了chart.configure_mesh().x_desc(“x = Array::range(1., 7., 1.);”)y_desc(“y = f(x)”)语句,以丰富我们的绘图,并带有有意义的注释。通过包含x_desc(“x = Array::range(1., 7., 1.);”),我们用简洁的描述标记x轴上的数据。同样,y_desc(“y = f(x)”)为y轴分配一个标签,指示功能关系。此外,Caption(“Plot Demo”, (“Arial”, 20).into_font())提供了一个信息丰富的标题,为绘图提供了上下文。这些元素共同提高了可视化的可解释性,确保观众可以轻松理解绘图的目的和内容。

除了标签和标题之外,Plotters还允许我们创建一个图例来区分绘图中的多条曲线。通过在label函数的label参数中传递参数,并随后调用legend函数,我们可以生成一个图例。考虑以下代码示例:

evcxr_figure((640, 480), |root| {    let mut chart = ChartBuilder::on(&root)        .caption("绘图示例", ("Arial", 20).into_font())        .x_label_area_size(50)        .y_label_area_size(50)        .build_cartesian_2d(1f32..7f32, 1f32..14f32)?;        let x = Array::range(1., 7., 0.1);        chart.configure_mesh()        .x_desc("x = Array::range(1., 7., 1.);")        .y_desc("y = f(x)")        .draw()?;    chart.draw_series(LineSeries::new(        x.iter().map(|x| (*x, *x)),        &RED    )).unwrap()        .label("y = x")        .legend(|(x,y)| PathElement::new(vec![(x,y), (x + 20,y)], &RED));        chart.draw_series(LineSeries::new(        x.iter().map(|x| (*x, *x * 2.0)),        &GREEN    )).unwrap()        .label("y = 2 * x")        .legend(|(x,y)| PathElement::new(vec![(x,y), (x + 20,y)], &GREEN));    chart.configure_series_labels()        .background_style(&WHITE)        .border_style(&BLACK)        .draw()?;    Ok(())}).style("width: 60%")
包含标签、图例和网格的多线绘图(作者提供的图像)

通过执行此代码,我们创建了一个与绘图中各曲线对应的图例。在调用draw_series()函数之后,legend()函数会自动生成一个图例,以帮助观看者识别和区分不同的函数。与网格、轴标签和标题结合使用,图例提高了绘图的整体可读性和理解性。

默认情况下,图例框位于绘图的中右侧。然而,如果我们希望更改图例框的位置,可以通过在position函数中指定SeriesLabelPosition位置参数来实现。让我们相应地修改我们的代码片段:

evcxr_figure((640, 480), |root| {    let mut chart = ChartBuilder::on(&root)        .caption("绘图示例", ("Arial", 20).into_font())        .x_label_area_size(50)        .y_label_area_size(50)        .build_cartesian_2d(1f32..7f32, 1f32..14f32)?;        let x = Array::range(1., 7., 0.1);        chart.configure_mesh()        .x_desc("x = Array::range(1., 7., 0.1);")        .y_desc("y = f(x)")        .draw()?;    chart.draw_series(LineSeries::new(        x.iter().map(|x| (*x, *x)),        &RED    )).unwrap()        .label("y = x")        .legend(|(x,y)| PathElement::new(vec![(x,y), (x + 20,y)], &RED));        chart.draw_series(LineSeries::new(        x.iter().map(|x| (*x, *x * 2.0)),        &GREEN    )).unwrap()        .label("y = 2 * x")        .legend(|(x,y)| PathElement::new(vec![(x,y), (x + 20,y)], &GREEN));    chart.configure_series_labels()        .position(SeriesLabelPosition::UpperMiddle)        .background_style(&WHITE)        .border_style(&BLACK)        .draw()?;    Ok(())}).style("width: 60%")
一个带有位于图形上中部的图例的多行图表(作者提供的图片)

通过在configure_series_labels函数上包含参数position(SeriesLabelPosition::UpperMiddle),我们将图例框重新定位到图表的上中部。这样可以微调图例的位置,确保它不会干扰绘制的曲线或其他注释。自定义图例位置的能力增加了我们绘图的灵活性和美观性。

通过了解和利用Plotters中的这些功能,我们可以创建具有视觉吸引力和信息量的图表,自定义轴限制,添加标签和标题,加入图例,并将我们的可视化保存为图像文件。这些功能使我们能够以引人注目和有意义的方式有效地传达和展示我们的数据。

颜色和标记

Plotters提供了多种样式和标记,可以设计具有视觉吸引力且易于理解的图表。样式可修改线条的外观,而标记则有助于强调绘图上的特定数据点。通过将不同的颜色、样式和标记与Plotters的功能结合使用,可以打造出符合特定要求的独特图表。

Plotters提供了高级颜色映射,可以使用各种颜色可视化复杂的数据。通过style参数,可以选择使用一系列预定义的颜色映射或使用内置的类似RGBColor的结构设计自定义颜色。当表示包含广泛值范围的数据或强调特定绘图线条或其他形状时,这个参数尤其有益。您可以参考完整的调色板以获取不同的RGB颜色值。

evcxr_figure((640, 480), |root| {    let mut chart = ChartBuilder::on(&root)        .caption("绘图演示", ("Arial", 20).into_font())        .x_label_area_size(50)        .y_label_area_size(50)        .build_cartesian_2d(1f32..7f32, 1f32..14f32)?;        let x = Array::range(1., 7., 0.1);        chart.configure_mesh()        .x_desc("x = Array::range(1., 7., 0.1);")        .y_desc("y = f(x)")        .draw()?;    chart.draw_series(LineSeries::new(        x.iter().map(|x| (*x, *x)),        &RGBColor(0,0,255) // 红色:0,绿色:0,蓝色:255 -> 颜色为蓝色    ))?;        Ok(())}).style("width: 60%")
一个颜色为蓝色的单线图表(作者提供的图片)

在这个例子中,我们将线条的颜色更改为蓝色。您也可以使用其他颜色格式,比如HSLColor,使用HSL谱系值指定自定义颜色。

为了提升Plotters中线图表的视觉吸引力,考虑加入标记来表示每个绘图的不同符号。如果您希望个性化,有几种方法可以自定义这些标记。首先,我们可以使用draw_series方法,根据个人喜好或特定数据集特征,使用大小和颜色等标记样式两次绘制数据。

evcxr_figure((640, 480), |root| {    let mut chart = ChartBuilder::on(&root)        .caption("绘图演示", ("Arial", 20).into_font())        .x_label_area_size(50)        .y_label_area_size(50)        .build_cartesian_2d(1f32..7f32, 1f32..8f32)?;        let x = Array::range(1., 7., 0.1);        chart.configure_mesh()        .x_desc("x = Array::range(1., 7., 0.1);")        .y_desc("y = f(x)")        .draw()?;    chart.draw_series(LineSeries::new(        x.iter().map(|x| (*x, *x)),        &RED    ))?;    chart.draw_series(x.map(|x| {        EmptyElement::at((*x, *x))        + Cross::new((0, 0), 2, GREEN) // 坐标相对于EmptyElement    }))?;    Ok(())}).style("width: 60%")
一个带有标记的线性单线图(作者提供的图像)

或者,我们可以使用point_size方法来设置标记的大小,该方法允许创建实心或空心圆形标记。

evcxr_figure((640, 480), |root| {    let mut chart = ChartBuilder::on(&root)        .caption("绘图演示", ("Arial", 20).into_font())        .x_label_area_size(50)        .y_label_area_size(50)        .build_cartesian_2d(1f32..7f32, 1f32..8f32)?;        let x = Array::range(1., 7., 0.1);        chart.configure_mesh()        .x_desc("x = Array::range(1., 7., 0.1);")        .y_desc("y = f(x)")        .draw()?;    chart.draw_series(LineSeries::new(        x.iter().map(|x| (*x, *x)),        &RED    ).point_size(2))?; // 空心圆形标记    Ok(())}).style("width: 60%")
一个带有标记的线性图(作者提供的图像)

您可以将所有这些技术(例如颜色、标记、图例)组合起来自定义可视化效果,如下所示:

evcxr_figure((640, 480), |root| {    let mut chart = ChartBuilder::on(&root)        .caption("绘图演示", ("Arial", 20).into_font())        .x_label_area_size(50)        .y_label_area_size(50)        .build_cartesian_2d(1f32..7f32, 1f32..342f32)?;        let x = Array::range(1., 7., 0.1);        chart.configure_mesh()        .x_desc("x = Array::range(1., 7., 0.1);")        .y_desc("y = f(x)")        .draw()?;    chart.draw_series(LineSeries::new(        x.iter().map(|x| (*x, *x)),        RED.filled()    ).point_size(2)).unwrap()        .label("y = x")        .legend(|(x,y)| PathElement::new(vec![(x,y), (x + 20,y)], &RED));    chart.draw_series(LineSeries::new(        x.iter().map(|x| (*x, (*x).powi(3))),        BLUE    ).point_size(2)).unwrap()        .label("y = x ^ 3")        .legend(|(x,y)| PathElement::new(vec![(x,y), (x + 20,y)], &BLUE));    chart.draw_series(LineSeries::new(        x.iter().map(|x| (*x, (*x).powi(2))),        &GREEN    )).unwrap()        .label("y = x ^ 2")        .legend(|(x,y)| PathElement::new(vec![(x,y), (x + 20,y)], &GREEN));    chart.draw_series(x.map(|x| {        EmptyElement::at((*x, (*x).powi(2)))        + Cross::new((0, 0), 2, WHITE) // 坐标相对于EmptyElement    }))?;    chart.configure_series_labels()        .background_style(&WHITE)        .border_style(&BLACK)        .draw()?;    Ok(())}).style("width: 60%")
一个带有不同线条颜色、标记、标签、标题和图例的多线图(作者提供的图像)

总的来说,Plotters提供了一种简单而轻松的方式来个性化颜色和标记,使您能够创建出色的可视化效果。通过选择适当的调色板,您的图表可以轻松有效地传达有价值的信息。选择正确的颜色和标记可以在成功传达您的信息方面起到至关重要的作用。

子图

Plotters子图(作者提供的图像)

子图技术是在同一输出中显示多个图形的一种强大方式。当您想要比较不同的数据集或展示一个数据集的各个方面时,这种方法尤其有用。使用Plotters,创建子图变得非常简单,因为它允许您创建一个网格布局,每个后续图的位置可以相对于前一个图指定。

此外,每个子图都有可自定义的规格,如标题和标签,使用户可以根据特定需求轻松定制输出。在科学和数据分析环境中处理复杂信息时,子图特别有用,因为它有助于简洁而有效地传达重要发现。

要在Plotters中生成子图,您可以使用split_evenly方法,该方法需要一个参数:一个元组,其中包含行数和列数。例如,如果您想要为子图创建一个1×2布局,并在第一个子图上绘制数据,则可以使用以下代码片段:

let linear_coordinates: Vec<(f32, f32)> = {    let x_y_axes = array!([[1., 2., 3., 4.], [1., 2., 3., 4.]]);    let x_axis: Vec<f32> = x_y_axes.slice(s![0, 0, ..]).to_vec();    let y_axis: Vec<f32> = x_y_axes.slice(s![0, 1, ..]).to_vec();    x_axis.into_iter().zip(y_axis).collect()};let quadratic_coordinates: Vec<(f32, f32)> = {    let x_y_axes = array!([[1., 2., 3., 4.], [1., 4., 9., 16.]]);    let x_axis: Vec<f32> = x_y_axes.slice(s![0, 0, ..]).to_vec();    let y_axis: Vec<f32> = x_y_axes.slice(s![0, 1, ..]).to_vec();    x_axis.into_iter().zip(y_axis).collect()};evcxr_figure((640, 480), |root| {    let sub_areas = root.split_evenly((1,2)); // 1x2 grid ( 1 row, 2 columns)    let graphs = vec![        ("y = x", linear_coordinates.clone(), &RED),        ("y= x ^ 2", quadratic_coordinates.clone(), &GREEN),    ];    for ((idx, area), graph) in (1..).zip(sub_areas.iter()).zip(graphs.iter()) {        let mut chart = ChartBuilder::on(&area)            .caption(graph.0, ("Arial", 15).into_font())            .x_label_area_size(40)            .y_label_area_size(40)            .build_cartesian_2d(0f32..5f32, 0f32..17f32)?;        chart.draw_series(LineSeries::new(            graph.1.iter().map(|(x, y)| (*x, *y)),            graph.2,        )).unwrap()            .label(graph.0)            .legend(|(x,y)| PathElement::new(vec![(x,y), (x + 20,y)], &GREEN));        chart.configure_mesh()            .y_labels(10)            .light_line_style(&TRANSPARENT)            .disable_x_mesh()            .x_desc("x = Array::range(1., 7., 0.1);")            .y_desc(graph.0)            .draw()?;    }    Ok(())}).style("width:100%")

这将创建一个1×2布局的子图网格,并在两个子图中绘制数据,同时指定标题和轴标签。传递给split_evenly的元组参数表示网格(1行和2列)。在Plotters中有许多进行子图绘制的方法,如split_verticallysplit_horizontallysplit_evenlysplit_by_breakpoints

通过利用Plotters的子图功能,可以实现令人惊叹的可视化效果,从而通过清晰准确地呈现洞察力来帮助沟通。

误差线

作者提供的带有垂直误差线的单个图形

为了准确地表示数据,必须承认并透明地呈现误差的潜在性。这可以通过使用误差线来实现,误差线是图形表示,显示测量的变异性并指示不确定性水平。Plotters通过其ErrorBar函数提供了一个简单的解决方案,允许用户通过指定x/y坐标、颜色/样式首选项以及提供相关误差值来向任何图形中添加这些必要的视觉辅助。让我们来看看以下代码片段:

evcxr_figure((640, 480), |root| {    let mut chart = ChartBuilder::on(&root)        .caption("垂直误差线图", ("Arial", 20).into_font())        .x_label_area_size(50)        .y_label_area_size(50)        .build_cartesian_2d(1f32..7f32, 1f32..50f32)?;        let x = Array::range(1., 7., 0.1);    chart.configure_mesh()        .x_desc("x = Array::range(1., 7., 0.1);")        .y_desc("y = f(x)")        .draw()?;    chart.draw_series(LineSeries::new(        x.iter().map(|x| (*x, (*x as f32).powi(2))),        &GREEN    )).unwrap()        .label("y = x ^ 2")        .legend(|(x,y)| PathElement::new(vec![(x,y), (x + 20,y)], &GREEN));      chart.draw_series(x.map(|x| {        ErrorBar::new_vertical(*x, (*x as f32).powi(2) - 1.5, (*x as f32).powi(2), (*x as f32).powi(2) + 1.4, RED.filled(), 2)    })).unwrap();    chart.configure_series_labels()        .background_style(&WHITE)        .border_style(&BLACK)        .draw()?;    Ok(())}).style("width: 100%")

在这个例子中,我们选择在y轴上显示误差,因为它通常更为普遍。下面的图像是我们数据的视觉表示,展示了每个数据点周围明显的误差线。这些线条表示在特定置信水平下可能的值范围;较长的线条表示测量中较大的不确定性

然而,在某些情况下,同时在两个轴上显示误差数据可能会有益 — 特别是在处理时间序列或包含多个独立变量的实验数据时。在这种情况下,使用ErrorBar::new_horizontal方法并传递一个x轴误差的数组(类似于y轴误差)就足够了。

evcxr_figure((640, 480), |root| {    let mut chart = ChartBuilder::on(&root)        .caption("水平误差线图", ("Arial", 20).into_font())        .x_label_area_size(50)        .y_label_area_size(50)        .build_cartesian_2d(1f32..7f32, 1f32..50f32)?;        let x = Array::range(1., 7., 0.1);    chart.configure_mesh()        .x_desc("x = Array::range(1., 7., 0.1);")        .y_desc("y = f(x)")        .draw()?;    chart.draw_series(LineSeries::new(        x.iter().map(|x| (*x, (*x as f32).powi(2))),        &GREEN    )).unwrap()        .label("y = x ^ 2")        .legend(|(x,y)| PathElement::new(vec![(x,y), (x + 20,y)], &GREEN));      chart.draw_series(x.map(|x| {        ErrorBar::new_horizontal((*x as f32).powi(2), *x - 0.3, *x, *x + 0.3, RED.filled(), 2)    })).unwrap();    chart.configure_series_labels()        .background_style(&WHITE)        .border_style(&BLACK)        .draw()?;    Ok(())}).style("width: 100%")
作者提供的带有水平误差条的单一图形

通过将这些元素纳入您的可视化中,无论是作为科学家分享研究结果还是作为业务分析师展示销售数据,观众都可以更好地理解所呈现信息的不确定性。因此,利用这个重要功能将确保准确传达精细细节,同时保持清晰度,而不会因使用Plotters的特性(如误差条)在图形化表示的数据集中产生混淆。

散点图

散点图是可视化数据和获得关于两个变量之间关系的重要工具。通过将一个变量分配给x轴,另一个变量分配给y轴,并在相应的坐标上绘制每个点,Plotters可以轻松创建Rust中的散点图。通过调整点的颜色和大小,您可以表示数据集中的附加维度。

使用散点图的主要优势是它们可以揭示数据中的模式或聚类,这些模式或聚类可能仅通过表格或图表本身无法明显看出。通过这种方法,异常值也很容易识别。

此外,这些图形具有直观的特性,使任何人(无论统计专业知识如何)都能快速理解不同方面之间的关系,因此在呈现研究结果时它们是有用的沟通工具。

以下代码片段将生成具有均匀分布的数据样本的散点图:

evcxr_figure((640, 480), |root| {    _ = root.fill(&WHITE);    let mut chart = ChartBuilder::on(&root)        .caption("均匀分布散点图", ("Arial", 20).into_font())        .x_label_area_size(40)        .y_label_area_size(40)        .build_cartesian_2d(0f32..1f32, 0f32..1f32)?;    chart.configure_mesh()        .disable_x_mesh()        .disable_y_mesh()        .y_labels(5)        .x_label_formatter(&|x| format!("{:.1}", *x as f64 / 100.0))        .y_label_formatter(&|y| format!("{}%", (*y * 100.0) as u32))        .draw()?;    let _ = chart.draw_series(random_samples.iter().map(|(x,y)| Circle::new((*x,*y), 3, GREEN.filled())));        Ok(())}).style("width:100%")

生成的散点图如下:

作者提供的均匀分布数据样本的散点图

总之,散点图提供了强大的可视化能力,使我们能够更好地了解数据集,同时提供了与他人共享信息的简单方式,这主要归功于Rust编程语言环境中提供的Plotters库函数的易用性特性!

直方图

直方图在分析数据分布时非常宝贵。它们以图形方式展示信息在不同类别或区间中的分布情况,使我们更容易理解和解释复杂的数据集。通过使用Plotters,利用将数据点分组到表示每个区间频率的条形中的线性数组的Histogram::vertical函数简化了这个过程。

例如,如果我们需要绘制一个随机生成的均匀分布,创建直方图将详细显示每个可能结果的频率,同时揭示数据集中存在的任何模式或趋势。分析这些图形可以帮助我们发现有关底层分布的有价值的见解,例如人口中的年龄组的人口统计数据,照片中的光照曝光水平,或城市中观察到的每月降水率。

以下代码片段是绘制随机生成的均匀分布数据样本的示例:

evcxr_figure((640, 480), |root| {    let mut chart = ChartBuilder::on(&root)        .caption("直方图", ("Arial", 20).into_font())        .x_label_area_size(50)        .y_label_area_size(50)        .build_cartesian_2d(0u32..100u32, 0f64..0.5f64)?;    chart.configure_mesh()        .disable_x_mesh()        .disable_y_mesh()        .y_labels(5)        .x_label_formatter(&|x| format!("{:.1}", *x as f64 / 100.0))        .y_label_formatter(&|y| format!("{}%", (*y * 100.0) as u32))        .draw()?;    let hist = Histogram::vertical(&chart)        .style(RED.filled())        .margin(0)        .data(random_samples.iter().map(|(x,_)| ((x*100.0) as u32, 0.01)));    let _ = chart.draw_series(hist);        Ok(())}).style("width:100%")

生成的直方图如下所示:

作者提供的均匀分布数据样本的直方图

总结一下,直方图为我们提供了强大的工具,可以洞察各种数据集并准确地识别影响它们的关键因素。通过使用 Plotters 的特性,如根据我们的需要定制的可调整的箱子大小,我们在快速解释大量信息时具有更大的灵活性,而不会牺牲准确性!

结论

Photo by Aaron Burden on Unsplash

本文强调了可视化的重要性以及如何定制 Plotters 来满足不同的需求。 Plotters 在创建各种类型的图表(如单线或多线图、散点图和直方图)方面发挥了无价之宝的作用。此外,我们还了解了自定义布局设计选择的功能,例如颜色线条、标记、图例等。

拥有这些新的知识,您可以自信地轻松使用 Plotters 的各种功能。有效利用这些方法将增强您对数据的理解,并能更好地传达研究结果。

在接下来的系列文章中,特别是第二部分,我们将探索引人注目的数据可视化,包括但不限于饼图和3D可视化。我们的目标是让您成为一名熟练的数据可视化故事讲述者,揭示前所未有的隐藏洞见!

结束语

Photo by Nick Morrison on Unsplash

随着本教程的结束,我要衷心感谢所有付出时间和精力完成它的人。很高兴能与您一起展示 Rust 编程语言的非凡能力。

作为对数据科学充满激情的人,我承诺从现在开始每周至少写一篇关于相关主题的全面文章。如果您对我的工作保持更新感兴趣,请考虑在各种社交媒体平台上与我联系,或直接寻求其他方面的帮助。

谢谢!

资源

GitHub – wiseaidev/rust-data-analysis: The ultimate data analysis with Rust course.

The ultimate data analysis with Rust course. Contribute to wiseaidev/rust-data-analysis development by creating an…

github.com

plotters – Rust

Plotters – 一个专注于数据绘图的 Rust 绘图库,适用于 WASM 和本地应用程序 🦀📈🚀

docs.rs

evcxr-jupyter-integration

因为 evcxr 仅使用 SVG 图像和所有类型的系列,所以我们不需要其他类型的后端。所以我们应该放…

plotters-rs.github.io

Leave a Reply

Your email address will not be published. Required fields are marked *