Partiernas kön

I november förra året undersökte SCB i sin partisympatiundersökning hur fördelningen mellan män och kvinnor såg ut bland de som kunde tänka sig att rösta på ett visst parti i nästa val. Resultatet blev följande:

scb

Vad som kanske överraskade lite var den stora övervikten av män bland Sverigedemokraterna. Den var så stor att den fick alla andra partier att bli (svagt) kvinnodominerade. Sådan blir ju effekten av att ha riksdagsvalet som modell för vad som ”gör” ett parti. Allt kommer i slutändan bli 100 procent.

Finns det ett annat sätt att belysa kön på? En opinionsundersökning i linje med SCBs undersökning siktar ju bara på valresultatet som ett slag facit och ger ett extensivt mått. Men hur ser det ut när det politiska diskuteras? Ett sätt att studera det offentliga samtalet är att se vad som sägs på Facebook. På så sätt får man en mera processuell och rörelsebaserad indikation på hur det politiska byggs från handling till handling i offentligheterna. Vilka är det som dominerar i detta samtal?

För att undersöka detta laddade jag ned ett stort antal poster på de olika partiernas Facebooksidor (från 400 till 1000 poster, exklusive Kd som inte har något officiell sida) och gick igenom de ca. 100 000 namn som förekommer i kommentarstrådarna. Facebook håller förvisso ganska bra koll på vilket kön varje användare har, men denna information är inte öppet tillgänglig för övervakning via Graph-APIet som man använder för att skrapa data.

Så då fick jag lösa det med ett litet hack.

Jag körde alla hundratusen namn genom mitt lilla program gendercounter (som även utgör motorn i Genuskollen.se) och räknade hur ofta manliga och kvinnliga namn förekom (i absoluta tal, dvs. samma unika namn kan förekomma flera gånger). Resultatet blev så här:

resultat

När det kommer till kommentarer på Facebook är männen ständigt överrepresenterade, med undantag för Feministiskt initiativ som är det enda parti som har fler kommenterande kvinnor än män. Den generella trenden är att högerut är det fler män och vänsterut fler kvinnor. Men Sd sticker i denna undersökning inte ut mer än något annat högerparti. Istället är det Moderaterna som har flest män som kommenterar på sin Facebooksida.

Så här ser datamängden ut i tabellform för frekvens av namn (procenttalen avrundade, därför kan det bli 101% när man summerar):

Parti Kvinnor Män Obestämd N
Kommentarstrådar
Fi 55 32 13 10345 853
V 38 48 14 19429 998
S 37 48 15 20174 1004
Mp 34 53 13 15101 965
C 24 66 10 7512 732
L 27 65 9 10404 725
M 22 70 8 22354 1002
Sd 25 64 11 10529 427

Nu finns det flera metodproblem som jag ännu inte har löst, så dessa resultat ska läsas med försiktighet. Det som sorteras som ”obestämt” är  namn som inte förekommer i listorna som matchar namn. Det kan exempelvis röra sig om olika ”smeknamn”, men det kan även om genusneutrala namn. Jag jobbar kontinuerligt med att öka denna träffsäkerhet, men först måste man utsätta sig för riktig data innan man hittar anomalierna.

Trenden är ändå ganska tydlig. Kanske kastar denna typ av undersökningar nytt ljus på de politiska samtalens förutsättningar. Det verkar i så fall som att nästan alla Facebooktrådar är sorgligt mansdominerade.

\\

Metod / Kod

Först skrapade jag varje partis Facebooksida med följande skript. Fyll i sidornas ID-nummer och din access_token:

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

group_id =""
access_token = ""

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', i)
    with open('Partinamn/%scontent%i.json' % (group_id, i), 'w') as outfile:
        json.dump(p, outfile, indent = 4, cls=DjangoJSONEncoder, ensure_ascii=False)
    i += 1

Jag stoppade skriptet när det översteg 1000 poster för att inte översköljas av data.

Därefter körde jag följande skript för att räkna namn. Observera att jag importerar gendercounter som en modul, vilket gör att man först måste avkommentera några rader som sköter filöppning etc (se kodkommentarerna) samt lägga både gendercounter och dess namnlistor i samma katalog.

#-*- coding: utf8 -*-
import json
from json import load
from os import listdir
import gendercounter

counter = 0
kvinnocounter = 0
maencounter = 0
undetermined = 0
commentfield = 0

recordlist = []

directory = "Partinamn/"


