[PYTHON] Ecrire un réseau de co-auteurs dans un domaine spécifique en utilisant les informations d'arxiv

Préface

C'est un contenu avec lequel je joue en collectant des informations d'arxiv et en essayant d'obtenir des informations utiles. La procédure de ce que j'ai fait cette fois est la suivante.

  1. Rassemblez les titres de papier, les résumés, les auteurs, etc. soumis dans un certain délai

Je me demande si je peux voir un cluster tel qu'un grand groupe de recherche dans le domaine de la recherche lié aux mots-clés appropriés. Enfin, le réseau de co-auteurs (conditionné par le nombre d'articles) tel qu'illustré dans le graphique ci-dessous est extrait.

Il s'agit d'un réseau de co-auteurs lorsque les articles quant-ph d'arxiv de 2015 à 2020 qui incluent "quantum comput" dans le titre / absolu sont sélectionnés et que seuls les auteurs avec 15 articles ou plus applicables sont sélectionnés. (Le nombre d'articles co-rédigés et la normalisation pour chaque groupe)

** (Ajout: mise à jour de la fluctuation de la notation du nom de l'auteur et correction des données) **

スクリーンショット 2020-05-06 01.59.56.png

Collecter des informations auprès d'arxiv

Scraping normalement en Python. (Il y avait aussi une API arxiv) Appuyez directement sur la recherche avancée d'arxiv pour le récupérer. Les informations collectées chaque année sont converties en un DataFrame pandas et enregistrées sous forme de fichier csv.

Les informations à collecter

--Cite: arxiv: XXXXX. --Titre: Titre du papier --Abst: Abst --Authors: enregistre en tant que liste d'auteurs, une liste de noms d'auteurs dans le texte lié et un tuple de chaînes utilisées dans les requêtes de recherche. --Fields: informations sur le terrain. S'il s'agit d'une liste croisée, plusieurs fichiers seront enregistrés. --DOI: Get si DOI est ajouté. --OrigDateY: Première année d'affichage --OrigDateM: premier mois de publication --Date Info: enregistre les informations de date telles que les autres révisions sous forme de texte.

Après tout, il y a des informations que je n'ai pas utilisées cette fois.

** * Correction du code car il ne fonctionne pas en raison des spécifications d'arxiv si le nombre de cibles de recherche pour un an dépasse 10 000. Si vous vérifiez le nombre de cas et qu'il dépasse, effectuez une recherche mensuelle et collectez des données. ** **

from bs4 import BeautifulSoup
import requests
import re
import pandas as pd
import numpy as np
import math 

def month_string_to_number(string):
    m = {
        'jan': 1,
        'feb': 2,
        'mar': 3,
        'apr':4,
         'may':5,
         'jun':6,
         'jul':7,
         'aug':8,
         'sep':9,
         'oct':10,
         'nov':11,
         'dec':12
        }
    s = string.strip()[:3].lower()

    try:
        out = m[s]
        return out
    except:
        raise ValueError('Not a month')

#Obtenez le nombre de résultats de recherche sur la page de résultats de recherche
def get_number_of_searchresult(url):
    html_doc = requests.get(url).text
    soup = BeautifulSoup(html_doc, "html.parser") 
    tags = soup.find_all("h1",{"class":"title is-clearfix"})
    text = tags[0].text.strip()
    if "Showing" in text and "results" in text:
        stext = text.split(" ")
        datanum = int(stext[3].replace(',', ''))#Obtenez le nombre de résultats de recherche
    else:
        datanum=math.nan
    return datanum

#Obtenir des informations à partir des résultats de recherche et de np.ndarray
def collect_info_from_advancedsearch(urlhead, datanum,key):

    titles=[]#Liste pour le stockage des données
    absts=[]#Abst
    cites=[]#citer des informations(arxiv:XXXXXX)
    authors=[]# author
    dates=[]#Informations sur la date
    dates_orig_m =[]#Premier mois de publication
    dates_orig_y =[]#Première année d'affichage
    dois = []#doi
    fields=[]#cross-Informations sur le terrain, y compris la liste

    startnum=0
    while datanum > startnum:
        print(str(startnum)+"...", end="")
        url = urlhead+str(startnum)#URL de recherche avancée
        html_doc = requests.get(url).text
        soup = BeautifulSoup(html_doc, "html.parser") 

        #informations sur le titre
        tags1= soup.find_all("p", {"class": "title is-5 mathjax"}) 
        titles_tmp = [tag.text.strip() for tag in tags1]

        #information abst
        tags2 = soup.find_all("span", {"class": "abstract-full has-text-grey-dark mathjax"})
        absts_tmp = [tag.text[:-7].strip() for tag in tags2 if "Less" in tag.text]

        #citer des informations
        tags3 =soup.find_all("p", {"class": "list-title is-inline-block"})
        cites_tmp = [tag.select("a")[0].text for tag in tags3]

        #Informations sur la date
        tags4 = soup.find_all("p",{"class":"is-size-7"})
        text = [tag.text.strip() for tag in tags4 if "originally announced" in tag.text ]
        dates_tmp = text
        dates_orig_y_tmp=[txt.split(" ")[-1][:-1] for txt in text]
        dates_orig_m_tmp=[month_string_to_number(txt.split(" ")[-2]) for txt in text]

        #Informations DOI
        tags5 = soup.find_all("div",{"class":"is-marginless"})
        dois_tmp = [tag.text[tag.text.rfind("doi"):].split("\n")[1] for tag in tags5 if key in tag.text ]   

        #Informations sur l'auteur
        tags6= soup.find_all("p", {"class": "authors"}) 
        auths_tmp = []
        for tag in tags6:
            auths=tag.select("a")
            authlist=[(author.text,author.get("href")[33:]) for author in auths]
            auths_tmp.append(authlist)

        # Cross-informations de liste
        tags7= soup.find_all("div", {"class": "tags is-inline-block"})  # title#("span", {"class": "tag is-small is-link tooltip is-tooltip-top"})  # title
        fields_tmp=[tag.text.strip().split("\n") for tag in tags7]

        #Ajouter aux résultats
        titles.extend(titles_tmp)
        absts.extend(absts_tmp)
        cites.extend(cites_tmp)
        authors.extend(auths_tmp)
        dates.extend(dates_tmp)
        dates_orig_y.extend(dates_orig_y_tmp)
        dates_orig_m.extend(dates_orig_m_tmp)
        dois.extend(dois_tmp)
        fields.extend(fields_tmp)
        
        #Mettre à jour le numéro de départ sur la page suivante des résultats de recherche
        startnum += sizenum
        
    nt = np.array(titles)
    na = np.array(absts)
    nauth = np.array(authors)
    ncite = np.array(cites)
    nd = np.array(dates)
    ndy = np.array(dates_orig_y)
    ndm = np.array(dates_orig_m)
    ndoi = np.array(dois)
    nfields=np.array(fields)
    npdataset = np.concatenate([[ncite],[nt],[na],[nauth],[nfields],[ndoi],[ndy],[ndm],[nd]],axis=0).T
    print(" collected data number : ", npdataset.shape[0])
    return npdataset

#Dictionnaire pour spécifier la classification de la cible de recherche de la requête de recherche
dict_class={'cs': '&classification-computer_science=y',
 'econ': '&classification-economics=y',
 'eess': '&classification-eess=y',
 'math': '&classification-mathematics=y',
 'q-bio': '&classification-q_biology=y',
 'q-fin': '&classification-q_finance=y',
 'stat': '&classification-statistics=y',
 'all': '&classification-physics=y&classification-physics_archives=all',
 'astro-ph': '&classification-physics=y&classification-physics_archives=astro-ph',
 'cond-mat': '&classification-physics=y&classification-physics_archives=cond-mat',
 'gr-qc': '&classification-physics=y&classification-physics_archives=gr-qc',
 'hep-ex': '&classification-physics=y&classification-physics_archives=hep-ex',
 'hep-lat': '&classification-physics=y&classification-physics_archives=hep-lat',
 'hep-ph': '&classification-physics=y&classification-physics_archives=hep-ph',
 'hep-th': '&classification-physics=y&classification-physics_archives=hep-th',
 'math-ph': '&classification-physics=y&classification-physics_archives=math-ph',
 'nlin': '&classification-physics=y&classification-physics_archives=nlin',
 'nucl-ex': '&classification-physics=y&classification-physics_archives=nucl-ex',
 'nucl-th': '&classification-physics=y&classification-physics_archives=nucl-th',
 'physics': '&classification-physics=y&classification-physics_archives=physics',
 'quant-ph': '&classification-physics=y&classification-physics_archives=quant-ph'}

years = [y for y in range(2015,2020)]
key = "quant-ph"#Rechercher le champ cible dict_Spécifiez la clé de classe
output_fname="df_quant-ph" #Nom du fichier de sortie

url0="https://arxiv.org/search/advanced?advanced=&terms-0-operator=AND&terms-0-term=&terms-0-field=title"
url1="&classification-include_cross_list=include"
url_daterange="&date-year=&date-filter_by=date_range"
url2="&date-date_type=submitted_date&abstracts=show&size="
urlmid = "&order=-announced_date_first&start="

sizenum = 25
startnum=0
for year in years:
    m_divide = 1 #Nombre de divisions dans la période de recherche
    mstart =1
    mstop = 1
    url_date = "&date-from_date="+str(year)+"-"+str(mstart).zfill(2)+"-01&date-to_date="+str(year+1)+"-"+str(mstop).zfill(2)+"-01"
    urlhead = url0+dict_class[key]+url1+url_daterange+url_date+url2
    urlmid = "&order=-announced_date_first&start="
    url = urlhead+str(sizenum)+urlmid+str(startnum)
    
    datanum=get_number_of_searchresult(url) #Obtenez le nombre de résultats de recherche
    print("Number of search results ("+str(year)+") : "+str(datanum))
    
    if datanum >=10000: #Si le nombre de cas dépasse la limite, l'acquisition de données pour un an sera divisée par mois.
        m_divide = 13
        for month_divide in range(2,12): #Recherchez le nombre de divisions où le nombre de cas individuels est inférieur ou égal à la limite
            flag_numlimit = False
            for idx in range(month_divide):
                mstart = int(idx*12/month_divide+1)
                mstop = (int((idx+1)*12/month_divide)+1)%12
                if mstop !=1:
                    url_date = "&date-from_date="+str(year)+"-"+str(mstart).zfill(2)+"-01&date-to_date="+str(year)+"-"+str(mstop).zfill(2)+"-01"
                else:
                    url_date = "&date-from_date="+str(year)+"-"+str(mstart).zfill(2)+"-01&date-to_date="+str(year+1)+"-"+str(mstop).zfill(2)+"-01"
                    
                urlhead = url0+dict_class[key]+url1+url_daterange+url_date+url2
                url = urlhead+str(sizenum)+urlmid+str(startnum)
                datanum=get_number_of_searchresult(url)#Obtenez le nombre de données pour chaque nombre de divisions
                if datanum >= 10000:
                    flag_numlimit = True
            if not flag_numlimit:
                m_divide = month_divide
                break
        if m_divide > 12:
            print("*** Number of search result is over the limit 10,000. Please refine your search. ***")
    
    sizenum=200
    npdataset = np.empty((0,9))
    for idx in range(m_divide):
        mstart = int(idx*12/m_divide+1)
        mstop = (int((idx+1)*12/m_divide)+1)%12
        if mstop !=1:
            url_date = "&date-from_date="+str(year)+"-"+str(mstart).zfill(2)+"-01&date-to_date="+str(year)+"-"+str(mstop).zfill(2)+"-01"
        else:
            url_date = "&date-from_date="+str(year)+"-"+str(mstart).zfill(2)+"-01&date-to_date="+str(year+1)+"-"+str(mstop).zfill(2)+"-01"
 
        urlhead = url0+dict_class[key]+url1+url_daterange+url_date+url2       
        url = urlhead+str(25)+urlmid+str(0)
        datanum=get_number_of_searchresult(url)
        
        print("Collect search results ..." + url_date + ", Number of search results : " + str(datanum))
        urlhead2 = urlhead+str(sizenum)+urlmid
        npdataset_tmp = collect_info_from_advancedsearch(urlhead2,datanum,key)
        npdataset = np.concatenate([npdataset, npdataset_tmp], axis=0)
        
    #Convertissez une année d'informations en Numpy et Pandas DataFrame et enregistrez-les au format CSV
    dataset = pd.DataFrame(npdataset)
    dataset.columns =["Cite","Title","Abst","Authors","Fields","DOI", "OrigDateY","OrigDateM","Date Info"]
    dataset.to_csv(output_fname+str(year)+".csv")
    

Extraire des articles contenant des mots-clés spécifiques

Lisez et concaténez le csv collecté chaque année Extrayez les titres et les résumés contenant des mots-clés pour les ensembles de données concaténés. Cette fois, le mot clé est le calcul quantique.

fname_head = "df_quant-ph"

fname = fname_head + str(2020)+".csv"
dataset = pd.read_csv(fname, index_col=0)

for year in range(2010,2020):
    fname = fname_head + str(year)+".csv"
    print(fname)
    dataset_tmp = pd.read_csv(fname, index_col=0)
    dataset = pd.concat([dataset,dataset_tmp])

dataset =dataset.reset_index()
dataset_r=dataset.query('title.str.contains("quantum comput")  or abst.str.contains("quantum comput")', engine='python')

Cependant, je pensais que ce formulaire ne serait pas en mesure de rechercher correctement les problèmes de cas et d'autres mots de recherche. Nous avons également ajouté le traitement Stemming en utilisant nltk. Il est basé sur ici. J'ai écrit Lemmatization pour le moment, mais je le commente maintenant.

import re
import nltk
from nltk.corpus import stopwords
from nltk.stem.porter import PorterStemmer
from nltk.stem.wordnet import WordNetLemmatizer
from nltk.corpus import stopwords

def preprocess_text(text):
    text = re.sub('[^a-zA-Z]', ' ', text)#Remove punctuations
    text = text.lower()
    text=re.sub("</?.*?>"," <> ",text)#remove tags
    text=re.sub("(\\d|\\W)+"," ",text)# remove special characters and digits
    
    text = text.split()
    
    stop_words = set(stopwords.words("english"))
    ##Stemming
    #Lemmatisation
    ps=PorterStemmer()
    #lem = WordNetLemmatizer()
    text = [ps.stem(word) for word in text if not word in stop_words]
    text=" ".join(text)
            
    return text

ndata = (dataset['Title']+" "+ dataset['Abst']).values
ndata= np.array([preprocess_text(text) for text in ndata])
dataset['Keywords'] = ndata
dataset_r=dataset.query('Keywords.str.contains("quantum comput")', engine='python')

Créer un graphique des articles extraits et des noms d'auteurs

Utilisez networkx pour dessiner un graphique orienté (les sommets sont le nom de l'auteur et le numéro d'article) avec la relation entre l'article et l'auteur comme des arêtes. Je fais quelque chose d'étrange parce que j'ai mis les informations sur l'auteur dans un format difficile à lire.

Post-scriptum: Concernant les fluctuations telles qu'une partie du nom en cours d'initialisation ou non, autre que le nom de famille? Prend des données à l'aide de la chaîne de requête initialisée. (Pour la notation, l'une des valeurs les plus fréquentes est sélectionnée. Si vous souhaitez la modifier, veuillez modifier le dictionnaire auth_dict séparément.) Cependant, il peut y avoir des noms d'auteurs qui sont sur-combinés car ils sont recherchés par la chaîne de caractères initialisée. Si vous souhaitez classer tel quel, utilisez le nom d'authentification commenté.

