Skip to content

Commit 1b83c6d

Browse files
Merge pull request #181 from scikit-learn-contrib/pklm-implementation
Pklm implementation
2 parents 80e3d43 + 2f1cdc8 commit 1b83c6d

13 files changed

Lines changed: 490 additions & 269 deletions

File tree

.github/workflows/test.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,13 @@ jobs:
6464
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
6565
- name: Check Changed Files
6666
id: changed-files
67-
run: |
68-
git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} --depth=1
69-
git diff --name-only ${{ github.base_ref }} > changed_files.txt
67+
uses: tj-actions/changed-files@v40
68+
with:
69+
files: |
70+
docs/**
71+
**.rst
72+
7073
- name: Build Docs
71-
if: contains(fromJSON('["docs/", ".rst"]').join(','), fromJSON('["${{ steps.changed-files.outputs.files }}"]').join(','))
74+
if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest' && steps.changed-files.outputs.any_changed == 'true'
7275
run: |
7376
poetry run sphinx-build -b html docs/ _build/html

.python-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
env_qolmat_3.9
1+
3.12.10

HISTORY.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
History
33
=======
44

5-
0.1.10 (2024-??-??)
5+
0.1.11 (2025-12-30)
6+
------------------
7+
* PLKM test implemented and documented in the holes_characterization module
8+
9+
0.1.10 (2025-08-30)
610
------------------
711
* Long EM and RPCA operations wrapped with tqdm progress bars
812
* Readme code sample updated, and results table made consistent
913

10-
0.1.9 (2024-08-29)
14+
0.1.9 (2025-08-29)
1115
------------------
1216
* Tutorials reproducibility improved with random_state parameters
1317
* RPCA now accepts random_state parameters

docs/conf.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
source_suffix = ".rst"
7676

7777
# Generate the plots for the gallery
78-
plot_gallery = True
78+
plot_gallery = "True"
7979

8080
# The master toctree document.
8181
master_doc = "index"
@@ -156,9 +156,11 @@
156156
"gallery_dirs": ["examples/tutorials/"],
157157
"doc_module": "qolmat",
158158
"backreferences_dir": os.path.join("generated"),
159-
"reference_url": {"qolmat": None},
159+
"reference_url": {},
160+
"inspect_global_variables": False,
160161
}
161162

163+
162164
suppress_warnings = ["autosectionlabel.*"]
163165

164166
# doctest configuration

docs/sg_execution_times.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
2+
:orphan:
3+
4+
.. _sphx_glr_sg_execution_times:
5+
6+
7+
Computation times
8+
=================
9+
**00:00.000** total execution time for 6 files **from all galleries**:
10+
11+
.. container::
12+
13+
.. raw:: html
14+
15+
<style scoped>
16+
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet" />
17+
<link href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css" rel="stylesheet" />
18+
</style>
19+
<script src="https://code.jquery.com/jquery-3.7.0.js"></script>
20+
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
21+
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
22+
<script type="text/javascript" class="init">
23+
$(document).ready( function () {
24+
$('table.sg-datatable').DataTable({order: [[1, 'desc']]});
25+
} );
26+
</script>
27+
28+
.. list-table::
29+
:header-rows: 1
30+
:class: table table-striped sg-datatable
31+
32+
* - Example
33+
- Time
34+
- Mem (MB)
35+
* - :ref:`sphx_glr_examples_tutorials_plot_tuto_benchmark_TS.py` (``../examples/tutorials/plot_tuto_benchmark_TS.py``)
36+
- 00:00.000
37+
- 0.0
38+
* - :ref:`sphx_glr_examples_tutorials_plot_tuto_categorical.py` (``../examples/tutorials/plot_tuto_categorical.py``)
39+
- 00:00.000
40+
- 0.0
41+
* - :ref:`sphx_glr_examples_tutorials_plot_tuto_diffusion_models.py` (``../examples/tutorials/plot_tuto_diffusion_models.py``)
42+
- 00:00.000
43+
- 0.0
44+
* - :ref:`sphx_glr_examples_tutorials_plot_tuto_hole_generator.py` (``../examples/tutorials/plot_tuto_hole_generator.py``)
45+
- 00:00.000
46+
- 0.0
47+
* - :ref:`sphx_glr_examples_tutorials_plot_tuto_mcar.py` (``../examples/tutorials/plot_tuto_mcar.py``)
48+
- 00:00.000
49+
- 0.0
50+
* - :ref:`sphx_glr_examples_tutorials_plot_tuto_mean_median.py` (``../examples/tutorials/plot_tuto_mean_median.py``)
51+
- 00:00.000
52+
- 0.0

examples/pklm/p_value_validity/p_value_validity.ipynb

Lines changed: 154 additions & 0 deletions
Large diffs are not rendered by default.

examples/tutorials/plot_tuto_mcar.py

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"""
88

99
# %%
10-
# First import some libraries
10+
# First, import some libraries
1111
from matplotlib import pyplot as plt
1212

1313
import numpy as np
@@ -51,7 +51,7 @@
5151
# The test compares distributions of different missing patterns.
5252
#
5353
# The null hypothesis, H0, is: "Distributions within each pattern are similar.".
54-
# We choose to use the classic threshold of 5%. If the test p-value is below this threshold,
54+
# We choose to use the classical threshold of 5%. If the test p-value is below this threshold,
5555
# we reject the null hypothesis.
5656
# This notebook shows how the Little and PKLM tests perform on a simplistic case and their
5757
# limitations. We instantiate a test object with a random state for reproducibility.
@@ -102,7 +102,7 @@
102102
print(f"The p-value of the PKLM test is: {pklm_result:.2%}")
103103
# %%
104104
# The two p-values are larger than 0.05, therefore we don't reject the H0 MCAR assumption.
105-
# In this case this is a true negative.
105+
# In this case, this is a true negative.
106106

107107
# %%
108108
# Case 2: MAR holes with mean bias (True positive)
@@ -147,13 +147,13 @@
147147
print(f"The p-value of the PKLM test is: {pklm_result:.2%}")
148148
# %%
149149
# The two p-values are smaller than 0.05, therefore we reject the H0 MCAR assumption.
150-
# In this case this is a true positive.
150+
# In this case, this is a true positive.
151151

152152
# %%
153153
# Case 3: MAR holes with any mean bias (False negative)
154154
# =====================================================
155155
#
156-
# The specific case is designed to emphasize the Little's test limits. In the case, we generate
156+
# The specific case is designed to emphasize the Little's test limits. In this case, we generate
157157
# holes when the absolute value of the first feature is high. This missingness mechanism is clearly
158158
# MAR but the means between missing patterns is not statistically different.
159159

@@ -197,18 +197,18 @@
197197
print(f"The p-value of the PKLM test is: {pklm_result:.2%}")
198198
# %%
199199
# The Little's p-value is larger than 0.05, therefore, using this test we don't reject the H0 MCAR
200-
# assumption. In this case this is a false negative since the missingness mechanism is MAR.
200+
# assumption. In this case, this is a false negative since the missingness mechanism is MAR.
201201
#
202-
# However the PKLM test p-value is smaller than 0.05 therefore we don't reject the H0 MCAR
203-
# assumption. In this case this is a true negative.
202+
# However the PKLM test p-value is smaller than 0.05 therefore we reject the H0 MCAR
203+
# assumption. In this case, this is a true negative.
204204

205205
# %%
206206
# Limitations and conclusion
207207
# ==========================
208208
# In this tutorial, we can see that Little's test fails to detect covariance heterogeneity between
209209
# patterns.
210210
#
211-
# We also note that the Little's test does not handle categorical data or temporally
211+
# We also note that Little's test does not handle categorical data or temporally
212212
# correlated data.
213213
#
214214
# This is why we have implemented the PKLM test, which makes up for the shortcomings of the Little
@@ -231,33 +231,43 @@
231231
#
232232

233233
# %%
234-
235-
"""
236-
Calculation time
237-
================
238-
239-
+------------+------------+----------------------+
240-
| **n_rows** | **n_cols** | **Calculation_time** |
241-
+============+============+======================+
242-
| 200 | 2 | 2"12 |
243-
+------------+------------+----------------------+
244-
| 500 | 2 | 2"24 |
245-
+------------+------------+----------------------+
246-
| 500 | 4 | 2"18 |
247-
+------------+------------+----------------------+
248-
| 1000 | 4 | 2"48 |
249-
+------------+------------+----------------------+
250-
| 1000 | 6 | 2"42 |
251-
+------------+------------+----------------------+
252-
| 10000 | 6 | 20"54 |
253-
+------------+------------+----------------------+
254-
| 10000 | 10 | 14"48 |
255-
+------------+------------+----------------------+
256-
| 100000 | 10 | 4'51" |
257-
+------------+------------+----------------------+
258-
| 100000 | 15 | 3'06" |
259-
+------------+------------+----------------------+
260-
"""
234+
# Calculation time
235+
# ================
236+
#
237+
# .. list-table::
238+
# :header-rows: 1
239+
# :widths: 15 15 25
240+
#
241+
# * - **n_rows**
242+
# - **n_cols**
243+
# - **Calculation time**
244+
# * - 200
245+
# - 2
246+
# - 2"12
247+
# * - 500
248+
# - 2
249+
# - 2"24
250+
# * - 500
251+
# - 4
252+
# - 2"18
253+
# * - 1000
254+
# - 4
255+
# - 2"48
256+
# * - 1000
257+
# - 6
258+
# - 2"42
259+
# * - 10000
260+
# - 6
261+
# - 20"54
262+
# * - 10000
263+
# - 10
264+
# - 14"48
265+
# * - 100000
266+
# - 10
267+
# - 4'51"
268+
# * - 100000
269+
# - 15
270+
# - 3'06"
261271

262272
# %%
263273
# 2.1 Parameters and Hyperparameters
@@ -268,10 +278,9 @@
268278
# * ``nb_projections``: Number of projections on which the test statistic is calculated. This
269279
# parameter has the greatest influence on test calculation time. Its default value
270280
# ``nb_projections=100``.
271-
# Est-ce qu'on donne des ordres de grandeurs utiles ? J'avais un peu fait ce travail.
272281
#
273-
# * ``nb_permutation`` : Number of permutations of the projected targets. The higher is better. This
274-
# parameter has little impact on calculation time.
282+
# * ``nb_permutation`` : Number of permutations of the projected targets. The higher is better.
283+
# This parameter has little impact on calculation time.
275284
# Its default value ``nb_permutation=30``.
276285
#
277286
# * ``nb_trees_per_proj`` : The number of subtrees in each random forest fitted. In order to
@@ -296,8 +305,8 @@
296305
# ================================================
297306
#
298307
# As we have seen, Little's test only applies to quantitative data. In real life, however, it is
299-
# common to have to deal with mixed data. Here's an example of how to use the PKLM test on a dataset
300-
# with mixed data types.
308+
# common to have to deal with mixed data. Here's an example of how to use the PKLM test on a
309+
# dataset with mixed data types.
301310

302311
# %%
303312
n_rows = 100
@@ -308,9 +317,7 @@
308317
modalities = ["A", "B", "C", "D"]
309318
col4 = rng.choice(modalities, n_rows)
310319

311-
df = pd.DataFrame(
312-
{"Numeric1": col1, "Numeric2": col2, "Boolean": col3, "Object": col4}
313-
)
320+
df = pd.DataFrame({"Numeric1": col1, "Numeric2": col2, "Boolean": col3, "Object": col4})
314321

315322
hole_gen = UniformHoleGenerator(
316323
n_splits=1,
@@ -327,7 +334,7 @@
327334
print(f"The p-value of the PKLM test is: {pklm_result:.2%}")
328335

329336
# %%
330-
# To perform the PKLM test over mixed data types, non numerical features need to be encoded. The
337+
# To perform the PKLM test over mixed data types, non-numerical features need to be encoded. The
331338
# default encoder in the :class:`~qolmat.analysis.holes_characterization.PKLMTest` class is the
332339
# default OneHotEncoder from scikit-learn. If you wish to use an encoder adapted to your data, you
333340
# can perform this encoding step beforehand, and then use the PKLM test.
@@ -343,21 +350,17 @@
343350
# 2.3 Partial p-values
344351
# ================================================
345352
#
346-
# In addition, the PKLM test can be used to calculate partial p-values. We denote as many partial
353+
# In addition, the PKLM test can be used to calculate partial p-values. There are as many partial
347354
# p-values as there are columns in the input dataframe. This “partial” p-value corresponds to the
348355
# effect of removing the patterns induced by variable k.
349356
#
350357
# Let's take a look at an example of how to use this feature
351358

352359
# %%
353360
data = rng.multivariate_normal(
354-
mean=[0, 0, 0, 0],
355-
cov=[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]],
356-
size=400,
357-
)
358-
df = pd.DataFrame(
359-
data=data, columns=["Column 1", "Column 2", "Column 3", "Column 4"]
361+
mean=[0, 0, 0, 0], cov=[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], size=400
360362
)
363+
df = pd.DataFrame(data=data, columns=["Column 1", "Column 2", "Column 3", "Column 4"])
361364

362365
df_mask = pd.DataFrame(
363366
{
@@ -391,9 +394,7 @@
391394

392395
# %%
393396
for col_index, partial_p_v in enumerate(partial_p_values):
394-
print(
395-
f"The partial p-value for the column index {col_index + 1} is: {partial_p_v:.2%}"
396-
)
397+
print(f"The partial p-value for the column index {col_index + 1} is: {partial_p_v:.2%}")
397398

398399
# %%
399400
# As a result, by removing the missing patterns induced by variable 2, the p-value rises

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ codecov = "^2.1.13"
7676
[tool.poetry.group.docs.dependencies]
7777
numpydoc = "1.1.0"
7878
sphinx = ">= 5.0"
79-
sphinx-gallery = "0.10.1"
79+
sphinx-gallery = ">= 0.15"
8080
sphinx_rtd_theme = "1.0.0"
8181
sphinx_markdown_tables = "0.0.17"
8282

0 commit comments

Comments
 (0)