Skip to content

Data conversion and persistence

This page documents 4 public symbols. Each entry includes its purpose, import path, full API docstring, and the maintained example that exercises it.

Conceptual guide

load_data

Load a user data file and extract model features, target, and coordinates. Extract predictors, an optional response, and coordinates from a user data file.

Property Value
Type function
Import from pygwrx.io import load_data
Signature load_data(filepath: 'PathLike', x_cols: 'Optional[Sequence[str]]' = None, y_col: 'Optional[str]' = None, coord_cols: 'Optional[Tuple[str, str]]' = None, *, dropna: 'bool' = True) -> 'Tuple[np.ndarray, Optional[np.ndarray], np.ndarray]'
Maintained example examples/io/02_tabular_roundtrip.py

load_data

load_data(
    filepath: PathLike,
    x_cols: Optional[Sequence[str]] = None,
    y_col: Optional[str] = None,
    coord_cols: Optional[Tuple[str, str]] = None,
    *,
    dropna: bool = True
) -> Tuple[np.ndarray, Optional[np.ndarray], np.ndarray]

Load a user data file and extract model features, target, and coordinates. Extract predictors, an optional response, and coordinates from a user data file.

Supported inputs are CSV, Shapefile, GeoJSON, GeoPackage, and Parquet. For spatial files, coordinates are read from Point geometry when coord_cols is omitted.

Parameters:

Name Type Description Default
filepath PathLike

Input data file.

required
x_cols Optional[Sequence[str]]

Feature columns. When omitted, all numeric columns except the target, coordinate columns, and active geometry column are used.

None
y_col Optional[str]

Target column. When omitted, y is returned as None.

None
coord_cols Optional[Tuple[str, str]]

Explicit x/y or longitude/latitude columns. Required for non-spatial tables such as CSV unless the input is a GeoDataFrame-backed format.

None
dropna bool

Remove rows containing NaN or infinite values in X, y, or coordinates. One shared row mask is used, so returned arrays always remain aligned.

True

Returns:

Name Type Description
X ndarray

Feature matrix with floating-point dtype.

y Optional[ndarray]

Target values, or None when y_col is omitted.

coords ndarray

Coordinate matrix.

Examples:

Load a CSV file with explicit coordinate columns:

>>> from pygwrx.io import load_data
>>> X, y, coords = load_data(
...     "observations.csv",
...     x_cols=["income", "population"],
...     y_col="house_price",
...     coord_cols=("x", "y"),
... )

Load a point Shapefile using its geometry:

