/Rfacebook

Dev version of Rfacebook package: Access to Facebook API via R

Primary LanguageR

<script type="text/javascript"> var hljs=new function(){function m(p){return p.replace(/&/gm,"&").replace(/"}while(y.length||w.length){var v=u().splice(0,1)[0];z+=m(x.substr(q,v.offset-q));q=v.offset;if(v.event=="start"){z+=t(v.node);s.push(v.node)}else{if(v.event=="stop"){var p,r=s.length;do{r--;p=s[r];z+=("")}while(p!=v.node);s.splice(r,1);while(r'+M[0]+""}else{r+=M[0]}O=P.lR.lastIndex;M=P.lR.exec(L)}return r+L.substr(O,L.length-O)}function J(L,M){if(M.sL&&e[M.sL]){var r=d(M.sL,L);x+=r.keyword_count;return r.value}else{return F(L,M)}}function I(M,r){var L=M.cN?'':"";if(M.rB){y+=L;M.buffer=""}else{if(M.eB){y+=m(r)+L;M.buffer=""}else{y+=L;M.buffer=r}}D.push(M);A+=M.r}function G(N,M,Q){var R=D[D.length-1];if(Q){y+=J(R.buffer+N,R);return false}var P=q(M,R);if(P){y+=J(R.buffer+N,R);I(P,M);return P.rB}var L=v(D.length-1,M);if(L){var O=R.cN?"":"";if(R.rE){y+=J(R.buffer+N,R)+O}else{if(R.eE){y+=J(R.buffer+N,R)+O+m(M)}else{y+=J(R.buffer+N+M,R)+O}}while(L>1){O=D[D.length-2].cN?"":"";y+=O;L--;D.length--}var r=D[D.length-1];D.length--;D[D.length-1].buffer="";if(r.starts){I(r.starts,"")}return R.rE}if(w(M,R)){throw"Illegal"}}var E=e[B];var D=[E.dM];var A=0;var x=0;var y="";try{var s,u=0;E.dM.buffer="";do{s=p(C,u);var t=G(s[0],s[1],s[2]);u+=s[0].length;if(!t){u+=s[1].length}}while(!s[2]);if(D.length>1){throw"Illegal"}return{r:A,keyword_count:x,value:y}}catch(H){if(H=="Illegal"){return{r:0,keyword_count:0,value:m(C)}}else{throw H}}}function g(t){var p={keyword_count:0,r:0,value:m(t)};var r=p;for(var q in e){if(!e.hasOwnProperty(q)){continue}var s=d(q,t);s.language=q;if(s.keyword_count+s.r>r.keyword_count+r.r){r=s}if(s.keyword_count+s.r>p.keyword_count+p.r){r=p;p=s}}if(r.language){p.second_best=r}return p}function i(r,q,p){if(q){r=r.replace(/^((<[^>]+>|\t)+)/gm,function(t,w,v,u){return w.replace(/\t/g,q)})}if(p){r=r.replace(/\n/g,"
")}return r}function n(t,w,r){var x=h(t,r);var v=a(t);var y,s;if(v){y=d(v,x)}else{return}var q=c(t);if(q.length){s=document.createElement("pre");s.innerHTML=y.value;y.value=k(q,c(s),x)}y.value=i(y.value,w,r);var u=t.className;if(!u.match("(\\s|^)(language-)?"+v+"(\\s|$)")){u=u?(u+" "+v):v}if(/MSIE [678]/.test(navigator.userAgent)&&t.tagName=="CODE"&&t.parentNode.tagName=="PRE"){s=t.parentNode;var p=document.createElement("div");p.innerHTML="
"+y.value+"
";t=p.firstChild.firstChild;p.firstChild.cN=s.cN;s.parentNode.replaceChild(p.firstChild,s)}else{t.innerHTML=y.value}t.className=u;t.result={language:v,kw:y.keyword_count,re:y.r};if(y.second_best){t.second_best={language:y.second_best.language,kw:y.second_best.keyword_count,re:y.second_best.r}}}function o(){if(o.called){return}o.called=true;var r=document.getElementsByTagName("pre");for(var p=0;p|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.ER="(?![\\s\\S])";this.BE={b:"\\\\.",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.inherit=function(r,s){var p={};for(var q in r){p[q]=r[q]}if(s){for(var q in s){p[q]=s[q]}}return p}}();hljs.LANGUAGES.cpp=function(){var a={keyword:{"false":1,"int":1,"float":1,"while":1,"private":1,"char":1,"catch":1,"export":1,virtual:1,operator:2,sizeof:2,dynamic_cast:2,typedef:2,const_cast:2,"const":1,struct:1,"for":1,static_cast:2,union:1,namespace:1,unsigned:1,"long":1,"throw":1,"volatile":2,"static":1,"protected":1,bool:1,template:1,mutable:1,"if":1,"public":1,friend:2,"do":1,"return":1,"goto":1,auto:1,"void":2,"enum":1,"else":1,"break":1,"new":1,extern:1,using:1,"true":1,"class":1,asm:1,"case":1,typeid:1,"short":1,reinterpret_cast:2,"default":1,"double":1,register:1,explicit:1,signed:1,typename:1,"try":1,"this":1,"switch":1,"continue":1,wchar_t:1,inline:1,"delete":1,alignof:1,char16_t:1,char32_t:1,constexpr:1,decltype:1,noexcept:1,nullptr:1,static_assert:1,thread_local:1,restrict:1,_Bool:1,complex:1},built_in:{std:1,string:1,cin:1,cout:1,cerr:1,clog:1,stringstream:1,istringstream:1,ostringstream:1,auto_ptr:1,deque:1,list:1,queue:1,stack:1,vector:1,map:1,set:1,bitset:1,multiset:1,multimap:1,unordered_set:1,unordered_map:1,unordered_multiset:1,unordered_multimap:1,array:1,shared_ptr:1}};return{dM:{k:a,i:"",k:a,r:10,c:["self"]}]}}}();hljs.LANGUAGES.r={dM:{c:[hljs.HCM,{cN:"number",b:"\\b0[xX][0-9a-fA-F]+[Li]?\\b",e:hljs.IMMEDIATE_RE,r:0},{cN:"number",b:"\\b\\d+(?:[eE][+\\-]?\\d*)?L\\b",e:hljs.IMMEDIATE_RE,r:0},{cN:"number",b:"\\b\\d+\\.(?!\\d)(?:i\\b)?",e:hljs.IMMEDIATE_RE,r:1},{cN:"number",b:"\\b\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",e:hljs.IMMEDIATE_RE,r:0},{cN:"number",b:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",e:hljs.IMMEDIATE_RE,r:1},{cN:"keyword",b:"(?:tryCatch|library|setGeneric|setGroupGeneric)\\b",e:hljs.IMMEDIATE_RE,r:10},{cN:"keyword",b:"\\.\\.\\.",e:hljs.IMMEDIATE_RE,r:10},{cN:"keyword",b:"\\.\\.\\d+(?![\\w.])",e:hljs.IMMEDIATE_RE,r:10},{cN:"keyword",b:"\\b(?:function)",e:hljs.IMMEDIATE_RE,r:2},{cN:"keyword",b:"(?:if|in|break|next|repeat|else|for|return|switch|while|try|stop|warning|require|attach|detach|source|setMethod|setClass)\\b",e:hljs.IMMEDIATE_RE,r:1},{cN:"literal",b:"(?:NA|NA_integer_|NA_real_|NA_character_|NA_complex_)\\b",e:hljs.IMMEDIATE_RE,r:10},{cN:"literal",b:"(?:NULL|TRUE|FALSE|T|F|Inf|NaN)\\b",e:hljs.IMMEDIATE_RE,r:1},{cN:"identifier",b:"[a-zA-Z.][a-zA-Z0-9._]*\\b",e:hljs.IMMEDIATE_RE,r:0},{cN:"operator",b:"<\\-(?!\\s*\\d)",e:hljs.IMMEDIATE_RE,r:2},{cN:"operator",b:"\\->|<\\-",e:hljs.IMMEDIATE_RE,r:1},{cN:"operator",b:"%%|~",e:hljs.IMMEDIATE_RE},{cN:"operator",b:">=|<=|==|!=|\\|\\||&&|=|\\+|\\-|\\*|/|\\^|>|<|!|&|\\||\\$|:",e:hljs.IMMEDIATE_RE,r:0},{cN:"operator",b:"%",e:"%",i:"\\n",r:1},{cN:"identifier",b:"`",e:"`",r:0},{cN:"string",b:'"',e:'"',c:[hljs.BE],r:0},{cN:"string",b:"'",e:"'",c:[hljs.BE],r:0},{cN:"paren",b:"[[({\\])}]",e:hljs.IMMEDIATE_RE,r:0}]}};

