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, unzip};
use ndarray::Array1;
use std::fs::File;
use std::io::Write as _;
const SENTIMENT_SENTENCES_DATA_URL: &str = "https://archive.ics.uci.edu/ml/machine-learning-databases/00331/sentiment%20labelled%20sentences.zip";
const SENTIMENT_SENTENCES_ZIP_FILENAME: &str = "sentiment_labelled_sentences.zip";
const SENTIMENT_SENTENCES_SUBDIR: &str = "sentiment labelled sentences";
const SENTIMENT_SENTENCES_SOURCE_FILES: [(&str, &str); 3] = [
("amazon", "amazon_cells_labelled.txt"),
("imdb", "imdb_labelled.txt"),
("yelp", "yelp_labelled.txt"),
];
const SENTIMENT_SENTENCES_FILENAME: &str = "sentiment_sentences.csv";
const SENTIMENT_SENTENCES_SHA256: &str =
"3a6aac64fa37c8075d49678cd73140eaa70a95c984d540ddf93ec7b021e05725";
const SENTIMENT_SENTENCES_DATASET_NAME: &str = "sentiment_sentences";
const N_SAMPLES: usize = 3_000;
const N_COLUMNS: usize = 3;
const SOURCE_COLUMN: usize = 0;
const SENTENCE_COLUMN: usize = 1;
const LABEL_COLUMN: usize = 2;
#[derive(Debug)]
pub struct SentimentSentences {
dataset: Dataset<Table, DatasetError>,
}
impl SentimentSentences {
pub const FEATURE_NAMES: [&'static str; 1] = ["text"];
pub const TARGET: &'static str = "label";
pub fn new(storage_dir: &str) -> Self {
SentimentSentences {
dataset: Dataset::new(storage_dir, Self::load_data),
}
}
fn load_data(dir: &str) -> Result<Table, DatasetError> {
let file_path = acquire_dataset(
dir,
SENTIMENT_SENTENCES_FILENAME,
SENTIMENT_SENTENCES_DATASET_NAME,
Some(SENTIMENT_SENTENCES_SHA256),
|temp_path| {
download_to_with_retries(
SENTIMENT_SENTENCES_DATA_URL,
temp_path,
Some(SENTIMENT_SENTENCES_ZIP_FILENAME),
DOWNLOAD_RETRIES,
)?;
unzip(&temp_path.join(SENTIMENT_SENTENCES_ZIP_FILENAME), temp_path)?;
let src_dir = temp_path.join(SENTIMENT_SENTENCES_SUBDIR);
let combined_path = temp_path.join(SENTIMENT_SENTENCES_FILENAME);
let mut combined = File::create(&combined_path)?;
for (source, filename) in SENTIMENT_SENTENCES_SOURCE_FILES {
let content = std::fs::read_to_string(src_dir.join(filename))?;
for line in content.lines() {
if line.trim().is_empty() {
continue;
}
combined.write_all(source.as_bytes())?;
combined.write_all(b"\t")?;
combined.write_all(line.as_bytes())?;
combined.write_all(b"\n")?;
}
}
combined.flush()?;
Ok(combined_path)
},
)?;
let file = File::open(&file_path)?;
let mut rdr = ReaderBuilder::new()
.delimiter(b'\t')
.has_headers(false)
.quoting(false)
.from_reader(file);
let mut texts: Vec<String> = Vec::with_capacity(N_SAMPLES);
let mut sources: Vec<String> = Vec::with_capacity(N_SAMPLES);
let mut labels: Vec<String> = Vec::with_capacity(N_SAMPLES);
for (idx, result) in rdr.records().enumerate() {
let record = result
.map_err(|e| DatasetError::csv_read_error(SENTIMENT_SENTENCES_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(
SENTIMENT_SENTENCES_DATASET_NAME,
N_COLUMNS,
record.len(),
line_num,
));
}
let source = match &record[SOURCE_COLUMN] {
"amazon" => "amazon",
"imdb" => "imdb",
"yelp" => "yelp",
other => {
return Err(DatasetError::invalid_value(
SENTIMENT_SENTENCES_DATASET_NAME,
"source",
other,
line_num,
));
}
};
let label = match &record[LABEL_COLUMN] {
"0" => "negative",
"1" => "positive",
other => {
return Err(DatasetError::invalid_value(
SENTIMENT_SENTENCES_DATASET_NAME,
"label",
other,
line_num,
));
}
};
sources.push(source.to_string());
labels.push(label.to_string());
texts.push(record[SENTENCE_COLUMN].to_string());
}
Table::new(
SENTIMENT_SENTENCES_DATASET_NAME,
vec![
Column::new(
Self::FEATURE_NAMES[0],
ColumnData::String(Array1::from_vec(texts)),
),
Column::new("source", ColumnData::String(Array1::from_vec(sources))),
Column::new(Self::TARGET, ColumnData::String(Array1::from_vec(labels))),
],
)
}
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!(SentimentSentences, "sentiment_sentences");