Skip to content

Commit cd49225

Browse files
committed
[scpc] created scaffold
1 parent 06f5634 commit cd49225

9 files changed

Lines changed: 706 additions & 29 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
2-
name = "repo-template"
2+
name = "scpc"
33
version = "0.1.0"
4-
description = "Add your description here"
4+
description = "Python port of scpcR"
55
readme = "README.md"
66
requires-python = ">=3.11"
77
dependencies = []

src/scpc/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .core import scpc
2+
from .types import SCPCResult
3+
4+
__all__ = ["scpc", "SCPCResult"]

src/scpc/core.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Sequence
4+
5+
from .types import DataFrameLike, ModelLike, SCPCResult
6+
7+
8+
def scpc(
9+
model: ModelLike,
10+
data: DataFrameLike,
11+
lon: str | None = None,
12+
lat: str | None = None,
13+
coord_euclidean: Sequence[str] | None = None,
14+
cluster: str | None = None,
15+
ncoef: int | None = None,
16+
avc: float = 0.03,
17+
uncond: bool = False,
18+
cvs: bool = False,
19+
k: int | None = None,
20+
) -> SCPCResult:
21+
"""Run spatial correlation-robust inference.
22+
23+
This is the package entrypoint. It combines the data alignment helpers,
24+
the spatial setup helpers, and the matrix calculations into one inference
25+
routine that mirrors the public `scpc()` function in the R package.
26+
27+
Args:
28+
model: Fitted model object to analyze.
29+
data: Data used to fit the model.
30+
lon: Longitude column name for geodesic coordinates.
31+
lat: Latitude column name for geodesic coordinates.
32+
coord_euclidean: Euclidean coordinate column names.
33+
cluster: Optional clustering column.
34+
ncoef: Number of coefficients to report.
35+
avc: Upper bound on average pairwise correlation.
36+
uncond: Whether to skip the conditional adjustment.
37+
cvs: Whether to return additional critical values.
38+
k: Backward-compatible alias for `ncoef`.
39+
40+
Returns:
41+
The fitted SCPC result object.
42+
43+
Raises:
44+
ValueError: Raised later for invalid combinations of arguments.
45+
"""
46+
pass