Pour vérifier les noms d'auteurs groupés
Par exemple, si vous exécutez le code suivant, vous obtiendrez la sortie affichée dans l'image ci-dessous.
authtext = " ".join(auths)
match = re.findall(r"(\()(.*?)\)",authtext)
for key in auth_dict.keys():
    l=[m[1].split(m[1][0])[1] for m in match if key in m[1]]
    c = collections.Counter(l)
    print(c.most_common())
スクリーンショット 2020-05-06 03.36.45.png
import networkx as nx
import collections

auths= dataset_r['Authors'].values
datanum = auths.shape[0]

#Créer un dictionnaire de noms d'auteur
auth_dict = {} 
for idx in range(datanum):
    match = re.findall(r"(\()(.*?)\)",auths[idx])
    for m in match:
        auth = m[1].split(m[1][-1])[-2]
        authname = m[1].split(m[1][0])[1]
        auth_dict[auth]=authname
#Remplacez chaque élément du dictionnaire par la valeur la plus fréquente
authtext = " ".join(auths)
match = re.findall(r"(\()(.*?)\)",authtext)
for key in auth_dict.keys():
    l=[m[1].split(m[1][0])[1] for m in match if key in m[1]]
    c = collections.Counter(l)
    auth_dict[key]=c.most_common()[0][0]

