diff --git a/contributors/JohnnySimple.txt b/contributors/JohnnySimple.txt
new file mode 100644
index 0000000..1d6ce2b
--- /dev/null
+++ b/contributors/JohnnySimple.txt
@@ -0,0 +1 @@
+Obeng Johnson Boateng
\ No newline at end of file
diff --git a/twitter_mining/.ipynb_checkpoints/twitter_mining-checkpoint.ipynb b/twitter_mining/.ipynb_checkpoints/twitter_mining-checkpoint.ipynb
new file mode 100644
index 0000000..15c8ab7
--- /dev/null
+++ b/twitter_mining/.ipynb_checkpoints/twitter_mining-checkpoint.ipynb
@@ -0,0 +1,617 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "rziXcUSBskS3"
+ },
+ "outputs": [],
+ "source": [
+ "#from google.colab import drive\n",
+ "#drive.mount('/content/drive')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Twitter Developer Account\n",
+ "In order to use Twitter’s API, we have to create a developer account on the Twitter apps site.\n",
+ " * Log in or make a Twitter account at https://apps.twitter.com/.\n",
+ " * Create a new app (button on the top right)\n",
+ " \n",
+ "
\n",
+ "\n",
+ "Fill in the app creation page with a unique name, a website name (use a placeholder website if you don’t have one), and a project description. Accept the terms and conditions and proceed to the next page.\n",
+ "\n",
+ "Once your project has been created, click on the “Keys and Access Tokens” tab. You should now be able to see your consumer secret and consumer key.\n",
+ "\n",
+ "
\n",
+ "\n",
+ "You’ll also need a pair of access tokens. Scroll down and request those tokens. The page should refresh, and you should now have an access token and access token secret.\n",
+ "\n",
+ "
\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Import necessary modules"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "UI2fIQFxrNLB",
+ "run_control": {
+ "frozen": false,
+ "read_only": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import sys\n",
+ "import os\n",
+ "import json\n",
+ "import pandas as pd\n",
+ "import matplotlib.pyplot as plt\n",
+ "import re\n",
+ "import string"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "3pf5Xapqrq0M"
+ },
+ "outputs": [],
+ "source": [
+ "#Import the necessary methods from tweepy library \n",
+ "\n",
+ "#install tweepy if you don't have it\n",
+ "#!pip install tweepy\n",
+ "import tweepy\n",
+ "from tweepy.streaming import StreamListener\n",
+ "from tweepy import OAuthHandler\n",
+ "from tweepy import Stream\n",
+ "\n",
+ "#sentiment analysis package\n",
+ "#!pip install textblob\n",
+ "from textblob import TextBlob\n",
+ "\n",
+ "#general text pre-processor\n",
+ "#!pip install nltk\n",
+ "import nltk\n",
+ "from nltk.corpus import stopwords\n",
+ "from nltk import word_tokenize\n",
+ "\n",
+ "#tweet pre-processor \n",
+ "# !pip install tweet-preprocessor\n",
+ "import preprocessor as p\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Starting code\n",
+ "Below we define some starting codes (python classes and function) to illustrate and assist on how to fetch data from twitter and analyse them. \n",
+ "\n",
+ "### **Your task is**\n",
+ "1. Go through the code and understand it. Know what each function does\n",
+ "2. If you find error, fix it. Ask for help in the slack channel if you find serious mistake\n",
+ "3. Extend the code such that it will be useful for topics you choose to analyse\n",
+ "4. Make nice plots and share your finding (e.g. insight on the main covid19 twitter converstions about your country)\n",
+ "5. Submit what ever you managed to do by Wednesday morning. But you should keep using what you build to write blogs, share on facebook, etc. \n",
+ "\n",
+ "\n",
+ "[Reference used to build some of the functions here](https://towardsdatascience.com/extracting-twitter-data-pre-processing-and-sentiment-analysis-using-python-3-0-7192bd8b47cf)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class tweetsearch():\n",
+ " '''\n",
+ " This is a basic class to search and download twitter data.\n",
+ " You can build up on it to extend the functionalities for more \n",
+ " sophisticated analysis.\n",
+ " \n",
+ " '''\n",
+ " def __init__(self,cols=None,auth=None):\n",
+ " #\n",
+ " if not cols is None:\n",
+ " self.cols = cols\n",
+ " else:\n",
+ " self.cols = ['id', 'created_at', 'source', 'original_text','clean_text', \n",
+ " 'sentiment','polarity','subjectivity', 'lang',\n",
+ " 'favorite_count', 'retweet_count', 'original_author', \n",
+ " 'possibly_sensitive', 'hashtags',\n",
+ " 'user_mentions', 'place', 'place_coord_boundaries']\n",
+ " \n",
+ " if auth is None:\n",
+ " #Variables that contains the user credentials to access Twitter API \n",
+ " consumer_key = os.environ.get('TWITTER_API_KEY')\n",
+ " consumer_secret = os.environ.get('TWITTER_API_SECRET')\n",
+ " access_token = os.environ.get('TWITTER_ACCESS_TOKEN')\n",
+ " access_token_secret = os.environ.get('TWITTER_ACCESS_TOKEN_SECRET')\n",
+ "\n",
+ "\n",
+ " #This handles Twitter authetification and the connection to Twitter \n",
+ " #Streaming API\n",
+ "# auth = OAuthHandler(consumer_key, consumer_secret)\n",
+ "# auth.set_access_token(access_token, access_token_secret)\n",
+ "\n",
+ " auth = OAuthHandler('Dl1sJGGSyHg45rl5cvGRFqB4q', '2AWIDJ89G4xFR5E2ggg6MEWvMKghupDzchE7XqIlEF5QeeQKbN')\n",
+ " auth.set_access_token('1064907352963522561-Rn0VyQSiBZCVy22JiqjGNVE0c8XfAr', '97gl0daSxBWcIIgoIdOzuXSSsWAlrZZHw2toCaIBD69wV')\n",
+ " \n",
+ "\n",
+ " # \n",
+ " self.auth = auth\n",
+ " self.api = tweepy.API(auth, wait_on_rate_limit=True) \n",
+ " \n",
+ "\n",
+ " def clean_tweets(self,twitter_text):\n",
+ "\n",
+ " #use pre processor\n",
+ " tweet = p.clean(twitter_text)\n",
+ "\n",
+ " #HappyEmoticons\n",
+ " emoticons_happy = set([\n",
+ " ':-)', ':)', ';)', ':o)', ':]', ':3', ':c)', ':>', '=]', '8)', '=)', ':}',\n",
+ " ':^)', ':-D', ':D', '8-D', '8D', 'x-D', 'xD', 'X-D', 'XD', '=-D', '=D',\n",
+ " '=-3', '=3', ':-))', \":'-)\", \":')\", ':*', ':^*', '>:P', ':-P', ':P', 'X-P',\n",
+ " 'x-p', 'xp', 'XP', ':-p', ':p', '=p', ':-b', ':b', '>:)', '>;)', '>:-)',\n",
+ " '<3'\n",
+ " ])\n",
+ "\n",
+ " # Sad Emoticons\n",
+ " emoticons_sad = set([\n",
+ " ':L', ':-/', '>:/', ':S', '>:[', ':@', ':-(', ':[', ':-||', '=L', ':<',\n",
+ " ':-[', ':-<', '=\\\\', '=/', '>:(', ':(', '>.<', \":'-(\", \":'(\", ':\\\\', ':-c',\n",
+ " ':c', ':{', '>:\\\\', ';('\n",
+ " ])\n",
+ "\n",
+ " #Emoji patterns\n",
+ " emoji_pattern = re.compile(\"[\"\n",
+ " u\"\\U0001F600-\\U0001F64F\" # emoticons\n",
+ " u\"\\U0001F300-\\U0001F5FF\" # symbols & pictographs\n",
+ " u\"\\U0001F680-\\U0001F6FF\" # transport & map symbols\n",
+ " u\"\\U0001F1E0-\\U0001F1FF\" # flags (iOS)\n",
+ " u\"\\U00002702-\\U000027B0\"\n",
+ " u\"\\U000024C2-\\U0001F251\"\n",
+ " \"]+\", flags=re.UNICODE)\n",
+ "\n",
+ " #combine sad and happy emoticons\n",
+ " emoticons = emoticons_happy.union(emoticons_sad)\n",
+ "\n",
+ " stop_words = set(stopwords.words('english'))\n",
+ " word_tokens = word_tokenize(tweet)\n",
+ " #after tweepy preprocessing the colon symbol left remain after \n",
+ " #removing mentions\n",
+ " tweet = re.sub(r':', '', tweet)\n",
+ " tweet = re.sub(r'…', '', tweet)\n",
+ "\n",
+ " #replace consecutive non-ASCII characters with a space\n",
+ " tweet = re.sub(r'[^\\x00-\\x7F]+',' ', tweet)\n",
+ "\n",
+ " #remove emojis from tweet\n",
+ " tweet = emoji_pattern.sub(r'', tweet)\n",
+ "\n",
+ " #filter using NLTK library append it to a string\n",
+ " self.filtered_tweet = [w for w in word_tokens if not w in stop_words]\n",
+ "\n",
+ " #looping through conditions\n",
+ " filtered_tweet = [] \n",
+ " for w in word_tokens:\n",
+ " #check tokens against stop words , emoticons and punctuations\n",
+ " if w not in stop_words and w not in emoticons and w not in string.punctuation:\n",
+ " filtered_tweet.append(w)\n",
+ "\n",
+ " return ' '.join(filtered_tweet) \n",
+ "\n",
+ " def get_tweets(self, keyword, csvfile=None):\n",
+ " \n",
+ " \n",
+ " df = pd.DataFrame(columns=self.cols)\n",
+ " \n",
+ " if not csvfile is None:\n",
+ " #If the file exists, then read the existing data from the CSV file.\n",
+ " if os.path.exists(csvfile):\n",
+ " df = pd.read_csv(csvfile, header=0)\n",
+ " \n",
+ "\n",
+ " #page attribute in tweepy.cursor and iteration\n",
+ " for page in tweepy.Cursor(self.api.search, q=keyword,count=200, include_rts=False).pages():\n",
+ "\n",
+ "\n",
+ " for status in page:\n",
+ " \n",
+ " new_entry = []\n",
+ " status = status._json\n",
+ " \n",
+ " #filter by language\n",
+ " if status['lang'] != 'en':\n",
+ " continue\n",
+ "\n",
+ " \n",
+ " #if this tweet is a retweet update retweet count\n",
+ " if status['created_at'] in df['created_at'].values:\n",
+ " i = df.loc[df['created_at'] == status['created_at']].index[0]\n",
+ " #\n",
+ " cond1 = status['favorite_count'] != df.at[i, 'favorite_count']\n",
+ " cond2 = status['retweet_count'] != df.at[i, 'retweet_count']\n",
+ " if cond1 or cond2:\n",
+ " df.at[i, 'favorite_count'] = status['favorite_count']\n",
+ " df.at[i, 'retweet_count'] = status['retweet_count']\n",
+ " continue\n",
+ "\n",
+ " #calculate sentiment\n",
+ " clean_text = p.clean(status['text'])\n",
+ " filtered_tweet = self.clean_tweets(clean_text)\n",
+ " blob = TextBlob(filtered_tweet)\n",
+ " Sentiment = blob.sentiment \n",
+ " polarity = Sentiment.polarity\n",
+ " subjectivity = Sentiment.subjectivity\n",
+ "\n",
+ " new_entry += [status['id'], status['created_at'],\n",
+ " status['source'], status['text'],filtered_tweet, \n",
+ " Sentiment,polarity,subjectivity, status['lang'],\n",
+ " status['favorite_count'], status['retweet_count']]\n",
+ "\n",
+ " new_entry.append(status['user']['screen_name'])\n",
+ "\n",
+ " try:\n",
+ " is_sensitive = status['possibly_sensitive']\n",
+ " except KeyError:\n",
+ " is_sensitive = None\n",
+ "\n",
+ " new_entry.append(is_sensitive)\n",
+ "\n",
+ " hashtags = \", \".join([hashtag_item['text'] for \\\n",
+ " hashtag_item in status['entities']['hashtags']])\n",
+ " new_entry.append(hashtags) #append the hashtags\n",
+ "\n",
+ " #\n",
+ " mentions = \", \".join([mention['screen_name'] for \\\n",
+ " mention in status['entities']['user_mentions']])\n",
+ " new_entry.append(mentions) #append the user mentions\n",
+ "\n",
+ " try:\n",
+ " xyz = status['place']['bounding_box']['coordinates']\n",
+ " coordinates = [coord for loc in xyz for coord in loc]\n",
+ " except TypeError:\n",
+ " coordinates = None\n",
+ " #\n",
+ " new_entry.append(coordinates)\n",
+ "\n",
+ " try:\n",
+ " location = status['user']['location']\n",
+ " except TypeError:\n",
+ " location = ''\n",
+ " #\n",
+ " new_entry.append(location)\n",
+ "\n",
+ " #now append a row to the dataframe\n",
+ " single_tweet_df = pd.DataFrame([new_entry], columns=self.cols)\n",
+ " df = df.append(single_tweet_df, ignore_index=True)\n",
+ "\n",
+ " if not csvfile is None:\n",
+ " #save it to file\n",
+ " df.to_csv(csvfile, columns=self.cols, index=False, encoding=\"utf-8\")\n",
+ " \n",
+ " return df\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Search twitter and fetch data example"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "covid_keywords = '#COVID19 OR #COVID19Africa' #hashtag based search\n",
+ "tweets_file = 'covid19_23june2020.json'\n",
+ "\n",
+ "#get data on keywords\n",
+ "ts = tweetsearch()\n",
+ "# df = ts.get_tweets(covid_keywords, csvfile=tweets_file) #you saved the \n",
+ "df = ts.get_tweets(covid_keywords)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Stream data and save it to file\n",
+ "In the above we saw how to search and fetch data, below we will see how we will stream data from twitter. Make sure you understand the difference between search and stream features of twitter api.\n",
+ "\n",
+ "### **SAME TASK AS ABOVE**\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "r6lcy009rX_e"
+ },
+ "outputs": [],
+ "source": [
+ "#This is a basic listener that writes received tweets to file.\n",
+ "class StdOutListener(StreamListener):\n",
+ "\n",
+ " def __init__(self,fhandle, stop_at = 1000):\n",
+ " self.tweet_counter = 0\n",
+ " self.stop_at = stop_at\n",
+ " self.fhandle = fhandle\n",
+ " \n",
+ " \n",
+ " def on_data(self, data):\n",
+ " self.fhandle.write(data)\n",
+ " \n",
+ " #stop if enough tweets are obtained\n",
+ " self.tweet_counter += 1 \n",
+ " if self.tweet_counter < self.stop_at: \n",
+ " return True\n",
+ " else:\n",
+ " print('Max number of tweets reached: #tweets = ' + str(self.tweet_counter))\n",
+ " return False\n",
+ "\n",
+ " def on_error(self, status):\n",
+ " print (status)\n",
+ "\n",
+ "def stream_tweet_data(filename='data/tweets.json',\n",
+ " keywords=['COVID19Africa','COVID19Ethiopia'],\n",
+ " is_async=False):\n",
+ " # tweet topics to use as a filter. The tweets downloaded\n",
+ " # will have one of the topics in their text or hashtag \n",
+ "\n",
+ " print('saving data to file: ',filename)\n",
+ "\n",
+ " #print the tweet topics \n",
+ " print('TweetKeywords are: ',keywords)\n",
+ " print('For testing case, please interupt the downloading process using ctrl+x after about 5 mins ')\n",
+ " print('To keep streaming in the background, pass is_async=True')\n",
+ "\n",
+ " #Variables that contains the user credentials to access Twitter API \n",
+ " consumer_key = os.environ.get('TWITTER_API_KEY')\n",
+ " consumer_secret = os.environ.get('TWITTER_API_SECRET')\n",
+ " access_token = os.environ.get('TWITTER_ACCESS_TOKEN')\n",
+ " access_token_secret = os.environ.get('TWITTER_ACCESS_TOKEN_SECRET')\n",
+ "\n",
+ " #open file \n",
+ " fhandle=open(filename,'w')\n",
+ "\n",
+ " #This handles Twitter authetification and the connection to Twitter Streaming API\n",
+ " l = StdOutListener(fhandle)\n",
+ " auth = OAuthHandler(consumer_key, consumer_secret)\n",
+ " auth.set_access_token(access_token, access_token_secret)\n",
+ "\n",
+ " stream = Stream(auth, l)\n",
+ "\n",
+ " #This line filter Twitter Streams to capture data by the keywords: first argument to this code\n",
+ " stream.filter(track=keywords,is_async=is_async)\n",
+ "\n",
+ " return None\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Use case of the above code"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "F8tcPcSMrNLL",
+ "outputId": "d7abd9c2-065c-40e8-f71b-e808d985c364"
+ },
+ "outputs": [],
+ "source": [
+ "tweets_file = 'data/covid19_23june2020.json'\n",
+ "stream_tweet_data(filename=tweets_file,keywords=['covid19','#COVID19Africa']) #\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Filter twitter data and do basic analysis\n",
+ "**Extend it to gain more insight**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "F8tcPcSMrNLL",
+ "outputId": "d7abd9c2-065c-40e8-f71b-e808d985c364"
+ },
+ "outputs": [],
+ "source": [
+ "tweets_data = []\n",
+ "for line in open(tweets_file, \"r\"):\n",
+ " try:\n",
+ " tweet = json.loads(line)\n",
+ " x=tweet['text']\n",
+ " tweets_data.append(tweet)\n",
+ " except:\n",
+ " continue\n",
+ "\n",
+ "\n",
+ "print('saved numbers of tweets: ', len(tweets_data))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "hlFKyGnYrNLX"
+ },
+ "outputs": [],
+ "source": [
+ "tweets = pd.DataFrame(columns=['text','lang','country'])\n",
+ "\n",
+ "tweets['text'] = list(map(lambda tweet: tweet['text'], tweets_data))\n",
+ "tweets['lang'] = list(map(lambda tweet: tweet['lang'], tweets_data))\n",
+ "tweets['country'] = list(map(lambda tweet: tweet['place']['country'] if tweet['place'] != None else None, \n",
+ " tweets_data))\n",
+ "\n",
+ "\n",
+ "tweets_by_lang = tweets['lang'].value_counts()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "aEPPoCBtrNLd",
+ "outputId": "bfe0cee1-814d-4f30-f47d-205218b3adc9"
+ },
+ "outputs": [],
+ "source": [
+ "fig, ax = plt.subplots()\n",
+ "ax.tick_params(axis='x', labelsize=15)\n",
+ "ax.tick_params(axis='y', labelsize=10)\n",
+ "ax.set_xlabel('Languages', fontsize=15)\n",
+ "ax.set_ylabel('Number of tweets' , fontsize=15)\n",
+ "ax.set_title('Top 5 languages', fontsize=15, fontweight='bold')\n",
+ "tweets_by_lang[:5].plot(ax=ax, kind='bar', color='red')\n",
+ "\n",
+ "tweets_by_country = tweets['country'].value_counts()\n",
+ "\n",
+ "fig, ax = plt.subplots()\n",
+ "ax.tick_params(axis='x', labelsize=15)\n",
+ "ax.tick_params(axis='y', labelsize=10)\n",
+ "ax.set_xlabel('Countries', fontsize=15)\n",
+ "ax.set_ylabel('Number of tweets' , fontsize=15)\n",
+ "ax.set_title('Top 5 countries', fontsize=15, fontweight='bold')\n",
+ "tweets_by_country[:5].plot(ax=ax, kind='bar', color='blue')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "IQYFg6t5rNLj"
+ },
+ "source": [
+ "# Hashtag histogram. \n",
+ "\n",
+ "## Please write code that will help you answer the following questions\n",
+ " 1) What is the most used hashtag?\n",
+ " \n",
+ " 2) What is the most used referenced username?\n",
+ " \n",
+ " 3) What is the most retweeted tweet?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "p0VUwYR8rNLk"
+ },
+ "outputs": [],
+ "source": [
+ "# getting the most used hashtag\n",
+ "tags = df['hashtags']\n",
+ "hashtag_counts = tags.value_counts()\n",
+ "print(\"The most used hashtag is: \", tags[hashtag_counts.iloc[0]])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# getting the most used referenced username\n",
+ "names = df['user_mentions']\n",
+ "username_counts = names.value_counts()\n",
+ "print(\"The most used referenced username is: \", names[username_counts.iloc[0]])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tweets = df['original_text']\n",
+ "retweets = df['retweet_count']\n",
+ "print(\"The most retweeted tweet is: \", tweets[retweet.max()])"
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "name": "vistweet.ipynb",
+ "provenance": []
+ },
+ "hide_input": false,
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.6"
+ },
+ "latex_envs": {
+ "bibliofile": "biblio.bib",
+ "cite_by": "apalike",
+ "current_citInitial": 1,
+ "eqLabelWithNumbers": true,
+ "eqNumInitial": 0
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/twitter_mining/JohnnySimple.ipynb b/twitter_mining/JohnnySimple.ipynb
new file mode 100644
index 0000000..15c8ab7
--- /dev/null
+++ b/twitter_mining/JohnnySimple.ipynb
@@ -0,0 +1,617 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "rziXcUSBskS3"
+ },
+ "outputs": [],
+ "source": [
+ "#from google.colab import drive\n",
+ "#drive.mount('/content/drive')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Twitter Developer Account\n",
+ "In order to use Twitter’s API, we have to create a developer account on the Twitter apps site.\n",
+ " * Log in or make a Twitter account at https://apps.twitter.com/.\n",
+ " * Create a new app (button on the top right)\n",
+ " \n",
+ "
\n",
+ "\n",
+ "Fill in the app creation page with a unique name, a website name (use a placeholder website if you don’t have one), and a project description. Accept the terms and conditions and proceed to the next page.\n",
+ "\n",
+ "Once your project has been created, click on the “Keys and Access Tokens” tab. You should now be able to see your consumer secret and consumer key.\n",
+ "\n",
+ "
\n",
+ "\n",
+ "You’ll also need a pair of access tokens. Scroll down and request those tokens. The page should refresh, and you should now have an access token and access token secret.\n",
+ "\n",
+ "
\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Import necessary modules"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "UI2fIQFxrNLB",
+ "run_control": {
+ "frozen": false,
+ "read_only": false
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import sys\n",
+ "import os\n",
+ "import json\n",
+ "import pandas as pd\n",
+ "import matplotlib.pyplot as plt\n",
+ "import re\n",
+ "import string"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "3pf5Xapqrq0M"
+ },
+ "outputs": [],
+ "source": [
+ "#Import the necessary methods from tweepy library \n",
+ "\n",
+ "#install tweepy if you don't have it\n",
+ "#!pip install tweepy\n",
+ "import tweepy\n",
+ "from tweepy.streaming import StreamListener\n",
+ "from tweepy import OAuthHandler\n",
+ "from tweepy import Stream\n",
+ "\n",
+ "#sentiment analysis package\n",
+ "#!pip install textblob\n",
+ "from textblob import TextBlob\n",
+ "\n",
+ "#general text pre-processor\n",
+ "#!pip install nltk\n",
+ "import nltk\n",
+ "from nltk.corpus import stopwords\n",
+ "from nltk import word_tokenize\n",
+ "\n",
+ "#tweet pre-processor \n",
+ "# !pip install tweet-preprocessor\n",
+ "import preprocessor as p\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Starting code\n",
+ "Below we define some starting codes (python classes and function) to illustrate and assist on how to fetch data from twitter and analyse them. \n",
+ "\n",
+ "### **Your task is**\n",
+ "1. Go through the code and understand it. Know what each function does\n",
+ "2. If you find error, fix it. Ask for help in the slack channel if you find serious mistake\n",
+ "3. Extend the code such that it will be useful for topics you choose to analyse\n",
+ "4. Make nice plots and share your finding (e.g. insight on the main covid19 twitter converstions about your country)\n",
+ "5. Submit what ever you managed to do by Wednesday morning. But you should keep using what you build to write blogs, share on facebook, etc. \n",
+ "\n",
+ "\n",
+ "[Reference used to build some of the functions here](https://towardsdatascience.com/extracting-twitter-data-pre-processing-and-sentiment-analysis-using-python-3-0-7192bd8b47cf)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class tweetsearch():\n",
+ " '''\n",
+ " This is a basic class to search and download twitter data.\n",
+ " You can build up on it to extend the functionalities for more \n",
+ " sophisticated analysis.\n",
+ " \n",
+ " '''\n",
+ " def __init__(self,cols=None,auth=None):\n",
+ " #\n",
+ " if not cols is None:\n",
+ " self.cols = cols\n",
+ " else:\n",
+ " self.cols = ['id', 'created_at', 'source', 'original_text','clean_text', \n",
+ " 'sentiment','polarity','subjectivity', 'lang',\n",
+ " 'favorite_count', 'retweet_count', 'original_author', \n",
+ " 'possibly_sensitive', 'hashtags',\n",
+ " 'user_mentions', 'place', 'place_coord_boundaries']\n",
+ " \n",
+ " if auth is None:\n",
+ " #Variables that contains the user credentials to access Twitter API \n",
+ " consumer_key = os.environ.get('TWITTER_API_KEY')\n",
+ " consumer_secret = os.environ.get('TWITTER_API_SECRET')\n",
+ " access_token = os.environ.get('TWITTER_ACCESS_TOKEN')\n",
+ " access_token_secret = os.environ.get('TWITTER_ACCESS_TOKEN_SECRET')\n",
+ "\n",
+ "\n",
+ " #This handles Twitter authetification and the connection to Twitter \n",
+ " #Streaming API\n",
+ "# auth = OAuthHandler(consumer_key, consumer_secret)\n",
+ "# auth.set_access_token(access_token, access_token_secret)\n",
+ "\n",
+ " auth = OAuthHandler('Dl1sJGGSyHg45rl5cvGRFqB4q', '2AWIDJ89G4xFR5E2ggg6MEWvMKghupDzchE7XqIlEF5QeeQKbN')\n",
+ " auth.set_access_token('1064907352963522561-Rn0VyQSiBZCVy22JiqjGNVE0c8XfAr', '97gl0daSxBWcIIgoIdOzuXSSsWAlrZZHw2toCaIBD69wV')\n",
+ " \n",
+ "\n",
+ " # \n",
+ " self.auth = auth\n",
+ " self.api = tweepy.API(auth, wait_on_rate_limit=True) \n",
+ " \n",
+ "\n",
+ " def clean_tweets(self,twitter_text):\n",
+ "\n",
+ " #use pre processor\n",
+ " tweet = p.clean(twitter_text)\n",
+ "\n",
+ " #HappyEmoticons\n",
+ " emoticons_happy = set([\n",
+ " ':-)', ':)', ';)', ':o)', ':]', ':3', ':c)', ':>', '=]', '8)', '=)', ':}',\n",
+ " ':^)', ':-D', ':D', '8-D', '8D', 'x-D', 'xD', 'X-D', 'XD', '=-D', '=D',\n",
+ " '=-3', '=3', ':-))', \":'-)\", \":')\", ':*', ':^*', '>:P', ':-P', ':P', 'X-P',\n",
+ " 'x-p', 'xp', 'XP', ':-p', ':p', '=p', ':-b', ':b', '>:)', '>;)', '>:-)',\n",
+ " '<3'\n",
+ " ])\n",
+ "\n",
+ " # Sad Emoticons\n",
+ " emoticons_sad = set([\n",
+ " ':L', ':-/', '>:/', ':S', '>:[', ':@', ':-(', ':[', ':-||', '=L', ':<',\n",
+ " ':-[', ':-<', '=\\\\', '=/', '>:(', ':(', '>.<', \":'-(\", \":'(\", ':\\\\', ':-c',\n",
+ " ':c', ':{', '>:\\\\', ';('\n",
+ " ])\n",
+ "\n",
+ " #Emoji patterns\n",
+ " emoji_pattern = re.compile(\"[\"\n",
+ " u\"\\U0001F600-\\U0001F64F\" # emoticons\n",
+ " u\"\\U0001F300-\\U0001F5FF\" # symbols & pictographs\n",
+ " u\"\\U0001F680-\\U0001F6FF\" # transport & map symbols\n",
+ " u\"\\U0001F1E0-\\U0001F1FF\" # flags (iOS)\n",
+ " u\"\\U00002702-\\U000027B0\"\n",
+ " u\"\\U000024C2-\\U0001F251\"\n",
+ " \"]+\", flags=re.UNICODE)\n",
+ "\n",
+ " #combine sad and happy emoticons\n",
+ " emoticons = emoticons_happy.union(emoticons_sad)\n",
+ "\n",
+ " stop_words = set(stopwords.words('english'))\n",
+ " word_tokens = word_tokenize(tweet)\n",
+ " #after tweepy preprocessing the colon symbol left remain after \n",
+ " #removing mentions\n",
+ " tweet = re.sub(r':', '', tweet)\n",
+ " tweet = re.sub(r'…', '', tweet)\n",
+ "\n",
+ " #replace consecutive non-ASCII characters with a space\n",
+ " tweet = re.sub(r'[^\\x00-\\x7F]+',' ', tweet)\n",
+ "\n",
+ " #remove emojis from tweet\n",
+ " tweet = emoji_pattern.sub(r'', tweet)\n",
+ "\n",
+ " #filter using NLTK library append it to a string\n",
+ " self.filtered_tweet = [w for w in word_tokens if not w in stop_words]\n",
+ "\n",
+ " #looping through conditions\n",
+ " filtered_tweet = [] \n",
+ " for w in word_tokens:\n",
+ " #check tokens against stop words , emoticons and punctuations\n",
+ " if w not in stop_words and w not in emoticons and w not in string.punctuation:\n",
+ " filtered_tweet.append(w)\n",
+ "\n",
+ " return ' '.join(filtered_tweet) \n",
+ "\n",
+ " def get_tweets(self, keyword, csvfile=None):\n",
+ " \n",
+ " \n",
+ " df = pd.DataFrame(columns=self.cols)\n",
+ " \n",
+ " if not csvfile is None:\n",
+ " #If the file exists, then read the existing data from the CSV file.\n",
+ " if os.path.exists(csvfile):\n",
+ " df = pd.read_csv(csvfile, header=0)\n",
+ " \n",
+ "\n",
+ " #page attribute in tweepy.cursor and iteration\n",
+ " for page in tweepy.Cursor(self.api.search, q=keyword,count=200, include_rts=False).pages():\n",
+ "\n",
+ "\n",
+ " for status in page:\n",
+ " \n",
+ " new_entry = []\n",
+ " status = status._json\n",
+ " \n",
+ " #filter by language\n",
+ " if status['lang'] != 'en':\n",
+ " continue\n",
+ "\n",
+ " \n",
+ " #if this tweet is a retweet update retweet count\n",
+ " if status['created_at'] in df['created_at'].values:\n",
+ " i = df.loc[df['created_at'] == status['created_at']].index[0]\n",
+ " #\n",
+ " cond1 = status['favorite_count'] != df.at[i, 'favorite_count']\n",
+ " cond2 = status['retweet_count'] != df.at[i, 'retweet_count']\n",
+ " if cond1 or cond2:\n",
+ " df.at[i, 'favorite_count'] = status['favorite_count']\n",
+ " df.at[i, 'retweet_count'] = status['retweet_count']\n",
+ " continue\n",
+ "\n",
+ " #calculate sentiment\n",
+ " clean_text = p.clean(status['text'])\n",
+ " filtered_tweet = self.clean_tweets(clean_text)\n",
+ " blob = TextBlob(filtered_tweet)\n",
+ " Sentiment = blob.sentiment \n",
+ " polarity = Sentiment.polarity\n",
+ " subjectivity = Sentiment.subjectivity\n",
+ "\n",
+ " new_entry += [status['id'], status['created_at'],\n",
+ " status['source'], status['text'],filtered_tweet, \n",
+ " Sentiment,polarity,subjectivity, status['lang'],\n",
+ " status['favorite_count'], status['retweet_count']]\n",
+ "\n",
+ " new_entry.append(status['user']['screen_name'])\n",
+ "\n",
+ " try:\n",
+ " is_sensitive = status['possibly_sensitive']\n",
+ " except KeyError:\n",
+ " is_sensitive = None\n",
+ "\n",
+ " new_entry.append(is_sensitive)\n",
+ "\n",
+ " hashtags = \", \".join([hashtag_item['text'] for \\\n",
+ " hashtag_item in status['entities']['hashtags']])\n",
+ " new_entry.append(hashtags) #append the hashtags\n",
+ "\n",
+ " #\n",
+ " mentions = \", \".join([mention['screen_name'] for \\\n",
+ " mention in status['entities']['user_mentions']])\n",
+ " new_entry.append(mentions) #append the user mentions\n",
+ "\n",
+ " try:\n",
+ " xyz = status['place']['bounding_box']['coordinates']\n",
+ " coordinates = [coord for loc in xyz for coord in loc]\n",
+ " except TypeError:\n",
+ " coordinates = None\n",
+ " #\n",
+ " new_entry.append(coordinates)\n",
+ "\n",
+ " try:\n",
+ " location = status['user']['location']\n",
+ " except TypeError:\n",
+ " location = ''\n",
+ " #\n",
+ " new_entry.append(location)\n",
+ "\n",
+ " #now append a row to the dataframe\n",
+ " single_tweet_df = pd.DataFrame([new_entry], columns=self.cols)\n",
+ " df = df.append(single_tweet_df, ignore_index=True)\n",
+ "\n",
+ " if not csvfile is None:\n",
+ " #save it to file\n",
+ " df.to_csv(csvfile, columns=self.cols, index=False, encoding=\"utf-8\")\n",
+ " \n",
+ " return df\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Search twitter and fetch data example"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "covid_keywords = '#COVID19 OR #COVID19Africa' #hashtag based search\n",
+ "tweets_file = 'covid19_23june2020.json'\n",
+ "\n",
+ "#get data on keywords\n",
+ "ts = tweetsearch()\n",
+ "# df = ts.get_tweets(covid_keywords, csvfile=tweets_file) #you saved the \n",
+ "df = ts.get_tweets(covid_keywords)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Stream data and save it to file\n",
+ "In the above we saw how to search and fetch data, below we will see how we will stream data from twitter. Make sure you understand the difference between search and stream features of twitter api.\n",
+ "\n",
+ "### **SAME TASK AS ABOVE**\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "r6lcy009rX_e"
+ },
+ "outputs": [],
+ "source": [
+ "#This is a basic listener that writes received tweets to file.\n",
+ "class StdOutListener(StreamListener):\n",
+ "\n",
+ " def __init__(self,fhandle, stop_at = 1000):\n",
+ " self.tweet_counter = 0\n",
+ " self.stop_at = stop_at\n",
+ " self.fhandle = fhandle\n",
+ " \n",
+ " \n",
+ " def on_data(self, data):\n",
+ " self.fhandle.write(data)\n",
+ " \n",
+ " #stop if enough tweets are obtained\n",
+ " self.tweet_counter += 1 \n",
+ " if self.tweet_counter < self.stop_at: \n",
+ " return True\n",
+ " else:\n",
+ " print('Max number of tweets reached: #tweets = ' + str(self.tweet_counter))\n",
+ " return False\n",
+ "\n",
+ " def on_error(self, status):\n",
+ " print (status)\n",
+ "\n",
+ "def stream_tweet_data(filename='data/tweets.json',\n",
+ " keywords=['COVID19Africa','COVID19Ethiopia'],\n",
+ " is_async=False):\n",
+ " # tweet topics to use as a filter. The tweets downloaded\n",
+ " # will have one of the topics in their text or hashtag \n",
+ "\n",
+ " print('saving data to file: ',filename)\n",
+ "\n",
+ " #print the tweet topics \n",
+ " print('TweetKeywords are: ',keywords)\n",
+ " print('For testing case, please interupt the downloading process using ctrl+x after about 5 mins ')\n",
+ " print('To keep streaming in the background, pass is_async=True')\n",
+ "\n",
+ " #Variables that contains the user credentials to access Twitter API \n",
+ " consumer_key = os.environ.get('TWITTER_API_KEY')\n",
+ " consumer_secret = os.environ.get('TWITTER_API_SECRET')\n",
+ " access_token = os.environ.get('TWITTER_ACCESS_TOKEN')\n",
+ " access_token_secret = os.environ.get('TWITTER_ACCESS_TOKEN_SECRET')\n",
+ "\n",
+ " #open file \n",
+ " fhandle=open(filename,'w')\n",
+ "\n",
+ " #This handles Twitter authetification and the connection to Twitter Streaming API\n",
+ " l = StdOutListener(fhandle)\n",
+ " auth = OAuthHandler(consumer_key, consumer_secret)\n",
+ " auth.set_access_token(access_token, access_token_secret)\n",
+ "\n",
+ " stream = Stream(auth, l)\n",
+ "\n",
+ " #This line filter Twitter Streams to capture data by the keywords: first argument to this code\n",
+ " stream.filter(track=keywords,is_async=is_async)\n",
+ "\n",
+ " return None\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Use case of the above code"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "F8tcPcSMrNLL",
+ "outputId": "d7abd9c2-065c-40e8-f71b-e808d985c364"
+ },
+ "outputs": [],
+ "source": [
+ "tweets_file = 'data/covid19_23june2020.json'\n",
+ "stream_tweet_data(filename=tweets_file,keywords=['covid19','#COVID19Africa']) #\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Filter twitter data and do basic analysis\n",
+ "**Extend it to gain more insight**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "F8tcPcSMrNLL",
+ "outputId": "d7abd9c2-065c-40e8-f71b-e808d985c364"
+ },
+ "outputs": [],
+ "source": [
+ "tweets_data = []\n",
+ "for line in open(tweets_file, \"r\"):\n",
+ " try:\n",
+ " tweet = json.loads(line)\n",
+ " x=tweet['text']\n",
+ " tweets_data.append(tweet)\n",
+ " except:\n",
+ " continue\n",
+ "\n",
+ "\n",
+ "print('saved numbers of tweets: ', len(tweets_data))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "hlFKyGnYrNLX"
+ },
+ "outputs": [],
+ "source": [
+ "tweets = pd.DataFrame(columns=['text','lang','country'])\n",
+ "\n",
+ "tweets['text'] = list(map(lambda tweet: tweet['text'], tweets_data))\n",
+ "tweets['lang'] = list(map(lambda tweet: tweet['lang'], tweets_data))\n",
+ "tweets['country'] = list(map(lambda tweet: tweet['place']['country'] if tweet['place'] != None else None, \n",
+ " tweets_data))\n",
+ "\n",
+ "\n",
+ "tweets_by_lang = tweets['lang'].value_counts()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "aEPPoCBtrNLd",
+ "outputId": "bfe0cee1-814d-4f30-f47d-205218b3adc9"
+ },
+ "outputs": [],
+ "source": [
+ "fig, ax = plt.subplots()\n",
+ "ax.tick_params(axis='x', labelsize=15)\n",
+ "ax.tick_params(axis='y', labelsize=10)\n",
+ "ax.set_xlabel('Languages', fontsize=15)\n",
+ "ax.set_ylabel('Number of tweets' , fontsize=15)\n",
+ "ax.set_title('Top 5 languages', fontsize=15, fontweight='bold')\n",
+ "tweets_by_lang[:5].plot(ax=ax, kind='bar', color='red')\n",
+ "\n",
+ "tweets_by_country = tweets['country'].value_counts()\n",
+ "\n",
+ "fig, ax = plt.subplots()\n",
+ "ax.tick_params(axis='x', labelsize=15)\n",
+ "ax.tick_params(axis='y', labelsize=10)\n",
+ "ax.set_xlabel('Countries', fontsize=15)\n",
+ "ax.set_ylabel('Number of tweets' , fontsize=15)\n",
+ "ax.set_title('Top 5 countries', fontsize=15, fontweight='bold')\n",
+ "tweets_by_country[:5].plot(ax=ax, kind='bar', color='blue')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "IQYFg6t5rNLj"
+ },
+ "source": [
+ "# Hashtag histogram. \n",
+ "\n",
+ "## Please write code that will help you answer the following questions\n",
+ " 1) What is the most used hashtag?\n",
+ " \n",
+ " 2) What is the most used referenced username?\n",
+ " \n",
+ " 3) What is the most retweeted tweet?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "p0VUwYR8rNLk"
+ },
+ "outputs": [],
+ "source": [
+ "# getting the most used hashtag\n",
+ "tags = df['hashtags']\n",
+ "hashtag_counts = tags.value_counts()\n",
+ "print(\"The most used hashtag is: \", tags[hashtag_counts.iloc[0]])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# getting the most used referenced username\n",
+ "names = df['user_mentions']\n",
+ "username_counts = names.value_counts()\n",
+ "print(\"The most used referenced username is: \", names[username_counts.iloc[0]])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tweets = df['original_text']\n",
+ "retweets = df['retweet_count']\n",
+ "print(\"The most retweeted tweet is: \", tweets[retweet.max()])"
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "name": "vistweet.ipynb",
+ "provenance": []
+ },
+ "hide_input": false,
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.6"
+ },
+ "latex_envs": {
+ "bibliofile": "biblio.bib",
+ "cite_by": "apalike",
+ "current_citInitial": 1,
+ "eqLabelWithNumbers": true,
+ "eqNumInitial": 0
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/twitter_mining/twitter_mining.ipynb b/twitter_mining/twitter_mining.ipynb
deleted file mode 100644
index a6460ee..0000000
--- a/twitter_mining/twitter_mining.ipynb
+++ /dev/null
@@ -1,635 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "rziXcUSBskS3"
- },
- "outputs": [],
- "source": [
- "#from google.colab import drive\n",
- "#drive.mount('/content/drive')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Twitter Developer Account\n",
- "In order to use Twitter’s API, we have to create a developer account on the Twitter apps site.\n",
- " * Log in or make a Twitter account at https://apps.twitter.com/.\n",
- " * Create a new app (button on the top right)\n",
- " \n",
- "
\n",
- "\n",
- "Fill in the app creation page with a unique name, a website name (use a placeholder website if you don’t have one), and a project description. Accept the terms and conditions and proceed to the next page.\n",
- "\n",
- "Once your project has been created, click on the “Keys and Access Tokens” tab. You should now be able to see your consumer secret and consumer key.\n",
- "\n",
- "
\n",
- "\n",
- "You’ll also need a pair of access tokens. Scroll down and request those tokens. The page should refresh, and you should now have an access token and access token secret.\n",
- "\n",
- "
\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Import necessary modules"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 32,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "UI2fIQFxrNLB",
- "run_control": {
- "frozen": false,
- "read_only": false
- }
- },
- "outputs": [],
- "source": [
- "import sys\n",
- "import os\n",
- "import json\n",
- "import pandas as pd\n",
- "import matplotlib.pyplot as plt"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "3pf5Xapqrq0M"
- },
- "outputs": [],
- "source": [
- "#Import the necessary methods from tweepy library \n",
- "\n",
- "#install tweepy if you don't have it\n",
- "#!pip install tweepy\n",
- "import tweepy\n",
- "from tweepy.streaming import StreamListener\n",
- "from tweepy import OAuthHandler\n",
- "from tweepy import Stream\n",
- "\n",
- "#sentiment analysis package\n",
- "#!pip install textblob\n",
- "from textblob import TextBlob\n",
- "\n",
- "#general text pre-processor\n",
- "#!pip install nltk\n",
- "import nltk\n",
- "from nltk.corpus import stopwords\n",
- "\n",
- "#tweet pre-processor \n",
- "#!pip install tweet-preprocessor\n",
- "import preprocessor as ppr"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Starting code\n",
- "Below we define some starting codes (python classes and function) to illustrate and assist on how to fetch data from twitter and analyse them. \n",
- "\n",
- "### **Your task is**\n",
- "1. Go through the code and understand it. Know what each function does\n",
- "2. If you find error, fix it. Ask for help in the slack channel if you find serious mistake\n",
- "3. Extend the code such that it will be useful for topics you choose to analyse\n",
- "4. Make nice plots and share your finding (e.g. insight on the main covid19 twitter converstions about your country)\n",
- "5. Submit what ever you managed to do by Wednesday morning. But you should keep using what you build to write blogs, share on facebook, etc. \n",
- "\n",
- "\n",
- "[Reference used to build some of the functions here](https://towardsdatascience.com/extracting-twitter-data-pre-processing-and-sentiment-analysis-using-python-3-0-7192bd8b47cf)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 38,
- "metadata": {},
- "outputs": [],
- "source": [
- "class tweetsearch():\n",
- " '''\n",
- " This is a basic class to search and download twitter data.\n",
- " You can build up on it to extend the functionalities for more \n",
- " sophisticated analysis.\n",
- " \n",
- " '''\n",
- " def __init__(self,cols=None,auth=None):\n",
- " #\n",
- " if not cols is None:\n",
- " self.cols = cols\n",
- " else:\n",
- " self.cols = ['id', 'created_at', 'source', 'original_text','clean_text', \n",
- " 'sentiment','polarity','subjectivity', 'lang',\n",
- " 'favorite_count', 'retweet_count', 'original_author', \n",
- " 'possibly_sensitive', 'hashtags',\n",
- " 'user_mentions', 'place', 'place_coord_boundaries']\n",
- " \n",
- " if auth is None:\n",
- " #Variables that contains the user credentials to access Twitter API \n",
- " consumer_key = os.environ.get('TWITTER_API_KEY')\n",
- " consumer_secret = os.environ.get('TWITTER_API_SECRET')\n",
- " access_token = os.environ.get('TWITTER_ACCESS_TOKEN')\n",
- " access_token_secret = os.environ.get('TWITTER_ACCESS_TOKEN_SECRET')\n",
- "\n",
- "\n",
- " #This handles Twitter authetification and the connection to Twitter \n",
- " #Streaming API\n",
- " auth = OAuthHandler(consumer_key, consumer_secret)\n",
- " auth.set_access_token(access_token, access_token_secret)\n",
- " \n",
- "\n",
- " # \n",
- " self.auth = auth\n",
- " self.api = tweepy.API(auth) \n",
- " \n",
- "\n",
- " def clean_tweets(self,twitter_text):\n",
- "\n",
- " #use pre processor\n",
- " tweet = p.clean(twitter_text)\n",
- "\n",
- " #HappyEmoticons\n",
- " emoticons_happy = set([\n",
- " ':-)', ':)', ';)', ':o)', ':]', ':3', ':c)', ':>', '=]', '8)', '=)', ':}',\n",
- " ':^)', ':-D', ':D', '8-D', '8D', 'x-D', 'xD', 'X-D', 'XD', '=-D', '=D',\n",
- " '=-3', '=3', ':-))', \":'-)\", \":')\", ':*', ':^*', '>:P', ':-P', ':P', 'X-P',\n",
- " 'x-p', 'xp', 'XP', ':-p', ':p', '=p', ':-b', ':b', '>:)', '>;)', '>:-)',\n",
- " '<3'\n",
- " ])\n",
- "\n",
- " # Sad Emoticons\n",
- " emoticons_sad = set([\n",
- " ':L', ':-/', '>:/', ':S', '>:[', ':@', ':-(', ':[', ':-||', '=L', ':<',\n",
- " ':-[', ':-<', '=\\\\', '=/', '>:(', ':(', '>.<', \":'-(\", \":'(\", ':\\\\', ':-c',\n",
- " ':c', ':{', '>:\\\\', ';('\n",
- " ])\n",
- "\n",
- " #Emoji patterns\n",
- " emoji_pattern = re.compile(\"[\"\n",
- " u\"\\U0001F600-\\U0001F64F\" # emoticons\n",
- " u\"\\U0001F300-\\U0001F5FF\" # symbols & pictographs\n",
- " u\"\\U0001F680-\\U0001F6FF\" # transport & map symbols\n",
- " u\"\\U0001F1E0-\\U0001F1FF\" # flags (iOS)\n",
- " u\"\\U00002702-\\U000027B0\"\n",
- " u\"\\U000024C2-\\U0001F251\"\n",
- " \"]+\", flags=re.UNICODE)\n",
- "\n",
- " #combine sad and happy emoticons\n",
- " emoticons = emoticons_happy.union(emoticons_sad)\n",
- "\n",
- " stop_words = set(stopwords.words('english'))\n",
- " word_tokens = word_tokenize(tweet)\n",
- " #after tweepy preprocessing the colon symbol left remain after \n",
- " #removing mentions\n",
- " tweet = re.sub(r':', '', tweet)\n",
- " tweet = re.sub(r'…', '', tweet)\n",
- "\n",
- " #replace consecutive non-ASCII characters with a space\n",
- " tweet = re.sub(r'[^\\x00-\\x7F]+',' ', tweet)\n",
- "\n",
- " #remove emojis from tweet\n",
- " tweet = emoji_pattern.sub(r'', tweet)\n",
- "\n",
- " #filter using NLTK library append it to a string\n",
- " filtered_tweet = [w for w in word_tokens if not w in stop_words]\n",
- "\n",
- " #looping through conditions\n",
- " filtered_tweet = [] \n",
- " for w in word_tokens:\n",
- " #check tokens against stop words , emoticons and punctuations\n",
- " if w not in stop_words and w not in emoticons and w not in string.punctuation:\n",
- " filtered_tweet.append(w)\n",
- "\n",
- " return ' '.join(filtered_tweet) \n",
- "\n",
- " def get_tweets(self, keyword, csvfile=None):\n",
- " \n",
- " \n",
- " df = pd.DataFrame(columns=self.cols)\n",
- " \n",
- " if not csvfile is None:\n",
- " #If the file exists, then read the existing data from the CSV file.\n",
- " if os.path.exists(csvfile):\n",
- " df = pd.read_csv(csvfile, header=0)\n",
- " \n",
- "\n",
- " #page attribute in tweepy.cursor and iteration\n",
- " for page in tweepy.Cursor(api.search, q=keyword,count=200, include_rts=False):\n",
- "\n",
- "\n",
- " for status in page:\n",
- " \n",
- " new_entry = []\n",
- " status = status._json\n",
- " \n",
- " #filter by language\n",
- " if status['lang'] != 'en':\n",
- " continue\n",
- "\n",
- " \n",
- " #if this tweet is a retweet update retweet count\n",
- " if status['created_at'] in df['created_at'].values:\n",
- " i = df.loc[df['created_at'] == status['created_at']].index[0]\n",
- " #\n",
- " cond1 = status['favorite_count'] != df.at[i, 'favorite_count']\n",
- " cond2 = status['retweet_count'] != df.at[i, 'retweet_count']\n",
- " if cond1 or cond2:\n",
- " df.at[i, 'favorite_count'] = status['favorite_count']\n",
- " df.at[i, 'retweet_count'] = status['retweet_count']\n",
- " continue\n",
- "\n",
- " #calculate sentiment\n",
- " blob = TextBlob(filtered_tweet)\n",
- " Sentiment = blob.sentiment \n",
- " polarity = Sentiment.polarity\n",
- " subjectivity = Sentiment.subjectivity\n",
- "\n",
- " new_entry += [status['id'], status['created_at'],\n",
- " status['source'], status['text'],filtered_tweet, \n",
- " Sentiment,polarity,subjectivity, status['lang'],\n",
- " status['favorite_count'], status['retweet_count']]\n",
- "\n",
- " new_entry.append(status['user']['screen_name'])\n",
- "\n",
- " try:\n",
- " is_sensitive = status['possibly_sensitive']\n",
- " except KeyError:\n",
- " is_sensitive = None\n",
- "\n",
- " new_entry.append(is_sensitive)\n",
- "\n",
- " hashtags = \", \".join([hashtag_item['text'] for \\\n",
- " hashtag_item in status['entities']['hashtags']])\n",
- " new_entry.append(hashtags) #append the hashtags\n",
- "\n",
- " #\n",
- " mentions = \", \".join([mention['screen_name'] for \\\n",
- " mention in status['entities']['user_mentions']])\n",
- " new_entry.append(mentions) #append the user mentions\n",
- "\n",
- " try:\n",
- " xyz = status['place']['bounding_box']['coordinates']\n",
- " coordinates = [coord for loc in xyz for coord in loc]\n",
- " except TypeError:\n",
- " coordinates = None\n",
- " #\n",
- " new_entry.append(coordinates)\n",
- "\n",
- " try:\n",
- " location = status['user']['location']\n",
- " except TypeError:\n",
- " location = ''\n",
- " #\n",
- " new_entry.append(location)\n",
- "\n",
- " #now append a row to the dataframe\n",
- " single_tweet_df = pd.DataFrame([new_entry], columns=self.cols)\n",
- " df = df.append(single_tweet_df, ignore_index=True)\n",
- "\n",
- " if not csvfile is None:\n",
- " #save it to file\n",
- " df.to_csv(csvfile, columns=self.cols, index=False, encoding=\"utf-8\")\n",
- " \n",
- " return df\n",
- "\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Search twitter and fetch data example"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "covid_keywords = '#COVID19 OR #COVID19Africa' #hashtag based search\n",
- "tweets_file = 'data/ethiopia_covid19_23june2020.json'\n",
- "\n",
- "#get data on keywords\n",
- "ts = tweetsearch()\n",
- "df = ts.get_tweets(covid_keywords, csvfile=tweets_file) #you saved the "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Stream data and save it to file\n",
- "In the above we saw how to search and fetch data, below we will see how we will stream data from twitter. Make sure you understand the difference between search and stream features of twitter api.\n",
- "\n",
- "### **SAME TASK AS ABOVE**\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 41,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "r6lcy009rX_e"
- },
- "outputs": [],
- "source": [
- "#This is a basic listener that writes received tweets to file.\n",
- "class StdOutListener(StreamListener):\n",
- "\n",
- " def __init__(self,fhandle, stop_at = 1000):\n",
- " self.tweet_counter = 0\n",
- " self.stop_at = stop_at\n",
- " self.fhandle = fhandle\n",
- " \n",
- " \n",
- " def on_data(self, data):\n",
- " self.fhandle.write(data)\n",
- " \n",
- " #stop if enough tweets are obtained\n",
- " self.tweet_counter += 1 \n",
- " if self.tweet_counter < self.stop_at: \n",
- " return True\n",
- " else:\n",
- " print('Max number of tweets reached: #tweets = ' + str(self.tweet_counter))\n",
- " return False\n",
- "\n",
- " def on_error(self, status):\n",
- " print (status)\n",
- "\n",
- "def stream_tweet_data(filename='data/tweets.json',\n",
- " keywords=['COVID19Africa','COVID19Ethiopia'],\n",
- " is_async=False):\n",
- " # tweet topics to use as a filter. The tweets downloaded\n",
- " # will have one of the topics in their text or hashtag \n",
- "\n",
- " print('saving data to file: ',filename)\n",
- "\n",
- " #print the tweet topics \n",
- " print('TweetKeywords are: ',keywords)\n",
- " print('For testing case, please interupt the downloading process using ctrl+x after about 5 mins ')\n",
- " print('To keep streaming in the background, pass is_async=True')\n",
- "\n",
- " #Variables that contains the user credentials to access Twitter API \n",
- " consumer_key = os.environ.get('TWITTER_API_KEY')\n",
- " consumer_secret = os.environ.get('TWITTER_API_SECRET')\n",
- " access_token = os.environ.get('TWITTER_ACCESS_TOKEN')\n",
- " access_token_secret = os.environ.get('TWITTER_ACCESS_TOKEN_SECRET')\n",
- "\n",
- " #open file \n",
- " fhandle=open(filename,'w')\n",
- "\n",
- " #This handles Twitter authetification and the connection to Twitter Streaming API\n",
- " l = StdOutListener(fhandle)\n",
- " auth = OAuthHandler(consumer_key, consumer_secret)\n",
- " auth.set_access_token(access_token, access_token_secret)\n",
- "\n",
- " stream = Stream(auth, l)\n",
- "\n",
- " #This line filter Twitter Streams to capture data by the keywords: first argument to this code\n",
- " stream.filter(track=keywords,is_async=is_async)\n",
- "\n",
- " return None\n",
- "\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Use case of the above code"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 42,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "F8tcPcSMrNLL",
- "outputId": "d7abd9c2-065c-40e8-f71b-e808d985c364"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "saving data to file: data/covid19_23june2020.json\n",
- "TweetKeywords are: ['covid19', '#COVID19Africa']\n",
- "For testing case, please interupt the downloading process using ctrl+x after about 5 mins \n",
- "To keep streaming in the background, pass is_async=True\n",
- "Max number of tweets reached: #tweets = 1000\n"
- ]
- }
- ],
- "source": [
- "tweets_file = 'data/covid19_23june2020.json'\n",
- "stream_tweet_data(filename=tweets_file,keywords=['covid19','#COVID19Africa']) #\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Filter twitter data and do basic analysis\n",
- "**Extend it to gain more insight**"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 27,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "F8tcPcSMrNLL",
- "outputId": "d7abd9c2-065c-40e8-f71b-e808d985c364"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "saved numbers of tweets: 998\n"
- ]
- }
- ],
- "source": [
- "tweets_data = []\n",
- "for line in open(tweets_file, \"r\"):\n",
- " try:\n",
- " tweet = json.loads(line)\n",
- " x=tweet['text']\n",
- " tweets_data.append(tweet)\n",
- " except:\n",
- " continue\n",
- "\n",
- "\n",
- "print('saved numbers of tweets: ', len(tweets_data))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 30,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "hlFKyGnYrNLX"
- },
- "outputs": [],
- "source": [
- "tweets = pd.DataFrame(columns=['text','lang','country'])\n",
- "\n",
- "tweets['text'] = list(map(lambda tweet: tweet['text'], tweets_data))\n",
- "tweets['lang'] = list(map(lambda tweet: tweet['lang'], tweets_data))\n",
- "tweets['country'] = list(map(lambda tweet: tweet['place']['country'] if tweet['place'] != None else None, \n",
- " tweets_data))\n",
- "\n",
- "\n",
- "tweets_by_lang = tweets['lang'].value_counts()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 31,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "aEPPoCBtrNLd",
- "outputId": "bfe0cee1-814d-4f30-f47d-205218b3adc9"
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- ""
- ]
- },
- "execution_count": 31,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "fig, ax = plt.subplots()\n",
- "ax.tick_params(axis='x', labelsize=15)\n",
- "ax.tick_params(axis='y', labelsize=10)\n",
- "ax.set_xlabel('Languages', fontsize=15)\n",
- "ax.set_ylabel('Number of tweets' , fontsize=15)\n",
- "ax.set_title('Top 5 languages', fontsize=15, fontweight='bold')\n",
- "tweets_by_lang[:5].plot(ax=ax, kind='bar', color='red')\n",
- "\n",
- "tweets_by_country = tweets['country'].value_counts()\n",
- "\n",
- "fig, ax = plt.subplots()\n",
- "ax.tick_params(axis='x', labelsize=15)\n",
- "ax.tick_params(axis='y', labelsize=10)\n",
- "ax.set_xlabel('Countries', fontsize=15)\n",
- "ax.set_ylabel('Number of tweets' , fontsize=15)\n",
- "ax.set_title('Top 5 countries', fontsize=15, fontweight='bold')\n",
- "tweets_by_country[:5].plot(ax=ax, kind='bar', color='blue')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "IQYFg6t5rNLj"
- },
- "source": [
- "# Hashtag histogram. \n",
- "\n",
- "## Please write code that will help you answer the following questions\n",
- " 1) What is the most used hashtag?\n",
- " \n",
- " 2) What is the most used referenced username?\n",
- " \n",
- " 3) What is the most retweeted tweet?"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "p0VUwYR8rNLk"
- },
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "colab": {
- "collapsed_sections": [],
- "name": "vistweet.ipynb",
- "provenance": []
- },
- "hide_input": false,
- "kernelspec": {
- "display_name": "Python [conda env:.conda-10x]",
- "language": "python",
- "name": "conda-env-.conda-10x-py"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.3"
- },
- "latex_envs": {
- "bibliofile": "biblio.bib",
- "cite_by": "apalike",
- "current_citInitial": 1,
- "eqLabelWithNumbers": true,
- "eqNumInitial": 0
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}