src/scpc/types.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Sequence
4+
from dataclasses import dataclass
5+
from typing import Any, TypeAlias
6+
7+
ArrayLike: TypeAlias = Any
8+
MatrixLike: TypeAlias = Any
9+
ModelLike: TypeAlias = Any
10+
DataFrameLike: TypeAlias = Any
11+
12+
13+
@dataclass(slots=True)
14+
class CoordinateData:
15+
"""Normalized coordinates for spatial distance calculations.
16+
17+
This record holds the observation-aligned coordinate matrix together with
18+
the information needed to interpret those coordinates correctly. In this
19+
package, locations can come in either as longitude/latitude pairs or as
20+
ordinary Euclidean coordinates. `CoordinateData` gives the rest of the
21+
code one consistent representation of that information.
22+
23+
Attributes:
24+
coords: Numeric coordinate matrix aligned to the model observations.
25+
latlong: Whether the coordinates should be treated as longitude and
26+
latitude rather than Euclidean coordinates.
27+
"""
28+
29+
coords: MatrixLike
30+
latlong: bool
31+
32+
33+
@dataclass(slots=True)
34+
class ConditionalProjectionSetup:
35+
"""Regressor information for the conditional SCPC adjustment.
36+
37+
This record contains the regression-side objects needed to build the
38+
conditional projection basis. In higher-level terms, it represents the
39+
covariate space that spatial directions must be made orthogonal to before
40+
conditional SCPC inference can be computed.
41+
42+
Attributes:
43+
model_mat: Regression design matrix aligned to the active observations.
44+
include_intercept: Whether the conditional projection should include an
45+
explicit intercept column.
46+
fixef_id: Optional fixed-effect identifiers used for demeaning.
47+
"""
48+
49+
model_mat: MatrixLike
50+
include_intercept: bool
51+
fixef_id: ArrayLike | None
52+
53+
54+
@dataclass(slots=True)
55+
class SpatialSetup:
56+
"""Spatial reference objects derived from the distance matrix.
57+
58+
This record gathers the main outputs of the spatial setup stage: the final
59+
projection basis, the critical value attached to that basis, the omega
60+
grid used for size control, and the kernel scales behind those objects.
61+
It exists so the main inference routine can pass around one coherent
62+
description of the spatial environment.
63+
64+
Attributes:
65+
wfin: Final spatial projection matrix.
66+
cvfin: Critical value attached to the final projection.
67+
omsfin: Omega matrices over the spatial correlation grid.
68+
c0: Kernel scale matching the target average correlation bound.
69+
cmax: Largest kernel scale used in the spatial grid search.
70+
"""
71+
72+
wfin: MatrixLike
73+
cvfin: float
74+
omsfin: list[MatrixLike]
75+
c0: float
76+
cmax: float
77+
78+
79+
@dataclass(slots=True)
80+
class SCPCResult:
81+
"""SCPC estimates, intervals, and projection metadata.
82+
83+
This is the main user-facing result type returned by `scpc()`. It keeps
84+
together the estimated coefficient table, any stored critical values, and
85+
the spatial projection information that explains how the inference was
86+
constructed.
87+
88+
Attributes:
89+
scpcstats: Main result table with estimates, standard errors, test
90+
statistics, p-values, and confidence interval endpoints.
91+
scpccvs: Optional table of stored critical values at supported levels.
92+
w: Final spatial projection matrix used for inference.
93+
avc: Average pairwise correlation bound supplied by the user.
94+
c0: Kernel scale implied by `avc`.
95+
cv: Unconditional 5 percent critical value.
96+
q: Number of non-constant spatial principal components retained.
97+
call: Optional textual representation of the original call.
98+
"""
99+
100+
scpcstats: MatrixLike
101+
scpccvs: MatrixLike | None
102+
w: MatrixLike
103+
avc: float
104+
c0: float
105+
cv: float
106+
q: int
107+
call: str | None = None
108+
109+
def __repr__(self) -> str:
110+
"""Return a developer-oriented representation of the result.
111+
112+
This method provides a compact object representation for interactive
113+
work and debugging, where a short structural summary is more useful
114+
than the full printed results table.
115+
116+
Returns:
117+
A representation string.
118+
"""
119+
pass
120+
121+
def __str__(self) -> str:
122+
"""Return a user-facing summary string.
123+
124+
This method fills the role of `print.scpc`. It is the quick, readable
125+
view of the SCPC result that a user sees when they inspect the object
126+
at the console.
127+
128+
Returns:
129+
A formatted summary string.
130+
"""
131+
pass
132+
133+
def summary(self) -> str:
134+
"""Return an extended formatted summary.
135+
136+
This method fills the role of `summary.scpc`. It exists for the case
137+
where the user wants the fuller inference table, confidence intervals,
138+
and any stored critical values in one readable summary.
139+
140+
Returns:
141+
A formatted summary string.
142+
"""
143+
pass
144+
145+
def coef(self) -> Any:
146+
"""Return the coefficient estimates.
147+
148+
This method fills the role of `coef.scpc`. It gives users the compact
149+
coefficient vector without making them manually pull it out of the full
150+
result table.
151+
152+
Returns:
153+
The coefficient estimates.
154+
"""
155+
pass
156+
157+
def confint(
158+
self,
159+
parm: Sequence[str] | Sequence[int] | None = None,
160+
level: float = 0.95,
161+
) -> Any:
162+
"""Return confidence intervals for selected coefficients.
163+
164+
This method fills the role of `confint.scpc`. It is the focused access
165+
path for interval extraction when a user wants selected coefficients or
166+
a supported confidence level without reading the full summary output.
167+
168+
Args:
169+
parm: Coefficient names or positions to include.
170+
level: Confidence level to request.
171+
172+
Returns:
173+
Confidence intervals for the selected coefficients.
174+
175+
Raises:
176+
ValueError: Raised later for unknown coefficients or unsupported
177+
confidence levels.
178+
"""
179+
pass

