5个版本

0.1.4 2020年12月8日
0.1.3 2020年11月20日
0.1.2 2020年11月18日
0.1.1 2020年11月18日
0.1.0 2020年11月18日

#481 in 数学

MIT许可证

61KB
380

RavenCol

CratesIo Documentacion

文档

西班牙语版本:这里

Rust中的表格数据操作。

RavenCol允许以简单的方式消费表格数据并在其上执行操作。在数据方面,最常用的格式之一是表格数据(CSV文件),因此在这个第一阶段,所有函数都集中在消费CSV文件和获取允许对其操作的表格数据结构。

主要结构是RawFrame。从构建RawFrame开始,可以生成其列或列集的迭代器,可以对它们执行更复杂的操作。与RawFrame相关的大多数方法都返回迭代器,这允许使用Rust的迭代器功能的所有能力来计算RawFrame及其组件。

使用Rust的迭代器功能,可以实现许多在RawFrame中期望的功能。

RawFrame构建

目前,只能从CSV文件构建RawFrame。要构建RawFrame,需要文件路径。有2个函数用于创建RawFrame

  • 从OsString创建RawFrame:RawFrame::from_os_string(file_path: OsString)
  • 当二进制文件执行时,从第n个参数创建RawFrame:RawFrame::from_arg(n: usize)

加载CSV文件的示例

let path = OsString::from("./datos_test/test.csv");
let datos = RawFrame::from_os_string(path).unwrap();

每个RawFrame有两个元素

  • columns,其中存储从CSV文件的第一行获得的列名
  • records,其中存储所有记录,作为行向量的集合。

如果我们的数据在多个文件中,则可以将RawFrame连接起来,此操作会修改基本RawFrame并消耗目标RawFrame。到目前为止,连接函数仅检查两个DataFrame的列数是否相同。保证两个RawFrame的列具有语义意义并且顺序相同是用户的责任。

加载目录中所有文件的示例

以下示例假设目录路径作为执行位置的1个参数提供,并且该目录仅包含具有相同类型列的CSV文件。

let directorio = reading::read_arg(1)?;

let mut paths = fs::read_dir(directorio)?.map(|path| path.unwrap().path().into_os_string());
let mut base = RawFrame::from_os_string(paths.next().unwrap())?;

loop {

    let file = match paths.next() {
        Some(rec) => rec,
        None => break,
    };

    base.concat(RawFrame::from_os_string(file)?)?;

}

列创建

一旦存在一个RawFrame,就可以使用它的列。RawFrames被视为存储在内存中的数据结构,作为操作列的数据源。可以通过访问函数获取列并生成可消费的迭代器。

RavenCol的一般理念是在RawFrame中存储数据,并从那里生成可消费的迭代器来计算数据。

当从RawFrame生成列时,我们必须考虑两件事

  • 我的列将包含的数据类型
  • 如何处理无法用该数据类型表示的数据

列访问函数允许您定义特定类型数据的列,但是指定要使用的数据类型是调用函数者的责任,因为这些函数是以通用数据类型定义的。

一旦决定了数据类型,就有三种主要方法来处理无法用该数据类型表示的数据。如何处理这些数据将定义用于列的函数类型:无法获得有效数据的行可以过滤,或者可以赋予我们定义的值。

创建选项列

您可以获得一个列,其中每个数据项都包含在Option中,这样当数据项无效时,其值为None。要创建这种风格的列,请使用函数col_type (column),其中column是您要获取的列的名称。

fn get_data() -> ravencol::RawFrame {
    let path = OsString::from("./datos_test/test.csv");
    let datos = RawFrame::from_os_string(path).unwrap();
    datos
}

let datos = get_data();

let columna: Vec<Option<i32>> = datos.col_type("col_a").unwrap().collect();

通过过滤无效数据创建数据列

可以获取一个列,其中仅保留可表示在所选数据类型中的数据,这样当数据项无效时,它不会被包含在结果迭代器中。要创建这种风格的列,请使用函数col_fil (column),其中column是您要获取的列的名称。当使用此方法时,获得的迭代器元素数量不等于RawFrame中的行数,必须谨慎使用。

fn get_data() -> ravencol::RawFrame {
    let path = OsString::from("./datos_test/test.csv");
    let datos = RawFrame::from_os_string(path).unwrap();
    datos
}

let datos = get_data();

let columna: Vec<i32> = datos.col_fil("col_a").unwrap().collect();

通过插值无效数据创建数据列

可以获取一个列,其中每个无法表示在所选数据类型中的数据项都被替换为方法参数中提供的值,这样当数据项无效时,插值值将代表它。要创建这种风格的列,请使用函数col_imp (column, none_val),其中column是要获取的列的名称, none_val是要替换或插值的值。

fn get_data() -> ravencol::RawFrame {
    let path = OsString::from("./datos_test/test.csv");
    let datos = RawFrame::from_os_string(path).unwrap();
    datos
}

let datos = get_data();

let columna: Vec<i32> = datos.col_imp("col_a",0).unwrap().collect();

创建泛型列

您还可以尝试识别要生成的列的每行数据是整数、浮点数、字符串还是空值来创建泛型列。为此,我们使用创建来表示泛型数据的Enum Datum。

Datum {
    Integer(i32),
    Float(f64),
    NotNumber(&str),
    None
}

要创建这种类型的泛型列,我们使用函数column (column),其中参数column是要获取的列的名称。

fn get_data() -> ravencol::RawFrame {
    let path = OsString::from("./datos_test/test.csv");
    let datos = RawFrame::from_os_string(path).unwrap();
    datos
}

let datos = get_data();

let columna: Vec<Datum> = datos.column("col_a").unwrap().collect();

创建列集

有时需要包含来自多列的数据集的迭代器。例如,为了绘制点,我们需要坐标对。在 RavenCol 中有获取这些数据集的方法。逻辑是相同的,选择数据类型,并定义如何处理无法用该类型表示的值。到目前为止,所有值都必须具有相同的类型,如果需要不同类型的数据结构,可以使用 Datum 类型,然后进行处理。

为了创建这种多列类型,提供了 slice_col_fil (columns)slice_col_imp (columns, imp_vals) 方法,其中 columns 是包含列名的向量,imp_vals 是包含每个列要插值的值的向量。

一个特殊情况是创建列对,因为它们用于创建图形,对于这些情况,我们有 pair_col_fil (xcolumn, ycolumn)pair_col_imp (xcolumn, ycolumn, none_val_x, none_val_y) 这两种方法都返回包含值元组的迭代器。对于连接点图形的特殊情况,即线绘图,提供了考虑第一个列的元素顺序的方法。这些方法是 pair_col_fil_sorted (xcolumn, ycolumn)pair_col_imp_sorted (xcolumn, ycolumn, none_val_x, none_val_y)。排序功能是基本的,始终以第一个列的顺序进行,并且始终是升序。如果需要更复杂的排序,可以使用 Rust 提供的迭代器操作功能来完成。

使用 plotters 绘图示例

use std::error::Error;
use std::process;
use ordered_float::OrderedFloat;
use plotters::prelude::*;

use ravencol::RawFrame;

fn main() {
    if let Err(err) = run() {
        println!("{}", err);
        process::exit(1);
    }
}

fn run() -> Result<(), Box<dyn Error>> {

    let path = OsString::from("./datos_test/pizzas.csv");
    let datos = RawFrame::from_os_string(path).unwrap();

    let col_x = "Reservations";
    let col_y = "Pizzas";

    let extent_x: (OrderedFloat<f64>,OrderedFloat<f64>) = datos.extent_num_fil(col_x)?;
    let extent_y: (OrderedFloat<f64>,OrderedFloat<f64>) = datos.extent_num_fil(col_y)?;

    let x_range = extent_x.0.into_inner()..extent_x.1.into_inner();
    let y_range = extent_y.0.into_inner()..extent_y.1.into_inner();


    let drawing_area = BitMapBackend::new("./test.png", (1024, 768)).into_drawing_area();

    drawing_area.fill(&WHITE).unwrap();

    let mut chart = ChartBuilder::on(&drawing_area)
                    .caption("Pizzas!", ("Arial", 30))
                    .margin(10)
                    .set_label_area_size(LabelAreaPosition::Left, 50)
                    .set_label_area_size(LabelAreaPosition::Bottom, 50)
                    .build_cartesian_2d(x_range,y_range)?;

    chart.configure_mesh()
            .y_desc("Pizzas")
            .x_desc("Reservaciones")
            .axis_desc_style(("sans-serif", 20))
            .draw().unwrap();

    chart.draw_series(
        LineSeries::new(datos.pair_col_fil_sorted(col_x,col_y)? ,&RED),
    )?;

    chart.draw_series(
        AreaSeries::new(datos.pair_col_fil_sorted(col_x,col_y)?,0.0,&BLUE.mix(0.2)),
    )?;

    chart.draw_series(datos.pair_col_fil(col_x, col_y).unwrap().map(|point| Circle::new(point, 3, &RED)))
        .unwrap();

    Ok(())
}

依赖关系

~1.3–1.7MB
~21K SLoC