hljs.initHighlightingOnLoad(); </script>

Rfacebook: Access to Facebook API via R

This package provides a series of functions that allow R users to access Facebook's API to get information about users and posts, and collect public status updates that mention specific keywords.

Current CRAN release is 0.3. To install the most updated version (0.4) from GitHub, type:

library(devtools)
install_github("Rfacebook", "pablobarbera", subdir="Rfacebook")

Click here to read the documentation and here to read the vignette.

New in version 0.4: support for FQL queries.

Installation and authentication

Rfacebook can be installed directly from CRAN, but the most updated version will always be on GitHub. The code below shows how to install from both sources.

install.packages("Rfacebook")  # from CRAN
library(devtools)
install_github("Rfacebook", "pablobarbera", subdir = "Rfacebook")  # from GitHub

Most API requests require the use of an access token. There are two ways of making authenticated requests with Rfacebook. One option is to generate a temporary token on the Graph API Explorer. Then just copy and paste the code into the R console and save it as a string vector to be passed as an argument to any Rfacebook function, as I show below. However, note that this access token will only be valid for two hours. It is possible to generate a 'long-lived' token (valid for two months) using the fbOAuth function, but the process is a bit more complicated. For a step-by-step tutorial, check this fantastic blog post by JulianHi.

library(Rfacebook)
# token generated here: https://developers.facebook.com/tools/explorer 
token <- "XXXXXXXXXXXXXX"
me <- getUsers("pablobarbera", token, private_info = TRUE)
me$name # my name
## [1] "Pablo Barberá"
me$hometown # my hometown
## [1] "Cáceres, Spain"

