Monadologiska undersökningar 3: Visualisera kommentarsfält på Facebook

faschmap

Jag blev lite knäpp av att bara göra något halvgjort i förra inlägget. Så här kommer någon form av fortsättning, dels ett lite mera aggressivt inhämtningsskript och dels ett sätt att skapa nätverksfiler av datan som man får ned från facebook.

Först testade jag att hämta ned så mycket jag kunde från ett gäng nyfascistiska Facebooksidor. Mest för att se om det fanns kopplingar på monad/divid-nivå. Jag uppgraderade det förra skriptet att iterera över en dictionary och spara i en ny katalog med passande filnamn:

from facepy import GraphAPI
from django.core.serializers.json import DjangoJSONEncoder
import json
import time
import os

fascistfeeds = {"Sverigedemokraterna": 676246449160270,
                "Avpixlat": 1456773041274824,
                "SvP": 277195872311475,
                "Sdkvinnor": 169003303168526,
                "Ungsvenskarna": 1491422081182615,
                "Sdkuriren": 665250476818577,
                "Samtiden": 256690774389618,
                "Folkomrostningen": 1710097892544837,
                "Giveemhell": 1075216222492967,
                "Kabbelsvetsarn": 742925315824067,
                "Armanrasist": 146597985381739
                }

#Create a directory with today's date.
print("Today's date is:" + time.strftime("%Y%m%d"))
todaydate = time.strftime("%Y%m%d")

if not os.path.exists("scrape" + todaydate):
    dirdatename = "scrape" + todaydate
    os.makedirs(dirdatename)
else:
    print("Directory already exists, exiting.")
    quit()


for key, value in fascistfeeds.items():
    print(key, value)

    group_id = "%s" % value
    access_token = "Put here the token from https://developers.facebook.com/tools/explorer/"
    graph = GraphAPI(access_token)
    pages = graph.get(group_id + "/feed", page=True, retry=3, limit=1) #This is the number of posts per file!! original value 1
    i = 0
    for p in pages:
        print('Downloading page', key, i)
        with open('%s/%scontent%i.json' % (dirdatename, key, i), 'w') as outfile:
            json.dump(p, outfile, indent = 4, cls=DjangoJSONEncoder, ensure_ascii=False)
        i += 1

Urvalet är pseudo-slumpmässigt. Jag klickade bara runt lite i den bruna sörjan för att få mera data. När jag körde skriptet ovan fick jag hem 7858 postningar som sparas i varsin fil, sammanlagt 168 Mb data.

Redan här har man en datamängd som man kan jobba rätt bra med. Man kan söka efter ord och användare. Troligtvis finns det intressanta saker man kan göra med alla kommentarers fulltext, deras språkbruk, vem som säger vad etc. Men det är en annan form av analys.

Jag är intresserad av hur vanligt det är att en person kommenterar på en post på Sverigedemokraternas sida och sedan går över till Avpixlat och gör det samma, och så vidare längs hela datamängden. Då får vi fram en relation mellan dessa två aktörer som är medierad av en divid. Sverigedemokraterna och Avpixlat kan närma sig varandra till exempeln när en en Sd-politiker skriver på nyhetssidan. Men ett närmande kan också ske “underifrån” i och med att en aktör-divid kommenterar på båda sidorna, ett närmande som är minst lika viktigt för att skapa associationer. Att den ena aktören är en politiker och den andra en “helt vanlig människa” är inte nödvändigtvis en gradskillnad, utan snarare en slags modulation av en liknande relation. Attraktion är en kraft för mikrofascismen.

Nog med teori! För att vaska fram de aktörer som kommenterat på sidorna som nedskrapats från Facebook, skrev jag det här krytpiska skriptet:

from gexf import *
from json import load
from os import listdir
import hmac
import hashlib


gexf = Gexf("Facebook interactions", "Comments to Page")
graph = gexf.addGraph("directed", "static", "Facebook directed network")

edgelist = []

