Wolfram Computation Meets Knowledge

Twittering with Mathematica

The popularity of Twitter has really exploded in the past few months. The service poses a simple question: “What are you doing?” Users respond in 140 characters or less. The 140-character limit comes from the 160-character limit of SMS messages, minus a few characters for things like the user’s screen name. Twitter could probably best be described as “micro-blogging.” It’s kind of a cross between blogging and instant messaging.

People use Twitter for all kinds of reasons, everything from staying in touch with friends to receiving announcements and support from companies with a presence on Twitter. Here are a few examples:

"Can't wait to see Nigel, Laura and Ben later."

"Who's up for cutting class today and hitting 18 holes? and the 19th too."

"Research Project Manager,Seattle, WA, United States: Penn, Schoen and Berland Associates is currently seeking qu.. http://tinyurl.com/cyj7pd"

That last one trails off a bit due to the 140-character limit.

StringLength["Research Project Manager,Seattle, WA, United States: Penn, Schoen and Berland Associates is currently seeking qu.. http://tinyurl.com/cyj7pd"]
140

Status messages (“tweets”) can be posted from the web, a mobile phone, or any number of desktop or mobile applications built specifically to interact with Twitter via its API. Twitter’s REST API is particularly interesting because it allows many operations to be performed using simple HTTP queries that return XML documents. Recall that Mathematica can do HTTP (via Import or J/Link) and can also import XML. You can probably guess where I’m going with this: Twittering with Mathematica.

We’ll begin with a simple example that doesn’t require any authentication, the public timeline. This URL returns an XML document containing a list of 20 of the most recent posts to Twitter by all users.

xml=Import["https://twitter.com/statuses/public_timeline.xml"];
statuses=Cases[xml,XMLElement["status",_,_],\[Infinity]]; Length[statuses]
20

Here are the users who made the posts in that document:

Cases[xml, XMLElement["screen_name",_,{s_String}]:>s,\[Infinity]]
Listing the Twitter posters

We can also get a list of status messages from (and replies to) a specific user—in this case WolframResearch.

xml=Import["https://twitter.com/statuses/user_timeline.xml?screen_name=WolframResearch"];

The textual contents of the tweets are found in the “text” XML element.

tweets=Cases[xml,XMLElement["text",_,{s_String}]:>s,\[Infinity]]
Column[tweets, Dividers -> All]
Textual contents of each message

Not all of the data on Twitter is publicly available. Certain things require authentication for a particular user account. Import handles these cases as well. It will prompt the user for login credentials if necessary. Here we will log in as WolframResearch.

Let’s say we want to retrieve a list of users who are following us.

page1=Import["https://twitter.com/statuses/followers.xml?screen_name=WolframResearch"];
Short[results1=Cases[page1,XMLElement["screen_name",_,{s_String}]:>s,\[Infinity]]]
{drnedflanders,marcambinder,kerberus13,<<95>>,lukemepham,richardbotley}
Length[results1]
100

Most Twitter APIs return a fixed number of results. For status messages it’s usually 20 results at a time. For user lists like this it’s usually 100 results at a time. This returned a full 100 results, so let’s get more results from page 2.

page2 = Import["https://twitter.com/statuses/followers.xml?screen_name=\ WolframResearch&page=2"];
results2=Cases[page2,XMLElement["screen_name",_,{s_String}]:>s,\[Infinity]];” title=”results2=Cases[page2,XMLElement["screen_name",_,{s_String}]:>s,\[Infinity]];” /><br />
<img src=
100

Keep going…

page3=Import["https://twitter.com/statuses/followers.xml?screen_name=WolframResearch&page=3"];
Length[results3=Cases[page3,XMLElement["screen_name",_,{s_String}]:>s,\[Infinity]]]
100
page4=Import["https://twitter.com/statuses/followers.xml?screen_name=WolframResearch&page=4"];
Length[results4 = Cases[page4, XMLElement["screen_name", _, {s_String}] :> s, \[Infinity]]]
5
page5=Import["https://twitter.com/statuses/followers.xml?screen_name=WolframResearch&page=5"];
Length[results5 = Cases[page5, XMLElement["screen_name", _, {s_String}] :> s, \[Infinity]]]
0

Page 5 returned 0 results, so we’re done.