>>> X, y, coords = load_data(
...     "observations.shp",
...     x_cols=["income", "population"],
...     y_col="house_price",
... )
Source code in src/pygwrx/io/data.py
def load_data(
    filepath: PathLike,
    x_cols: Optional[Sequence[str]] = None,
    y_col: Optional[str] = None,
    coord_cols: Optional[Tuple[str, str]] = None,
    *,
    dropna: bool = True,
) -> Tuple[np.ndarray, Optional[np.ndarray], np.ndarray]:
    """Load a user data file and extract model features, target, and coordinates. Extract
    predictors, an optional response, and coordinates from a user data file.

    Supported inputs are CSV, Shapefile, GeoJSON, GeoPackage, and Parquet.
    For spatial files, coordinates are read from Point geometry when
    ``coord_cols`` is omitted.

    Args:
        filepath: Input data file.
        x_cols: Feature columns. When omitted, all numeric columns except the target,
            coordinate columns, and active geometry column are used.
        y_col: Target column. When omitted, ``y`` is returned as ``None``.
        coord_cols: Explicit x/y or longitude/latitude columns. Required for non-spatial
            tables such as CSV unless the input is a GeoDataFrame-backed format.
        dropna: Remove rows containing NaN or infinite values in X, y, or coordinates.
            One shared row mask is used, so returned arrays always remain aligned.

    Returns:
        X: Feature matrix with floating-point dtype.
        y: Target values, or ``None`` when ``y_col`` is omitted.
        coords: Coordinate matrix.

    Examples:
        Load a CSV file with explicit coordinate columns:

        >>> from pygwrx.io import load_data
        >>> X, y, coords = load_data(
        ...     "observations.csv",
        ...     x_cols=["income", "population"],
        ...     y_col="house_price",
        ...     coord_cols=("x", "y"),
        ... )

        Load a point Shapefile using its geometry:

        >>> X, y, coords = load_data(
        ...     "observations.shp",
        ...     x_cols=["income", "population"],
        ...     y_col="house_price",
        ... )
    """
    if not isinstance(filepath, (str, Path)):
        raise TypeError("filepath must be a string or pathlib.Path.")
    if not isinstance(dropna, (bool, np.bool_)):
        raise TypeError("dropna must be a boolean.")
    if y_col is not None and (not isinstance(y_col, str) or not y_col.strip()):
        raise TypeError("y_col must be a non-empty string or None.")

    path = Path(filepath).expanduser()
    if not path.exists():
        raise FileNotFoundError(f"Data file does not exist: {path}")
    if not path.is_file():
        raise ValueError(f"filepath must point to a file: {path}")
    if path.suffix.lower() not in _SUPPORTED_INPUT_SUFFIXES:
        raise ValueError(
            f"Unsupported file format {path.suffix!r}. Supported extensions are: "
            f"{sorted(_SUPPORTED_INPUT_SUFFIXES)}."
        )

    frame = _read_data_file(path)
    x_columns = _normalize_column_names(x_cols, parameter_name="x_cols")

    if coord_cols is not None:
        if not isinstance(coord_cols, (tuple, list)) or len(coord_cols) != 2:
            raise TypeError("coord_cols must contain exactly two column names.")
        coord_columns = _normalize_column_names(coord_cols, parameter_name="coord_cols")
        assert coord_columns is not None  # for type checkers
    else:
        coord_columns = None

    if y_col is not None:
        _validate_columns(frame, [y_col], role="target")
    if coord_columns is not None:
        _validate_columns(frame, coord_columns, role="coordinate")

    geometry_name: Optional[str] = None
    geopandas = _optional_geopandas()
    if geopandas is not None and isinstance(frame, geopandas.GeoDataFrame):
        geometry_name = frame.geometry.name

    if x_columns is None:
        excluded = {column for column in ([y_col] if y_col else [])}
        if coord_columns is not None:
            excluded.update(coord_columns)
        if geometry_name is not None:
            excluded.add(geometry_name)
        x_columns = [
            column
            for column in frame.select_dtypes(include=[np.number]).columns
            if column not in excluded
        ]
        if not x_columns:
            raise ValueError(
                "No numeric feature columns were found. Provide x_cols explicitly."
            )
    else:
        _validate_columns(frame, x_columns, role="feature")

    if y_col is not None and y_col in x_columns:
        raise ValueError("y_col cannot also appear in x_cols.")
    if coord_columns is not None:
        overlap = sorted(set(x_columns).intersection(coord_columns))
        if overlap:
            raise ValueError(
                f"Coordinate columns cannot also be feature columns: {overlap}."
            )

    X = _numeric_frame(frame, x_columns).to_numpy(dtype=float)
    y = (
        pd.to_numeric(frame[y_col], errors="coerce").to_numpy(dtype=float)
        if y_col is not None
        else None
    )

    if coord_columns is not None:
        coords = _numeric_frame(frame, coord_columns).to_numpy(dtype=float)
    elif geopandas is not None and isinstance(frame, geopandas.GeoDataFrame):
        coords = _extract_point_coordinates(frame)
    else:
        raise ValueError(
            "coord_cols must be provided for a non-spatial table such as CSV."
        )

    if X.shape[0] != coords.shape[0] or (y is not None and y.shape[0] != X.shape[0]):
        raise RuntimeError(
            "Loaded features, target, and coordinates are not row-aligned."
        )

    if dropna:
        mask = _finite_row_mask(X, y, coords)
        if not bool(mask.any()):
            raise ValueError(
                "No valid rows remain after removing NaN and infinite values."
            )
        X = X[mask]
        coords = coords[mask]
        if y is not None:
            y = y[mask]

    return X, y, coords

to_geodataframe

Convert aligned arrays into a point GeoDataFrame.

Property Value
Type function
Import from pygwrx.io import to_geodataframe
Signature to_geodataframe(X: 'np.ndarray', y: 'Optional[np.ndarray]', coords: 'np.ndarray', feature_names: 'Optional[Sequence[str]]' = None, target_name: 'str' = 'target', crs: 'Optional[Union[str, int]]' = None) -> 'gpd.GeoDataFrame'
Maintained example examples/io/03_geodataframe_roundtrip.py