#### Multi file parser from directory
for filename in listdir("scrape20151128/"): #change path here (1/2)
    with open("scrape20151128/" + filename) as currentFile: #change path here (2/2)
        jsondata = load(currentFile) #parse json.

    ###The hash function. Note that the hashlib function needs to
    ###be inside the loop to preserve data integrity.
    for item in jsondata['data']:
        #print(item['from']['name'])
        targetgroup = item['from']['name']

        #This loop extracts comments. make sure to separate "i" and "item"
        #Change whatever you want to print.
        try:
            if item['comments']['data']:

                for i in item['comments']['data']:

                    digest_maker = hmac.new(b'rep7ac3withs0p3recretk3y', i['from']['id'].encode(), hashlib.sha256)
                    #print("Encrypted id: " + digest_maker.hexdigest()) #for debugging
                    hasheduser = digest_maker.hexdigest()
                    user = i['from']['id'] + i['from']['name'] #Plaintext user, for debugging only.
                    #print(hasheduser + targetgroup)
                    #strhasheduser = hasheduser.decode(encoding='UTF-8')
                    graph.addNode(hasheduser, hasheduser)
                    graph.addNode(targetgroup, targetgroup)
                    edgelist.append( (hasheduser, targetgroup) )

        except KeyError:
            break

#print(edgelist) #for debugging

for k, v in enumerate(edgelist):
    print(k, v[0], v[1])
    graph.addEdge(k, v[0], v[1])
    #print(k, v)

output_file=open("fascistfeeds.gexf","wb")
gexf.write(output_file)

Om allt går som det ska så spottar skriptet sedan ur sig:
Facebook directed network directed static
number of nodes : 13138
number of edges : 73721

Den .gexf-fil som sedan genereras kan öppnas med Gephi. Det blir lite kärvt med minne och processor vid så här stora datamängder, men det gick på min laptop.

På bilden ovan (ursäkta unicode-haveriet, någonstans i Gephi gick det fel) ser vi alltså facebook-sidorna som de stora noderna. Varje gång en divid kommenterar på en sida, skapas en relation, en så kallad “edge”. Varje divid kan kommentera på flera olika sidor (de hashade facebook-idnumrena håller reda på dem utan att avslöja identiteten) vilket gör att sidorna hamnar lite närmare varandra.

Det här var återigen bara ett test. Inga slutsatser att går att dra av resultatet. Men däremot borde det gå att fundera på vad som kan göras rent metodologiskt.

Monadologiska undersökningar 2 – Överlappande facebooksidor

Jag snubblade över en liten idé (eller så snubblade den på mig) när jag satt och kodade på en anonymiseringsteknik för facebookdata. Det konventionella sättet att se på politiska ideologier är ju att de formas och formuleras “uppifrån”, att de har en normkälla och är underblåsta av en kraftig bas som skapar dess centraliserande kraft kring ett Budskap (som forskaren tolkar och säger Sanningen om till de som inte vet bättre).

Ett annat sätt att se på saken är att börja “nedifrån och upp”, alltså i det sorlande hav av monader, divider, aktanter (vad vi kallar dem spelar inte särskilt stor roll). Här blir det intressant att studera de politiska varelsernas rörelser, eftersom det är dessa mönster och interaktioner, dessa översättningar och allianser, som i sin tur konstituerar det som vi ser som en politisk helhet. Här talar man mindre om de totalitära idéerna som texter och teser, utan letar efter totalitära affekter, begär, existensformer, interaktionsformer etc. Här hittar man inte fascismen, utan mikrofascismen.

Den empiriska data som presenteras nedan är bara ett litet test och det går inte att dra några slutsatser av dem. Själva insamlingen gjordes medelst det lilla pythonbiblioteket facepy. Jag gick till de öppna sidorna för Sverigedemokraterna, Miljöpartiet, Svenskarnas parti och Avpixlat och laddade ned så mycket jag kunde med Facebooks API-nyckel. Inga speciella tankar om urval alltså, jag hämtade bara vad jag fick, vilket varierade från några hundra till några tusen poster. Därefter valde jag ut alla kommentarer på alla poster och förvandlade varje användares unika Facebook-id till en kryptografisk hashsumma (för anonymisering). Dessa extraherades och skrevs till nya filer som sedan jämfördes i en korstabell:

Sd Mp Svp Avpixlat
Sd 80 92 47
Mp 80 22 3
Svp 92 22 5
Avpixlat 47 3 5

