001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.text.nlp.sentiment.type; 031 032import gnu.trove.map.hash.TObjectIntHashMap; 033import gnu.trove.procedure.TObjectIntProcedure; 034 035import java.util.HashMap; 036import java.util.Map; 037 038import org.openimaj.text.nlp.sentiment.model.wordlist.util.TFF; 039import org.openimaj.text.nlp.sentiment.model.wordlist.util.TFF.Clue; 040import org.openimaj.text.nlp.sentiment.model.wordlist.util.TFF.Polarity; 041 042/** 043 * A Discrete count sentiment is one which a set of arbitrary sentiments are given a count in a given phrase . 044 * 045 * The sentiments hold values for counts of words considered to be one of the N sentiments provided and the total number of words the sentiment was 046 * decided against is also provided. 047 * 048 * An assumption is made that a single term can only have a single sentiment, therefore 049 * sum(sentiment_count) < total 050 * though perhaps not equal as some terms may be none of the N sentiments (stop words etc.) 051 * 052 * @author Sina Samangooei (ss@ecs.soton.ac.uk) 053 * 054 */ 055public class TFFCountSentiment implements Sentiment, BipolarSentimentProvider, WeightedBipolarSentimentProvider, DiscreteCountBipolarSentimentProvider{ 056 057 private static final class BipolarTFFPolarityIterator implements TObjectIntProcedure<TFF.Polarity> { 058 public int negative = 0; 059 public int neutral = 0; 060 public int positive = 0; 061 062 @Override 063 public boolean execute(Polarity a, int b) { 064 if(a.equals(Polarity.positive) || a.equals(Polarity.strongpos) || a.equals(Polarity.weakpos)){ 065 positive+=b; 066 } 067 else if(a.equals(Polarity.both)){ 068 positive+=b; 069 negative+=b; 070 } 071 else if(a.equals(Polarity.neutral)){ 072 neutral+=b; 073 } 074 else{ 075 negative+=b; 076 } 077 return true; 078 } 079 } 080 private TObjectIntHashMap<TFF.Polarity> sentiments; 081 private int total; 082 083 /** 084 * all weights set to 0 085 */ 086 public TFFCountSentiment() { 087 sentiments = new TObjectIntHashMap<TFF.Polarity>(); 088 for (Polarity polarity : TFF.Polarity.values()) { 089 sentiments.put(polarity, 0); 090 } 091 } 092 093 /** 094 * @param total instnatiate with a total number of words 095 */ 096 public TFFCountSentiment(int total) { 097 this(); 098 this.total = total; 099 } 100 101 /** 102 * @param entry 103 * @param increment 104 */ 105 public void incrementClue(TFF.Clue entry, int increment){ 106 this.sentiments.adjustOrPutValue(entry.polarity, increment, increment); 107 } 108 109 @Override 110 public BipolarSentiment bipolar() { 111 BipolarTFFPolarityIterator instance = new BipolarTFFPolarityIterator(); 112 this.sentiments.forEachEntry(instance); 113 if(instance.positive > instance.negative){ 114 if(instance.positive > instance.neutral){ 115 return BipolarSentiment.POSITIVE; 116 } 117 else{ 118 return BipolarSentiment.NEUTRAL; 119 } 120 } 121 else{ 122 if(instance.negative > instance.neutral){ 123 return BipolarSentiment.NEGATIVE; 124 } 125 else{ 126 return BipolarSentiment.NEUTRAL; 127 } 128 } 129 } 130 131 @Override 132 public BipolarSentiment bipolar(double deltaThresh) { 133 BipolarTFFPolarityIterator instance = new BipolarTFFPolarityIterator(); 134 this.sentiments.forEachEntry(instance); 135 if(instance.positive > instance.negative * deltaThresh){ 136 if(instance.positive > instance.neutral * deltaThresh){ 137 return BipolarSentiment.POSITIVE; 138 } 139 else if(instance.neutral > instance.positive * deltaThresh){ 140 return BipolarSentiment.NEUTRAL; 141 } 142 } 143 else{ 144 if(instance.negative > instance.neutral * deltaThresh){ 145 return BipolarSentiment.NEGATIVE; 146 } 147 else if(instance.neutral > instance.negative * deltaThresh){ 148 return BipolarSentiment.NEUTRAL; 149 } 150 } 151 return null; 152 } 153 154 @Override 155 public WeightedBipolarSentiment weightedBipolar() { 156 BipolarTFFPolarityIterator instance = new BipolarTFFPolarityIterator(); 157 this.sentiments.forEachEntry(instance); 158 return new WeightedBipolarSentiment( 159 instance.positive / (double)this.total, 160 instance.negative / (double)this.total, 161 instance.neutral / (double)this.total 162 ); 163 } 164 @Override 165 public DiscreteCountBipolarSentiment countBipolarSentiment() { 166 BipolarTFFPolarityIterator instance = new BipolarTFFPolarityIterator(); 167 this.sentiments.forEachEntry(instance); 168 try { 169 return new DiscreteCountBipolarSentiment( 170 instance.positive , 171 instance.negative , 172 instance.neutral , 173 this.total 174 ); 175 } catch (InvalidSentimentException e) { 176 return null; // should never happen 177 } 178 } 179 @Override 180 public Map<String, ?> asMap() { 181 HashMap<String, Integer> ret = new HashMap<String,Integer>(); 182 for (Polarity polarity: TFF.Polarity.values()) { 183 ret.put(polarity.name(), this.sentiments.get(polarity)); 184 } 185 ret.put("total", total); 186 return ret; 187 } 188 189 @Override 190 public void fromMap(Map<String, ?> map) throws UnrecognisedMapException { 191 for (Polarity polarity : TFF.Polarity.values()) { 192 Object value = map.get(polarity.name()); 193 if(value == null) throw new UnrecognisedMapException("Could not find polarity: " + polarity); 194 this.sentiments.put(polarity, (Integer)value); 195 } 196 if(!map.containsKey("total")) throw new UnrecognisedMapException("Could not find total"); 197 this.total = (Integer) map.get("total"); 198 } 199 200 @Override 201 public boolean equals(Object obj) { 202 if(!(obj instanceof TFFCountSentiment)) return false; 203 TFFCountSentiment that = (TFFCountSentiment) obj; 204 if(this.total != that.total) return false; 205 for (Object clue : this.sentiments.keys()) { 206 if(this.sentiments.get(clue) != that.sentiments.get(clue))return false; 207 } 208 return true; 209 } 210 211 /** 212 * @param clue 213 * @return given a clue construct a {@link TFFCountSentiment} with 1 word and 1 clue and call {@link TFFCountSentiment#bipolar()} 214 */ 215 public static BipolarSentiment bipolar(Clue clue) { 216 TFFCountSentiment count = new TFFCountSentiment(1); 217 count.incrementClue(clue, 1); 218 return count.bipolar(); 219 } 220 221 222 223}