The example above shows how to retrieve information about a Facebook user. Note that this can be applied to any user (or vector of users), and that both user screen names or IDs can be used as arguments. Private information will be, of course, returned only for friends and if the token has been given permission to access such data.

Analyzing your network of friends

The function getFriends allows the user to capture information about his/her Facebook friends. Since user IDs are assigned in consecutive order, it's possible to find out which of our friends was the first one to open a Facebook account. (In my case, one of my friends is user ~35,000 – he was studying in Harvard around the time Facebook was created.)

my_friends <- getFriends(token, simplify = TRUE)
head(my_friends$id, n = 1) # get lowest user ID
## [1] "35XXX"

To access additional information about a list of friends (or any other user), you can use the getUsers function, which will return a data frame with users' Facebook data. Some of the variables that are available for all users are: gender, language, and country. It is also possible to obtain relationship status, hometown, birthday, and location for our friends if we set private_info=TRUE.

A quick analysis of my Facebook friends (see below) indicates that 65% of them are male, more than half of them live in the US and use Facebook in English, and only 2 indicate that their relationship status “is complicated”. (Note that language and country are extracted from the locale codes.)

my_friends_info <- getUsers(my_friends$id, token, private_info = TRUE)
table(my_friends_info$gender)  # gender
## female   male 
##    155    292
table(substr(my_friends_info$locale, 1, 2))  # language
##  ca  da  de  en  es  fi  fr  gl  it  nb  nl  pl  pt  ru  tr 
##  38   2  10 302  82   1   6   1  11   1   1   1   2   1   2
table(substr(my_friends_info$locale, 4, 5))  # country
##  BR  DE  DK  ES  FI  FR  GB  IT  LA  NL  NO  PL  PT  RU  TR  US 
##   1  10   2  82   1   6  79  11  39   1   1   1   1   1   2 223
table(my_friends_info$relationship_status)["It's complicated"]  # relationship status
## It's complicated 
##                2

Finally, the function getNetwork extracts a list of all the mutual friendships among the user friends, which can be then used to analyze and visualize a Facebook ego network. The first step is to use the getNetwork function. If the format option is set equal to edgelist, it will return a list of all the edges of that network. If format=adj.matrix, then it will return an adjacency matrix of dimensions (n x n), with n being the number of friends, and 0 or 1 indicating whether the user in row 'i' is also friends with user in column 'j'.

