use csv::ReaderBuilder;
use dataset_core::{Dataset, DatasetError, acquire_dataset, download_to};
use ndarray::{Array1, Array2};
use std::fs::File;
type AbaloneData = (Array2<String>, Array2<f64>, Array1<f64>);
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<AbaloneData, DatasetError>,
}
impl Abalone {
pub fn new(storage_dir: &str) -> Self {
Abalone {
dataset: Dataset::new(storage_dir, Self::load_data),
}
}
fn load_data(dir: &str) -> Result<AbaloneData, DatasetError> {
let file_path = acquire_dataset(
dir,
ABALONE_FILENAME,
ABALONE_DATASET_NAME,
Some(ABALONE_SHA256),
|temp_path| {
download_to(ABALONE_DATA_URL, temp_path, Some(ABALONE_FILENAME))?;
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<String> = Vec::with_capacity(N_SAMPLES * N_STRING_FEATURES);
let mut numeric_features: Vec<f64> = Vec::with_capacity(N_SAMPLES * N_NUMERIC_FEATURES);
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 &(col, name) in STRING_COLUMNS.iter() {
let value = &record[col];
if value.is_empty() {
return Err(DatasetError::invalid_value(
ABALONE_DATASET_NAME,
name,
value,
line_num,
));
}
string_features.push(value.to_string());
}
for &(col, name) in NUMERIC_COLUMNS.iter() {
let value: f64 = record[col].parse().map_err(|e| {
DatasetError::parse_failed(ABALONE_DATASET_NAME, name, line_num, e)
})?;
numeric_features.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 n_samples = targets.len();
if n_samples == 0 {
return Err(DatasetError::empty_dataset(ABALONE_DATASET_NAME));
}
let string_array = Array2::from_shape_vec((n_samples, N_STRING_FEATURES), string_features)
.map_err(|e| {
DatasetError::array_shape_error(ABALONE_DATASET_NAME, "string_features", e)
})?;
let numeric_array =
Array2::from_shape_vec((n_samples, N_NUMERIC_FEATURES), numeric_features).map_err(
|e| DatasetError::array_shape_error(ABALONE_DATASET_NAME, "numeric_features", e),
)?;
let targets_array = Array1::from_vec(targets);
Ok((string_array, numeric_array, targets_array))
}
pub fn features(&self) -> Result<(&Array2<String>, &Array2<f64>), DatasetError> {
let data = self.dataset.load()?;
Ok((&data.0, &data.1))
}
pub fn targets(&self) -> Result<&Array1<f64>, DatasetError> {
Ok(&self.dataset.load()?.2)
}
pub fn data(&self) -> Result<&AbaloneData, DatasetError> {
self.dataset.load()
}
pub fn get_data(&self) -> Option<&AbaloneData> {
self.dataset.get()
}
pub fn get_data_mut(&mut self) -> Option<&mut AbaloneData> {
self.dataset.get_mut()
}
pub fn into_data(self) -> Result<AbaloneData, DatasetError> {
self.dataset.load()?;
Ok(self
.dataset
.into_inner()
.expect("data is present after a successful load"))
}
pub fn take_data(&mut self) -> Result<AbaloneData, DatasetError> {
self.dataset.load()?;
Ok(self
.dataset
.take()
.expect("data is present after a successful load"))
}
}