Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
62 changes: 62 additions & 0 deletions gwlearn/ensemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ class GWRandomForestClassifier(BaseClassifier):
Pooled out-of-bag (OOB) true labels across all fitted local models.
oob_pred_pooled_ : numpy.ndarray
Pooled out-of-bag (OOB) predictions/scores across all fitted local models.
oob_pooled_score_ : float
Accuracy computed from all out-of-bag predictions pooled together.
score_ : float
Alias for ``oob_pooled_score_``.

Examples
--------
Expand Down Expand Up @@ -297,6 +301,32 @@ def fit(

return self

@property
def oob_pooled_score_(self) -> float:
"""Accuracy on pooled out-of-bag predictions vs pooled OOB true labels.

Returns
-------
float
Accuracy computed from all out-of-bag predictions pooled together.
"""
if self.oob_y_pooled_.size == 0 or self.oob_pred_pooled_.size == 0:
return float("nan")
y_true = self.oob_y_pooled_.ravel()
y_pred = self.oob_pred_pooled_.ravel()
return (y_true == y_pred).mean()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use sklearn.metrics.acccuracy, do not reimplement.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, you're right.. changed in latest commit.


@property
def score_(self) -> float:
"""Alias for oob_pooled_score_.

Returns
-------
float
Accuracy computed from all out-of-bag predictions pooled together.
"""
return self.oob_pooled_score_

def _get_score_data(
self,
local_model: BaseEstimator,
Expand Down Expand Up @@ -646,6 +676,10 @@ class GWRandomForestRegressor(BaseRegressor):
Pooled out-of-bag (OOB) true values across all fitted local models.
oob_pred_pooled_ : numpy.ndarray
Pooled out-of-bag (OOB) predictions across all fitted local models.
oob_pooled_score_ : float
R² computed from all out-of-bag predictions pooled together.
score_ : float
Alias for ``oob_pooled_score_``.

Examples
--------
Expand Down Expand Up @@ -789,6 +823,34 @@ def fit(

return self

@property
def oob_pooled_score_(self) -> float:
"""R² on pooled out-of-bag predictions vs pooled OOB true values.

Returns
-------
float
R² computed from all out-of-bag predictions pooled together.
"""
if len(self.oob_y_pooled_) == 0:
return float("nan")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return float("nan")
return np.nan

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed in latest commit.

y_true = self.oob_y_pooled_.ravel()
y_pred = self.oob_pred_pooled_.ravel()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need ravel() here? How come? This is not flat array?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're rigth, i'll remove .ravel() from oob_pooled_score and move it to _get_oob_score_data.

ss_res = ((y_true - y_pred) ** 2).sum()
ss_tot = ((y_true - y_true.mean()) ** 2).sum()
return 1 - ss_res / ss_tot if ss_tot != 0 else float("nan")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use sklearn.metrics.r2_score, do not reimplement. I don't want to think if this is correct or not. Minimise maintenance burden.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above.


@property
def score_(self) -> float:
"""Alias for oob_pooled_score_.

Returns
-------
float
R² computed from all out-of-bag predictions pooled together.
"""
return self.oob_pooled_score_

def _get_score_data(
self,
local_model: BaseEstimator,
Expand Down
63 changes: 62 additions & 1 deletion gwlearn/linear_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ class GWLogisticRegression(BaseClassifier):
left_out_w_ : np.ndarray
Array of weights on left out observations in local models when
``leave_out`` is set.
pooled_score_ : float
Accuracy computed from all local model predictions pooled together.
score_ : float
Alias for ``pooled_score_``.

Examples
--------
Expand All @@ -164,7 +168,6 @@ class GWLogisticRegression(BaseClassifier):
dtype: boolean
"""

# TODO: score_ should be an alias of pooled_score_ - this is different from MGWR
def __init__(
self,
bandwidth: float | None = None,
Expand Down Expand Up @@ -261,6 +264,30 @@ def fit(self, X: pd.DataFrame, y: pd.Series, geometry: gpd.GeoSeries | None = No

return self

@property
def pooled_score_(self) -> float:
"""Accuracy on pooled predictions vs pooled true labels.

Returns
-------
float
Accuracy computed from all local model predictions pooled together.
"""
if self.y_pooled_.size == 0 or self.pred_pooled_.size == 0:
return float("nan")
return (self.y_pooled_ == self.pred_pooled_).mean()

@property
def score_(self) -> float:
"""Alias for pooled_score_.

Returns
-------
float
Accuracy computed from all local model predictions pooled together.
"""
return self.pooled_score_

def _get_score_data(
self,
local_model: BaseEstimator,
Expand Down Expand Up @@ -382,6 +409,10 @@ class GWLinearRegression(BaseRegressor):
each location
local_intercept_ : pd.Series
Local intercept values at each location
pooled_score_ : float
R² computed from all local model predictions pooled together.
score_ : float
Alias for ``pooled_score_``.

Examples
--------
Expand Down Expand Up @@ -484,4 +515,34 @@ def fit(self, X: pd.DataFrame, y: pd.Series, geometry: gpd.GeoSeries | None = No
[x[1] for x in self._score_data], index=self._names
)

# Store pooled y for score computation
self.y_pooled_ = y.values
self.pred_pooled_ = self.pred_.values

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. This not not pooled y, this is focal y and focal prediction.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


return self

@property
def pooled_score_(self) -> float:
"""R² on pooled predictions vs pooled true values.

Returns
-------
float
R² computed from all local model predictions pooled together.
"""
if len(self.y_pooled_) == 0:
return float("nan")
ss_res = ((self.y_pooled_ - self.pred_pooled_) ** 2).sum()
ss_tot = ((self.y_pooled_ - self.y_pooled_.mean()) ** 2).sum()
return 1 - ss_res / ss_tot if ss_tot != 0 else float("nan")

@property
def score_(self) -> float:
"""Alias for pooled_score_.

Returns
-------
float
R² computed from all local model predictions pooled together.
"""
return self.pooled_score_