sad_panda 1.0.1 → 1.1.0

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,43 @@
1
+ module SadPanda
2
+ # Holds the hardcoded data
3
+ module Bank
4
+ # common english words
5
+ STOPWORDS = %w{i a a's able about above according accordingly across actually after
6
+ afterwards again against ain't all allow allows almost alone along already also although
7
+ always am among amongst an and another any anybody anyhow anyone anything anyway anyways
8
+ anywhere apart appear appreciate appropriate are aren't around as aside ask asking associated
9
+ at available away awfully b be became because become becomes becoming been before beforehand
10
+ behind being believe below beside besides best better between beyond both brief but by c c'mon
11
+ c's came can can't cannot cant cause causes certain certainly changes clearly co com come comes
12
+ concerning consequently consider considering contain containing contains corresponding could couldn't
13
+ course currently d definitely described despite did didn't different do does doesn't doing don't
14
+ done down downwards during e each edu eg eight either else elsewhere enough entirely especially et
15
+ etc even ever every everybody everyone everything everywhere ex exactly example except f far few
16
+ fifth first five followed following follows for former formerly forth four from further furthermore
17
+ g get gets getting given gives go goes going gone got gotten greetings h had hadn't happens hardly
18
+ has hasn't have haven't having he he's hello help hence her here here's hereafter hereby herein
19
+ hereupon hers herself hi him himself his hither hopefully how howbeit however i i'd i'll i'm
20
+ i've ie if ignored immediate in inasmuch inc indeed indicate indicated indicates inner insofar
21
+ instead into inward is isn't it it'd it'll it's its itself j just k keep keeps kept know knows known
22
+ l last lately later latter latterly least less lest let let's like liked likely little look looking
23
+ looks ltd m mainly many may maybe me mean meanwhile merely might more moreover most mostly much must
24
+ my myself n name namely nd near nearly necessary need needs neither never nevertheless new next nine
25
+ no nobody non none noone nor normally not nothing novel now nowhere o obviously of off often oh ok
26
+ okay old on once one ones only onto or other others otherwise ought our ours ourselves out outside
27
+ over overall own p particular particularly per perhaps placed please plus possible presumably probably
28
+ provides q que quite qv r rather rd re really reasonably regarding regardless regards relatively
29
+ respectively right s said same saw say saying says second secondly see seeing seem seemed seeming
30
+ seems seen self selves sensible sent serious seriously seven several shall she should shouldn't since
31
+ six so some somebody somehow someone something sometime sometimes somewhat somewhere soon sorry specified
32
+ specify specifying still sub such sup sure t t's take taken tell tends th than thank thanks thanx that
33
+ that's thats the their theirs them themselves then thence there there's thereafter thereby therefore therein
34
+ theres thereupon these they they'd they'll they're they've think third this thorough thoroughly those
35
+ though three through throughout thru thus to together too took toward towards tried tries truly try
36
+ trying twice two u un under unfortunately unless unlikely until unto up upon us use used useful uses using
37
+ usually uucp v value various very via viz vs w want wants was wasn't way we we'd we'll we're we've welcome
38
+ well went were weren't what what's whatever when whence whenever where where's whereafter whereas whereby
39
+ wherein whereupon wherever whether which while whither who who's whoever whole whom whose why will willing wish
40
+ with within without won't wonder would would wouldn't x y yes yet yo you you'd you'll you're you've your yours
41
+ yourself yourselves z zero}.freeze
42
+ end
43
+ end
@@ -0,0 +1,78 @@
1
+ require 'sad_panda/helpers'
2
+
3
+ module SadPanda
4
+ # Emotion determining logic in here
5
+ class Emotion
6
+ include Helpers
7
+
8
+ attr_reader :words, :scores
9
+
10
+ def initialize(text)
11
+ @words = words_in(text)
12
+ @scores = { anger: 0, disgust: 0, joy: 0,
13
+ surprise: 0, fear: 0, sadness: 0,
14
+ ambiguous: 0 }
15
+ end
16
+
17
+ # Main method that initiates scoring emotions
18
+ def call
19
+ words = stems_for(remove_stopwords_in(@words))
20
+ score_words(frequencies_for(words))
21
+
22
+ scores.key(scores.values.max)
23
+ end
24
+
25
+ # MethodMissing to implement metods that
26
+ # are the names of each emotion that will returen
27
+ # the score of that specific emotion for the text
28
+ def method_missing(emotion)
29
+ return scores[emotion] || 0 if scores.keys.include? emotion
30
+
31
+ raise NoMethodError, "#{emotion} is not defined"
32
+ end
33
+
34
+ private
35
+
36
+ # Last part of the scoring process
37
+ # If all scores are empty ambiguous is scored as 1
38
+ def ambiguous_score
39
+ unq_scores = scores.values.uniq
40
+ scores[:ambiguous] = 1 if unq_scores.length == 1 && unq_scores.first.zero?
41
+ end
42
+
43
+ # Increments the score of an emotion if the word exist
44
+ # in that emotion bank
45
+ def score_emotions(emotion, term, frequency)
46
+ return unless SadPanda::Bank::EMOTIONS[emotion].include?(term)
47
+
48
+ scores[emotion] += frequency
49
+ end
50
+
51
+ # Iterates all emotions for word in text
52
+ def set_emotions(word, frequency)
53
+ SadPanda::Bank::EMOTIONS.keys.each do |emotion|
54
+ score_emotions(emotion, word, frequency)
55
+ end
56
+ end
57
+
58
+ def score_emoticons
59
+ happy = happy_emoticon?(words)
60
+ sad = sad_emoticon?(words)
61
+
62
+ scores[:ambiguous] += 1 if happy && sad
63
+ scores[:joy] += 1 if happy
64
+ scores[:sadness] += 1 if sad
65
+ end
66
+
67
+ # Logic to score all unique words in the text
68
+ def score_words(word_frequencies)
69
+ word_frequencies.each do |word, frequency|
70
+ set_emotions(word, frequency)
71
+ end
72
+
73
+ score_emoticons
74
+
75
+ ambiguous_score
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,62 @@
1
+ module SadPanda
2
+ # Helper methods for SadPanda
3
+ module Helpers
4
+ def sad_emojies
5
+ [':(', ':-(', ':[', ':-[']
6
+ end
7
+
8
+ def happy_emojies
9
+ [':)', ':-)', ':]', ':-]']
10
+ end
11
+
12
+ # Returns a Hash of frequencies of each uniq word in the text
13
+ def frequencies_for(words)
14
+ word_frequencies = {}
15
+ words.each { |word| word_frequencies[word] = words.count(word) }
16
+ word_frequencies
17
+ end
18
+
19
+ # Converts all the words to its stem form
20
+ def stems_for(words)
21
+ stemmer = Lingua::Stemmer.new(language: 'en')
22
+ words.map! { |word| stemmer.stem(word) }
23
+ end
24
+
25
+ # Strips the words array of stop words
26
+ def remove_stopwords_in(words)
27
+ words - SadPanda::Bank::STOPWORDS
28
+ end
29
+
30
+ # Captures and returns emojies in the text
31
+ def emojies_in(text)
32
+ (sad_emojies + happy_emojies).map do |emoji|
33
+ text.scan(emoji)
34
+ end.flatten
35
+ end
36
+
37
+ # Removing non ASCII characters from text
38
+ def sanitize(text)
39
+ text.gsub!(/[^a-z ]/i, '')
40
+ text.gsub!(/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/, '')
41
+ text.gsub!(/(?=\w*h)(?=\w*t)(?=\w*t)(?=\w*p)\w*/, '')
42
+ text.gsub!(/\s\s+/, ' ')
43
+
44
+ text.downcase
45
+ end
46
+
47
+ # Removes all the unwated characters from the text
48
+ def words_in(text)
49
+ emojies_in(text) + sanitize(text).split
50
+ end
51
+
52
+ # Checks if words has a happy emoji
53
+ def happy_emoticon?(words)
54
+ (happy_emojies & words).any?
55
+ end
56
+
57
+ # Checks if words has a sad emoji
58
+ def sad_emoticon?(words)
59
+ (sad_emojies & words).any?
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,46 @@
1
+ require 'sad_panda/helpers'
2
+
3
+ module SadPanda
4
+ # Polarity calculation logic in here
5
+ class Polarity
6
+ include Helpers
7
+
8
+ attr_reader :words, :polarities
9
+
10
+ def initialize(text)
11
+ @words = words_in(text)
12
+ @polarities = []
13
+ end
14
+
15
+ # Main method that initiates calculating polarity
16
+ def call
17
+ words = stems_for(remove_stopwords_in(@words))
18
+
19
+ score_polarities_for(frequencies_for(words))
20
+
21
+ polarities.empty? ? 5.0 : (polarities.inject(0){ |sum, polarity| sum + polarity } / polarities.length)
22
+ end
23
+
24
+ private
25
+
26
+ # Checks if words has happy or sad emoji and adds polarity for it
27
+ def score_emoticon_polarity
28
+ happy = happy_emoticon?(words)
29
+ sad = sad_emoticon?(words)
30
+
31
+ polarities << 5.0 if happy && sad
32
+ polarities << 8.0 if happy
33
+ polarities << 2.0 if sad
34
+ end
35
+
36
+ # Appends polarities of words to array polarities
37
+ def score_polarities_for(word_frequencies)
38
+ word_frequencies.each do |word, frequency|
39
+ polarity = SadPanda::Bank::POLARITIES[word.to_sym]
40
+ polarities << (polarity * frequency.to_f) if polarity
41
+ end
42
+
43
+ score_emoticon_polarity
44
+ end
45
+ end
46
+ end
@@ -1,3 +1,3 @@
1
1
  module SadPanda
