wordlevel 0.0.1a2__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.
- wordlevel/__init__.py +11 -0
- wordlevel/_version.py +24 -0
- wordlevel/api/__init__.py +0 -0
- wordlevel/api/catalog/__init__.py +0 -0
- wordlevel/api/catalog/catalog.py +265 -0
- wordlevel/api/catalog/constructors.py +287 -0
- wordlevel/api/catalog/plot.py +516 -0
- wordlevel/api/comparison/__init__.py +0 -0
- wordlevel/api/comparison/comparison.py +386 -0
- wordlevel/api/comparison/normalization.py +53 -0
- wordlevel/api/comparison/score.py +1136 -0
- wordlevel/api/lexicons/__init__.py +0 -0
- wordlevel/api/lexicons/lexicon.py +91 -0
- wordlevel/api/lexicons/types/__init__.py +0 -0
- wordlevel/api/lexicons/types/labmt.py +15 -0
- wordlevel/api/lexicons/types/socialsent.py +21 -0
- wordlevel/configs.py +374 -0
- wordlevel/constants.py +26 -0
- wordlevel/core/__init__.py +0 -0
- wordlevel/core/catalog/__init__.py +0 -0
- wordlevel/core/catalog/plot/__init__.py +0 -0
- wordlevel/core/catalog/plot/shift/__init__.py +0 -0
- wordlevel/core/catalog/plot/shift/render/__init__.py +0 -0
- wordlevel/core/catalog/plot/shift/render/bars.py +434 -0
- wordlevel/core/catalog/plot/shift/render/border.py +167 -0
- wordlevel/core/catalog/plot/shift/render/labels.py +244 -0
- wordlevel/core/catalog/plot/shift/render/scales.py +147 -0
- wordlevel/core/catalog/plot/shift/render/shift.py +259 -0
- wordlevel/core/catalog/plot/shift/render/totals.py +364 -0
- wordlevel/core/catalog/plot/shift/render/zero_line.py +43 -0
- wordlevel/core/catalog/plot/shift/transform.py +103 -0
- wordlevel/core/catalog/plot/utils.py +11 -0
- wordlevel/core/comparison/__init__.py +0 -0
- wordlevel/core/comparison/normalization.py +59 -0
- wordlevel/core/comparison/score/__init__.py +0 -0
- wordlevel/core/comparison/score/attribution.py +134 -0
- wordlevel/core/comparison/score/components.py +285 -0
- wordlevel/core/comparison/score/lexicon.py +126 -0
- wordlevel/core/comparison/score/metrics/__init__.py +0 -0
- wordlevel/core/comparison/score/metrics/entropy.py +84 -0
- wordlevel/core/comparison/score/metrics/frequency.py +6 -0
- wordlevel/core/comparison/score/metrics/jensen_shannon_divergence.py +75 -0
- wordlevel/core/comparison/score/metrics/kullback_leibler_divergence.py +101 -0
- wordlevel/core/comparison/score/metrics/rank_divergence.py +92 -0
- wordlevel/core/comparison/score/plan.py +268 -0
- wordlevel/core/lexicons/__init__.py +0 -0
- wordlevel/core/lexicons/load.py +43 -0
- wordlevel/core/utils.py +61 -0
- wordlevel/datasets/__init__.py +0 -0
- wordlevel/datasets/data/presidental_speeches.parquet +0 -0
- wordlevel/datasets/dataset.py +71 -0
- wordlevel-0.0.1a2.dist-info/METADATA +17 -0
- wordlevel-0.0.1a2.dist-info/RECORD +55 -0
- wordlevel-0.0.1a2.dist-info/WHEEL +4 -0
- wordlevel-0.0.1a2.dist-info/licenses/LICENSE +7 -0
wordlevel/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from wordlevel.api.catalog.catalog import Catalog
|
|
2
|
+
from wordlevel.api.comparison.comparison import comp
|
|
3
|
+
from wordlevel.api.lexicons.lexicon import lex
|
|
4
|
+
from wordlevel.datasets.dataset import Dataset
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from wordlevel._version import __version__
|
|
8
|
+
except ImportError:
|
|
9
|
+
__version__ = "0.0.0"
|
|
10
|
+
|
|
11
|
+
__all__ = ["__version__", "Catalog", "comp", "Dataset", "lex"]
|
wordlevel/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.0.1a2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 0, 1, 'a2')
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Collection, Mapping, Sequence
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from types import MappingProxyType
|
|
6
|
+
|
|
7
|
+
import polars as pl
|
|
8
|
+
|
|
9
|
+
from wordlevel.api.catalog.constructors import (
|
|
10
|
+
from_catalog,
|
|
11
|
+
from_polars,
|
|
12
|
+
from_dict_of_freqs,
|
|
13
|
+
from_list_of_freqs,
|
|
14
|
+
)
|
|
15
|
+
from wordlevel.api.catalog.plot import Plotter
|
|
16
|
+
from wordlevel.api.catalog.constructors import CatalogData, DictOfFreqs, ListOfFreqs
|
|
17
|
+
from wordlevel.api.comparison.comparison import Comparison, ComparisonResult
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Catalog:
|
|
21
|
+
"""
|
|
22
|
+
The workspace for corpora and making comparisons between them.
|
|
23
|
+
|
|
24
|
+
For specific construction functions, see the [construction](construct/index.md) documentation.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
data:
|
|
28
|
+
The corpora token-frequency data. Accepts a Polars DataFrame or LazyFrame,
|
|
29
|
+
[`ListOfFreqs`][wordlevel.api.catalog.constructors.ListOfFreqs],
|
|
30
|
+
[`DictOfFreqs`][wordlevel.api.catalog.constructors.DictOfFreqs], or an existing
|
|
31
|
+
`Catalog`. The class will attempt to infer the structure of the data and use the
|
|
32
|
+
appropriate constructor.
|
|
33
|
+
token_col:
|
|
34
|
+
If the `data` is a Polars dataframe, the column that contains the tokens (words,
|
|
35
|
+
subwords, n-grams, etc.). Ignored otherwise
|
|
36
|
+
corpora:
|
|
37
|
+
If the `data` is a [`ListOfFreqs`][wordlevel.api.catalog.constructors.ListOfFreqs], the
|
|
38
|
+
names of the corpora, listed in the same order as `data`. If not provided, will default
|
|
39
|
+
to `corpus1`, `corpus2`, etc. If the `data` is a Polars Dataframe or Lazyframe, the
|
|
40
|
+
names of the columns containing token frequencies, which must be provided. Ignored
|
|
41
|
+
otherwise
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
TypeError: If `data` is not one of the supported types.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
data: pl.DataFrame | pl.LazyFrame | Catalog | CatalogData | DictOfFreqs | ListOfFreqs,
|
|
50
|
+
token_col: str | None = None,
|
|
51
|
+
corpora: Sequence[str] | None = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
if isinstance(data, CatalogData):
|
|
54
|
+
init_data = data
|
|
55
|
+
elif isinstance(data, Catalog):
|
|
56
|
+
init_data = from_catalog(data)
|
|
57
|
+
elif isinstance(data, (pl.DataFrame, pl.LazyFrame)):
|
|
58
|
+
if token_col is None:
|
|
59
|
+
raise ValueError("`token_col` must be specified if providing a DataFrame")
|
|
60
|
+
if corpora is None:
|
|
61
|
+
raise ValueError("`corpora` must be specified if providing a DataFrame")
|
|
62
|
+
init_data = from_polars(
|
|
63
|
+
df=data,
|
|
64
|
+
token_col=token_col,
|
|
65
|
+
freq_cols=corpora,
|
|
66
|
+
)
|
|
67
|
+
elif isinstance(data, Mapping):
|
|
68
|
+
init_data = from_dict_of_freqs(freqs=data)
|
|
69
|
+
elif isinstance(data, Sequence):
|
|
70
|
+
init_data = from_list_of_freqs(freqs=data, corpora=corpora)
|
|
71
|
+
else:
|
|
72
|
+
raise TypeError(f"Input type not supported: {type(data)}")
|
|
73
|
+
|
|
74
|
+
self._init_from_catalog_data(init_data)
|
|
75
|
+
|
|
76
|
+
def _init_from_catalog_data(self, data: CatalogData) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Initializes the catalog given the core `CatalogData`.
|
|
79
|
+
"""
|
|
80
|
+
self._df = data.df
|
|
81
|
+
self._token_col = data.token_col
|
|
82
|
+
self._freq_cols = set(data.freq_cols)
|
|
83
|
+
self._normalization_suffix = data.normalization_suffix
|
|
84
|
+
self._comparisons = data.comparisons
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def from_catalog(cls, catalog: Catalog) -> Catalog:
|
|
88
|
+
"""
|
|
89
|
+
Creates a new catalog from an existing one.
|
|
90
|
+
|
|
91
|
+
The new catalog copies the data of the original one without mutating it.
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
```python
|
|
95
|
+
--8<-- "snippets/catalog/catalog.py:from_catalog"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
catalog: An existing `Catalog`
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
A new `Catalog` with the same internal data as the provided catalog
|
|
103
|
+
"""
|
|
104
|
+
init_data = from_catalog(catalog)
|
|
105
|
+
|
|
106
|
+
return cls(data=init_data)
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def from_polars(
|
|
110
|
+
cls,
|
|
111
|
+
df: pl.DataFrame | pl.LazyFrame,
|
|
112
|
+
token_col: str,
|
|
113
|
+
corpora: Collection[str],
|
|
114
|
+
normalize: bool = True,
|
|
115
|
+
) -> Catalog:
|
|
116
|
+
"""
|
|
117
|
+
Creates a `Catalog` of corpora from a Polars dataframe.
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
```python
|
|
121
|
+
--8<-- "snippets/catalog/catalog.py:from_polars"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
df:
|
|
126
|
+
A Polars DataFrame or LazyFrame containing corpus information. Rows are tokens and
|
|
127
|
+
columns are their frequencies in different corpora
|
|
128
|
+
token_col:
|
|
129
|
+
The column that contains the tokens (words, subwords, n-grams, etc.)
|
|
130
|
+
corpora:
|
|
131
|
+
The columns that include the token frequencies for each corpus
|
|
132
|
+
normalize:
|
|
133
|
+
Whether to normalize the corpus frequencies for each corpus so that they are in the
|
|
134
|
+
range $[0, 1]$ and sum to 1. The original corpus frequencies will be preserved
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
A `Catalog` of corpora representing the provided tokens and frequencies
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
KeyError: If `token_col` is not a column in `df`.
|
|
141
|
+
KeyError: If any column in `corpora` is not a column in `df`.
|
|
142
|
+
ValueError: If fewer than two columns are provided in `corpora`.
|
|
143
|
+
"""
|
|
144
|
+
init_data = from_polars(
|
|
145
|
+
df=df,
|
|
146
|
+
token_col=token_col,
|
|
147
|
+
freq_cols=corpora,
|
|
148
|
+
normalize=normalize,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return cls(data=init_data)
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def from_list_of_freqs(
|
|
155
|
+
cls,
|
|
156
|
+
freqs: ListOfFreqs,
|
|
157
|
+
corpora: Sequence[str] | None = None,
|
|
158
|
+
) -> Catalog:
|
|
159
|
+
"""
|
|
160
|
+
Creates a `Catalog` of corpora from a sequence of token-to-frequency mappings, each
|
|
161
|
+
representing the frequencies of tokens in a particular corpus.
|
|
162
|
+
|
|
163
|
+
Example:
|
|
164
|
+
```python
|
|
165
|
+
--8<-- "snippets/catalog/catalog.py:from_list_of_freqs"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
freqs:
|
|
170
|
+
A sequence of token-to-frequency mappings, one per corpus. Each mapping is
|
|
171
|
+
`{token: frequency}` for a single corpus
|
|
172
|
+
corpora:
|
|
173
|
+
The names of the corpora, listed in the same order as `freqs`. If not provided, will
|
|
174
|
+
default to `corpus1`, `corpus2`, etc.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
A `Catalog` of corpora representing the provided tokens and frequencies
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
ValueError: If `freqs` and `corpora` have different lengths.
|
|
181
|
+
ValueError: If fewer than two corpora are provided in `freqs`.
|
|
182
|
+
ValueError: If any corpus in `freqs` has no token frequencies.
|
|
183
|
+
"""
|
|
184
|
+
init_data = from_list_of_freqs(freqs=freqs, corpora=corpora)
|
|
185
|
+
|
|
186
|
+
return cls(data=init_data)
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def from_dict_of_freqs(cls, freqs: DictOfFreqs) -> Catalog:
|
|
190
|
+
"""
|
|
191
|
+
Creates a `Catalog` of corpora from named token-to-frequency mappings.
|
|
192
|
+
|
|
193
|
+
Example:
|
|
194
|
+
```python
|
|
195
|
+
--8<-- "snippets/catalog/catalog.py:from_dict_of_freqs"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
freqs:
|
|
200
|
+
A mapping of corpus names to token-to-frequency mappings, one per corpus. Each
|
|
201
|
+
mapping is `{corpus: {token: frequency}}`, with one mapping per corpus
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
A `Catalog` of corpora representing the provided tokens and frequencies
|
|
205
|
+
|
|
206
|
+
Raises:
|
|
207
|
+
ValueError: If fewer than two corpora are provided in `freqs`.
|
|
208
|
+
ValueError: If any corpus in `freqs` has no token frequencies.
|
|
209
|
+
"""
|
|
210
|
+
init_data = from_dict_of_freqs(freqs=freqs)
|
|
211
|
+
|
|
212
|
+
return cls(data=init_data)
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def comparisons(self) -> Mapping[str, ComparisonResult]:
|
|
216
|
+
"""
|
|
217
|
+
Comparisons made between corpora in the catalog, keyed by each comparison's alias.
|
|
218
|
+
"""
|
|
219
|
+
return MappingProxyType(self._comparisons)
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def df(self) -> pl.DataFrame:
|
|
223
|
+
"""
|
|
224
|
+
Dataframe representing the frequencies of tokens in the corpora in the catalog.
|
|
225
|
+
"""
|
|
226
|
+
return self._df.collect()
|
|
227
|
+
|
|
228
|
+
@cached_property
|
|
229
|
+
def plot(self) -> Plotter:
|
|
230
|
+
"""
|
|
231
|
+
Namespace for plotting methods. See [available plots](plot/index.md).
|
|
232
|
+
"""
|
|
233
|
+
return Plotter(self)
|
|
234
|
+
|
|
235
|
+
def with_comparisons(self, *comparisons: Comparison) -> Catalog:
|
|
236
|
+
"""
|
|
237
|
+
Returns a new catalog with comparisons added to it.
|
|
238
|
+
|
|
239
|
+
New comparisons will overwrite existing comparisons with the same name.
|
|
240
|
+
|
|
241
|
+
Example:
|
|
242
|
+
```python
|
|
243
|
+
--8<-- "snippets/catalog/catalog.py:setup"
|
|
244
|
+
|
|
245
|
+
--8<-- "snippets/catalog/catalog.py:with_comparisons"
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
comparisons: The comparisons to add to the catalog
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
The catalog with the comparisons added to it
|
|
253
|
+
"""
|
|
254
|
+
catalog = self.__class__(self)
|
|
255
|
+
|
|
256
|
+
for comparison in comparisons:
|
|
257
|
+
result = ComparisonResult.from_comparison(
|
|
258
|
+
comparison,
|
|
259
|
+
self._df,
|
|
260
|
+
token_col=self._token_col,
|
|
261
|
+
normalization_suffix=self._normalization_suffix,
|
|
262
|
+
)
|
|
263
|
+
catalog._comparisons[result.alias] = result
|
|
264
|
+
|
|
265
|
+
return catalog
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Collection
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, Iterable, Mapping, Sequence, TypeAlias
|
|
6
|
+
|
|
7
|
+
import polars as pl
|
|
8
|
+
from polars import col
|
|
9
|
+
|
|
10
|
+
from wordlevel.constants import NORMALIZATION_SUFFIX, TOKEN
|
|
11
|
+
from wordlevel.core.utils import normalize_freqs
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from wordlevel.api.catalog.catalog import Catalog
|
|
16
|
+
from wordlevel.api.comparison.comparison import ComparisonResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class CatalogData:
|
|
21
|
+
"""
|
|
22
|
+
The core data that is required to initialize and copy a `Catalog`
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
df: Polars
|
|
26
|
+
DataFrame representing the frequencies of tokens in the corpora in the catalog.
|
|
27
|
+
token_col:
|
|
28
|
+
The column in `df` that contains the tokens (words, subwords, n-grams, etc.)
|
|
29
|
+
freq_cols:
|
|
30
|
+
The columns in `df` that include the token frequencies for each corpus
|
|
31
|
+
normalization_suffix:
|
|
32
|
+
The suffix used on the `freq_cols` to indicate their normalized forms where all
|
|
33
|
+
frequencies are in the range $[0, 1]$ and sum to 1
|
|
34
|
+
comparisons:
|
|
35
|
+
Comparisons made between corpora in the catalog, keyed by each comparison's alias
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
df: pl.LazyFrame
|
|
39
|
+
token_col: str
|
|
40
|
+
freq_cols: Collection[str]
|
|
41
|
+
normalization_suffix: str
|
|
42
|
+
comparisons: dict[str, ComparisonResult]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
ListOfFreqs: TypeAlias = Sequence[Mapping[str, float]]
|
|
46
|
+
"""
|
|
47
|
+
A sequence of token-to-frequency mappings, one per corpus. Each mapping is `{word: frequency}` for
|
|
48
|
+
a single corpus
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
```python
|
|
52
|
+
list_of_freqs = [
|
|
53
|
+
{"hi": 1, "hey": 2, "hello": 3},
|
|
54
|
+
{"hi": 2, "hey": 4, "bye": 6}
|
|
55
|
+
]
|
|
56
|
+
```
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
DictOfFreqs: TypeAlias = Mapping[str, Mapping[str, float]]
|
|
61
|
+
"""
|
|
62
|
+
A mapping of corpus names to word-to-frequency mappings. The mapping is
|
|
63
|
+
`{corpus: {word: frequency}}`
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
```python
|
|
67
|
+
dict_of_freqs = {
|
|
68
|
+
"corpus1": {"hi": 1, "hey": 2, "hello": 3},
|
|
69
|
+
"corpus2": {"hi": 2, "hey": 4, "bye": 6}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def from_catalog(catalog: Catalog) -> CatalogData:
|
|
76
|
+
"""
|
|
77
|
+
Creates the data for a new catalog from an existing one.
|
|
78
|
+
|
|
79
|
+
Copies the data of the original catalog without mutating it.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
catalog: An existing `Catalog`
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The `CatalogData` needed to initialize a new catalog
|
|
86
|
+
"""
|
|
87
|
+
return CatalogData(
|
|
88
|
+
df=catalog._df.clone().lazy(),
|
|
89
|
+
token_col=catalog._token_col,
|
|
90
|
+
freq_cols=catalog._freq_cols,
|
|
91
|
+
normalization_suffix=catalog._normalization_suffix,
|
|
92
|
+
comparisons=catalog._comparisons.copy(),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def from_polars(
|
|
97
|
+
df: pl.DataFrame | pl.LazyFrame,
|
|
98
|
+
token_col: str,
|
|
99
|
+
freq_cols: Collection[str],
|
|
100
|
+
normalize: bool = True,
|
|
101
|
+
) -> CatalogData:
|
|
102
|
+
"""
|
|
103
|
+
Creates the data for a new catalog from a Polars dataframe.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
df:
|
|
107
|
+
A Polars DataFrame or LazyFrame containing corpus information. Rows are tokens and
|
|
108
|
+
columns are their frequencies in different corpora
|
|
109
|
+
token_col:
|
|
110
|
+
The column that contains the tokens (words, subwords, n-grams, etc.)
|
|
111
|
+
freq_cols:
|
|
112
|
+
The columns that include the token frequencies for each corpus
|
|
113
|
+
normalize:
|
|
114
|
+
Whether to normalize the corpus frequencies for each corpus so that they are in the
|
|
115
|
+
range $[0, 1]$ and sum to 1. The original corpus frequencies will be preserved
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
The `CatalogData` needed to initialize a new catalog
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
KeyError: If `token_col` is not a column in `df`.
|
|
122
|
+
KeyError: If any column in `freq_cols` is not a column in `df`.
|
|
123
|
+
ValueError: If fewer than two columns are provided in `freq_cols`.
|
|
124
|
+
"""
|
|
125
|
+
# Check all columns exist
|
|
126
|
+
deduped_freq_cols = set(freq_cols)
|
|
127
|
+
|
|
128
|
+
if isinstance(df, pl.DataFrame):
|
|
129
|
+
cols = set(df.columns)
|
|
130
|
+
else:
|
|
131
|
+
cols = set(df.collect_schema().names())
|
|
132
|
+
|
|
133
|
+
if token_col not in cols:
|
|
134
|
+
raise KeyError(f"Token column not in provided dataframe: {token_col}")
|
|
135
|
+
|
|
136
|
+
if len(deduped_freq_cols) < 2:
|
|
137
|
+
raise ValueError("At least two frequency columns must be provided via `freq_cols`")
|
|
138
|
+
|
|
139
|
+
for freq_col in deduped_freq_cols:
|
|
140
|
+
if freq_col not in cols:
|
|
141
|
+
raise KeyError(f"Frequency column not in provided dataframe: {freq_col}")
|
|
142
|
+
|
|
143
|
+
# Do necessary transformations
|
|
144
|
+
lf = df.clone().lazy()
|
|
145
|
+
lf = lf.select(token_col, *[col(freq_col).fill_null(0) for freq_col in deduped_freq_cols])
|
|
146
|
+
|
|
147
|
+
if normalize:
|
|
148
|
+
lf = _normalize_frequency_columns(
|
|
149
|
+
lf,
|
|
150
|
+
freq_cols=deduped_freq_cols,
|
|
151
|
+
normalization_suffix=NORMALIZATION_SUFFIX,
|
|
152
|
+
)
|
|
153
|
+
normalization_suffix = NORMALIZATION_SUFFIX
|
|
154
|
+
else:
|
|
155
|
+
normalization_suffix = ""
|
|
156
|
+
|
|
157
|
+
return CatalogData(
|
|
158
|
+
df=lf,
|
|
159
|
+
token_col=token_col,
|
|
160
|
+
freq_cols=deduped_freq_cols,
|
|
161
|
+
normalization_suffix=normalization_suffix,
|
|
162
|
+
comparisons=dict(),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def from_list_of_freqs(
|
|
167
|
+
freqs: ListOfFreqs,
|
|
168
|
+
corpora: Sequence[str] | None = None,
|
|
169
|
+
) -> CatalogData:
|
|
170
|
+
"""
|
|
171
|
+
Creates the data for a new catalog from a sequence of token-to-frequency mappings, each
|
|
172
|
+
representing the frequencies of tokens in a particular corpus.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
freqs:
|
|
176
|
+
A sequence of token-to-frequency mappings, one per corpus. Each mapping is
|
|
177
|
+
`{token: frequency}` for a single corpus
|
|
178
|
+
corpora:
|
|
179
|
+
The names of the corpora, listed in the same order as `freqs`. If not provided, will
|
|
180
|
+
default to `corpus1`, `corpus2`, etc.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
The `CatalogData` needed to initialize a new catalog
|
|
184
|
+
|
|
185
|
+
Raises:
|
|
186
|
+
ValueError: If `freqs` and `corpora` have different lengths.
|
|
187
|
+
ValueError: If fewer than two corpora are provided in `freqs`.
|
|
188
|
+
ValueError: If any corpus in `freqs` has no token frequencies.
|
|
189
|
+
"""
|
|
190
|
+
if corpora is None:
|
|
191
|
+
corpora = [f"corpus{n}" for n in range(1, len(freqs) + 1)]
|
|
192
|
+
else:
|
|
193
|
+
n_freqs = len(freqs)
|
|
194
|
+
n_corpora = len(corpora)
|
|
195
|
+
if n_freqs != n_corpora:
|
|
196
|
+
raise ValueError(
|
|
197
|
+
f"`freqs` and `corpora` must be the same length ({n_freqs} vs. {n_corpora})"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if len(freqs) < 2:
|
|
201
|
+
raise ValueError("At least two frequency sets (corpora) need to be provided")
|
|
202
|
+
|
|
203
|
+
return _from_freqs(
|
|
204
|
+
freqs=zip(corpora, freqs),
|
|
205
|
+
token=TOKEN,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def from_dict_of_freqs(
|
|
210
|
+
freqs: DictOfFreqs,
|
|
211
|
+
) -> CatalogData:
|
|
212
|
+
"""
|
|
213
|
+
Creates the data for a new catalog from named token-to-frequency mappings.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
freqs:
|
|
217
|
+
A mapping of corpus names to token-to-frequency mappings, one per corpus. Each
|
|
218
|
+
mapping is `{corpus: {token: frequency}}`, with one mapping per corpus
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
The `CatalogData` needed to initialize a new catalog
|
|
222
|
+
|
|
223
|
+
Raises:
|
|
224
|
+
ValueError: If fewer than two corpora are provided in `freqs`.
|
|
225
|
+
ValueError: If any corpus in `freqs` has no token frequencies.
|
|
226
|
+
"""
|
|
227
|
+
if len(freqs) < 2:
|
|
228
|
+
raise ValueError("At least two frequency sets (corpora) need to be provided")
|
|
229
|
+
|
|
230
|
+
return _from_freqs(
|
|
231
|
+
freqs=freqs.items(),
|
|
232
|
+
token=TOKEN,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _from_freqs(
|
|
237
|
+
freqs: Iterable[tuple[str, Mapping[str, float]]],
|
|
238
|
+
token: str = TOKEN,
|
|
239
|
+
) -> CatalogData:
|
|
240
|
+
"""
|
|
241
|
+
Creates the data for a new catalog from token-frequency data structures
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
freqs:
|
|
245
|
+
A sequence of tuples, one per corpus. Each tuple is `(corpus, {token: frequency})`
|
|
246
|
+
token:
|
|
247
|
+
The name the use for the column representing tokens in the produced catalog dataframe
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
The `CatalogData` needed to initialize a new catalog
|
|
251
|
+
"""
|
|
252
|
+
freq_cols: list[str] = []
|
|
253
|
+
lf = pl.LazyFrame(schema={token: pl.String})
|
|
254
|
+
|
|
255
|
+
for corpus, token2freq in freqs:
|
|
256
|
+
if not token2freq:
|
|
257
|
+
raise ValueError(f"There are no token frequencies for `{corpus}`")
|
|
258
|
+
|
|
259
|
+
freq_cols.append(corpus)
|
|
260
|
+
|
|
261
|
+
tokens, token_freqs = zip(*token2freq.items())
|
|
262
|
+
lf_corpus = pl.LazyFrame([tokens, token_freqs], schema=[token, corpus])
|
|
263
|
+
lf = lf.join(lf_corpus, on=token, how="full", coalesce=True)
|
|
264
|
+
|
|
265
|
+
return from_polars(
|
|
266
|
+
lf,
|
|
267
|
+
token_col=token,
|
|
268
|
+
freq_cols=freq_cols,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _normalize_frequency_columns(
|
|
273
|
+
df: pl.LazyFrame,
|
|
274
|
+
freq_cols: Collection[str],
|
|
275
|
+
normalization_suffix: str = NORMALIZATION_SUFFIX,
|
|
276
|
+
) -> pl.LazyFrame:
|
|
277
|
+
"""
|
|
278
|
+
Normalizes columns in a dataframe so all values are in the range $[0, 1]$ and sum to 1. The
|
|
279
|
+
normalized columns are named using a `normalization_suffix`, appended to the original column
|
|
280
|
+
names.
|
|
281
|
+
"""
|
|
282
|
+
normed_cols = [
|
|
283
|
+
normalize_freqs(col(freq_col)).alias(f"{freq_col}{normalization_suffix}")
|
|
284
|
+
for freq_col in freq_cols
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
return df.with_columns(*normed_cols)
|