Vad säger då denna figur? Den visar på samförekomst (co-occurrence) av (hashade) Facebook-id mellan Facebooksidorna. Det finns alltså 80 tillfällen då samma person har kommenterat på både Miljöpartiet och Sverigedemokraternas sida, 92 gånger på Sd och Svenskanas Partis sidor, osv. Vi följer alltså konkreta personer, men har avidentifierat dem (visst skulle detta vara en läskig form av “åsiktsregistrering”).

Men det känns ju väldigt kontraintuitivt att det skulle vara större samförekomst mellan Sd och Mp än vad det skulle vara mellan Sd och Avpixlat. Sd och Svp ligger förvisso nära varandra “ideologiskt”, men å andra sidan konkurrerar de om om röster. Som ni märker förklarar den aggregerade nivån väldigt lite. Men den kan ändå ge oss vissa riktningar att gå i, vissa spår att följa.

Vad är det då som förklarar siffrorna? Svaret är att informationsrikedomen ökar med förstoringen. Vi måste alltså dyka ned i de 80 samförekomsterna (i ett datamaterial av tiotusentals interaktioner) för att se vad Miljöpartister och Sverigedemokrater har gemensamt (inom ramen för mitt slarvigt genomförda testurval). Men om vi gör det så bryter vi anonymiteten!

Nu kan man förvisso tänka att det är oproblematiskt att ändå analysera detaljerna i helt öppna Facebooksidor och därmed tumma på anonymiteten, eftersom de är tillgängliga för vem som helst på internet.

En viktig sak är nämligen att varje enskilt fall av samförekomst kan ha diametralt olika anledningar till varför den uppstod. Jag hittar exempelvis en miljöpartist som gillar djur som har kommenterat på Sverigedemokraternas sida med:

“Något Sd borde fundera över? Man måste inte alltid tycka som storebror?”

Å andra sidan kommenterar en Sverigedemokrat så här på Miljopartiets sida:

TBC-fall ökar och ökningen härrör uteslutande från personer som kommer hit utifrån. Vad gör ni konkret för att säkerställa att inte diverse smittsamma sjukdomar sprids (dvs det handlar om upprätthållande av folkhälsan)? Vilka resurser tillför ni nu skolor, sjukvård, sociala myndigheter,polis, säkerhetspolis, skolor, kommuner när belastningen på dem ökar kraftigt?

Två exempel av 80 möjliga, totalt icke-representativa. Poängen med detta är bara att göra en distiktion mellan det molekylära och det molära. Hade vi genomfört en opinionsundersökning hade vi fått en jämförelse av folks åsikter utefter kategoriella frågor; vad anser du om X, Y, Z? Resultat: ett genomsnitt.

Men med denna approach, å andra sidan, får vi istället ett “omvänt arbete”. Varje monad som interagerar med andra monader definierar själva frågan som ställs av dem, men inte utifrån en given kategori, utan snarare utefter imitation/repetition.

Vad som är målet är alltså att kvantifiera dessa kvalitéer så att man dels kan se aggregatens struktur utan att tumma på monadernas myller. Men detta sker nu  utan abstraktioner och “abstrakt tänkande”. Istället når vi framkomlighet genom repetitioner och associativt tänkande. Lättare sagt än gjort.

Vad tror ni? Förslag på saker som skulle vara intressanta att titta närmare på?

\\

Några skriptsnuttar jag använt mig av.

För att kryptera de enskilda användarnas id-nummer i kommentarsfältena:

from json import load
from os import listdir
import hmac
import hashlib


#### Multi file parser from directory
for filename in listdir("miljopartiet/"): #change path here (1/2)
    with open("miljopartiet/" + filename) as currentFile: #change path here (2/2)
        jsondata = load(currentFile) #parse json.

        #This loop extracts comments. make sure to separate "i" and "item"
        #and hashes them
        try:
            if item['comments']['data']:
                for i in item['comments']['data']:
                    digest_maker = hmac.new(b'r3placewithmegasecretk3y', i['from']['id'].encode(), hashlib.sha256)
                    print(digest_maker.hexdigest())
        except KeyError:
            break

För att räkna fram co-occurrences är det bara att jämför hashsummorna som extraherats ovan, fil för fil. Ungefär:

with open('mpencrypted.txt', 'r') as file1:
    with open('sdencryptedt.txt', 'r') as file2:
        same = set(file1).intersection(file2)

same.discard('\n')

print(same)
print("Number of co-occurences: ")
print(len(same))

Anonymisera facebookdata för forskning med hmac och hashlib

Om man vill samla in data från Facebook i forskningssyfte stöter man genast på forskningsetiska problem. Facebook ger alldeles för mycket information om informanterna, utan att man ens behöver be om lov.

Här tänkte jag bara ta upp en detalj som handlar om den “farligaste” uppgiften i den datamängd som man kan extrahera med Facebooks API: det unika id-numret.

Varje Facebookanvändare har ett femtonsiffrigt nummer tatuerat i sitt digitala skinn. Det ser ut så här (i (förkortat) json-format):

    {
     "id": "48236456782311475_63332119186437",
     "message": "This is a message written by an informant",
     "type": "status",
     "from": {
         "id": "121506454323368",
         "name": "Jane Doe"
     },
     "created_time": "2011-06-11T25:48:31+0000",
     "status_type": "wall_post",
     "is_expired": false
    }

De två fält som är uppenbart identifierande är alltså id och name. Här tänkte jag bara ta upp id-numret. Att ersätta namnet men behålla man/kvinna-distinktionen är ett problem som måste lösas på ett annat sätt.

För att kunna bevara det unika med id-numret utan att kunna identifiera en riktig person tänkte jag att man skulle kunna använda kryptering. Men jag är inte så duktig på det så jag undrar nu om detta kan vara ett vettigt angreppssätt.

Eftersom id-numrena alltid är femtonsiffriga så är det nämligen möjligt att räkna fram alla hashsummor, även om det skulle ta lite tid. Men om man däremot använder HMAC så lägger man till ett lager av säkerhet genom att man använder en unik hemlig nyckel och krypterar med. Så här tänkte jag ungefär:

from json import load
from os import listdir
import hmac
import hashlib

#Some code for reading files and parsing the data to jsondata

    for item in jsondata['data']:
        print("-" * 20)
        print("Name: " + item['from']['name'])
        print("Facebook id: " + item['from']['id'])
        digest_maker = hmac.new(b'replacewithsuperecretk3y', item['from']['id'].encode(), hashlib.sha256)
        print("Encrypted id: " + digest_maker.hexdigest())

Detta skriver ut:

Name: Jane Doe
Facebook id: 121506454323368
Encrypted id: dfef8d5bed530668b004e28a29488e8252e5a5224d3178f00c0f7d0bf48e4b6a

Detta gör att man kan bygga om forskningsdatan och ersätta id-numret med det krypterade numret.

Nu finns det tusen andra saker i materialet som kan avslöja en användares identitet. Men, bortsett från detta, tror ni att den här krypteringsmetoden är tillräckligt bra?

Om övervakning 2

bildt

Jag tror inte att det är en bra idé att utöka övervakningen precis här och nu. Man måste se bortom både fruktansvärda dåd  och misslyckade polisinsatser. Istället bör man titta bakåt och framåt längre än den politiska opinionen kan nå för tillfället. Här kommer några hållpunkter som kan vara bra att ladda in i det tänkande arbetsminnet, så att impulserna inte får enväldig makt över argumentationen.

För det första: datalagringsdirektivet. Eftersom detta förklarats ogiltigt av EU-domstolen så skulle man kunna tänka sig att det hänger löst även i Sverige. Men jag tror att det sitter ganska säkert. Dels misstänker jag att det inte kommer finnas någon som helst opinion för att riva i den svenska implementeringen (ingen politiker skulle våga). Men under ytan finns en viktigare aspekt.

Som avslöjades i ett SVT-reportage för två år sedan har man i princip automatiserat SÄPOs tillgång till datalagrade uppgifter. Det behövs alltså inga juridiska kontrollmekanismer för att data ska lämnas över, bara några knapptryckningar. När reportaget skrevs hade systemet bara införts delvis. Hur det funkar idag vet jag inte (länktips mottages gärna!). Men när ett system redan permanenterats som ett verktyg är det nog svårt att nedmontera det, även om det skulle vara ogiltigt på EU-nivå.

För det andra: frågan om “statstrojaner“. Här råder det ju en förvirring eftersom många verkar tro att det handlar om att polisen ska få tillgång till exempelvis Skype. Men så är knappast fallet, tillgång till denna typer av tjänster får de redan som det är. När det kommer till de (i huvudsak amerikanska) nätjättarna så har man upparbetat rutiner för att snabbt få tillgång till informationen, som ju såklart är krypterad.

Med trojaner menas något annat. Det innebär att i de flesta fall att man köper mjukvara från ett skumt företag som Hacking Team, Finfisher eller Bluecoat (som Telecomix avslöjade), företag som inte har några skrupler att sälja samma produkter till vidriga diktaturer. Dessa mjukvaror kapar sedan den misstänktes dator eller telefon, på lite olika sätt beroende av hur bra produkten är. Man kan även tänka sig att man utvecklar en egen lösning, men det har troligtvis inte SÄPO resurser att göra. Fördelen med dessa är att de kräver någon form av eftertanke innan de används. De skalar inte upp på det sätt som datalagringen gör (fritt fram att extrahera hur mycket data som helst), utan man måste koncentrera sig till misstänkta individer. Däremot är dessa system ganska osäkra. När det började läcka om de företag (se bland annat Wikileaks Spyfiles och Hacking Teams läckta e-post) som sålde trojanska hästar så framkom det hur taffliga deras produkter var. Det innebär alltså en säkerhetsrisk bara att använda dem. Dels är går de att upptäcka, vilket genast gör den misstänkte medveten om att hen är övervakad. Dels öppnar man upp en IT-säkerhetslucka mot sig själv, eftersom programvaran ju “ringer hem”.

Det är viktigt att både SÄPO och FRA har precis rätt verktyg och befogenheter som krävs för att försvara det öppna samhället utan att för den sakens skull upplösa det. Det är en mycket svår balansgång som man inte ska försöka vandra om det blåser kraftiga vindar.

Om övervakning

Jag skriver inte så ofta om övervakning längre, men i kölvattnet av den senaste tidens terrordåd skrev jag ändå en debattartikel i Expressen. Egentligen manar jag mest till eftertanke snarare än handlingskraft. Det är inte så genomtänkt att stifta övervakningslagar direkt efter ett dåd, något som historien visar.

När det kommer till diskussionen kring huruvida polisen ska få hacka sig in i folks datorer, vad man på tyska så vackert kallar för Staatstrojaner, så har jag blandade känslor. När denna typ av övervakning används av president Mubarak framkallas genast bilden av en mycket totalitär form av övervakning. Samtidigt utgör detta tillvägagångssätt en mycket riktad form av övervakning, alltså, motsatsen till “massövervakning”. Vad säger ni?

Medborgarprogrammering 2 – Riksdagens API

Jag har fastnat lite i ett hobbyprojekt som handlar om att koda mot olika former av öppna data. Jag inspirerades av en väldigt intressant bloggpost om hur man kan visualisera data från data.riksdagen.se med hjälp av D3.js och javascript. På twitter tipsades jag även om den intressanta sidan demokratikollen.se.

Över ett par koppar kaffe satte jag igång ett försök att anropa dessa datamängder till mitt nyligen inlärda programmeringsmodersmål Python. Det gick “sådär”. Problemet är att jag inte riktigt begriper strukturen på den data man får när man anropar Riksdagens API. Om någon begriper vad jag gör fel, kommentera gärna.

Uppdatering: Problemet löstes av Jimmy. Skrolla längst ned för att kopiera.

Här är skriptet som jag har skrivit i ett försök att komma åt e-postaddresser (med mera) som gömmer sig en bit in i json-strukturen som man får när man anropar APIt:

#-*- coding: utf-8 -*-
from urllib2 import urlopen
from json import load


#Load and parse JSON objects from web.
apiUrl = "http://data.riksdagen.se/personlista/?iid=&fnamn=&enamn=&f_ar=&kn=&parti=&valkrets=&rdlstatus=&org=&utformat=json&termlista="
apiParam = ""
outputFormat = "?format=json"
response = urlopen(apiUrl + apiParam + outputFormat)

#For more responsiveness, save the above file and use:
#response = open('data.json')

json_obj = load(response)

jsonData = json_obj['personlista']['person']

validentries = 0
validemail = 0

for item in jsonData:
    validentries += 1

for item in jsonData:
        print "-" * 20
        print item['tilltalsnamn'] + " " + item['efternamn'] + ", " + item['parti'] + ", " + item['kon']
        data = item['personuppgift']['uppgift']
        try:
            for u in data:
                if u['kod'] == 'Officiell e-postadress':
                    print u['uppgift']
                    validemail += 1
        except: TypeError

#print control variables. Entries should be 349 MPs
print "Found %d valid entries (MPs)" %validentries
print "Found %d valid e-mail addresses" %validemail

Skriptet är lite omständigt för jag har lagt in lite kontrollsummor för att hela tiden ha koll på att alla 349 riksdagsledamöter sysslar med. På rad 27 hämtar jag bara namn, parti och kön för att printa ut en första rad. Men på rad 28 försöker jag gräva mig ned djupare för att få tag på följande data:

"personuppgift": {
          "uppgift": [
            {
              "kod": "Officiell e-postadress",
              "uppgift": "anna.wallen[på]riksdagen.se",
              "typ": "eadress",
              "intressent_id": "0970427364222"
            }

Det är inga problem så länge datastrukturen ser ut som ovan. Inuti en lång lista som börjar med [ ligger ett antal dictionaries som sedan innehåller datan som är intressant (här finns även telefonnummer, hemsidor, osv.). Men så kommer man till ca. 30 av 300 riksdagsledamoter som ser ut så här:

"personuppgift": {
          "uppgift": {
            "kod": "Officiell e-postadress",
            "uppgift": "magnus.manhammar[på]riksdagen.se",
            "typ": "eadress",
            "intressent_id": "025967059622"
          }
        }

Observera avsaknaden av bracket(s). Av någon obegriplig anledning så genererar riksdagen bara en dictionary när det bara finns en enda dictionary. Alltså, den läggs inte i en lista, som i exemplet ovan.

Nu till min fråga. Hur skriver jag skriptet så att det även kan få in informationen i denna “lägre” nivå. Alltså, vad ska jag skriva efter except för att även få med den sista informationen?

Tacksam för svar, frågor eller ledtrådar.

\\
Här kommer det fungerande skriptet, tack vare Jimmys insatser i kommentarsfältet. Nu är det bara att sätta igång att modifiera!

#-*- coding: utf-8 -*-
from urllib2 import urlopen
from json import load


#Load and parse JSON objects
apiUrl = "http://data.riksdagen.se/personlista/?iid=&fnamn=&enamn=&f_ar=&kn=&parti=&valkrets=&rdlstatus=&org=&utformat=json&termlista="
apiParam = ""
outputFormat = "?format=json"
response = urlopen(apiUrl + apiParam + outputFormat)

#For more responsiveness, save the above file and use:
#response = open('data.json')
json_obj = load(response)

jsonData = json_obj['personlista']['person']

#some control variables to check for errors.
validentries = 0
validemail = 0

for item in jsonData:
    validentries += 1

for item in jsonData:
        print "-" * 20
        print item['tilltalsnamn'] + " " + item['efternamn'] + ", " + item['parti'] + ", " + item['kon']
        data = item['personuppgift']['uppgift']
        if isinstance(data, list):
            for u in data:
                if u['kod'] == 'Officiell e-postadress':
                    print u['uppgift']
                    validemail += 1
        elif isinstance(data, dict):
                    print data['uppgift']
                    validemail += 1
        else:
            raise TypeError

#print control variables. Entries should be 349 MPs
print "Found %d valid entries (MPs)" %validentries
print "Found %d valid e-mail addresses" %validemail

Lite mer om felmarginalsuträkningar

Sitter och uppdaterar det lilla hobbyprojektet felmarginalen. Eftersom jag precis håller på att lära mig javascript är jag inte helt säker på att jag får de olika matematiska formlerna rätt när jag översätter dem till kod.

För att räkna ut felmarginalen har jag kodat:

Math.sqrt(((procentsats * (100 - procentsats)) / urval)) * 1.96

och, för att räkna baklänges, alltså för att få ut hur stort urval man behöver, har jag vänt på ekvationen så här:

Math.pow(1.96, 2) * (procentsats * (100 - procentsats)) / Math.pow(felmarginal, 2)

Ni som är bra på statistik och javascript, visst funkar detta?

Nästa steg blir att göra så att man kan välja säkerhetsnivåer (90, 95, 99 procent) samt eventuellt göra en graf i D3.js. Förslag och kommentarer mottages varmt.

Medborgarprogrammering

I början av året skrev Copyriot om förslaget att man bör satsa på programmering i skolan (även kommentarerna är väldigt intressanta att läsa). Jag har sedan dess tänkt ganska mycket på just denna fråga eftersom jag har börjat använda programmeringsinslag i min egen forskning. Vad är programmering bra för, bortom instrumentella anvädningsområden?

Men här tänkte jag varken diskutera programmering som forskningsmetod eller programmering som något som man lär sig för att bli “programmerare”. Istället tänker jag att det finns utrymme för att beskriva en (tänkbar) programmeringspraktik som ryms inom begreppet “medborgarskap”. Alltså, frågan blir istället, kan vi tala om programmering som en slags bildning, en praktik vi kan kalla medborgarprogrammering?

Mina programmeringskunskaper är förvisso (än så länge) ganska ytliga. Jag tillbringade några lata veckor på olika caféer i Beirut med självstudieboken Learn Python the Hard Way och repeterade sedan på Codecademy. Därefter var det enkelt att börja tillämpa dessa grunder på mera specialiserade problem. Python är nämligen inte bara enkelt till sitt syntax, vilket gör inlärningen relativt snabb, utan innehåller framförallt ett gigantiskt bibliotek av mjukvarupaket som enkelt går att importera i den programkod man skriver. Man behöver alltså inte uppfinna hjulet flera gånger.

Trots att mina programmmeringskunskaper är ganska ytliga har de i någon mening har de ändå gett mig en slags indikation på att det finns ett viktigt ‘kritiskt-intellektuellt’ element i programmering bortom den tillämpade praktiken, ett element som är svårt att tänka utan att först ha redskapen för att kunna tänka dem. Om vi går med på att ett medborgarskap ökar i omfattning och agens genom utökade kunskaper har alltså programmeringskunskaperna en dimension som överskrider “anställningsbarhet i IT-samhället”, dvs. bortom standardargumentet för varför programmering är viktigt.

Ur ett medborgerligt perspektiv är det kanske viktigaste med programmering att vi kan tänka bortom världen så som den är given till oss eftersom vi i allt större utsträckning begriper den. I dagens samhälle utgör den ökade digitaliseringen en central förändringsanordning. Att acceptera den så som den är “förprogrammerad” – att bara ge upp och bli ställd av den – är inte rätt väg framåt.

När man diskuterar värdet av programmeringskunskaper stöter man ofta på två skeptiska invändningar. Den första är mediehistorisk och lyder ungefär “men typsättarna, boktryckarna, telegrafisterna, bokbindarna, kameramännen hade enbart tekniska kunskaper som andra sedan omvandlade till idéer, handlingar och kunskaper”. Det är kanske sant att om man kan binda en bok eller telegrafera ett meddelande så har man i dessa fall väldigt begränsade kunskaper och får inte mera makt över “innehållet”. Man kan till och med tänka sig en icke-läskunnig bokbindare. Men, denna jämförelse haltar på flera sätt. För det första lämnar denna typ av (funktionalistisk) mediematerialistisk teori väldigt lite utrymme för att inkorporera ett tänkande i anordningarna som analyseras. För det andra inbegriper programmering bara i begränsad utsträckning något som har med “medier” eller “innehåll” att göra. Programmering omfattar ju alla typer av problem som kan omvandlas till maskininstruktioner, inte bara tillverkandet av något som kan “medieras” (typsättarens datormotsvarighet är kanske html-kodaren, fast redan här är den senare kvalitativt sett mera avancerad).

Inte bara medier är programmerade. Även infrastruktur och saker som vi sällan lägger märke till i vårt vardagliga liv är programmerade, direkt eller indirekt. Att slänga soporna, låna en bok på biblioteket, gå till tandläkaren, handla mat – alla dessa aktiviteter är datoriserade. Och även om de är “teoretiskt” genomförbara utan att blanda in datorer (så gjordes det ju “förr”) så är det praktiskt taget omöjligt. Kostnaderna och prishöjningarna (alternativt lönesänkningarna) skulle vara alltför stora.

Moderniteten går inte att begripa i sin nuvarande form utan att begripa dess maskininstruktioner.

Med detta vill jag dock inte säga att man måste lära sig programmering för att förstå moderniteten, det vore ju minst sagt begränsande. Vad jag vill mena är istället att programmeringskunskaper bidrar till att förstå den produktiva infrastruktur som hela tiden skjuts framåt i en slags moderniseringsfront som präglar vår samtid. På samma sätt som massproduktion, standardisering, löpande band, byråkratisering utgör konstitutiva element i nittonhundratalets industrikapitalism, har datoriseringen utlöst en serie kvalitativa skillnader i hur våra moderna liv “formateras”, för att låna ett begrepp från filsystemens värld. Förvisso är vi människor inga disketter (minns ni dem!) som formateras, men när vi ska köpa en liter mjölk är det praktist omöjligt att inte involvera programmerade maskiner.

Den andra invändningen brukar klinga i en tonalitet av falskt medvetande och “totaliseringstänkande”. Denna skeptiska idé brukar i första led bestå av en kritik av ‘programmeringsoptimismen’ (en optimism som ofta uttrycks av politiker, “evangelister” och företag) och istället visa hur denna är en produkt av vad man slarvigt kan kalla för en “kapitalistisk ideologi”. All överbyggnad som talar varmt om programmeringens medborgerliga, demokratiska eller bildningsmässiga kapaciteter är i grunden underblåst av och hämtar sin näring från ett ökande behov av programmerare som arbetare. Google sponsrar programmering och öppen källkod med ett givet egenintresse av framtida arbetskraft. Politiker som säger att vi ska lära barnen att programmera har i baktanken en framtida tillväxt och välfärd.

Båda invändningarna har en viss giltighet. Speciellt när programmering utförs i allt mera specialiserade företag och när programmeringsarbetsuppgifter flyter fritt på en globaliserad marknad. De miljoner rader kod som vi dagligen använder oss av bara genom att slå på våra datorer, telefoner eller busskort är abstraherade produkter som köps och säljs på en global marknad. Vi förblir i stor utsträckning konsumenter av dessa produkter.

Men invändningarna motsäger inte att det parallellt med en sådan mittfåra kan finnas utrymme för en annan form av programmering, en form som upptäcker och omformar de existentiella maskininstruktioner som omger oss.

Jag vill inte definiera vad en sådan form skulle kunna inbegripa. Det är troligtvis en dålig idé eftersom exakt vad som är programmerbart och vad som kommer att vara programmerbart i framtiden är en ganska öppen fråga. Dessutom har jag börjat tänka på dessa saker först och främst genom att mina väldigt vardagliga observationer förändrades i takt med att jag tänkte alltmer på programmering medan jag lärde mig Python. Att se hur världen är programmerad gränsar i sin överdrivna form till hallucination, en kognitiv patologi där allt blir kod. Jag ser elementet i lägenheten och funderar på hur termostaten är programmerad med olika if-satser för att kunna slå av och på värmen vid rätt tillfälle. Jag åker spårvagn och funderar på om det går att hämta informationen om trafiken i realtid från något API på internet.

Jag funderar på när Vetenskap och Folkbildning gör en opinionsundersökning och om hur man lättare kunde räkna ut felmarginaler.

Medborgarprogrammering handlar inte om att man kan mäta “demokratiska effekter” av olika projekt och produkter. Snarare tänker jag att man borde ta utgångspunkt i hur det kritiska tänkandet förändras. Om det alls förändras? Vad tror ni?

Felmarginalen

I förra inlägget råkade jag klaga på bristande kunskaper bland journalister för att räkna felmarginaler. Det var inte så konstruktivt och jag kände mig lite “gnällig”.

Så med lite javascript och lite snabbt sammanrafsad html ordnades felmarginalen.se, en “lathund” som även försöker vara lite folkbildande.

Det är mest ett test än så länge. Kommentera gärna olika saker man kan göra för att förbättra sidan.