From 1d676a7d0ff65dcacea3c31ddf77cfba422084af Mon Sep 17 00:00:00 2001 From: PrimozGodec Date: Fri, 14 Apr 2023 09:24:17 +0200 Subject: [PATCH] Filter - language form corpus in StopwordsFilter --- orangecontrib/text/annotate_documents.py | 2 +- orangecontrib/text/preprocess/filter.py | 51 ++++++++++++++++--- orangecontrib/text/tests/test_preprocess.py | 36 ++++++++++--- orangecontrib/text/widgets/owannotator.py | 2 +- .../text/widgets/tests/test_owannotator.py | 2 +- 5 files changed, 75 insertions(+), 18 deletions(-) diff --git a/orangecontrib/text/annotate_documents.py b/orangecontrib/text/annotate_documents.py index 5cdb338f1..28e612273 100644 --- a/orangecontrib/text/annotate_documents.py +++ b/orangecontrib/text/annotate_documents.py @@ -289,7 +289,7 @@ def _hypergeom_clusters( corpus_ = Corpus.from_file("book-excerpts") for pp in (LowercaseTransformer(), RegexpTokenizer(r"\w+"), - StopwordsFilter("English"), FrequencyFilter(0.1)): + StopwordsFilter("en"), FrequencyFilter(0.1)): corpus_ = pp(corpus_) transformed_corpus = BowVectorizer().transform(corpus_) diff --git a/orangecontrib/text/preprocess/filter.py b/orangecontrib/text/preprocess/filter.py index 851c5b7ef..4c24043bb 100644 --- a/orangecontrib/text/preprocess/filter.py +++ b/orangecontrib/text/preprocess/filter.py @@ -1,5 +1,5 @@ from itertools import compress -from typing import List, Callable +from typing import List, Callable, Optional import os import re @@ -11,6 +11,7 @@ from Orange.util import wrap_callback, dummy_callback from orangecontrib.text import Corpus +from orangecontrib.text.language import ISO2LANG from orangecontrib.text.misc import wait_nltk_data from orangecontrib.text.preprocess import TokenizedPreprocessor @@ -72,16 +73,52 @@ def from_file(path): class StopwordsFilter(BaseTokenFilter, FileWordListMixin): - """ Remove tokens present in NLTK's language specific lists or a file. """ + """ + Remove tokens present in NLTK's language-specific lists or a file. + + Attributes + ---------- + language + The language for NLTK stopwords selection. If None, language from the + Corpus will be used. + use_default_stopwords + Indication whether to use NLTK's stopwords since setting language to + None doesn't prevent the use of NLTK's stopwords. + path + The path to the file with its stopwords will be used if present. + The file must contain a newline-separated list of words. + """ name = 'Stopwords' - @wait_nltk_data - def __init__(self, language='English', path: str = None): + # nltk uses different language nams for some languages + nltk_mapping = {"Slovenian": "Slovene"} + + def __init__( + self, + language: Optional[str] = None, + use_default_stopwords: bool = True, + path: str = None, + ): super().__init__() FileWordListMixin.__init__(self, path) - self.__stopwords = set(x.strip() for x in - stopwords.words(language.lower())) \ - if language else [] + self.__language = language + self.__use_default_stopwords = use_default_stopwords + self.__stopwords = set() + + @wait_nltk_data + def __call__(self, corpus: Corpus, callback: Callable = None) -> Corpus: + # use language set in init if not None and Corpus's language otherwise + la = ISO2LANG[self.__language or corpus.language] + la = self.nltk_mapping.get(la, la) + if self.__use_default_stopwords: + if la in self.supported_languages(): + self.__stopwords = set(x.strip() for x in stopwords.words(la.lower())) + else: + raise ValueError( + "The stopwords filter does not support the Corpus's or " + "selected language." + ) + return super().__call__(corpus, callback) @staticmethod @wait_nltk_data diff --git a/orangecontrib/text/tests/test_preprocess.py b/orangecontrib/text/tests/test_preprocess.py index f94ba5f81..2fe6209d3 100644 --- a/orangecontrib/text/tests/test_preprocess.py +++ b/orangecontrib/text/tests/test_preprocess.py @@ -430,25 +430,45 @@ def _check(self, token): self.assertEqual(filtered, ['a']) def test_stopwords(self): - f = preprocess.StopwordsFilter('english') - self.assertFalse(f._check('a')) - self.assertTrue(f._check('filter')) + f = preprocess.StopwordsFilter("en") with self.corpus.unlocked(): - self.corpus.metas[0, 0] = 'a snake is in a house' + self.corpus.metas[0, 0] = "a snake is in a house" + self.corpus.metas[1, 0] = "a filter" corpus = f(self.corpus) self.assertListEqual(["snake", "house"], corpus.tokens[0]) + self.assertListEqual(["filter"], corpus.tokens[1]) self.assertEqual(len(corpus.used_preprocessor.preprocessors), 2) def test_stopwords_slovene(self): - f = preprocess.StopwordsFilter('slovene') - self.assertFalse(f._check('in')) - self.assertTrue(f._check('abeceda')) + f = preprocess.StopwordsFilter("sl") with self.corpus.unlocked(): - self.corpus.metas[0, 0] = 'kača je v hiši' + self.corpus.metas[0, 0] = "kača je v hiši" + self.corpus.metas[1, 0] = "in abeceda" + self.corpus.attributes["language"] = "sl" corpus = f(self.corpus) self.assertListEqual(["kača", "hiši"], corpus.tokens[0]) + self.assertListEqual(["abeceda"], corpus.tokens[1]) self.assertEqual(len(corpus.used_preprocessor.preprocessors), 2) + def test_stopwords_language_from_corpus(self): + f = preprocess.StopwordsFilter() + self.corpus.attributes["language"] = "en" + corpus = f(self.corpus) + self.assertListEqual( + ["Human", "machine", "interface", "lab", "abc", "computer", "applications"], + corpus.tokens[0], + ) + + # fail when use_default_stopwords and language not supported + f = preprocess.StopwordsFilter(use_default_stopwords=True) + self.corpus.attributes["language"] = "am" + with self.assertRaises(ValueError): + f(self.corpus) + # success when not use_default_stopwords and language not supported + f = preprocess.StopwordsFilter(use_default_stopwords=False) + corpus = f(self.corpus) + self.assertEqual(len(corpus.tokens), 9) + def test_lexicon(self): f = tempfile.NamedTemporaryFile(delete=False) f.write(b'filter\n') diff --git a/orangecontrib/text/widgets/owannotator.py b/orangecontrib/text/widgets/owannotator.py index 68b1da9db..cdb3db200 100644 --- a/orangecontrib/text/widgets/owannotator.py +++ b/orangecontrib/text/widgets/owannotator.py @@ -611,7 +611,7 @@ def onDeleteWidget(self): corpus_ = Corpus.from_file("book-excerpts") for pp in (LowercaseTransformer(), RegexpTokenizer(r"\w+"), - StopwordsFilter("English"), FrequencyFilter(0.1)): + StopwordsFilter("en"), FrequencyFilter(0.1)): corpus_ = pp(corpus_) transformed_corpus = BowVectorizer().transform(corpus_) diff --git a/orangecontrib/text/widgets/tests/test_owannotator.py b/orangecontrib/text/widgets/tests/test_owannotator.py index cd3a6098b..06237aa27 100644 --- a/orangecontrib/text/widgets/tests/test_owannotator.py +++ b/orangecontrib/text/widgets/tests/test_owannotator.py @@ -22,7 +22,7 @@ def preprocess(corpus: Corpus) -> Corpus: for pp in (LowercaseTransformer(), RegexpTokenizer(r"\w+"), - StopwordsFilter("English"), FrequencyFilter(0.25, 0.5)): + StopwordsFilter("en"), FrequencyFilter(0.25, 0.5)): corpus = pp(corpus) transformed_corpus = BowVectorizer().transform(corpus)