mat <- getNetwork(token, format = "adj.matrix")
 |=================================================================| 100%
dim(mat)
## [1] 462 462

This adjacency matrix can then be converted into an igraph object, which facilitates the task of computing measures of centrality, discovering communities, or visualizing the structure of the network. As an illustration, the plot below displays my Facebook ego network, where the colors represent clusters discovered with a community detection algorithm, which clearly overlap with membership in offline communities. This was one of the examples from my workshop on data visualization with R and ggplot2. The code to replicate it with your own Facebook data is available here. David Smith has also posted code to generate a similar network plot.

Facebook ego network

Searching public Facebook posts

Rfacebook can also be used to collect public Facebook posts that mention a given keyword using the searchFacebook function. As shown in the example below, it is possible to search within specific a time range with the until and since options. If they are left empty, the most recent public status updates will be returned. The n argument specifies the maximum number of posts to capture, as long as there are enough posts that contain the keyword. Due to the limitations of the API, it is only possible to search for a single keyword, and the results will not include messages that are more than around two weeks old.

posts <- searchFacebook(string = "upworthy", token, n = 500, 
    since = "25 november 2013 00:00", until = "25 november 2013 23:59")
## 148 posts
posts[which.max(posts$likes_count), ]
##            from_id from_name                                message
## 87 354522044588660  Upworthy Is this real life?!?!? - Adam Mordecai
##                created_time type                link
## 87 2013-11-25T17:10:47+0000 link http://u.pw/1gbVzsV
##                                 id likes_count comments_count shares_count
## 87 354522044588660_669076639799864        8174            559         2701

The code above shows which public Facebook post mentioning the website “upworthy” and published on November 25th received the highest number of likes. It also illustrates the type of information that is returned for each post: the name and user ID of who posted it, the text of the status update (“message”), a timestamp, the type of post (link, status, photo or video), the URL of the link, the ID of the post, and the counts of likes, comments, and shares. Going to facebook.com and pasting the ID of the post after the slash shows that the most popular public Facebook post was this one.

Analyzing data from a Facebook page

Facebook pages are probably the best source of information about how individuals use this social networking site, since all posts, likes, and comments can be collected combining the getPage and getPost functions. For example, assume that we're interested in learning about how the Facebook page Humans of New York has become popular, and what type of audience it has. The first step would be to retrieve a data frame with information about all of its posts using the code below. To make sure I collect every single post, I set n to a very high number, and the function will stop automatically when it reaches the total number of available posts (3,674).

page <- getPage("humansofnewyork", token, n = 5000)
## 100 posts (...) 3674 posts
page[which.max(page$likes_count), ]
##              from_id          from_name
## 1915 102099916530784 Humans of New York
               message
## 1915 Today I met an NYU student named Stella.  I took a photo of her.  (...)
##                  created_time  type
## 1915 2012-10-19T00:27:36+0000 photo
##                                                                                                                            link
## 1915 https://www.facebook.com/photo.php?fbid=375691212504985&set=a.102107073196735.4429.102099916530784&type=1&relevant_count=1
##                                   id likes_count comments_count
## 1915 102099916530784_375691225838317      894583         117337
##      shares_count
## 1915        60528

The most popular post ever received almost 900,000 likes and 120,000 comments, and was shared over 60,000 times. As we can see, the variables returned for each post are the same as when we search for Facebook posts: information about the content of the post, its author, and its popularity and reach. Using this data frame, it is relatively straightforward to visualize how the popularity of Humans of New York has grown exponentially over time. The code below illustrates how to aggregate the metrics by month in order to compute the median count of likes/comments/shares per post: for example, in November 2013 the average post received around 40,000 likes.

