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 std::fs::File;
const ABALONE_DATA_URL: &str =
"https://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data";
const ABALONE_FILENAME: &str = "abalone.csv";
const ABALONE_SHA256: &str = "de37cdcdcaaa50c309d514f248f7c2302a5f1f88c168905eba23fe2fbc78449f";
const ABALONE_DATASET_NAME: &str = "abalone";
const N_SAMPLES: usize = 4_177;
const N_STRING_FEATURES: usize = 1;
const N_NUMERIC_FEATURES: usize = 7;
const N_COLUMNS: usize = 9;
const TARGET_COLUMN: usize = 8;
const STRING_COLUMNS: [(usize, &str); N_STRING_FEATURES] = [(0, "sex")];
const NUMERIC_COLUMNS: [(usize, &str); N_NUMERIC_FEATURES] = [
(1, "length"),
(2, "diameter"),
(3, "height"),
(4, "whole_weight"),
(5, "shucked_weight"),
(6, "viscera_weight"),
(7, "shell_weight"),
];
#[derive(Debug)]
pub struct Abalone {
dataset: Dataset<Table, DatasetError>,
}
impl Abalone {
pub const FEATURE_NAMES: [&'static str; N_COLUMNS - 1] = [
"sex",
"length",
"diameter",
"height",
"whole_weight",
"shucked_weight",
"viscera_weight",
"shell_weight",
];
pub const TARGET: &'static str = "rings";
pub fn new(storage_dir: &str) -> Self {
Abalone {
dataset: Dataset::new(storage_dir, Self::load_data),
}
}
fn load_data(dir: &str) -> Result<Table, DatasetError> {
let file_path = acquire_dataset(
dir,
ABALONE_FILENAME,
ABALONE_DATASET_NAME,
Some(ABALONE_SHA256),
|temp_path| {
download_to_with_retries(
ABALONE_DATA_URL,
temp_path,
Some(ABALONE_FILENAME),
DOWNLOAD_RETRIES,
)?;
Ok(temp_path.join(ABALONE_FILENAME))
},
)?;
let file = File::open(&file_path)?;
let mut rdr = ReaderBuilder::new().has_headers(false).from_reader(file);
let mut string_features: Vec<Vec<String>> = STRING_COLUMNS
.iter()
.map(|_| Vec::with_capacity(N_SAMPLES))
.collect();
let mut numeric_features: Vec<Vec<f64>> = NUMERIC_COLUMNS
.iter()
.map(|_| Vec::with_capacity(N_SAMPLES))
.collect();
let mut targets: Vec<f64> = Vec::with_capacity(N_SAMPLES);
for (idx, result) in rdr.records().enumerate() {
let record =
result.map_err(|e| DatasetError::csv_read_error(ABALONE_DATASET_NAME, e))?;
let line_num = idx + 1;
if record.iter().all(|f| f.is_empty()) {
continue;
}
if record.len() != N_COLUMNS {
return Err(DatasetError::invalid_column_count(
ABALONE_DATASET_NAME,
N_COLUMNS,
record.len(),
line_num,
));
}
for (values, &(col, name)) in string_features.iter_mut().zip(STRING_COLUMNS.iter()) {
let value = &record[col];
if value.is_empty() {
return Err(DatasetError::invalid_value(
ABALONE_DATASET_NAME,
name,
value,
line_num,
));
}
values.push(value.to_string());
}
for (values, &(col, name)) in numeric_features.iter_mut().zip(NUMERIC_COLUMNS.iter()) {
let value: f64 = record[col].parse().map_err(|e| {
DatasetError::parse_failed(ABALONE_DATASET_NAME, name, line_num, e)
})?;
values.push(value);
}
let target: f64 = record[TARGET_COLUMN].parse().map_err(|e| {
DatasetError::parse_failed(ABALONE_DATASET_NAME, "rings", line_num, e)
})?;
targets.push(target);
}
let mut columns = Vec::with_capacity(N_COLUMNS);
for (values, &(_col, name)) in string_features.into_iter().zip(STRING_COLUMNS.iter()) {
columns.push(Column::new(
name,
ColumnData::String(Array1::from_vec(values)),
));
}
for (values, &(_col, name)) in numeric_features.into_iter().zip(NUMERIC_COLUMNS.iter()) {
columns.push(Column::new(
name,
ColumnData::Numeric(Array1::from_vec(values)),
));
}
columns.push(Column::new(
Self::TARGET,
ColumnData::Numeric(Array1::from_vec(targets)),
));
Table::new(ABALONE_DATASET_NAME, columns)
}
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!(Abalone, "abalone");