ideadensity 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ from .idea_density_rater import cpidr
2
+ from .depid import depid
3
+
4
+ __all__ = ['cpidr', 'depid']
ideadensity/depid.py ADDED
@@ -0,0 +1,291 @@
1
+ from typing import Callable, List, Optional, Set, Tuple
2
+
3
+ import spacy
4
+
5
+ PROPOSITION_DEPENDENCIES = frozenset(
6
+ [
7
+ "advcl",
8
+ "advmod",
9
+ "amod",
10
+ "appos",
11
+ "cc",
12
+ "csubj",
13
+ "csubjpass",
14
+ "det",
15
+ "neg",
16
+ "npadvmod",
17
+ "nsubj",
18
+ "nsubjpass",
19
+ "nummod",
20
+ "poss",
21
+ "predet",
22
+ "preconj",
23
+ "prep",
24
+ "quantmod",
25
+ "tmod",
26
+ "vmod",
27
+ ]
28
+ )
29
+ EXCLUDED_DETERMINERS = frozenset(["a", "an", "the"])
30
+ EXCLUDED_NSUBJ = frozenset(["it", "this"])
31
+
32
+ _nlp = None
33
+
34
+
35
+ def get_nlp() -> spacy.language.Language:
36
+ """
37
+ Load and return the spaCy English language model.
38
+
39
+ Returns:
40
+ spacy.language.Language: The loaded spaCy English language model.
41
+ """
42
+ global _nlp
43
+ if _nlp is None:
44
+ try:
45
+ _nlp = spacy.load("en_core_web_sm")
46
+ except OSError:
47
+ raise OSError(
48
+ "The 'en_core_web_sm' model is not installed. Please install it using: `python -m spacy download en_core_web_sm`."
49
+ )
50
+ return _nlp
51
+
52
+
53
+ def filter_i_you_subject(sent: spacy.tokens.Span) -> bool:
54
+ """
55
+ Check if the sentence contains 'I' or 'you' as the subject of the main verb.
56
+
57
+ This function is a sentence filter used by (Sirts et al., 2017) in their DEPID algorithm.
58
+
59
+ It was added by Sirts et al. to achieve better performance on DementiaBank interviews.
60
+
61
+ It returns False if the sentence contains 'I' or 'you' as the subject of the root verb,
62
+ and True otherwise.
63
+
64
+ Args:
65
+ sent (spacy.tokens.Span): A spaCy sentence span to analyze.
66
+
67
+ Returns:
68
+ bool: False if 'I' or 'you' is the subject of the main verb, True otherwise.
69
+ """
70
+ for token in sent:
71
+ if (
72
+ token.text.lower() in ["i", "you"]
73
+ and token.dep_ == "nsubj"
74
+ and token.head.dep_ == "ROOT"
75
+ ):
76
+ return False
77
+ return True
78
+
79
+
80
+ def filter_excluded_determiners(token: spacy.tokens.Token) -> bool:
81
+ """
82
+ Check if a token is an excluded determiner.
83
+
84
+ This function is a token filter used by (Sirts et al., 2017) in their DEPID algorithm.
85
+ It returns False if the token is a determiner ('det') and is in the list of excluded determiners,
86
+ and True otherwise.
87
+
88
+ Args:
89
+ token (spacy.tokens.Token): A spaCy token to analyze.
90
+
91
+ Returns:
92
+ bool: False if the token is an excluded determiner, True otherwise.
93
+ """
94
+ if token.dep_ == "det" and token.text.lower() in EXCLUDED_DETERMINERS:
95
+ return False
96
+ return True
97
+
98
+
99
+ def filter_cc(token: spacy.tokens.Token) -> bool:
100
+ """
101
+ Check if a token is an excluded coordinating conjunction.
102
+
103
+ (Sirts et al., 2017) exclude cc dependencies from the proposition list in their DEPID algorithm.
104
+
105
+ It was added by Sirts et al. to achieve better performance on DementiaBank interviews.
106
+
107
+ It returns False if the token is a coordinating conjunction ('cc'),
108
+ and True otherwise.
109
+
110
+ Args:
111
+ token (spacy.tokens.Token): A spaCy token to analyze.
112
+
113
+ Returns:
114
+ bool: False if the token is a coordinating conjunction, True otherwise.
115
+ """
116
+ if token.dep_ == "cc":
117
+ return False
118
+ return True
119
+
120
+
121
+ def filter_excluded_nsubjs(token: spacy.tokens.Token) -> bool:
122
+ """
123
+ Check if a token is an excluded nominal subject.
124
+
125
+ This function is a token filter used by (Sirts et al., 2017 in their DEPID algorithm.
126
+ It returns False if the token is a nominal subject ('nsubj') and is in the list of excluded subjects,
127
+ and True otherwise.
128
+
129
+ Args:
130
+ token (spacy.tokens.Token): A spaCy token to analyze.
131
+
132
+ Returns:
133
+ bool: False if the token is an excluded nominal subject, True otherwise.
134
+ """
135
+ if token.dep_ == "nsubj" and token.text.lower() in EXCLUDED_NSUBJ:
136
+ return False
137
+ return True
138
+
139
+
140
+ SENTENCE_FILTERS = [filter_i_you_subject]
141
+ TOKEN_FILTERS = [filter_excluded_determiners, filter_excluded_nsubjs, filter_cc]
142
+
143
+
144
+ def _get_token_filters(
145
+ use_excluded_determiner_filter: bool = True,
146
+ use_excluded_nsubj_filter: bool = True,
147
+ use_excluded_cc_filter: bool = False,
148
+ custom_token_filters: Optional[List[Callable[[spacy.tokens.Token], bool]]] = None,
149
+ ) -> List[Callable[[spacy.tokens.Token], bool]]:
150
+ token_filters = []
151
+ if use_excluded_determiner_filter:
152
+ token_filters.append(filter_excluded_determiners)
153
+ if use_excluded_nsubj_filter:
154
+ token_filters.append(filter_excluded_nsubjs)
155
+ if use_excluded_cc_filter:
156
+ token_filters.append(filter_cc)
157
+ if custom_token_filters:
158
+ token_filters.extend(custom_token_filters)
159
+ return token_filters
160
+
161
+
162
+ def _get_sentence_filters(
163
+ use_i_you_subject_filter: bool = False,
164
+ custom_sentence_filters: Optional[List[Callable[[spacy.tokens.Span], bool]]] = None,
165
+ ) -> List[Callable[[spacy.tokens.Span], bool]]:
166
+ sentence_filters = []
167
+ if use_i_you_subject_filter:
168
+ sentence_filters.append(filter_i_you_subject)
169
+ if custom_sentence_filters:
170
+ sentence_filters.extend(custom_sentence_filters)
171
+
172
+ return sentence_filters
173
+
174
+
175
+ def _filter_sentences(
176
+ doc: spacy.tokens.Doc,
177
+ sentence_filters: List[Callable[[spacy.tokens.Span], bool]],
178
+ nlp: spacy.language.Language,
179
+ ) -> spacy.tokens.Doc:
180
+ if sentence_filters:
181
+ filtered_sents = [
182
+ sent
183
+ for sent in doc.sents
184
+ if all(filter_func(sent) for filter_func in sentence_filters)
185
+ ]
186
+ doc = spacy.tokens.Doc(
187
+ doc.vocab, words=[token.text for sent in filtered_sents for token in sent]
188
+ )
189
+ doc = nlp(doc)
190
+
191
+ return doc
192
+
193
+
194
+ def depid(
195
+ text: str,
196
+ is_depid_r: bool = False,
197
+ use_excluded_determiner_filter: bool = True,
198
+ use_excluded_nsubj_filter: bool = True,
199
+ use_excluded_cc_filter: bool = False,
200
+ use_i_you_subject_filter: bool = False,
201
+ custom_sentence_filters: Optional[List[Callable[[spacy.tokens.Span], bool]]] = None,
202
+ custom_token_filters: Optional[List[Callable[[spacy.tokens.Token], bool]]] = None,
203
+ ) -> (
204
+ Tuple[float, int, List[Tuple[str, str, str]]]
205
+ | Tuple[float, int, Set[Tuple[str, str, str]]]
206
+ ):
207
+ """
208
+ Calculate the Dependency-based Idea Density (DEPID) for a given text.
209
+
210
+ This function implements the DEPID (Dependency-based Propositional Idea Density) algorithm
211
+ as described by (Sirts et al., 2017)
212
+
213
+ It processes the input text, applies various filters, and calculates the
214
+ proposition density based on dependency relations.
215
+
216
+ Args:
217
+ text (str): The input text to analyze.
218
+ is_depid_r (bool): If True, returns unique dependencies as a set. Otherwise, returns all dependencies as a list.
219
+ Default is False.
220
+ use_excluded_determiner_filter (bool): If True, applies the excluded determiner filter.
221
+ Default is True. Condition used by default by Sirts et al.
222
+ use_excluded_nsubj_filter (bool): If True, applies the excluded nominal subject filter.
223
+ Default is True. Condition used by default by Sirts et al.
224
+ use_excluded_cc_filter (bool): If True, applies the excluded coordinating conjunction filter.
225
+ Default is False. Sirts et al. added this condition to achieve better performance
226
+ on DementiaBank interviews.
227
+ use_i_you_subject_filter (bool): If True, applies the 'I' and 'you' subject filter.
228
+ Default is False. Sirts et al. added this condition to achieve better performance
229
+ on DementiaBank interviews.
230
+ custom_sentence_filters (Optional[List[Callable[[spacy.tokens.Span], bool]]]): Custom sentence-level filters to apply.
231
+ Default is None.
232
+ custom_token_filters (Optional[List[Callable[[spacy.tokens.Token], bool]]]): Custom token-level filters to apply.
233
+ Default is None.
234
+
235
+ Returns:
236
+ Tuple[float, int, List[Tuple[str, str, str]]] | Tuple[float, int, Set[Tuple[str, str, str]]]:
237
+ A tuple containing:
238
+ - The calculated DEPID (proposition density)
239
+ - The total word count
240
+ - A list or set of tuples, each representing a dependency (token, dependency type, head)
241
+
242
+ Note:
243
+ The function applies various filters to exclude certain types of words and
244
+ sentences based on the DEPID algorithm specifications. It then calculates
245
+ the density as the ratio of identified propositions to the total word count.
246
+ """
247
+
248
+ nlp = get_nlp()
249
+ doc = nlp(text)
250
+
251
+ word_count = len(
252
+ [token for token in doc if not token.is_punct and not token.is_space]
253
+ )
254
+
255
+ sentence_filters = _get_sentence_filters(
256
+ use_i_you_subject_filter=use_i_you_subject_filter,
257
+ custom_sentence_filters=custom_sentence_filters,
258
+ )
259
+ token_filters = _get_token_filters(
260
+ use_excluded_determiner_filter=use_excluded_determiner_filter,
261
+ use_excluded_nsubj_filter=use_excluded_nsubj_filter,
262
+ use_excluded_cc_filter=use_excluded_cc_filter,
263
+ custom_token_filters=custom_token_filters,
264
+ )
265
+
266
+ doc = _filter_sentences(doc, sentence_filters, nlp)
267
+ dependencies = _get_final_dependencies(is_depid_r, doc, token_filters)
268
+ density = (len(dependencies) / word_count) if word_count > 0 else 0.0
269
+
270
+ return density, word_count, dependencies
271
+
272
+
273
+ def _get_final_dependencies(is_depid_r, doc, token_filters):
274
+ if is_depid_r:
275
+ dependencies = set()
276
+ else:
277
+ dependencies = []
278
+
279
+ for token in doc:
280
+ if not token.dep_ in PROPOSITION_DEPENDENCIES:
281
+ continue
282
+ if token_filters and any(
283
+ not filter_func(token) for filter_func in token_filters
284
+ ):
285
+ continue
286
+ if is_depid_r:
287
+ dependencies.add((token.text, token.dep_, token.head.text))
288
+ else:
289
+ dependencies.append((token.text, token.dep_, token.head.text))
290
+
291
+ return dependencies
@@ -0,0 +1,64 @@
1
+ import logging
2
+ from typing import Tuple, Optional
3
+ from ideadensity.idea_density_rater_rules import apply_idea_counting_rules
4
+ from ideadensity.tagger import tag_text
5
+ from ideadensity.word_item import WordList
6
+
7
+ # Create a logger for this module
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def cpidr(
12
+ text: str, speech_mode: bool = False
13
+ ) -> Tuple[int, int, float, Optional[WordList]]:
14
+ return rate_text(text, speech_mode)
15
+
16
+
17
+ def rate_text(
18
+ text: str, speech_mode: bool = False
19
+ ) -> Tuple[int, int, float, Optional[WordList]]:
20
+ """
21
+ Rate the idea density of the given text.
22
+
23
+ Args:
24
+ text (str): The input text to analyze.
25
+ speech_mode (bool): Whether to use speech mode for idea counting rules.
26
+
27
+ Returns:
28
+ Tuple[int, int, float, WordList]: A tuple containing:
29
+ - word_count: Total number of words.
30
+ - proposition_count: Number of propositions.
31
+ - density: Idea density (propositions / words).
32
+ - word_list: Processed WordList object.
33
+ """
34
+ if text is None:
35
+ return 0, 0, 0.0, WordList([])
36
+
37
+ try:
38
+ tagged_text = tag_text(text)
39
+ word_list = WordList(tagged_text)
40
+ apply_idea_counting_rules(word_list.items, speech_mode)
41
+
42
+ word_count, proposition_count = count_words_and_propositions(word_list)
43
+ density = proposition_count / word_count if word_count > 0 else 0.0
44
+
45
+ return word_count, proposition_count, density, word_list
46
+ except Exception as e:
47
+ logger.exception("An error occurred while processing the text")
48
+ return 0, 0, 0.0, None
49
+
50
+
51
+ def count_words_and_propositions(word_list: WordList) -> Tuple[int, int]:
52
+ """
53
+ Count the number of words and propositions in the given WordList.
54
+
55
+ Args:
56
+ word_list (WordList): The processed WordList object.
57
+
58
+ Returns:
59
+ Tuple[int, int]: A tuple containing the word count and proposition count.
60
+ """
61
+ word_count = sum(1 for word in word_list.items if word.is_word)
62
+ proposition_count = sum(1 for word in word_list.items if word.is_proposition)
63
+
64
+ return word_count, proposition_count