#### Multi file parser from directory
for filename in listdir(directory):
    with open(directory + filename) as currentFile:
        jsondata = load(currentFile)

    for item in jsondata['data']:
        #print(item['status_type']) #For debugging
        if item:
            try:
                if item['comments']['data']:
                    commentfield += 1
                    print("Begin new post.")
                    for i in item['comments']['data']:
                        print("-" * 20)
                        namefield = i['from']['name']
                        splitted = namefield.split()
                        firstname = splitted[0]
                        print(firstname)
                        resultat = gendercounter.textinput(firstname)

                        if resultat[0][0]:
                            print("Kvinnonamn " + str(resultat[0][0]))
                            kvinnocounter += 1
                        elif resultat[1][0]:
                            print("Mansnamn " + str(resultat[1][0]))
                            maencounter += 1
                        else:
                            print("Couldn't determine gender")
                            undetermined += 1

            except KeyError:
                print("there was a key error")
            print("\n")


print("Results for the dicretory: " + directory)
print("There number of comment threads: " + str(commentfield))

summa = kvinnocounter + maencounter + undetermined
print("Summa namn: " + str(summa))

print("Kvinnocounter: " + str(kvinnocounter))
print("Maencounter: " + str(maencounter))
print("Undetermined: " + str(undetermined))

print("Kvinnor, " + str(kvinnocounter / summa))
print("Män, " + str(maencounter / summa))
print("Icke-bestämda, " + str(undetermined / summa))

 

Uppdateringar Genuskollen

I helgen fortsatte jag att medborgarprogrammeragenuskollen.se (och dess algoritm gendercounter.py). Jag gjorde några uppdateringar som jag tror ökar funktionaliteten. Här kommer några tekniska minnesanteckningar.

  1. Jag lade till en funktion för att detektera personliga pronomen hen/hon/han med varianter. Här finns det vissa felkällor, till exempel är ju ”Hans” både ett personligt pronomen och ett namn. Jag skrev tre funktioner för strängmatchning via reguljära uttryck som ser ut så här:
    def raknahen():
        hen = 0
        henom = 0
        hens = 0
        for t3 in text:
            henregexp = re.findall(r'\bhen\b', t3, flags = re.IGNORECASE)
            henomregexp = re.findall(r'\bhenom\b', t3, flags = re.IGNORECASE)
            hensregexp = re.findall(r'\bhens\b', t3, flags = re.IGNORECASE)
            if henregexp:
                hen += 1
            elif henomregexp:
                henom += 1
            elif hensregexp:
                hens += 1
            else:
                continue
        return(hen, henom, hens)

    Jag är inte helt säker på hur etablerade ”henom” och ”hens” är. De är ännu väldigt ovanligt förekommande. Kommentera gärna!

  2. Jag lade till funktionalitet för att ladda upp filer i formaten txt, pdf och docx, och säkrade upp filnamnen med werkzeug secure_filename. Det svåra i detta avsende är egenligen pdf. Det är ett omöjligt filformat på flera sätt och det tar väldigt stora systemresurser i anspråk att extrahera ut hyfsat ren text. Jag landade i pdfminer3k som är enkelt att använda på bekostnad av ganska långsam prestanda. Som tur är kommer nog inte genuskollen att behöva hantera många samtida användare så jag klarar mig nog med den nuvarande klena hårdvaran.
  3. Jag lade till en funktion som sparar själva resultatet av genomsökningen så att man enkelt kan länka till den. Här är ett exempel. Det uppenbara vore att strössla en sådan delningsfunktion med knappar till facebook, twitter, pinterest etc för att underlätta delningen. Men å andra sidan utsätter man då användaren för spionskript som ”ringer hem” till Silicon Valley. Det är inte helt entydigt vilken lösning som är den bästa här. Vad tycker ni?

Nästa steg i detta lilla hobbyprojekt blir nog att öka exaktheten i själva namnsökningen. Den konventionella strängmatchningen är inte tillräckligt exakt. Det finns ju namn som inte är namn, exempelvis ”Mina”, ”Hans”, ”De”, ”Dina” osv. Här har jag nu börjat snegla på olika lösningar för Named Entity Recognition, en funktion som finns bland annat i Korps annoteringslabb. På så sätt skulle man kunna förprocessera texten så att man får fram namn på en syntaktisk nivå innan man sedan matcharn namn/kön. Det finns gränssnitt för att integrera Standford NER med Python (och andra språk). Detta kräver dock lite inläsningsarbete. En annan helg!

 

 

 