to_geodataframe

to_geodataframe(
    X: ndarray,
    y: Optional[ndarray],
    coords: ndarray,
    feature_names: Optional[Sequence[str]] = None,
    target_name: str = "target",
    crs: Optional[Union[str, int]] = None,
) -> gpd.GeoDataFrame

Convert aligned arrays into a point GeoDataFrame.

crs defaults to None deliberately: assigning EPSG:4326 to unknown projected coordinates would mislabel rather than transform the data.

Parameters:

Name Type Description Default
X ndarray

Feature or result matrix.

required
y Optional[ndarray]

Optional target/result vector.

required
coords ndarray

x/y or longitude/latitude coordinates.

required
feature_names Optional[Sequence[str]]

Names of X columns. Defaults to feature_0, feature_1, ...

None
target_name str

Name used for y when y is supplied.

'target'
crs Optional[Union[str, int]]

Coordinate reference system, for example "EPSG:32650".

None

Returns:

Name Type Description
GeoDataFrame GeoDataFrame

Point GeoDataFrame containing the supplied columns.

Examples:

>>> from pygwrx.io import to_geodataframe
>>> gdf = to_geodataframe(
...     X,
...     y,
...     coords,
...     feature_names=["income", "population"],
...     target_name="house_price",
...     crs="EPSG:32650",
... )
Source code in src/pygwrx/io/data.py
def to_geodataframe(
    X: np.ndarray,
    y: Optional[np.ndarray],
    coords: np.ndarray,
    feature_names: Optional[Sequence[str]] = None,
    target_name: str = "target",
    crs: Optional[Union[str, int]] = None,
) -> gpd.GeoDataFrame:
    """Convert aligned arrays into a point GeoDataFrame.

    ``crs`` defaults to ``None`` deliberately: assigning EPSG:4326 to unknown
    projected coordinates would mislabel rather than transform the data.

    Args:
        X: Feature or result matrix.
        y: Optional target/result vector.
        coords: x/y or longitude/latitude coordinates.
        feature_names: Names of X columns. Defaults to ``feature_0``, ``feature_1``, ...
        target_name: Name used for y when y is supplied.
        crs: Coordinate reference system, for example ``"EPSG:32650"``.

    Returns:
        GeoDataFrame: Point GeoDataFrame containing the supplied columns.

    Examples:
        >>> from pygwrx.io import to_geodataframe
        >>> gdf = to_geodataframe(
        ...     X,
        ...     y,
        ...     coords,
        ...     feature_names=["income", "population"],
        ...     target_name="house_price",
        ...     crs="EPSG:32650",
        ... )
    """
    X_array = np.asarray(X)
    coords_array = np.asarray(coords)

    if X_array.ndim == 1:
        X_array = X_array.reshape(-1, 1)
    if X_array.ndim != 2:
        raise ValueError("X must be a one- or two-dimensional array.")
    if coords_array.ndim != 2 or coords_array.shape[1] != 2:
        raise ValueError("coords must have shape (n_samples, 2).")
    if X_array.shape[0] != coords_array.shape[0]:
        raise ValueError("X and coords must contain the same number of rows.")

    try:
        coords_float = coords_array.astype(float, copy=False)
    except (TypeError, ValueError) as exc:
        raise TypeError("coords must contain numeric values.") from exc
    if not np.isfinite(coords_float).all():
        raise ValueError("coords must contain only finite numeric values.")

    names = _normalize_column_names(feature_names, parameter_name="feature_names")
    if names is None:
        names = [f"feature_{index}" for index in range(X_array.shape[1])]
    if len(names) != X_array.shape[1]:
        raise ValueError(
            "feature_names length must equal the number of columns in X: "
            f"expected {X_array.shape[1]}, got {len(names)}."
        )

    reserved_geometry_name = "geometry"
    if reserved_geometry_name in names:
        raise ValueError("feature_names cannot contain the reserved name 'geometry'.")

    if not isinstance(target_name, str) or not target_name.strip():
        raise TypeError("target_name must be a non-empty string.")
    if y is not None:
        if target_name == reserved_geometry_name:
            raise ValueError("target_name cannot be 'geometry'.")
        if target_name in names:
            raise ValueError("target_name cannot duplicate a feature name.")

    data = {name: X_array[:, index] for index, name in enumerate(names)}

    if y is not None:
        y_array = np.asarray(y)
        if y_array.ndim == 2 and y_array.shape[1] == 1:
            y_array = y_array[:, 0]
        if y_array.ndim != 1:
            raise ValueError("y must be one-dimensional or a single-column array.")
        if y_array.shape[0] != X_array.shape[0]:
            raise ValueError("X, y, and coords must contain the same number of rows.")
        data[target_name] = y_array

    geopandas = _require_geopandas(purpose="GeoDataFrame conversion")
    geometry = geopandas.points_from_xy(coords_float[:, 0], coords_float[:, 1])
    return geopandas.GeoDataFrame(data, geometry=geometry, crs=crs)

