Repository: GlobalMaksimum/sadedegel (language: python)

Issue:
Implement Product Review Sentiment Dataset
Previous datasets were rejected due to dataset size, I found a larger dataset in huggingface.
[turkish_product_reviews](https://huggingface.co/datasets/turkish_product_reviews)

This is not benchmarked but benchmarked datasets are hybrid which means they contain movie reviews, twitter posts and hotel reviews, which can be addressed as seperate datasets.


Discussion / hints:
Thanks for your recommendation. Here are two options for you

1. If there is already an academic work which we can bencmark ourselves please do refer to that one so that we can apply their method
2. Otherwise let's assume that this is a uniform dataset to see how well we perform over such a mixed copus ;)

In any way waiting for your baseline model

Regards,

Fix patch:
diff --git a/sadedegel/dataset/product_sentiment/__init__.py b/sadedegel/dataset/product_sentiment/__init__.py
new file mode 100644
index 00000000..6b259577
--- /dev/null
+++ b/sadedegel/dataset/product_sentiment/__init__.py
@@ -0,0 +1,2 @@
+from ._core import load_product_sentiment_train
+from ._core import CLASS_VALUES, CORPUS_SIZE
diff --git a/sadedegel/dataset/product_sentiment/__main__.py b/sadedegel/dataset/product_sentiment/__main__.py
new file mode 100644
index 00000000..65d693a0
--- /dev/null
+++ b/sadedegel/dataset/product_sentiment/__main__.py
@@ -0,0 +1,82 @@
+import gzip
+import os.path
+import sys
+from itertools import tee
+from pathlib import Path
+from shutil import copyfileobj
+
+import boto3
+import click
+from loguru import logger
+from rich.console import Console
+from smart_open import open
+
+from ._core import load_product_sentiment_train, CORPUS_SIZE
+
+console = Console()
+
+logger.disable("sadedegel")
+
+
+@click.group(help="Product Sentiment Dataset Commandline")
+def cli():
+    pass
+
+
+@cli.command()
+@click.option("--access-key", help="Access Key ID to download dataset.", prompt=True,
+              default=lambda: os.environ.get('sadedegel_access_key', ''))
+@click.option("--secret-key", help="Secret Key ID to download dataset.", prompt=True,
+              default=lambda: os.environ.get('sadedegel_secret_key', ''))
+@click.option("--data_home", '-d', help="Data home directory", default="~/.sadedegel_data")
+def download(access_key, secret_key, data_home):
+    """Download product sentiment corpus from cloud with your key."""
+
+    data_home = Path(os.path.expanduser(data_home)) / 'product_sentiment'
+    data_home.mkdir(parents=True, exist_ok=True)
+    console.print(f"Data directory for product sentiment data {data_home}")
+
+    transport_params = {
+        'session': boto3.Session(aws_access_key_id=access_key,
+                                 aws_secret_access_key=secret_key),
+        'resource_kwargs': {
+            'endpoint_url': 'https://storage.googleapis.com',
+        }
+    }
+    
+    url = f"s3://sadedegel/dataset/product_sentiment.csv.gz"
+
+    with open(url, 'rb', transport_params=transport_params) as fp, gzip.open(data_home / os.path.basename(url),
+                                                                             "wb") as wp:
+        copyfileobj(fp, wp)
+
+
+@cli.command()
+def validate():
+    """Sanity check on corpus
+    """
+    with console.status("[bold yellow]Validating train"):
+        train = load_product_sentiment_train()
+
+        train_clone, train = tee(train, 2)
+
+        n_train = sum(1 for _ in train_clone)
+        categories = set([row['sentiment_class'] for row in train])
+
+        if n_train == CORPUS_SIZE:
+            console.log("Cardinality check [yellow]DONE[/yellow]")
+        else:
+            console.log(f"Cardinality check [red]FAILED[/red]")
+            console.log(f"|Product sentiment (train)| : {n_train} ({CORPUS_SIZE} expected)")
+            sys.exit(1)
+
+        if categories == {0, 1, 2}:
+            console.log("Label check [yellow]DONE[/yellow]")
+        else:
+            console.log("Class check [red]FAILED[/red]")
+            console.log(f"\tProduct sentiment classes : {categories} ({set(['POSITIVE', 'NEGATIVE', 'NEUTRAL'])} expected)")
+            sys.exit(1)
+
+
+if __name__ == "__main__":
+    cli()
diff --git a/sadedegel/dataset/product_sentiment/_core.py b/sadedegel/dataset/product_sentiment/_core.py
new file mode 100644
index 00000000..b15dd05b
--- /dev/null
+++ b/sadedegel/dataset/product_sentiment/_core.py
@@ -0,0 +1,51 @@
+import csv
+import gzip
+from pathlib import Path
+from rich.console import Console
+
+CLASS_VALUES = ['NEUTRAL', 'NEGATIVE', 'POSITIVE']
+CORPUS_SIZE = 11426
+
+console = Console()
+
+__general_download_message__ = """Ensure that you have properly downloaded product sentiment corpus using
+
+            python -m sadedegel.dataset.product_sentiment download --access-key xxx --secret-key xxxx
+
+        Unfortunately due to data licensing issues we could not share data publicly. 
+        Get in touch with sadedegel team to obtain a download key.
+        """
+
+
+def check_directory_structure(path: str) -> bool:
+    base_dir = Path(path).expanduser()
+
+    product_sentiment_dir = base_dir / 'product_sentiment'
+
+    if not base_dir.exists():
+        console.log(f"Dataset base directory ([bold red]{base_dir}[/bold red]) does not exist")
+
+    elif not product_sentiment_dir.exists():
+        console.log(
+            f"Product sentiment directory ([bold red]{product_sentiment_dir}[/bold red]) does not exist")
+
+    else:
+        return True
+
+    console.log(__general_download_message__)
+
+    return False
+
+
+def load_product_sentiment_train(data_home="~/.sadedegel_data"):
+    if not check_directory_structure(data_home):
+        raise Exception("Product Sentiment Corpus validation error")
+
+    train_csv = Path(data_home).expanduser() / "product_sentiment"
+    train_csv = train_csv / "product_sentiment.csv.gz"
+
+    with gzip.open(train_csv, "rt") as csvfile:
+        rd = csv.DictReader(csvfile)
+
+        for rec in rd:
+            yield dict(text=rec['text'], sentiment_class=CLASS_VALUES.index(rec['sentiment']))


Test patch:
diff --git a/tests/datasets/context.py b/tests/datasets/context.py
index 8db9cfcc..168e45be 100644
--- a/tests/datasets/context.py
+++ b/tests/datasets/context.py
@@ -7,6 +7,8 @@
 from sadedegel.dataset.extended import load_extended_metadata, load_extended_sents_corpus, load_extended_raw_corpus  # noqa # pylint: disable=unused-import, wrong-import-position
 from sadedegel.dataset.tscorpus import load_tokenization_raw,load_tokenization_tokenized, check_and_display, CORPUS_SIZE # noqa # pylint: disable=unused-import, wrong-import-position
 from sadedegel.dataset.tweet_sentiment import load_tweet_sentiment_train, CLASS_VALUES # noqa # pylint: disable=unused-import, wrong-import-position
+from sadedegel.dataset.product_sentiment import load_product_sentiment_train # noqa # pylint: disable=unused-import, wrong-import-position
+from sadedegel.dataset.product_sentiment import CLASS_VALUES as PS_CLASS_VALUES # noqa # pylint: disable=unused-import, wrong-import-position
 from sadedegel.bblock.cli.__main__ import tok_eval # noqa # pylint: disable=unused-import, wrong-import-position
 from sadedegel.dataset import util # noqa # pylint: disable=unused-import, wrong-import-position
 from sadedegel.dataset import file_paths, CorpusTypeEnum # noqa # pylint: disable=unused-import, wrong-import-position
diff --git a/tests/datasets/test_product_sentiment.py b/tests/datasets/test_product_sentiment.py
new file mode 100644
index 00000000..8d948652
--- /dev/null
+++ b/tests/datasets/test_product_sentiment.py
@@ -0,0 +1,14 @@
+import pytest
+from pathlib import Path  # pylint: disable=unused-import
+from os.path import expanduser  # pylint: disable=unused-import
+from .context import load_product_sentiment_train, PS_CLASS_VALUES
+
+
+@pytest.mark.skipif('not Path(expanduser("~/.sadedegel_data/product_sentiment")).exists()')
+def test_data_load():
+    data = load_product_sentiment_train()
+    for i, row in enumerate(data):
+        assert any(key in row.keys() for key in ['text', 'sentiment_class'])
+        assert isinstance(row['text'], str)
+        assert PS_CLASS_VALUES[row['sentiment_class']] in ['NEUTRAL', 'POSITIVE', 'NEGATIVE']
+    assert i + 1 == 11426