Genuskollen

Häromdagen upptäckte jag att man kan köra Pythonskript på en webserver. På så sätt slipper användaren interagera med terminalgränssnittet. Eftersom beräkningarna körs på en fjärrserver behöver man inte ens ha Python installerat. Allt detta är ju självklarheter, men det kan ibland innebära en hel del handpåläggning innnan man får det att fungera.

Som jag tidigare bloggade om har jag börjat laborera med ett skript som jag kallar för Gendercounter. Det är detta skript som är motorn för själva beräknandet under huven på webtjänsten. Men nu kan man alltså enkelt räkna kvinnliga och manliga namn i vilken text som helst.

Resultat: Genuskollen (beta)

Men, det finns flera metodproblem som måste lösas innan detta blir någorlunda exakt. Därför tar jag gärna emot buggrapporter och observationer om felkällor så att jag kan förbättra tjänsten och skriptet framöver.

För den som vill köra detta på stora textmängder och vill göra mera avancerade saker är det bara att ladda ned källkoden.

Mäta gubbslem i texter – ett program

Citeringens praktiker är performativa, något som  Sara Ahmed reflekterar över inför sin kommande bok. Hon har valt att inte citera en enda vit man. Men hur ser det ut i vilken text som helst?

För det mesta reflekterar vi inte över vilka vi citerar eller omnämner när vi skriver. Ofta går det av bara farten och helt plötsligt sitter vi med en riktigt gubslemmig text. Med ”citering” kan man förstå även intervjupersoner och vilka vi skriver om i texterna. På så sätt kan man även inkludera journalistiska texter i analysen.

Jag skrev ett litet skript som räknar förekomsten av manliga och kvinnliga namn i en text. Programmet heter Gendercounter (ursäkta min dåliga fantasi). Programmet kollar varje ord i en text och matchar det sedan mot två listor, en med manliga namn och en med kvinnliga. Det finns förvisso ett flertal metodologiska problem med detta angrepssätt (namn är inte en hundraprocentig enkönade). Men man kan ändå använda detta som en första indikator.

Här kommer några tester jag gjorde. (uppdaterade efter att jag hittat några buggar)

Jag började med den Statliga offentliga utredningen Om Sverige i framtiden – en antologi om digitaliseringens möjligheter (ren textversion länkad). Resultat: 281 Kvinnor, 597 Män.

Sedan en lite äldre text, Den sociala och kulturella utvecklingen från Oskar I:s tid till våra dagar samt De politiska förhållandena under Karl XV:s, Oscar II:s och Gustaf V:s regering 1859-1923 Resultat: 418 Kvinnor, 1447 Män.

Till sist, Teknisk tidskrift årgång 1962 (1368 sid.) Resultat: 537 Kvinnor, 2472 Män.

Skriptet är än så länge bara ett test. Men det borde finnas intressanta saker man kan göra med denna form av kvantitativa innehållsanalys, givet att man har i bakhuvudet de metodologiska fallgroparna.

Testa gärna skriptet själva, instruktioner finns på Github-sidan. Fråga gärna i kommentarerna om något är oklart.

Förslag och kommentarer mottages gärna!

Sara Ahmed, lycka, olycka och falskt medvetande

Den 9 februari håller jag föredrag om Sara Ahmed, lycka, olycka och falskt medvetande på ABFs filosofiscen. Detta knyter an till min pågående forskning om lyckoforskning, men jag kommer även att diskutera Ahmeds kritik av ”affektfilosofin” och dess betoning på ”joy”, bland så som den kommer till uttryck hos Braidotti och Colebrook. I detta mellanrum ryms en ny form av lyckokritk som går bortom Benthams och Aristoteles välkända positioner.

Samförfattarnätverk ur Swepub?

Och nu ännu mera bibliometri. Swepubs databas innehåller via API-funktionen uppgifter om forskares institutionstillhörighet. En kollega ringde för ett par veckor sedan och påpekade att detta skulle kunna användas för att studera bland annat disciplin- och fakultetsgränser i relation till den faktiska forskningen som bedrivs.

Två personer som samförfattar en artikel kan ju sägas samarbeta. Om de gör det över disciplingränser kallar vi det för inter/tvär/disciplinär/vetenskap, beroende på perspektiv. Fördelen med den monadologiska nivån är att vi varken behöver göra avkall på den kvaliativa mikronivån eller den aggregerade makronivån. Vi kan zooma in och ut.