#Ajouter des informations au graphique
G=nx.DiGraph()
for idx in range(datanum):
    match = re.findall(r"(\()(.*?)\)",auths[idx])
    for m in match:
        auth = m[1].split(m[1][-1])[-2]
        #authname = m[1].split(m[1][0])[1]#Si vous souhaitez créer un graphique avec la notation telle qu'elle est, ajoutez un côté avec ce nom d'authentification
        G.add_edges_from([(idx,auth_dict[auth])])

Pour chaque auteur, excluez les auteurs avec un petit nombre d'articles du graphique. (Supprimé 15 ou moins cette fois) Par conséquent, ceux dont l'ordre est 0 pour chaque article sont exclus.

thr=15
authorlist = [n  for n in G.nodes() if type(n) is str]
for auth in authorlist:
    deg=G.degree(auth)
    if deg <=thr:
        G.remove_node(auth)

for idx in range(datanum):
    deg=G.degree(idx)
    if deg <=0:
        G.remove_node(idx)

Dessinez le graphique résultant. Dessinez l'auteur en bleu et le papier en rouge J'essaye d'augmenter la taille des sommets importants en utilisant le PageRank. Cet article est utilisé comme référence.

def draw_graph(G, label = False):
    #Calcul du pagerank
    pr = nx.pagerank(G)
    pos = nx.spring_layout(G)
    
    fig = plt.figure(figsize=(8.0, 6.0))
    c=[(0.4,0.4,1) if type(n) is str else (1,0.4,0.4)  for n in G.nodes()]

    nx.draw_networkx_edges(G,pos, edge_color=(0.3,0.3,0.3))
    nx.draw_networkx_nodes(G,pos, node_color=c, node_size=[5000*v for v in pr.values()])