from_geodataframe

Extract aligned arrays from a point GeoDataFrame.

Property Value
Type function
Import from pygwrx.io import from_geodataframe
Signature from_geodataframe(gdf: "'gpd.GeoDataFrame'", x_cols: 'Optional[Sequence[str]]' = None, y_col: 'Optional[str]' = None, *, dropna: 'bool' = True) -> 'Tuple[np.ndarray, Optional[np.ndarray], np.ndarray]'
Maintained example examples/io/03_geodataframe_roundtrip.py

from_geodataframe

from_geodataframe(
    gdf: "gpd.GeoDataFrame",
    x_cols: Optional[Sequence[str]] = None,
    y_col: Optional[str] = None,
    *,
    dropna: bool = True
) -> Tuple[np.ndarray, Optional[np.ndarray], np.ndarray]

Extract aligned arrays from a point GeoDataFrame.

Parameters:

Name Type Description Default
gdf 'gpd.GeoDataFrame'

Input GeoDataFrame with Point geometry.

required
x_cols Optional[Sequence[str]]

Feature columns. When omitted, all numeric columns except y are used.

None
y_col Optional[str]

Target column. When omitted, y is returned as None.

None
dropna bool

Remove rows containing NaN or infinite values in X, y, or coordinates.

True

Returns:

Type Description
Tuple[ndarray, Optional[ndarray], ndarray]

X, y, coords: Floating-point feature matrix, optional target vector, and coordinates.

Examples:

>>> from pygwrx.io import from_geodataframe
>>> X, y, coords = from_geodataframe(
...     gdf,
...     x_cols=["income", "population"],
...     y_col="house_price",
... )
Source code in src/pygwrx/io/data.py
def from_geodataframe(
    gdf: "gpd.GeoDataFrame",
    x_cols: Optional[Sequence[str]] = None,
    y_col: Optional[str] = None,
    *,
    dropna: bool = True,
) -> Tuple[np.ndarray, Optional[np.ndarray], np.ndarray]:
    """Extract aligned arrays from a point GeoDataFrame.

    Args:
        gdf: Input GeoDataFrame with Point geometry.
        x_cols: Feature columns. When omitted, all numeric columns except y are used.
        y_col: Target column. When omitted, y is returned as ``None``.
        dropna: Remove rows containing NaN or infinite values in X, y, or coordinates.

    Returns:
        X, y, coords: Floating-point feature matrix, optional target vector, and coordinates.

    Examples:
        >>> from pygwrx.io import from_geodataframe
        >>> X, y, coords = from_geodataframe(
        ...     gdf,
        ...     x_cols=["income", "population"],
        ...     y_col="house_price",
        ... )
    """
    geopandas = _require_geopandas(purpose="GeoDataFrame conversion")
    if not isinstance(gdf, geopandas.GeoDataFrame):
        raise TypeError("gdf must be a geopandas.GeoDataFrame.")
    if not isinstance(dropna, (bool, np.bool_)):
        raise TypeError("dropna must be a boolean.")
    if y_col is not None and (not isinstance(y_col, str) or not y_col.strip()):
        raise TypeError("y_col must be a non-empty string or None.")

    x_columns = _normalize_column_names(x_cols, parameter_name="x_cols")
    if y_col is not None:
        _validate_columns(gdf, [y_col], role="target")

    if x_columns is None:
        geometry_name = gdf.geometry.name
        excluded = {geometry_name}
        if y_col is not None:
            excluded.add(y_col)
        x_columns = [
            column
            for column in gdf.select_dtypes(include=[np.number]).columns
            if column not in excluded
        ]
        if not x_columns:
            raise ValueError(
                "No numeric feature columns were found. Provide x_cols explicitly."
            )
    else:
        _validate_columns(gdf, x_columns, role="feature")

    if y_col is not None and y_col in x_columns:
        raise ValueError("y_col cannot also appear in x_cols.")

    X = _numeric_frame(gdf, x_columns).to_numpy(dtype=float)
    y = (
        pd.to_numeric(gdf[y_col], errors="coerce").to_numpy(dtype=float)
        if y_col is not None
        else None
    )
    coords = _extract_point_coordinates(gdf)

    if dropna:
        mask = _finite_row_mask(X, y, coords)
        if not bool(mask.any()):
            raise ValueError(
                "No valid rows remain after removing NaN and infinite values."
            )
        X = X[mask]
        coords = coords[mask]
        if y is not None:
            y = y[mask]

    return X, y, coords