Med Skript A (se nedan) hämtade jag alla artiklar som publicerats på Göteborgs universitet 2015-16. Sedan genererade jag en visualiserbar nätverksfil med Skript B. Därefter gjorde jag några justeringar i Gephi: Jag tog bort alla institutioner förutom de som ryms inom samhällsvetenskaplig och humanistisk fakultet. Sedan sorterade jag ut alla ensamförfattare och inkluderade bara artiklar med minst två författare.

Med lite färger och annat visuellt förtydligande får vi en bilds som vi kan ta fram i en helhet som en pdf. Här klustrar sig forskargrupperna ungefär så här:

Screenshot from 2016-01-18 22:28:33

Ett typiskt göteborgskluster: Statsvetenskap och medie- och kommunikationsvetenskap.

Eller:

Screenshot from 2016-01-18 22:30:33

Överskridande av fakultetsgränser. Bra jobbat!

Eller:

Screenshot from 2016-01-18 22:33:15

Min egen forskningsmiljö. Inte superbra. (till mitt försvar måste jag tillägga att Tillämpad IT inte är med i datamängden som jag samarbetar med).

Om någon vill laborera med visualiseringarna går Gephi-filen att ladda ned här.

Nu vet jag inte riktigt vad detta leder någonstans eller hur det kan göras mera överskådligt. Förslag?

 

// Bilagor (Python3)

Skript A

from urllib.request import urlopen

counter = 1

#while True:
while counter < 50000:
  url = 'http://libris.kb.se/xsearch?d=swepub&q=%C3%A5r%3a%282015%29&hitlist&q=l%C3%A4ros%C3%A4te%3agu&f=ext&spell=true&hist=true&n=200&start=' + str(counter)
  print ("Fetching: " + url)
  data = urlopen(url).read()
    #f not data.find(b'"identifier"') >= 0:
  #	print("No more records!")
  #	break
  with open(str(counter) + ".xml", "wb") as outputfile:
    print("Writing file...")
    outputfile.write(data)
  counter += 200

Skript B

from os import listdir
from lxml import etree as ET
from gexf import *
from itertools import combinations
#import xml.etree.ElementTree as ET #Use this if you don't have lxml installed

# Open up a gexf file
gexf = Gexf("Author-Institution network", "GU")
graph = gexf.addGraph("undirected", "static", "Swepub network")
attribute_node = graph.addNodeAttribute("University", "default_value", "string")
attribute_nodetwo = graph.addNodeAttribute("Institution", "default_value", "string")

records = 0 # Just a counter for control
therecords = [] # A list for storing many dictionaries created in the loop below

# The loop for extracting author/institution from the xml-files
for filename in listdir("GUYear2015N47761Searched20160110/"):
    with open("GUYear2015N47761Searched20160110/" + filename) as currentFile:
        tree = ET.parse(currentFile)
        root = tree.getroot()

        for child in root[0]:
            records += 1 #Add to counter above
            #print("-" * 10)
            coauthors = {}
            for c in child: #This iterates over the records
                if c.get("tag") == "100": # The 100 Value is first author
                    authorlist = []
                    for value in c:
                        if value.get("code") == "a": # a is author name
                            author = value.text
                            #print(author)
                        elif value.get("code") == "u": # u is institution
                            #print(value.text)
                            institution = value.text
                            #print(institution)
                            coauthors.update({author: institution})

                elif c.get("tag") == "700": # The 700 value is authors
                    for value in c:
                        if value.get("code") == "a":
                            author = value.text
                            #print(author)
                        elif value.get("code") == "u":
                            #print(value.text)
                            institution = value.text
                            #print(institution)
                            coauthors.update({author: institution})

            therecords.append(coauthors) # Add each dictionary to the list above

#this removes only EXACT duplicate dictionaries from therecords list
#Possible false negative: Two articles may have identical groups of authors
seen = set()
therecordsdeduplicated = []
for d in therecords:
    t = tuple(d.items())
    if t not in seen:
        seen.add(t)
        therecordsdeduplicated.append(d)

