Skip to content

cc() truncates periodic coordinates for integer input with fractional bounds #272

Open

Description

@ktz03

cc() silently truncates periodic coordinates when its input has an integer dtype and its exterior bounds are non-integral. The same numeric values produce different spline bases when passed as integers and floats, including when predicting with an existing DesignInfo.

Reproduced with the latest PyPI release, Patsy 1.0.3, and current master 8ce7441586fc228b9ff487a97e3a6ebe9fde6aad on Windows with Python 3.13.15, NumPy 2.5.4 and SciPy 1.18.1:

import numpy as np
from patsy import cc, dmatrix, build_design_matrices

x = np.arange(-1, 6)
kwargs = dict(knots=[1., 2., 3.], lower_bound=0.1, upper_bound=3.4)
integer_basis = cc(x, **kwargs)
float_basis = cc(x.astype(float), **kwargs)
print(np.max(np.abs(integer_basis - float_basis)))
# 1.1751149047931713 (expected: 0)

formula = "0 + cc(x, knots=[1., 2., 3.], lower_bound=0.1, upper_bound=3.4)"
training = dmatrix(formula, {"x": np.array([1., 2., 3.])})
integer_prediction = build_design_matrices([training.design_info], {"x": x})[0]
float_prediction = build_design_matrices([training.design_info], {"x": x.astype(float)})[0]
print(np.max(np.abs(integer_prediction - float_prediction)))
# 1.1751149047931713 (expected: 0)

For example, the period is 3.3, so x=4 maps to approximately 0.7. _map_cyclic() writes that coordinate into a copy of the integer input and truncates it to zero; its subsequent lower-bound mapping then moves it to another incorrect coordinate. The returned design matrix contains no warning about this loss of precision.

Integer and float input agree for points inside the bounds, for whole-number exterior bounds, and for the corresponding natural spline cr() control. A fix can convert integer coordinates to floating point before periodic mapping while preserving the existing floating-point input dtype and input immutability.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions