Mientras ingerimos los datos de la API, aplicaremos algunos criterios. En primer lugar, solo ingeriremos documentos cuyo año esté entre 2016 y 2022. Queremos un lenguaje bastante reciente, ya que los términos y la taxonomía de ciertos temas pueden cambiar en largos períodos de tiempo.
También agregaremos términos clave y realizaremos múltiples búsquedas. Si bien normalmente ingeriríamos áreas temáticas aleatorias, usaremos términos clave para limitar nuestra búsqueda. De esta manera, tendremos una idea de cuántos temas de alto nivel tenemos y podremos compararlos con el resultado del modelo. A continuación, creamos una función donde podemos agregar términos clave y realizar búsquedas a través de la API.
import pandas as pd
import requests
def import_data(pages, start_year, end_year, search_terms):"""
This function is used to use the OpenAlex API, conduct a search on works, a return a dataframe with associated works.
Inputs:
- pages: int, number of pages to loop through
- search_terms: str, keywords to search for (must be formatted according to OpenAlex standards)
- start_year and end_year: int, years to set as a range for filtering works
"""
#create an empty dataframe
search_results = pd.DataFrame()
for page in range(1, pages):
#use paramters to conduct request and format to a dataframe
response = requests.get(f'https://api.openalex.org/works?page={page}&per-page=200&filter=publication_year:{start_year}-{end_year},type:article&search={search_terms}')
data = pd.DataFrame(response.json()['results'])
#append to empty dataframe
search_results = pd.concat([search_results, data])
#subset to relevant features
search_results = search_results[["id", "title", "display_name", "publication_year", "publication_date",
"type", "countries_distinct_count","institutions_distinct_count",
"has_fulltext", "cited_by_count", "keywords", "referenced_works_count", "abstract_inverted_index"]]
return(search_results)
Realizamos 5 búsquedas diferentes, cada una de ellas en un área tecnológica diferente. Estas áreas tecnológicas están inspiradas en las “Áreas tecnológicas críticas” del Departamento de Defensa. Vea más aquí:
A continuación se muestra un ejemplo de una búsqueda que utiliza la sintaxis OpenAlex requerida:
#search for Trusted AI and Autonomy
ai_search = import_data(35, 2016, 2024, "'artificial intelligence' OR 'deep learn' OR 'neural net' OR 'autonomous' OR drone")
Después de compilar nuestras búsquedas y eliminar los documentos duplicados, debemos limpiar los datos para prepararlos para nuestro modelo de temas. Hay dos problemas principales con nuestro resultado actual.
- Los resúmenes se devuelven como un índice invertido (por razones legales). Sin embargo, podemos utilizarlos para devolver el texto original.
- Una vez que obtengamos el texto original, estará crudo y sin procesar, lo que generará ruido y dañará nuestro modelo. Realizaremos un preprocesamiento de PNL tradicional para prepararlo para el modelo.
A continuación se muestra una función para devolver el texto original de un índice invertido.
def undo_inverted_index(inverted_index):"""
The purpose of the function is to 'undo' and inverted index. It inputs an inverted index and
returns the original string.
"""
#create empty lists to store uninverted index
word_index = []
words_unindexed = []
#loop through index and return key-value pairs
for k,v in inverted_index.items():
for index in v: word_index.append([k,index])
#sort by the index
word_index = sorted(word_index, key = lambda x : x[1])
#join only the values and flatten
for pair in word_index:
words_unindexed.append(pair[0])
words_unindexed = ' '.join(words_unindexed)
return(words_unindexed)
Ahora que tenemos el texto sin procesar, podemos realizar nuestros pasos de preprocesamiento tradicionales, como estandarización, eliminación de palabras vacías, lematización, etc. A continuación, se presentan funciones que se pueden asignar a una lista o serie de documentos.
def preprocess(text):"""
This function takes in a string, coverts it to lowercase, cleans
it (remove special character and numbers), and tokenizes it.
"""
#convert to lowercase
text = text.lower()
#remove special character and digits
text = re.sub(r'\d+', '', text)
text = re.sub(r'[^\w\s]', '', text)
#tokenize
tokens = nltk.word_tokenize(text)
return(tokens)
def remove_stopwords(tokens):"""
This function takes in a list of tokens (from the 'preprocess' function) and
removes a list of stopwords. Custom stopwords can be added to the 'custom_stopwords' list.
"""
#set default and custom stopwords
stop_words = nltk.corpus.stopwords.words('english')
custom_stopwords = []
stop_words.extend(custom_stopwords)
#filter out stopwords
filtered_tokens = [word for word in tokens if word not in stop_words]
return(filtered_tokens)
def lemmatize(tokens):"""
This function conducts lemmatization on a list of tokens (from the 'remove_stopwords' function).
This shortens each word down to its root form to improve modeling results.
"""
#initalize lemmatizer and lemmatize
lemmatizer = nltk.WordNetLemmatizer()
lemmatized_tokens = [lemmatizer.lemmatize(token) for token in tokens]
return(lemmatized_tokens)
def clean_text(text):"""
This function uses the previously defined functions to take a string and\
run it through the entire data preprocessing process.
"""
#clean, tokenize, and lemmatize a string
tokens = preprocess(text)
filtered_tokens = remove_stopwords(tokens)
lemmatized_tokens = lemmatize(filtered_tokens)
clean_text = ' '.join(lemmatized_tokens)
return(clean_text)
Ahora que tenemos una serie de documentos preprocesados, ¡podemos crear nuestro primer modelo de tema!