2
- VERSION = "1.0.1"
2
+ VERSION = '1.1.0'.freeze
3
3
  end
@@ -1,26 +1,27 @@
1
1
  # coding: utf-8
2
+
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'sad_panda/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "sad_panda"
8
+ spec.name = 'sad_panda'
8
9
  spec.version = SadPanda::VERSION
9
- spec.authors = ["Matt Buckley"]
10
- spec.email = ["matt.d.buckley1212@gmail.com"]
10
+ spec.authors = ['Matt Buckley', 'Edwin Rozario']
11
+ spec.email = ['matt.d.buckley1212@gmail.com']
11
12
  spec.description = %q{sad_panda is a gem featuring tools for sentiment analysis of natural language: positivity/negativity and emotion classification.}
12
13
  spec.summary = %q{sad_panda is a gem featuring tools for sentiment analysis of natural language: positivity/negativity and emotion classification.}
13
- spec.homepage = ""
14
- spec.license = "MIT"
14
+ spec.homepage = 'https://github.com/mattThousand/sad_panda'
15
+ spec.license = 'MIT'
15
16
 
16
17
  spec.files = `git ls-files`.split($/)
17
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
20
+ spec.require_paths = ['lib']
20
21
 
21
- spec.add_development_dependency "bundler", "~> 1.3"
22
- spec.add_development_dependency "rake"
23
- spec.add_runtime_dependency "ruby-stemmer"
24
- spec.add_development_dependency "pry"
25
- spec.add_development_dependency "rspec"
22
+ spec.add_development_dependency 'bundler', '~> 1.3'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_runtime_dependency 'ruby-stemmer'
25
+ spec.add_development_dependency 'pry'
26
+ spec.add_development_dependency 'rspec'
26
27
  end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe SadPanda::Bank do
