occu_choice_pf_2 0.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. occu_choice_pf_2-0.6.1/.gitignore +19 -0
  2. occu_choice_pf_2-0.6.1/CHANGELOG.md +437 -0
  3. occu_choice_pf_2-0.6.1/LICENSE +21 -0
  4. occu_choice_pf_2-0.6.1/PKG-INFO +285 -0
  5. occu_choice_pf_2-0.6.1/README.md +268 -0
  6. occu_choice_pf_2-0.6.1/pyproject.toml +45 -0
  7. occu_choice_pf_2-0.6.1/src/occu_choice_pf/__init__.py +19 -0
  8. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/regressions/code/run_regressions.py +63 -0
  9. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/regressions/logs/.gitkeep +0 -0
  10. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/regressions/output/tab_ols.tex +24 -0
  11. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/regressions/output/tab_poisson.tex +24 -0
  12. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/regressions/output/tab_poisson_active.tex +17 -0
  13. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/regressions/output/tab_poisson_quadratic.tex +26 -0
  14. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/code/run_underestimation_gap.py +102 -0
  15. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/logs/.gitkeep +0 -0
  16. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_gaps.tex +15 -0
  17. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_gaps_gender.tex +13 -0
  18. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_gaps_gender_end.tex +14 -0
  19. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_gaps_parents.tex +16 -0
  20. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_gaps_se.tex +25 -0
  21. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_gaps_zero.tex +15 -0
  22. occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_log_gaps.tex +15 -0
  23. occu_choice_pf_2-0.6.1/src/occu_choice_pf/functions/labels.py +119 -0
  24. occu_choice_pf_2-0.6.1/src/occu_choice_pf/functions/regressions.py +460 -0
  25. occu_choice_pf_2-0.6.1/src/occu_choice_pf/functions/underestimation_gap.py +517 -0
  26. occu_choice_pf_2-0.6.1/src/occu_choice_pf/functions/utils.py +123 -0
  27. occu_choice_pf_2-0.6.1/src/occu_choice_pf/simulation/__init__.py +1 -0
  28. occu_choice_pf_2-0.6.1/src/occu_choice_pf/simulation/regressions.py +122 -0
  29. occu_choice_pf_2-0.6.1/src/occu_choice_pf/simulation/underestimation_gap.py +152 -0
  30. occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/__init__.py +0 -0
  31. occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/conftest.py +30 -0
  32. occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/test_examples.py +84 -0
  33. occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/test_regressions.py +438 -0
  34. occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/test_regressions_data.py +117 -0
  35. occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/test_run_tests.py +47 -0
  36. occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/test_underestimation_gap.py +602 -0
  37. occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/test_underestimation_gap_data.py +135 -0
