From 6853ce98e8b688eaad9d75f2653f17b8b9d7689d Mon Sep 17 00:00:00 2001 From: Andrei Nesterov Date: Mon, 11 Sep 2023 11:52:12 +0200 Subject: [PATCH] LODlit changes --- AAT/aat_bows.ipynb | 9 +- LODlitParser/lodlitparser.py | 87 --------------- LODlitParser/requirements.txt | 3 - ODWN/querying_odwn.ipynb | 2 +- dist/LODlit-0.0.0-py3-none-any.whl | Bin 0 -> 21973 bytes dist/LODlit-0.0.0.tar.gz | Bin 0 -> 18560 bytes pyproject.toml | 20 ++++ src/LODlit.egg-info/PKG-INFO | 4 + src/LODlit.egg-info/SOURCES.txt | 12 ++ src/LODlit.egg-info/dependency_links.txt | 1 + src/LODlit.egg-info/top_level.txt | 1 + {LODlitParser => src/LODlit}/aat.py | 133 +++++++++-------------- {LODlitParser => src/LODlit}/bows.py | 22 ++-- {LODlitParser => src/LODlit}/odwn.py | 59 +++++----- {LODlitParser => src/LODlit}/pwn31.py | 46 ++++---- src/LODlit/requirements.txt | 8 ++ {LODlitParser => src/LODlit}/wd.py | 120 +++++++------------- top_10_by_cs_metrics.ipynb | 2 +- 18 files changed, 197 insertions(+), 332 deletions(-) delete mode 100644 LODlitParser/lodlitparser.py delete mode 100644 LODlitParser/requirements.txt create mode 100644 dist/LODlit-0.0.0-py3-none-any.whl create mode 100644 dist/LODlit-0.0.0.tar.gz create mode 100644 pyproject.toml create mode 100644 src/LODlit.egg-info/PKG-INFO create mode 100644 src/LODlit.egg-info/SOURCES.txt create mode 100644 src/LODlit.egg-info/dependency_links.txt create mode 100644 src/LODlit.egg-info/top_level.txt rename {LODlitParser => src/LODlit}/aat.py (76%) rename {LODlitParser => src/LODlit}/bows.py (92%) rename {LODlitParser => src/LODlit}/odwn.py (88%) rename {LODlitParser => src/LODlit}/pwn31.py (91%) create mode 100644 src/LODlit/requirements.txt rename {LODlitParser => src/LODlit}/wd.py (87%) diff --git a/AAT/aat_bows.ipynb b/AAT/aat_bows.ipynb index abde476..44aa473 100644 --- a/AAT/aat_bows.ipynb +++ b/AAT/aat_bows.ipynb @@ -137,18 +137,11 @@ "with open('aat_bows_nl.json', 'w') as jf:\n", " json.dump(aat_bows_nl, jf)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/LODlitParser/lodlitparser.py b/LODlitParser/lodlitparser.py deleted file mode 100644 index 0d66532..0000000 --- a/LODlitParser/lodlitparser.py +++ /dev/null @@ -1,87 +0,0 @@ -# lodlitparser -# install NLTK -# Important! download wordnet31 ('https://github.com/nltk/nltk_data/blob/gh-pages/packages/corpora/wordnet31.zip'); -# put the content of 'wordnet31' to 'wordnet' in 'nltk_data/corpora' (it is not possible to import wordnet31 from nltk.corpus; See explanations on the WordNet website (retrieved on 10.02.2023): https://wordnet.princeton.edu/download/current-version; "WordNet 3.1 DATABASE FILES ONLY. You can download the WordNet 3.1 database files. Note that this is not a full package as those above, nor does it contain any code for running WordNet. However, you can replace the files in the database directory of your 3.0 local installation with these files and the WordNet interface will run, returning entries from the 3.1 database. This is simply a compressed tar file of the WordNet 3.1 database files." -# download OpenDutchWordnet from 'https://github.com/cultural-ai/OpenDutchWordnet' - -import json -import requests -import time -import re -import sys -from io import BytesIO -from zipfile import ZipFile -from SPARQLWrapper import SPARQLWrapper, JSON -from nltk.corpus import wordnet as wn - -def main(): - if __name__ == "__main__": - main() - -# OpenDutch Wordnet - -def odwn(le_ids:list, path_odwn:str) -> dict: - ''' - Getting Lemma, sense definitions, examples, synset ID, synset definitions of ODWN Lexical Entries - le_ids: list of Lexical Entries IDs (str) - path_odwn: str path to the directory with OpenDutchWordnet (not including the module itself, for example 'user/Downloads') - Returns a dict: {'le_id': {'lemma': '', - 'sense_def': '', - 'examples': [], - 'synset_ID': '', - 'synset_def': []} - ''' - # importing ODWN - sys.path.insert(0,path_odwn) - from OpenDutchWordnet import Wn_grid_parser - # creating an instance - instance = Wn_grid_parser(Wn_grid_parser.odwn) - - # importing all synset definitions - path_to_glosses = "https://raw.githubusercontent.com/cultural-ai/wordsmatter/main/ODWN/odwn_synset_glosses.json" - synset_glosses = requests.get(path_to_glosses).json() - - results_odwn = {} - - for le_id in le_ids: - le = instance.les_find_le(le_id) - synset_id = le.get_synset_id() - - sense_def = le.get_definition() - if sense_def == '': - sense_def = None - - results_odwn[le_id] = {'lemma': le.get_lemma(), - 'sense_def': sense_def, - 'examples': le.get_sense_example(), - 'synset_ID': synset_id, - 'synset_def': synset_glosses.get(synset_id)} - return results_odwn - -# Wereldculturen Thesaurus NMVW - -def nmvw(term_ids:list) -> dict: - ''' - Getting info about terms by their handle IDs in NMVW-thesaurus - term_ids: list of term IDs (str) - Returns a dict with query results: {'ID': {'prefLabel': '', - 'altLabel': [], - 'notes': [], - 'exactMatch': '', - 'scheme': ''}} - ''' - - # nmvw: importing thesaurus - path_to_nmvw = 'https://github.com/cultural-ai/wordsmatter/raw/main/NMVW/nmvw_thesaurus.json.zip' - nmvw_raw = requests.get(path_to_nmvw).content - nmvw_zip = ZipFile(BytesIO(nmvw_raw)) - nmvw_json = json.loads(nmvw_zip.read(nmvw_zip.infolist()[0]).decode()) - - results_nmvw = {} - - for term_id in term_ids: - handle = 'https://hdl.handle.net/20.500.11840/termmaster' + term_id - results_nmvw[handle] = nmvw_json.get(handle) - - return results_nmvw - diff --git a/LODlitParser/requirements.txt b/LODlitParser/requirements.txt deleted file mode 100644 index dec4276..0000000 --- a/LODlitParser/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -NLTK, -wordnet31: 'https://github.com/nltk/nltk_data/blob/gh-pages/packages/corpora/wordnet31.zip', -OpenDutchWordnet: 'https://github.com/cultural-ai/OpenDutchWordnet' \ No newline at end of file diff --git a/ODWN/querying_odwn.ipynb b/ODWN/querying_odwn.ipynb index 0401f84..6a3ac98 100644 --- a/ODWN/querying_odwn.ipynb +++ b/ODWN/querying_odwn.ipynb @@ -96,7 +96,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/dist/LODlit-0.0.0-py3-none-any.whl b/dist/LODlit-0.0.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..a8a3adb6ba324f633824df5c634a4b6ec74dc931 GIT binary patch literal 21973 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

pl`7ZPPK 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 literal 0 HcmV?d00001 diff --git a/dist/LODlit-0.0.0.tar.gz b/dist/LODlit-0.0.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..370cac9fed54ee2602395481bb90a54ece298e5f GIT binary patch literal 18560 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�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 LVMY+40N?=t$wt#r literal 0 HcmV?d00001 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ff74a56 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[LODlit] +dependencies = [ + 'nltk==3.8.1', + 'pandas==2.0.3', + 'numpy==1.21.0', + 'requests==2.31.0', + 'simplemma==0.9.1', + 'spacy==3.6.1', + 'SPARQLWrapper==2.0.0', + 'lxml==4.9.3' + ] + +[metadata] +name = "LODlit" +version = "1.0.0" +description = "Retrieving literal values from LOD" +author = "Andrei Nesterov" +author_email = "nesterov@cwi.nl" +license = "CC BY 4.0" +keywords = ["LOD", "literals", "linked open data", "strings", "NLP"] \ No newline at end of file diff --git a/src/LODlit.egg-info/PKG-INFO b/src/LODlit.egg-info/PKG-INFO new file mode 100644 index 0000000..900ac9e --- /dev/null +++ b/src/LODlit.egg-info/PKG-INFO @@ -0,0 +1,4 @@ +Metadata-Version: 2.1 +Name: LODlit +Version: 0.0.0 +License-File: LICENSE diff --git a/src/LODlit.egg-info/SOURCES.txt b/src/LODlit.egg-info/SOURCES.txt new file mode 100644 index 0000000..77b2b12 --- /dev/null +++ b/src/LODlit.egg-info/SOURCES.txt @@ -0,0 +1,12 @@ +LICENSE +README.md +pyproject.toml +src/LODlit/aat.py +src/LODlit/bows.py +src/LODlit/odwn.py +src/LODlit/pwn31.py +src/LODlit/wd.py +src/LODlit.egg-info/PKG-INFO +src/LODlit.egg-info/SOURCES.txt +src/LODlit.egg-info/dependency_links.txt +src/LODlit.egg-info/top_level.txt \ No newline at end of file diff --git a/src/LODlit.egg-info/dependency_links.txt b/src/LODlit.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/LODlit.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/LODlit.egg-info/top_level.txt b/src/LODlit.egg-info/top_level.txt new file mode 100644 index 0000000..fd252db --- /dev/null +++ b/src/LODlit.egg-info/top_level.txt @@ -0,0 +1 @@ +LODlit diff --git a/LODlitParser/aat.py b/src/LODlit/aat.py similarity index 76% rename from LODlitParser/aat.py rename to src/LODlit/aat.py index ce43795..71f4460 100644 --- a/LODlitParser/aat.py +++ b/src/LODlit/aat.py @@ -1,6 +1,5 @@ # Getty AAT -# A module to parse query results (json) from Getty Art&Archiechture Thesaurus -# to use 'get_bows', download stopwords from nltk: nltk.download('stopwords'); install simplelemma +# A module to parse query results (json) from The Getty Art & Archiechture Thesaurus (AAT) import json import gzip @@ -63,6 +62,7 @@ def sparql(aat_uri:list, lang:str) -> dict: sparql.setReturnFormat(JSON) results = sparql.query().convert() + prefLabel = None altLabels = [] scopeNote = None prefLabel_comment = None @@ -70,6 +70,8 @@ def sparql(aat_uri:list, lang:str) -> dict: for result in results['results']['bindings']: + if 'prefLabel' in result: + prefLabel = result['prefLabel']['value'] if 'altLabels' in result: altLabels = result['altLabels']['value'].split('#') if 'scopeNote' in result: @@ -80,7 +82,7 @@ def sparql(aat_uri:list, lang:str) -> dict: altLabel_comment = result['altLabel_comment']['value'] result_dict[uri]['lang'] = lang - result_dict[uri]['prefLabel'] = result['prefLabel']['value'] + result_dict[uri]['prefLabel'] = prefLabel result_dict[uri]['altLabels'] = altLabels result_dict[uri]['prefLabel_comment'] = prefLabel_comment result_dict[uri]['altLabel_comment'] = altLabel_comment @@ -156,9 +158,11 @@ def find_term_in_literals(query_term:str, lang:str) -> list: ''' # reading the gzip json file with aat search results - # change the path to GitHub - with gzip.open(f"/Users/anesterov/reps/LODlit/AAT/gzip_aat_subgraph_{lang}.json", 'r') as gzip_json: - aat_gzip = json.loads(gzip_json.read().decode('utf-8')) + # path to raw gzip on GitHub + gzip_path = f"https://github.com/cultural-ai/LODlit/raw/main/AAT/gzip_aat_subgraph_{lang}.json" + + # decompressing + aat_gzip = json.loads(gzip.decompress(requests.get(gzip_path).content)) list_of_results = [] @@ -220,19 +224,18 @@ def find_term_in_literals(query_term:str, lang:str) -> list: return list_of_results -def get_bows(path_to_results:str, lang:str) -> dict: +def get_bows(lang:str) -> dict: ''' Getting bag of words (BoW) from the AAT search results for every search term - path_to_results: str, a path to the search results (in json format) lang: str, 'en' or 'nl' Returns a dict with BoWs per hit per term: {term:[{aat_URI:['token1','token2','token3']}]} ''' - + + path_to_results = f"https://github.com/cultural-ai/LODlit/raw/main/AAT/aat_query_results_{lang}.json" + search_results = requests.get(path_to_results).json() + wnl = WordNetLemmatizer() all_bows = {} - - with open(path_to_results,'r') as jf: - search_results = json.load(jf) for query_term, results in search_results.items(): @@ -274,61 +277,32 @@ def get_lit_related_matches_bow(lang:str) -> dict: path_rm = "https://github.com/cultural-ai/wordsmatter/raw/main/related_matches/rm.json" rm = requests.get(path_rm).json() - # checking lang - if lang == "en": - # change path - with open('/Users/anesterov/reps/LODlit/AAT/aat_bows_en.json','r') as jf: - aat_bows = json.load(jf) - - # getting a list of all AAT URIs of related matches - # terms with no related matches won't be included in the file - related_matches_aat = list(set([values["related_matches"]["aat"][0] for values in rm.values() \ - if values["lang"] == "en" and values["related_matches"]["aat"][0] != 'None'])) - - # getting BoWs for AAT concept URIs - related_matches_aat_uri_bows = {} - for uri_rm in related_matches_aat: - for hits in aat_bows.values(): - for hit in hits: - for uri, bow in hit.items(): - if uri == uri_rm: - related_matches_aat_uri_bows[uri_rm] = bow - - # shaping resulting dict: terms with related matches and BoWs - for values in rm.values(): - if values["lang"] == "en": - rm_aat = values["related_matches"]["aat"][0] - if rm_aat != "None": - for term in values["query_terms"]: - if rm_aat in related_matches_aat_uri_bows.keys(): - results[term] = {"aat_uri":rm_aat,"bow":related_matches_aat_uri_bows[rm_aat]} - - if lang == "nl": - # change path - with open('/Users/anesterov/reps/LODlit/AAT/aat_bows_nl.json','r') as jf: - aat_bows = json.load(jf) - - # getting a list of all AAT URIs of related matches - related_matches_aat = list(set([values["related_matches"]["aat"][0] for values in rm.values() \ - if values["lang"] == "nl" and values["related_matches"]["aat"][0] != 'None'])) - - # getting BoWs for AAT concept URIs - related_matches_aat_uri_bows = {} - for uri_rm in related_matches_aat: - for hits in aat_bows.values(): - for hit in hits: - for uri, bow in hit.items(): - if uri == uri_rm: - related_matches_aat_uri_bows[uri_rm] = bow - - # shaping resulting dict: terms with related matches and BoWs - for values in rm.values(): - if values["lang"] == "nl": - rm_aat = values["related_matches"]["aat"][0] - if rm_aat != "None": - for term in values["query_terms"]: - if rm_aat in related_matches_aat_uri_bows.keys(): - results[term] = {"aat_uri":rm_aat,"bow":related_matches_aat_uri_bows[rm_aat]} + # load aat bows + path_to_bows = f"https://github.com/cultural-ai/LODlit/raw/main/AAT/aat_bows_{lang}.json" + aat_bows = requests.get(path_to_bows).json() + + # getting a list of all AAT URIs of related matches + # terms with no related matches won't be included in the file + related_matches_aat = list(set([values["related_matches"]["aat"][0] for values in rm.values() \ + if values["lang"] == lang and values["related_matches"]["aat"][0] != 'None'])) + + # getting BoWs for AAT concept URIs + related_matches_aat_uri_bows = {} + for uri_rm in related_matches_aat: + for hits in aat_bows.values(): + for hit in hits: + for uri, bow in hit.items(): + if uri == uri_rm: + related_matches_aat_uri_bows[uri_rm] = bow + + # shaping resulting dict: terms with related matches and BoWs + for values in rm.values(): + if values["lang"] == lang: + rm_aat = values["related_matches"]["aat"][0] + if rm_aat != "None": + for term in values["query_terms"]: + if rm_aat in related_matches_aat_uri_bows.keys(): + results[term] = {"aat_uri":rm_aat,"bow":related_matches_aat_uri_bows[rm_aat]} return results @@ -342,24 +316,18 @@ def get_cs(lang:str): query_term, aat_URI, bow, cs_rm, cs_wm, cs_rm_wm ''' - nlp = bows._load_spacy_nlp(lang) + nlp = bows.load_spacy_nlp(lang) # load bckground info - # change path - with open('/Users/anesterov/reps/LODlit/bg/background_info_bows.json','r') as jf: - bg_info = json.load(jf) - - aat_df = pd.DataFrame(columns=['term','hit_id','bow','cs_rm','cs_wm','cs_rm_wm']) + path_bg = "https://github.com/cultural-ai/LODlit/raw/main/bg/background_info_bows.json" + bg_info = requests.get(path_bg).json() - # check lang and load appropriate file + # load aat bows - if lang == "en": - with open('/Users/anesterov/reps/LODlit/AAT/aat_bows_en.json','r') as jf: - aat_bows = json.load(jf) + path_aat_bows = f"https://github.com/cultural-ai/LODlit/raw/main/AAT/aat_bows_{lang}.json" + aat_bows = requests.get(path_aat_bows).json() - if lang == "nl": - with open('/Users/anesterov/reps/LODlit/AAT/aat_bows_nl.json','r') as jf: - aat_bows = json.load(jf) + aat_df = pd.DataFrame(columns=['term','hit_id','bow','cs_rm','cs_wm','cs_rm_wm']) for term, hits in aat_bows.items(): @@ -390,7 +358,4 @@ def get_cs(lang:str): else: aat_df.loc[len(aat_df)] = [term,None,None,None,None,None] - return aat_df - - - + return aat_df \ No newline at end of file diff --git a/LODlitParser/bows.py b/src/LODlit/bows.py similarity index 92% rename from LODlitParser/bows.py rename to src/LODlit/bows.py index 66f9b36..1d0bfdd 100644 --- a/LODlitParser/bows.py +++ b/src/LODlit/bows.py @@ -1,7 +1,10 @@ -#download 'en_core_web_lg' and 'nl_core_news_lg' from https://spacy.io/models +# Functions to make bag-of-words from strings, collect background information, and calculare cosine similarity +# To use 'calculate_cs', download 'en_core_web_lg' and 'nl_core_news_lg' from https://spacy.io/models: +# python -m spacy download en_core_web_lg AND python -m spacy download nl_core_news_lg import json import re +import requests import math import pandas as pd import nltk @@ -94,14 +97,11 @@ def get_top_tokens_tfidf(bow:list,doc_freq:dict,n_docs:int) -> list: if len(tokens_scores) < 10: top_tokens = [t[0] for t in tokens_scores] else: - #cut_off_score = tokens_scores[9][1] # taking top 10 scores - #top_tokens = [t[0] for t in tokens_scores if t[1] >= cut_off_score] - #if len(top_tokens) > 10: top_tokens = [t[0] for t in tokens_scores[0:10]] return top_tokens -def _load_spacy_nlp(lang:str): +def load_spacy_nlp(lang:str): ''' Loads an NLP pipeline from spacy for terms vectorisation; lang: str, language of the strings to process, 'en' or 'nl'; @@ -123,7 +123,7 @@ def calculate_cs(bow_1:list, bow_2:list, nlp) -> float: Calculates cosine similarity between two bags of words; based on the spacy vectors bow_1 and bow_2: list, two bags of words; - nlp: spacy nlp class loaded through spacy.load + nlp: spacy nlp class loaded through spacy.load; use the function 'load_spacy_nlp' ''' # converting list to str, spaces as a separator @@ -242,7 +242,7 @@ def _vectorize_bows(bow_1:list, bow_2:list) -> int: def get_top_10(cs_table_path:str, metric:str, lang:str, groupby='lemma'): ''' Getting top-10 results (max 10) (entities) for every query term based on a metric - cs_table_path: str, path to the csv table with cosine similarity scores + cs_table_path: str, path to the csv table with cosine similarity scores, see the files '[dataset]_[lang]_cs.csv'; for example, 'aat_en_cs.csv'; wikidata en cs is zipped; metric: str, which metric to take to get top-10 options for metric: (1) "cs_rm" -- only related matches, (2) "cs_wm" -- only WM text, and (3) "cs_rm_wm" -- extended bows with related matches and WM text lang: str, 'en' or 'nl'; language of the dataset in teh csv table @@ -261,10 +261,10 @@ def get_top_10(cs_table_path:str, metric:str, lang:str, groupby='lemma'): top_10 = top_10.append(group[1].sort_values(by=metric, ascending=False)[0:10]) if groupby == 'lemma': - # loading the query terms - # change path - with open('/Users/anesterov/reps/LODlit/query_terms.json','r') as jf: - query_terms = json.load(jf) + + # loading query terms + path_query_terms = "https://github.com/cultural-ai/LODlit/raw/main/query_terms.json" + query_terms = requests.get(path_query_terms).json() # insert the lemmas column lemmas = [] diff --git a/LODlitParser/odwn.py b/src/LODlit/odwn.py similarity index 88% rename from LODlitParser/odwn.py rename to src/LODlit/odwn.py index 6b0eb02..1436bd8 100644 --- a/LODlitParser/odwn.py +++ b/src/LODlit/odwn.py @@ -1,11 +1,12 @@ # Open Dutch WordNet parser -# Download OpenDutchWordnet from "https://github.com/cultural-ai/OpenDutchWordnet" -# to use 'get_bows', download stopwords from nltk: nltk.download('stopwords'); install simplelemma +# Download Open Dutch WordNet from "https://github.com/cultural-ai/OpenDutchWordnet" +# requires lxml: pip install lxml import sys import json import csv import re +import requests import pandas as pd import nltk from nltk.corpus import stopwords @@ -42,12 +43,9 @@ def get_le_info(le_ids:list, path_odwn:str) -> dict: # importing ODWN instance = _set_odwn(path_odwn) - # change later - # importing all synset definitions from GitHub - # path_to_glosses = "https://raw.githubusercontent.com/cultural-ai/wordsmatter/main/ODWN/odwn_synset_glosses.json" - # synset_glosses = requests.get(path_to_glosses).json() - with open("/Users/anesterov/reps/LODlit/ODWN/odwn_synset_glosses.json","r") as jf: - synset_glosses = json.load(jf) + # load all glosses + path_to_glosses = "https://github.com/cultural-ai/LODlit/raw/main/ODWN/odwn_synset_glosses.json" + synset_glosses = requests.get(path_to_glosses).json() results_odwn = {} @@ -66,12 +64,18 @@ def get_le_info(le_ids:list, path_odwn:str) -> dict: "synset_def": synset_glosses.get(synset_id)} return results_odwn -def _shape_search_results(instance:"wn_grid_parser.Wn_grid_parser",query_term:str,le:"le.Le",all_synset_definitions:dict,found_in:str,example="") -> dict: +def _shape_search_results(instance,query_term:str,le,all_synset_definitions:dict,found_in:str,example="") -> dict: """ + instance: class wn_grid_parser.Wn_grid_parser + query_term: str + le: class le.Le + all_synset_definitions: dict, parses "https://github.com/cultural-ai/LODlit/raw/main/ODWN/odwn_synset_glosses.json" + found_in: str + example: str, default is "" Returns a dict of search results This function does not perform searching, - But puts the search results of "find_terms" in a dict + but puts the search results of "find_terms" in a dict """ result_dict = {} @@ -132,12 +136,9 @@ def find_terms(query_terms:list, path_odwn:str) -> dict: # importing ODWN instance = _set_odwn(path_odwn) - # change later - # importing all synset definitions from GitHub - # path_to_glosses = "https://raw.githubusercontent.com/cultural-ai/wordsmatter/main/ODWN/odwn_synset_glosses.json" - # synset_glosses = requests.get(path_to_glosses).json() - with open("/Users/anesterov/reps/LODlit/ODWN/odwn_synset_glosses.json","r") as jf: - synset_glosses = json.load(jf) + # load all glosses + path_to_glosses = "https://github.com/cultural-ai/LODlit/raw/main/ODWN/odwn_synset_glosses.json" + synset_glosses = requests.get(path_to_glosses).json() results = {} @@ -240,14 +241,13 @@ def get_lit_related_matches_bow() -> dict: results = {} # loading related matches - # change path - with open('/Users/anesterov/reps/LODlit/bg/related_matches_odwn.json','r') as jf: - rm = json.load(jf) + path_rm = "https://github.com/cultural-ai/wordsmatter/raw/main/related_matches/rm.json" + rm = requests.get(path_rm).json() + + # load odwn bows + path_to_bows = f"https://github.com/cultural-ai/LODlit/raw/main/ODWN/odwn_bows.json" + odwn_bows = requests.get(path_to_bows).json() - # change path - with open('/Users/anesterov/reps/LODlit/ODWN/odwn_bows.json','r') as jf: - odwn_bows = json.load(jf) - # getting a list of all ODWN LE IDs and synset IDs of related matches odwn_les_synsets = [] for values in rm.values(): @@ -293,15 +293,14 @@ def get_cs(): nlp = bows._load_spacy_nlp("nl") # load bckground info - # change path - with open('/Users/anesterov/reps/LODlit/bg/background_info_bows.json','r') as jf: - bg_info = json.load(jf) + path_bg = "https://github.com/cultural-ai/LODlit/raw/main/bg/background_info_bows.json" + bg_info = requests.get(path_bg).json() - odwn_df = pd.DataFrame(columns=['term','hit_id','bow','cs_rm','cs_wm','cs_rm_wm']) + # load all pwn bows + path_odwn_bows = f"https://github.com/cultural-ai/LODlit/raw/main/ODWN/odwn_bows.json" + odwn_bows = requests.get(path_pwn_bows).json() - # load all odwn bows - with open('/Users/anesterov/reps/LODlit/ODWN/odwn_bows.json','r') as jf: - odwn_bows = json.load(jf) + odwn_df = pd.DataFrame(columns=['term','hit_id','bow','cs_rm','cs_wm','cs_rm_wm']) for term, hits in odwn_bows.items(): diff --git a/LODlitParser/pwn31.py b/src/LODlit/pwn31.py similarity index 91% rename from LODlitParser/pwn31.py rename to src/LODlit/pwn31.py index 7053c47..362f58b 100644 --- a/LODlitParser/pwn31.py +++ b/src/LODlit/pwn31.py @@ -1,12 +1,12 @@ -# Module for Princeton WordNet 3.1 +# Module to parse Princeton WordNet 3.1 -# install NLTK -# Important! download wordnet31 ('https://github.com/nltk/nltk_data/blob/gh-pages/packages/corpora/wordnet31.zip'); -# put the content of 'wordnet31' to 'wordnet' in 'nltk_data/corpora' (it is not possible to import wordnet31 from nltk.corpus; See explanations on the WordNet website (retrieved on 10.02.2023): https://wordnet.princeton.edu/download/current-version; "WordNet 3.1 DATABASE FILES ONLY. You can download the WordNet 3.1 database files. Note that this is not a full package as those above, nor does it contain any code for running WordNet. However, you can replace the files in the database directory of your 3.0 local installation with these files and the WordNet interface will run, returning entries from the 3.1 database. This is simply a compressed tar file of the WordNet 3.1 database files." -# to use 'get_bows', download stopwords from nltk: nltk.download('stopwords'); +# Important! After installing NLTK, download wordnet31 ('https://github.com/nltk/nltk_data/blob/gh-pages/packages/corpora/wordnet31.zip'); +# put the content of 'wordnet31' to 'wordnet' in 'nltk_data/corpora' (it is not possible to import wordnet31 from nltk.corpus; See explanations on the WordNet website (retrieved on 10.02.2023): https://wordnet.princeton.edu/download/current-version; "WordNet 3.1 DATABASE FILES ONLY. You can download the WordNet 3.1 database files. Note that this is not a full package as those above, nor does it contain any code for running WordNet. However, you can replace the files in the database directory of your 3.0 local installation with these files and the WordNet interface will run, returning entries from the 3.1 database. This is simply a compressed tar file of the WordNet 3.1 database files."; +# Use check_version() to ensure that WordNet 3.1 is imported import json import re +import requests import nltk from nltk.corpus import wordnet as wn from nltk.corpus import stopwords @@ -162,13 +162,12 @@ def get_lit_related_matches_bow() -> dict: results = {} # loading related matches - # change path - with open('/Users/anesterov/reps/wordsmatter/related_matches/rm.json','r') as jf: - rm = json.load(jf) - - # change path - with open('/Users/anesterov/reps/LODlit/PWN/pwn31_bows.json','r') as jf: - pwn_bows = json.load(jf) + path_rm = "https://github.com/cultural-ai/wordsmatter/raw/main/related_matches/rm.json" + rm = requests.get(path_rm).json() + + # load pwn bows + path_to_bows = f"https://github.com/cultural-ai/LODlit/raw/main/PWN/pwn31_bows.json" + pwn_bows = requests.get(path_to_bows).json() # getting a list of all PWN synsets of related matches pwn_synsets = [] @@ -209,18 +208,17 @@ def get_cs(): query_term, hit_id, bow, cs_rm, cs_wm, cs_rm_wm ''' - nlp = bows._load_spacy_nlp("en") + nlp = bows.load_spacy_nlp("en") # load bckground info - # change path - with open('/Users/anesterov/reps/LODlit/bg/background_info_bows.json','r') as jf: - bg_info = json.load(jf) - - pwn_df = pd.DataFrame(columns=['term','hit_id','bow','cs_rm','cs_wm','cs_rm_wm']) + path_bg = "https://github.com/cultural-ai/LODlit/raw/main/bg/background_info_bows.json" + bg_info = requests.get(path_bg).json() # load all pwn bows - with open('/Users/anesterov/reps/LODlit/PWN/pwn31_bows.json','r') as jf: - pwn_bows = json.load(jf) + path_pwn_bows = f"https://github.com/cultural-ai/LODlit/raw/main/PWN/pwn31_bows.json" + pwn_bows = requests.get(path_pwn_bows).json() + + pwn_df = pd.DataFrame(columns=['term','hit_id','bow','cs_rm','cs_wm','cs_rm_wm']) for term, hits in pwn_bows.items(): @@ -251,10 +249,4 @@ def get_cs(): else: pwn_df.loc[len(pwn_df)] = [term,None,None,None,None,None] - return pwn_df - - - - - - + return pwn_df \ No newline at end of file diff --git a/src/LODlit/requirements.txt b/src/LODlit/requirements.txt new file mode 100644 index 0000000..021c002 --- /dev/null +++ b/src/LODlit/requirements.txt @@ -0,0 +1,8 @@ +nltk==3.8.1 +pandas==2.0.3 +numpy==1.21.0 +requests==2.31.0 +simplemma==0.9.1 +spacy==3.6.1 +SPARQLWrapper==2.0.0 +lxml==4.9.3 \ No newline at end of file diff --git a/LODlitParser/wd.py b/src/LODlit/wd.py similarity index 87% rename from LODlitParser/wd.py rename to src/LODlit/wd.py index 363718f..c6f6476 100644 --- a/LODlitParser/wd.py +++ b/src/LODlit/wd.py @@ -1,6 +1,4 @@ -# Wikidata module -# to use 'get_bows': (1) download stopwords from nltk: nltk.download('stopwords'); -# (2) install simplelemma +# Wikidata module: sending API requests and claning the search results import nltk from nltk.corpus import stopwords @@ -326,9 +324,10 @@ def get_labels_by_q(qids:list,lang:list,user_agent:str) -> dict: def _claims_parse_properties(query_results:dict, QID:str, propepties:list) -> dict: """ Parses the values of the properties of entities from Wikidata response, - for example: get the values of the properrty P31 for the entity Q104534511; - Takes Wikidata response in json format, QID (str) (only 1 QID), - and properties (list of str; for example ['P31','P279']); + for example: get the values of the property P31 for the entity Q104534511; + query_results: dict, Wikidata response in json format; + QID: str, only 1 QID; + properties: list of str; for example ['P31','P279']; Return a dict {'QID': {'property_ID':{'property_value': ''}}}; The empty value of the property_value is intended for its label, which is queried using another function """ @@ -609,64 +608,33 @@ def get_lit_related_matches_bow(lang:str) -> dict: results = {} # loading related matches - # change path - with open('/Users/anesterov/reps/wordsmatter/related_matches/rm.json','r') as jf: - rm = json.load(jf) + path_rm = "https://github.com/cultural-ai/wordsmatter/raw/main/related_matches/rm.json" + rm = requests.get(path_rm).json() - # checking lang - if lang == "en": - # change path - with gzip.open(f"/Users/anesterov/reps/LODlit/Wikidata/gzip_wd_bows_en.json", 'r') as gzip_json: - wd_bow = json.loads(gzip_json.read().decode('utf-8')) - - # getting a list of all QIDs of related matches in Wikidata - related_matches_wd_qid = list(set([values["related_matches"]["wikidata"][0] for values in rm.values() \ - if values["lang"] == "en" and values["related_matches"]["wikidata"][0] != 'None'])) - - # getting BoWs for QIDs - related_matches_wd_qid_bows = {} - for q_id_rm in related_matches_wd_qid: - for hits in wd_bow.values(): - for hit in hits: - for q_id, bow in hit.items(): - if q_id == q_id_rm: - related_matches_wd_qid_bows[q_id] = bow - - # shaping resulting dict: terms with related matches and BoWs - for values in rm.values(): - if values["lang"] == "en": - rm_wd = values["related_matches"]["wikidata"][0] - if rm_wd != "None": - for term in values["query_terms"]: - if rm_wd in related_matches_wd_qid_bows.keys(): - results[term] = {"QID":rm_wd,"bow":related_matches_wd_qid_bows[rm_wd]} - - if lang == "nl": - # change path - with gzip.open(f"/Users/anesterov/reps/LODlit/Wikidata/gzip_wd_bows_nl.json", 'r') as gzip_json: - wd_bow = json.loads(gzip_json.read().decode('utf-8')) - - # getting a list of all QIDs of related matches in Wikidata - related_matches_wd_qid = list(set([values["related_matches"]["wikidata"][0] for values in rm.values() \ - if values["lang"] == "nl" and values["related_matches"]["wikidata"][0] != 'None'])) - - # getting BoWs for QIDs - related_matches_wd_qid_bows = {} - for q_id_rm in related_matches_wd_qid: - for hits in wd_bow.values(): - for hit in hits: - for q_id, bow in hit.items(): - if q_id == q_id_rm: - related_matches_wd_qid_bows[q_id] = bow - - # shaping resulting dict: terms with related matches and BoWs - for values in rm.values(): - if values["lang"] == "nl": - rm_wd = values["related_matches"]["wikidata"][0] - if rm_wd != "None": - for term in values["query_terms"]: - if rm_wd in related_matches_wd_qid_bows.keys(): - results[term] = {"QID":rm_wd,"bow":related_matches_wd_qid_bows[rm_wd]} + # load wd bows + path_to_bows = f"https://github.com/cultural-ai/LODlit/raw/main/Wikidata/gzip_wd_bows_{lang}.json" + wd_bow = requests.get(path_to_bows).json() + + # getting a list of all QIDs of related matches in Wikidata + related_matches_wd_qid = list(set([values["related_matches"]["wikidata"][0] for values in rm.values() \ + if values["lang"] == lang and values["related_matches"]["wikidata"][0] != 'None'])) + # getting BoWs for QIDs + related_matches_wd_qid_bows = {} + for q_id_rm in related_matches_wd_qid: + for hits in wd_bow.values(): + for hit in hits: + for q_id, bow in hit.items(): + if q_id == q_id_rm: + related_matches_wd_qid_bows[q_id] = bow + + # shaping resulting dict: terms with related matches and BoWs + for values in rm.values(): + if values["lang"] == lang: + rm_wd = values["related_matches"]["wikidata"][0] + if rm_wd != "None": + for term in values["query_terms"]: + if rm_wd in related_matches_wd_qid_bows.keys(): + results[term] = {"QID":rm_wd,"bow":related_matches_wd_qid_bows[rm_wd]} return results @@ -679,26 +647,17 @@ def get_cs(lang:str): query_term, QID, bow, cs_rm, cs_wm, cs_rm_wm ''' - nlp = bows._load_spacy_nlp(lang) + nlp = bows.load_spacy_nlp(lang) # load bckground info - # change path - with open('/Users/anesterov/reps/LODlit/bg/background_info_bows.json','r') as jf: - bg_info = json.load(jf) + path_bg = "https://github.com/cultural-ai/LODlit/raw/main/bg/background_info_bows.json" + bg_info = requests.get(path_bg).json() wikidata_df = pd.DataFrame(columns=['term','hit_id','bow','cs_rm','cs_wm','cs_rm_wm']) - # check lang and load appropriate file - - if lang == "en": - # change path - with gzip.open(f"/Users/anesterov/reps/LODlit/Wikidata/gzip_wd_bows_en.json", 'r') as gzip_json: - wikidata_bows = json.loads(gzip_json.read().decode('utf-8')) - - if lang == "nl": - # change path - with gzip.open(f"/Users/anesterov/reps/LODlit/Wikidata/gzip_wd_bows_nl.json", 'r') as gzip_json: - wikidata_bows = json.loads(gzip_json.read().decode('utf-8')) + # load wd bows + path_wd_bows = f"https://github.com/cultural-ai/LODlit/raw/main/Wikidata/gzip_wd_bows_{lang}.json" + wikidata_bows = requests.get(path_wd_bows).json() for term, hits in wikidata_bows.items(): @@ -743,8 +702,9 @@ def get_n_hits_by_properties(path_to_results:str, lang:str, group_by_lemma=False Returns a pandas dataframe ''' # importing query terms with lemmas - with open('/Users/anesterov/reps/LODlit/query_terms.json','r') as jf: - query_terms = json.load(jf) + # loading query terms + path_query_terms = "https://github.com/cultural-ai/LODlit/raw/main/query_terms.json" + query_terms = requests.get(path_query_terms).json() with open(path_to_results,'r') as jf: wd_results = json.load(jf) diff --git a/top_10_by_cs_metrics.ipynb b/top_10_by_cs_metrics.ipynb index de2225b..b5ea86f 100644 --- a/top_10_by_cs_metrics.ipynb +++ b/top_10_by_cs_metrics.ipynb @@ -2461,7 +2461,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" },