This class provides methods to create a sequence from directories, documents, strings… To calculate different metrics from these sequences. It consists of a Python library for calculating different metrics from plain texts.
In this library, we have sequences and analyzers.
Sequences: are the main element of the library. A sequence has three main attributes:
Format: is a string with the origin format of a sequence. This format can be a string, a file(.txt), a directory, a token, etc.
Metadata: This is a dictionary where we store the metadata of a sequence, like the source text of a sequence (if the origin of the sequence is a file of text), or the path of the directory (if the origin of the sequence is a directory). Different analyzers store the result of the analysis inside this dictionary if the result of the analysis is metadata (the number of words, the label with the emotion of a text, the text source replacing words...).
Children: This is a dictionary where we store a list of sequences that came from the actual sequence. For example, if we have a text, we can split this text in phrases. "Phrases" will be the key in the dictionary and each phrase of the text will be a sequence inside the list of sequences of the key of the children's dictionary. Each phrase can be split into words too, that we will store in the children's dictionary of the phrase sequences. So, inside the original sequence(text) we have a sequence of phrases and inside of them sequences of words. This forms the different levels of a sequence.
So, to access children of level Words we can use "Phrases/Words" in filter or analyze. In the same mode, we can use "Phrases/Words/text" to access a text(value of metadata dictionary) at the Words level in functions like filterMetadata or analyze.
Analyzers: The analyzers provide methods to analyze sequences and store the result in a sequence. These analyzers can analyze the metadata of a sequence or the children of a sequence. And can store the result in any of these dictionaries (metadata or children).
Analyzer.py: This module provides an abstract class with methods for the calculation of different metrics on a sequence.
ComplexityAnalyzer.py: This module provides class methods for the calculation of different complexity metrics on a sequence. This class inherits from Analyzer.py
CREA_5000.txt: A dataset of 5000 Spanish words ordered by its absolute frequency.
EmotionAnalyzer.py: This module provides class methods for the calculation of the emotions on a sequence. This class inherits from Analyzer.py
LemmaAnalyzer.py: This module provides class methods for the calculation of different lemma metrics on a sequence. This class inherits from Analyzer.py
LexicalDiversityAnalyzer.py: This module provides a class methods for the calculation of different lexical diversity measures. This class inherits from Analyzer.py
POSAnalyzer.py: This module provides a class methods for the calculation of different Part-of-speech metrics on a sequence. This class inherits from Analyzer.py
PolarityAnalyzer.py: This module provides class methods for the calculation of the polarity on a sequence. This class inherits from Analyzer.py
Sequence.py: This module provides an abstract class with methods for creating sequences from different sources. A sequence contains 2 dictionaries, one for metadata and the other for the subsequence of this sequence.
SequenceDirectory.py: This module provides methods for creating a sequence from a directory.
SequenceFile.py: This module provides methods for creating a sequence from a file or a document.
SequenceString.py: This module provides methods for creating a sequence from a string.
SequenceToken.py: This module provides methods for creating a sequence from a token.
StylometryAnalyzer.py: This module provides class methods for the calculation of different stylometry metrics on a sequence. This class inherits from Analyzer.py
VolumetryAnalyzer.py: This module provides class methods for the calculation of different volumetry metrics on a sequence. This class inherits from Analyzer.py
IronityAnalyzer.py: This module provides class methods for the calculation of the ironity on a sequence. This class inherits from Analyzer.py
NERAnalyzer.py: This module provides class methods for the search of different NER on a sequence. This class inherits from Analyzer.py
NGramsAnalyzer.py: This module provides class methods for the calculation of n-grams and their frequence on a sequence. This class inherits from Analyzer.py
EmojiAnalyzer.py: This module provides class methods for the calculation of different emojis metrics on a sequence. This class inherits from Analyzer.py
TextEmojiPolarityAnalyzer.py: This module provides class methods to calculate the polarity of a text related to text emojis on a sequence. This class inherits from Analyzer.py
EmojiPolarityAnalyzer.py: This module provides class methods to calculate the polarity of a text related to emojis on a sequence. This class inherits from Analyzer.py
PerplexityAnalyzer.py: This module provides class methods to calculate the perplexity of a text. This class inherits from Analyzer.py
Note: All of the analyzers implemented by default are applied to plain text.
example_text.txt: Simple .txt file to test the library.
example.ipynb: Colab notebook that shows how to use the different methods of textFlow.
AnalyzeADataframe.ipynb: Colab notebook that shows how to use textFlow with a Dataframe of pandas.
In this section, we introduce the different metrics offered in this Python library. These metrics are returned by the corresponding analyzer and stored in the corresponding dictionary (metadata or children) of a sequence.
Volumetry: Here it calculates different metrics that are stored in a metadata dictionary:
Lemmas: It calculates different metrics that are stored in a metadata dictionary:
Part-of-speech (POS): It calculates different metrics that are stored in a metadata dictionary:
Complexity: It calculates different metrics that are stored in a metadata dictionary:
Stylometry: It calculates different metrics that are stored in a metadata dictionary:
LexicalDiversity It calculates different metrics on lexical diversity:
Polarity: Polarity score of a text.
Emotions: Emotions score of a text.
Emojis: It calculates different metrics that are stored in a metadata dictionary:
NER: It calculates different metrics that are stored in a metadata dictionary:
N-Grams: It calculates different metrics that are stored in a metadata dictionary:
Ironity: Ironity score of a text.
TextEmojiPolarity: It calculates a text's polarity based on text emojis. (Martínez-Cámara, E., Jiménez-Zafra, S. M., Martin-Valdivia, M. T., & Lopez, L. A. U. (2014, August). SINAI: voting system for twitter sentiment analysis. In Proceedings of the 8th International Workshop on Semantic Evaluation (SemEval 2014) (pp. 572-577))
EmojiPolarity: It calculates a text's polarity based on emojis.
Perplexity: It calculates the perplexity of a text.
ComplexityAnalyzer.py, POSAnalyzer, LemmaAnalyzer, and NERAnalyzer: In these classes, spacy is used to calculate the different metrics of the analyzers. If you want to use other packages, you should implement the methods nlp, sents, pos_, lemma_, and text.
IronityAnalizer.py, EmotionAnalyzer.py, PolarityAnalyzer, and PerplexityAnalyzer.py: These classes use models and pipelines of transformers, you can use different models to infer the emotion, the polarity, or the perplexity of a text.
EmojiAnalizer.py, EmojiPolarityAnalyzer.py: This class use emoji library.
If you want to create a class to initialize a sequence without a class for it, you can create your own.
We must create a class that inherits from Sequence.py
Inside of this class, we have to create the following functions, because these functions implement the basic functionality of a Sequence. The basic functionality is like iterating in the sequence, filtering in subsequences(children), printing the sequence...:
def initializeSequence(self, format):
'''
Initializes the attributes of a sequence.
Args:
format: a string with the origin format of the sequence.
'''
super().initializeSequence(format)
def __str__(self):
'''
Convert a Sequence to a string
Returns:
A string that contains the text of a Sequence
'''
return super().__str__()
def __repr__(self):
'''
Convert a Sequence to a string
Returns:
A string with the formal representation of a Sequence
'''
return super().__repr__()
def __len__(self):
'''
Calculate the length of a Sequence.
The length of a Sequence is the length of the children.
Returns:
A number with the length of the Sequence
'''
return super().__len__()
def __iter__(self):
'''
Iterate in a Sequence
To do this, we iterates througth the children dictionary
Returns:
A Sequence Iterator
'''
return super().__iter__()
def __getitem__(self, idx):
'''
Get the value of a key from the dictionary of children
Args:
idx: a string that represent the key of the children dictionary
or an integer that represent the position of the key in children dictionary keys
Returns:
A List of Sequences
'''
return super().__getitem__(idx)
def __eq__(self, other):
'''
Check if a sequence it is the same that the current one.
Args:
other: a sequence to check if it is the same that the current one.
Returns:
True if the sequences are equals.
False in others cases.
'''
return super().__eq__(other)
def depth(self,dictionaryList: Optional[list] = None):
'''
Calculate the maximum depth of a Sequence
Args:
diccionaryList: the inicial list to calculate the depth.
Returns:
A tuple that contains a number (the depth of a Sequence) and a list (the route of the max depth)
'''
return super().depth(dictionaryList)
def filter(self, level, criteria):
'''
Filter the children of a Sequence according to a criteria
Args:
level: the route of the level as string, separating each level with "/"
criteria: the filter function
Returns:
A generator with the result of the filter
'''
return super().filter(level,criteria)
def filterMetadata(self, level, criteria):
'''
Filter the children of a Sequence according to a criteria
Args:
level: the route of the level as string, separating each level with "/"
criteria: the filter function
Returns:
A generator with the result of the filter
'''
return super().filterMetadata(level,criteria)
Now, we have to create the init function in the class:
def __init__(self):
pass
First of all, we have to call self.initializeSequence("format"), this function initialize the metadata, and children dictionary of a Sequence and put the format of a sequence.
We are going to see, how to initialize a sequence from a directory.
def __init__(self):
# Initializes the attributes of a sequence.
self.initializeSequence("directory")
Then, we have to think about the metadata that we will have and how the children sequences are going to be built in that initializer.
In the example we have like metadata the name of the files of the directory and the path of the diferent subdirectories.
def __init__(self,src):
'''
Initialize a Sequence from a directory path
By default, create subsequences for any directories and files in the source directory
and for each file, create subsequence, splitting the text of the file into words.
Args:
src: the path of the directory
'''
# Initializes the attributes of a sequence.
self.initializeSequence("directory")
# Create the metadata and children of a Sequence
self.metadata["nameFiles"] = []
self.metadata["directoriesPath"] = []
contenido = os.listdir(src)
for file in contenido:
if os.path.isfile(src+"/"+file):
self.metadata["nameFiles"].append(file)
else:
self.metadata["directoriesPath"].append(src+"/"+file)
Finally,we can create the sequences down to the lowest level by calling other sequence initializers and what labels they will have in the children dictionary.
Here, we can see how we add new parameters to create more sublevels in the original sequence.
def __init__(self,src,listLabel = ["directories","files","tokens"],listClasses=[SequenceFile,SequenceString],listTokenizer=[WhitespaceTokenizer()]):
'''
Initialize a Sequence from a directory path
By default, create subsequences for any directories and files in the source directory
and for each file, create subsequence, splitting the text of the file into words.
Args:
src: the path of the directory
listLabel: a list with different labels to create new levels in the children dictionary
listClasses: a list with different classes that inicialize a sequence with sublevels
listTokenizer: a list with the tokenizer to inicialize the different subsequences
'''
# Initializes the attributes of a sequence.
self.initializeSequence("directory")
# Create the metadata and children of a Sequence
self.metadata["nameFiles"] = []
self.metadata["directoriesPath"] = []
if not listTokenizer or listTokenizer == None:
listTokenizer = [WhitespaceTokenizer()]
contenido = os.listdir(src)
for file in contenido:
if os.path.isfile(src+"/"+file):
self.metadata["nameFiles"].append(file)
if listLabel and listClasses:
if listLabel[1] in self.children:
#Create a sublevel of sequence
self.children[listLabel[1]].append(listClasses[0](src+"/"+file,listLabel[1:],listClasses[1:],listTokenizer[1:]))
else:
#Create a sublevel of sequence
self.children[listLabel[1]] = [listClasses[0](src+"/"+file,listLabel[1:],listClasses[1:],listTokenizer[1:])]
else:
self.metadata["directoriesPath"].append(src+"/"+file)
if listLabel[0] in self.children:
self.children[listLabel[0]].append(SequenceDirectory(src+"/"+file,listLabel,listClasses,listTokenizer ))
else:
self.children[listLabel[0]] = [SequenceDirectory(src+"/"+file,listLabel,listClasses,listTokenizer)]
The result of the new Initializer of a Sequence from directory is look like:
class SequenceDirectory(Sequence):
"""
A class that provides methods to create a sequence from a directory
Attributes:
format: a string with the origin format of a sequence.
metadata: a dictionary with the metadata of a sequence.
children: a dictionary with the subsequence of a sequence.
"""
def __init__(self,src,listLabel = ["directories","files","tokens"],listClasses=[SequenceFile,SequenceString],listTokenizer=[WhitespaceTokenizer()]):
'''
Initialize a Sequence from a directory path
By default, create subsequences for any directories and files in the source directory
and for each file, create subsequence, splitting the text of the file into words.
Args:
src: the path of the directory
listLabel: a list with different labels to create new levels in the children dictionary
listClasses: a list with different classes that inicialize a sequence with sublevels
listTokenizer: a list with the tokenizer to inicialize the different subsequences
'''
self.initializeSequence("directory")
self.metadata["nameFiles"] = []
self.metadata["directoriesPath"] = []
if not listTokenizer or listTokenizer == None:
listTokenizer = [WhitespaceTokenizer()]
contenido = os.listdir(src)
for file in contenido:
if os.path.isfile(src+"/"+file):
self.metadata["nameFiles"].append(file)
if listLabel and listClasses:
if listLabel[1] in self.children:
self.children[listLabel[1]].append(listClasses[0](src+"/"+file,listLabel[1:],listClasses[1:],listTokenizer[1:]))
else:
self.children[listLabel[1]] = [listClasses[0](src+"/"+file,listLabel[1:],listClasses[1:],listTokenizer[1:])]
else:
self.metadata["directoriesPath"].append(src+"/"+file)
if listLabel[0] in self.children:
self.children[listLabel[0]].append(SequenceDirectory(src+"/"+file,listLabel,listClasses,listTokenizer ))
else:
self.children[listLabel[0]] = [SequenceDirectory(src+"/"+file,listLabel,listClasses,listTokenizer)]
def initializeSequence(self, format):
'''
Initializes the attributes of a sequence.
Args:
format: a string with the origin format of the sequence.
'''
super().initializeSequence(format)
def __str__(self):
'''
Convert a Sequence to a string
Returns:
A string that contains the text of a Sequence
'''
return super().__str__()
def __repr__(self):
'''
Convert a Sequence to a string
Returns:
A string with the formal representation of a Sequence
'''
return super().__repr__()
def __len__(self):
'''
Calculate the length of a Sequence.
The length of a Sequence is the length of the children.
Returns:
A number with the length of the Sequence
'''
return super().__len__()
def __iter__(self):
'''
Iterate in a Sequence
To do this, we iterates througth the children dictionary
Returns:
A Sequence Iterator
'''
return super().__iter__()
def __getitem__(self, idx):
'''
Get the value of a key from the dictionary of children
Args:
idx: a string that represent the key of the children dictionary
or an integer that represent the position of the key in children dictionary keys
Returns:
A List of Sequences
'''
return super().__getitem__(idx)
def __eq__(self, other):
'''
Check if a sequence it is the same that the current one.
Args:
other: a sequence to check if it is the same that the current one.
Returns:
True if the sequences are equals.
False in others cases.
'''
return super().__eq__(other)
def depth(self,dictionaryList: Optional[list] = None):
'''
Calculate the maximum depth of a Sequence
Args:
diccionaryList: the inicial list to calculate the depth.
Returns:
A tuple that contains a number (the depth of a Sequence) and a list (the route of the max depth)
'''
return super().depth(dictionaryList)
def filter(self, level, criteria):
'''
Filter the children of a Sequence according to a criteria
Args:
level: the route of the level as string, separating each level with "/"
criteria: the filter function
Returns:
A generator with the result of the filter
'''
return super().filter(level,criteria)
def filterMetadata(self, level, criteria):
'''
Filter the children of a Sequence according to a criteria
Args:
level: the route of the level as string, separating each level with "/"
criteria: the filter function
Returns:
A generator with the result of the filter
'''
return super().filterMetadata(level,criteria)
The steps to create an analyzer are:
Create a class that inherits of Analyzer.py
Create the init function of this class with the params to configurate this class
For example, if we want to do a ironity analyzer, we use a pipeline that need a task, a model, a maximum of Embedding and if return all the scores of only the label with the maximum score of the text. The analyzer must be as flexible as possible, so all of the parameters that need the pipeline are passed like params at the init function.
Look that inside the init function we modified the labels of the model, this is because the model by defect dont have the labels clearly defined. (NI = Non-Ironic and I = Ironic)
def __init__(self, task = "text-classification",modelIronity = 'dtomas/roberta-base-bne-irony', allScores = True, maxEmbedding = 514):
"""
Create an ironic analyzer.
Args:
task: the task defining which pipeline will be returned.
model: the model that will be used by the pipeline to make predictions.
allScores: True, if we want that the classifier returns all scores. False, in other case.
maxEmbedding: The number of max_position_embeddings in the config.json of the model selected.
"""
if modelIronity == 'dtomas/roberta-base-bne-irony':
model = AutoModelForSequenceClassification.from_pretrained(modelIronity)
model.config.id2label = {0: 'NI', 1: 'I'}
model.config.label2id = {'NI': 0, 'I': 1}
tokenizer = AutoTokenizer.from_pretrained(modelIronity)
self.ironityClassifier = pipeline(task,model= model, tokenizer=tokenizer,return_all_scores=allScores, truncation=True)
else:
self.ironityClassifier = pipeline(task,model= modelIronity, return_all_scores=allScores)
self.maxEmbedding = maxEmbedding
Create a function that analyze a list of things that we want to analyze and return a list with the result of each text.
For example, we want to create an analyzer of text, so we need a function that receives a list of texts and applies what it needs to each text. This function have to return a list with the result of the analisys.
We can see an example of a ironity function, that receives a list of text, for each text apply the ironity classifier and put the result in a list. This list is returned by the function:
def ironity(self, arrayText):
"""
Function that analyzes the ironity of a list of texts.
Args:
arrayText: list that contains the texts that we want to analyze
Returns:
A list with the dictionaries. Each dictionary contains the result
of the analysis of the corresponding text.
"""
arrayResults =[]
for text in arrayText:
prediction = self.ironityClassifier(text[:self.maxEmbedding])
arrayResults.append(prediction)
return arrayResults
Create the analyze function (inherit function of Analyzer.py):
Replace self.analyzeFunction with the analyzer function that we have implemented in the last point.
The parameter "True" of the super().analyze is because this analyzer is a metadata analyzer. If do you want to create an analyzer of sequence, this parameter must be "False".
def analyze(self, sequence, tag, levelOfAnalyzer, levelOfResult:Optional[str] = ""):
"""
Analyze a sequence with a ironic function.
Args:
sequence: the Sequence we want to analyze.
tag: the label to store the analysis result.
levelOfAnalyzer: the path of the sequence level to analyze inside of the result.
levelOfResult: the path of the sequence level to store the result.
"""
super().analyze(self.analyzeFunction,sequence, tag, levelOfAnalyzer, levelOfResult, True)
In our example this function looks like:
def analyze(self, sequence, tag, levelOfAnalyzer, levelOfResult:Optional[str] = ""):
"""
Analyze a sequence with a ironic function.
Args:
sequence: the Sequence we want to analyze.
tag: the label to store the analysis result.
levelOfAnalyzer: the path of the sequence level to analyze inside of the result.
levelOfResult: the path of the sequence level to store the result.
"""
super().analyze(self.ironity,sequence, tag, levelOfAnalyzer, levelOfResult, True)
The result of the new analyzer is:
class IronityAnalyzer(Analyzer): """ A class that provides methods to analyze the ironity of the text of a sequence.
Attributes: ironityClassifier: a pipeline that uses a model for inference the ironity of the text of a sequence. By default, the label 'NI' is non-ironic and 'I' ironic. maxEmbedding: The number of max_position_embeddings in the config.json of the model selected. """
def init(self, task = "text-classification",modelIronity = 'dtomas/roberta-base-bne-irony', allScores = True, maxEmbedding = 514): """ Create an ironic analyzer.
Args:
task: the task defining which pipeline will be returned.
model: the model that will be used by the pipeline to make predictions.
allScores: True, if we want that the classifier returns all scores. False, in other case.
maxEmbedding: The number of max_position_embeddings in the config.json of the model selected.
"""
if modelIronity == 'dtomas/roberta-base-bne-irony':
model = AutoModelForSequenceClassification.from_pretrained(modelIronity)
model.config.id2label = {0: 'NI', 1: 'I'}
model.config.label2id = {'NI': 0, 'I': 1}
tokenizer = AutoTokenizer.from_pretrained(modelIronity)
self.ironityClassifier = pipeline(task,model= model, tokenizer=tokenizer,return_all_scores=allScores, truncation=True)
else:
self.ironityClassifier = pipeline(task,model= modelIronity, return_all_scores=allScores)
self.maxEmbedding = maxEmbedding
def analyze(self, sequence, tag, levelOfAnalyzer, levelOfResult:Optional[str] = ""): """ Analyze a sequence with a ironic function.
Args:
sequence: the Sequence we want to analyze.
tag: the label to store the analysis result.
levelOfAnalyzer: the path of the sequence level to analyze inside of the result.
levelOfResult: the path of the sequence level to store the result.
"""
super().analyze(self.ironity,sequence, tag, levelOfAnalyzer, levelOfResult, True)
def ironity(self, arrayText): """ Function that analyzes the ironity of a list of texts.
Args:
arrayText: list that contains the texts that we want to analyze
Returns:
A list with the dictionaries. Each dictionary contains the result
of the analysis of the corresponding text.
"""
arrayResults =[]
for text in arrayText:
prediction = self.ironityClassifier(text[:self.maxEmbedding])
arrayResults.append(prediction)
return arrayResults
If you have any questions about the use of the library, you can contact with Estrella Vallecillo ([email protected]).
This work has been partially supported by the CONSENSO (PID2021-122263OB-C21), MODERATES (TED2021-130145B-I00), SocialTOX (PDC2022-133146-C21) projects financed by the Plan Nacional I+D+i of the Government of Spain, and the PRECOM (SUBV-00016) project financed by the Ministry of Consumer Affairs of the Government of Spain.
This class provides methods to create a sequence from directories, documents, strings… To calculate different metrics from these sequences. It consists of a Python library for calculating different metrics from plain texts.
In this library, we have sequences and analyzers.
Sequences: are the main element of the library. A sequence has three main attributes:
Format: is a string with the origin format of a sequence. This format can be a string, a file(.txt), a directory, a token, etc.
Metadata: This is a dictionary where we store the metadata of a sequence, like the source text of a sequence (if the origin of the sequence is a file of text), or the path of the directory (if the origin of the sequence is a directory). Different analyzers store the result of the analysis inside this dictionary if the result of the analysis is metadata (the number of words, the label with the emotion of a text, the text source replacing words...).
Children: This is a dictionary where we store a list of sequences that came from the actual sequence. For example, if we have a text, we can split this text in phrases. "Phrases" will be the key in the dictionary and each phrase of the text will be a sequence inside the list of sequences of the key of the children's dictionary. Each phrase can be split into words too, that we will store in the children's dictionary of the phrase sequences. So, inside the original sequence(text) we have a sequence of phrases and inside of them sequences of words. This forms the different levels of a sequence.
So, to access children of level Words we can use "Phrases/Words" in filter or analyze. In the same mode, we can use "Phrases/Words/text" to access a text(value of metadata dictionary) at the Words level in functions like filterMetadata or analyze.
Analyzers: The analyzers provide methods to analyze sequences and store the result in a sequence. These analyzers can analyze the metadata of a sequence or the children of a sequence. And can store the result in any of these dictionaries (metadata or children).
Analyzer.py: This module provides an abstract class with methods for the calculation of different metrics on a sequence.
ComplexityAnalyzer.py: This module provides class methods for the calculation of different complexity metrics on a sequence. This class inherits from Analyzer.py
CREA_5000.txt: A dataset of 5000 Spanish words ordered by its absolute frequency.
EmotionAnalyzer.py: This module provides class methods for the calculation of the emotions on a sequence. This class inherits from Analyzer.py
LemmaAnalyzer.py: This module provides class methods for the calculation of different lemma metrics on a sequence. This class inherits from Analyzer.py
LexicalDiversityAnalyzer.py: This module provides a class methods for the calculation of different lexical diversity measures. This class inherits from Analyzer.py
POSAnalyzer.py: This module provides a class methods for the calculation of different Part-of-speech metrics on a sequence. This class inherits from Analyzer.py
PolarityAnalyzer.py: This module provides class methods for the calculation of the polarity on a sequence. This class inherits from Analyzer.py
Sequence.py: This module provides an abstract class with methods for creating sequences from different sources. A sequence contains 2 dictionaries, one for metadata and the other for the subsequence of this sequence.
SequenceDirectory.py: This module provides methods for creating a sequence from a directory.
SequenceFile.py: This module provides methods for creating a sequence from a file or a document.
SequenceString.py: This module provides methods for creating a sequence from a string.
SequenceToken.py: This module provides methods for creating a sequence from a token.
StylometryAnalyzer.py: This module provides class methods for the calculation of different stylometry metrics on a sequence. This class inherits from Analyzer.py
VolumetryAnalyzer.py: This module provides class methods for the calculation of different volumetry metrics on a sequence. This class inherits from Analyzer.py
IronityAnalyzer.py: This module provides class methods for the calculation of the ironity on a sequence. This class inherits from Analyzer.py
NERAnalyzer.py: This module provides class methods for the search of different NER on a sequence. This class inherits from Analyzer.py
NGramsAnalyzer.py: This module provides class methods for the calculation of n-grams and their frequence on a sequence. This class inherits from Analyzer.py
EmojiAnalyzer.py: This module provides class methods for the calculation of different emojis metrics on a sequence. This class inherits from Analyzer.py
TextEmojiPolarityAnalyzer.py: This module provides class methods to calculate the polarity of a text related to text emojis on a sequence. This class inherits from Analyzer.py
EmojiPolarityAnalyzer.py: This module provides class methods to calculate the polarity of a text related to emojis on a sequence. This class inherits from Analyzer.py
PerplexityAnalyzer.py: This module provides class methods to calculate the perplexity of a text. This class inherits from Analyzer.py
Note: All of the analyzers implemented by default are applied to plain text.
example_text.txt: Simple .txt file to test the library.
example.ipynb: Colab notebook that shows how to use the different methods of textFlow.
AnalyzeADataframe.ipynb: Colab notebook that shows how to use textFlow with a Dataframe of pandas.
In this section, we introduce the different metrics offered in this Python library. These metrics are returned by the corresponding analyzer and stored in the corresponding dictionary (metadata or children) of a sequence.
Volumetry: Here it calculates different metrics that are stored in a metadata dictionary:
Lemmas: It calculates different metrics that are stored in a metadata dictionary:
Part-of-speech (POS): It calculates different metrics that are stored in a metadata dictionary:
Complexity: It calculates different metrics that are stored in a metadata dictionary:
Stylometry: It calculates different metrics that are stored in a metadata dictionary:
LexicalDiversity It calculates different metrics on lexical diversity:
Polarity: Polarity score of a text.
Emotions: Emotions score of a text.
Emojis: It calculates different metrics that are stored in a metadata dictionary:
NER: It calculates different metrics that are stored in a metadata dictionary:
N-Grams: It calculates different metrics that are stored in a metadata dictionary:
Ironity: Ironity score of a text.
TextEmojiPolarity: It calculates a text's polarity based on text emojis. (Martínez-Cámara, E., Jiménez-Zafra, S. M., Martin-Valdivia, M. T., & Lopez, L. A. U. (2014, August). SINAI: voting system for twitter sentiment analysis. In Proceedings of the 8th International Workshop on Semantic Evaluation (SemEval 2014) (pp. 572-577))
EmojiPolarity: It calculates a text's polarity based on emojis.
Perplexity: It calculates the perplexity of a text.
ComplexityAnalyzer.py, POSAnalyzer, LemmaAnalyzer, and NERAnalyzer: In these classes, spacy is used to calculate the different metrics of the analyzers. If you want to use other packages, you should implement the methods nlp, sents, pos_, lemma_, and text.
IronityAnalizer.py, EmotionAnalyzer.py, PolarityAnalyzer, and PerplexityAnalyzer.py: These classes use models and pipelines of transformers, you can use different models to infer the emotion, the polarity, or the perplexity of a text.
EmojiAnalizer.py, EmojiPolarityAnalyzer.py: This class use emoji library.
If you want to create a class to initialize a sequence without a class for it, you can create your own.
We must create a class that inherits from Sequence.py
Inside of this class, we have to create the following functions, because these functions implement the basic functionality of a Sequence. The basic functionality is like iterating in the sequence, filtering in subsequences(children), printing the sequence...:
def initializeSequence(self, format):
'''
Initializes the attributes of a sequence.
Args:
format: a string with the origin format of the sequence.
'''
super().initializeSequence(format)
def __str__(self):
'''
Convert a Sequence to a string
Returns:
A string that contains the text of a Sequence
'''
return super().__str__()
def __repr__(self):
'''
Convert a Sequence to a string
Returns:
A string with the formal representation of a Sequence
'''
return super().__repr__()
def __len__(self):
'''
Calculate the length of a Sequence.
The length of a Sequence is the length of the children.
Returns:
A number with the length of the Sequence
'''
return super().__len__()
def __iter__(self):
'''
Iterate in a Sequence
To do this, we iterates througth the children dictionary
Returns:
A Sequence Iterator
'''
return super().__iter__()
def __getitem__(self, idx):
'''
Get the value of a key from the dictionary of children
Args:
idx: a string that represent the key of the children dictionary
or an integer that represent the position of the key in children dictionary keys
Returns:
A List of Sequences
'''
return super().__getitem__(idx)
def __eq__(self, other):
'''
Check if a sequence it is the same that the current one.
Args:
other: a sequence to check if it is the same that the current one.
Returns:
True if the sequences are equals.
False in others cases.
'''
return super().__eq__(other)
def depth(self,dictionaryList: Optional[list] = None):
'''
Calculate the maximum depth of a Sequence
Args:
diccionaryList: the inicial list to calculate the depth.
Returns:
A tuple that contains a number (the depth of a Sequence) and a list (the route of the max depth)
'''
return super().depth(dictionaryList)
def filter(self, level, criteria):
'''
Filter the children of a Sequence according to a criteria
Args:
level: the route of the level as string, separating each level with "/"
criteria: the filter function
Returns:
A generator with the result of the filter
'''
return super().filter(level,criteria)
def filterMetadata(self, level, criteria):
'''
Filter the children of a Sequence according to a criteria
Args:
level: the route of the level as string, separating each level with "/"
criteria: the filter function
Returns:
A generator with the result of the filter
'''
return super().filterMetadata(level,criteria)
Now, we have to create the init function in the class:
def __init__(self):
pass
First of all, we have to call self.initializeSequence("format"), this function initialize the metadata, and children dictionary of a Sequence and put the format of a sequence.
We are going to see, how to initialize a sequence from a directory.
def __init__(self):
# Initializes the attributes of a sequence.
self.initializeSequence("directory")
Then, we have to think about the metadata that we will have and how the children sequences are going to be built in that initializer.
In the example we have like metadata the name of the files of the directory and the path of the diferent subdirectories.
def __init__(self,src):
'''
Initialize a Sequence from a directory path
By default, create subsequences for any directories and files in the source directory
and for each file, create subsequence, splitting the text of the file into words.
Args:
src: the path of the directory
'''
# Initializes the attributes of a sequence.
self.initializeSequence("directory")
# Create the metadata and children of a Sequence
self.metadata["nameFiles"] = []
self.metadata["directoriesPath"] = []
contenido = os.listdir(src)
for file in contenido:
if os.path.isfile(src+"/"+file):
self.metadata["nameFiles"].append(file)
else:
self.metadata["directoriesPath"].append(src+"/"+file)
Finally,we can create the sequences down to the lowest level by calling other sequence initializers and what labels they will have in the children dictionary.
Here, we can see how we add new parameters to create more sublevels in the original sequence.
def __init__(self,src,listLabel = ["directories","files","tokens"],listClasses=[SequenceFile,SequenceString],listTokenizer=[WhitespaceTokenizer()]):
'''
Initialize a Sequence from a directory path
By default, create subsequences for any directories and files in the source directory
and for each file, create subsequence, splitting the text of the file into words.
Args:
src: the path of the directory
listLabel: a list with different labels to create new levels in the children dictionary
listClasses: a list with different classes that inicialize a sequence with sublevels
listTokenizer: a list with the tokenizer to inicialize the different subsequences
'''
# Initializes the attributes of a sequence.
self.initializeSequence("directory")
# Create the metadata and children of a Sequence
self.metadata["nameFiles"] = []
self.metadata["directoriesPath"] = []
if not listTokenizer or listTokenizer == None:
listTokenizer = [WhitespaceTokenizer()]
contenido = os.listdir(src)
for file in contenido:
if os.path.isfile(src+"/"+file):
self.metadata["nameFiles"].append(file)
if listLabel and listClasses:
if listLabel[1] in self.children:
#Create a sublevel of sequence
self.children[listLabel[1]].append(listClasses[0](src+"/"+file,listLabel[1:],listClasses[1:],listTokenizer[1:]))
else:
#Create a sublevel of sequence
self.children[listLabel[1]] = [listClasses[0](src+"/"+file,listLabel[1:],listClasses[1:],listTokenizer[1:])]
else:
self.metadata["directoriesPath"].append(src+"/"+file)
if listLabel[0] in self.children:
self.children[listLabel[0]].append(SequenceDirectory(src+"/"+file,listLabel,listClasses,listTokenizer ))
else:
self.children[listLabel[0]] = [SequenceDirectory(src+"/"+file,listLabel,listClasses,listTokenizer)]
The result of the new Initializer of a Sequence from directory is look like:
class SequenceDirectory(Sequence):
"""
A class that provides methods to create a sequence from a directory
Attributes:
format: a string with the origin format of a sequence.
metadata: a dictionary with the metadata of a sequence.
children: a dictionary with the subsequence of a sequence.
"""
def __init__(self,src,listLabel = ["directories","files","tokens"],listClasses=[SequenceFile,SequenceString],listTokenizer=[WhitespaceTokenizer()]):
'''
Initialize a Sequence from a directory path
By default, create subsequences for any directories and files in the source directory
and for each file, create subsequence, splitting the text of the file into words.
Args:
src: the path of the directory
listLabel: a list with different labels to create new levels in the children dictionary
listClasses: a list with different classes that inicialize a sequence with sublevels
listTokenizer: a list with the tokenizer to inicialize the different subsequences
'''
self.initializeSequence("directory")
self.metadata["nameFiles"] = []
self.metadata["directoriesPath"] = []
if not listTokenizer or listTokenizer == None:
listTokenizer = [WhitespaceTokenizer()]
contenido = os.listdir(src)
for file in contenido:
if os.path.isfile(src+"/"+file):
self.metadata["nameFiles"].append(file)
if listLabel and listClasses:
if listLabel[1] in self.children:
self.children[listLabel[1]].append(listClasses[0](src+"/"+file,listLabel[1:],listClasses[1:],listTokenizer[1:]))
else:
self.children[listLabel[1]] = [listClasses[0](src+"/"+file,listLabel[1:],listClasses[1:],listTokenizer[1:])]
else:
self.metadata["directoriesPath"].append(src+"/"+file)
if listLabel[0] in self.children:
self.children[listLabel[0]].append(SequenceDirectory(src+"/"+file,listLabel,listClasses,listTokenizer ))
else:
self.children[listLabel[0]] = [SequenceDirectory(src+"/"+file,listLabel,listClasses,listTokenizer)]
def initializeSequence(self, format):
'''
Initializes the attributes of a sequence.
Args:
format: a string with the origin format of the sequence.
'''
super().initializeSequence(format)
def __str__(self):
'''
Convert a Sequence to a string
Returns:
A string that contains the text of a Sequence
'''
return super().__str__()
def __repr__(self):
'''
Convert a Sequence to a string
Returns:
A string with the formal representation of a Sequence
'''
return super().__repr__()
def __len__(self):
'''
Calculate the length of a Sequence.
The length of a Sequence is the length of the children.
Returns:
A number with the length of the Sequence
'''
return super().__len__()
def __iter__(self):
'''
Iterate in a Sequence
To do this, we iterates througth the children dictionary
Returns:
A Sequence Iterator
'''
return super().__iter__()
def __getitem__(self, idx):
'''
Get the value of a key from the dictionary of children
Args:
idx: a string that represent the key of the children dictionary
or an integer that represent the position of the key in children dictionary keys
Returns:
A List of Sequences
'''
return super().__getitem__(idx)
def __eq__(self, other):
'''
Check if a sequence it is the same that the current one.
Args:
other: a sequence to check if it is the same that the current one.
Returns:
True if the sequences are equals.
False in others cases.
'''
return super().__eq__(other)
def depth(self,dictionaryList: Optional[list] = None):
'''
Calculate the maximum depth of a Sequence
Args:
diccionaryList: the inicial list to calculate the depth.
Returns:
A tuple that contains a number (the depth of a Sequence) and a list (the route of the max depth)
'''
return super().depth(dictionaryList)
def filter(self, level, criteria):
'''
Filter the children of a Sequence according to a criteria
Args:
level: the route of the level as string, separating each level with "/"
criteria: the filter function
Returns:
A generator with the result of the filter
'''
return super().filter(level,criteria)
def filterMetadata(self, level, criteria):
'''
Filter the children of a Sequence according to a criteria
Args:
level: the route of the level as string, separating each level with "/"
criteria: the filter function
Returns:
A generator with the result of the filter
'''
return super().filterMetadata(level,criteria)
The steps to create an analyzer are:
Create a class that inherits of Analyzer.py
Create the init function of this class with the params to configurate this class
For example, if we want to do a ironity analyzer, we use a pipeline that need a task, a model, a maximum of Embedding and if return all the scores of only the label with the maximum score of the text. The analyzer must be as flexible as possible, so all of the parameters that need the pipeline are passed like params at the init function.
Look that inside the init function we modified the labels of the model, this is because the model by defect dont have the labels clearly defined. (NI = Non-Ironic and I = Ironic)
def __init__(self, task = "text-classification",modelIronity = 'dtomas/roberta-base-bne-irony', allScores = True, maxEmbedding = 514):
"""
Create an ironic analyzer.
Args:
task: the task defining which pipeline will be returned.
model: the model that will be used by the pipeline to make predictions.
allScores: True, if we want that the classifier returns all scores. False, in other case.
maxEmbedding: The number of max_position_embeddings in the config.json of the model selected.
"""
if modelIronity == 'dtomas/roberta-base-bne-irony':
model = AutoModelForSequenceClassification.from_pretrained(modelIronity)
model.config.id2label = {0: 'NI', 1: 'I'}
model.config.label2id = {'NI': 0, 'I': 1}
tokenizer = AutoTokenizer.from_pretrained(modelIronity)
self.ironityClassifier = pipeline(task,model= model, tokenizer=tokenizer,return_all_scores=allScores, truncation=True)
else:
self.ironityClassifier = pipeline(task,model= modelIronity, return_all_scores=allScores)
self.maxEmbedding = maxEmbedding
Create a function that analyze a list of things that we want to analyze and return a list with the result of each text.
For example, we want to create an analyzer of text, so we need a function that receives a list of texts and applies what it needs to each text. This function have to return a list with the result of the analisys.
We can see an example of a ironity function, that receives a list of text, for each text apply the ironity classifier and put the result in a list. This list is returned by the function:
def ironity(self, arrayText):
"""
Function that analyzes the ironity of a list of texts.
Args:
arrayText: list that contains the texts that we want to analyze
Returns:
A list with the dictionaries. Each dictionary contains the result
of the analysis of the corresponding text.
"""
arrayResults =[]
for text in arrayText:
prediction = self.ironityClassifier(text[:self.maxEmbedding])
arrayResults.append(prediction)
return arrayResults
Create the analyze function (inherit function of Analyzer.py):
Replace self.analyzeFunction with the analyzer function that we have implemented in the last point.
The parameter "True" of the super().analyze is because this analyzer is a metadata analyzer. If do you want to create an analyzer of sequence, this parameter must be "False".
def analyze(self, sequence, tag, levelOfAnalyzer, levelOfResult:Optional[str] = ""):
"""
Analyze a sequence with a ironic function.
Args:
sequence: the Sequence we want to analyze.
tag: the label to store the analysis result.
levelOfAnalyzer: the path of the sequence level to analyze inside of the result.
levelOfResult: the path of the sequence level to store the result.
"""
super().analyze(self.analyzeFunction,sequence, tag, levelOfAnalyzer, levelOfResult, True)
In our example this function looks like:
def analyze(self, sequence, tag, levelOfAnalyzer, levelOfResult:Optional[str] = ""):
"""
Analyze a sequence with a ironic function.
Args:
sequence: the Sequence we want to analyze.
tag: the label to store the analysis result.
levelOfAnalyzer: the path of the sequence level to analyze inside of the result.
levelOfResult: the path of the sequence level to store the result.
"""
super().analyze(self.ironity,sequence, tag, levelOfAnalyzer, levelOfResult, True)
The result of the new analyzer is:
class IronityAnalyzer(Analyzer): """ A class that provides methods to analyze the ironity of the text of a sequence.
Attributes: ironityClassifier: a pipeline that uses a model for inference the ironity of the text of a sequence. By default, the label 'NI' is non-ironic and 'I' ironic. maxEmbedding: The number of max_position_embeddings in the config.json of the model selected. """
def init(self, task = "text-classification",modelIronity = 'dtomas/roberta-base-bne-irony', allScores = True, maxEmbedding = 514): """ Create an ironic analyzer.
Args:
task: the task defining which pipeline will be returned.
model: the model that will be used by the pipeline to make predictions.
allScores: True, if we want that the classifier returns all scores. False, in other case.
maxEmbedding: The number of max_position_embeddings in the config.json of the model selected.
"""
if modelIronity == 'dtomas/roberta-base-bne-irony':
model = AutoModelForSequenceClassification.from_pretrained(modelIronity)
model.config.id2label = {0: 'NI', 1: 'I'}
model.config.label2id = {'NI': 0, 'I': 1}
tokenizer = AutoTokenizer.from_pretrained(modelIronity)
self.ironityClassifier = pipeline(task,model= model, tokenizer=tokenizer,return_all_scores=allScores, truncation=True)
else:
self.ironityClassifier = pipeline(task,model= modelIronity, return_all_scores=allScores)
self.maxEmbedding = maxEmbedding
def analyze(self, sequence, tag, levelOfAnalyzer, levelOfResult:Optional[str] = ""): """ Analyze a sequence with a ironic function.
Args:
sequence: the Sequence we want to analyze.
tag: the label to store the analysis result.
levelOfAnalyzer: the path of the sequence level to analyze inside of the result.
levelOfResult: the path of the sequence level to store the result.
"""
super().analyze(self.ironity,sequence, tag, levelOfAnalyzer, levelOfResult, True)
def ironity(self, arrayText): """ Function that analyzes the ironity of a list of texts.
Args:
arrayText: list that contains the texts that we want to analyze
Returns:
A list with the dictionaries. Each dictionary contains the result
of the analysis of the corresponding text.
"""
arrayResults =[]
for text in arrayText:
prediction = self.ironityClassifier(text[:self.maxEmbedding])
arrayResults.append(prediction)
return arrayResults
If you have any questions about the use of the library, you can contact with Estrella Vallecillo ([email protected]).
This work has been partially supported by the CONSENSO (PID2021-122263OB-C21), MODERATES (TED2021-130145B-I00), SocialTOX (PDC2022-133146-C21) projects financed by the Plan Nacional I+D+i of the Government of Spain, and the PRECOM (SUBV-00016) project financed by the Ministry of Consumer Affairs of the Government of Spain.