followers=Union[results1,results2,results3,results4,results5];Length[followers]
305

Now we get to the fun part: setting your status programmatically from Mathematica. The tricky thing about this is that it requires the HTTP POST method. So far we’ve been using the HTTP GET method, and Import always uses the GET method.

No matter. J/Link provides access to all of Java from within Mathematica, and we can make use of Java to perform the HTTP POST necessary to set our Twitter status.

The first step is to initialize J/Link and create an HttpClient object.

<<JLink`
client=JavaNew["org.apache.commons.httpclient.HttpClient"]
« JavaObject[org.apache.commons.httpclient.HttpClient]»

Next we create a credentials object with a user name and password.

username="WolframResearch";password="FAKEPASSWORD";
creds = JavaNew["org.apache.commons.httpclient.UsernamePasswordCredentials",username, password]
« JavaObject[org.apache.commons.httpclient.UsernamePasswordCredentials]»

Set the authorization scope to twitter.com port 443. We could just as easily use port 80 with HTTP, but it’s better to send the password over the network encrypted, so we’ll use port 443 with HTTPS instead.

LoadJavaClass["org.apache.commons.httpclient.auth.AuthScope"];
scope = JavaNew["org.apache.commons.httpclient.auth.AuthScope","twitter.com", 443, AuthScope`ANYUREALM]
« JavaObject[org.apache.commons.httpclient.auth.AuthScope]»
client@getState[]@setCredentials[scope, creds]

Next we encode the tweet into percent-escaped UTF-8 bytes.

hexEncode[str_String]:=StringJoin[Riffle[IntegerString[ToCharacterCode[str,"UTF-8"],16,2],"%",{1,-2,2}]]
status=hexEncode["This tweet came from Mathematica"]
%54%68%69%73%20%74%77%65%65%74%20%63%61%6d%65%20%66%72%6f%6d%20%4d%61%74%68%65%6d%61%74%69%63%61

Create the HTTP POST method.

method = JavaNew["org.apache.commons.httpclient.methods.PostMethod","https://twitter.com/statuses/update.xml?status="<>status]
« JavaObject[org.apache.commons.httpclient.methods.PostMethod]»

Finally, execute the HTTP POST method.

res = client@executeMethod[method]
200

A result of 200 means the post was successful. Let’s get the resulting data and import it as XML.

StringLength[data=method@getResponseBodyAsString[]]
1684
xml=ImportString[data,"XML"];
id=First@Cases[xml,XMLElement["status", _,{___,XMLElement["id",_,{s_String}],___}]:>s,\[Infinity]]
1630155428

The other Twitter API function that requires the HTTP POST method is the one that deletes a status message. Let’s create a new HTTP POST method with the URL to destroy a tweet. Then we’ll execute it using the same client as before, as this client has already been authenticated.

method = JavaNew["org.apache.commons.httpclient.methods.PostMethod","https://twitter.com/statuses/destroy/"<>id<>".xml"]
« JavaObject[org.apache.commons.httpclient.methods.PostMethod]»
res = client@executeMethod[method]
200

Success.

Now that we’ve seen the guts of the code necessary to interact with Twitter, it would be helpful to simplify things with some reusable functions that encapsulate this code. I have written a Mathematica package (Twitter.m—click to download at the end of this post) which does exactly that. It incorporates all of the functionality we’ve seen so far and more. Let’s try a few things with it.

