File Coverage

blib/lib/Lingua/EN/Opinion.pm
Criterion Covered Total %
statement 119 133 89.4
branch 23 30 76.6
condition 1 2 50.0
subroutine 24 29 82.7
pod 13 13 100.0
total 180 207 86.9


line stmt bran cond sub pod time code
1             package Lingua::EN::Opinion;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Measure the emotional sentiment of text
5              
6             our $VERSION = '0.1702';
7              
8 1     1   1396 use Moo;
  1         12039  
  1         6  
9              
10 1     1   2389 use Lingua::EN::Opinion::Positive;
  1         4  
  1         37  
11 1     1   931 use Lingua::EN::Opinion::Negative;
  1         3  
  1         35  
12 1     1   8597 use Lingua::EN::Opinion::Emotion;
  1         4  
  1         62  
13              
14 1     1   8 use Carp qw( croak );
  1         2  
  1         71  
15 1     1   630 use File::Slurper qw( read_text );
  1         14026  
  1         67  
16 1     1   504 use Lingua::EN::Sentence qw( get_sentences );
  1         14674  
  1         83  
17 1     1   480 use Statistics::Lite qw( mean );
  1         1583  
  1         66  
18 1     1   8 use Try::Tiny qw( try catch );
  1         2  
  1         60  
19              
20 1     1   6 use strictures 2;
  1         10  
  1         45  
21 1     1   251 use namespace::clean;
  1         3  
  1         10  