#this creates a list which can be used to create edges
edges = []
coauthorcounter = 0 #just a counter
for t in therecordsdeduplicated:
    if len(t) > 1: #This removes single author articles, only more than one authors are allowed
        coauthorcounter += 1 # counts the number of articles with minimum 2 authors
        #print("--")
        #print(len(t))
        edgelist = []
        for key, value in t.items():
            coauthoredge = list(combinations(t, 2)) #This function calculates all possible relations between authors of an article.
            for c in coauthoredge:
                edgelist.append(c)
            newvalue = value.split(', ')
            n = graph.addNode(key, key)
            try:
                print(newvalue[0])
                n.addAttribute(attribute_node, newvalue[0])
            except IndexError:
                print("nothing here...")
            try:
                print(newvalue[1])
                n.addAttribute(attribute_nodetwo, newvalue[1])
            except IndexError:
                print("nothing here...")


            #graph.addNode(value, value) #This adds the Institutions as nodes
        edges.append(edgelist)
        #print(edgelist)

#this creates a list of edges then enumerates it and creates edges
authoredges = []
for e in edges:
    for ee in e:
        #print(ee)
        authoredges.append(ee)

for enumer, e in enumerate(authoredges):
    #print(enumer, e[0], e[1])
    graph.addEdge(enumer, e[0], e[1])


# Print some meta-data
print("There are " + str(records) + " records")
print("There are " + str(coauthorcounter) + " co-authored articles (more than 1 author)\n")

#write file
gexf_file = open("coauthors.gexf", "wb")
gexf.write(gexf_file)

 

Hur programmera mot Swepubs csv-filer?

Swepub har lanserat en bibliometrisida som låter användare specificera en sökning och sedan ladda ned resultaten i ett flertal öppna format (csv, tsv, xml, json). Detta är väldigt positiva nyheter för oss forskare som vill göra scientometriska analyser, och äntligen ett bibliometriskt index som inte är kontrollerat av stora företag!

Men jag funderar nu på hur jag på ett smart sätt ska kunna programmera mot den data man får ut ur Swepubsystemet. Det är nämligen så att varje artikel förekommer flera gånger. Om man till exempel sparar ned en csv-fil ser det ut så här om man öppnar den i exempelvis LibreOffice (eller Excel):

Screenshot from 2016-01-16 14:34:32

Varje artikel har ett unikt Swepubnummer, vilket gör det enkelt att isolera artikelnivån. Men sedan är varje författare angiven i en ny rad, istället för att vara inkluderade men separerade inom ett och samma värden, vilket är fallet med data från Web of Science och Scopus. Detta är lite ”jobbigt”. Men givetvis går det att ordna. Jag letar nu efter den enklaste och mest eleganta lösningen och behöver lite input.

En av de mest intressanta analyserna är samförfattarskap. Att se vilka som skriver artiklar tillsammans kan vara ett väldigt kraftfullt mått på interdisciplinaritet, internationalisering, forskningspraktiker osv. Men då måste man för varje artikel få en lista på författare (och gärna så mycket meta-data om dessa författare som möjligt, ex. institutionstillhörighet).

Csv-filen är superenkel att läsa in till Python:

import csv

thefile = open('query_result.csv', 'r')
csv = csv.reader(thefile, delimiter=',')
next(csv) #remove headers

for c in csv:
    print(c[24]) #put here what values you want, see list below.

Därefter är det bara att välja vilken meta-data man önskar. Jag begriper inte allt, men här är ett första index (om ni vet mer kommentera gärna):

 """
[0] = Swepub ID-nummer
[1] = Organisation/Lärosäte (ex. "gu")
[2] = Publikationsår
[3] = Status (ex. "PUBLISHED")
[4] = Publikationstyp
[5] = Innehållstyp
[6] = Publikationstyp (igen)
[7] = Open Access-status (ex. "gold", "green")
[8] = "_hsv1" ?
[9] = "_hsv3" ?
[10] = "_hsv5"?
[11] = Titel
[12] = Antal författare
[13] = "_numLocalCreator", ?
[14] = Författar (namn)
[15] = Användarnamn i systemet (ex. xjsode)
[16] = ORCID (se http://orcid.org)
[17] = Affiliering (ex. "gu.se")
[18] = Källa (ex. "Journal of Python Snakes")
[19] = ISSN
[20] = Förlag
[21] = URL
[22] = Fulltext (länk till URL)
[23] = ISI-nummer (Web of Science)
[24] = DOI
[25] = Scopus-nummer
[26] = Pubmed-nummer
[27] = ISBN
[28] = "_projekt", ?
[29] = "_program", ?
[30] = "_contract", ?
[31] = "_dubblettID" ?
"""

Eftersom Swepub ID är unikt, är det ju enkelt att isolera artiklar var för sig. Men jag skulle vilja skriva en funktion som gjorde ungefär så här:

