Skip to content

ENH: Von Mises distribution fit - #17585

Merged
tupui merged 16 commits into
scipy:mainfrom
dschmitz89:vonmises_fit
Dec 26, 2022
Merged

tupui merged 16 commits into
scipy:mainfrom
dschmitz89:vonmises_fit

Conversation

@dschmitz89

Copy link
Copy Markdown
Contributor

Reference issue

Closes #17576

What does this implement/fix?

Overrides the fit method of the von mises distribution.

Additional information

This is still pretty rough, mainly to get review started. The math should be pretty solid but I count on the reviewer's knowledge about how to properly align all possible fit cases.

In my opinion, the loc/shape/scale parametrization makes contributing to the distributions in stats unfortunately much harder. Maybe I did not find all dev doc about it but it just feels very much unintuitive, especially for a circular distribution.

@tupui tupui added scipy.stats enhancement A new feature or improvement labels Dec 12, 2022

@tupui tupui left a comment •

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.

Thanks @dschmitz89 for the PR.

It needs a bit more tests I think to show it behaves nicely on a wide range of params. It does not necessarily need to be in the test suite in the end, but that would be good to see some evidence here.

Also CI failures are related to this PR.

In my opinion, the loc/shape/scale parametrization makes contributing to the distributions in stats unfortunately much harder. Maybe I did not find all dev doc about it but it just feels very much unintuitive, especially for a circular distribution.

Yes this is something we want to address moving forward when we will be doing an overhaul of the distribution infrastructure (we did not start yet, but it's planned for 2nd Quarter next year.)

Comment thread scipy/stats/tests/test_distributions.py Outdated
Comment on lines +215 to +216
loc_vector = np.array([np.cos(loc), np.sin(loc)])
loc_vector_fit = np.array([np.cos(loc_fit), np.sin(loc_fit)])

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.

Seems unnecessary, vs the other test

Comment thread scipy/stats/_continuous_distns.py Resolved
@dschmitz89

dschmitz89 commented Dec 14, 2022 •

Copy link
Copy Markdown
Contributor Author

Test failures come from von_mises_line, not von_mises itself. Had not checked those tests before but was able to reproduce locally now. I am not sure how to best proceed with it.

The only easy but probably tedious solution to me seems to make von_mises_line its own distribution. Currently it is just another instance of von_mises.

Implemented a check if the distribution is an instance of vonmises_line by checking its support. Not pretty but solved the problem AFAICS.

@dschmitz89

Copy link
Copy Markdown
Contributor Author

The latest CI failure seems unrelated to this PR.

@tupui

tupui commented Dec 16, 2022

Copy link
Copy Markdown
Member

The latest CI failure seems unrelated to this PR.

FYI, we have some failures in main as well on Linux and Cirrus is flaky.

@dschmitz89

dschmitz89 commented Dec 17, 2022 •

Copy link
Copy Markdown
Contributor Author

More extensive testing results for $\mu=\frac{\pi}{2}$ and varying $\kappa$.

VonMisesFitTest

Details
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

def test_fit(mu_ref, kappa_ref, n_samples, n_trials):
    angular_errors = np.zeros((n_trials, ))
    kappa_fitted = np.zeros((n_trials, ))

    for i in range(n_trials):
        samples = stats.vonmises(loc=mu_ref, kappa=kappa_ref).rvs(n_samples)
        mu, kappa =stats.vonmises.fit(samples)
        kappa_fitted[i] = kappa 
        angular_errors[i] = np.arccos(np.cos(mu)*np.cos(mu_ref) + np.sin(mu)*np.sin(mu_ref))
    mu_error_avg = np.mean(angular_errors)
    kappa_rel_error_avg = np.mean(np.abs((kappa_fitted - kappa_ref))/kappa_ref)
    return mu_error_avg, kappa_rel_error_avg

mu_ref = 0.5 * np.pi
kappas = np.linspace(0.1, 100, 50)
mu_mean_angular_error = np.zeros_like(kappas)
kappa_mean_rel_error = np.zeros_like(kappas)
for index, kappa in enumerate(kappas):
    mu_error_avg, kappa_rel_error_avg = test_fit(mu_ref=mu_ref, kappa_ref=kappa, n_samples=100000, n_trials=50)
    mu_mean_angular_error [index] = mu_error_avg
    kappa_mean_rel_error[index] = kappa_rel_error_avg

plt.plot(kappas, mu_mean_angular_error , label="$\mu$ angular error")
plt.plot(kappas, kappa_mean_rel_error, label="$\kappa$ relative error")
plt.xlabel("$\kappa$")
plt.legend()


data, fshape, floc, fscale = _check_fit_input_parameters(self, data,
args, kwds)
if self.a == -np.pi:

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.

Not sure this is the preferred way but it allows vonmises_line to be untouched by this PR.

@dschmitz89

Copy link
Copy Markdown
Contributor Author

From my side this is ready for review again.

Changes:

  • The fit method only accpets data in the range [-pi, pi] now to comply with the support of the distribution.
  • The PR only affects vonmises, not vonmises_line

@tupui tupui left a comment

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.

Thanks @dschmitz89 I don't have much more comments.

Comment thread scipy/stats/tests/test_distributions.py Resolved
@tupui tupui added this to the 1.11.0 milestone Dec 18, 2022
@dschmitz89

Copy link
Copy Markdown
Contributor Author

Thanks @tupui :).

Just noticed one thing though: technically, this might break something because so far vonmises.fit accepted any data I think. CC @mdhaber

@mdhaber

mdhaber commented Dec 18, 2022

Copy link
Copy Markdown
Contributor

The fit method only accpets data in the range [-pi, pi] now

Why not wrap the data?

@dschmitz89

Copy link
Copy Markdown
Contributor Author

The fit method only accpets data in the range [-pi, pi] now

Why not wrap the data?

I am always a little sceptical when a user can input pretty much anything. But here you are probably right that it is the better solution, people might be relying on this.

So in principle it would be enough to add np.mod(data, 2*np.pi) before and add a test with shifted/non shifted data.

@tupui

tupui commented Dec 18, 2022

Copy link
Copy Markdown
Member

But then maybe we need some notes in the doc if we don't want a warning.

@dschmitz89

Copy link
Copy Markdown
Contributor Author

@tupui @mdhaber feel free to push to this branch whatever is needed to get this over the finish line, from my side the math and tests are done.

@tupui tupui left a comment

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.

LGTM and we can still iterate before the next release if we need.

Thanks again @dschmitz89

@tupui
tupui merged commit d9f75db into scipy:main Dec 26, 2022
windows-server-2003 pushed a commit to windows-server-2003/scipy that referenced this pull request Jan 9, 2023
@dschmitz89
dschmitz89 deleted the vonmises_fit branch July 18, 2023 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement A new feature or improvement scipy.stats

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ENH: override fit method for von mises

3 participants