use csv::ReaderBuilder;
use dataset_core::{Dataset, DatasetError, acquire_dataset, download_to, unzip};
use ndarray::Array1;
use std::fs::File;
use std::io::Write as _;
type SentimentSentencesData = (Array1<String>, Array1<&'static str>, Array1<&'static str>);
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<SentimentSentencesData, DatasetError>,
}
impl SentimentSentences {
pub fn new(storage_dir: &str) -> Self {
SentimentSentences {
dataset: Dataset::new(storage_dir, Self::load_data),
}
}
fn load_data(dir: &str) -> Result<SentimentSentencesData, DatasetError> {
let file_path = acquire_dataset(
dir,
SENTIMENT_SENTENCES_FILENAME,
SENTIMENT_SENTENCES_DATASET_NAME,
Some(SENTIMENT_SENTENCES_SHA256),
|temp_path| {
download_to(
SENTIMENT_SENTENCES_DATA_URL,
temp_path,
Some(SENTIMENT_SENTENCES_ZIP_FILENAME),
)?;
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<&'static str> = Vec::with_capacity(N_SAMPLES);
let mut labels: Vec<&'static str> = 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);
labels.push(label);
texts.push(record[SENTENCE_COLUMN].to_string());
}
let n_samples = labels.len();
if n_samples == 0 {
return Err(DatasetError::empty_dataset(
SENTIMENT_SENTENCES_DATASET_NAME,
));
}
let texts_array = Array1::from_vec(texts);
let sources_array = Array1::from_vec(sources);
let labels_array = Array1::from_vec(labels);
Ok((texts_array, sources_array, labels_array))
}
pub fn texts(&self) -> Result<&Array1<String>, DatasetError> {
Ok(&self.dataset.load()?.0)
}
pub fn sources(&self) -> Result<&Array1<&'static str>, DatasetError> {
Ok(&self.dataset.load()?.1)
}
pub fn labels(&self) -> Result<&Array1<&'static str>, DatasetError> {
Ok(&self.dataset.load()?.2)
}
pub fn data(&self) -> Result<&SentimentSentencesData, DatasetError> {
self.dataset.load()
}
pub fn get_data(&self) -> Option<&SentimentSentencesData> {
self.dataset.get()
}
pub fn get_data_mut(&mut self) -> Option<&mut SentimentSentencesData> {
self.dataset.get_mut()
}
pub fn into_data(self) -> Result<SentimentSentencesData, DatasetError> {
self.dataset.load()?;
Ok(self
.dataset
.into_inner()
.expect("data is present after a successful load"))
}
pub fn take_data(&mut self) -> Result<SentimentSentencesData, DatasetError> {
self.dataset.load()?;
Ok(self
.dataset
.take()
.expect("data is present after a successful load"))
}
}