save_results

Save model results to CSV, Parquet, Shapefile, GeoJSON, or GeoPackage.

Property Value
Type function
Import from pygwrx.io import save_results
Signature save_results(results: "Union[np.ndarray, pd.DataFrame, 'gpd.GeoDataFrame']", filepath: 'PathLike', format: 'Optional[str]' = None) -> 'Path'
Maintained example examples/io/02_tabular_roundtrip.py

save_results

save_results(
    results: Union[ndarray, DataFrame, "gpd.GeoDataFrame"],
    filepath: PathLike,
    format: Optional[str] = None,
) -> Path

Save model results to CSV, Parquet, Shapefile, GeoJSON, or GeoPackage.

Parameters:

Name Type Description Default
results Union[ndarray, DataFrame, 'gpd.GeoDataFrame']

Results to save. Spatial formats require a GeoDataFrame.

required
filepath PathLike

Output path. Parent directories are created automatically.

required
format Optional[str]

Explicit output format. When omitted, it is inferred from the suffix. Accepted values include csv, parquet, shapefile/shp, geojson, and gpkg.

None

Returns:

Type Description
Path

pathlib.Path: The file path written to disk.

Examples:

Save a table:

>>> from pygwrx.io import save_results
>>> output = save_results(results_df, "outputs/gwr_results.csv")

Save mapped coefficients:

>>> output = save_results(coef_gdf, "outputs/gwr_coefficients.geojson")
Source code in src/pygwrx/io/data.py
def save_results(
    results: Union[np.ndarray, pd.DataFrame, "gpd.GeoDataFrame"],
    filepath: PathLike,
    format: Optional[str] = None,
) -> Path:
    """Save model results to CSV, Parquet, Shapefile, GeoJSON, or GeoPackage.

    Args:
        results: Results to save. Spatial formats require a GeoDataFrame.
        filepath: Output path. Parent directories are created automatically.
        format: Explicit output format. When omitted, it is inferred from the suffix.
            Accepted values include ``csv``, ``parquet``, ``shapefile``/``shp``,
            ``geojson``, and ``gpkg``.

    Returns:
        pathlib.Path: The file path written to disk.

    Examples:
        Save a table:

        >>> from pygwrx.io import save_results
        >>> output = save_results(results_df, "outputs/gwr_results.csv")

        Save mapped coefficients:

        >>> output = save_results(coef_gdf, "outputs/gwr_coefficients.geojson")
    """
    if not isinstance(filepath, (str, Path)):
        raise TypeError("filepath must be a string or pathlib.Path.")

    path = Path(filepath).expanduser()
    output_format, path = _normalize_output_format(path, format)
    path.parent.mkdir(parents=True, exist_ok=True)

    geopandas = _optional_geopandas()
    if isinstance(results, np.ndarray):
        table: Union[pd.DataFrame, "gpd.GeoDataFrame"] = _array_to_dataframe(results)
    elif isinstance(results, pd.DataFrame) or (
        geopandas is not None and isinstance(results, geopandas.GeoDataFrame)
    ):
        table = results
    else:
        raise TypeError("results must be a NumPy array, DataFrame, or GeoDataFrame.")

    if output_format == "csv":
        table.to_csv(path, index=False)
    elif output_format == "parquet":
        table.to_parquet(path, index=False)
    else:
        geopandas = _require_geopandas(purpose=f"writing {output_format} files")
        if not isinstance(table, geopandas.GeoDataFrame):
            raise TypeError(
                f"The {output_format} format requires a GeoDataFrame with geometry."
            )
        if table.geometry.name not in table.columns:
            raise ValueError("The GeoDataFrame has no active geometry column.")
        if output_format == "shapefile":
            table.to_file(path, driver="ESRI Shapefile", index=False)
        elif output_format == "geojson":
            table.to_file(path, driver="GeoJSON", index=False)
        elif output_format == "gpkg":
            table.to_file(path, driver="GPKG", index=False)
        else:  # pragma: no cover - protected by format normalization
            raise RuntimeError(f"Unhandled output format: {output_format}")

    if not path.exists():
        raise OSError(f"The output file was not created: {path}")
    return path

