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.
- ideadensity/__init__.py +4 -0
- ideadensity/depid.py +291 -0
- ideadensity/idea_density_rater.py +64 -0
- ideadensity/idea_density_rater_rules.py +725 -0
- ideadensity/tagger.py +48 -0
- ideadensity/utils/constants.py +248 -0
- ideadensity/utils/word_search_utils.py +91 -0
- ideadensity/word_item.py +59 -0
- ideadensity-0.2.0.dist-info/LICENSE +339 -0
- ideadensity-0.2.0.dist-info/METADATA +224 -0
- ideadensity-0.2.0.dist-info/RECORD +12 -0
- ideadensity-0.2.0.dist-info/WHEEL +4 -0
ideadensity/__init__.py
ADDED
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
|