src/scpc/utils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from __future__ import annotations

src/scpc/utils/data.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Sequence
4+
5+
from ..types import (
6+
ArrayLike,
7+
ConditionalProjectionSetup,
8+
CoordinateData,
9+
DataFrameLike,
10+
MatrixLike,
11+
ModelLike,
12+
)
13+
14+
15+
def get_obs_index(model: ModelLike, data: DataFrameLike) -> ArrayLike:
16+
"""Recover which rows of the input data were used by the fitted model.
17+
18+
Fitted models often drop observations because of missing data or internal
19+
preprocessing. SCPC needs to know exactly which rows survived so that
20+
score contributions, regressors, and coordinates are all aligned to the
21+
same observations before any spatial calculations are done.
22+
23+
Args:
24+
model: Fitted model object.
25+
data: Original data passed to the model.
26+
27+
Returns:
28+
Integer indices locating the model observations in `data`.
29+
30+
Raises:
31+
ValueError: Raised later if the model rows cannot be mapped back to
32+
the provided data.
33+
"""
34+
pass
35+
36+
37+
def get_scpc_model_matrix(model: ModelLike) -> MatrixLike:
38+
"""Recover the regression design matrix used for SCPC adjustments.
39+
40+
SCPC needs a matrix representation of the fitted regressors because the
41+
conditional adjustment works by removing covariate-driven variation from
42+
spatial directions. This helper extracts that aligned design matrix.
43+
44+
Args:
45+
model: Fitted model object.
46+
47+
Returns:
48+
The model matrix aligned to the fitted coefficients.
49+
"""
50+
pass
51+
52+
53+
def has_fixest_fe(model: ModelLike) -> bool:
54+
"""Check whether a fixest-style model uses absorbed fixed effects.
55+
56+
Fixed effects matter here because they change how the regression space
57+
should be represented before conditional SCPC orthogonalization. This
58+
helper makes that branch explicit.
59+
60+
Args:
61+
model: Fitted model object.
62+
63+
Returns:
64+
Whether the model includes absorbed fixed effects.
65+
"""
66+
pass
67+
68+
69+
def get_conditional_projection_setup(
70+
model: ModelLike,
71+
model_mat: MatrixLike,
72+
n: int,
73+
uncond: bool,
74+
) -> ConditionalProjectionSetup:
75+
"""Prepare the regression-side objects for conditional SCPC.
76+
77+
Conditional SCPC needs a version of the regressor space that matches the
78+
fitted coefficient space exactly, including any demeaning implied by fixed
79+
effects. This helper standardizes that information before the spatial
80+
directions are orthogonalized against it.
81+
82+
Args:
83+
model: Fitted model object.
84+
model_mat: Raw model matrix extracted from the model.
85+
n: Expected number of active observations.
86+
uncond: Whether unconditional inference was requested.
87+
88+
Returns:
89+
The normalized conditional projection setup.
90+
91+
Raises:
92+
ValueError: Raised later if the regression objects cannot be aligned.
93+
"""
94+
pass
95+
96+
97+
def resolve_coords_input(
98+
data: DataFrameLike,
99+
obs_index: ArrayLike,
100+
lon: str | None,
101+
lat: str | None,
102+
coord_euclidean: Sequence[str] | None,
103+
) -> CoordinateData:
104+
"""Normalize the location information used for spatial distances.
105+
106+
This helper turns the user's coordinate arguments into one clean numeric
107+
representation aligned to the active observations. It is the place where
108+
SCPC decides whether the problem is being described in longitude/latitude
109+
space or in ordinary Euclidean coordinates.
110+
111+
Args:
112+
data: Original input data.
113+
obs_index: Indices of the observations used by the model.
114+
lon: Longitude column name for geodesic coordinates.
115+
lat: Latitude column name for geodesic coordinates.
116+
coord_euclidean: Euclidean coordinate column names.
117+
118+
Returns:
119+
The normalized coordinate representation used by the spatial engine.
120+
121+
Raises:
122+
ValueError: Raised later for invalid or inconsistent coordinate input.
123+
"""
124+
pass

0 commit comments

Comments
 (0)