För varje unikt Swepub-ID, gå igenom alla författare och bygg om [14] till en lista (eller kanske en lista av dictionaries/hashtabeller) som inkluderar författarnamn + ORCID + Affiliering.

På så sätt kan man sedan enkelt ta varje artikel och genast skapa ett co-authornätverk. Här finns massor av intressanta analyser att göra. Hur mycket tvärvetenskap finns på svenska universitet? Hur internationella är forskare egentligen? Vilka institutioner samarbetar ofta?

Men allt hänger på att man hittar en smidig lösning på att varje artikel förekommer flera gånger och att metadata kan sammafogas på ett smart sätt.

Någon som har en intressant och elegant strategi för att bygga om datamängden så här? Kanske är det enklare med json?

Länk till datamängden jag jobbar mot (1.7 Mb, zip)

Uppdatering: Mattias Östmar har löst problemet så här. (se även kommentarerna nedan).

Vad är medborgarforskning?

Igår publicerades min och Dick Kasperowskis studie What is Citizen Science – A Scientometric Meta-analysis i open-accesstidskriften PLOS ONE. I ett pressmeddelande på svenska förklarar vi att:

Medborgarforskning, eller ”citizen science”, har på allvar slagit igenom inom flera vetenskapliga discipliner. Fenomenet finns både inom natur- och samhällsvetenskaperna, visar den hittills största systematiska analysen av medborgarforskning, som publiceras i tidskriften PLOS ONE.

− Vi ser att forskare inom naturvetenskaperna framför allt har samlat in och klassificerat data med hjälp av intresserade volontärer. Inom samhällsvetenskaperna har fokus legat på att bjuda in berörda delar av allmänheten för att få kunskap om hur forskningen påverkar deras liv. Det kan till exempel handla om miljöproblem och risker, säger Christopher Kullenberg, vetenskapsteoretiker.

Det som är speciellt roligt med PLOS ONE är, förutom att den är helt öppen för vem som helst att läsa, är att all källdata också går att ladda ned. Detta gör inte bara att man kan replikera och verifiera/falsifiera våra resultat, utan detta är även värdefullt för den som vill göra andra typer av analyser med materialet än vad vi har gjort. Dessutom kan man med öppen data bygga vidare från resultat till resultat på ett intressant sätt. Humaniora har nog en del att lära av naturvetenskaperna på detta område.

Dessutom är det fritt att använda alla våra visualiseringar och figurer, så länge man refererar korrekt!

\\

För den datorintresserade:

PLOS ONE har en policy att all data ska vara helt och hållet replikerbar, figur för figur. Eftersom Excel är ett svåranvänt program för att göra saker med data fick jag göra en liten bisarr lösning. I S1 Appendix finns ett litet Pythonskript som emulerar en Topic-sökning i Web of Science. Detta används för att räkna hur många artiklar ett citizen science-projekt har producerat, men man måste ta ut och återimportera data ur Excel/LibreOffice för att kunna återskapa detta resultat. Det är lite otydligt när det bara är inklistrat i ett kalkylark, men så här ser det ut:

#Use with Python 3 or greater
import re

#Export the raw data from the Excel sheet as a comma separated file.
with open('NamesSearch633recs20151016.csv','r') as csv:
    next(csv) #skip the first line containing only headers
    WoSdata = [line.strip().split('","') for line in csv] # Change the delimiter according to your desired output. Could be \t or simply one comma.

#Save the project names from the Excel file as a plain text file delimited with a newline (\n)
with open('projectnames.txt', 'r') as thefile:
    projectnames = [] #build a list to iterate the search below
    for line in thefile:
        projectnames.append(line.rstrip().split("\n"))

for n in projectnames: #Iterate over each project name to build a search string
    searchword = n[0] #This accesses the project name
    counter = 0 #reset the counter to count occurences.
    for W in WoSdata:
        TSsearch = W[4] + W[8] + W[10] #This emulates the WoS Topic Search, accessing Title, Keywords, and Abstract (but leaves out keyword plus as found in W[9])
        match = re.findall(searchword, TSsearch, flags=re.IGNORECASE) #Ignorecase is needed (as this is default in WoS TS)
        if match:
            counter +=1

    print(searchword + "\t" + str(counter))

#To re-import to excel, pipe the output to a new text file,
#which will in turn be a tab-separated file that can nicely
# be pasted into a spreadsheet and then sorted

Detta hade man säkert kunnat göra inuti Excel, men det känns icke-transparent på något sätt. För att emulera en Topic-sökning i Web of Science så söker man efter ett ord i Titel, Abstract eller Keywords. Nuförtiden har WoS lagt till Keyword Plus (maskininlärda nyckelord), vilket är väldigt oberäkneligt, eftersom man då måste veta exakt hur Keyword Plus-algoritmen i sin tur är tränad. Så lösningen ovan tar bort det problemet. Detta är en slags funktionalitet som jag tänkte skriva rent som en enskild funktion, men även lägga in i Citepy.

Citepy

När man analyserar bibliometrisk data behöver man ofta mjukvara för att göra den överskådlig. Ett program som tidigare varit populärt är Histcite. Men detta program fungerar bara i (gamla versioner av) Windows, och dessutom verkar det som att det mer eller mindre verkar ha förfallit som abandonware. Den slutna koden gör även att det inte går att förbättra programmet.

Så jag tog mig an uppgiften att försöka skriva ett program i Python med motsvarande funktionalitet. Men även så att funktionaliteten kan byggas ut efter behov. Man kan säga att jag kommit ungefär halvvägs. Detta halvfärdiga arbete har jag nu publicerat på Github under namnet Citepy.

Jag har även lagt upp en live-demo av ett litet dataset (10 artiklar). Den som har använt Histcite tidigare känner igen sig. Men med Python är det ju väldigt enkelt att utöka funktionaliteten. Så jag har bland annat gjort så att det automatiskt skapas nätverksfiler som kan öppnas i Gephi och lagt till en enkel visualisering av artiklar/år.

Målet är att fortsätta utveckla programmet tills det helt ersätter funktionaliteten i Histcite och överskrider den med funktioner som är användbara för forskare.

Så som programmet är i nuläget har användarvänlighet nedprioriterats till förmån för att man ska kunna forma det efter sina egna behov. Kanske framöver går det att bygga ett enklare gränssnitt och en Windows-executable.

Programmet är licensierat med MIT-licensen, så man får göra vad man vill med det ungefär.

Om någon hittar något användbart, hojta gärna till i kommentarerna. Även förslag till vilka funktioner som kan vara användbara tas emot varmt.

 

Hämta och behandla bibliometrisk data från SwePub

SwePub är en databas som svenska universitet och högskolor rapporterar in vetenskapliga publikationer i. Den administreras av Kungliga biblioteket och det som är intressant med denna databas är att den innehåller många fler typer av texter än de stora vetenskapliga databaserna (Scopus, Web of Science etc.). Här förekommer till exempel populärvetenskap och rapporter som inte skulle kvalificera sig för de stora databaserna.

En annan aspekt är hur denna databas kan användas för att administrera tilldelning av forskningsmedel. Men det kan vi lämna åt sidan.

En tredje aspekt, som är väldigt intressant, är att SwePub har ett API utan några begränsningar! Jämfört med de kommersiella databaserna är detta fantastiskt bra datakommunalism som bör uppmuntras!

Med lite programmering kan man enkelt hämta och behandla både stora och precisa datamängder. Här följer några snuttar Python(3)-kod som visar vad som kan göras på en eftermiddag.

Uppdatering: Det går att skippa detta steg. Fick precis reda på att man kan få datan på ett mycket enklare sätt via SwePubs (ännu beta-) funktion för uttag av data. Steget nedan innebär alltså att gå över ån efter vatten 😀

Uppdatering 2: Det verkar ändå som att man måste använda denna metod för att komma åt institutionstillhörighet. Se denna post + kommentarer.

För att hämta data anger man bara en söksträng så returnerar API:t datamängden i ett urval av format (grundläge är XML och 10 artiklar åt gången). Så här sökte jag alla de 91622 artiklar som svarade mot en sökning på lärosäte Göteborgs universitet. Först själva URLen:

http://libris.kb.se/xsearch?d=swepub&hitlist&q=lärosäte%3gu&f=ext&spell=true&hist=true&n=200&p=1

Det viktiga här är dels databasen d=swepub samt själva söksträngen q=lärosäte%3gu. Se även hur jag får den att returnera 200 artiklar. Om man vill kan man lägga till &format=json för att få tillbaka json-data. Men då kommer man dels att stöta på några datafel som beror på att SwePub verkar tillåta en del tecken som är förbjudna i json. Dessutom innehåller inte json alla datafält.

Men ett enkelt anrop ger bara 200 artiklar åt gången. Men eftersom API:t är obegränsat är det bara att fråga om och om igen. Med lite hjälp från @skagedal skapades följande skript:

from urllib.request import urlopen

