De 10 flittigste twitterer på #dffuvinter16:
Refresh siden for opdatering.
De 10 flittigste twitterer på #dffuvinter16:
Refresh siden for opdatering.
It took a long time. I’ve actually been ready for a couple of days, but theres a cold on its way, and I gave a presentation on the history of e-bookreaders earlier today. But now I’m ready!
Wordclouds will have to wait – I’m simply not a good enough javascript programmer. And the live-updating tweetcount have the same problem. But besides that:
The 10 most prolific tweeters:
The number of tweets as a function of time:
The number of tweets pr language (ISO-codes, sorry)
The number of tweets pr location (ISO-codes, sorry)
Note – more than one chart on the same page, might interfere. Function names has to be different.
Whats next? There were a lot of things to finetune in some of the previous posts. I would really like to make a wordcloud. But right now: Terminate the python script. Clean the spreadsheet. Add the right hashtag to the script. Rerun. Wait.
So. I want to collect more data. That means I have to add it to the spreadsheet. I also want to enumerate the tweets. Most importantly – I need to be able to handle missing fields in the json I get from Twitter. If there are no hashtags in the tweet, the field “hashtags” wont be there. The script should be able to handle that. A lot of the tweets I have collected, are retweets. Thats fine. But they begin with the string “RT”. Should I remove that?
It is tempting to collect all the data. But I don’t think I’m going to do that. A lot of the fields does not appear in every tweet. And I’m not sure I’ll ever figure out what to do with it. So it’s going to be a selection of them.
Oh, and by the way – why should I break the current spreadsheet? I’ll just add new columns. No need to rework previous graphs. The actual content may vary, but what the heck. And those coordinates that are more or less useless (because no-one uses them)? Lets replace them with the counter.
Anyways which fields?
| column | What | code | Note |
| A | Timestamp | data[‘timestamp_ms’] | |
| B | Counter | =row() | Replaces coordinates |
| C | Screenname | data[‘user’][‘screen_name’] | |
| D | Link for image | data[‘user’][‘profile_image_url’] | Profile image for the tweeter |
| E | Text | data[‘text’] | The actual tweet |
| F | ID-string | id_str | ID of the tweet – as a string |
| G | Location | user:location | Registered location of the user |
| H | Hashtags | The hashtags of the tweet | |
| I | Language | lang | Language of the tweet, as identified by Twitter |
I think thats gonna be it.
Getting to the hashtags is a bit difficult. First of all, there might be none. Secondly, there might be more than one. Remember that I get the tweet in JSON-format? And I call the variable “data”.
The first hashtag – given that there is one, can be called like this:
data[‘entities’][‘hashtags’][0][‘text’]
The second of course is found simply by replacing “0” with “1”. The third with “2” etc. The number of hashtags can be found like this:
len(data[‘entities’][‘hashtags’])
Putting it together:
try:
for x in range(0, len(data[‘entities’][‘hashtags’])):
hashtag = hashtag + data[‘entities’][‘hashtags’][x][‘text’] + ” ”
except:
hashtag = ” ”
til_regneark.append(hashtag)
Done. I should put some timers in. It does take quite some time from the tweet is caught until it is written to the spreadsheet. Thats simple. Let me just try that.
Yeah. It is writing to the spreadsheet that takes time. About 4 seconds. Not much I can do about that.
And the timing? I already have the “time” library imported. Just place a “start_a = time.clock()” before what you want to time. And an “end_a = time.clock()” right after. And then “print (end_a – start_a)”.
Done. Now I have the data I need. Or I will have in a little while. All that is left is to plot it.
Now we’re getting somewhere! At the bottom of this post will be links to the previous parts of this odyssey.
I now have a spreadsheet with some data. How do I make that interesting to look at? First of all, I have to decide on a framework for graphing this. In part IV, I made a choice. I chose to save the data in a Google Spreadsheet. For two reasons. One is that I know something about how to work with it. As the previous parts have revealed, I dont know much. But I know enough. The other reason is, that Google has a chart API, that I should be able to use. That in itself is not a very good reason. I know how to work with HighCharts, so why not use that, instead of learning something new? Because Google promises, that the graphs will update live, as I fill in more data in the spreadsheet.
How cool is that?
Where to begin? I don’t think that Google actually provides any good examples. A lot of other people does – it just takes at bit of googling to find somthing that is helpful. I think this example is a good beginning. Go on, take a look.
OK, first of all it begins like this:
function drawChart() {
var query = new google.visualization.Query(‘http://docs.google.com/spreadsheet/tq?key=0AhCv9Xu_eRnSdHBELWZoZHRaQTl5VXdRa3JLTzByUlE&gid=1’);
That tells the chart software where to find the data. It does not look like the ordinary link to a spreadsheet. That looks a bit more like this:
https://docs.google.com/spreadsheets/d/14kCXRFEOxBMgbGDhwBP1Zy8yhZhC7ty54iBVlCovKxc/edit#gid=1
I’m not sure exactly what the difference is, but when I follow the URL in the script, my browser downloads a JSON-file. I think it contains the data from the spreadsheet. So I need to take the URL I already have for my spreadsheet, and format it like in the example.
https://docs.google.com/spreadsheet/tq?key=1kDIxZ5jBWYB2eiAjfsM_jRBr2mYWxudORECFApXBULE&gid=3
I’m almost certain that the gid=3 points to sheet number 3 in the spreadsheet. I’ve accessed the spreadsheet from the example, and copied the data to sheet no. 3 in my own sheet. If I change the long string of random letters in the example, nothing should happen.
It did’nt. The graph on that fiddle looks exactly the same. Now, lets try to the real data. That is in sheet number 1.
Lets also change the “query.setQuery(‘SELECT A, B, etc’)” to “query.setQuery(‘SELECT B, A’)”. That should collect the data from colums B and A in the sheet.
Try again. Yep. I’m now succesfully plotting the data.
It does not, however appears to update live…
It appears that Google does not live up to the promises. I’ll need HighCharts for that. If, that is, I can figure out how to get live data.
There is another problem. This is a very bad graph. It plots the timestamp of the tweet, against the coordinates from the tweet (remember that those were allmost always “None”?). Not that interesting. Maybe Google supports some way of counting? Quick test: Exchange ‘SELECT B, A’ with ‘SELECT COUNT(A), A)’. In theory, is that I should get the count of column A on the x-axis, and the timestamp on the y-axis.
Nope. Better take a look at the query language. Its some variant of SQL. The documentation can be found here. Note – the graph above is not the real thing. I might have to change the spreadsheet, and that will change the graph. Therefore it is an image of the graph. The code will be given below.
And now, before continuing, I have to do something about the laundry.
And now, the promised list of the previuos parts:
Part I: The idea, and the hardware
Part II: Getting my hands on tweets
Part III: Deciding what data to save (that is going to change over the next few days)
Part IV: Storing the data
Part V: Fixing some problems
Part VI: Problems that can wait
And the code for the graph:
<script src=”http://www.google.com/jsapi?fake=.js” type=”text/javascript”></script>
<script type=”text/javascript”>// <![CDATA[
function drawChart() { var query = new google.visualization.Query(‘http://docs.google.com/spreadsheet/tq?key=1kDIxZ5jBWYB2eiAjfsM_jRBr2mYWxudORECFApXBULE&gid=1’); query.setQuery(‘SELECT B, A’); query.send(function (response) { if (response.isError()) { alert(‘Error in query: ‘ + response.getMessage() + ‘ ‘ + response.getDetailedMessage()); return; } var data = response.getDataTable(); var chart = new google.visualization.LineChart(document.querySelector(‘#google_test_container’)); chart.draw(data, { chartArea: {width: ‘70%’, height: ‘50%’}, fontName: [“Arial”], colors:[‘#274358′,’#5e87a5′,’#a2cdf6’], curveType: [‘none’], fontSize: [’11’], hAxis: {title: ‘Tweets over time’, titleTextStyle: {italic: false, color: ‘black’, fontSize: 12}}, legend: {position: ‘right’, textStyle: {color: ‘black’, fontSize: 12}}, lineWidth: 2, pointSize: 7, tooltip: {textStyle: {color: ‘Black’}, showColorCode: false} }); }); } google.load(‘visualization’, ‘1’, {packages:[‘corechart’], callback: drawChart});
// ]]></script>
<div id=”google_test_container”></div>
Yep, its messy.
Done. The test at the end of last chapter was a success. An ugly hack to be sure. As long as it works. This is going to be a shorter part in the series. There are a number of different things I should do. But now I’m getting close to the interesting part. A list of stuff that can wait, because I’m impatient:
That will have to wait. Apart from the last one, that is actully somewhat critical. I’m getting close to being able to visualise some data. I’ll begin playing in a short while, but right now I think I need to spend some time on the couch.
Oh, and for those just joining the party:
Part I: The idea, and the hardware
Part II: Getting my hands on tweets
Part III: Deciding what data to save (that is going to change over the next few days)
Part IV: Storing the data
Part V: Fixing some problems
In part I I covered the basic idea, and the hardware. Now I’m ready to turn my attention on actually getting my hands on some tweets.
Twitter has an API, an Application Program Interface, that allows programs to talk directly with the Twitter-servers. In order to gain access, I need to have an authenticated Twitter account. I already have that, it was not easy, as Twitter requires a phonenumber to be associated with the account. That can only happen if the phone company supports receiving SMS’es from Twitter. My phone company did not until recently.
That hurdle tackled, I go to the Twitter Apps site. I have to be logged in, and click “Create New App”. After I’ve filled out the required information, I’m provided with four long random strings. I’m not going to show them here. Lets just say that they are long, and contains both digits and letters, both upper and lower case. I get a Consumer Key and a Consumer Secret, basically a login to Twitter. And I get an Access Token and an Access Token Secret – that are specific to the given application I’m gonna write. Those four strings are my credentials, that I’m going to use to gain access.
Next step. I could write a program that sends a request to the Twitter API, and receives data. Or rather, I could’nt, because I’m not that good a programmer. As luck would have it, I’m not the first person in this situation.
But I’m getting ahead of myself. The first question should actually be: Which language should I write this in?
The de facto preferred language on the Raspberry Pi is Python. Python is not the fastest language in the world – its an interpreted program rather than a compiled. But it has a very large userbase, the fact that it is interpreted makes development quick. And it is very popular in data science circles. Propably because it is pretty accessible to people that are not really programmers.
I could write a program – script actually – from the bottom up, that handles all the connectivity with Twitter. Or rather I can’t. I’m not that good a programmer. But someone else has already written a library, a collection of code, that handles all the really difficult parts. Python comes with a LOT of libraries. One of the libraries is called Twython, a portmanteau of Twitter and Python. The intelligent reader of this post, can guess what Twython does.
I do not need to understand everything myself. Others have done that, and on this page, I find an example of how to get tweets with Twython in Python. The code is as follows:
from twython import TwythonStreamer class TweetStreamer(TwythonStreamer): def on_success(self, data): if 'text' in data: print data['text'].encode('utf-8') def on_error(self, status_code, data): print status_code self.disconnect() # replace these with the details from your Twitter Application consumer_key = '' consumer_secret = '' access_token = '' access_token_secret = '' streamer = TweetStreamer(consumer_key, consumer_secret, access_token, access_token_secret) streamer.statuses.filter(track = 'python')
OK. I enter my credentials from Twitter, and runs the code. sudo python collect.py.
And tweets containing the word “python” is streaming down my screen:
Actually it was a bit more difficult. The Twitter API requires a safe connection, and none of the requisite libraries handling safe connections were installed. That took quite a lot of googling. A pro-tip: Read the error messages you get. Some of them contains links to solutions.
I must admit that I’m not quite sure what is actually happening. Object-based programming is not my strong suit. Never mind, I just need to understand enough.
The line
streamer.statuses.filter(track = 'python')
Tells the API that I’m interested in tweets containing the word “python”
And this part of the script:
def on_success(self, data): if 'text' in data: print data['text'].encode('utf-8')
Does stuff to the tweet I receive. The tweet is returned from Twitter in a format called JSON. And it is saved in the variable called “data”. There are different fields in the format, one of them is “text”. I can access the content of that field by calling data[‘text’]. I may need to do something about the encoding. People tweet in a lot of different languages, so tweets are encoded in the character set utf-8. As you can see in the screenshot, my computer does not really know what to do with exotic characters.
There are a lot of other fields containing data in the tweets. But the main objective is achieved – I can get tweets on my screen in realtime. I you tweet something containing the word “python”, I will get it on my screen moments after.
The second part of the mission is accomplished. On to the third. But first – we’re going to the mall for a burger.
Now we’re getting somewhere! It took most of last night reading up on long forgotten SQL-skills. I present to you, the most prolific tweeters:
OK, I need to do something about the graphical stuff. The text is pretty cramped.
The magic? This SQL-style selection.
SELECT C, COUNT(A) GROUP BY C ORDER BY COUNT(A) DESC LIMIT 15
Combined with choosing BarChart rather than lines. It really is that simple. Although it certainly was anything but simple to figure out (until I gave up, and consulted my sql-books).
What else? I want a graph showing the number of tweets as a function of time. I would also like a wordcloud. And a tagcloud. I dont think I can make that with the google framework. Or maybe I can: I’d better take a look at this page. The tag-cloud would be easier if I import other fields from the tweets to the spreadsheet.
And if I want a count – it appears that I have to add “=ROW()” to a column in the spreadsheet. That will, I hope, give me a counter.
Of course, that is going to seriously mess up the chart above. But it will only take a change of two letters, to fix. Next step will be to import all fields from the tweets to the database. Just a moment 🙂
Data is the next big thing. Big data is even bigger. And as always when something new happens, libraries jump on the bandwagon from the beginning.
We are coming to it at bit slow this time, at least in Denmark. We might have been intimidated by the Big in big data. But we’re getting there. And as always when we do something new, we have the problem of figuring out exactly what we should be able to do. Wich competencies should we have, what is the level of support that we should provide to our patrons?
I imagine that other places trying to get into data science, would begin by hiring a data scientist. Or at least send the most qualified personel on courses and training sessions. Not so in a library. We have all the books, we’ll just read them, and then we will know how to do it. It actually appeals to me as an engineer:
BOSS: We should do some data science
ME: OK, do we have anyone on staff that knows anything about that?
BOSS: No.
ME: Hmmm. Are we going to hire someone who knows anything about data science?
BOSS: No.
ME: Are we going to invest in training or courses to learn about it?
BOSS: No.
ME: OK, lets get started.
It gives us a chance to play around with exciting and difficult stuff, without being burdened with any actural knowledge about what we are doing. That is more or less how engineers define fun!
So. What should we be able to do?
Thats it. How are we going to get there? I’ll get back to that later – now I have a meeting on LibQual.
Twitter er ikke specielt stort i Danmark. Men det er der dog. Det lader til primært at være brugt af politikere, der ønsker at kommunikere til journalister, journalister og andre kommmunikationsfolk der ønsker at kommunikere med andre journalister og kommunikationsfolk. Og bibliotekarer, der desperat forsøger at kommunikere med hvem som helst. Det er i høj grad ikke et folkeligt medie, men snarere det LinkedIn godt ville være.
Ønsker man en faglig selvpromovering kan det derfor, specielt når man er i biblioteksbranchen, være en rigtig god ide at være på Twitter. Så det er jeg. Det er sat i system. Jeg sætter tid af hver weekend til kvidren – de lagres i tweetdeck, og spredes ud over ugen, og hver mandag går jeg lige efter og sikrer mig at jeg har til resten af ugen. Dertil kommer en række løse tweets i løbet af ugen. Men der kvidres hver dag.
Jeg går også semiseriøst efter flere følgere. Der er optimeringspotentialer: Hvem fulgte jeg hvornår, og hvis de ikke har fulgt tilbage, var det så ikke på tide at affølge dem. Hvem har unfollowed mig i dag? Skal jeg blive ved at følge dem? Og så videre. Jeg trækker ved hjælp af et pythonscript på en Raspberry Pi oplysninger om hvor mange følgere jeg har en gang i timen, og trækker de samme oplysninger på et antal kolleger i branchen. Skal jeg udbygge det netværk til noget der kan bruges (og det kan det!) i mit arbejde, skal jeg have et synligt mål og kunne følge udviklingen. Jeg skal have flere følgere end Knut – ikke fordi jeg konkurrerer med ham, men fordi jeg skal have et mål, gerne et der bevæger sig lidt (men ikke for meget) , som jeg kan gå efter.
Så langt så godt, der har været andre indlæg der har omtalt det. De burde opdateres, for jeg har omsider fået adgang til Twitters API, og kan nu trække data langt hurtigere, elegantere og legalt, end jeg kunne før.
Men det virker, og selvom jeg skal have migreret et script til python3.4, er det ikke noget der haster.
Så ud over de mindre potentielle indsatsområder, så er der en anden ting der kunne være interessant. Også for andre end mig. Nemlig: Hvordan ser netværkene ud? Hvordan er de danske bibliotekarer forbundet på dette, i en dansk kontekst trods alt relativt begrænsede økosystem?
Der er nogle trin på vejen. Nogen af dem har jeg styr på.
Jeg skal bruge oplysninger om hvem der følger hvem. Lad os bare nøjes med Knut og mig. Hvem følger mig? Hvem følger Knut? Hvem følger vi hver især? Er der nogen vi begge følger? Hvor er de gensidige forbindelser?
Det er trivielt. Der er lidt udfordringer i at jeg ikke kan trække mere end 200 følgere ad gangen, og at jeg ikke kan gøre det mere end en gang i minuttet. Det er der veje uden om.
Hvad jeg har lidt flere problemer med, er at få styr på hvordan jeg vil visualisere det. Der er fine frameworks derude. Det ender nok med D3 til det her formål, den kan også lave fine animationer. Traditionelt gøres den slags med bobler, og det er vel oplagt at min bobbel i grafen har en størrelse der er proportional med det antal følgere jeg har. Derfor vil Knuts også være større.
Så har jeg nogen der følger mig, uden at jeg følger dem. Der skal være en linie af en eller anden art mellem deres bobbel, og min bobbel. Det betyder også at jeg skal have en ide om hvor stor deres bobbel er – jeg skal have trukket oplysninger om hvor mange følgere de har.
Så er der dem som jeg følger, og som også følger mig. der skal også være en linie af en art mellem os.
Endelig er der dem jeg følger, som ikke følger mig. Der skal ligeledes være en linie.
Boblerne er simple. Eller, det er de nok ikke, men det er ikke det jeg har svært ved at folde hjernen om. Det der udfordrer mig er linierne (eller kanterne) i netværket. Der er tre typer. Hvordan skelner jeg mellem dem?
Farver? Det ender det nok med.
Man kunne også overveje om det kunne gøres enklere. Der findes værktøjer derude til den slags. Twecoll for at være mere præcis.
Well. D3. Farver på de forskellige kanter. Så skal der trækkes data. I et eksempel jeg har fundet, tager D3 en JSON fil, med nodes og links. Disse nodes har en source og et target. Det giver god mening for alle andre end de links der er gensidige.
Jeg tager lige et kig på denne her: http://bl.ocks.org/mbostock/4062045
Det er en lidt kølig visualisering af hvilke karakterer der optræder sammen i Les Miserables. Selve koden tror jeg at jeg starter med at kopiere. Så skal jeg generere en JSON-fil. Den har en source et target og en value i link-delen. Hvad der forvirrer mig er, at dens nodes liste har navne og “gruppe”. Gruppe kan man lege med. Men der er ikke et ID der matcher source i links. Der lader det til at være således, at ID’erne matcher positionen i listen i nodes. Hm. Jeg ville klart foretrække at ID’et var et sted i Nodes. Det skal jeg nok tænke lidt mere over.
Rambling on. Vi gør det som D3 vil ha’ det. Så skal jeg bare have det genereret.
Jeg har en liste i json-formatet med de enkelte nodes. De har et navn. Og kan have andre atributter også. I det eksempel jeg tager udgangspunkt i, har de en gruppe. Og det er det. Jeg skal bruge deres index i en liste. Så det første jeg skal er at få genereret den. Jeg trækker alle som følger Knut. Og alle som følger mig. Det giver to lister med twitter-id. Dem gemmer vi i hver sin fil. Hvert eneste ID i den liste er karakteriseret af at de følger Knut eller mig.
Det ville altså være lettere hvis jeg kunne bruge twitter-ID direkte. Det sparer et opslag i node-listen hver gang.
Og det kan man: http://plnkr.co/edit/20t4F02vsM1U55ktCv66?p=preview
Også:
http://stackoverflow.com/questions/23986466/d3-force-layout-linking-nodes-by-name-instead-of-index
Godt. Så vender vi tilbage til at generere listen over edges… Helt så trivielt er det heller ikke.
Note: I’ll be using the male pronoun. Not because there are only men in the world. But because I really like the association between Man-Month and Mothman. For a long time, I
thought that the title was inspired by the urban legend of the Mothman. It probably isn’t. I still like it.
In a previous post I discussed the Bermuda Triangle of Project Management. Or the Iron Triangle as it is properly known. I promised to return to the mythical man-month later. Lets do that.
The following thoughts are not my own. They come from a classic essay by Frederick Brooks, written in 1975. And it appears to have been completely forgotten. Well. Not forgotten, it is a classic. But apparently no-one has learned anything from it.
Think back to the ideal world of project management. In that world everyone knows, that if we cut ressources, the project will be delayed. It follows logically, that if the project gets delayed, we should assign more ressources to it. That might be a good idea. It might be a horrible idea. And in some situations, the thought of getting an extra man-month should fill the project manager with the same terror as the mothman did to the good citizens of Point Pleasant back in 1966.
The basic idea is, that if the project runs late, it should get more ressources, in order to finish on time. The observation is, that the added man-months will actually delay the project even more. Why would that happen?
First of all, in most projects, men and months are not interchangeable. We all know that some of our colleagues are more productive that others. Maybe they just have other qualifications. Just because you are a great database engineer, does not mean that you are qualified to make the userinterface. And the man-month your project is assigned does not know the project. Some of the members of the project team will have to spend time training the new guy. In a really bad scenario, Brooks describes a situation where one member of the team have to use a month to train three new team members. The project might have been assigned three man-months. But the first month uses four man-months on training. Placing a net drain of one man-month on the ressources available to the project, compared to the initial situation. After one month, the project is even more delayed. And the project manager now has to explain to his boss why. A difficult question to answer when you have just been assigned more ressources.
Secondly, the tasks performed in a project are not interchangeable. They are usually sequential in nature. First we have to do this, then we have to test it.
Project managers often refers to people who believes that if you assign nine women to the task of carrying a pregnancy to terms – you will become a father next month.
So – what to do? There is no simple answer. The task of management is to pressure the project manager to perform. As a project manager, it is easy to come to the conclusion that they dont understand anything, and that their demands are unreasonable. But maybe, just maybe, you actually could do better.
When you have done better, you are still left with the task of explaining to your boss, that he is not going to be a father next month, just because you get more womanpower. Good luck.