Le résultat de sortie est le suivant. De nombreux articles sont dans un état où ils se réfèrent à un seul auteur.

graph0.png

Convertir en un graphique co-auteur avec le nombre de papiers comme poids des côtés

Pour le moment, j'étais intéressé par le réseau des co-auteurs, donc les informations contenues dans l'article sont pondérées.

import itertools

def convert_weightedGraph(graph):
    graph_new =nx.Graph()
    for node in graph.nodes:
        if type(node) is str:
            continue
        n_new = [e[1] for e in graph.edges if e[0]==node]
        for e_new in itertools.permutations(n_new, 2):

            flag_dup = False
            for e_check in graph_new.edges(data=True):
                if e_new[0] in e_check and e_new[1] in e_check:
                    e_check[2]['weight']+=1
                    flag_dup = True
            if not flag_dup:
                graph_new.add_edge(e_new[0],e_new[1],weight=1)
    return graph_new

wG=convert_weightedGraph(G)

Dessinez le graphe obtenu avec la fonction suivante. Si les étiquettes se chevauchent, vous pouvez les ajuster avec la taille de la figure ou l'argument k de spring_layout. (Référence)

def draw_weightedG(G):
    fig = plt.figure(figsize=(8.0, 6.0))
    pr = nx.pagerank(G)
    pos = nx.fruchterman_reingold_layout(G,k=k0/math.sqrt(G.order()))
    #pos = nx.spring_layout(G,k=15/math.sqrt(G.order())) 
    #Utilisez une mise en page appropriée. Ajustez la distance entre les nœuds avec la valeur de k
 
    
    x_values, y_values = zip(*pos.values())
    x_max = max(x_values)
    x_min = min(x_values)
    x_margin = (x_max - x_min) * 0.25
    plt.xlim(x_min - x_margin, x_max + x_margin) #Fixez une marge pour que les caractères de l'étiquette ne soient pas coupés

    node_sizes=[5000*v for v in pr.values()]
    edge_colors = [e[2]['weight'] for e in G.edges(data=True)] #Coloration avec des poids latéraux
    nodes = nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color='#9999ff')
    edges = nx.draw_networkx_edges(G, pos, node_size=node_sizes, arrowstyle='->',
                                   arrowsize=10, edge_color=edge_colors,
                                   edge_cmap=plt.cm.Blues, width=2)
    nx.draw_networkx_labels(G,pos)

    ax = plt.gca()
    ax.set_axis_off()

