dstklib 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dstk/__init__.py +12 -0
- dstk/collocations.py +121 -0
- dstk/count_models.py +112 -0
- dstk/geometric_distance.py +107 -0
- dstk/lib_types/__init__.py +9 -0
- dstk/lib_types/dstk_types.py +26 -0
- dstk/lib_types/fasttext_types.py +1 -0
- dstk/lib_types/gensim_types.py +1 -0
- dstk/lib_types/matplotlib_types.py +4 -0
- dstk/lib_types/nltk_types.py +1 -0
- dstk/lib_types/numpy_types.py +2 -0
- dstk/lib_types/pandas_types.py +1 -0
- dstk/lib_types/sklearn_types.py +1 -0
- dstk/lib_types/spacy_types.py +6 -0
- dstk/matrix_base.py +113 -0
- dstk/pipeline_tools.py +27 -0
- dstk/pipelines.py +114 -0
- dstk/plot_embeddings.py +240 -0
- dstk/predict_models.py +189 -0
- dstk/text_matrix_builder.py +87 -0
- dstk/text_processor.py +450 -0
- dstk/weight_matrix.py +71 -0
- dstk/workflow_tools.py +257 -0
- dstklib-1.0.0.dist-info/LICENSE +674 -0
- dstklib-1.0.0.dist-info/METADATA +360 -0
- dstklib-1.0.0.dist-info/RECORD +28 -0
- dstklib-1.0.0.dist-info/WHEEL +5 -0
- dstklib-1.0.0.dist-info/top_level.txt +1 -0
dstk/__init__.py
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
from .count_models import *
|
2
|
+
from .geometric_distance import *
|
3
|
+
from .plot_embeddings import *
|
4
|
+
from .predict_models import *
|
5
|
+
from .collocations import *
|
6
|
+
from .text_matrix_builder import *
|
7
|
+
from .text_processor import *
|
8
|
+
from .weight_matrix import *
|
9
|
+
from .matrix_base import *
|
10
|
+
from .workflow_tools import *
|
11
|
+
from .pipeline_tools import *
|
12
|
+
from .pipelines import *
|
dstk/collocations.py
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
from collections import Counter
|
2
|
+
from dataclasses import dataclass
|
3
|
+
import matplotlib.pyplot as plt
|
4
|
+
from .workflow_tools import workflow, requires, WorkflowManager
|
5
|
+
|
6
|
+
from .lib_types import BarContainer, Collocate
|
7
|
+
|
8
|
+
STAGES = [
|
9
|
+
"start", # Before any processing
|
10
|
+
"collocates", # Manipulation of collocates
|
11
|
+
"count", # Operations dealing with the counts of the words appearing around the target word
|
12
|
+
"end" # End of the workflow. After this stage the user must necessarily call result to continue with the analysis
|
13
|
+
]
|
14
|
+
|
15
|
+
@dataclass
|
16
|
+
class Ngrams:
|
17
|
+
collocates: list[tuple[str, ...]]
|
18
|
+
bigrams: list[Collocate]
|
19
|
+
|
20
|
+
class Collocations(WorkflowManager):
|
21
|
+
"""
|
22
|
+
Extracts n-grams for a target word by a context window (both directed or undirected). Counts and plots the terms that co-occur with the target
|
23
|
+
|
24
|
+
:param tokens: A list of tokenized words (e.g. from a text or corpus) in which to search for the target word and its collocates.
|
25
|
+
|
26
|
+
"""
|
27
|
+
|
28
|
+
_start: list[str]
|
29
|
+
_end: BarContainer
|
30
|
+
|
31
|
+
def __init__(self, tokens: list[str] | None = None):
|
32
|
+
"""
|
33
|
+
Initializes TargetCollocations with given attributes.
|
34
|
+
"""
|
35
|
+
|
36
|
+
super().__init__()
|
37
|
+
|
38
|
+
# Stages
|
39
|
+
|
40
|
+
self._collocates: Ngrams
|
41
|
+
self._count: Counter[str]
|
42
|
+
|
43
|
+
self._set_workflow(input_arg=tokens)
|
44
|
+
|
45
|
+
@requires(stages=["start"])
|
46
|
+
@workflow(input_arg="tokens", input_process="_start", output_process="_collocates", next_stage="collocates")
|
47
|
+
def extract_ngrams(self, *, tokens: list[str], target_word: str, window_size: tuple[int, int], directed: bool = False) -> Ngrams:
|
48
|
+
"""
|
49
|
+
Extracts both the context words of the target collocation, returned as tuples whose lenght corresponds to the specified window_size, and the collocations of the target word, in either directed or undirected manner.
|
50
|
+
|
51
|
+
:param tokens: A list of tokenized words (e.g. from a text or corpus) in which to search for the target word and its collocates.
|
52
|
+
:param target_word: Target word whose collocation are to be identified.
|
53
|
+
:param window_size: Context window represented as a tuple (left, right) of the number of words to be included to left and right of the target word.
|
54
|
+
:param directed: If True, the position of collocates relative to the target word is considered (i.e., direction matters); if False, direction is ignored. Defaults to False.
|
55
|
+
"""
|
56
|
+
|
57
|
+
collocates: list[tuple[str, ...]] = []
|
58
|
+
bigrams: list[Collocate] = []
|
59
|
+
|
60
|
+
for index, word in enumerate(tokens):
|
61
|
+
if word == target_word:
|
62
|
+
start: int = max(0, index - window_size[0])
|
63
|
+
end: int = min(len(tokens), index + window_size[1] + 1)
|
64
|
+
|
65
|
+
left_context: list[str] = tokens[start:index]
|
66
|
+
right_context: list[str] = tokens[index + 1:end]
|
67
|
+
|
68
|
+
context: list[str] = left_context + right_context
|
69
|
+
|
70
|
+
collocates.append(tuple(context))
|
71
|
+
|
72
|
+
if directed == True:
|
73
|
+
bigrams.extend([(word, ("L", target_word)) for word in left_context] + [(word, ("R", target_word)) for word in right_context])
|
74
|
+
else:
|
75
|
+
bigrams.extend([(word, target_word) for word in context])
|
76
|
+
|
77
|
+
return Ngrams(collocates, bigrams)
|
78
|
+
|
79
|
+
@requires(stages=["collocates"])
|
80
|
+
@workflow(input_arg="collocates", input_attrs={"collocates": "collocates"}, input_process="_collocates", output_process="_count", next_stage="count")
|
81
|
+
def count_collocates(self, *, collocates: list[tuple[str, ...]]) -> Counter[str]:
|
82
|
+
"""
|
83
|
+
Counts the collocates of the target word.
|
84
|
+
|
85
|
+
:param collocates: A list of collocates to count.
|
86
|
+
"""
|
87
|
+
|
88
|
+
all_words: list[str] = [word for collocation in collocates for word in collocation]
|
89
|
+
word_counts: Counter[str] = Counter(all_words)
|
90
|
+
|
91
|
+
return word_counts
|
92
|
+
|
93
|
+
@requires(stages=["count"])
|
94
|
+
@workflow(input_arg="word_counts", input_process="_count", output_process="_end", next_stage="end")
|
95
|
+
def plot(self, *, word_counts: Counter[str], size: int = 10, show: bool = True, path: str | None = None) -> BarContainer:
|
96
|
+
"""
|
97
|
+
Plots the count of the collocates.
|
98
|
+
|
99
|
+
:param word_counts: A Counter object with the counts of each word. Defaults to None.
|
100
|
+
:param size: The number of the most common collocates to plot. Defaults to 10.
|
101
|
+
:param show: If True, shows the plot. Defaults to True.
|
102
|
+
:param path: If provided, saves the plot in the specified path. Defaults to None.
|
103
|
+
"""
|
104
|
+
|
105
|
+
counts: list[tuple[str, int]] = word_counts.most_common(size)
|
106
|
+
|
107
|
+
words: tuple[str, ...]
|
108
|
+
values: tuple[int, ...]
|
109
|
+
words, values = zip(*counts)
|
110
|
+
|
111
|
+
fig: BarContainer = plt.bar(words, values)
|
112
|
+
plt.xlabel("Palabras")
|
113
|
+
plt.ylabel("Cuentas")
|
114
|
+
|
115
|
+
if path:
|
116
|
+
plt.savefig(path)
|
117
|
+
|
118
|
+
if show:
|
119
|
+
plt.show()
|
120
|
+
|
121
|
+
return fig
|
dstk/count_models.py
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
from sklearn.preprocessing import StandardScaler
|
2
|
+
from sklearn.decomposition import PCA, TruncatedSVD
|
3
|
+
from .workflow_tools import requires, workflow, WorkflowManager
|
4
|
+
from .matrix_base import MatrixRepresentation, accept_matrix_representation, matrix_to_dataframe
|
5
|
+
|
6
|
+
from .lib_types import ndarray, DataFrame
|
7
|
+
|
8
|
+
STAGES = [
|
9
|
+
"start", # Before any transformation to the Co-Matrixis applied
|
10
|
+
"embeddings", # Result of the embeddings
|
11
|
+
"end" # Embeddings transformed to dataframe
|
12
|
+
]
|
13
|
+
|
14
|
+
class CountModels(WorkflowManager):
|
15
|
+
"""
|
16
|
+
Generates word embeddings using dimensionality reduction techniques on a co-occurrence matrix, such as SVD and PCA.
|
17
|
+
|
18
|
+
:param co_ocurrence_matrix: A Co-ocurrence matrix from which embeddings will be generated.
|
19
|
+
"""
|
20
|
+
|
21
|
+
_start: ndarray | DataFrame
|
22
|
+
_end: DataFrame
|
23
|
+
|
24
|
+
def __init__(self, co_ocurrence_matrix: DataFrame | None = None):
|
25
|
+
"""
|
26
|
+
Initializes CountModels with given attributes.
|
27
|
+
"""
|
28
|
+
super().__init__()
|
29
|
+
|
30
|
+
self._embeddings: MatrixRepresentation
|
31
|
+
|
32
|
+
self._set_workflow(input_arg=co_ocurrence_matrix)
|
33
|
+
|
34
|
+
@requires(stages=["start"])
|
35
|
+
@workflow(input_arg="matrix", input_process="_start", output_process="_embeddings", next_stage="embeddings")
|
36
|
+
@accept_matrix_representation()
|
37
|
+
def scale_matrix(self, *, matrix: ndarray | DataFrame, **kwargs) -> MatrixRepresentation:
|
38
|
+
"""
|
39
|
+
Scales the input matrix to have zero mean and unit variance for each feature.
|
40
|
+
|
41
|
+
This method applies standardization using scikit-learn's StandardScaler, which transforms the data such that each colum (feature) has a mean of 0 and a standard deviation of 1.
|
42
|
+
|
43
|
+
:param matrix: The input data to scale.
|
44
|
+
:param kwargs: Additional keyword arguments to pass to sklearn's StandardScaler. For more information check: https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html
|
45
|
+
|
46
|
+
This method supports different matrix forms due to decorator-based preprocessing:
|
47
|
+
- matrix: ndarray or Dataframe
|
48
|
+
- matrix representation: MatrixRepresentation
|
49
|
+
"""
|
50
|
+
|
51
|
+
scaler: StandardScaler = StandardScaler(**kwargs)
|
52
|
+
scaled_matrix: ndarray = scaler.fit_transform(matrix)
|
53
|
+
|
54
|
+
return MatrixRepresentation(scaled_matrix, matrix.index if isinstance(matrix, DataFrame) else None, matrix.columns if isinstance(matrix, DataFrame) else None)
|
55
|
+
|
56
|
+
@requires(stages=["embeddings"])
|
57
|
+
@workflow(input_arg="matrix", input_process="_embeddings", output_process="_embeddings")
|
58
|
+
@accept_matrix_representation(override=("columns", None))
|
59
|
+
def svd_embeddings(self, *, matrix: ndarray | DataFrame, **kwargs) -> MatrixRepresentation:
|
60
|
+
"""
|
61
|
+
Generates word embeddings using truncated Single Value Descomposition (SVD).
|
62
|
+
|
63
|
+
:param matrix: A Co-occurrence matrix from which embeddings will be generated.
|
64
|
+
:param kwargs: Additional keyword arguments to pass to sklearn's TruncatedSVD.
|
65
|
+
Common options include:
|
66
|
+
- n_components: Specifies the number of dimensions to reduce the Co-ocurrence matrix to.
|
67
|
+
For more information check: https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.TruncatedSVD.html
|
68
|
+
|
69
|
+
This method supports different matrix forms due to decorator-based preprocessing:
|
70
|
+
- matrix: ndarray or Dataframe
|
71
|
+
- matrix representation: MatrixRepresentation
|
72
|
+
"""
|
73
|
+
|
74
|
+
svd: TruncatedSVD = TruncatedSVD(**kwargs)
|
75
|
+
embeddings: ndarray = svd.fit_transform(matrix)
|
76
|
+
|
77
|
+
return MatrixRepresentation(embeddings, matrix.index if isinstance(matrix, DataFrame) else None, None)
|
78
|
+
|
79
|
+
@requires(stages=["embeddings"])
|
80
|
+
@workflow(input_arg="matrix", input_process="_embeddings", output_process="_embeddings")
|
81
|
+
@accept_matrix_representation(override=("columns", None))
|
82
|
+
def pca_embeddings(self, *, matrix: ndarray | DataFrame, **kwargs) -> MatrixRepresentation:
|
83
|
+
"""
|
84
|
+
Generates word embeddings using Principal Component Analysis (PCA).
|
85
|
+
|
86
|
+
:param matrix: A Co-occurrence matrix from which embeddings will be generated.
|
87
|
+
:param kwargs: Additional keyword arguments to pass to sklearn's PCA.
|
88
|
+
Common options include:
|
89
|
+
- n_components: If an integer, specifies the number of dimensions to reduce the Co-ocurrence matrix to. If a float, the amount of variance to preserve during PCA.
|
90
|
+
For more information check: https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html
|
91
|
+
|
92
|
+
This method supports different matrix forms due to decorator-based preprocessing:
|
93
|
+
- matrix: ndarray or Dataframe
|
94
|
+
- matrix representation: MatrixRepresentation
|
95
|
+
"""
|
96
|
+
|
97
|
+
pca: PCA = PCA(**kwargs)
|
98
|
+
embeddings: ndarray = pca.fit_transform(matrix)
|
99
|
+
|
100
|
+
return MatrixRepresentation(embeddings, matrix.index if isinstance(matrix, DataFrame) else None, None)
|
101
|
+
|
102
|
+
@requires(stages=["embeddings"])
|
103
|
+
@workflow(input_arg="matrix", input_process="_embeddings", output_process="_end", next_stage="end")
|
104
|
+
def to_dataframe(self, *, matrix: MatrixRepresentation, **kwargs) -> DataFrame:
|
105
|
+
"""
|
106
|
+
Creates a dataframe from a matrix representation.
|
107
|
+
|
108
|
+
:param matrix: An matrix represenation from which to create a dataframe.
|
109
|
+
:param kwargs: Additional keyword arguments to pass to sklearn's pandas' DataFrame.
|
110
|
+
"""
|
111
|
+
|
112
|
+
return matrix_to_dataframe(matrix=matrix, **kwargs)
|
@@ -0,0 +1,107 @@
|
|
1
|
+
from sklearn.neighbors import NearestNeighbors
|
2
|
+
import pandas as pd
|
3
|
+
import numpy as np
|
4
|
+
from sklearn.metrics.pairwise import cosine_similarity
|
5
|
+
from .workflow_tools import requires, workflow, WorkflowManager
|
6
|
+
|
7
|
+
from .lib_types import ndarray, Series, DataFrame
|
8
|
+
|
9
|
+
STAGES = [
|
10
|
+
"start", # The embeddings array
|
11
|
+
"end" # After a distance has been applied
|
12
|
+
]
|
13
|
+
|
14
|
+
class GeometricDistance(WorkflowManager):
|
15
|
+
"""
|
16
|
+
Provides a set of methods to calculate the distance between the embeddings of words, such as Euclidean distance, Manhattan distance, Cosine similarity, Nearest neighbors, etc.
|
17
|
+
"""
|
18
|
+
|
19
|
+
def __init__(self, embeddings: DataFrame | None = None):
|
20
|
+
"""
|
21
|
+
Initializes GeometricDistance with given attributes.
|
22
|
+
|
23
|
+
:param embeddings: A matrix of word embeddings.
|
24
|
+
:param vocab: Sequence of words representing the vocabulary aligned with the embeddings.
|
25
|
+
"""
|
26
|
+
|
27
|
+
super().__init__()
|
28
|
+
|
29
|
+
self._set_workflow(input_arg=embeddings)
|
30
|
+
|
31
|
+
@requires(stages=["start", "end"], multiple_calls=True)
|
32
|
+
@workflow(input_arg="embeddings", input_process="_start", output_process="_end", next_stage="end") # It would be interesting if you could select a set of distances as then result return all of them for comparision. Or you could call different words and return an array wit the result of all of them.
|
33
|
+
def euclidean_distance(self, *, embeddings: DataFrame, first_word: str, second_word: str) -> float:
|
34
|
+
"""
|
35
|
+
Computes the Euclidean distance between the embeddings of two words.
|
36
|
+
|
37
|
+
:param embeddings: A dataframe containing the word embeddings.
|
38
|
+
:param first_word: The first word in the pair.
|
39
|
+
:param second_word: The second word in the pair.
|
40
|
+
"""
|
41
|
+
|
42
|
+
first_word_vector: Series = embeddings.loc[first_word]
|
43
|
+
second_word_vector: Series = embeddings.loc[second_word]
|
44
|
+
|
45
|
+
return float(np.linalg.norm(first_word_vector - second_word_vector))
|
46
|
+
|
47
|
+
@requires(stages=["start", "end"], multiple_calls=True)
|
48
|
+
@workflow(input_arg="embeddings", input_process="_start", output_process="_end", next_stage="end")
|
49
|
+
def manhattan_distance(self, *, embeddings: DataFrame, first_word: str, second_word: str) -> float:
|
50
|
+
"""
|
51
|
+
Computes the Manhattan distance between the embeddings of two words.
|
52
|
+
|
53
|
+
:param embeddings: A dataframe containing the word embeddings.
|
54
|
+
:param first_word: The first word in the pair.
|
55
|
+
:param second_word: The second word in the pair.
|
56
|
+
"""
|
57
|
+
|
58
|
+
first_word_vector: Series = embeddings.loc[first_word]
|
59
|
+
second_word_vector: Series = embeddings.loc[second_word]
|
60
|
+
|
61
|
+
return np.sum(np.abs(first_word_vector - second_word_vector))
|
62
|
+
|
63
|
+
@requires(stages=["start", "end"], multiple_calls=True)
|
64
|
+
@workflow(input_arg="embeddings", input_process="_start", output_process="_end", next_stage="end")
|
65
|
+
def cos_similarity(self, *, embeddings: DataFrame, first_word: str, second_word: str) -> float:
|
66
|
+
"""
|
67
|
+
Computes the cosine similarity between the embeddings of two words.
|
68
|
+
|
69
|
+
:param embeddings: A dataframe containing the word embeddings.
|
70
|
+
:param first_word: The first word in the pair.
|
71
|
+
:param second_word: The second word in the pair.
|
72
|
+
"""
|
73
|
+
|
74
|
+
first_word_vector: ndarray = np.array(embeddings.loc[first_word]).reshape(1, -1)
|
75
|
+
second_word_vector: ndarray = np.array(embeddings.loc[second_word]).reshape(1, -1)
|
76
|
+
|
77
|
+
cos_sim: ndarray = cosine_similarity(first_word_vector, second_word_vector)
|
78
|
+
|
79
|
+
return cos_sim[0][0]
|
80
|
+
|
81
|
+
@requires(stages=["start", "end"], multiple_calls=True)
|
82
|
+
@workflow(input_arg="embeddings", input_process="_start", output_process="_end", next_stage="end")
|
83
|
+
def nearest_neighbors(self, *, embeddings: DataFrame, word: str, metric: str, n_words: int = 5, **kwargs) -> list[tuple[str, float]]:
|
84
|
+
"""
|
85
|
+
Returns the top N most semantically similar words to a given target word, based on the specified distance or similarity metric.
|
86
|
+
|
87
|
+
:param embeddings: A dataframe containing the word embeddings.
|
88
|
+
:param word: The target word to find neighbors for.
|
89
|
+
:param metric: The distance or similarity metric to use (e.g., 'cosine', 'euclidean').
|
90
|
+
:param n_words: Number of nearest neighbors to return. Defaults to 5.
|
91
|
+
:param kwargs: Additional keyword arguments to pass to sklearn's NearestNeighbors. For more information check: https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.NearestNeighbors.html
|
92
|
+
"""
|
93
|
+
|
94
|
+
neighbors: NearestNeighbors = NearestNeighbors(n_neighbors=n_words, algorithm="auto", metric=metric, **kwargs)
|
95
|
+
neighbors.fit(embeddings.to_numpy())
|
96
|
+
|
97
|
+
word_vector: Series = embeddings.loc[word]
|
98
|
+
|
99
|
+
distances: ndarray
|
100
|
+
indices: ndarray
|
101
|
+
distances, indices = neighbors.kneighbors([word_vector], n_neighbors=n_words + 1)
|
102
|
+
|
103
|
+
neighbor_tuples = zip(indices[0], distances[0])
|
104
|
+
|
105
|
+
results: list[tuple[str, float]] = [(embeddings.index[index], 1 - distance) for index, distance in neighbor_tuples if embeddings.index[index] != word]
|
106
|
+
|
107
|
+
return results
|
@@ -0,0 +1,9 @@
|
|
1
|
+
from .spacy_types import *
|
2
|
+
from .sklearn_types import *
|
3
|
+
from .numpy_types import *
|
4
|
+
from .pandas_types import *
|
5
|
+
from .matplotlib_types import *
|
6
|
+
from .fasttext_types import *
|
7
|
+
from .gensim_types import *
|
8
|
+
from .nltk_types import *
|
9
|
+
from .dstk_types import *
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from typing import TypeAlias, Callable, TypeVar, Any
|
2
|
+
from .spacy_types import Doc, Span, Token
|
3
|
+
from .sklearn_types import csc_matrix, csr_matrix
|
4
|
+
from .numpy_types import ndarray, NDArray, str_
|
5
|
+
from .pandas_types import Index
|
6
|
+
|
7
|
+
# TextProcessor
|
8
|
+
TokenIterator: TypeAlias = Doc | Span | list[Token]
|
9
|
+
POSIterator: TypeAlias = list[tuple[Token, str]]
|
10
|
+
SentenceIterator: TypeAlias = list[Span] | list[list[Token]] | list[POSIterator]
|
11
|
+
TextIterator: TypeAlias = list[str] | list[tuple[str, str]] | list[list[tuple[str, str]]]
|
12
|
+
|
13
|
+
Sentence: TypeAlias = Span | list[Token] | POSIterator | list[tuple[str, str]]
|
14
|
+
Sentences: TypeAlias = list[Sentence]
|
15
|
+
POSTags: TypeAlias = list[tuple[Token | str, str]]
|
16
|
+
Function = TypeVar("Function", bound=Callable[..., object])
|
17
|
+
|
18
|
+
# TargetCollocations
|
19
|
+
Collocate = tuple[str, tuple[str, str]] | tuple[str, str]
|
20
|
+
|
21
|
+
# Matrices
|
22
|
+
Matrix: TypeAlias = csr_matrix | csc_matrix | ndarray
|
23
|
+
Labels: TypeAlias = NDArray[str_] | Index | list[str] | None
|
24
|
+
|
25
|
+
#Workflow
|
26
|
+
MethodSpec: TypeAlias = dict[str, dict[str, Any]]
|
@@ -0,0 +1 @@
|
|
1
|
+
from fasttext.FastText import _FastText as FastText
|
@@ -0,0 +1 @@
|
|
1
|
+
from gensim.models import Word2Vec
|
@@ -0,0 +1 @@
|
|
1
|
+
from nltk import Text
|
@@ -0,0 +1 @@
|
|
1
|
+
from pandas import DataFrame, Series, Index
|
@@ -0,0 +1 @@
|
|
1
|
+
from scipy.sparse import csr_matrix, csc_matrix
|
dstk/matrix_base.py
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
from dataclasses import dataclass, field
|
2
|
+
import pandas as pd
|
3
|
+
from .lib_types import Matrix, Labels, ndarray, DataFrame, csr_matrix, csc_matrix
|
4
|
+
from .workflow_tools import accepts_generic
|
5
|
+
|
6
|
+
from typing import Any, cast, Callable
|
7
|
+
|
8
|
+
@dataclass
|
9
|
+
class MatrixRepresentation:
|
10
|
+
"""
|
11
|
+
Container for a matrix and its associated row and column labels, with optional metadata.
|
12
|
+
|
13
|
+
:param matrix: The core matrix data, typically a NumPy array.
|
14
|
+
:param rows: Optional row labels.
|
15
|
+
:param columns: Optional column labels.
|
16
|
+
:param meta: Optional dictionary to store additional metadata.
|
17
|
+
"""
|
18
|
+
|
19
|
+
matrix: Matrix
|
20
|
+
rows: Labels = None
|
21
|
+
columns: Labels = None
|
22
|
+
meta: dict[str, Any] = field(default_factory=dict)
|
23
|
+
|
24
|
+
def accept_matrix_representation(accepts: bool = True, custom_error_message: str = "", intercept: bool = True, meta: str | None = None, override: tuple[str, Any] | None = None) -> Callable:
|
25
|
+
"""
|
26
|
+
Decorator that allows a method to accept a MatrixRepresentation object as input.
|
27
|
+
|
28
|
+
Extracts the underlying matrix for processing and optionally reattaches metadata, labels, or captures non-matrix outputs into metadata.
|
29
|
+
|
30
|
+
:param accepts: Whether to accept MatrixRepresentation inputs.
|
31
|
+
:param custom_error_message: Optional error message if input type is not accepted.
|
32
|
+
:param intercept: Whether to intercept the input and repackage the output.
|
33
|
+
:param meta: Optional metadata key to store non-matrix outputs in the result.
|
34
|
+
|
35
|
+
:return: A decorated method that supports MatrixRepresentation as input.
|
36
|
+
"""
|
37
|
+
|
38
|
+
def is_matrix_representation(matrix: Any) -> bool:
|
39
|
+
"""
|
40
|
+
If matrix is an instance of MatrixRepresentation, returns True. Else, returns False.
|
41
|
+
|
42
|
+
:param matrix: A matrix to check its instance.
|
43
|
+
"""
|
44
|
+
|
45
|
+
return True if isinstance(matrix, MatrixRepresentation) else False
|
46
|
+
|
47
|
+
def intercept_matrix(self, input_value: MatrixRepresentation, method: Callable, *args, **kwargs) -> MatrixRepresentation:
|
48
|
+
result: MatrixRepresentation | Any = method(self, *args, matrix=input_value.matrix, **kwargs)
|
49
|
+
|
50
|
+
matrix: MatrixRepresentation
|
51
|
+
|
52
|
+
if isinstance(result, MatrixRepresentation):
|
53
|
+
matrix = result
|
54
|
+
|
55
|
+
matrix.rows = input_value.rows
|
56
|
+
matrix.columns = input_value.columns
|
57
|
+
|
58
|
+
if override:
|
59
|
+
attr, value = override
|
60
|
+
setattr(matrix, attr, value)
|
61
|
+
else:
|
62
|
+
if isinstance(result, ndarray):
|
63
|
+
matrix = MatrixRepresentation(result, input_value.rows, input_value.columns)
|
64
|
+
elif not isinstance(result, ndarray) and meta:
|
65
|
+
matrix = MatrixRepresentation(input_value.matrix, input_value.rows, input_value.columns)
|
66
|
+
matrix.meta[meta] = result
|
67
|
+
|
68
|
+
return matrix
|
69
|
+
|
70
|
+
return accepts_generic(
|
71
|
+
type_checker=is_matrix_representation,
|
72
|
+
input_arg="matrix",
|
73
|
+
accepts=accepts,
|
74
|
+
intercept=intercept,
|
75
|
+
interceptor=lambda self, input_value, method, *args, **kwargs: intercept_matrix(self, input_value, method, *args, **kwargs),
|
76
|
+
input_type=MatrixRepresentation,
|
77
|
+
custom_error_message=custom_error_message
|
78
|
+
)
|
79
|
+
|
80
|
+
def matrix_to_dataframe(matrix: MatrixRepresentation, **kwargs):
|
81
|
+
"""
|
82
|
+
Converts a MatrixRepresentation to a pandas DataFrame.
|
83
|
+
|
84
|
+
:param matrix: A MatrixRepresentation instance.
|
85
|
+
:param kwargs: Additional keyword arguments to pass to sklearn's pandas' DataFrame.
|
86
|
+
|
87
|
+
:return: A pandas DataFrame with corresponding data and labels.
|
88
|
+
"""
|
89
|
+
|
90
|
+
if isinstance(matrix.matrix, csr_matrix) or isinstance(matrix.matrix, csc_matrix):
|
91
|
+
matrix.matrix = matrix.matrix.toarray()
|
92
|
+
|
93
|
+
return pd.DataFrame(
|
94
|
+
matrix.matrix,
|
95
|
+
index=matrix.rows,
|
96
|
+
columns=matrix.columns,
|
97
|
+
**kwargs
|
98
|
+
)
|
99
|
+
|
100
|
+
def dataframe_to_matrix(dataframe: DataFrame):
|
101
|
+
"""
|
102
|
+
Converts a pandas DataFrame to a MatrixRepresentation.
|
103
|
+
|
104
|
+
:param dataframe: A pandas DataFrame.
|
105
|
+
|
106
|
+
:return: A MatrixRepresentation with matrix data and index/column labels.
|
107
|
+
"""
|
108
|
+
|
109
|
+
return MatrixRepresentation(
|
110
|
+
matrix=dataframe.to_numpy(),
|
111
|
+
rows=list(dataframe.index),
|
112
|
+
columns=list(dataframe.columns)
|
113
|
+
)
|
dstk/pipeline_tools.py
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
from .workflow_tools import WorkflowBuilder, WorkflowManager
|
2
|
+
|
3
|
+
from typing import Any, Callable
|
4
|
+
|
5
|
+
class PipelineBuilder:
|
6
|
+
"""
|
7
|
+
Automates the execution of a sequence of workflows on a WorkflowBuilder or Callable subclass.
|
8
|
+
|
9
|
+
:param workflows: A subclass of WorkflowBuilder representing the workflow to execute or a function to hook in the pipeline.
|
10
|
+
"""
|
11
|
+
|
12
|
+
def __init__(self, workflows: list[WorkflowBuilder | Callable]):
|
13
|
+
"""
|
14
|
+
Initializes WorkflowBuilder with given attributes.
|
15
|
+
"""
|
16
|
+
|
17
|
+
self.workflows: list[WorkflowBuilder | Callable] = workflows
|
18
|
+
|
19
|
+
def __call__(self, **kwargs) -> Any:
|
20
|
+
workflows: list[WorkflowBuilder | Callable] = self.workflows
|
21
|
+
entry_workflow: WorkflowBuilder | Callable = workflows.pop(0)
|
22
|
+
result: Any = entry_workflow(**kwargs)
|
23
|
+
|
24
|
+
for workflow in workflows:
|
25
|
+
result = workflow(result)
|
26
|
+
|
27
|
+
return result
|