API reference¶
Every public name in lexsync is documented here, grouped along the path a study takes: reach a
corpus, derive the dimensions, build a pool and match it, describe a trial, counterbalance it,
report what the matching achieved, generate the experiment, and write down the provenance. The
groups are the same ones the R package's reference index
uses, so a name can be found in the same place on either site.
Everything listed under a group heading is importable straight from lexsync, with a handful of
exceptions that are noted where they appear and are reached through their own module. Guides with
worked examples are linked from the home page, and the published work that these entries
cite is listed in full on the references page.
Corpora and lexica¶
Languages are supplied through a corpus registry, so reaching a new one takes a registry entry and no code. These functions find a corpus, fetch it if it is not already local, and read a derived lexicon or a prepared item table into the frame everything else expects.
lexsync.list_corpora(registry_path=None)
¶
Source code in src/lexsync/corpora.py
33 34 35 36 37 38 39 40 41 42 43 44 | |
lexsync.fetch_corpus(name, registry_path=None, n_words=10000)
¶
Fetch a registered corpus into the cache.
If name is a language code supported by the wordfreq connector, a lexicon
is built with wordfreq. Otherwise the corpus's registered URL is downloaded,
once its scheme has been checked; the transfer lands in a sidecar file that
is renamed into the cache only after the size cap, the markup sniff and any
registered sha256 have all passed.
The file lands in :func:cache_dir. That cache persists between sessions and
the package never prunes it; one corpus may reach the 200 MB download cap, so
several of them add up. Nothing kept there is irreplaceable, so the directory
may be deleted at any time and the next call downloads the corpus again.
Source code in src/lexsync/corpora.py
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 | |
lexsync.corpora.cache_dir()
¶
Per-user cache directory for fetched corpora, created on first use.
Where :func:fetch_corpus puts a download unless told otherwise, and the only
place the package writes to without being handed a path.
The cache persists between sessions and lexsync never prunes it. A registered corpus is a delimited word list, and a download is refused above 200 MB, so a cache holding several large corpora can reach a few hundred megabytes. It holds nothing that cannot be fetched again, so it may be deleted at any time, whole or file by file, and the next call downloads afresh. The R twin documents the same contract in lexsync_cache_dir.Rd; only the location differs, since R uses tools::R_user_dir.
Source code in src/lexsync/corpora.py
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | |
lexsync.load_lexicon(path, schema, language=None)
¶
Source code in src/lexsync/querying.py
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 | |
lexsync.load_items(path, required_fields)
¶
Load a paradigm item table (prime-target pairs, sentences, …).
The table must carry an item identifier, a condition label and the
paradigm's presented fields. Field values are validated (no control
characters; bounded length) so a crafted item cannot corrupt the generated
loop table or scripts. Items are mapped to a deterministic integer set id
(byte order) so counterbalancing matches the corpus path and the two engines.
Source code in src/lexsync/querying.py
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 | |
lexsync.load_pool(path, schema, lexicon=None, language=None)
¶
Load a supplied candidate pool of words and give it the matcher's dimensions.
A researcher who already has a curated word list (from a previous study, a norming session, a colleague) should not have to dress it up as a corpus lexicon to get lexsync's matching, validation and datasheet. This reads such a list and returns something the matcher accepts.
The list needs only a word column. Length and the syllable estimate are derived
from the form. Everything else is either supplied on the list itself or looked up:
with lexicon given, the corpus dimensions (frequency above all) are joined for
those words, and a word the lexicon does not have is a hard error rather than a
NaN, because the tolerance windows drop missing rows silently and the pool would
then be smaller than the user believes it is.
The returned reference matters as much as the pool. n_density and old20
are properties of a word in its language, not among the handful of words a study
happens to use, so computing them against a 200-word supplied list would give
numbers that mean nothing. When a lexicon is given, the reference is the lexicon's
words; only without one does it fall back to the pool itself.
Returns {"pool": DataFrame, "reference": list}. Mirrors load_pool in
R_workflow/R/querying.R.
Source code in src/lexsync/querying.py
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 | |
lexsync.merge_norms(lexicon, norms, on='word', columns=None)
¶
Left-join a norm table (e.g. concreteness, age of acquisition, valence).
norms is a data frame or the path to a CSV with a word column and one or
more norm columns. This is the connector for semantic dimensions: the norm data
themselves are fetched separately (licensing varies), then merged here so the
matcher can equate on them.
The result is the lexicon itself with the norm columns appended, and the key is
looked up positionally rather than through merge. That is what makes the two
engines agree by construction, with nothing to repair afterwards, because merge
and R's merge() were measured to diverge in three ways, each of them silent:
R hoists the by column to position 1 while pandas keeps the left frame's
order, so the column order differed whenever on was not already first; R
disambiguates a colliding column name with .x/.y and pandas with
_x/_y, and either way a dimension the design matches on disappears under
a name nothing looks for; and R's merge(sort = FALSE) leaves the row order
unspecified. A positional lookup has none of those degrees of freedom. A
colliding name is now an error instead.
The key is trimmed and case-folded on both sides. Only the norm table's side
was normalised before, so a lexicon holding Dog matched nothing and the
design carried on with an all-NaN dimension. Because both engines agreed on
that wrong answer, no parity test could have caught it. The lexicon's
own spelling is preserved rather than folded in place: word is the
byte-order tie-break behind every selection, so the join must not rewrite it.
Mirrors merge_norms in R_workflow/R/querying.R.
Source code in src/lexsync/querying.py
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 | |
Lexical dimensions¶
Two dimensions arrive with the lexicon and the rest are derived from the orthographic forms. Derive them before matching, and compute the neighbourhood measures against the full lexicon rather than the pool, since a word's neighbours do not stop existing because a design excluded them.
lexsync.add_neighbourhood(df, reference=None, n_old=20)
¶
Coltheart's N (same-length, single substitution) and OLD20 for each word.
Source code in src/lexsync/querying.py
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 | |
lexsync.add_bigram_frequency(df, reference=None)
¶
Mean bigram probability (type-based, non-positional), a phonotactic-probability proxy.
For each word, the mean over its adjacent letter bigrams of the corpus bigram probability (count divided by the total bigram count). Computed from integer counts and rounded, so it is identical in the R and Python engines.
Source code in src/lexsync/querying.py
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 | |
lexsync.add_pair_overlap(df, prime='prime', target='target')
¶
Orthographic overlap between the two members of each pair.
Adds two columns. pair.lev is the Levenshtein distance between the pair's
two orthographic forms, and pair.overlap is 1 - lev / max(len), the
proportion of the longer form the two share. Overlap is the standard confound
control in a priming design: a related pair that also shares letters confounds
semantic relatedness with orthographic similarity.
Both engines return identical values, and the reasons are worth stating because
they are the constraints on any future relational dimension. The core is an
integer edit distance, and rapidfuzz's Levenshtein.distance and
stringdist(method = "lv") agree exactly, including on decomposed Unicode and
CJK, which is the same cross-library agreement add_neighbourhood already
stakes old20 on. Length is counted in code points, len() and R's
nchar() default, never in bytes. The arithmetic uses only - and /,
which IEEE-754 mandates be correctly rounded, and the result is rounded to nine
decimal places, the constant used everywhere else in the package. A degenerate
pair of two empty forms returns 0 rather than 0/0, because a NaN would be
sorted and compared and would then drop the row from one engine's control window
but not the other's.
Source code in src/lexsync/querying.py
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 | |
lexsync.count_syllables(word)
¶
An orthographic syllable estimate: the number of maximal vowel runs.
Source code in src/lexsync/querying.py
32 33 34 | |
Pools and matching¶
The pool is the set of candidates a design will consider at all, and the matcher works only on what
it is given. match_stimuli never reads a design's pool_filters, so build_pool is a required
step.
lexsync.build_pool(lexicon, filters=None)
¶
Build an experimental candidate pool by filtering a lexicon.
filters maps a column to either a two-element numeric range or a list of
permitted values. A row missing the filtered column is dropped under either
kind, and a range with a reversed or non-finite bound is an error rather than an
empty pool.
A filter naming a column the frame does not have is silently skipped, because the
same function filters lexica, supplied pools and pair tables, and those carry
different columns. The cost is that a misspelt key silently widens a
selection, so every caller that takes its filters from a design checks the names
against the frame first: run_pipeline for pool_filters,
match_stimuli for a condition's define_by, and
select_continuous_pairs for both. Mirrors build_pool in
R_workflow/R/querying.R.
Source code in src/lexsync/querying.py
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 | |
lexsync.match_stimuli(pool, design, schema, verbose=False)
¶
Match stimuli across conditions on the match_on dimensions.
Two policies govern degraded selections, each read from the design's
matching block with the schema as fallback: shortfall ("error", the
default, refuses to return fewer sets than requested; "allow" accepts the
shrink) and on_insufficient_tolerance ("relax", the default, widens an
undersupplied tolerance window to the full condition subpool and records the
relaxation in out.attrs["audit"]; "error" refuses instead).
Source code in src/lexsync/matching.py
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 | |
lexsync.resample_stimuli(pool, design, schema, n_sets, verbose=False)
¶
Produce n_sets disjoint matched item sets (a replicate column).
Each replicate is an independent, fully matched set drawn from the pool with the items of earlier replicates removed, so no item is reused. This lets a study treat its items as a random factor (running different item samples across participant groups, or showing an effect holds across samples) instead of treating them as a fixed set (Clark, 1973; Yarkoni, 2022). Deterministic: the matcher is deterministic and the used-item set evolves identically across engines.
The replicates are concatenated, which drops the attrs["audit"] entry
:func:match_stimuli uses to report a relaxed tolerance window, so a relaxation
inside a replicate reaches the console under verbose but not the run log or
the datasheet.
Source code in src/lexsync/matching.py
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 | |
Continuous designs¶
Dichotomising a continuous predictor costs power and can introduce selection artefacts. A design may instead span the predictor evenly while holding its controls near-constant, and be analysed by regression or a mixed model.
lexsync.select_continuous_stimuli(pool, design, schema, verbose=False, key='word', label='continuous', renumber_sets=True)
¶
Select a set that spans a continuous predictor, holding controls constant.
Instead of dichotomising the predictor into conditions and matching, items are chosen to cover the predictor's range evenly while the control dimensions are held within a tolerance band, so they stay near-constant and near-uncorrelated with the predictor. The set is analysed by regression / mixed models rather than by between-condition contrasts, which avoids the loss of power and the selection artefacts of matched dichotomies (Kuperman, 2015; Liben-Nowell et al., 2019).
Two deterministic passes reuse the matcher's even-spread primitive, so the R and Python engines select byte-identical stimuli: an even spread over the predictor defines a tolerance window on each control; the pool is filtered to that window; a second even spread over the filtered pool is the selection. There is no per-item matching and no random number generator.
The design is checked before anything is selected, so a design that cannot be
honoured raises ValueError outright. continuous.controls must be non-empty
and must not name the predictor, match_on must name exactly the same dimensions
as continuous.controls, every dimension named and the key column must be
present in the pool, no tolerance_k may be negative, and the pool must not be
empty.
Returns the selected stimuli. Unless label is None the condition
column is set to it, continuous by default, and unless renumber_sets is
False the set column is renumbered 1..n.
Source code in src/lexsync/matching.py
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 | |
lexsync.match_report_continuous(stimuli, predictor, controls, schema)
¶
Realised-control report for a continuous design.
Returns the same {descriptives, comparisons} shape as :func:match_report,
so the pipeline and datasheet stay uniform, but the comparisons describe a
continuous predictor instead of a between-condition contrast: the predictor's
realised span and, for each control, its Pearson correlation with the predictor
(near zero when the control is held constant). The set is meant for regression /
mixed-model analysis, not equivalence tests.
Source code in src/lexsync/validation.py
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | |
Pseudoword generation¶
Non-words are generated deterministically, with no sampling anywhere, preserving length exactly and keeping every letter bigram attested in the corpus. Both methods select byte-identical stimuli in the R and Python engines.
lexsync.generate_pseudowords(base_words, reference_words)
¶
A length-matched pseudoword for each base word.
Base words are processed in byte order so the used set evolves identically
across engines. Returns a frame with base_word and pseudoword.
Source code in src/lexsync/generation.py
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |
lexsync.make_pseudoword(word, bigrams, lexicon, used)
¶
The most bigram-plausible legal non-word at the smallest edit distance.
Searches single-letter substitutions first, then two-letter substitutions; candidates are ranked by summed bigram frequency with a byte-order tie-break, so the choice is deterministic and identical across engines.
Source code in src/lexsync/generation.py
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 | |
lexsync.build_lexdec_stimuli(pool, n, reference_words=None, method='letter_substitution')
¶
Assemble a word-vs-pseudoword lexical-decision set from a candidate pool.
Real words are drawn by an even spread across the byte-ordered pool (the same
deterministic device as the matcher's anchor), then a length-matched
pseudoword is generated for each. The pool is first filtered to lower-case
a-z forms, the only ones the pseudoword generators are defined for, so the
eligible pool can be smaller than the request; the pipeline's shortfall
policy then decides whether that errors. reference_words (the full lexicon)
supplies the bigram statistics and the real-word list a pseudoword must avoid;
it falls back to the pool when not given. The presented string is the
target column; conditions are word and pseudoword and set
pairs them.
Source code in src/lexsync/generation.py
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 | |
Paradigms and trial events¶
A trial is a list of event dictionaries rather than backend code, which is what lets one engine serve
five paradigms and three presentation targets. A design either names a paradigm and inherits its
event sequence, or supplies its own events.
lexsync.PARADIGMS = {'factorial': {'stimulus_fields': ['word'], 'counterbalance': 'factorial', 'events': [{'type': 'fixation', 'content': '+', 'duration_frames': _FIX}, {'type': 'text', 'content': '{word}', 'duration_frames': _WORD, 'trigger': 'condition', 'onset_locked': True}, {'type': 'response', 'keys': ['left', 'right'], 'timeout_ms': 2000}, {'type': 'blank', 'duration_frames': _ISI}]}, 'lexical_decision': {'stimulus_fields': ['target'], 'counterbalance': 'factorial', 'events': [{'type': 'fixation', 'content': '+', 'duration_frames': _FIX}, {'type': 'text', 'content': '{target}', 'duration_frames': _WORD, 'trigger': 'condition', 'onset_locked': True}, {'type': 'response', 'keys': ['left', 'right'], 'timeout_ms': 2000}, {'type': 'blank', 'duration_frames': _ISI}]}, 'priming': {'stimulus_fields': ['prime', 'target'], 'counterbalance': 'latin_square_target', 'events': [{'type': 'fixation', 'content': '+', 'duration_frames': _FIX}, {'type': 'text', 'content': '{prime}', 'duration_frames': 3, 'trigger': 20, 'onset_locked': True}, {'type': 'mask', 'content': '#####', 'duration_frames': 2}, {'type': 'text', 'content': '{target}', 'duration_frames': _WORD, 'trigger': 'condition', 'onset_locked': True}, {'type': 'response', 'keys': ['left', 'right'], 'timeout_ms': 2000}, {'type': 'blank', 'duration_frames': _ISI}]}, 'categorisation': {'stimulus_fields': ['target', 'category', 'answer'], 'counterbalance': 'latin_square_target', 'events': [{'type': 'fixation', 'content': '+', 'duration_ms': 500}, {'type': 'text', 'content': '{category}', 'duration_ms': 750}, {'type': 'text', 'content': '{target}', 'duration_ms': 800, 'trigger': 'condition', 'onset_locked': True}, {'type': 'response', 'keys': ['f', 'j'], 'timeout_ms': 2500}, {'type': 'blank', 'duration_ms': 250}]}, 'self_paced_reading': {'stimulus_fields': ['sentence', 'question'], 'counterbalance': 'latin_square_target', 'events': [{'type': 'fixation', 'content': '+', 'duration_frames': _FIX}, {'type': 'region_by_region', 'content': '{sentence}', 'advance': 'space', 'critical_region_trigger': 'condition'}, {'type': 'question', 'content': '{question}', 'keys': ['f', 'j'], 'timeout_ms': 5000}, {'type': 'blank', 'duration_frames': _ISI}]}}
module-attribute
¶
lexsync.resolve_events(design)
¶
Return the event list for a design: explicit events or paradigm default.
Source code in src/lexsync/paradigms.py
130 131 132 133 134 135 | |
lexsync.resolve_trial_timing(stimuli, design, schema)
¶
Realise per-trial event durations onto the stimuli table.
An event may declare a duration that varies from trial to trial, either read from an item column or drawn from a range. A drawn value is a pure function of the keyed hash, so both engines realise the same milliseconds, and it is written into the stimuli table as well as the generated script, because timing that varies is a variable the analysis needs, not presentation detail.
Source code in src/lexsync/scripting.py
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 | |
lexsync.required_fields(design)
¶
Trial fields a design needs present in its items (paradigm + events).
Source code in src/lexsync/paradigms.py
156 157 158 159 160 161 162 163 | |
Counterbalancing¶
Two recipes are available, and the paradigm chooses between them. Trial order comes from a seeded,
keyed-hash shuffle, a pure function of the design with no generator behind it, so the same seed
gives the same order in both engines. balance_lists is the optional search for a list assignment
whose lists are equated on the item dimensions, where the plain deal goes by set rank.
lexsync.counterbalance(stimuli, design, schema, list_of_set=None)
¶
Source code in src/lexsync/counterbalancing.py
314 315 316 317 318 319 320 321 322 | |
lexsync.balance_lists(stimuli, design, schema)
¶
Assign item sets to lists so the lists match on the item dimensions.
The factorial recipe's default deal is by set rank, which balances nothing. This searches instead for an assignment whose lists have near-equal totals on each declared dimension, by steepest-descent pairwise swaps between lists. List sizes are preserved, because a swap exchanges one set for another.
The search is deterministic and identical in the R and Python engines: the
objective is all-integer (see the notes in this module), the descent takes the
single best swap each pass, and ties are broken by the seeded keyed hash rather
than by position, so no list is favoured by being numbered first. Because the cost
is a non-negative integer that strictly decreases, the search terminates;
max_passes bounds it anyway and the report says whether the bound was reached.
Five situations raise ValueError rather than being answered with an assignment
that would mislead. A Latin-square design is refused because every item already
appears in every list there, so there is nothing left to equate, and fewer than two
lists leaves no pair of lists to exchange sets between. A design with no resolvable
balance dimension is refused, as is one naming a dimension the stimuli do not carry,
and the message names the columns. The last refusal is arithmetic: the search stops
if the integer objective would leave the range a double represents exactly, since
past that point the two engines could disagree.
Returns {"list_of_set": {set: list}, "report": {...}}.
Source code in src/lexsync/counterbalancing.py
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 | |
lexsync.participant_table(factors, n_participants)
¶
Source code in src/lexsync/counterbalancing.py
390 391 392 393 394 395 396 397 398 399 | |
Validation and equivalence¶
A matched design claims that its controls do not differ, and a non-significant test of difference does not establish that. These functions report the realised control instead: the standardised difference with its interval, an equivalence test against a declared bound, and the variance ratio that a mean-based statistic would miss.
lexsync.match_report(stimuli, dims, schema)
¶
Build the full match-quality report: descriptives and comparisons.
Every comparison is against the first condition in order of appearance, so a
design with a single condition has nothing to compare and comparisons comes
back with its columns and no rows.
Source code in src/lexsync/validation.py
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 | |
lexsync.describe_stimuli(stimuli, dims, by='condition')
¶
Source code in src/lexsync/validation.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | |
lexsync.balance_check(stimuli, columns)
¶
Source code in src/lexsync/validation.py
142 143 144 145 146 147 148 149 150 151 152 153 | |
lexsync.variance_ratio(cond, ref)
¶
Ratio of a condition's variance to the reference's: a distributional balance check that complements the mean-based Cohen's d and TOST.
Two conditions can share a mean yet differ in spread and still confound, which
a mean-based statistic misses (Armstrong, Watson & Plaut, 2012; Austin, 2009).
A ratio near 1 is balanced; a common heuristic flags ratios outside about
[0.5, 2] as unequal spread. Returns None when a variance is undefined.
Source code in src/lexsync/validation.py
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | |
lexsync.cohens_d(x, y)
¶
Source code in src/lexsync/validation.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 | |
lexsync.cohens_d_ci(x, y, alpha=0.05)
¶
Cohen's d with a confidence interval, complementing the TOST verdict.
The interval is the (1 - 2 * alpha) confidence interval for the
standardised mean difference; for alpha = 0.05 this is the 90% interval
that corresponds exactly to a two one-sided tests (TOST) decision at the .05
level (Lakens, 2017). Reporting the interval, rather than only a binary
"equivalent / not" verdict, makes the realised imbalance and its sampling
uncertainty explicit and keeps the dependence on the number of items visible
rather than hidden: with few items the interval is wide, so a small point
estimate cannot be over-read as evidence of a small true difference
(Sassenhagen & Alday, 2016). The upper limit of the interval on |d| is the
largest imbalance still consistent with the stimuli.
Source code in src/lexsync/validation.py
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 | |
lexsync.tost_equiv(x, y, bound_d=0.5, alpha=0.05)
¶
Source code in src/lexsync/validation.py
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | |
Experiment generation¶
All three targets are rendered from the same event list. Generation imports neither PsychoPy nor
pyserial, so it needs no laboratory hardware. The experiment extra is for running the result.
assign_triggers is reached as lexsync.scripting.assign_triggers, and export_experiments calls
it for you.
lexsync.export_experiments(stimuli, design, schema, outdir, base=None)
¶
Source code in src/lexsync/scripting.py
783 784 785 786 787 | |
lexsync.export_psychopy(stimuli, design, schema, outdir, base=None)
¶
Source code in src/lexsync/scripting.py
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 | |
lexsync.export_opensesame(stimuli, design, schema, outdir, base=None)
¶
Source code in src/lexsync/scripting.py
647 648 649 650 651 652 653 654 655 656 | |
lexsync.export_jspsych(stimuli, design, schema, outdir, base=None)
¶
A browser-runnable jsPsych experiment from the event list.
The same rendered events and the trial data are embedded in one HTML file, so anyone can reproduce the exact procedure online from the same materials. The jsPsych library and stylesheet are loaded from a CDN, so the machine running the file needs an internet connection; the trial data are embedded and the responses are saved locally, so no server is required either to run it or to collect them. Onset triggers are recorded in each trial's data (a browser cannot drive a parallel port); online EEG synchronisation needs WebSerial/LSL or a photodiode.
Source code in src/lexsync/scripting.py
747 748 749 750 751 752 753 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 | |
lexsync.scripting.assign_triggers(stimuli)
¶
A per-condition marker and a per-item marker, both 0-255 EEG codes.
The item range holds 200 codes (an 8-bit-port constraint), so past 200 sets the codes wrap and repeat, and a runtime notice says so.
Source code in src/lexsync/scripting.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | |
Materials datasheet¶
The datasheet is the provenance record that travels with a stimulus set: where the items came from,
how they were selected, the realised control, the candidate-pool sizes, the checksums and the
versions. run_pipeline builds and writes one for every run.
lexsync.build_datasheet(design, schema, report, stimuli, source_path, artifacts, seed, engine='python', candidate_pool=None, norms=None, balance=None, blocks=None, design_path=None, schema_path=None, selection_audit=None, neighbourhood_reference=None)
¶
Assemble the datasheet dictionary from the pipeline's objects.
candidate_pool (optional) is a list of {"condition", "n_candidates"}
recording how many items satisfied each condition's window before matching,
the size of the discretionary pool the selection drew from, reported so that
item-selection bias is auditable (Forster, 2000; Simmons et al., 2011).
norms (optional) is a list of norm-table provenance records from the design's
norms: block. Each names a file, its sha256, the join key and the per-column
coverage. Recorded because a norm table can supply the very variable a design
manipulates, so a record that omitted it would describe a selection over columns
of unstated origin.
balance (optional) is the balance-optimiser report from balance_lists.
Recorded because it decides which items each participant sees.
design_path / schema_path (optional) are the design and schema files the
run read; when given, their sha256 checksums complete the reproducibility
record, because those two files decide everything the seed does not.
selection_audit (optional) is the matcher's audit record; its
window_relaxations entries are recorded because a relaxed window changes
what "matched" means for that condition.
neighbourhood_reference (optional) records the lexicon the neighbourhood
dimensions were computed against ({"source", "n_words", "sha256"}),
verbatim.
Source code in src/lexsync/datasheet.py
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 | |
lexsync.write_datasheet(ds, json_path, md_path)
¶
Source code in src/lexsync/datasheet.py
743 744 745 746 747 748 749 750 751 752 | |
lexsync.methods_paragraph(ds)
¶
Source code in src/lexsync/datasheet.py
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 | |
Pipeline and logging¶
run_pipeline is the orchestrator behind lexsync run, and run_all loops it over a directory of
designs. The logging functions are reached as lexsync.logging.*, which does not shadow the standard
library's logging for absolute imports.
lexsync.run_pipeline
¶
The orchestrator. Mirrors R_workflow/R/run_pipeline.R.
For each design it obtains stimuli from the configured item source (a corpus matched on lexical dimensions, generated pseudowords for lexical decision, or an item table for priming and self-paced reading), then counterbalances, writes any match-quality report and the run log, and exports the PsychoPy and OpenSesame scripts from the design's trial-event sequence. run_all loops over every design.
lexsync.run_all(config_dir='config', schema_path=None, outdir='output', verbose=True)
¶
Source code in src/lexsync/run_pipeline.py
405 406 407 408 409 410 411 412 413 414 415 416 417 418 | |
lexsync.logging.new_run_log(name, meta=None)
¶
Source code in src/lexsync/logging.py
21 22 23 24 25 26 27 28 | |
lexsync.logging.log_step(log, message, data=None)
¶
Source code in src/lexsync/logging.py
52 53 54 55 56 | |
lexsync.logging.log_artefact(log, path, rows=None)
¶
Source code in src/lexsync/logging.py
59 60 61 | |
lexsync.logging.write_run_log(log, md_path, jsonl_path=None)
¶
Source code in src/lexsync/logging.py
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 | |