À partir du résultat du dessin du graphe obtenu, vous pouvez voir qu'il existe un graphe partiel non connecté.

wG.png

Divisez en graphiques partiels selon qu'ils sont connectés ou non

Nous allons créer un graphe partiel connecté à partir de chaque nœud du graphe. Je n'avais pas d'outil que je pourrais utiliser en un coup d'œil, alors je l'ai écrit solidement.

def add_edges_to_wsubgraph(subg, edge_new,node,edges_all):
    subg.add_edges_from([edge_new])
    
    if node == edge_new[1]:
        node_new = edge_new[0]
    else:
        node_new = edge_new[1]
      
    edges_new = [e for e in edges_all if node_new in e and e not in subg.edges(data=True)]
    for edge in edges_new:
        if edge not in subg.edges(data=True):
            add_edges_to_wsubgraph(subg,edge,node_new,edges_all)

def separate_wG_by_connectivity(G):
    nodes_all =[n for n in G.nodes()]
    edges_all = [e for e in G.edges(data=True)]
    subgraphs = []

    for node in nodes_all:
        usedflag = any([node in g.nodes for g in subgraphs])
        if usedflag:
            continue 

        subg = nx.Graph()
        subg.add_node(node)
        edges_new = [e for e in edges_all if node in e]
        for edge in edges_new:
            if edge not in subg.edges(data=True):
                add_edges_to_wsubgraph(subg,edge,node,edges_all)
        subgraphs.append(subg)
    
    return subgraphs

