Skip to content

Create a minimal environment with core dependencies #830

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ jobs:
- name: Run tests against r
run: |
pixi run tests-against-r
pixi run tests-fixest-minimal

- name: Upload coverage to Codecov (partial)
uses: codecov/codecov-action@v4
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,13 @@ You can install the release version from `PyPI` by running
# inside an active virtual environment
python -m pip install pyfixest
```
To install a version with only core functionality and a reduced dependency set, you can run

or the development version from github by running
```py
python -m pip install pyfixest[minimal]
```

You can install the development version from github by running

```py
python -m pip install git+https://github.com/py-econometrics/pyfixest
Expand Down
16,905 changes: 7,439 additions & 9,466 deletions pixi.lock

Large diffs are not rendered by default.

34 changes: 29 additions & 5 deletions pyfixest/estimation/decomposition.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
import warnings
from dataclasses import dataclass, field
from typing import Any, Optional

import numpy as np
import pandas as pd
from joblib import Parallel, delayed
from numpy.typing import NDArray
from scipy.sparse import hstack, spmatrix, vstack
from scipy.sparse.linalg import lsqr
from tqdm import tqdm

try:
from tqdm import tqdm
except ImportError:
warnings.warn(
"The tqdm package is not installed. Progress bars are disabled. Note that tqdm is included in the pyfixest default environment."
)

def tqdm(iterable, *args, **kwargs):
"Define a dummy tqdm function."
return iterable


try:
from joblib import Parallel, delayed

joblib_available = True
except ImportError:
joblib_available = False
warnings.warn(
"The joblib package is not installed. Parallel processing is disabled. Note that joblib is included in the pyfixest default environment."
)


