Statistics¶
The stats stage: assembling the analysis table, testing group differences, correlating features with outcomes, and writing the plain-English report. Principal component analysis (with varimax rotation) lives here too, as does the scorer that applies one or more saved models to a new dataset, and the figures the reports draw (word clouds, and the charts training reports use).
taters.stats.assemble ¶
Build the analysis table: every chosen feature table, one row per text, joined with the metadata columns the statistics need.
The feature extractors each write their own CSV keyed by text_id (or by a
multi-column key for media runs). Statistics want ONE wide table -- features
side by side, with the grouping and outcome columns from the user's
spreadsheet -- and nothing in Taters joined columns before this module: the
gathers stack rows, they never widen.
The join is INNER, with accounting. A left join would seed missing values into every downstream analysis for rows some extractor skipped (a zero-token document missing from a term matrix, say); keeping only rows every table has is the honest denominator, and honesty about the cost lives in the manifest: rows in, rows surviving each join, rows each filter removed. Those counts are also written into the report, because "we analyzed 99,881 of your 100,000 rows, and here is where the rest went" is the first thing a reviewer asks.
Filters are the "select * where" of the stage: [column, op, value]
triples a row must ALL satisfy to stay. A blank (missing) cell fails every
filter -- a row whose word count is unknown does not sneak past
word_count >= 25.
assemble_analysis_table ¶
assemble_analysis_table(
*,
feature_csvs,
filter_csvs=(),
metadata_csv=None,
metadata_cols=(),
key_cols=("text_id",),
text_cols=(),
split_col=None,
filters=None,
bookkeeping="aside",
bookkeeping_cols=(),
out_dir="stats_results",
out_csv=None,
keep_table=True,
overwrite_existing=False,
on_progress=None,
verbose=True,
encoding="utf-8-sig"
)
Join feature tables and metadata into one wide analysis table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
feature_csvs
|
Sequence[PathLike]
|
The feature tables to include, each with exactly one row per key.
Non-numeric columns other than the key are dropped (with a note):
the analyses consume numbers, and a stray |
required |
text_cols
|
Sequence[str]
|
Columns to carry through even though they are not numbers. Needed
because a saved model fitted with a categorical control knows a
predictor called |
()
|
filter_csvs
|
Sequence[PathLike]
|
Tables joined in so their columns can be filtered on, but never
offered to the analyses as features. A word count computed because
someone asked to drop texts under 25 words is a gate, not a
predictor -- and it turned up as one in a ridge regression, which is
the kind of result that looks fine and is not (a real report). Their
columns land in the analysis table and in the sidecar's |
()
|
metadata_csv
|
Optional[PathLike]
|
Optional table holding the grouping/outcome columns (typically the
run's |
None
|
metadata_cols
|
Sequence[str]
|
Which metadata columns to carry. Empty means all of them (minus any
column literally named |
()
|
key_cols
|
Sequence[str]
|
The join key. |
('text_id',)
|
split_col
|
Optional[str]
|
A second dimension the feature tables are keyed on but the
metadata is not -- |
None
|
filters
|
Optional[Sequence[Sequence]]
|
Row filters, |
None
|
bookkeeping
|
Literal['aside', 'features']
|
What to do with the count columns the steps write beside their
measures -- a matrix's |
'aside'
|
bookkeeping_cols
|
Sequence[str]
|
Column names to keep aside as well, for a table with no record to declare them (one made by hand, or by an older build). |
()
|
out_dir
|
PathLike
|
Where the table goes; |
'stats_results'
|
out_csv
|
PathLike
|
Where the table goes; |
'stats_results'
|
keep_table
|
bool
|
Whether the merged table survives the run. It is written either way -- every analysis reads it from disk -- but with False the report step deletes it at the end. Keep it when you want the dataset the statistics were computed on, which is usually: it is the file to hand to a colleague, or to open in R. Turn it off when it is enormous and derivable, since a wide feature set makes it the largest thing the run produces. |
True
|
overwrite_existing
|
bool
|
When False (default) and the table exists, it is returned untouched. |
False
|
Returns:
| Type | Description |
|---|---|
Path
|
The analysis table. |
Source code in src\taters\stats\assemble.py
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 | |
taters.stats.group_differences ¶
Group differences: one-way ANOVA per feature, with post-hoc tests that agree with the omnibus test's assumptions.
The default is the classic decomposition -- pooled-variance F with
Tukey-Kramer post-hocs (Tukey's HSD generalized to unequal group sizes).
welch=True switches BOTH tests: Welch's F (variances not pooled,
Satterthwaite degrees of freedom) with Games-Howell post-hocs. The pairing is
deliberate: Tukey's q statistic divides by a pooled error term, which is
incoherent if you chose Welch precisely because you distrust pooling;
Games-Howell is the standard heteroscedastic companion, built from per-pair
Welch degrees of freedom and the same studentized-range distribution.
Multiple comparisons are handled twice, at two levels: post-hoc p-values are
already family-adjusted within a feature (that is what the studentized range
does), and the omnibus p-values are corrected across features within each
feature set by the method you choose (p_adjust; Benjamini-Hochberg by
default) -- testing 160 cohesion measures at alpha=.05 without correction
would hand you eight "findings" by chance alone.
Missing data: listwise per feature (a row with a blank feature value or a
blank group cell sits out that feature's test); the per-group n_* columns
make the cost visible. NA is never zero.
analyze_group_differences ¶
analyze_group_differences(
*,
table_csv,
group_col,
feature_sets=None,
control_cols=(),
categorical_controls=(),
pca="off",
pca_components=0,
pca_retain="parallel",
pca_rotation=True,
pca_max_missing=MAX_MISSING,
split_col=None,
welch=False,
posthoc="auto",
p_adjust="fdr_bh",
alpha=0.05,
out_dir=None,
overwrite_existing=False,
on_progress=None,
verbose=True,
encoding="utf-8-sig",
rounding=4
)
Test every feature for differences between the groups in one column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table_csv
|
PathLike
|
The assembled analysis table (see the assemble step). |
required |
group_col
|
str
|
The column naming each row's group. Rows with a blank group sit out. |
required |
feature_sets
|
Which features to test: |
None
|
|
control_cols
|
Sequence[str]
|
Columns to hold constant, which turns each comparison into an
analysis of covariance: the groups are compared on what is left
once these have had their say, and the table reports each group's
adjusted mean -- its predicted value at the average age, the average
word count -- rather than its raw one. Continuous controls enter as
themselves; categorical ones become indicators against a reference
level the report names. |
()
|
categorical_controls
|
Sequence[str]
|
Which of |
()
|
pca
|
str or list of str
|
Analyze components instead of the raw measures. Set per analysis, deliberately: raw variables read better in a correlation table, where each row is a measure you can name, while a ridge over four hundred collinear measures is what components are for. The loadings land beside these results and named after them, because a component means nothing without the table saying what loads on it. |
"off"
|
pca_components
|
int
|
How many components to keep, when reducing. |
0
|
pca_retain
|
('parallel', 'kaiser')
|
How the component count is chosen when |
"parallel"
|
pca_rotation
|
bool
|
Rotate the components (varimax) so each loads on a small cluster of features and is therefore nameable. |
True
|
pca_max_missing
|
float
|
When reducing, set aside any feature missing for more than this fraction of the rows rather than letting it delete them -- the same rule, and the same default, the prediction steps apply to predictors. Of the rows that remain, the components are fitted on those with every kept feature present and a row still missing one is left unscored rather than guessed at; both counts are reported. |
0.5
|
split_col
|
Optional[str]
|
Run the whole analysis once per value of this column, labeling each
result with it. Set to |
None
|
welch
|
bool
|
False (default): classic pooled ANOVA. True: Welch's F, for when the groups' variances should not be pooled. |
False
|
posthoc
|
('auto', 'tukey', 'games_howell', 'bonferroni', 'none')
|
Which pairwise comparison to run, and therefore how the pairwise p-values are corrected:
|
"auto"
|
p_adjust
|
('none', 'fdr_bh', 'fdr_by', 'holm', 'bonferroni')
|
How the omnibus p-values are adjusted across features, which is a
separate family from the pairwise one. |
"none"
|
alpha
|
float
|
The threshold used for the |
0.05
|
out_dir
|
Optional[PathLike]
|
Where the two CSVs go; defaults to the analysis table's folder. |
None
|
overwrite_existing
|
bool
|
When False (default) and the results already exist, they are returned untouched instead of recomputed. |
False
|
rounding
|
int
|
Decimal places in the output tables. Values too small for it (a p of 3e-12) switch to significant digits rather than collapsing to 0. |
4
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Notes
A p-value printed as 0 means the tail underflowed double precision
(roughly p < 1e-300), not that the probability is zero. The columns stay
numeric so R, Excel and pandas read them as numbers; report such a value
the way every statistics package does, as "p < .001".
Source code in src\taters\stats\group_differences.py
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 | |
taters.stats.correlations ¶
Correlations between features and outcomes, laid out for perusal.
Features as rows, outcomes as column blocks -- for each outcome its r, its p, its FDR-adjusted p, and the pairwise N -- because that is how a researcher actually reads this table: scan down an outcome's column for the big coefficients, then check that the N behind each one is respectable.
Missing data is handled by pairwise deletion: each (feature, outcome)
cell uses exactly the rows where both values exist, and the _n column
says how many that was. NA is never zero, and a cell whose pairwise-complete
subset is too small (n < 3) or constant is left honestly blank -- with its
_n still filled, so the blank explains itself.
Spearman coefficients rank each pair's complete subset (average ranks for
ties), matching scipy.stats.spearmanr on that subset; a global ranking
would let rows missing from one pair distort another's coefficients.
analyze_correlations ¶
analyze_correlations(
*,
table_csv,
outcome_cols,
feature_sets=None,
control_cols=(),
categorical_controls=(),
pca="off",
pca_components=0,
pca_retain="parallel",
pca_rotation=True,
pca_max_missing=MAX_MISSING,
split_col=None,
method="pearson",
p_adjust="fdr_bh",
out_dir=None,
overwrite_existing=False,
on_progress=None,
verbose=True,
encoding="utf-8-sig",
rounding=4
)
Correlate every feature with every outcome column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table_csv
|
PathLike
|
The assembled analysis table. |
required |
outcome_cols
|
Sequence[str]
|
The outcome columns (numeric). Each becomes a block of four columns
in the output: |
required |
feature_sets
|
|
None
|
|
control_cols
|
Sequence[str]
|
Columns to hold constant, turning every coefficient into a partial
correlation: the relationship that is left once these have had their
say. Continuous controls (age, word count) enter as themselves;
categorical ones (gender, site) are expanded into indicators against
a reference level, which the report names. The degrees of freedom
fall by one per control column, and rows missing any control drop
out -- so the |
()
|
categorical_controls
|
Sequence[str]
|
Which of |
()
|
pca
|
str or list of str
|
Analyze components instead of the raw measures. Set per analysis, deliberately: raw variables read better in a correlation table, where each row is a measure you can name, while a ridge over four hundred collinear measures is what components are for. The loadings land beside these results and named after them, because a component means nothing without the table saying what loads on it. |
"off"
|
pca_components
|
int
|
How many components to keep, when reducing. |
0
|
pca_retain
|
('parallel', 'kaiser')
|
How the component count is chosen when |
"parallel"
|
pca_rotation
|
bool
|
Rotate the components (varimax) so each loads on a small cluster of features and is therefore nameable. |
True
|
pca_max_missing
|
float
|
When reducing, set aside any feature missing for more than this fraction of the rows rather than letting it delete them -- the same rule, and the same default, the prediction steps apply to predictors. Of the rows that remain, the components are fitted on those with every kept feature present and a row still missing one is left unscored rather than guessed at; both counts are reported. |
0.5
|
split_col
|
Optional[str]
|
Run the whole analysis once per value of this column, labeling each
result with it. Set to |
None
|
method
|
('pearson', 'spearman', 'both')
|
|
"pearson"
|
p_adjust
|
('none', 'fdr_bh', 'fdr_by', 'holm', 'bonferroni')
|
How p-values are adjusted for the number of correlations tested:
|
"none"
|
out_dir
|
Optional[PathLike]
|
Where the CSV(s) go; defaults to the analysis table's folder. |
None
|
overwrite_existing
|
bool
|
When False (default) and the results already exist, they are returned untouched instead of recomputed. |
False
|
rounding
|
int
|
Decimal places in the output tables. Values too small for it (a p of 3e-12) switch to significant digits rather than collapsing to 0. |
4
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Notes
A p-value printed as 0 means the tail underflowed double precision
(roughly p < 1e-300), not that the probability is zero. The columns stay
numeric so R, Excel and pandas read them as numbers; report such a value
the way every statistics package does, as "p < .001".
Source code in src\taters\stats\correlations.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 | |
taters.stats.ridge ¶
Ridge regression with cross-validation: predict an outcome from the features, honestly, and keep the model.
Language features are many and collinear -- three readability indices are three views of sentence length -- which is exactly the situation ordinary least squares handles worst and ridge handles well: the penalty trades a little bias for a large drop in variance, and it never has to invert a singular matrix. What ridge does not do is choose its own penalty, so this searches a grid of alphas by k-fold cross-validation and reports the out-of-fold performance at the chosen one. In-sample R-squared is reported too, next to it, because the gap between them is the whole story of whether a model learned anything or memorized.
The arithmetic is plain numpy on purpose. One economy SVD per fold serves the
entire alpha grid -- coefficients for any alpha are
V diag(s/(s^2+a)) U' y -- so a 33-point grid costs one decomposition
rather than 33 solves, and the numbers are exactly reproducible from the
stored model rather than depending on a solver's iteration count.
A fit is an instrument, not a result: the model file carries the predictor names, the training centering and scaling, the chosen alpha and the grid it came from, and the coefficients, so the same model can score a new dataset later -- standardized against the training sample, which is what makes the scores comparable. That is the same fit-once/apply-many discipline the MEM topic model and PCA use here.
apply_ridge_csv ¶
apply_ridge_csv(
*,
model_json,
input_csv,
out_csv=None,
overwrite_existing=False,
on_progress=None,
verbose=True,
encoding="utf-8-sig",
rounding=4,
id_cols=None
)
Score a new table with a saved ridge model.
Predictors are matched by name, in any column order; every other column passes through as an identifier. A predictor the new table lacks is refused by name rather than imputed -- a model scored on a feature it never sees is not the model that was validated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model_json
|
PathLike
|
A model written by :func: |
required |
input_csv
|
PathLike
|
The table to score: an analysis table, or any feature table carrying the model's predictors. |
required |
out_csv
|
Optional[PathLike]
|
Defaults to |
None
|
overwrite_existing
|
bool
|
When False (default) and the predictions already exist, they are returned untouched instead of recomputed. |
False
|
rounding
|
int
|
Decimal places in the predicted values. |
4
|
Returns:
| Type | Description |
|---|---|
Path
|
The predictions table: the input's identifier columns plus one
|
Source code in src\taters\stats\ridge.py
754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 | |
default_alphas ¶
default_alphas()
The default penalty grid, as a plain list (JSON-storable).
Source code in src\taters\stats\ridge.py
73 74 75 76 77 78 79 | |
fit_ridge_csv ¶
fit_ridge_csv(
*,
table_csv,
outcome_cols,
feature_sets=None,
control_cols=(),
categorical_controls=(),
control_combos="none_and_all",
set_combos="subsets",
pca="off",
pca_components=0,
pca_retain="parallel",
pca_rotation=True,
split_col=None,
alphas=None,
n_folds=5,
stratify=True,
zscore=True,
max_missing=MAX_MISSING,
seed=0,
out_dir=None,
out_models_dir=None,
overwrite_existing=False,
on_progress=None,
verbose=True,
encoding="utf-8-sig",
rounding=4
)
Fit a cross-validated ridge model per outcome, and keep it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table_csv
|
PathLike
|
The assembled analysis table. |
required |
outcome_cols
|
Sequence[str]
|
The numeric column(s) to predict. Each gets its own model, its own chosen penalty and its own row of performance. |
required |
feature_sets
|
|
None
|
|
control_cols
|
Sequence[str]
|
Columns to fit alongside -- and instead of -- the language, so the
language's own contribution can be read off. With controls the run
fits three models per outcome on one common sample: the controls
alone, the language alone, and both; the combined model's row
carries The shared sample is the part that matters: fitting each model on whatever rows it happened to have would make the difference between them part sample and part language, with no way to tell which. |
()
|
categorical_controls
|
Sequence[str]
|
Which of |
()
|
set_combos
|
('none', 'each_and_all', 'subsets')
|
When the feature tables are analyzed together ( |
"none"
|
control_combos
|
('none_and_all', 'each', 'subsets')
|
How many control sets to try. The default pair answers the usual
question. |
"none_and_all"
|
pca
|
str or list of str
|
Analyze components instead of the raw measures. Set per analysis, deliberately: raw variables read better in a correlation table, where each row is a measure you can name, while a ridge over four hundred collinear measures is what components are for. The loadings land beside these results and named after them, because a component means nothing without the table saying what loads on it. |
"off"
|
pca_components
|
int
|
How many components to keep, when reducing. |
0
|
pca_retain
|
('parallel', 'kaiser')
|
How the component count is chosen when |
"parallel"
|
pca_rotation
|
bool
|
Rotate the components (varimax) so each loads on a small cluster of features and is therefore nameable. |
True
|
split_col
|
Optional[str]
|
Fit separately for each value of this column, labeling each result
with it. Set to |
None
|
alphas
|
Optional[Sequence[float]]
|
Penalties to search. Default: 33 points from 1e-3 to 1e5. Every value must be greater than zero; alpha = 0 is ordinary least squares, which is exactly what ridge exists to avoid on collinear features. |
None
|
n_folds
|
int
|
Cross-validation folds. The reported performance is out-of-fold: each row is predicted by a model that never saw it. |
5
|
stratify
|
bool
|
Make the folds alike: each gets the same spread of the outcome and sizes within one of each other (rows are ordered by the outcome and dealt round the folds). Off, the folds are a plain seeded shuffle, which can put most of the high scores in one fold. |
True
|
zscore
|
bool
|
Standardize predictors on the training statistics. Coefficients are then per standard deviation and comparable to each other; with False they stay in the features' own units and are not. |
True
|
max_missing
|
float
|
Set aside any predictor missing for more than this fraction of the rows, rather than letting it delete them. Some cohesion measures compare paragraphs two apart and so are blank for any text with fewer than three; left in, 60 such columns once deleted 885 of 938 rows and the model was fitted on 52. Set it to 1.0 to keep every column and accept the row loss; what was dropped is always reported. |
MAX_MISSING
|
seed
|
int
|
Fixes the fold shuffle. Stored in the model, because a cross-validated number nobody can reproduce is a number nobody can check. |
0
|
out_dir
|
Optional[PathLike]
|
Where the tables and the model files go; both default beside the
analysis table (models under |
None
|
out_models_dir
|
Optional[PathLike]
|
Where the tables and the model files go; both default beside the
analysis table (models under |
None
|
overwrite_existing
|
bool
|
When False (default) and the metrics table exists, it is returned untouched. |
False
|
rounding
|
int
|
Decimal places in the output tables. |
4
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Notes
Rows missing any predictor or the outcome sit out that model's fit
entirely (listwise), and n_used in the metrics table says how many
were left. A predictor that never varies in the training rows is dropped
by name rather than silently contributing nothing.
Source code in src\taters\stats\ridge.py
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 | |
predict_rows ¶
predict_rows(
cells, kept, mu, sigma, coef, intercept, rounding
)
Predictions for a block of rows -- the one scorer fit and apply share.
cells is (n_rows, n_predictors) in the model's predictor order. A row
with a missing value among the predictors it needs cannot be scored: the
missing value carries through the arithmetic as NaN -- deliberately, not
incidentally -- and the row comes back as None for the caller to
write as a blank cell. A prediction built from a filled-in guess would
look exactly like a real one in the output.
Only the predictors the model actually kept are read, so a row missing a predictor that was dropped as constant still scores.
Source code in src\taters\stats\ridge.py
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | |
taters.stats.report ¶
The plain-English report: one markdown file summarizing a run's statistics.
Each analysis writes its own section fragment into stats_results/_sections/
as it runs (see :func:taters.stats._common.write_section); this module just
concatenates them in their fixed order under a dated header. It computes no
statistics of its own -- every number in the report was produced, and is owned,
by the module that understands it. That division is what lets a future
analysis join the report by writing one fragment, with no edits here.
write_stats_report ¶
write_stats_report(
*,
stats_dir="stats_results",
title=None,
out_md=None,
keep_table=True,
overwrite_existing=False,
verbose=True
)
Concatenate the analyses' section fragments into report.md.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stats_dir
|
PathLike
|
The stats output folder holding |
'stats_results'
|
title
|
Optional[str]
|
Heading for the report; defaults to "Statistical results". |
None
|
out_md
|
Optional[PathLike]
|
Where to write; defaults to |
None
|
keep_table
|
bool
|
Whether to leave the merged analysis table in place. Deleting it is
this step's job rather than the assemble step's for the obvious
reason: every analysis reads that table, so the only moment it is
safe to remove is after the last one has run, and this is the step
that runs last. The sidecar describing it goes with it -- a map of a
file that is not there helps nobody -- while the row accounting in
|
True
|
overwrite_existing
|
bool
|
Unlike the analyses, the report is cheap and derived, so the default skip is mostly about symmetry: pass True to rebuild after re-running any analysis. |
False
|
Returns:
| Type | Description |
|---|---|
Path
|
The report file. |
Source code in src\taters\stats\report.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | |
taters.stats.pca ¶
Streaming, exact PCA with varimax rotation, over CSVs of any length.
The engine behind the MEM topic model, and a standalone tool for reducing any feature table (dictionary scores, readability measures, embeddings) before a downstream model. Two design facts:
-
Exact, one pass, bounded memory. Feature tables are long (rows) but narrow (columns), so the fit streams the rows once, accumulating the column sums and the p-by-p cross-product in chunks (BLAS does the work: ~50x faster than row-at-a-time), then eigendecomposes the correlation matrix in memory. No randomized SVD, no seed, no dask: the decomposition is verified against scikit-learn's full SVD to machine precision, and the finished rotated solution against R's
psych::principalto ~2 decimal places on a real 1,000-term MEM (seevarimax-- Kaiser normalization plus iterate-to-convergence, deliberately correcting the dask script this module replaces, whose rotation was unnormalized and capped at 20 sweeps). Randomized methods earn their keep when p is tens of thousands; if that day comes, it is a solver option behind this same interface. -
A fit is a reusable instrument.
fit_pca_csvwrites a model file with the feature names, the training means and deviations, and the projection;apply_pca_csvmatches features by name in any new table, standardizes with the TRAINING statistics, and projects. Applying a model to its own training table reproduces the training scores exactly.
Eigenvalues are those of the correlation matrix (they average 1.0), so both
retention rules mean what the textbooks say: parallel analysis (the default
for n_components=0) keeps a component while its eigenvalue beats what
random data of the same size produce at that rank, and the Kaiser criterion
keeps every eigenvalue >= 1. (dask-ml and scikit-learn
scale variances by n-1 rather than n; that uniform sqrt((n-1)/n) on loadings
is a bookkeeping convention, not a disagreement.)
apply_in_memory ¶
apply_in_memory(x, axes)
Project rows onto axes from :func:fit_in_memory.
Standardized with the axes' own means and deviations, not ones recomputed from the rows being projected -- which is what lets a saved reduction score a new table on the original sample's scale.
Source code in src\taters\stats\pca.py
674 675 676 677 678 679 680 681 682 683 684 685 686 687 | |
apply_pca_csv ¶
apply_pca_csv(
model_json,
input_csv,
*,
out_scores_csv=None,
overwrite_existing=False,
on_progress=None,
encoding="utf-8-sig",
rounding=4
)
Score a new feature table on a saved PCA model.
The model's features are matched by name in the new table's header (any column order; extra columns pass through as identifiers), then each row is standardized with the training means and deviations and projected. A feature the model needs that the table lacks is refused -- filling it with anything would silently move every score.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model_json
|
PathLike
|
A |
required |
input_csv
|
PathLike
|
Any CSV whose header contains every feature the model was fit on. |
required |
out_scores_csv
|
optional
|
Defaults to |
None
|
overwrite_existing
|
bool
|
If |
False
|
encoding
|
str
|
Encoding for reading and writing CSV files. |
"utf-8-sig"
|
rounding
|
int
|
Decimal places for the component scores. |
4
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Source code in src\taters\stats\pca.py
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 | |
component_names ¶
component_names(n)
Component_1 … Component_n -- one name everywhere. The file API
wrote PC_1 and the in-analysis reduction Component_1 for the same
thing, so a reader met two names for one idea in one results folder.
Component_1 … Component_n -- what the analyses call them, so a
loadings table and a results table can be read side by side.
Source code in src\taters\stats\pca.py
690 691 692 693 694 695 696 | |
describe_retention ¶
describe_retention(retention)
One sentence on how the component count was chosen, for a report.
Source code in src\taters\stats\pca.py
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 | |
fit_axes ¶
fit_axes(
n,
sums,
cross,
*,
n_components,
rotation,
retain="parallel",
on_progress=None
)
From streamed moments to finished axes.
Returns (kept, mu, sigma, loadings, projection, eigenvalues, pct,
retention): kept indexes the non-constant columns; loadings is
the rotated feature-by-component matrix (interpretation); projection
scores a row -- standardized kept cells @ projection -- and both share
one component order (descending rotated eigenvalue) and one sign
convention (each component's strongest feature loads positive), so
reruns cannot come back mirror-flipped or shuffled. retention says
how the count was chosen: the rule, and for parallel analysis the
unrotated eigenvalues beside the chance thresholds up to the first one
that fell short, so the decision can be read and argued with.
n_components > 0 is honored as asked; 0 chooses by retain,
one of :data:RETAIN_RULES.
Source code in src\taters\stats\pca.py
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 | |
fit_in_memory ¶
fit_in_memory(
x, *, n_components=0, rotation=True, retain="parallel"
)
Fit the same axes as :func:fit_pca_csv, from a matrix already in memory.
The analyses hold their design matrix in memory already -- the whole
filtered table, since a reduction is fitted once per analysis before the
analysis runs (see the planning decisions) -- so they cannot go through
the CSV-streaming path, which reads a file. Same moments, same
:func:fit_axes, same sign and order conventions, so a reduction fitted
here and one fitted from a file agree.
Returns the dict :func:apply_in_memory consumes, plus the loadings and
eigenvalues a reader wants to inspect.
Source code in src\taters\stats\pca.py
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 | |
fit_pca_csv ¶
fit_pca_csv(
input_csv,
*,
start_col=2,
n_components=0,
retain="parallel",
rotation=True,
out_scores_csv=None,
out_model_json=None,
out_loadings_csv=None,
out_eigenvalues_csv=None,
overwrite_existing=False,
on_progress=None,
encoding="utf-8-sig",
rounding=4
)
Fit a varimax-rotated PCA to the numeric columns of any feature CSV.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_csv
|
PathLike
|
Any wide feature table: identifier column(s) first, numeric feature columns after. Every Taters feature file (dictionary scores, readability, a document-term matrix) has this shape. |
required |
start_col
|
int
|
1-based position of the first feature column, exactly as in the
house dask script this replaces. Everything before it is carried
into the scores file unchanged as identifiers. The default fits
tables shaped |
2
|
n_components
|
int
|
|
0
|
retain
|
('parallel', 'kaiser')
|
How |
"parallel"
|
rotation
|
bool
|
Varimax-rotate. Off gives the raw principal axes. |
True
|
out_scores_csv
|
Optional[PathLike]
|
Defaults: |
None
|
out_model_json
|
Optional[PathLike]
|
Defaults: |
None
|
out_loadings_csv
|
Optional[PathLike]
|
Defaults: |
None
|
out_eigenvalues_csv
|
Optional[PathLike]
|
Defaults: |
None
|
overwrite_existing
|
bool
|
If |
False
|
encoding
|
str
|
Encoding for reading and writing CSV files. |
"utf-8-sig"
|
rounding
|
int
|
Decimal places for scores, loadings, and eigenvalues. |
4
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Source code in src\taters\stats\pca.py
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 | |
parallel_thresholds ¶
parallel_thresholds(
n,
p,
*,
draws=PARALLEL_DRAWS,
percentile=PARALLEL_PERCENTILE,
seed=0,
chunk_rows=_CHUNK_ROWS,
on_progress=None
)
The eigenvalues chance alone produces: for each rank, the percentile
of the correlation-matrix eigenvalues of draws random standard-normal
data sets with n rows and p columns.
Memory-safe by construction: no random data set is ever held whole. Each
draw is generated chunk_rows rows at a time and folded into a running
p-by-p cross-product, exactly as :func:stream_moments folds a
real table, so the cost in memory is one more p-by-p matrix --
the same order as the fit already holds -- whatever n is. The cost in
time is draws decompositions; fifty is the usual number and takes
seconds at a few hundred features.
Source code in src\taters\stats\pca.py
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | |
project_row ¶
project_row(cells, kept, mu, sigma, projection, rounding)
One row's component scores from raw feature cells -- shared by every fit and every apply, so the two can never drift.
Source code in src\taters\stats\pca.py
341 342 343 344 345 346 347 348 | |
stream_moments ¶
stream_moments(
csv_path,
*,
encoding,
skip_cols,
on_progress=None,
message="measuring the table"
)
One pass over the rows: n, per-column sums, and X^T X for the columns
after the first skip_cols. Rows are accumulated in chunks so the
cross-product is a handful of BLAS calls per chunk rather than an outer
product per row -- same numbers, a large constant factor faster.
Source code in src\taters\stats\pca.py
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | |
varimax ¶
varimax(
loadings, gamma=1.0, q=2000, tol=1e-13, normalize=True
)
Orthogonal varimax rotation of an (n_features, n_components) loadings matrix; returns (rotated_loadings, rotation_matrix).
normalize=True is Kaiser normalization -- each feature's loading
row is scaled to unit communality while the rotation is chosen, which
is what stats::varimax, psych::principal and SPSS all do by
default. Without it, high-communality features dominate the criterion
and the solution genuinely differs (a real MEM run diverged from its R
twin until this matched; the worst theme correlated at 0.39 unnormalized
and 0.996 normalized). Normalization only steers the choice of rotation:
the returned loadings are still loadings @ R exactly, so a scorer
using the same R stays coherent with them.
The iteration budget is a ceiling, not a schedule -- the loop stops on convergence, and the predecessor's cap of 20 sweeps was itself a source of divergence at MEM sizes (1,000 terms x 25 themes converges in ~100).
Source code in src\taters\stats\pca.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | |
warn_if_wide ¶
warn_if_wide(n_features, *, warn_gb=1.0)
Memory truth-telling before the work starts.
The fit is memory-safe against corpus LENGTH by construction -- rows stream through in chunks and are gone. Its one memory cost grows with table WIDTH: the p-by-p cross-product/correlation matrix plus the eigendecomposition's workspace, roughly three p*p float64 buffers. At the default 250-term vocabulary that is under 2 MB; at 5,000 features about 0.6 GB; at 20,000 it would be ~10 GB, which deserves a warning before hours of streaming, not an OOM after.
Source code in src\taters\stats\pca.py
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | |
taters.stats.describe ¶
Descriptive statistics for every feature table a run writes.
The first thing anyone does with a new measure is look at its distribution:
how many texts have it, what its mean and spread are, whether it is skewed
into a corner, whether a suspicious maximum is one text or a hundred. Until
now that meant opening each CSV in a spreadsheet and doing it by hand, per
column, per table. This step does it once for every numeric column of every
feature table -- count, missing, mean, standard deviation, quartiles, range,
skewness, kurtosis, zeros, distinct values -- and writes one table of
descriptives per feature table under stats_descriptives/, whether or not
any statistics were asked for.
The numbers are the textbook sample statistics (standard deviation with
n-1; the bias-adjusted skewness and excess kurtosis that SPSS, R's
psych::describe with type=2 and Excel report), so they can be pasted
into a methods table beside anyone else's. Every cell is computed from the
values that are present; a blank cell is missing, and is counted as such.
One file per table, written only when the table is newer than it, so a second run of an unchanged pipeline writes nothing -- the resume contract.
describe_features ¶
describe_features(
feature_csvs,
out_dir="stats_descriptives",
*,
overwrite_existing=False,
encoding="utf-8-sig",
rounding=4,
verbose=True,
on_progress=None
)
Write a table of descriptive statistics for every feature table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
feature_csvs
|
sequence of str or Path
|
The feature tables to describe -- one row per text, identifier columns first, measures after. Every Taters feature file has this shape; the pipeline hands over every table the run wrote. |
required |
out_dir
|
str or Path
|
Where the descriptives go: |
"stats_descriptives"
|
overwrite_existing
|
bool
|
Recompute a table's descriptives even when they are newer than it. |
False
|
encoding
|
str
|
The tables' encoding, and the descriptives'. |
"utf-8-sig"
|
rounding
|
int
|
Decimal places written. |
4
|
verbose
|
bool
|
The usual. |
True
|
on_progress
|
bool
|
The usual. |
True
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Notes
Every numeric column is described except the identifiers (text_id,
source, speaker). Columns holding words are left out, since a
mean of them means nothing. Count columns the steps write beside their
measures (token_count, WC) are described like any other -- they
are the first thing to look at when a measure looks strange.
Source code in src\taters\stats\describe.py
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | |
describe_values ¶
describe_values(values)
The descriptives of one column, from its values with blanks as NaN.
sd needs two values, skew three and kurtosis four; below
that they are None rather than a division by zero dressed as a
number. Skewness is the bias-adjusted sample statistic (G1) and kurtosis
the bias-adjusted excess kurtosis (G2), zero for a normal distribution
-- the pair a methods table expects.
Source code in src\taters\stats\describe.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
taters.score_model ¶
Score this dataset with a model somebody already fitted.
A saved model is the one artifact in Taters meant to leave the run that made it: fit a ridge on a corpus where you have the outcome, then apply it to a corpus where you do not. That is one job as far as the researcher is concerned, and it was three -- a MEM topic model, a ridge and a classifier each had their own menu entry, their own vocabulary and their own place in the pipeline, and the ridge's place was at the very end, among the statistics, where it is least useful: what comes back is a feature, a column per text, and the next thing anyone wants to do with it is analyze it like any other.
So there is one entry, it lives with feature extraction, and it works out what to do from the model file. What a model needs is a fact about the model, not a question for the user:
- a MEM topic model needs text, and re-derives its own vocabulary from it;
- a ridge or a classifier needs the feature columns it was fitted on, by name, which means the steps that produce them have to have run.
The second case is the interesting one, and the honest thing to do when those columns are absent is to name them and stop -- not to impute, and not to score a model on a feature it has never seen and call the result a prediction.
What this does not yet do
A model fitted on features that were themselves transformed -- sentence embeddings reduced to fifty dimensions by PCA, say -- needs that whole recipe replayed before its weights mean anything, and a model file records its predictor names but not how they were made. So the columns have to already exist under the names the model knows. Getting from "here is a folder of text" to "here are the exact fifty numbers this model wants" is a pipeline the model would have to carry with it, and that is not built yet; until it is, this step tells you precisely which columns it wanted.
score_with_model ¶
score_with_model(
*,
model_json,
feature_csvs=(),
metadata_csv=None,
key_cols=("text_id",),
keep_inputs=False,
unverified_ok=(),
allow_unrecorded=False,
csv_path=None,
txt_dir=None,
analysis_csv=None,
gathered_csv=None,
out_csv=None,
overwrite_existing=False,
on_progress=None,
verbose=True,
workers=None,
device="auto",
encoding="utf-8-sig",
rounding=6,
text_cols=("text",),
id_cols=None,
mode="concat",
group_by=None,
delimiter=",",
joiner=" ",
num_buckets=512,
max_open_bucket_files=64,
tmp_root=None,
recursive=True,
pattern=None,
id_from="stem",
include_source_path=True
)
Score this dataset with a saved model, whatever kind of model it is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model_json
|
Union[PathLike, Sequence[PathLike]]
|
One or more models saved by earlier runs -- topic models, ridges, classifiers, word vectors, fine-tuned predictors -- as a file, a list of files, or a folder holding them (every model in it). Each model says what kind it is and what it needs; nothing here has to be told. Several models are scored one after another, each against the feature tables it was fitted on, into one file per model and one merged table; two models whose names read the same are refused (rename one under Settings → Manage saved models). |
required |
feature_csvs
|
Sequence[PathLike]
|
The feature tables this run produced, joined on |
()
|
metadata_csv
|
Optional[PathLike]
|
A table keyed like the feature tables that carries the spreadsheet's
own columns -- needed only by a model fitted with controls, whose
|
None
|
key_cols
|
Sequence[str]
|
The columns identifying a row, used to join the feature tables. |
('text_id',)
|
keep_inputs
|
bool
|
Keep the joined feature table the scoring read. Off by default: the row accounting that explains an unscored row is reported either way, and the table itself is a copy of columns that already exist. |
False
|
unverified_ok
|
Sequence[str]
|
Feature tables to score against even though their measuring settings cannot be checked -- named one at a time, never a blanket flag, because a single boolean would let you waive one unverifiable table and silently waive the acoustics table sitting next to it. This waives not knowing. It cannot waive a known mismatch: when both the model and the table state their settings and the settings differ, scoring is refused outright. |
()
|
allow_unrecorded
|
bool
|
Score a model that predates settings-recording at all. Such a model cannot be checked in either direction, so this is refused by default: it is exactly the case that reported a prediction seven years wrong without complaint. |
False
|
csv_path
|
Optional[PathLike]
|
Where the text comes from, for a model that reads text: a
spreadsheet, a folder of |
None
|
txt_dir
|
Optional[PathLike]
|
Where the text comes from, for a model that reads text: a
spreadsheet, a folder of |
None
|
analysis_csv
|
Optional[PathLike]
|
Where the text comes from, for a model that reads text: a
spreadsheet, a folder of |
None
|
gathered_csv
|
Optional[PathLike]
|
Where the text comes from, for a model that reads text: a
spreadsheet, a folder of |
None
|
out_csv
|
Optional[PathLike]
|
Where the scores go. With one model, its scores; default
|
None
|
workers
|
int
|
Parallelism for a text model, which has real work to do per row. Ignored by the others, whose arithmetic is one matrix multiply. |
None
|
device
|
('auto', 'cuda', 'cpu')
|
Where a text model's tagger runs, if it was built with the stanza engine -- a runtime choice, deliberately not stored in the model. |
"auto"
|
overwrite_existing
|
bool
|
When False (default) and the scores exist, they are returned untouched. |
False
|
rounding
|
int
|
Decimal places in the scores. |
6
|
text_cols
|
Sequence[str]
|
How to read a spreadsheet of text: which columns hold it, which
identify a row, and whether several text columns are joined
( |
('text',)
|
id_cols
|
Sequence[str]
|
How to read a spreadsheet of text: which columns hold it, which
identify a row, and whether several text columns are joined
( |
('text',)
|
mode
|
Sequence[str]
|
How to read a spreadsheet of text: which columns hold it, which
identify a row, and whether several text columns are joined
( |
('text',)
|
group_by
|
Sequence[str]
|
How to read a spreadsheet of text: which columns hold it, which
identify a row, and whether several text columns are joined
( |
('text',)
|
delimiter
|
Sequence[str]
|
How to read a spreadsheet of text: which columns hold it, which
identify a row, and whether several text columns are joined
( |
('text',)
|
joiner
|
Sequence[str]
|
How to read a spreadsheet of text: which columns hold it, which
identify a row, and whether several text columns are joined
( |
('text',)
|
num_buckets
|
int
|
Spill settings for grouping a large spreadsheet, passed straight through to the gatherer. |
512
|
max_open_bucket_files
|
int
|
Spill settings for grouping a large spreadsheet, passed straight through to the gatherer. |
512
|
tmp_root
|
int
|
Spill settings for grouping a large spreadsheet, passed straight through to the gatherer. |
512
|
recursive
|
bool
|
How to read a folder of |
True
|
pattern
|
bool
|
How to read a folder of |
True
|
id_from
|
bool
|
How to read a folder of |
True
|
include_source_path
|
bool
|
How to read a folder of |
True
|
Returns:
| Type | Description |
|---|---|
Path
|
The scores: one row per text, with the model's own output columns. A row the model could not score is blank rather than absent, so the table still joins back to everything else. |
Raises:
| Type | Description |
|---|---|
ValueError
|
When the model needs feature columns this run does not have. The message names them, because the fix is to add the steps that produce them and nothing else in the message helps with that. |
Source code in src\taters\score_model.py
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | |
taters.figures.wordclouds ¶
Which words go into which cloud, for every kind of result Taters writes.
Three entry points, one per stage of a run:
- :func:
stats_wordcloudsreads the tables under astats_resultsfolder -- ridge and classifier coefficients, correlations, pairwise group differences, PCA loadings, and the analysis table itself for per-group frequencies -- and draws one cloud per direction per outcome, plus a report section that shows them. - :func:
theme_wordcloudsdraws one cloud per theme of a topic model from its loadings table. - :func:
frequency_wordcloudsdraws the corpus's most frequent words from a frequency list.
Every function reads finished tables and computes no statistic of its own.
The choosing -- which rows, which model, which threshold -- is in pure
functions over lists of rows, tested without a picture ever being drawn; the
drawing is :mod:taters.figures.render. A cloud that would be empty is not
drawn and the report says why, because a missing picture reads as a bug and
"no feature passed p < .05" reads as a finding.
The folders follow the analyses: <analysis>/<feature set>/ holds that
set's clouds, with components/ and themes/ inside it for what its
predictors are made of -- a ridge over topic-model themes shows "Theme_5"
as a word, and the picture that says what Theme 5 is belongs beside it,
not three folders away.
Pictures are redrawn only when the table they came from is newer than they are, the same rule every statistics step follows, and the report section is rewritten only when its text changes -- so a second run of an unchanged pipeline writes nothing, which is the resume contract the whole pipeline keeps.
CloudSpec
dataclass
¶
CloudSpec(
analysis,
name,
title,
legend,
words=list(),
note="",
folder="",
set_name="",
stat="",
kind="cloud",
inputs=list(),
)
One picture to draw, or one sentence about why there is none.
folder is where it goes under the figures root and name the file
name without extension; analysis the heading it lists under in the
report and set_name the feature set it belongs to; words the
(label, weight) pairs; note replaces the picture when words is
empty; stat names the statistic the weights are, for the sentence
that says where a theme appeared ("TIPI_Open: β = +0.32"); kind is
cloud, component or theme; inputs the tables it was drawn
from, which decide whether it needs redrawing.
clouds_for_classifier ¶
clouds_for_classifier(rows, allowed)
Two clouds per class per outcome per feature set from
classifier_coefficients.csv: what pushes a text toward the class,
and what pushes it away.
Source code in src\taters\figures\wordclouds.py
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 | |
clouds_for_components ¶
clouds_for_components(rows, *, analysis, component_words)
One mixed-sign cloud per component from a *_pca_loadings*.csv:
the component_words features that load most strongly, blue for a
positive loading and red for a negative one. Filed under the analysis
and set they belong to, in a components/ folder of their own.
Source code in src\taters\figures\wordclouds.py
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 | |
clouds_for_correlations ¶
clouds_for_correlations(rows, *, max_p, method='')
Two clouds per outcome per feature set from a correlations table:
features correlated positively and negatively, sized by r, only those
under max_p (adjusted when the table has adjusted p-values).
Source code in src\taters\figures\wordclouds.py
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 | |
clouds_for_frequencies ¶
clouds_for_frequencies(rows, *, top_words)
The corpus cloud from a frequency list: the top_words most
frequent n-grams after :func:skip_redundant, sized by frequency.
Source code in src\taters\figures\wordclouds.py
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 | |
clouds_for_frequencies_by_group ¶
clouds_for_frequencies_by_group(
table_rows, sets, *, group_col, max_words
)
One cloud per group level per document-term set: the terms used most in that group's texts, sized by their summed count in the analysis table.
The analysis table already joins the matrix and the grouping column, so this is a column sum per level and nothing is re-tokenized.
Source code in src\taters\figures\wordclouds.py
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 | |
clouds_for_group_differences ¶
clouds_for_group_differences(rows, *, max_p)
Two clouds per pair of groups per feature set from
group_differences_pairwise.csv: what is higher in the first group
and what is higher in the second, sized by Cohen's d.
Source code in src\taters\figures\wordclouds.py
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 | |
clouds_for_neighbors ¶
clouds_for_neighbors(rows, *, top_words)
One cloud per probe from a word-vector model's neighbors table
(probe, rank, word, similarity), sized by similarity. A probe the
model does not know has a row with blanks, and gets a sentence.
Source code in src\taters\figures\wordclouds.py
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 | |
clouds_for_ridge ¶
clouds_for_ridge(rows, allowed)
Two clouds per outcome per feature set from ridge_coefficients.csv:
the predictors of a higher score and of a lower one, sized by their
standardized coefficient in the full model.
Source code in src\taters\figures\wordclouds.py
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | |
clouds_for_theme_predictors ¶
clouds_for_theme_predictors(
loadings,
mentions,
*,
analysis,
folder,
set_name,
top_words
)
What each theme that appears in a set's clouds is made of.
A ridge over topic-model themes draws "Theme_5" as a word, which says
nothing until the theme's own words are in view. For every theme named
in mentions (theme -> where it appeared, as (statistic, value) pairs
such as ("TIPI_Open: β", 0.32)) that has a column in the loadings
table, one mixed-sign cloud of its top_words strongest terms, filed
under the set's themes/ folder, its legend naming the three places
it mattered most.
Source code in src\taters\figures\wordclouds.py
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 | |
clouds_for_themes ¶
clouds_for_themes(
rows, *, top_words, min_abs_loading=0.0, shares=None
)
One mixed-sign cloud per theme from a topic model's loadings table
(term, [pos,] Theme_1…). A pos column joins the label, so
felt (VBD) reads as the tagged term it is.
Source code in src\taters\figures\wordclouds.py
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 | |
frequency_wordclouds ¶
frequency_wordclouds(
freq_csv,
out_dir=None,
*,
top_words=100,
enabled=True,
overwrite_existing=False,
encoding="utf-8-sig",
verbose=True,
on_progress=None
)
The corpus's most frequent words and phrases as one cloud.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freq_csv
|
str or Path
|
The n-gram frequency list ( |
required |
out_dir
|
str or Path
|
Where the picture goes. Defaults to
|
None
|
top_words
|
int
|
How many terms, by frequency, after phrases that only repeat a more frequent word are left out. |
100
|
enabled
|
bool
|
As for :func: |
True
|
overwrite_existing
|
bool
|
As for :func: |
True
|
encoding
|
bool
|
As for :func: |
True
|
verbose
|
bool
|
As for :func: |
True
|
on_progress
|
bool
|
As for :func: |
True
|
Returns:
| Type | Description |
|---|---|
Path
|
The output folder holding |
Source code in src\taters\figures\wordclouds.py
1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 | |
neighbor_wordclouds ¶
neighbor_wordclouds(
neighbors_csv,
out_dir=None,
*,
top_words=20,
enabled=True,
overwrite_existing=False,
encoding="utf-8-sig",
verbose=True,
on_progress=None
)
One word cloud per probe of a word-vector model's neighbors table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
neighbors_csv
|
str or Path
|
The |
required |
out_dir
|
str or Path
|
Where the pictures go. Defaults to
|
None
|
top_words
|
int
|
How many neighbors per probe, by similarity. |
20
|
enabled
|
bool
|
As for :func: |
True
|
overwrite_existing
|
bool
|
As for :func: |
True
|
encoding
|
bool
|
As for :func: |
True
|
verbose
|
bool
|
As for :func: |
True
|
on_progress
|
bool
|
As for :func: |
True
|
Returns:
| Type | Description |
|---|---|
Path
|
The output folder; it also holds an |
Source code in src\taters\figures\wordclouds.py
1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 | |
skip_redundant ¶
skip_redundant(phrases)
Drop an n-gram that only repeats words the cloud already shows.
Walking down by frequency, a phrase every one of whose words is already in the cloud on its own -- "of the" beside "of" and "the" -- says nothing new and is skipped; a phrase that brings at least one new word stays ("going to be" when "going" is not there yet). Single words are always kept. A frequency-based test was tried first and kept "of the" because "of" is rarer than "the", which is true and not the point.
Source code in src\taters\figures\wordclouds.py
578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 | |
stats_wordclouds ¶
stats_wordclouds(
stats_dir="stats_results",
*,
max_words=80,
max_p=0.05,
component_words=30,
group_col="",
enabled=True,
overwrite_existing=False,
encoding="utf-8-sig",
verbose=True,
on_progress=None
)
Draw word clouds of every statistics result in a folder, and a report section showing them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stats_dir
|
str or Path
|
The statistics output folder. Whatever tables it holds are drawn:
ridge and classifier coefficients, correlations, pairwise group
differences, PCA loadings, and per-group term frequencies when the
analysis table joins a document-term matrix and |
"stats_results"
|
max_words
|
int
|
The most words in any one cloud. |
80
|
max_p
|
float
|
Correlations and pairwise differences are drawn only when they pass this, on the adjusted p-value where the table has one. |
0.05
|
component_words
|
int
|
How many features to show for each principal component, and how many terms for each topic-model theme a set's clouds name. |
30
|
group_col
|
str
|
The metadata column whose levels get a frequency cloud each. Empty draws none. |
""
|
enabled
|
bool
|
Off draws nothing and writes nothing -- the switch behind
|
True
|
overwrite_existing
|
bool
|
Redraw pictures that are newer than their tables anyway. |
False
|
encoding
|
str
|
The tables' encoding. |
"utf-8-sig"
|
verbose
|
bool
|
The usual. |
True
|
on_progress
|
bool
|
The usual. |
True
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Notes
Coefficient clouds come from one model per outcome: language fitted
with every control when there were controls, language alone otherwise.
Controls themselves are not drawn -- age as the biggest word in a
cloud about language is true and beside the point. A cloud that would be
empty is not drawn; the report says so instead.
Source code in src\taters\figures\wordclouds.py
812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 | |
theme_wordclouds ¶
theme_wordclouds(
loadings_csv,
out_dir=None,
*,
top_words=30,
min_abs_loading=0.0,
enabled=True,
overwrite_existing=False,
encoding="utf-8-sig",
verbose=True,
on_progress=None
)
One word cloud per theme of a topic model, from its loadings table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
loadings_csv
|
str or Path
|
The |
required |
out_dir
|
str or Path
|
Where the pictures go. Defaults to
|
None
|
top_words
|
int
|
How many terms per theme, by the size of their loading. |
30
|
min_abs_loading
|
float
|
Leave out terms loading less than this in either direction. |
0.0
|
enabled
|
bool
|
As for :func: |
True
|
overwrite_existing
|
bool
|
As for :func: |
True
|
encoding
|
bool
|
As for :func: |
True
|
verbose
|
bool
|
As for :func: |
True
|
on_progress
|
bool
|
As for :func: |
True
|
Returns:
| Type | Description |
|---|---|
Path
|
The output folder; it also holds an |
Source code in src\taters\figures\wordclouds.py
963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 | |
taters.figures.render ¶
Draw a word cloud: words sized by a weight, colored by its sign and strength.
The layout is ours -- a spiral outward from the center, horizontal words only, no two boxes overlapping -- and it is pure Python, so it is tested without a font engine by handing it a fake measurer. Pillow is used for the two things that need a font: measuring a word at a size, and drawing the PNG. It is imported inside the functions that need it, so importing this module costs nothing and a machine without Pillow gets one plain sentence instead of a traceback.
What the picture means is fixed here, once, for every cloud in Taters: size follows the magnitude of the weight, shade darkens with it, blue is a positive weight and red a negative one. A caller that wants a cloud of one sign filters its words first; a caller with signed loadings passes them as they are and gets both colors in one picture.
Layout
dataclass
¶
Layout(placed, dropped, width, height, top_margin)
Where every word landed, and which did not fit at any legible size.
Placement
dataclass
¶
Placement(text, weight, size, x, y, w, h)
One word on the canvas: where its ink goes and how big it is.
color_for ¶
color_for(weight, max_abs)
Blue for a positive weight, red for a negative one, darker the larger it is relative to the cloud's strongest word.
Source code in src\taters\figures\render.py
152 153 154 155 156 | |
fit_budget ¶
fit_budget(
words,
sizes,
measure,
*,
width,
height,
top_margin=0,
min_font=12,
fill=FILL
)
Sizes scaled so the words can actually fit on the canvas.
:func:scale_sizes knows the weights and nothing about the canvas; eighty
words at up to 96 px need more than 1200 by 800 pixels, and the layout's
only answers are shrinking one word at a time or dropping it (36 of 80
dropped, on the first real picture). Measuring the ink at the proposed
sizes and scaling every size by the same factor keeps the picture's
proportions -- the strongest word is still that much larger -- while
making room for all of them.
The same rule grows a sparse cloud: five significant features at the
weight-scaled sizes sat in the middle of an otherwise empty picture, so
when the ink falls short of the budget every size is scaled up, until
the largest word is a quarter of the drawing height. Never below
min_font.
Source code in src\taters\figures\render.py
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 | |
layout ¶
layout(
words,
sizes,
measure,
*,
width,
height,
top_margin=0,
min_font=12,
shrink=0.85,
seed=0,
max_steps=6000
)
Place every word on a width by height canvas below top_margin.
Largest first, each on a spiral out from the center, so the strongest
words hold the middle and the rest fill in around them. A word that finds
no room at its size shrinks by shrink and tries again, down to
min_font; below that it is dropped and named in Layout.dropped
rather than drawn illegibly or on top of something. measure(text,
size) returns the ink width and height of a word; the renderer passes a
real font, the tests pass arithmetic. The only randomness is the angle
each spiral starts at, from seed, so the same input draws the same
picture every time.
Source code in src\taters\figures\render.py
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 | |
pillow_missing_reason ¶
pillow_missing_reason()
None when Pillow can be imported, else the sentence to show instead
of a figure.
Source code in src\taters\figures\render.py
86 87 88 89 90 91 92 93 | |
render_wordcloud ¶
render_wordcloud(
words,
out_png,
*,
title,
legend=None,
width=1200,
height=800,
max_words=80,
min_font=16,
max_font=96,
seed=0,
font_path=None,
on_progress=None,
verbose=False
)
Draw one word cloud to a PNG.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
words
|
sequence of (label, weight) or mapping
|
The words and their signed weights. Blue for positive, red for
negative, size and shade by magnitude; the strongest |
required |
out_png
|
str or Path
|
Where the picture goes. Written atomically. |
required |
title
|
str
|
Drawn across the top of the picture, so it still says what it is once pasted into a slide without the report. |
required |
legend
|
str
|
The second caption line. Defaults to the sentence that explains size
and color; pass |
None
|
width
|
int
|
Canvas size in pixels. |
1200
|
height
|
int
|
Canvas size in pixels. |
1200
|
max_words
|
int
|
How many words at most, and the smallest and largest type sizes. |
80
|
min_font
|
int
|
How many words at most, and the smallest and largest type sizes. |
80
|
max_font
|
int
|
How many words at most, and the smallest and largest type sizes. |
80
|
seed
|
int
|
Fixes the layout, so the same words draw the same picture. |
0
|
font_path
|
str or Path
|
A TrueType font to use instead of the bundled DejaVu Sans Bold. |
None
|
on_progress
|
Optional[Callable[..., None]]
|
The usual: a phase announcement, and a line naming any words that did not fit. |
None
|
verbose
|
Optional[Callable[..., None]]
|
The usual: a phase announcement, and a line naming any words that did not fit. |
None
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When Pillow is not installed, with a sentence that says what to do.
Callers that would rather skip the figure check
:func: |
Source code in src\taters\figures\render.py
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 | |
scale_sizes ¶
scale_sizes(
weights, min_font, max_font, *, few=8, few_floor=0.5
)
Font size for each weight: linear in its magnitude, between a floor and
max_font.
Linear, not by rank, because the picture is meant to show how much
stronger the top word is, not just that it is first. The floor rises to
few_floor * max_font when there are few words or fewer, so a cloud
of five significant features fills its frame instead of huddling in the
middle at the minimum size. One word gets max_font; weights that are
all equal share one middle size, since there is nothing to rank.
Source code in src\taters\figures\render.py
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
shade ¶
shade(hue, strength)
A color of the given hue whose lightness falls as strength rises.
Weak words come out pale and strong ones dark, so the eye reads strength twice -- in the size and in the ink -- and a strong word that had to shrink to fit still reads as strong.
Source code in src\taters\figures\render.py
138 139 140 141 142 143 144 145 146 147 148 149 | |
taters.figures.charts ¶
Small charts for training reports: line charts, scatter plots, heat tables.
A training report has three pictures a paper wants: a loss curve (a line per fold or per series over epochs), a predicted-versus-observed scatter, and a confusion matrix as a colored table. None of them needs a plotting library -- the axes, ticks, legend and points are a few hundred lines of arithmetic and Pillow, the same dependency the word clouds already use, so a headless machine draws them and a machine without Pillow gets one plain sentence instead of a traceback.
The layout arithmetic (tick choice, data-to-pixel mapping) is pure Python
and tested without a font engine; Pillow is imported inside the functions
that draw, as in :mod:taters.figures.render.
Axes
dataclass
¶
Axes(left, top, right, bottom, x_lo, x_hi, y_lo, y_hi)
The plotting rectangle in pixels and the data range it shows: the one mapping every mark in a chart goes through.
heat_cell_color ¶
heat_cell_color(value, lo, hi)
White at lo through to a saturated blue at hi; monotone in
the value, so a darker cell is always a larger number.
Source code in src\taters\figures\charts.py
294 295 296 297 298 299 300 301 302 303 304 | |
heat_table ¶
heat_table(
rows,
out_png,
*,
row_labels,
col_labels,
title="",
row_title="",
col_title="",
cell_format="{:g}",
font_path=None,
on_progress=None
)
A matrix as a colored table: each cell shaded by its value and printed with it. Built for confusion matrices (rows observed, columns predicted), where the diagonal should be the dark one.
Source code in src\taters\figures\charts.py
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 | |
line_chart ¶
line_chart(
series,
out_png,
*,
title="",
x_label="",
y_label="",
width=900,
height=560,
include_zero=False,
integer_x=True,
font_path=None,
on_progress=None
)
One line per series through its (x, y) points, in PALETTE order.
Built for loss curves: {"fold 1 train": [(1, 0.9), (2, 0.7)], "fold 1
validation": [...]}. With integer_x the x ticks fall on whole
numbers (epochs). include_zero forces the y axis to start at zero.
Source code in src\taters\figures\charts.py
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | |
nice_ticks ¶
nice_ticks(lo, hi, n=6)
Round tick positions covering [lo, hi]: about n of them, at a
step of 1, 2, 2.5 or 5 times a power of ten, the first at or below
lo and the last at or above hi. A flat range gets a unit
around its value, so a constant series still has an axis.
Source code in src\taters\figures\charts.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | |
pillow_missing_reason ¶
pillow_missing_reason()
None when Pillow can be imported, else the sentence to show instead
of a figure.
Source code in src\taters\figures\render.py
86 87 88 89 90 91 92 93 | |
scatter_chart ¶
scatter_chart(
points,
out_png,
*,
title="",
x_label="",
y_label="",
identity=True,
note="",
width=720,
height=720,
font_path=None,
on_progress=None
)
Points on square axes; with identity a dashed y = x line, so a
predicted-versus-observed plot shows at a glance how far from perfect
the predictions fall. note (r, R²) is written inside the axes.
Source code in src\taters\figures\charts.py
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | |