Does "the twitter ratio" apply to the #rstats community?

[This article was first published on d4tagirl, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Not long ago I came across a FiveThirtyEight post called “The Worst Tweeter In Politics Isn’t Trump”. Well, it was a long time ago actually, but this project was laying in my computer for a while ¯\(ツ)/¯. They gathered some resources from the media discussing that tweets leading to more replies than likes and retweets are the ones that make the community angry. This phenomenon is known as “The Ratio” as Luke O’Neil wrote recently in Esquire.

FiveThirtyEight used a ternary plot to illustrate the proportion of replies, retweets and likes of every Trump tweet. In this post I’m going to plot tweets with the rstats hashtag, suspecting that the ones that have a higher ratio of replies might be an exception to this rule since conversations tend to be pretty friendly in this community. But let’s find out!

Disclaimer

It wasn’t until I had this post ready for publishing that I realized the replies the media were discussing were the direct ones, without considering the replies of the replies, so I just invented a new ratio 😱 I spent a great amount of extra work to consider all the replies (direct and indirect ones) but I liked the way it turned out and the way I had to solve some problems, so I’ll just stick with my personal definition of the ratio, knowing it’s not what it’s supposed to be 💁

Retrieving the data

I’m becoming more and more fan of the rtweet package, built and maintained by Michael W. Kearney. It’s the way to go when you want to interact with Twitter’s API using R. I fetch some tweets with #rstats to analize!

<span class="n">library</span><span class="p">(</span><span class="n">rtweet</span><span class="p">)</span><span class="w">
</span><span class="n">library</span><span class="p">(</span><span class="n">dplyr</span><span class="p">)</span><span class="w">
</span><span class="n">tweets_rstats</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">search_tweets</span><span class="p">(</span><span class="n">q</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"#rstats"</span><span class="p">,</span><span class="w">
                                </span><span class="n">include_rts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">FALSE</span><span class="p">,</span><span class="w">
                                </span><span class="n">n</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">300</span><span class="p">)</span><span class="w">

</span><span class="n">tweets_rstats</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">tweets_rstats</span><span class="w"> </span><span class="o">%>%</span><span class="w">
  </span><span class="n">distinct</span><span class="p">()</span><span class="w">
</span>

\n \n \n
<\/th>\n screen_name<\/th>\n text<\/th>\n <\/tr>\n <\/thead>\n<\/table>","options":{"pageLength":3,"order":[],"autoWidth":false,"orderClasses":false,"columnDefs":[{"orderable":false,"targets":0}],"lengthMenu":[3,10,25,50,100],"rowCallback":"function(row, data) {\nvar value=data[1]; $(this.api().cell(row, 1).node()).css({'font-size':'15px'});\nvar value=data[2]; $(this.api().cell(row, 2).node()).css({'font-size':'15px'});\nvar value=data[3]; $(this.api().cell(row, 3).node()).css({'font-size':'15px'});\n}"}},"evals":["options.rowCallback"],"jsHooks":[]}

I keep only the original tweets with at least two likes because I want to keep relevant tweets. This is probably too arbitrary and surely can be improved, but here I go.

<span class="n">orig_tweets</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">tweets_rstats</span><span class="w"> </span><span class="o">%>%</span><span class="w"> 
  </span><span class="n">filter</span><span class="p">(</span><span class="nf">is.na</span><span class="p">(</span><span class="n">reply_to_status_id</span><span class="p">),</span><span class="w">
         </span><span class="n">favorite_count</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="m">1</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w">    
  </span><span class="n">select</span><span class="p">(</span><span class="n">status_id</span><span class="p">,</span><span class="w"> </span><span class="n">screen_name</span><span class="p">,</span><span class="w"> </span><span class="n">text</span><span class="p">,</span><span class="w"> </span><span class="n">favorite_count</span><span class="p">,</span><span class="w"> </span><span class="n">retweet_count</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w">
  </span><span class="n">distinct</span><span class="p">()</span><span class="w">
</span>

\n \n \n
<\/th>\n status_id<\/th>\n screen_name<\/th>\n text<\/th>\n <\/tr>\n <\/thead>\n<\/table>","options":{"pageLength":3,"order":[],"autoWidth":false,"orderClasses":false,"columnDefs":[{"orderable":false,"targets":0}],"lengthMenu":[3,10,25,50,100],"rowCallback":"function(row, data) {\nvar value=data[1]; $(this.api().cell(row, 1).node()).css({'font-size':'15px'});\nvar value=data[2]; $(this.api().cell(row, 2).node()).css({'font-size':'15px'});\nvar value=data[3]; $(this.api().cell(row, 3).node()).css({'font-size':'15px'});\n}"}},"evals":["options.rowCallback"],"jsHooks":[]}

I already have the number of retweets and the number of likes of each original tweet, but not the number of replies. To build the ternary plot (or pyramid as I prefer to call it) I need the number of replies as well. As the API doesn’t have a direct method to do this, so I have to do it by hand.

Here comes the purrr part. The purrr package is receiving a lot of love this year: there is a group sharing the #purrrResolution, courtesy of Isabella Ghement that you can join:

And Colin Fay created the Twitter collection #RStats — Your daily dose of #purrr with great tips!

I collect all the mentions to all screen_names in the orig_tweets dataframe. I use distinct(screen_names) because I don’t want to call the API more than once for every screen_name.

<span class="n">library</span><span class="p">(</span><span class="n">purrr</span><span class="p">)</span><span class="w">
</span><span class="n">library</span><span class="p">(</span><span class="n">tidyr</span><span class="p">)</span><span class="w">

</span><span class="n">orig_tweets_mentions</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">orig_tweets</span><span class="w"> </span><span class="o">%>%</span><span class="w">
  </span><span class="n">distinct</span><span class="p">(</span><span class="n">screen_name</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w">           
  </span><span class="n">mutate</span><span class="p">(</span><span class="n">query</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">paste0</span><span class="p">(</span><span class="s2">"@"</span><span class="p">,</span><span class="w"> </span><span class="n">screen_name</span><span class="p">,</span><span class="w"> </span><span class="s2">" OR "</span><span class="p">,</span><span class="w"> </span><span class="s2">"to:"</span><span class="p">,</span><span class="w"> </span><span class="n">screen_name</span><span class="p">,</span><span class="w"> </span><span class="s2">" OR "</span><span class="p">,</span><span class="w"> </span><span class="n">screen_name</span><span class="p">))</span><span class="w"> </span><span class="o">%>%</span><span class="w">
  </span><span class="n">mutate</span><span class="p">(</span><span class="n">tweets</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pmap</span><span class="p">(</span><span class="nf">list</span><span class="p">(</span><span class="n">q</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">.</span><span class="o">$</span><span class="n">query</span><span class="p">,</span><span class="w">
                            </span><span class="n">n</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">1000</span><span class="p">,</span><span class="w">
                            </span><span class="n">retryonratelimit</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">TRUE</span><span class="p">),</span><span class="w">
                       </span><span class="n">rtweet</span><span class="o">::</span><span class="n">search_tweets</span><span class="p">))</span><span class="w"> </span><span class="o">%>%</span><span class="w">
  </span><span class="n">select</span><span class="p">(</span><span class="n">tweets</span><span class="p">)</span><span class="w"> </span><span class="o">%>%</span><span class="w">
  </span><span class="n">unnest</span><span class="p">()</span><span class="w">
</span>

Here I’m joining the conversation by using the pmap function to fetch all the mentions to all the screen_names in the orig_tweets dataframe. The API only returns tweets from the last 6 to 10 days, but it should suffice. As Lucy pointed out in her post about Twitter Trees, querying the API using only to: screen_name misses some tweets, so I took her recommendation of including @screen_name and OR screen_name. You will notice that I took a lot of ideas from her blog post, which I highly recommend if you like to work with Twitter conversations.

I need to apply the rtweet::search_tweets function to each screen_name in the orig_tweets dataframe, passing more than one argument to the function: pmap is the answer! You can pass a list of arguments to the pmap function for it to pass them on to the search_tweets one. In this case I pass q: the query, n: the number of tweets I want, and retryonratelimit: set to TRUE for it to wait and retry when rate limited.

This is what I get:

To leave a comment for the author, please follow the link and comment on their blog: d4tagirl.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)