subgraphs = separate_wG_by_connectivity(wG)

Si vous dessinez chacun des graphiques partiels obtenus ici Vous obtiendrez les trois graphiques suivants. (Fabriqué manuellement en une seule image)

cnt=0
for subg in subgraphs:#Dessinez un graphique partiel et enregistrez l'image
    draw_weightedG(subg)
    plt.savefig("subgraph_"+str(cnt)+".png ")
    plt.cla()
    cnt+=1
スクリーンショット 2020-05-06 01.59.56.png

Résumé

En commençant par l'acquisition de données d'arxiv, j'ai écrit un truc de type réseau pour co-auteur sur un mot-clé spécifique. J'ai obtenu des résultats relativement clairs, mais dans certains cas, seuls de grands clusters sont restés, en fonction des mots-clés de recherche et des paramètres de seuil. Dans un tel cas, il peut être possible d'obtenir un peu plus de détails en réduisant le nombre d'articles co-rédigés, qui sont les poids des côtés, à un seuil approprié. Après cela, je pense qu'il serait intéressant de connaître la relation de citation du journal, mais je ne pouvais pas penser à une source d'information facile, donc je serais heureux si vous pouviez me dire quelque chose.

Recommended Posts

Ecrire un réseau de co-auteurs dans un domaine spécifique en utilisant les informations d'arxiv
Essayez de créer un réseau de neurones en Python sans utiliser de bibliothèque
Décrire un réseau qui accepte les informations d'annotation des utilisateurs dans Keras
Compter des chaînes spécifiques dans un fichier
Ecrire une dichotomie en Python
Ecrire un test piloté par table en C
Ecrire des algorithmes A * (A-star) en Python
Enregistrer une variable spécifique dans tensorflow.session
Ecrire un réseau résiduel avec TFLearn
Ecrire un graphique à secteurs en Python
Ecrire le plugin vim en Python
Écrire une recherche de priorité en profondeur en Python
Extraire des informations à l'aide de File :: Stat dans Ruby
Ecrire le test dans la docstring python
Ecrire une courte définition de propriété en Python
Ecrire un programme de chiffrement Caesar en Python
Scraping de sites Web à l'aide de JavaScript en Python
Ecrire une méthode de cupidité simple en Python
Ecrire un module python dans fortran en utilisant f2py
Ecrire un plugin Vim simple en Python 3
Dessinez une structure arborescente en Python 3 à l'aide de graphviz