## convert Facebook date format to R date format
format.facebook.date <- function(datestring) {
    date <- as.POSIXct(datestring, format = "%Y-%m-%dT%H:%M:%S+0000", tz = "GMT")
}
## aggregate metric counts over month
aggregate.metric <- function(metric) {
    m <- aggregate(page[[paste0(metric, "_count")]], list(month = page$month), 
        mean)
    m$month <- as.Date(paste0(m$month, "-15"))
    m$metric <- metric
    return(m)
}
# create data frame with average metric counts per month
page$datetime <- format.facebook.date(page$created_time)
page$month <- format(page$datetime, "%Y-%m")
df.list <- lapply(c("likes", "comments", "shares"), aggregate.metric)
df <- do.call(rbind, df.list)
# visualize evolution in metric
library(ggplot2)
library(scales)
ggplot(df, aes(x = month, y = x, group = metric)) + geom_line(aes(color = metric)) + 
    scale_x_date(breaks = "years", labels = date_format("%Y")) + scale_y_log10("Average count per post", 
    breaks = c(10, 100, 1000, 10000, 50000)) + theme_bw() + theme(axis.title.x = element_blank())

To retrieve more information about each individual post, you can use the getPost function, which will return the same variables as above, as well as a list of comments and likes. Continuing with my example, the code below shows how to collect a list of 1,000 users who liked the most recent post, for which we will also gather information in order to analyze the audience of this page in terms of gender, language, and country.

post_id <- head(page$id, n = 1)  ## ID of most recent post
post <- getPost(post_id, token, n = 1000, likes = TRUE, comments = FALSE)
users <- getUsers(post$likes$from_id, token)
## 500 users -- 1000 users --
table(users$gender)  # gender
## female   male 
##    784    209
table(substr(users$locale, 4, 5))  # country
##  AZ  BE  BG  BR  CA  CZ  DE  DK  EE  ES  FR  GB  GR  HR  HU  IL  IR  IT 
##   1   1   1   8   1   2   8   8   1   4   5 141   1   3   1   1   1   9 
##  LA  LT  NL  PI  PL  PT  RO  RS  RU  SE  SI  SK  TH  US  VA 
##   8   3   5   5   5   2   1   1   8   2   1   1   1 758   1
table(substr(users$locale, 1, 2))  # language
##  az  bg  cs  da  de  el  en  es  et  fa  fr  he  hr  hu  it  la  lt  nl 
##   1   1   2   8   8   1 904  12   1   1   6   1   3   1   9   1   3   6 
##  pl  pt  ro  ru  sk  sl  sr  sv  th 
##   5  10   1   8   1   1   1   2   1

Extracting personal information

Rfacebook also allows to read personal information about the authenticated users, such as the content from the Newsfeed, and friends' likes and checkins.

getLikes('pablobarbera', n=1, token)
## id           names
## 1 687958677914631 FiveThirtyEight
## website
## 1 http://www.fivethirtyeight.com/ 

getCheckins('pablobarbera', n=1, token=token)
##              checkin_time        place_id       place_name
## 1 2012-09-02T18:33:58+0000 120922987989887 Governors Island
## place_city place_state place_country place_lat place_long
## 1   New York          NY United States  40.69305  -74.01608
getNewsfeed(token, n=1)
##       from_id      from_name to_id to_name
## 1 51191684997 Rob DenBleyker      
##                                                                    message
## 1 Sorry for the late comic, it's up now!\n\nhttp://explosm.net/comics/3512/
##               created_time type                            link
## 1 2014-04-02T12:38:46+0000 link http://explosm.net/comics/3512/
##                              id likes_count comments_count   shares_count
## 1 51191684997_10152084439949998        6942            110            497

Executing FQL queries

To facilitate making API queries not implemented in the current version of the package, I have added the getFQL function, which will return an R list with the result of the query. A trivial example:

getFQL('SELECT name FROM profile where id = me()', token)
## [[1]]
## [[1]]$name
## [1] "Pablo Barberá"

Updating your Facebook status from R

Finally, yes:

updateStatus("You can also update your Facebook status from R", token)
## Success! Link to status update:
## http://www.facebook.com/557698085_10152090718708086

However, to make this work you will need to get a long-lived OAuth token first, setting private_info=TRUE in the fbOAUth function.

Concluding...

I hope this package is useful for R users interested in working with social media data. Future releases of the package will include additional function to cover other aspects of the API. My plan is to keep the GitHub version up to date fixing any possible bugs, and release only major versions to CRAN.

You can contact me at pablo.barbera[at]nyu.edu or via twitter (@p_barbera) for any question or suggestion you might have, or to report any bugs in the code.

<script type="text/javascript"> var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-1191254-10']); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); </script>