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.
- occu_choice_pf_2-0.6.1/.gitignore +19 -0
- occu_choice_pf_2-0.6.1/CHANGELOG.md +437 -0
- occu_choice_pf_2-0.6.1/LICENSE +21 -0
- occu_choice_pf_2-0.6.1/PKG-INFO +285 -0
- occu_choice_pf_2-0.6.1/README.md +268 -0
- occu_choice_pf_2-0.6.1/pyproject.toml +45 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/__init__.py +19 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/regressions/code/run_regressions.py +63 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/regressions/logs/.gitkeep +0 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/regressions/output/tab_ols.tex +24 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/regressions/output/tab_poisson.tex +24 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/regressions/output/tab_poisson_active.tex +17 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/regressions/output/tab_poisson_quadratic.tex +26 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/code/run_underestimation_gap.py +102 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/logs/.gitkeep +0 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_gaps.tex +15 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_gaps_gender.tex +13 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_gaps_gender_end.tex +14 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_gaps_parents.tex +16 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_gaps_se.tex +25 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_gaps_zero.tex +15 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/examples/underestimation_gap/output/tab_estimation_log_gaps.tex +15 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/functions/labels.py +119 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/functions/regressions.py +460 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/functions/underestimation_gap.py +517 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/functions/utils.py +123 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/simulation/__init__.py +1 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/simulation/regressions.py +122 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/simulation/underestimation_gap.py +152 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/__init__.py +0 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/conftest.py +30 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/test_examples.py +84 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/test_regressions.py +438 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/test_regressions_data.py +117 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/test_run_tests.py +47 -0
- occu_choice_pf_2-0.6.1/src/occu_choice_pf/tests/test_underestimation_gap.py +602 -0
- 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.
|