<<Twitter`

The first thing we’ll do is create a session to hold our login credentials. The user name and password can be passed explicitly to TwitterSessionOpen; if they are not, the function will prompt for them with a password dialog.

session=TwitterSessionOpen["User"->"WolframResearch"]
TwitterSession[<WolframResearch>]

We store the result in a variable called session. Most of the Twitter package functions require a session value to be passed as the first argument. It is possible to open multiple sessions simultaneously.

Pull out the user associated with this session.

user=TwitterSessionUser[session]
TwitterUser[<WolframResearch>]

Display a user interface element depicting the user, with image, text, hyperlink, and tooltip.

TwitterUserUI[user]

Name Wolfram Research Location Web: http://www.wolfram.com/ Bio

Find out information about the user.

TwitterUserID[user] TwitterUserScreenName[user] TwitterUserName[user] TwitterUserHomePage[user] TwitterUserProfilePage[user] TwitterUserProfileImage[user]
22659609
WolframResearch
Wolfram Research
http://www.wolfram.com/
http://twitter.com/WolframResearch
Spikey

Next, get a list of the user’s tweets.

tweets=TwitterUserTimeline[session]
All of the user's tweets

Find out information about the tweets.

tweet=First[tweets]; TwitterStatusText[tweet] TwitterStatusID[tweet] TwitterStatusUser[tweet] TwitterStatusDate[tweet] TwitterStatusPage[tweet] TwitterStatusSource[tweet]
Producing impressive high-quality Droste effect images with Mathematica https://blog.wolfram.com/2009/04/24/droste-effect-with-mathematica/
1605775403
TwitterUser[<WolframResearch>]
Fri 24 Apr 2009 12:50:16
http://twitter.com/WolframResearch/statuses/1605775403
web

Display a user interface element for the status message.

TwitterStatusUI[tweet]
User interface element

Let’s do a quick analysis of our friends and followers. We’ll take a look at the Union, Complement, and so on of our lists of friends and followers. We’ll have to use the unique ID values instead of the actual TwitterUser object wrappers, because the wrappers are not necessarily unique.

friends=TwitterAllFriends[session]; friendIDs=TwitterUserID/@friends;
followers=TwitterAllFollowers[session]; followerIDs=TwitterUserID/@followers;
bothIDs=Intersection[friendIDs,followerIDs]; N[Length[bothIDs]/{Length[friendIDs],Length[followerIDs]}]
{0.476341,0.495082}

So we can tell that 48% of our friends also follow us, while 50% of our followers are also our friends.

Next, let’s find out which group of users associated with us tweets the most. In this case we’ll need to map the unique ID values back to the wrapper objects.

friendOnlyIDs=Complement[friendIDs,followerIDs]; friendsOnly=friendOnlyIDs/.Map[(TwitterUserID[#]->#)&,friends];
followerOnlyIDs = Complement[followerIDs, friendIDs]; followersOnly = followerOnlyIDs /. Map[(TwitterUserID[#] -> #) &, followers];
both=bothIDs/.Map[(TwitterUserID[#]->#)&,Join[friends,followers]];
N[Mean[TwitterUserStatusCount/@#]]&/@{friendsOnly,followersOnly,both}
{2002.72,253.162,1357.72}

Comparing all of our friends and followers shows us that the chattiest group (highest average number of tweets per user) are those which are our friends-but-not-followers. The next-chattiest group are those who are both-friends-and-followers. The least-chatty group are those who are only our followers-but-not-friends.

Let’s combine the friends and followers into a single list of all the users associated with us.

allIDs=Union[friendIDs,followerIDs]; all=allIDs/.Map[(TwitterUserID[#]->#)&,Join[friends,followers]];
tweetCounts=TwitterUserStatusCount/@all;
BarChart[Sort[tweetCounts]]
Charting our friends and followers by tweet count--a very steep hyperbolic curve

So it looks like just a few of our friends and followers are responsible for the vast majority of the whole groups’ tweets.

Now let’s get the dates all these users joined Twitter and group them together by month.

allDates=TwitterUserDate/@all;
DateListPlot[Tally[{#[[1]],#[[2]],1,0,0,0}&/@allDates]]
Plotting when our friends and followers joined Twitter

Most of our friends and followers appear to have joined Twitter recently, but there are a few old-timers in that list.

Many of the features available in the Twitter API are accessible through this Twitter package. Feel free to explore them all.

Names["Twitter`*"]
Functions in the Twitter.m package
Functions in the Twitter.m package

Now that we’re done we can close the session. This step is optional.

TwitterSessionClose[session]

The ability to analyze Twitter data and to tweet programmatically opens the door to many interesting possibilities. Imagine running a long computation that notifies you via Twitter when it finishes. Imagine finding new friends by programmatically determining friends of friends or friends of friends of friends. Want more followers? Perhaps you could analyze the Twittering habits of popular users to see what might make them so popular. The possibilities are endless, as they usually are with data explorations in Mathematica.

Download and uncompress twitter.zip, then move Twitter.m into ToFileName[{$UserBaseDirectory, "Applications"}]

Download this notebook