sad_panda 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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