@dataclass
Expand Down Expand Up @@ -159,9 +180,12 @@ def bootstrap(self, rng: np.random.Generator, B: int = 1_000, alpha: float = 0.0
if self.unique_clusters is not None:
self.X_dict = {g: self.X_dict[g].tocsr() for g in self.X_dict}

_bootstrapped = Parallel(n_jobs=self.nthreads)(
delayed(self._bootstrap)(rng=rng) for _ in tqdm(range(B))
)
if joblib_available:
_bootstrapped = Parallel(n_jobs=self.nthreads)(
delayed(self._bootstrap)(rng=rng) for _ in tqdm(range(B))
)
else:
_bootstrapped = [self._bootstrap(rng=rng) for _ in tqdm(range(B))]

self._bootstrapped = {
key: np.concatenate([d[key] for d in _bootstrapped])
Expand Down
14 changes: 13 additions & 1 deletion pyfixest/estimation/feols_compressed_.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import logging
import warnings
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any, Literal, Optional, Union

import narwhals as nw
import numpy as np
import pandas as pd
from tqdm import tqdm

try:
from tqdm import tqdm
except ImportError:
warnings.warn(
"The tqdm package is not installed. Progress bars are disabled. Note that tqdm is included in the pyfixest default environment."
)

def tqdm(iterable, *args, **kwargs):
"Define a dummy tqdm function."
return iterable


from pyfixest.estimation.feols_ import Feols, PredictionErrorOptions, PredictionType
from pyfixest.estimation.FormulaParser import FixestFormula
Expand Down
42 changes: 31 additions & 11 deletions pyfixest/estimation/ritest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import numba as nb
import numpy as np
import pandas as pd
import seaborn as sns
from scipy.stats import gaussian_kde

# Make lets-plot an optional dependency
try:
Expand All @@ -25,11 +25,24 @@
except ImportError:
_HAS_LETS_PLOT = False

import warnings

from scipy.stats import norm
from tqdm import tqdm

from pyfixest.estimation.demean_ import demean

try:
from tqdm import tqdm
except ImportError:
warnings.warn(
"The tqdm package is not installed. Progress bars are disabled. Note that tqdm is included in the pyfixest default environment."
)

def tqdm(iterable, *args, **kwargs):
"Define a dummy tqdm function."
return iterable


# Only setup lets-plot if it's available
if _HAS_LETS_PLOT:
LetsPlot.setup_html()
Expand Down Expand Up @@ -382,10 +395,23 @@ def _plot_ritest_pvalue(
x_lab = "Test statistic"
y_lab = "Density"

def _plot_matplotlib(values: np.ndarray, sample_stat: np.ndarray) -> None:
kde = gaussian_kde(values)
x_range = np.linspace(min(values), max(values), 1000)
y_range = kde(x_range)

plt.figure(figsize=(10, 6))
plt.fill_between(x_range, y_range, color="blue", alpha=0.5)
plt.axvline(x=sample_stat, color="red", linestyle="--")
plt.title(title)
plt.xlabel(x_lab)
plt.ylabel(y_lab)
plt.show()

if plot_backend == "lets_plot":
if not _HAS_LETS_PLOT:
print("lets-plot is not installed. Falling back to matplotlib.")
plot_backend = "matplotlib"
return _plot_matplotlib(df["ri_stats"].to_numpy(), sample_stat)
else:
plot = (
ggplot(df, aes(x="ri_stats"))
Expand All @@ -397,16 +423,10 @@ def _plot_ritest_pvalue(
+ ylab(y_lab)
)

return plot.show()
return plot.show()

elif plot_backend == "matplotlib":
plt.figure(figsize=(10, 6))
sns.kdeplot(data=df, x="ri_stats", fill=True, color="blue", alpha=0.5)
plt.axvline(x=sample_stat, color="red", linestyle="--")
plt.title(title)
plt.xlabel(x_lab)
plt.ylabel(y_lab)
plt.show()
return _plot_matplotlib(df["ri_stats"].to_numpy(), sample_stat)

else:
raise ValueError(f"Unsupported plot backend: {plot_backend}")
Expand Down
16 changes: 14 additions & 2 deletions pyfixest/report/summarize.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

import numpy as np
import pandas as pd
from great_tables import GT
from tabulate import tabulate

from pyfixest.estimation.feiv_ import Feiv
from pyfixest.estimation.feols_ import Feols
Expand Down Expand Up @@ -156,6 +154,8 @@ def etable(
pf.etable([fit1, fit2])
```
"""
# check if etable installed else error

if signif_code is None:
signif_code = [0.001, 0.01, 0.05]
assert isinstance(signif_code, list) and len(signif_code) == 3, (
Expand Down Expand Up @@ -704,6 +704,11 @@ def _tabulate_etable_md(df, n_coef, n_fixef, n_models, n_model_stats):
-------
- formatted_table (str): The formatted table as a string.
"""
try:
from tabulate import tabulate
except ImportError:
raise ImportError("The tabulate package is required but it is not installed.")

# Format the DataFrame for tabulate
table = tabulate(
df,
Expand Down Expand Up @@ -1085,6 +1090,13 @@ def make_table(
rowname_col = dfs.columns[0]
groupname_col = None

try:
from great_tables import GT
except ImportError:
raise ImportError(
"The great_tables package is for running pf.etable() in 'gt' mode."
)

# Generate the table with GT
gt = GT(dfs, auto_align=False)

Expand Down
37 changes: 27 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,27 @@ dependencies = [
"formulaic>=1.1.0",
"pandas>=1.1.0",
"numba>=0.58.0",
"seaborn>=0.13.2",
"tabulate>=0.9.0",
"tqdm>=4.0.0",
"great-tables>=0.10.0",
"numpy>=1.19.0",
"narwhals>=1.13.3",
"joblib>=1.4.2,<2",
"matplotlib>=3.4",
]

[tool.pixi.feature.dev.dependencies]
rpy2 = ">=3.5.11,<4"

[tool.pixi.feature.minimaldev.dependencies]
rpy2 = ">=3.5.11,<4"


[project.optional-dependencies]

core = [
"joblib>=1.4.2,<2",
"tabulate>=0.9.0",
"tqdm>=4.0.0",
"great-tables>=0.10.0",
]

dev = [
"pytest>=7.2.0",
"pytest-cov>=4.1.0",
Expand All @@ -42,6 +50,13 @@ dev = [
"pyarrow>=14.0",
"jax>=0.4.15",
"jaxlib>=0.4.15",
"seaborn>=0.13.2",
]

minimaldev = [
"pytest>=7.2.0",
"pytest-cov>=4.1.0",
"pytest-xdist>=3.5.0",
]

plots = [
Expand Down Expand Up @@ -83,8 +98,10 @@ platforms = ["linux-64", "win-64", "osx-arm64", "osx-64"]
pyfixest = { path = ".", editable = true }

[tool.pixi.environments]
default = { solve-group = "default" }
minimal = { solve-group = "default" }
default = {features = ["core"], solve-group = "default"}
dev = { features = ["dev"], solve-group = "default" }
minimaldev = { features = ["minimaldev"], solve-group = "default" }
docs = { features = ["docs"], solve-group = "default" }
build = { features = ["build"], solve-group = "default" }
jax = { features = ["jax"], solve-group = "default" }
Expand All @@ -105,6 +122,10 @@ update-test-data = "Rscript tests/r_test_comparisons.R"
install-r = "Rscript r_test_requirements.R"
render-notebooks = "python scripts/run_notebooks.py"

[tool.pixi.feature.minimaldev.tasks]
tests-fixest-minimal = "pytest tests/test_vs_fixest.py::test_single_fit_feols -k 'not jax' --cov=pyfixest --cov-report=xml"


[tool.pixi.feature.build.tasks]
build-pip = 'python -m build .'

Expand All @@ -113,10 +134,6 @@ docs-build = "quartodoc build --verbose --config docs/_quarto.yml"
docs-render = "quarto render docs"
docs-preview = "quarto preview docs"

[tool.pixi.feature.docs.dependencies]
rpy2 = ">=3.5.11,<4"
r-fixest = ">=0.12.1,<0.13"
r-broom = ">=1.0.5,<2"

[tool.pytest.ini_options]
addopts = [
Expand Down
14 changes: 13 additions & 1 deletion scripts/run_notebooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,23 @@
"""

import logging
import warnings
from pathlib import Path

import papermill
from joblib import Parallel, delayed
from tqdm import tqdm

try:
from tqdm import tqdm
except ImportError:
warnings.warn(
"The tqdm package is not installed. Progress bars are disabled. Note that tqdm is included in the pyfixest default environment."
)

def tqdm(iterable, *args, **kwargs):
"Define a dummy tqdm function."
return iterable


KERNEL_NAME: str = "python3"
DOCS = Path("docs")
Expand Down
Loading