use csv::ReaderBuilder;
use dataset_core::{Dataset, DatasetError, acquire_dataset, download_to};
use ndarray::{Array1, Array2};
use serde::Deserialize;
use std::fs::File;
const BREAST_CANCER_DATA_URL: &str =
"https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data";
const BREAST_CANCER_FILENAME: &str = "breast_cancer.csv";
const BREAST_CANCER_SHA256: &str =
"d606af411f3e5be8a317a5a8b652b425aaf0ff38ca683d5327ffff94c3695f4a";
const BREAST_CANCER_DATASET_NAME: &str = "breast_cancer";
const N_FEATURES: usize = 30;
type BreastCancerData = (Array2<f64>, Array1<&'static str>);
#[derive(Deserialize)]
struct BreastCancerRecord {
#[allow(dead_code)]
id: u64,
diagnosis: String,
radius_mean: f64,
texture_mean: f64,
perimeter_mean: f64,
area_mean: f64,
smoothness_mean: f64,
compactness_mean: f64,
concavity_mean: f64,
concave_points_mean: f64,
symmetry_mean: f64,
fractal_dimension_mean: f64,
radius_se: f64,
texture_se: f64,
perimeter_se: f64,
area_se: f64,
smoothness_se: f64,
compactness_se: f64,
concavity_se: f64,
concave_points_se: f64,
symmetry_se: f64,
fractal_dimension_se: f64,
radius_worst: f64,
texture_worst: f64,
perimeter_worst: f64,
area_worst: f64,
smoothness_worst: f64,
compactness_worst: f64,
concavity_worst: f64,
concave_points_worst: f64,
symmetry_worst: f64,
fractal_dimension_worst: f64,
}
#[derive(Debug)]
pub struct BreastCancer {
dataset: Dataset<BreastCancerData, DatasetError>,
}
impl BreastCancer {
pub fn new(storage_dir: &str) -> Self {
BreastCancer {
dataset: Dataset::new(storage_dir, Self::load_data),
}
}
fn load_data(dir: &str) -> Result<BreastCancerData, DatasetError> {
let file_path = acquire_dataset(
dir,
BREAST_CANCER_FILENAME,
BREAST_CANCER_DATASET_NAME,
Some(BREAST_CANCER_SHA256),
|temp_path| {
download_to(
BREAST_CANCER_DATA_URL,
temp_path,
Some(BREAST_CANCER_FILENAME),
)?;
Ok(temp_path.join(BREAST_CANCER_FILENAME))
},
)?;
let file = File::open(&file_path)?;
let mut rdr = ReaderBuilder::new().has_headers(false).from_reader(file);
let mut features = Vec::new();
let mut labels = Vec::new();
for (idx, result) in rdr.deserialize::<BreastCancerRecord>().enumerate() {
let BreastCancerRecord {
id: _,
diagnosis,
radius_mean,
texture_mean,
perimeter_mean,
area_mean,
smoothness_mean,
compactness_mean,
concavity_mean,
concave_points_mean,
symmetry_mean,
fractal_dimension_mean,
radius_se,
texture_se,
perimeter_se,
area_se,
smoothness_se,
compactness_se,
concavity_se,
concave_points_se,
symmetry_se,
fractal_dimension_se,
radius_worst,
texture_worst,
perimeter_worst,
area_worst,
smoothness_worst,
compactness_worst,
concavity_worst,
concave_points_worst,
symmetry_worst,
fractal_dimension_worst,
} = result.map_err(|e| DatasetError::csv_read_error(BREAST_CANCER_DATASET_NAME, e))?;
let line_num = idx + 1;
features.extend_from_slice(&[
radius_mean,
texture_mean,
perimeter_mean,
area_mean,
smoothness_mean,
compactness_mean,
concavity_mean,
concave_points_mean,
symmetry_mean,
fractal_dimension_mean,
radius_se,
texture_se,
perimeter_se,
area_se,
smoothness_se,
compactness_se,
concavity_se,
concave_points_se,
symmetry_se,
fractal_dimension_se,
radius_worst,
texture_worst,
perimeter_worst,
area_worst,
smoothness_worst,
compactness_worst,
concavity_worst,
concave_points_worst,
symmetry_worst,
fractal_dimension_worst,
]);
labels.push(match diagnosis.as_str() {
"M" => "malignant",
"B" => "benign",
other => {
return Err(DatasetError::invalid_value(
BREAST_CANCER_DATASET_NAME,
"diagnosis",
other,
line_num,
));
}
});
}
let n_samples = labels.len();
if n_samples == 0 {
return Err(DatasetError::empty_dataset(BREAST_CANCER_DATASET_NAME));
}
let features_array =
Array2::from_shape_vec((n_samples, N_FEATURES), features).map_err(|e| {
DatasetError::array_shape_error(BREAST_CANCER_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<&'static str>, DatasetError> {
Ok(&self.dataset.load()?.1)
}
pub fn data(&self) -> Result<&BreastCancerData, DatasetError> {
self.dataset.load()
}
pub fn get_data(&self) -> Option<&BreastCancerData> {
self.dataset.get()
}
pub fn get_data_mut(&mut self) -> Option<&mut BreastCancerData> {
self.dataset.get_mut()
}
pub fn into_data(self) -> Result<BreastCancerData, DatasetError> {
self.dataset.load()?;
Ok(self
.dataset
.into_inner()
.expect("data is present after a successful load"))
}
pub fn take_data(&mut self) -> Result<BreastCancerData, DatasetError> {
self.dataset.load()?;
Ok(self
.dataset
.take()
.expect("data is present after a successful load"))
}
}