Runnable examples used on this page

examples/io/02_tabular_roundtrip.py
# SPDX-FileCopyrightText: 2026 Jinghao Hu
# SPDX-License-Identifier: MIT

"""Load a CSV and save NumPy/DataFrame results in tabular formats."""

# Allow this script to run directly from any working directory.
import sys
from pathlib import Path

_PROJECT_ROOT = Path(__file__).resolve().parents[2]
_EXAMPLES_ROOT = _PROJECT_ROOT / "examples"
_SRC_ROOT = _PROJECT_ROOT / "src"
for _path in (_SRC_ROOT, _EXAMPLES_ROOT):
    if str(_path) not in sys.path:
        sys.path.insert(0, str(_path))

import numpy as np
import pandas as pd
from _common import OUTPUT_DIR

from pygwrx.io import load_data, save_results

source = OUTPUT_DIR / "io_input.csv"
pd.DataFrame(
    {
        "east": [0.0, 1.0, 2.0],
        "north": [1.0, 1.5, 2.0],
        "x1": [2.0, 3.0, 4.0],
        "x2": [1.0, 0.0, 1.0],
        "target": [5.0, 6.0, 8.0],
    }
).to_csv(source, index=False)
X, y, coords = load_data(
    source, x_cols=["x1", "x2"], y_col="target", coord_cols=("east", "north")
)
print("loaded=", X.shape, y.shape, coords.shape)
print("csv=", save_results(np.column_stack((y, X)), OUTPUT_DIR / "array_results.csv"))
try:
    print(
        "parquet=",
        save_results(
            pd.DataFrame(X, columns=["x1", "x2"]),
            OUTPUT_DIR / "frame_results",
            format="parquet",
        ),
    )
except ImportError as exc:
    print("Parquet is optional; install pyGWRx[parquet]:", exc)
examples/io/03_geodataframe_roundtrip.py
# SPDX-FileCopyrightText: 2026 Jinghao Hu
# SPDX-License-Identifier: MIT

"""Convert arrays to/from GeoDataFrame and save a GeoJSON result."""

# Allow this script to run directly from any working directory.
import sys
from pathlib import Path

_PROJECT_ROOT = Path(__file__).resolve().parents[2]
_EXAMPLES_ROOT = _PROJECT_ROOT / "examples"
_SRC_ROOT = _PROJECT_ROOT / "src"
for _path in (_SRC_ROOT, _EXAMPLES_ROOT):
    if str(_path) not in sys.path:
        sys.path.insert(0, str(_path))

from _common import OUTPUT_DIR, spatial_regression

from pygwrx.io import from_geodataframe, save_results, to_geodataframe

X, y, coords = spatial_regression(n=8, p=2)
gdf = to_geodataframe(
    X.to_numpy(),
    y,
    coords.to_numpy(),
    feature_names=list(X.columns),
    target_name="response",
    crs="EPSG:3857",
)
print(gdf.head())
X2, y2, coords2 = from_geodataframe(gdf, x_cols=list(X.columns), y_col="response")
print("roundtrip=", X2.shape, y2.shape, coords2.shape)
print("geojson=", save_results(gdf, OUTPUT_DIR / "spatial_results.geojson"))