diff --git a/Ben Carson.jpg b/Ben Carson.jpg new file mode 100644 index 0000000..93bce3e Binary files /dev/null and b/Ben Carson.jpg differ diff --git a/Bernie Sanders.jpg b/Bernie Sanders.jpg new file mode 100644 index 0000000..14bd8a8 Binary files /dev/null and b/Bernie Sanders.jpg differ diff --git a/Donald Trump.jpg b/Donald Trump.jpg new file mode 100644 index 0000000..fce7eec Binary files /dev/null and b/Donald Trump.jpg differ diff --git a/Hillary Clinton.jpg b/Hillary Clinton.jpg new file mode 100644 index 0000000..75d2a9b Binary files /dev/null and b/Hillary Clinton.jpg differ diff --git a/Jeb Bush.jpg b/Jeb Bush.jpg new file mode 100644 index 0000000..4a0c56d Binary files /dev/null and b/Jeb Bush.jpg differ diff --git a/John Kasich.jpg b/John Kasich.jpg new file mode 100644 index 0000000..206214e Binary files /dev/null and b/John Kasich.jpg differ diff --git a/MP4Write-Up.pdf b/MP4Write-Up.pdf new file mode 100644 index 0000000..d86ed9e Binary files /dev/null and b/MP4Write-Up.pdf differ diff --git a/Marco Rubio.jpg b/Marco Rubio.jpg new file mode 100644 index 0000000..05b0e9f Binary files /dev/null and b/Marco Rubio.jpg differ diff --git a/Martin O'Malley.jpg b/Martin O'Malley.jpg new file mode 100644 index 0000000..4249cba Binary files /dev/null and b/Martin O'Malley.jpg differ diff --git a/Mike Pence.jpg b/Mike Pence.jpg new file mode 100644 index 0000000..ea2c047 Binary files /dev/null and b/Mike Pence.jpg differ diff --git a/README.md b/README.md index 61ec120..7df96fb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,30 @@ # InteractiveProgramming + This is the base repo for the interactive programming project for Software Design, Spring 2016 at Olin College. + +Here is what you will need to install: + +Twitter-mining: +``` +$ sudo pip3 install python-twitter +``` +Pygame: +``` +$ sudo apt-get build-dep python-pygame +$ sudo apt-get install python-dev +$ sudo pip3 install pygame +``` +Word-processing: +``` +$ sudo pip install -U nltk +``` + +First, download all the image files. + +Then, download either the pickle files or the twitter_textmining.py file (and run the subsequent file for the pickle). + +Download phrase_extractor.py and main.py. + +Run main.py. Hover over the candidate's names in order to see lines drawn, depicting the sentiment of candidates' tweets towards other candidates. + +Link to write-up: [finished write-up](https://github.com/prava-d/InteractiveProgramming/blob/fixed/MP4Write-Up.pdf) diff --git a/SoftDesProject4Proposal.pdf b/SoftDesProject4Proposal.pdf new file mode 100644 index 0000000..4178d2b Binary files /dev/null and b/SoftDesProject4Proposal.pdf differ diff --git a/Ted Cruz.jpg b/Ted Cruz.jpg new file mode 100644 index 0000000..ef3b61d Binary files /dev/null and b/Ted Cruz.jpg differ diff --git a/Tim Kaine.jpg b/Tim Kaine.jpg new file mode 100644 index 0000000..6f23962 Binary files /dev/null and b/Tim Kaine.jpg differ diff --git a/USMapMat-Front.jpg b/USMapMat-Front.jpg new file mode 100644 index 0000000..88f8af1 Binary files /dev/null and b/USMapMat-Front.jpg differ diff --git a/background.jpg b/background.jpg new file mode 100644 index 0000000..a9ff5bb Binary files /dev/null and b/background.jpg differ diff --git a/bernietwitters.pickle b/bernietwitters.pickle new file mode 100644 index 0000000..234b3e5 Binary files /dev/null and b/bernietwitters.pickle differ diff --git a/clintontwitters.pickle b/clintontwitters.pickle new file mode 100644 index 0000000..f3ec5f1 Binary files /dev/null and b/clintontwitters.pickle differ diff --git a/cruztwitters.pickle b/cruztwitters.pickle new file mode 100644 index 0000000..f73b299 Binary files /dev/null and b/cruztwitters.pickle differ diff --git a/interactive_play.py b/interactive_play.py new file mode 100644 index 0000000..fb68094 --- /dev/null +++ b/interactive_play.py @@ -0,0 +1,38 @@ +import tkinter + +class App: + def __init__(self, root): + def do_work(self): + if self.mouse_pressed: + self.Hover1.bind("", lambda event, h=self.Hover1: h.configure(bg="red")) + self.Hover1.bind("", lambda event, h=self.Hover1: h.configure(bg="SystemButtonFace")) + + self.Hover2.bind("", lambda event, h=self.Hover2: h.configure(bg="yellow")) + self.Hover2.bind("", lambda event, h=self.Hover2: h.configure(bg="SystemButtonFace")) + else: + self.Hover1.unbind("") + self.Hover1.unbind("") + self.Hover2.unbind("") + self.Hover2.unbind("") + + + + def do_work(self): + if self.mouse_pressed: + self.Hover1.bind("", lambda event, h=self.Hover1: h.configure(bg="red")) + self.Hover1.bind("", lambda event, h=self.Hover1: h.configure(bg="SystemButtonFace")) + + self.Hover2.bind("", lambda event, h=self.Hover2: h.configure(bg="yellow")) + self.Hover2.bind("", lambda event, h=self.Hover2: h.configure(bg="SystemButtonFace")) + + def OnMouseDown(self, event): + self.mouse_pressed = True + self.do_work() + + def OnMouseUp(self, event): + self.mouse_pressed = False + self.do_work() + +root=tkinter.Tk() +app = App(root) +root.mainloop() diff --git a/kainetwitters.pickle b/kainetwitters.pickle new file mode 100644 index 0000000..caacf41 Binary files /dev/null and b/kainetwitters.pickle differ diff --git a/kasichtwitters.pickle b/kasichtwitters.pickle new file mode 100644 index 0000000..a2450d1 Binary files /dev/null and b/kasichtwitters.pickle differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..34020bb --- /dev/null +++ b/main.py @@ -0,0 +1,285 @@ +""" +Display candidate names on a circle and show the candidate's +sentiments when mouse is on the candidate's name +""" + +import pygame +import math +import pickle +import phrase_extractor as pe + +SCREEN_WIDTH = 1200 +SCREEN_HEIGHT = 800 + +# Define some colors +RED = (255, 0 , 0) +BLUE = (0, 0, 255) +POWDER_BLUE = (176, 224, 230) +FIREBRICK = (178, 34, 34) + +# Define colors for sentiment +NEGATIVE = (255, 0 , 0) # red +POSITIVE = (0, 225, 0) # green +NEUTRAL = (125, 125, 125) # gray +NO_INFO = (0, 255, 255) # cyan + +class Candidate: + def __init__(self, name, image_file, pickle_file): + self.name = name + self.picture = image_file + f = open(pickle_file, 'rb') + self.quotes = pickle.load(f) + f.close() + self.sentiment = [] + self.screen_rect = None + self.screen_xy = None + + def update_sentiment(self, candidate): + names = candidate.name.split() + names.append(candidate.name) + + sentences = [] + for quote in self.quotes: + sentences += pe.get_sentences(quote, names) + + score = 0 + mood = NO_INFO + for s in sentences: + quote = pe.get_quote(s) + score += quote.tone + if len(sentences) > 0: + if score > 0: + mood = POSITIVE + elif score < 0: + mood = NEGATIVE + else: + mood = NEUTRAL + self.sentiment.append((candidate, mood)) + + +def get_sentiments(candidates): + """ + Update sentiments for all candidates + """ + for candidate1 in candidates: + for candidate2 in candidates: + if candidate1 != candidate2: + candidate1.update_sentiment(candidate2) + + +def show_sentiments(screen, candidate): + """ + Show sentiment lines for the candidate + """ + # Screen width and height + screen_width = screen.get_width() + screen_height = screen.get_height() + + # Photo + photo_size = (int(screen_height / 7), int(screen_height / 6)) + image = pygame.image.load(candidate.picture).convert() + image = pygame.transform.scale(image, photo_size) + screen.blit(image, [10, 10]) + + # lines + for (to_candidate, mood) in candidate.sentiment: + pygame.draw.line(screen, mood, candidate.screen_xy, to_candidate.screen_xy, 2) + + +def show_candidates(screen, candidates): + """ + Display candidates names and returns screen position details + """ + # Screen width and height + screen_width = screen.get_width() + screen_height = screen.get_height() + + # Display legend + font_size = int(screen_height * 0.02) + font = pygame.font.SysFont('Calibri', font_size, True, False) + + x = int(screen_width * 0.85) + y = int(screen_height - 11 * font_size) + t = font.render('Legend:', True, BLUE) + screen.blit(t, [x, y]) + + x = int(screen_width * 0.9) + y = int(screen_height - 10 * font_size) + t = font.render('Positive', True, POSITIVE) + screen.blit(t, [x, y]) + + y = int(screen_height - 9 * font_size) + t = font.render('Negative', True, NEGATIVE) + screen.blit(t, [x, y]) + + y = int(screen_height - 8 * font_size) + t = font.render('Neutral', True, NEUTRAL) + screen.blit(t, [x, y]) + + y = int(screen_height - 7 * font_size) + t = font.render('No Info', True, NO_INFO) + screen.blit(t, [x, y]) + + # Draw a circle + center = (int(screen_width/2), int(screen_height/2)) + radius = int(screen_height * 0.4) + width = int(screen_height * 0.02) + pygame.draw.circle(screen, FIREBRICK, center, radius, width) + + # Select the font to use, size, bold, italics + font_size = int(screen_height * 0.03) + font = pygame.font.SysFont('Calibri', font_size, True, False) + + a = 0 + for candidate in candidates: + # Name length on screen in pixels (approx) + name_len = int(len(candidate.name) * font_size / 2) + + angle = a * 2 * math.pi/len(candidates) + + # Find screen position to write names around the circle + x = center[0] + int(radius * math.cos(angle)) + y = center[1] + int(radius * math.sin(angle)) + + # Draw a small red circle + candidate.screen_xy = (x,y) + pygame.draw.circle(screen, POWDER_BLUE, candidate.screen_xy, 5) + + # Calculate the position for writing name on the screen + if x < center[0]: + x -= name_len + elif x == center[0]: + x -= int(name_len / 2) + + if y < center[1]: + y -= font_size + elif y == center[1]: + y -= int(font_size / 2) + + # Draw the text + candidate.screen_rect = pygame.Rect(x, y, name_len, font_size) + t = font.render(candidate.name, True, POWDER_BLUE) + screen.blit(t, [x, y]) + a += 1 + + +def show_popup(screen, candidate): + """ + Display candidate's details + """ + # Screen width and height + screen_width = screen.get_width() + screen_height = screen.get_height() + + # popup window + w = screen_width / 1.9 + x = (screen_width - w) / 2 + h = screen_height / 1.8 + y = (screen_height - h) / 2 + pygame.draw.rect(screen, win_color, (x, y, w, h), 0) + + # Photo + photo_size = (int(screen_height / 7), int(screen_height / 7)) + image = pygame.image.load(candidate.picture).convert() + image = pygame.transform.scale(image, photo_size) + screen.blit(image, [int(x * 1.2), int(y * 1.1)]) + + # Name + nx = int(x * 1.7) + ny = int(y * 1.3) + font_size = int(screen_height * 0.03) + font = pygame.font.SysFont('Calibri', font_size, True, False) + t = font.render(candidate.name, True, RED) + screen.blit(t, [nx, ny]) + + # Details + x = int(x * 1.1) + y = int(y * 1.8) + font_size = int(screen_height * 0.035) + line_size = int(screen_height * 0.06) + font = pygame.font.SysFont('Calibri', font_size, False, False) + text = candidate.quotes[0] + while len(text) > 0: + if len(text) > line_size: + line = text[0:line_size] + text = text[line_size:] + else: + line = text + text = '' + + t = font.render(line.strip(), True, text_color) + screen.blit(t, [x, y]) + y += font_size + + + +def main(): + """ + This is our main program. + """ + + candidates = [ + Candidate('Bernie Sanders', 'Bernie Sanders.jpg', 'bernietwitters.pickle'), + Candidate('Hillary Clinton', 'Hillary Clinton.jpg', 'clintontwitters.pickle'), + Candidate('Ted Cruz', 'Ted Cruz.jpg', 'cruztwitters.pickle'), + Candidate('Tim Kaine', 'Tim Kaine.jpg', 'kainetwitters.pickle'), + Candidate('John Kasich', 'John Kasich.jpg', 'kasichtwitters.pickle'), + Candidate('Martin O\'Malley', 'Martin O\'Malley.jpg', 'malleytwitters.pickle'), + Candidate('Mike Pence', 'Mike Pence.jpg', 'pencertwitter.pickle'), + Candidate('Donald Trump', 'Donald Trump.jpg', 'trumptwitters.pickle') + ] + + get_sentiments(candidates) + + pygame.init() + + # Set the height and width of the screen + size = [SCREEN_WIDTH, SCREEN_HEIGHT] + screen = pygame.display.set_mode(size) + background = pygame.image.load('background.jpg').convert() + background = pygame.transform.scale(background, size) + + pygame.display.set_caption("2016 Presidential Candidates") + + + + # Loop until the user clicks the close button. + done = False + + # Used to manage how fast the screen updates + clock = pygame.time.Clock() + + screen.blit(background, background.get_rect()) + show_candidates(screen, candidates) + + while not done: + + # --- Event Processing + for event in pygame.event.get(): + if event.type == pygame.QUIT: + done = True + elif event.type == pygame.MOUSEMOTION: + on_candidate = False + p = pygame.mouse.get_pos() + for candidate in candidates: + if candidate.screen_rect.collidepoint(p): + #show_popup(screen, candidate) + show_sentiments(screen, candidate) + on_candidate = True + if not on_candidate: + screen.blit(background, background.get_rect()) + show_candidates(screen, candidates) + + # --- Wrap-up + # Limit to 10 frames per second + clock.tick(10) + + # Go ahead and update the screen with what we've drawn. + pygame.display.flip() + + + # Close everything down + pygame.quit() + +if __name__ == "__main__": + main() diff --git a/malleytwitters.pickle b/malleytwitters.pickle new file mode 100644 index 0000000..b198274 Binary files /dev/null and b/malleytwitters.pickle differ diff --git a/negative_words.txt b/negative_words.txt new file mode 100644 index 0000000..2744d37 --- /dev/null +++ b/negative_words.txt @@ -0,0 +1,63 @@ +bad +worse +mean +crooked +liar +fraud +stupid +nasty +ruin +destroy +kill +racist +racists +bigot +bigots +extreme +extremist +extremists +terror +terrorist +terrorists +negative +fringe +hate +hates +hatred +dishonest +bombastic +sad +sick +deplorable +deplorables +coward +cowards +cowardice +corrupt +corruption +kkk +klan +unfit +demeaning +objectifying +insulting +insult +scandal +ungrateful +disrespect +disrespectful +deceit +deceitful +theif +theives +wrong +wrongful +loser +weak +treason +traiter +attack +attacks +investigate +investigates +investigation \ No newline at end of file diff --git a/pencertwitter.pickle b/pencertwitter.pickle new file mode 100644 index 0000000..decd8a8 Binary files /dev/null and b/pencertwitter.pickle differ diff --git a/phrase_extractor.py b/phrase_extractor.py new file mode 100644 index 0000000..6819fe1 --- /dev/null +++ b/phrase_extractor.py @@ -0,0 +1,82 @@ +""" +Retrive sentences from the text that have the candidate name(s) passed +""" + +import nltk + +# list of words for scoring positive/negative +with open ('positive_words.txt', 'r') as f: + pos_words = f.read().split() + +with open ('negative_words.txt', 'r') as f: + neg_words = f.read().split() + + +class Quote: + """ + A quote and a positive/negative/neutral flag + """ + def __init__(self): + self.text = '' + self.tone = 0 + + def __str__(self): + return self.text + ' (' + self.tone + ')' + + +def get_quote(text): + """ + We use a list of positive and negative words to identify the tone. + If positive word exists in the sentence, but not "not or no" + word, we add 1 to the score. + If negative word exists in the sentence, but not "not or no" + word, we subtract 1 from the score. + positive score indicates positive tone, negative score indicates negative tone and zero score indicates neutral + """ + articles = ['a', 'an', 'the'] + negatives = ['no', 'not'] + quote = Quote() + quote.text = text + text_words = nltk.word_tokenize(text) + score = 0 + + for index, word in enumerate(text_words): + if word.lower() in pos_words: + # logic to take care of phrases like "not good" or "not a good person" + if ((index > 0 and text_words[index-1].lower() in negatives) or + (index > 1 and text_words[index-1].lower() in articles and + text_words[index-2].lower() in negatives)): + score -= 1 # not positive = negative + else: + score += 1 # postive + + elif word.lower() in neg_words: + # logic to take care of phrases like "not bad" or "not a bad person" + if ((index > 0 and text_words[index-1].lower() in negatives) or + (index > 1 and text_words[index-1].lower() in articles and + text_words[index-2].lower() in negatives)): + score += 1 # not negative = positive + else: + score -= 1 # negative + + if score > 0: + quote.tone = 1 + elif score < 0: + quote.tone = -1 + + return quote + + +def get_sentences(text, names): + """ + Returns sentences from text that conains candidate names(s). + names is a list of name variations of candindate like ['Hillary', 'Clinton', 'Hillary Clinton'] + or ['Donald', 'Trump', 'Donald J. Trump'] + """ + + sentences1 = nltk.sent_tokenize(text) + sentences2 = [] + for sentence in sentences1: + if any(name in sentence.split() for name in names): + sentences2.append(sentence) + + return sentences2 + diff --git a/positive_words.txt b/positive_words.txt new file mode 100644 index 0000000..e7550f0 --- /dev/null +++ b/positive_words.txt @@ -0,0 +1,32 @@ +good +best +better +great +greatest +excellent +positive +tremendous +marvelous +amazing +wonderful +fabulous +truthful +decent +improve +intelligent +smart +honest +nice +fine +love +loves +congrats +congratulations +congratulate +grateful +respect +respectful +win +winner +strong +leader diff --git a/textmining_twitterdata.py b/textmining_twitterdata.py new file mode 100644 index 0000000..ca6871f --- /dev/null +++ b/textmining_twitterdata.py @@ -0,0 +1,85 @@ +import twitter +import pickle +from pathlib import Path + +consumer_key = 'B8wAGC920QYKPl7ivFSLu1bDj' +consumer_secret = 'RPiyW9BpVRldlvXnQuRy2jVosKJxpnbHecYGtL5u5Sx3tllAEB' +access_token = '838329122-pQUHuiSRqQ9MnXer7clyzVgQPSIIKUo56jLbzBmn' +access_token_secret = 'CMWwMm3n62lhx51MIor3SKhd5yLQ0IAfO2IgxOyeq9lAD' + + +def get_trump_tweets(): + '''Gets Trump tweets either from the pickle file or + if that doesn't exist, the Twitter API''' + my_file = Path('trumptwitters.pickle') + if my_file.is_file(): + # Load data from a file (will be part of your data processing script) + input_file = open('trumptwitters.pickle', 'rb') + reloaded_copy_of_texts = pickle.load(input_file) + return reloaded_copy_of_texts + else: + retrieve_tweets('@realDonaldTrump', 'trumptwitters.pickle', + '796130213826621440') + + +def get_clinton_tweets(): + '''Gets Clinton tweets either from the pickle file or + if that doesn't exist, the Twitter API''' + my_file = Path('clintontwitters.pickle') + if my_file.is_file(): + # Load data from a file (will be part of your data processing script) + input_file = open('clintontwitters.pickle', 'rb') + reloaded_copy_of_texts = pickle.load(input_file) + return reloaded_copy_of_texts + else: + retrieve_tweets('@HillaryClinton', 'clintontwitters.pickle', + '796123724479164416') + + +def get_tim_kaine_tweets(): + '''Gets Tim Kaine tweets either from the pickle file or + if that doesn't exist, the Twitter API''' + my_file = Path('kainetwitters.pickle') + if my_file.is_file(): + # Load data from a file (will be part of your data processing script) + input_file = open('kainetwitters.pickle', 'rb') + reloaded_copy_of_texts = pickle.load(input_file) + return reloaded_copy_of_texts + else: + retrieve_tweets('@SenKaineOffice', 'kainetwitters.pickle', + '796162901539164160') + + +def get_mike_pence(): + '''Gets MIke Pence tweets either from the pickle file or + if that doesn't exist, the Twitter API''' + my_file = Path('pencetwitter.pickle') + if my_file.is_file(): + # Load data from a file (will be part of your data processing script) + input_file = open('pencetwitters.pickle', 'rb') + reloaded_copy_of_texts = pickle.load(input_file) + return reloaded_copy_of_texts + else: + retrieve_tweets('@mike_pence', 'pencertwitter.pickle', + '822447933899599872') + + +def retrieve_tweets(name, filename, idnum): + '''Retrieves tweets from the given screen name that were published + before the given id from the twitter API and stores them in the + given file''' + + api = twitter.Api(consumer_key, consumer_secret, + access_token, + access_token_secret) + + tweets = api.GetUserTimeline(screen_name=name, count=199, max_id=idnum) + for status in tweets: + # Print the ID and the date published for each Tweet + print(status.id) + print(status.created_at) + tweets = [s.text for s in tweets] + # Save data to a file + f = open(filename, 'wb') + pickle.dump(tweets, f) + f.close() diff --git a/trumptwitters.pickle b/trumptwitters.pickle new file mode 100644 index 0000000..b152c4b Binary files /dev/null and b/trumptwitters.pickle differ