loading...
Cover image for Building the frontend - Part III (Live tweet sentiment analysis)

Building the frontend - Part III (Live tweet sentiment analysis)

amalshaji profile image Amal Shaji Originally published at amalshaji.wtf ・3 min read

This is the final article of the series Live tweet sentiment analysis. In the previous article, we saw how to create an API to serve our predictions. This article will cover how to build a frontend.

Normally, frontends are written in Reactjs or Vuejs(these are the only ones I know). But I'm a big fan of Python and I would like to build the frontend using python. Streamlit helps you build quickly an interface to serve your machine learning apps.

Even though the backend isn't necessary here(streamlit could've handled the prediction part also), it is important to shift the heavy-duty task to a backend that can spawn multiple workers(gunicorn instead of uvicorn) and implement better job-queuing and async methods.

Building the frontend

make sure you are in frontend directory

Streaming the hashtags

# tweets.py

import tweepy as tw

auth = tw.OAuthHandler(config.consumer_key, config.consumer_secret)
auth.set_access_token(config.access_token, config.access_token_secret)
api = tw.API(auth, wait_on_rate_limit=True)

tweets = tw.Cursor(api.search, q=hashtag, lang="en", since=date).items()

tweets object will hold the stream of tweets retrieved from twitter since the date given.

Building the UI

# tweets.py

import streamlit as st

st.title("Twitter Live Sentiment Visualizer (beta)")

# design the sibar elements
st.sidebar.title("Enter a hashtag")
hashtag = st.sidebar.text_input("hashtag", "trump")
date = st.sidebar.date_input("Analyse tweets from", datetime.date(2020, 8, 1))

# if "Live analysis" is clicked
if st.sidebar.button("Live analysis", key="analyse"):
    pos_count = 0
    neg_count = 0
    tweets_count = 0

    st.subheader(f"Analysing #{hashtag} from {date}")
    d = {"Positive": [pos_count], "Negative": [neg_count]}
    df = pd.DataFrame(data=d)

    hashtag = f"#{hashtag}"

    # get the tweets
    with st.spinner("Getting tweets..."):
        tweets = tw.Cursor(api.search, q=hashtag, lang="en", since=date).items()

    total_tweets = st.empty()
    pos_tweets = st.empty()
    neg_tweets = st.empty()

    sentiments = ["Positive", "Negative"]
    chart = st.line_chart(df)

    # iterate through all the tweets
    for idx, tweet in enumerate(tweets):
        tweets_count += 1
        output = requests.post("http://backend:8000/api", json={"tweet": tweet.text})
        output = output.content.decode("utf8")
        output = json.loads(output).get("sentiment")

        # count the number of each sentiment
        if output == "Negative":
            neg_count += 1
        elif output == "Positive":
            pos_count += 1

        total_tweets.text("Tweets Analysed: %d" % tweets_count)
        pos_tweets.text("Positive tweets: %d" % pos_count)
        neg_tweets.text("Negative tweets: %d" % neg_count)

        # update the DataFrame
        df2 = pd.DataFrame({"Positive": [pos_count], "Negative": [neg_count]})
        df.update(df2)
        # draw the cumulative chart
        chart.add_rows(df)

    if neg_count == 0 and pos_count == 0:
        st.warning(f"No Tweets Found on {hashtag}")
    else:
        st.success("Tweets classified")

Basically what this does is get the tweets from twitter API, send it to the backend for inference and plot the cumulative of each sentiment. You could even change this to a seven tweets moving averageπŸ˜‰. One important thing to notice is that

output = requests.post("http://backend:8000/api", json={"tweet": tweet.text})

we used backend:8000 instead of localhost:8000. This is because the service that we build for the backend will be called backend and the host will be backend instead of localhost

Dockerize the frontend

FROM python:3.8-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install -r requirements.txt

EXPOSE 8501

COPY . .

CMD ["streamlit", "run", "tweets.py"]

# nothing fancy here

Now let's bind everything to build our app

cd .. to move back to the root directory. Your project structure should look like this.

❯ tree .
.
β”œβ”€β”€ LICENSE
β”œβ”€β”€ README.md
β”œβ”€β”€ backend
β”‚Β Β  β”œβ”€β”€ Dockerfile
β”‚Β Β  β”œβ”€β”€ classify.py
β”‚Β Β  β”œβ”€β”€ models
β”‚Β Β  β”‚Β Β  └── sentiment_model.pickle
β”‚Β Β  β”œβ”€β”€ requirements.txt
β”‚Β Β  β”œβ”€β”€ server.py
β”‚Β Β  └── utils.py
β”œβ”€β”€ docker-compose.yml
└── frontend
    β”œβ”€β”€ Dockerfile
    β”œβ”€β”€ config.py
    β”œβ”€β”€ requirements.txt
    └── tweets.py

Docker-compose

Create a docker-compose.yml in the root directory

version: "3"
services:
  frontend:
    build: frontend
    ports:
      - "8501:8501"
    depends_on:
      - backend

  backend:
    build: backend
    ports:
      - "8000:8000"
  • version: Compose file version 3
  • services: here we define the services(frontend and backend)
  • depends_on: since frontend depends on backend
❯ docker-compose build
❯ docker-compose up -d
Starting sentwitter_backend_1 ... done
Starting sentwitter_frontend_1 ... done
# -d for detach mode (it'll run in background)

Navigate to localhost:8501 and you should see your app running. Let's do a quick run on #2Articles1Week.

2articles1week

That's a lot of red flags right there for the model. Nobody will write negatively about 2articles1week. It's just the model is so bad.

Stop the container

❯ docker-compose down
Stopping sentwitter_frontend_1 ... done
Stopping sentwitter_backend_1  ... done
Removing sentwitter_frontend_1 ... done
Removing sentwitter_backend_1  ... done
Removing network sentwitter_default

Feel free to drop a star if you liked this.

GitHub Repo: sentwitter

Support for more contentπŸ”₯πŸ”₯πŸ”₯

Reference

Discussion

pic
Editor guide