use crate::DOWNLOAD_RETRIES;
use crate::traits::impl_ml_dataset;
use dataset_core::{Dataset, DatasetError, acquire_dataset, download_to_with_retries, unzip};
use ndarray::{Array1, Array2};
use std::fs::File;
use csv::ReaderBuilder;
const BANKNOTE_AUTHENTICATION_DATA_URL: &str =
"https://archive.ics.uci.edu/static/public/267/banknote+authentication.zip";
const BANKNOTE_AUTHENTICATION_ZIP_FILENAME: &str = "banknote_authentication.zip";
const BANKNOTE_AUTHENTICATION_SOURCE_FILENAME: &str = "data_banknote_authentication.txt";
const BANKNOTE_AUTHENTICATION_FILENAME: &str = "banknote_authentication.csv";
const BANKNOTE_AUTHENTICATION_SHA256: &str =
"d0539aaed2139ba7a587b3e34fb345ce503ff7d5d33dbf9912d8e195ce425cb9";
const BANKNOTE_AUTHENTICATION_DATASET_NAME: &str = "banknote_authentication";
const N_SAMPLES: usize = 1372;
const N_FEATURES: usize = 4;
const N_COLUMNS: usize = N_FEATURES + 1;
const FEATURE_NAMES: [&str; N_FEATURES] = ["variance", "skewness", "curtosis", "entropy"];
type BanknoteAuthenticationData = (Array2<f64>, Array1<u8>);
#[derive(Debug)]
pub struct BanknoteAuthentication {
dataset: Dataset<BanknoteAuthenticationData, DatasetError>,
}
impl BanknoteAuthentication {
pub fn new(storage_dir: &str) -> Self {
BanknoteAuthentication {
dataset: Dataset::new(storage_dir, Self::load_data),
}
}
fn load_data(dir: &str) -> Result<BanknoteAuthenticationData, DatasetError> {
let file_path = acquire_dataset(
dir,
BANKNOTE_AUTHENTICATION_FILENAME,
BANKNOTE_AUTHENTICATION_DATASET_NAME,
Some(BANKNOTE_AUTHENTICATION_SHA256),
|temp_path| {
download_to_with_retries(
BANKNOTE_AUTHENTICATION_DATA_URL,
temp_path,
Some(BANKNOTE_AUTHENTICATION_ZIP_FILENAME),
DOWNLOAD_RETRIES,
)?;
unzip(
&temp_path.join(BANKNOTE_AUTHENTICATION_ZIP_FILENAME),
temp_path,
)?;
Ok(temp_path.join(BANKNOTE_AUTHENTICATION_SOURCE_FILENAME))
},
)?;
let file = File::open(&file_path)?;
let mut rdr = ReaderBuilder::new().has_headers(false).from_reader(file);
let mut features: Vec<f64> = Vec::with_capacity(N_SAMPLES * N_FEATURES);
let mut labels: Vec<u8> = Vec::with_capacity(N_SAMPLES);
for (idx, result) in rdr.records().enumerate() {
let record = result.map_err(|e| {
DatasetError::csv_read_error(BANKNOTE_AUTHENTICATION_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(
BANKNOTE_AUTHENTICATION_DATASET_NAME,
N_COLUMNS,
record.len(),
line_num,
));
}
for (col, name) in FEATURE_NAMES.iter().enumerate() {
let value: f64 = record[col].trim().parse().map_err(|e| {
DatasetError::parse_failed(
BANKNOTE_AUTHENTICATION_DATASET_NAME,
name,
line_num,
e,
)
})?;
features.push(value);
}
let raw_label = record[N_FEATURES].trim();
let label: u8 = raw_label.parse().map_err(|e| {
DatasetError::parse_failed(
BANKNOTE_AUTHENTICATION_DATASET_NAME,
"class",
line_num,
e,
)
})?;
if label > 1 {
return Err(DatasetError::invalid_value(
BANKNOTE_AUTHENTICATION_DATASET_NAME,
"class",
raw_label,
line_num,
));
}
labels.push(label);
}
let n_samples = labels.len();
if n_samples == 0 {
return Err(DatasetError::empty_dataset(
BANKNOTE_AUTHENTICATION_DATASET_NAME,
));
}
let features_array =
Array2::from_shape_vec((n_samples, N_FEATURES), features).map_err(|e| {
DatasetError::array_shape_error(BANKNOTE_AUTHENTICATION_DATASET_NAME, "features", e)
})?;
let labels_array = Array1::from_vec(labels);
Ok((features_array, labels_array))
}
pub fn features(&self) -> Result<&Array2<f64>, DatasetError> {
Ok(&self.dataset.load()?.0)
}
pub fn labels(&self) -> Result<&Array1<u8>, DatasetError> {
Ok(&self.dataset.load()?.1)
}
pub fn data(&self) -> Result<&BanknoteAuthenticationData, DatasetError> {
self.dataset.load()
}
pub fn get_data(&self) -> Option<&BanknoteAuthenticationData> {
self.dataset.get()
}
pub fn get_data_mut(&mut self) -> Option<&mut BanknoteAuthenticationData> {
self.dataset.get_mut()
}
pub fn into_data(self) -> Result<BanknoteAuthenticationData, DatasetError> {
self.dataset.load()?;
Ok(self
.dataset
.into_inner()
.expect("data is present after a successful load"))
}
pub fn take_data(&mut self) -> Result<BanknoteAuthenticationData, DatasetError> {
self.dataset.load()?;
Ok(self
.dataset
.take()
.expect("data is present after a successful load"))
}
}
impl_ml_dataset!(
BanknoteAuthentication,
BanknoteAuthenticationData,
"banknote_authentication"
);