4
+ let(:emotions) { SadPanda::Bank::EMOTIONS }
5
+
6
+ describe 'EMOTIONS constant' do
7
+ it 'returns a hash' do
8
+ expect(emotions).to be_a Hash
9
+ end
10
+
11
+ it 'is not empty' do
12
+ expect(emotions).to_not be_empty
13
+ end
14
+
15
+ it 'has all the emotions as keys' do
16
+ expect(emotions.keys).to eq [:anger, :disgust, :joy, :surprise, :fear, :sadness]
17
+ end
18
+
19
+ context 'constants' do
20
+ it 'returns an Array for SADNESS' do
21
+ expect(SadPanda::Bank::EMOTIONS[:anger]).to be_a Array
22
+ end
23
+
24
+ it 'returns an Array for JOY' do
25
+ expect(SadPanda::Bank::EMOTIONS[:joy]).to be_a Array
26
+ end
27
+
28
+ it 'returns an Array for ANGER' do
29
+ expect(SadPanda::Bank::EMOTIONS[:anger]).to be_a Array
30
+ end
31
+
32
+ it 'returns an Array for DISGUST' do
33
+ expect(SadPanda::Bank::EMOTIONS[:disgust]).to be_a Array
34
+ end
35
+
36
+ it 'returns an Array for FEAR' do
37
+ expect(SadPanda::Bank::EMOTIONS[:fear]).to be_a Array
38
+ end
39
+
40
+ it 'returns an Array for SURPRISE' do
41
+ expect(SadPanda::Bank::EMOTIONS[:surprise]).to be_a Array
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe SadPanda::Bank do
4
+ describe 'when POLARITIES is accessed' do
5
+ it 'returns a hash' do
6
+ expect(SadPanda::Bank::POLARITIES).to be_a Hash
7
+ end
8
+
9
+ it 'is not empty' do
10
+ expect(SadPanda::Bank::POLARITIES).to_not be_empty
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe SadPanda::Bank do
4
+ describe 'when STOPWORDS is accessed' do
5
+ it 'returns an Array' do
6
+ expect(SadPanda::Bank::STOPWORDS).to be_a Array
7
+ end
8
+
9
+ it 'is not empty' do
10
+ expect(SadPanda::Bank::STOPWORDS).to_not be_empty
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,175 @@
1
+ require 'spec_helper'
2
+
3
+ describe SadPanda::Emotion do
4
+ describe 'initialization' do
5
+ let(:object) { SadPanda::Emotion.new('Initialize this text in sad panda') }
6
+
7
+ it 'initializes scores as 0 for all emotions' do
8
+ expect(object.scores).to eq(:anger => 0, :disgust => 0, :joy => 0,
9
+ :surprise => 0, :fear => 0, :sadness => 0,
10
+ :ambiguous => 0)
11
+ end
12
+
13
+ it 'initializes words in an Array' do
14
+ expect(object.words).to eq %w[initialize this text in sad panda]
15
+ end
16
+ end
17
+
18
+ describe '#call' do
19
+ context 'when input contains words with emotions' do
20
+ let(:object) { SadPanda::Emotion.new('my lobster collection makes me happy!') }
21
+
22
+ it 'returns :joy' do
23
+ expect(object.call).to eq :joy
24
+ end
25
+
26
+ it 'scores emotions' do
27
+ object.call
28
+ expect(object.scores).to eq(:anger => 0, :disgust => 0, :joy => 1,
29
+ :surprise => 0, :fear => 0, :sadness => 0,
30
+ :ambiguous => 0)
31
+ end
32
+ end
33
+
34
+ context 'when input contains no words with emotions' do
35
+ let(:object) { SadPanda::Emotion.new(' ') }
36
+
37
+ it 'returns :ambiguous' do
38
+ expect(object.call).to eq :ambiguous
39
+ end
40
+
41
+ it 'does not scores emotions but ambiguous' do
42
+ object.call
43
+ expect(object.scores).to eq(:anger => 0, :disgust => 0, :joy => 0,
44
+ :surprise => 0, :fear => 0, :sadness => 0,
45
+ :ambiguous => 1)
46
+ end
47
+ end
48
+ end
49
+
50
+ describe 'emotions defined in method_missing' do
51
+ let(:object) { SadPanda::Emotion.new('This is a test affright message for anxiously sadness :)') }
52
+
53
+ before do
54
+ object.call
55
+ end
56
+
57
+ it 'returns emotion value for fear' do
58
+ expect(object.fear).to eq 2
59
+ end
60
+
61
+ it 'returns emotion value for anger' do
62
+ expect(object.anger).to eq 0
63
+ end
64
+
65
+ it 'returns emotion value for joy' do
66
+ expect(object.joy).to eq 1
67
+ end
68
+
69
+ it 'returns emotion value for disgust' do
70
+ expect(object.disgust).to eq 0
71
+ end
72
+
73
+ it 'returns emotion value for surprise' do
74
+ expect(object.surprise).to eq 0
75
+ end
76
+
77
+ it 'returns emotion value for sadness' do
78
+ expect(object.sadness).to eq 1
79
+ end
80
+
81
+ it 'returns emotion value for ambiguous' do
82
+ expect(object.ambiguous).to eq 0
83
+ end
84
+
85
+ it 'raises an exception if the method is not an emotion' do
86
+ expect { object.fake_emotion }.to raise_error(NoMethodError)
87
+ end
88
+ end
89
+
90
+ describe '#ambiguous_score' do
91
+ let(:object) { SadPanda::Emotion.new('') }
92
+
93
+ context 'when all emotion scores are 0' do
94
+ it 'adds ambiguous to scores as 1' do
95
+ expect(object.scores[:ambiguous]).to eq 0
96
+
97
+ object.send(:ambiguous_score)
98
+
99
+ expect(object.scores[:ambiguous]).to eq 1
100
+ end
101
+ end
102
+
103
+ context 'when some emotion scores are greater than 0' do
104
+ it 'does not adds ambiguous scores' do
105
+ object.scores[:joy] = 1
106
+
107
+ object.send(:ambiguous_score)
108
+
109
+ expect(object.scores[:ambiguous]).to eq 0
110
+ end
111
+ end
112
+ end
113
+
114
+ describe '#score_emotions' do
115
+ let(:object) { SadPanda::Emotion.new('') }
116
+
117
+ context 'when word dosent not exist in emotion bank' do
118
+ it 'does nothing and returns nil' do
119
+ expect(object.send(:score_emotions, :joy, 'notjoy', 1)).to eq nil
120
+ expect(object.scores[:joy]).to eq 0
121
+ end
122
+ end
123
+
124
+ context 'when word dose exist in emotion bank' do
125
+ it 'updates scores with frequency' do
126
+ expect(object.send(:score_emotions, :joy, 'happy', 1)).to eq 1
127
+ expect(object.scores[:joy]).to eq 1
128
+ end
129
+ end
130
+ end
131
+
132
+ describe '#set_emotions' do
133
+ let(:object) { SadPanda::Emotion.new('') }
134
+
135
+ context 'when word dosent not exist in any emotion bank' do
136
+ it 'does not add any score' do
137
+ object.send(:set_emotions, 'notaword', 1)
138
+ expect(object.scores).to eq(:anger => 0, :disgust => 0,:joy => 0,
139
+ :surprise => 0, :fear => 0, :sadness => 0,
140
+ :ambiguous => 0)
141
+ end
142
+ end
143
+
144
+ context 'when word dose exist in an emotion bank' do
145
+ it 'add frequency to score of the emotion' do
146
+ object.send(:set_emotions, 'happy', 1)
147
+ expect(object.scores).to eq(:anger => 0, :disgust => 0,:joy => 1,
148
+ :surprise => 0, :fear => 0, :sadness => 0,
149
+ :ambiguous => 0)
150
+ end
151
+ end
152
+ end
153
+
154
+ describe '#score_words' do
155
+ let(:object) { SadPanda::Emotion.new('') }
156
+
157
+ context 'when words dosent not exist in any emotion bank' do
158
+ it 'does not add any score but ambiguous' do
159
+ object.send(:score_words, { 'notword' => 1, 'foobar' => 2})
160
+ expect(object.scores).to eq(:anger => 0, :disgust => 0,:joy => 0,
161
+ :surprise => 0, :fear => 0, :sadness => 0,
162
+ :ambiguous => 1)
163
+ end
164
+ end
165
+
166
+ context 'when word dose exist in an emotion bank' do
167
+ it 'add frequency to score of the emotion' do
168
+ object.send(:score_words, { 'happy' => 1, 'sad' => 2})
169
+ expect(object.scores).to eq(:anger => 0, :disgust => 0,:joy => 1,
170
+ :surprise => 0, :fear => 0, :sadness => 2,
171
+ :ambiguous => 0)
172
+ end
173
+ end
174
+ end
175
+ end