22              
23              
24             has file => (
25             is => 'ro',
26             isa => sub { die "File $_[0] does not exist" unless -e $_[0] },
27             );
28              
29              
30             has text => (
31             is => 'ro',
32             );
33              
34              
35             has stem => (
36             is => 'ro',
37             default => sub { 0 },
38             );
39              
40              
41             has stemmer => (
42             is => 'ro',
43             lazy => 1,
44             builder => 1,
45             init_arg => undef,
46             );
47              
48             sub _build_stemmer {
49             try {
50 0     0   0 require WordNet::QueryData;
51 0         0 require WordNet::stem;
52              
53 0         0 my $wn = WordNet::QueryData->new();
54 0         0 my $stemmer = WordNet::stem->new($wn);
55              
56 0         0 return $stemmer;
57             }
58             catch {
59 0     0   0 croak 'The WordNet::QueryData and WordNet::stem modules must be installed and working to enable stemming support';
60 0     0   0 };
61             }
62              
63              
64             has sentences => (
65             is => 'rw',
66             init_arg => undef,
67             default => sub { [] },
68             );
69              
70              
71             has scores => (
72             is => 'rw',
73             init_arg => undef,
74             default => sub { [] },
75             );
76              
77              
78             has nrc_scores => (
79             is => 'rw',
80             init_arg => undef,
81             default => sub { [] },
82             );
83              
84              
85             has positive => (
86             is => 'ro',
87             init_arg => undef,
88             default => sub { Lingua::EN::Opinion::Positive->new },
89             );
90              
91              
92             has negative => (
93             is => 'ro',
94             init_arg => undef,
95             default => sub { Lingua::EN::Opinion::Negative->new },
96             );
97              
98              
99             has emotion => (
100             is => 'ro',
101             init_arg => undef,
102             default => sub { Lingua::EN::Opinion::Emotion->new },
103             );
104              
105              
106             has familiarity => (
107             is => 'rw',
108             init_arg => undef,
109             default => sub { { known => 0, unknown => 0 } },
110             );
111              
112              
113             sub analyze {
114 2     2 1 43897 my ($self) = @_;
115              
116 2         6 my @scores;
117 2         7 my ( $known, $unknown ) = ( 0, 0 );
118              
119 2         11 for my $sentence ( $self->_get_sentences ) {
120 22         27 my $score = 0;
121 22         54 ( $score, $known, $unknown ) = $self->get_sentence( $sentence, $known, $unknown );
122 22         55 push @scores, $score;
123             }
124              
125 2         23 $self->familiarity( { known => $known, unknown => $unknown } );
126              
127 2         13 $self->scores( \@scores );
128             }
129              
130              
131 1     1 1 6 sub averaged_score { shift->averaged_scores(@_) }
132              
133             sub averaged_scores {
134 1     1 1 4 my ( $self, $bins ) = @_;
135              
136 1   50     4 $bins ||= 10;
137              
138 1         2 my @scores = map { $_ } @{ $self->scores };
  11         18  
  1         5  
139              
140 1         2 my @averaged;
141              
142 1         7 while ( my @n = splice @scores, 0, $bins ) {
143 6         150 push @averaged, mean(@n);
144             }
145              
146 1         14 return \@averaged;
147             }
148              
149              
150 0     0 1 0 sub nrc_sentiment { shift->nrc_analyze(@_) };
151              
152             sub nrc_analyze {
153 1     1 1 583 my ($self) = @_;
154              
155 1         5 my $null_state = { map { $_ => 0 } qw/ anger anticipation disgust fear joy negative positive sadness surprise trust / };
  10         49  
156              
157 1         3 my @scores;
158 1         20 my ( $known, $unknown ) = ( 0, 0 );
159              
160 1         4 for my $sentence ( $self->_get_sentences ) {
161 11         16 my $score = {};
162              
163 11         24 ( $score, $known, $unknown ) = $self->nrc_get_sentence( $sentence, $known, $unknown );
164              
165 11 50       27 $score = $null_state
166             unless $score;
167              
168 11         22 push @scores, $score;
169             }
170              
171 1         26 $self->familiarity( { known => $known, unknown => $unknown } );
172              
173 1         9 $self->nrc_scores( \@scores );
174             }
175              
176              
177             sub get_word {
178 199     199 1 1880 my ( $self, $word ) = @_;
179              
180 199 50       386 $word = $self->_stemword($word)
181             if $self->stem;
182              
183             return exists $self->positive->wordlist->{$word} ? 1
184 199 100       793 : exists $self->negative->wordlist->{$word} ? -1
    100          
185             : undef;
186             }
187              
188              
189             sub set_word {
190 1     1 1 3 my ( $self, $word, $value ) = @_;
191              
192 1 50       6 if ($value > 0) {
193 1         6 $self->positive->wordlist->{$word} = $value;
194             }
195             else {
196 0         0 $self->negative->wordlist->{$word} = $value;
197             }
198             }
199              
200              
201             sub nrc_get_word {
202 102     102 1 3339 my ( $self, $word ) = @_;
203              
204 102 50       209 $word = $self->_stemword($word)
205             if $self->stem;
206              
207             return exists $self->emotion->wordlist->{$word}
208 102 100       331 ? $self->emotion->wordlist->{$word}
209             : undef;
210             }
211              
212              
213             sub nrc_set_word {
214 1     1 1 502 my ( $self, $word, $value ) = @_;
215              
216 1         2 my %emotion;
217              
218 1         5 for my $emotion (qw(
219             anger
220             anticipation
221             disgust
222             fear
223             joy
224             negative
225             positive
226             sadness
227             surprise
228             trust
229             )) {
230 10 50       19 if (exists $value->{$emotion}) {
231 10         19 $emotion{$emotion} = $value->{$emotion};
232             }
233             else {
234 0         0 $emotion{$emotion} = 0;
235             }
236             }
237              
238 1         8 $self->emotion->wordlist->{$word} = \%emotion;
239             }
240              
241              
242             sub get_sentence {
243 24     24 1 677 my ( $self, $sentence, $known, $unknown ) = @_;
244              
245 24         50 my @words = $self->tokenize($sentence);
246              
247 24         42 my $score = 0;
248              
249 24         40 for my $word ( @words ) {
250 194         346 my $value = $self->get_word($word);
251 194 100       313 if ( $value ) {
252 25         40 $known++;
253             }
254             else {
255 169         215 $unknown++;
256             }
257              
258 194 100       375 $score += $value
259             if defined $value;
260             }
261              
262 24         65 return $score, $known, $unknown;
263             }
264              
265              
266             sub nrc_get_sentence {
267 12     12 1 1623 my ( $self, $sentence, $known, $unknown ) = @_;
268              
269 12         24 my @words = $self->tokenize($sentence);
270              
271 12         22 my $score = {};
272              
273 12         24 for my $word ( @words ) {
274 97         154 my $value = $self->nrc_get_word($word);
275              
276 97 100       156 if ( $value ) {
277 30         40 $known++;
278              
279 30         162 for my $key ( keys %$value ) {
280 300         494 $score->{$key} += $value->{$key};
281             }
282             }
283             else {
284 67         95 $unknown++;
285             }
286             }
287              
288 12         36 return $score, $known, $unknown;
289             }
290              
291              
292             sub ratio {
293 7     7 1 11257 my ( $self, $flag ) = @_;
294              
295 7 100       34 my $numerator = $flag ? $self->familiarity->{unknown} : $self->familiarity->{known};
296              
297 7         35 my $ratio = $numerator / ( $self->familiarity->{known} + $self->familiarity->{unknown} );
298              
299 6         78 return $ratio;
300             }
301              
302              
303             sub tokenize {
304 36     36 1 67 my ( $self, $sentence ) = @_;
305 36         152 $sentence =~ s/[[:punct:]]//g; # Drop punctuation
306 36         79 $sentence =~ s/\d//g; # Drop digits
307 36         198 my @words = grep { $_ } map { lc $_ } split /\s+/, $sentence;
  291         493  
  291         493  
308 36         151 return @words;
309             }
310              
311             sub _stemword {
312 0     0   0 my ( $self, $word ) = @_;
313              
314 0         0 my @stems = $self->stemmer->stemWord($word);
315              
316 0 0       0 $word = [ sort @stems ]->[0]
317             if @stems;
318              
319 0         0 return $word;
320             }
321              
322             sub _get_sentences {
323 3     3   9 my ($self) = @_;
324              
325 3 100       5 unless ( @{ $self->sentences } ) {
  3         22  
326 2 100       21 my $contents = $self->file ? read_text( $self->file ) : $self->text;
327 2         271 $self->sentences( get_sentences($contents) );
328             }
329              
330 3         11518 return map { $_ } @{ $self->sentences };
  33         63  
  3         16  
331             }
332              
333             1;
334              
335             __END__