counter = 1

while True:
  url = 'http://libris.kb.se/xsearch?d=swepub&hitlist&q=l%C3%A4ros%C3%A4te%3agu&f=ext&spell=true&hist=true&n=200&start=' + str(counter)
  print ("Fetching: " + url)
  data = urlopen(url).read()
  if not data.find(b'"identifier"') >= 0:
    print("No more records!")
    break
  with open(str(counter) + ".json", "wb") as outputfile:
    print("Writing file...")
    outputfile.write(data)
  counter += 200

Vad som händer här är att skriptet skriver en fil för varje omgång om 200 artiklar. Tio minuter och 240 megabytes senare har man fått hem alla nittio tusen referenser. Att göra motsvarande i de begränsade kommersiella databaserna är en plåga!

Därefter ska xml-datan läsas in. Jag blir lätt förvirrad av xml, men jag fick hjälp av @jimmycallin att reda ut alla turer man måste ta för att komma åt (i det här fallet) Titel, Författare och Institution. Så här ser ett utsnitt av datan ut:

<datafield tag="245" ind1="1" ind2="0">
 <subfield code="a">Variability in quality of life 13 years after traumatic brain injury in childhood</subfield>
</datafield>

<datafield tag="700" ind1="1" ind2=" ">
 <subfield code="a">Emanuelson, Ingrid,</subfield>
 <subfield code="d">1955-,</subfield>
 <subfield code="u">Göteborgs universitet, Institutionen för kliniska vetenskaper, sektionen för kvinnors och barns hälsa, Avdelningen för pediatrik, University of Gothenburg, Institute of Clinical Sciences, Section for the Health of Women and Children, Department of Pediatrics</subfield>
 <subfield code="4">aut</subfield>
 <subfield code="0">(SwePub:chalmers.se)xeming</subfield>
</datafield>

<datafield tag="700" ind1="1" ind2=" ">
 <subfield code="a">Charlotte Smedler, Ann</subfield><subfield code="4">aut</subfield>
 <subfield code="0">(SwePub:chalmers.se)227391</subfield>

</datafield>
<datafield tag="700" ind1="1" ind2=" ">
 <subfield code="a">Smedler, Ann-Charlotte,</subfield>
 <subfield code="d">1948-,</subfield>
 <subfield code="u">Stockholms universitet, Psykologiska institutionen</subfield>
 <subfield code="4">aut</subfield>
 <subfield code="0">(SwePub:su)acsr</subfield>
</datafield>

Det som jag vill hämta här är dels titeln och sedan författare och deras institutionstillhörighet. Detta för att kunna studera samförfattarskap över institutionsgränser (ett tänkbart mått på tvärdisciplinaritet).

Så här kan man komma åt den informationen (och printa den):

from os import listdir
from lxml import etree as ET
#import xml.etree.ElementTree as ET #Use this if you don't have lxml installed

for filename in listdir("GU20151228N91622/"):
    with open("GU20151228N91622/" + filename) as currentFile:
        tree = ET.parse(currentFile)
        root = tree.getroot()

        for child in root[0]:
            for c in child:
                if c.get("tag") == "245":
                    for value in c:
                        if value.get("code") == "a":
                            print("-" * 50)
                            print(value.text)
                elif c.get("tag") == "700":
                    for value in c:
                        if value.get("code") == "a":
                            print(value.text)
                        elif value.get("code") == "u":
                            print(value.text)

Observera att jag i skriptet ovan har lagt alla filer i katalogen GU20151228/. Ur detta får vi sedan följande output:

Diagnosis and treatment of premenstrual dysphoria.
Andersch, Björn,
Göteborgs universitet, Institutionen för kvinnors och barns hälsa, Avdelningen för obstetrik och gynekologi, University of Gothenburg, Institute for the Health of Women and Children, Dept of Obstetrics and Gynaecology
Ho, Hoi-Por,
Göteborgs universitet, Institutionen för fysiologi och farmakologi, Avdelningen för farmakologi, University of Gothenburg, Institute of Physiology and Pharmacology, Dept of Pharmacology
Landén, Mikael,
Göteborgs universitet, Institutionen för klinisk neurovetenskap, Sektionen för psykiatri, University of Gothenburg, Institute of Clinical Neurosciences, Section of Psychiatry

Artikeln ovan är alltså resultatet av tre författare på tre olika institutioner. Intressant. Detta kan givetvis mätas och visualiseras. Men det blir en annan lat eftermiddags hackande.