@@ -0,0 +1,19 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .ipynb_checkpoints/
6
+ dist/
7
+ build/
8
+ *.egg-info/
9
+
10
+ # example logs; the tables in examples/*/output are committed
11
+ *.log
12
+
13
+ # fake data drawn by simulation/<module>.py
14
+ src/occu_choice_pf/simulation/data/
15
+
16
+ # LaTeX build of tex/examples.tex
17
+ tex/out_dir/
18
+
19
+ misc/
@@ -0,0 +1,437 @@
1
+ # Changelog
2
+
3
+ The server pins an exact version (`pip install occu-choice-pf==<version>`),
4
+ so a new release changes nothing there until the pin does. Every change to
5
+ how a function is called is listed under **Update a server script**, with the
6
+ old and the new call.
7
+
8
+ ## 0.6.1 (2026-10-08)
9
+
10
+ `underestimation_gap.gap_table`'s `share_column` also takes a list of both
11
+ share columns, `['underestimator', 'zero_gap']`. The package is now also
12
+ published as `occu_choice_pf_2`, a third name for the same code
13
+ (`.github/workflows/publish_2.yml`), so that three versions can be installed
14
+ on the server within 24 hours. No call changes.
15
+
16
+ ### Update a server script
17
+
18
+ - `pip install occu-choice-pf==0.6.1` (or `pip install occu_choice_pf_1==0.6.1`,
19
+ or `pip install occu_choice_pf_2==0.6.1`).
20
+ - Calls of 0.6.0 are unchanged, and give the same tables.
21
+
22
+ ### Added
23
+
24
+ - `underestimation_gap.gap_table(..., share_column=['underestimator', 'zero_gap'])`:
25
+ both share columns, 7 and 8 in the order of the list, each with its
26
+ standard errors and p-values; the counts follow in 9 and 10.
27
+
28
+ ## 0.6.0 (2026-10-08)
29
+
30
+ `underestimation_gap.gap_table` takes `share_column` for column 7:
31
+ `'underestimator'` (the default), `'zero_gap'`, the new share of job pairs
32
+ with no perceived wage gap (`d_Vij == 0`), or `None`. It replaces
33
+ `underestimator`, a change of call.
34
+
35
+ ### Update a server script
36
+
37
+ - `pip install occu-choice-pf==0.6.0` (or `pip install occu_choice_pf_1==0.6.0`).
38
+ - `gap_table`'s `underestimator` is gone; `share_column` says what column 7
39
+ shows, its column and header those of `labels`:
40
+ - old: `ug.gap_table(rows, underestimator=None)`
41
+ new: `ug.gap_table(rows, share_column=None)`
42
+ - old: `ug.gap_table(rows, underestimator={'u': 'Header'})`
43
+ new: `ug.gap_table({label: df.with_columns(d_v_vi_max_notmaxV_rate=c.u) for label, df in rows.items()})`;
44
+ the header is `labels.UNDERESTIMATOR`'s
45
+ - a call without `underestimator` is unchanged, and gives the same table.
46
+ - `run_tests` checks `d_Vij` too, the perceived wage gap in levels: a
47
+ number, exactly 0 for equal perceived wages, minus itself in the other
48
+ order of the pair, and a rate `d_Vj_Vij_rate` of 1 where it is 0.
49
+
50
+ ### Added
51
+
52
+ - `underestimation_gap.gap_table(..., share_column='zero_gap')`: column 7
53
+ is the share of job pairs whose perceived wage gap is 0 (`d_Vij == 0`),
54
+ among those where it is not missing, in place of the share of
55
+ underestimators; the header is `labels.ZERO_GAP`, with
56
+ $\Delta \widetilde{V_{i,j,j'}}=0$. Pairs with `d_Vij` missing are logged.
57
+ `share_column='underestimator'` (the default) is the share of
58
+ underestimators, and `share_column=None` no column 7.
59
+ - The simulated frame has `d_Vij`, and 10% of persons perceive two job
60
+ types as paying the same; the example tables are rerun on it, and
61
+ `tab_estimation_gaps_zero` (in `tex/examples.tex`) shows the new column.
62
+
63
+ ### Changed
64
+
65
+ - `gap_table(..., underestimator={column: header} | None)` is now
66
+ `share_column='underestimator' | 'zero_gap' | None`; another column goes
67
+ in under the default name, as for the other columns.
68
+
69
+ ## 0.5.2 (2026-10-08)
70
+
71
+ `underestimation_gap.gap_table` compares rows that are not next to each
72
+ other, a row with several others; `p_at_end=True` puts the p-values in the
73
+ last rows of the table, and `underestimator=None` leaves out the column of
74
+ the share of underestimators. The row numbers of a table of ten rows or
75
+ more are right-aligned, so that the labels line up.
76
+
77
+ ### Update a server script
78
+
79
+ - `pip install occu-choice-pf==0.5.2` (or `pip install occu_choice_pf_1==0.5.2`).
80
+ - Calls of 0.5.1 are unchanged, and give the same tables but for the row
81
+ numbers of a table of ten rows or more (see Changed).
82
+
83
+ ### Added
84
+
85
+ - `underestimation_gap.gap_table`: `compare` takes rows anywhere in `rows`,
86
+ a row in any number of pairs (they had to be next to each other, a row in
87
+ one pair at most). The rows from the first to the last of a pair are a
88
+ block, joined with the blocks it overlaps; the p-values of a block go
89
+ under it, in the order of `compare`, followed by a line. Comparing a row
90
+ with itself, or two rows twice, raises.
91
+ - `gap_table(..., p_at_end=True)`: all the p-values in the last rows of the
92
+ table, under a line, in the order of `compare`.
93
+ - `gap_table(..., underestimator=None)`: the table without column 7, the
94
+ share of underestimators, and its standard error and p-value; the frames
95
+ need not have the column.
96
+ - The example `tab_estimation_gaps_gender` compares men with women and with
97
+ women reweighted to men, rows (1)-(2) and (1)-(3), and
98
+ `tab_estimation_gaps_gender_end` is the same with `p_at_end=True`; both are
99
+ in `tex/examples.tex`.
100
+
101
+ ### Changed
102
+
103
+ - `underestimation_gap.gap_table` right-aligns the row numbers in a table
104
+ of ten rows or more, (1) to (9) padded by `\phantom{0}`, so that the row
105
+ labels line up with that of (10). Calls are unchanged; the example tables
106
+ are rerun.
107
+
108
+ ## 0.5.1 (2026-10-07)
109
+
110
+ A new module, `underestimation_gap`, with Table 3 of the paper, the
111
+ distribution of wage gap misperceptions by subsample:
112
+ `gap_table(rows)` takes one frame of the extra-long data per table row.
113
+ `bootstrap=B` adds standard errors from B draws of persons;
114
+ `compare=[(label, label)]` puts the p-value of the difference of two rows,
115
+ next to each other, under them, followed by a line; `ps_group` reweights
116
+ one group to the other by a propensity score model estimated anew in each
117
+ draw, as Table C1 of the parental transmission paper. The rows are
118
+ numbered, and a p-value row names the rows it compares.
119
+
120
+ ### Update a server script
121
+
122
+ - `pip install occu-choice-pf==0.5.1` (or `pip install occu_choice_pf_1==0.5.1`).
123
+ - Calls of 0.5.0 are unchanged.
124
+ - `run_tests` takes the extra-long frame too, and skips the data tests of
125
+ a frame it is not given: `occu_choice_pf.run_tests({'long': long, 'elong': elong})`.
126
+
127
+ ### Added
128
+
129
+ - `underestimation_gap.gap_table(rows, weights=None, bootstrap=0, compare=(), ps_group=None, ...)`:
130
+ Table 3 of the paper, its cells as in the author's script
131
+ `underestimation_gap.py`. `rows` is `{label: frame}`, each frame the
132
+ extra-long frame (`pnr` x `type_sizerank_job` x `type_sizerank_jobj`) or
133
+ a subsample of it made with polars; `weights={label: column}` weights a
134
+ row. Labels and headers break lines with `\n`. The columns are
135
+ `labels.GAP_RATE`, `GAP_LOG` and `UNDERESTIMATOR` (`{column: header}`).
136
+ Two departures from the script: persons whose underestimator value is
137
+ missing are left out of that share, with a warning in the log (the script
138
+ counted them as not underestimators); and the job pairs are unique
139
+ (person, {j, j'}), so (j, j') and (j', j) are one pair (the script
140
+ counted unique (person, j)). A row's frame with NaN raises, as a polars
141
+ filter such as `c.d_vj > 0.05` keeps NaN: make it missing in prep with
142
+ `elong.with_columns(pl.col(pl.Float64).fill_nan(None))`.
143
+ - `row_numbers=True` numbers the rows, (1), (2), ..., and a p-value row
144
+ reads `$p$-value of difference (1)-(2)`; `row_numbers=False` leaves both
145
+ out, as Table 3 in the paper.
146
+ - `bootstrap=B`: standard errors under each row, from B draws of persons
147
+ with replacement, the same draws for every row, drawn with `seed`; the estimate rows are
148
+ pulled up to them with `\vspace{-.4em}`, as in the paper's regression
149
+ tables.
150
+ - `compare=[(label, label), ...]`: the bootstrap p-value of no difference
151
+ between two rows next to each other, in a row under them (twice the
152
+ smaller share of draws on either side of 0, as the script for Table C1),
153
+ then an `\hline`, except under the last row.
154
+ - `ps_group`, `ps_xs`, `ps_fes`, `ps_limit`: adds the weights `ps_weight`
155
+ (each group reweighted to the other) and `ps_support` (1 for persons with
156
+ a weight) from a logit of the 0/1 `ps_group` on `ps_xs` with fixed effects
157
+ `ps_fes` (default `disced_6d`), estimated anew in each draw. As
158
+ `compute_weights` in the author's `p_scores_funcs`, with pyfixest's
159
+ `feglm` in place of fixest in R; with one fixed effect and no `ps_xs` the
160
+ model is saturated and the score is the group's share in the cell,
161
+ computed without pyfixest. `underestimation_gap.ps_weights` returns the
162
+ weights themselves.
163
+ - `labels.JOBJ`, `GAP_RATE`, `GAP_LOG`, `UNDERESTIMATOR`, `P_LABEL`,
164
+ `PS_FES`, `PS_LIMIT`.
165
+ - `simulation/underestimation_gap.py` (fake extra-long frame), the example
166
+ `examples/underestimation_gap`, tests and data tests
167
+ (`tests/test_underestimation_gap_data.py`: no NaN, one row per pair, each
168
+ pair in both orders, a person-level underestimator and major, finite gaps).
169
+
170
+ ### Changed
171
+
172
+ - The data tests of a frame not given to `run_tests` are skipped, rather
173
+ than failing to load it.
174
+ - `tex/examples.tex` sets the rows of a tabular 1.25 apart, as the paper.
175
+
176
+ ## 0.5.0 (2026-10-05)
177
+
178
+ A term of `cols_control` wrapped in `reg.plain()` enters its column as it
179
+ is, not times `x`, so a fixed effect can go in one column only. The labels
180
+ of the education dummies say "Major".
181
+
182
+ ### Update a server script
183
+
184
+ - `pip install occu-choice-pf==0.5.0` (or `pip install occu_choice_pf_1==0.5.0`).
185
+ - Calls of 0.4.0 are unchanged. The check-mark rows of `labels.EDUC_FIELD`
186
+ and `labels.DISCED` change (see Changed).
187
+
188
+ ### Added
189
+
190
+ - `regressions.plain(*terms)`: terms of `cols_control` that enter their
191
+ table column as they are, not times `x`. An `i.<column>` among them is a
192
+ fixed effect absorbed in that column only, e.g. a job-type fixed effect
193
+ in one column: `cols_control=[{}, {'Job type FE': reg.plain('i.' + JOB)}]`.
194
+ A list of terms may mix `plain()` with terms times `x`.
195
+ - A table column with a fixed effect twice raises.
196
+ - The example fits a second column with the job-type fixed effect from
197
+ `reg.plain()`.
198
+
199
+ ### Changed
200
+
201
+ - The labels of `labels.EDUC_FIELD` and `labels.DISCED` are
202
+ `Major field × ...` and `Major × ...` (were `Education field × ...` and
203
+ `Education (DISCED) × ...`), and so are their check-mark rows in the
204
+ default table. The example tables are rerun with them.
205
+
206
+ ## 0.4.0 (2026-10-01)
207
+
208
+ The table is `reg_control_table`, and the caller lists its columns:
209
+ `cols_control` takes the controls of each table column, and
210
+ `common_control` those of every column, in place of `base`, `powers`,
211
+ `controls`, `dummies` and `fe`. A term may be `i.<column>` (dummies, or in
212
+ `common_control` a fixed effect absorbed by pyfixest) or a polars
213
+ expression. `verbose=True` logs every coefficient.
214
+
215
+ ### Update a server script
216
+
217
+ - `pip install occu-choice-pf==0.4.0` (or `pip install occu_choice_pf_1==0.4.0`).
218
+ - `regressions.regression_table` is `regressions.reg_control_table`, and
219
+ `regressions.estimate` is `regressions.reg_control_fits`. With the default
220
+ columns the tables are the same, except where `educ_field` or `disced_6d`
221
+ is null (see Changed):
222
+
223
+ ```python
224
+ # 0.3.0
225
+ save_tex(reg.regression_table(long, method='ols'), out / 'tab_ols.tex')
226
+ fits = reg.estimate(long)
227
+ # 0.4.0
228
+ save_tex(reg.reg_control_table(long, method='ols'), out / 'tab_ols.tex')
229
+ fits = reg.reg_control_fits(long)
230
+ ```
231
+
232
+ - `powers`, `controls` and `dummies` become `cols_control`, a list with the
233
+ controls of each table column, every term times `vj_vij_job`. A power of
234
+ `vj_vij_job` is `vj_vij_job` to one less times `vj_vij_job`, and a set of
235
+ dummies is `i.<column>`:
236
+
237
+ ```python
238
+ # 0.3.0
239
+ reg.regression_table(long, powers={'Squared': [2]},
240
+ controls={'Age, gender': ['age', 'female']},
241
+ dummies={'DISCED': 'disced_6d'})
242
+ # 0.4.0
243
+ sq, own = {'Squared': [c.vj_vij_job]}, {'Age, gender': ['age', 'female']}
244
+ reg.reg_control_table(long, cols_control=[{}, sq, {**sq, **own},
245
+ {**sq, **own, 'DISCED': ['i.disced_6d']}])
246
+ ```
247
+
248
+ - `base` and `fe` become `common_control`, `{label: terms}`, where
249
+ `i.<column>` is a fixed effect. To report `vj_vij_job` rather than check it:
250
+
251
+ ```python
252
+ # 0.3.0
253
+ reg.regression_table(long, coefs={**COEFS, **BASE}, base={})
254
+ # 0.4.0
255
+ reg.reg_control_table(long, coefs={**COEFS, 'vj_vij_job': '$' + MIS[1:-1] + '$'},
256
+ common_control={'Person FE': ['i.pnr']})
257
+ ```
258
+
259
+ - In `labels`, `BASE`, `FE`, `CONTROLS` and `DUMMIES` are gone, and `POWERS`
260
+ holds the terms `[c(X), c(X) ** 2]` rather than the powers `[2, 3]`.
261
+ `COMMON_CONTROL`, `COLS_CONTROL` and the blocks `PRE_CONTROLS`,
262
+ `EDUC_FIELD` and `DISCED` are new.
263
+
264
+ ### Added
265
+
266
+ - `cols_control`: one table column per entry, `{label: terms}`, each term
267
+ times `x`; `{}` is a column with the common controls only. A label's
268
+ check-mark row is checked in each column that has it.
269
+ - `common_control`: the controls of every column, as they are. Several
270
+ fixed effects (`i.<column>`) are absorbed together, e.g. a job-type fixed
271
+ effect next to the person's; with none, the regression has a constant.
272
+ The check for separated rows takes them all.
273
+ - A term is a column, `i.<column>` (as Stata's `i.`) or a polars expression,
274
+ named by its `.alias()` or else written out (`c.vj_vij_job ** 2` is
275
+ `vj_vij_job_pow_2`). In `cols_control`, `i.age` is Stata's
276
+ `i.age#c.vj_vij_job`; the column may be a string.
277
+ - In `i.<column>`, as dummies or fixed effects, null and NaN are one value
278
+ of their own, `missing`, with its own dummy or group, so their rows stay.
279
+ - `person`: the column counted in the Individuals row and held to `min_n`
280
+ (was the fixed effect's).
281
+ - `verbose=True` in `reg_control_table` and `reg_control_fits` logs every
282
+ coefficient of every column with its standard error and p-value, and
283
+ names the terms dropped as collinear. These are not held to `min_n`.
284
+ - The example uses `i.age` for active job seekers, `verbose=True` in the
285
+ quadratic table, and a job-type fixed effect next to the person's.
286
+ - Data test: the pre-treatment controls have no missing values (null or
287
+ NaN), so the columns of a panel rest on the same rows but for the Poisson
288
+ drops.
289
+ - Data test: pyfixest's estimation sample (`fit._data`), whose persons the
290
+ Individuals row and `min_n` count, has the fit's *N* rows; it replaces
291
+ the check that a fit counts no more rows or persons than the frame. The
292
+ Individuals row is also checked against a count made in polars.
293
+
294
+ ### Changed
295
+
296
+ - A table with one outcome writes its title over all the columns, above
297
+ the column numbers, as Table A8 of the paper (it was a panel row below
298
+ them). Several outcomes keep their lettered panels.
299
+ - A null in a column made into dummies keeps its row, with the dummy of
300
+ the value `missing`, which NaN joins (in 0.3.0 a null dropped the row
301
+ from that table column, and NaN was a value of its own). In the default
302
+ table this changes columns (4) and (5) where `educ_field` or `disced_6d`
303
+ is null; to drop those rows as before, filter them out before the call.
304
+ - The dummies leave out the numerically lowest value of an integer column
305
+ (they left out the lowest as a string: 10 rather than 9). The other
306
+ coefficients are unchanged, as are the default tables, whose dummy
307
+ columns are strings.
308
+ - `labels.PRETREATMENT` uses the server's names of the expected wages:
309
+ `Vij_1k_1`, `Vij_2k_2`, `Vij_3k_3`, `Vij5_1k_1`, `Vij5_2k_2` and
310
+ `Vij5_3k_3` (were `Vij_1` … `Vij5_3`).
311
+ - The check for separated rows differences out the fixed effect with the
312
+ most groups, whatever the order of `common_control` (it took the first,
313
+ and made a person fixed effect listed second into a dummy per person).
314
+ The rows found are the same.
315
+ - `regressions.METHODS` is `{method: pyfixest function}` (was a tuple of
316
+ the names).
317
+ - The simulated `vj_vij_job` is NaN in half of its missing rows and null in
318
+ the other half (was null in all), so the tests meet both. The example
319
+ tables are unchanged.
320
+ - The README leaves the arguments, what drops out of a column and the input
321
+ checks to the docstrings, which now list the checks.
322
+
323
+ ### Fixed
324
+
325
+ - In `cols_control`, an `i.<column>` with two values whose dummies get one
326
+ name, such as `'a b'` and `'a-b'` (only letters, digits and _ are kept),
327
+ raises. Before, one of the two silently joined the reference.
328
+ - The data test of `T_vj_vij_job` counts NaN as missing, as the regressions
329
+ do; it failed on a frame with NaN rather than null in `vj_vij_job`.
330
+ - The data tests find the education columns of `labels.COLS_CONTROL` when
331
+ a block holds a single term rather than a list; before, they would have
332
+ dropped them from the one-value-per-person test without a failure.
333
+ - The notes in `tex/examples.tex` say "parentheses".
334
+
335
+ ## 0.3.0 (2026-09-30)
336
+
337
+ The job-level columns take their names in the server's long frame, where
338
+ every variable of the job type has the suffix `_job`, and the last column
339
+ has the DISCED dummies instead of both those and the field dummies.
340
+
341
+ ### Update a server script
342
+
343
+ - `pip install occu-choice-pf==0.3.0` (or `pip install occu_choice_pf_1==0.3.0`).
344
+ - Nothing else changes for a call with the defaults. Column (5) of the
345
+ default table has the DISCED dummies without the field dummies; since
346
+ DISCED codes lie within fields, its estimates are the same.
347
+ - The defaults now read `vj_vij_job`, `T_vj_vij_job` and `type_sizerank_job`
348
+ (were `vj_vij`, `T_vj_vij` and `type_sizerank`, which the long frame does
349
+ not have). A call with the defaults needs no change. A call that names
350
+ them passes the new names, e.g. `coefs={'T_vj_vij_job': ...}`,
351
+ `x='vj_vij_job'`.
352
+
353
+ ### Changed
354
+
355
+ - `labels.X`, `labels.COEFS`, `labels.BASE` and `labels.JOB` use the `_job`
356
+ names, and so do the simulated long frame, the example and the tests.
357
+ - The fixed effect is labelled `Person FE` (was `Person fixed effects`), and
358
+ the notes in `tex/examples.tex` say FE.
359
+ - Each set of `dummies` is an alternative rather than added up: column (5)
360
+ has the DISCED dummies and no longer the field dummies too (DISCED codes
361
+ lie within fields), and each dummies row is checked in its own column
362
+ only. Powers and controls still add up column by column.
363
+
364
+ ## 0.2.0 (2026-09-30)
365
+
366
+ The tables follow the layout of Table 5 of the paper, with `vj_vij` checked
367
+ under a `Controls:` row rather than reported. The default outcomes take the
368
+ server's column names, and the functions take only a polars DataFrame.
369
+
370
+ ### Update a server script
371
+
372
+ - `pip install occu-choice-pf==0.2.0` (or `pip install occu_choice_pf_1==0.2.0`).
373
+ - The tables no longer report the coefficient on `vj_vij`: it is in every
374
+ column through the new argument `base` and checked under a `Controls:`
375
+ row, as in Table 5 of the paper. To report it again:
376
+
377
+ ```python
378
+ from occu_choice_pf.labels import BASE, COEFS
379
+ reg.regression_table(long, coefs={**COEFS, **BASE}, base={})
380
+ ```
381
+
382
+ - A call without `outcomes` now reads `spell_1_new_job` and `appl_job` (was
383
+ the placeholders `job_j` and `appl_j`), so it can drop the argument:
384
+
385
+ ```python
386
+ # 0.1.0
387
+ reg.regression_table(long, {'spell_1_new_job': 'Found a job in the job type',
388
+ 'appl_job': 'Number of applications to the job type'})
389
+ # 0.2.0
390
+ reg.regression_table(long)
391
+ ```
392
+
393
+ ### Changed
394
+
395
+ - The check-mark block follows Table 5 of the paper: a `Controls:` row, then
396
+ one indented row per control (`base`, the fixed effect, each step). The
397
+ default labels of `POWERS`, `CONTROLS` and `DUMMIES` fit on one line.
398
+ - `labels.COEFS` is `T_vj_vij` only; `labels.BASE` is `vj_vij`.
399
+ - `labels.OUTCOMES` is `{'spell_1_new_job': ..., 'appl_job': ...}`, and the
400
+ simulated long frame has these columns.
401
+ - The active-job-seeker example controls for studying rather than for active
402
+ search, which is constant in that sample.
403
+ - `estimate` and `regression_table` take only a polars DataFrame and raise a
404
+ TypeError on anything else. The data work, the check for separated rows
405
+ included, is done in polars; pyfixest gets a pandas copy of each
406
+ regression's columns. The estimates are unchanged.
407
+
408
+ ### Added
409
+
410
+ - `tex/examples.tex`: the example tables with notes, in the paper's
411
+ `\showtable` layout (`cd tex && latexmk -pdf -outdir=out_dir examples.tex`).
412
+
413
+ ## 0.1.0 (2026-09-30)
414
+
415
+ First version.
416
+
417
+ ### Update a server script
418
+
419
+ - `pip install occu-choice-pf==0.1.0` (or `pip install occu_choice_pf_1==0.1.0`).
420
+ pyfixest is new on the server and installs with the package, with pandas,
421
+ scipy and formulaic.
422
+
423
+ ### Added
424
+
425
+ - `regressions.regression_table(df, ...)`: Poisson (`method='poisson'`,
426
+ pyfixest.fepois) or OLS (`method='ols'`, pyfixest.feols) regressions of
427
+ each outcome on `T_vj_vij` and `vj_vij` with a person fixed effect, one
428
+ panel per outcome, with `vj_vij` squared and cubed, the pre-treatment
429
+ controls times `vj_vij`, and education field and DISCED dummies times
430
+ `vj_vij` added column by column. Standard errors clustered on the person.
431
+ - `regressions.estimate(df, ...)`: the pyfixest fits behind a table.
432
+ - Separated rows are found with an exact linear program and dropped before
433
+ the Poisson regression.
434
+ - `labels`: `COEFS`, `OUTCOMES` (placeholder names), `PRETREATMENT`,
435
+ `POWERS`, `CONTROLS`, `DUMMIES`, `FE`.
436
+ - `run_tests(frames)`, `setup_log`, `save_tex`, `example_root`, as in
437
+ ad-data-pf.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Asker Nygaard David
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.