use crate::DOWNLOAD_RETRIES;
use crate::table::{Column, ColumnData, Table};
use crate::traits::impl_ml_dataset;
use csv::ReaderBuilder;
use dataset_core::{Dataset, DatasetError, acquire_dataset, download_to_with_retries};
use ndarray::Array1;
use serde::Deserialize;
use std::fs::File;
const IRIS_DATA_URL: &str = "https://gist.githubusercontent.com/curran/a08a1080b88344b0c8a7/raw/0e7a9b0a5d22642a06d3d5b9bcbad9890c8ee534/iris.csv";
const IRIS_FILENAME: &str = "iris.csv";
const IRIS_SHA256: &str = "c52742e50315a99f956a383faedf7575552675f6409ef0f9a47076dd08479930";
const IRIS_DATASET_NAME: &str = "iris";
const N_SAMPLES: usize = 150;
const N_FEATURES: usize = 4;
const SPECIES: [&str; 3] = ["setosa", "versicolor", "virginica"];
#[derive(Deserialize)]
struct IrisRecord {
sepal_length: f64,
sepal_width: f64,
petal_length: f64,
petal_width: f64,
species: String,
}
#[derive(Debug)]
pub struct Iris {
dataset: Dataset<Table, DatasetError>,
}
impl Iris {
pub const FEATURE_NAMES: [&'static str; N_FEATURES] =
["sepal_length", "sepal_width", "petal_length", "petal_width"];
pub const TARGET: &'static str = "species";
pub fn new(storage_dir: &str) -> Self {
Iris {
dataset: Dataset::new(storage_dir, Self::load_data),
}
}
fn load_data(dir: &str) -> Result<Table, DatasetError> {
let file_path = acquire_dataset(
dir,
IRIS_FILENAME,
IRIS_DATASET_NAME,
Some(IRIS_SHA256),
|temp_path| {
download_to_with_retries(IRIS_DATA_URL, temp_path, None, DOWNLOAD_RETRIES)?;
Ok(temp_path.join(IRIS_FILENAME))
},
)?;
let file = File::open(&file_path)?;
let mut rdr = ReaderBuilder::new().has_headers(false).from_reader(file);
let mut sepal_length = Vec::with_capacity(N_SAMPLES);
let mut sepal_width = Vec::with_capacity(N_SAMPLES);
let mut petal_length = Vec::with_capacity(N_SAMPLES);
let mut petal_width = Vec::with_capacity(N_SAMPLES);
let mut species = Vec::with_capacity(N_SAMPLES);
for (idx, result) in rdr.deserialize::<IrisRecord>().skip(1).enumerate() {
let record = result.map_err(|e| DatasetError::csv_read_error(IRIS_DATASET_NAME, e))?;
let line_num = idx + 2;
if !SPECIES.contains(&record.species.as_str()) {
return Err(DatasetError::invalid_value(
IRIS_DATASET_NAME,
Self::TARGET,
&record.species,
line_num,
));
}
sepal_length.push(record.sepal_length);
sepal_width.push(record.sepal_width);
petal_length.push(record.petal_length);
petal_width.push(record.petal_width);
species.push(record.species);
}
Table::new(
IRIS_DATASET_NAME,
vec![
Column::new(
Self::FEATURE_NAMES[0],
ColumnData::Numeric(Array1::from_vec(sepal_length)),
),
Column::new(
Self::FEATURE_NAMES[1],
ColumnData::Numeric(Array1::from_vec(sepal_width)),
),
Column::new(
Self::FEATURE_NAMES[2],
ColumnData::Numeric(Array1::from_vec(petal_length)),
),
Column::new(
Self::FEATURE_NAMES[3],
ColumnData::Numeric(Array1::from_vec(petal_width)),
),
Column::new(Self::TARGET, ColumnData::String(Array1::from_vec(species))),
],
)
}
pub fn data(&self) -> Result<&Table, DatasetError> {
self.dataset.load()
}
pub fn get_data(&self) -> Option<&Table> {
self.dataset.get()
}
pub fn get_data_mut(&mut self) -> Option<&mut Table> {
self.dataset.get_mut()
}
pub fn into_data(self) -> Result<Table, DatasetError> {
self.dataset.load()?;
Ok(self
.dataset
.into_inner()
.expect("data is present after a successful load"))
}
pub fn take_data(&mut self) -> Result<Table, DatasetError> {
self.dataset.load()?;
Ok(self
.dataset
.take()
.expect("data is present after a successful load"))
}
}
impl_ml_dataset!(Iris, "iris");