Skip to content

Add location and sign to KS test result - #17062

Merged
mdhaber merged 19 commits into
scipy:mainfrom
iosonofabio:issue_9389
Oct 23, 2022
Merged

mdhaber merged 19 commits into
scipy:mainfrom
iosonofabio:issue_9389

Conversation

@iosonofabio

Copy link
Copy Markdown
Contributor

Reference issue

Aims to close #9389

What does this implement/fix?

Moves the result of Kolmogorov-Smirnov tests (1 or 2 samples) to a "tuple bunch" with two additional attributes:

  • statistic_location indicating at what value of the stochastic variable (approximately) the maximal distance in CDF is reached
  • statistic_sign, which is only nonempty for alternative="two-sided", indicating whether the first distribution (for 2samp)/the empirical distribution (for 1samp) was above or below the second/theoretical distribution at the statistic_location. This is a cheap way to determine not only whether the distros are different, but also if they are, in what direction.

Additional information

Matt and I quickly discussed part 1, the statistic_location. I later realized having part 2 i.e. the sign is equally important for me and perhaps for other folks, and is a cheap addition in terms of performance.

I haven't adapted the part of the docstring that represents doctests yet, happy to do so but perhaps it'd be useful if one of the maintainers took a quick look the the draft PR first so I know if there are any major issues.

Thank you for your time and patience

@tylerjereddy tylerjereddy added scipy.stats enhancement A new feature or improvement labels Sep 20, 2022
@mdhaber

mdhaber commented Sep 20, 2022

Copy link
Copy Markdown
Contributor

@tupui @tirthasheshpatel @chrisb83 If you agree that this looks like a good idea, I can review this. There was a lot of user support in the original issue gh-9389, and they seem happy to have it added to the existing function, so I think it's fine to add these attributes rather than create a new function.

Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/_stats_py.py Outdated
Co-authored-by: Matt Haberland <mhaberla@calpoly.edu>
@mdhaber

mdhaber commented Sep 21, 2022

Copy link
Copy Markdown
Contributor

OK, looks like there's just one real failure now:

def test_nametuples_agree(self):
result = stats.kstest([1, 2], [3, 4])
assert_(isinstance(result, stats._stats_py.KstestResult))
result2 = stats._stats_py.Ks_2sampResult(result.statistic, result.pvalue)
assert_(isinstance(result2, stats._stats_py.Ks_2sampResult))
assert_equal(result, result2)

This Ks_2sampResult:

Ks_2sampResult = KstestResult

is no longer used anywhere, so I'd suggest just deleting the definition of it and deleting the test. That looks like it will fix the remaining failures.

Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/_stats_py.py Outdated
@tupui

tupui commented Sep 21, 2022

Copy link
Copy Markdown
Member

I am +0.5. I personally would go with a separate function. We don't really have a lot of extra info in other tests. Instead we could have a general function to get the distance between 2 distributions (our API is slim there) and it would make perfect sense to have the location there.

@mdhaber

mdhaber commented Sep 22, 2022 •

Copy link
Copy Markdown
Contributor

One part of my hesitation with adding a separate function is that if we were to add a function for the statistic used in each of our hypothesis tests, we'd double the number of functions in stats. I doubt we want to head in that direction, and I don't think the KS statistic is so special that is it deserves a separate function while the rest do not.

Also, it makes me think of Halton/Hammersley - those are actually different (Hammersley will take an additional input for the offset of the linear sweep), yet we're talking about combining them. Here, we'd duplicate the function that computes the statistic so that there are two nearly identical functions, the only difference being that one version has the location of the maximum difference and the other has a p-value?

We've started to add more outputs to hypothesis tests already (e.g. df and confidence interval methods in t-test), so I think this fits. We still need to be selective about what we add to keep the API clean, but there seems to be a fair amount of support for adding this somewhere, so I think an attribute is fine.

You're right, though - we could have a function for comparing two empirical CDFs, or an empirical CDF vs a theoretical CDF, with different distance metrics. The latter would be goodness_of_fit, basically, and the former wod be the equivalent function for two samples.

@tupui

tupui commented Sep 22, 2022

Copy link
Copy Markdown
Member

I agree that we don't want to duplicate the API with a location function for all tests.

Here it's just that for this specific test we do something very generic and useful otherwise which is to compute the distance between CDFs.

I am fine doing both as well. Adding now an output for the location and maybe latter have a separate distance function.

@iosonofabio

Copy link
Copy Markdown
Contributor Author

We could make an optional input argument called compute_Pvalue=True that skips the last part and is used just to compute the max distance and its location. Would that be a useful compromise in your opinion or just a mess? 😏

@mdhaber

mdhaber commented Sep 22, 2022

Copy link
Copy Markdown
Contributor

Mmm I'm more hesitant to add inputs to functions than outputs, and even more hesitant to add an input that instructs the function to add an output : )

It sounds like we have an agreement here - what @tupui said.

Adding now an output for the location and maybe latter have a separate [CDF] distance function.

@mdhaber

mdhaber commented Sep 24, 2022

Copy link
Copy Markdown
Contributor

So to sum up @iosonofabio we can continue with this PR. Please consider the adjustments recommended above and I can take a second pass. I'd also suggest adding a unit test in test_stats.py. a good test would be to confirm that the statistic and p-value result object can still be unpacked and match the attributes, and also that the new attributes are present and appropriate (if possible).

@iosonofabio

Copy link
Copy Markdown
Contributor Author

No problem, I'll get it done as soon as I get back to Oz (haven't slept in a while now...)

Thank you!

iosonofabio and others added 2 commits September 24, 2022 14:33
@mdhaber

mdhaber commented Oct 1, 2022 •

Copy link
Copy Markdown
Contributor

I saw you posted some new commits - are you ready for this to be reviewed again, or was there more to do?

@iosonofabio

Copy link
Copy Markdown
Contributor Author

Thanks @mdhaber - I was just overwhelmed in the last few days.

I have now added decent test functions, improved docstrings a little, and changed a piece of logic that you suggested and I believe was incorrect (please double check if you have time!).

If it passes CI, it is ready for you to take a look, thank you! Otherwise I'll just fix the errors first.

@mdhaber mdhaber left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @iosonofabio. The implementation looks perfect. My only comments are about the documentation and the tests.

Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/tests/test_stats.py Outdated
Co-authored-by: Matt Haberland <mhaberla@calpoly.edu>
Comment thread scipy/stats/_stats_py.py Outdated
Comment on lines +7640 to +7643
Value of `x` corresponding with the KS statistic; i.e., the
distance between the hypothesized cumulative distribution function
and the empirical distribution function is greatest at this
location.

@mdhaber mdhaber Oct 7, 2022 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this what you wanted statistic_location for ks_1samp to be?

This is what I expected it to be, but it's not that (as written). Instead, it is the CDF at which the statistic is measured.

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.

Oops, that explains why the ks_2samp tests are fine but I couldn't get the one sample tests to work... Will fix tonight, thank you

@mdhaber

mdhaber commented Oct 7, 2022

Copy link
Copy Markdown
Contributor

Please see iosonofabio#1.

MAINT: stats: suggestions for "Add location and sign to KS test result"
@iosonofabio
iosonofabio marked this pull request as ready for review October 7, 2022 22:48
@iosonofabio

Copy link
Copy Markdown
Contributor Author

Hey @mdhaber - thank you again for your PR. I merged it and added similar tests for the one-sample KS test.

I think this is ready to go in if you guys approve. Also happy to make other changes or, if you prefer, feel free to push commits into this PR directly 😄

@iosonofabio

iosonofabio commented Oct 11, 2022 via email

Copy link
Copy Markdown
Contributor Author

Comment thread scipy/stats/_stats_py.py Outdated
Comment thread scipy/stats/tests/test_stats.py Outdated
Comment thread scipy/stats/tests/test_stats.py Outdated
Comment thread scipy/stats/tests/test_stats.py Outdated
Comment thread scipy/stats/tests/test_stats.py Outdated
Comment thread scipy/stats/tests/test_stats.py Outdated
@iosonofabio

Copy link
Copy Markdown
Contributor Author

Thanks @mdhaber - implemented all requested changes. No further edits necessary AFAICT.

@mdhaber mdhaber left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It looks like the implementation in ks_1samp has been corrected and documentation is better. The main comments are about the tests. It seems that what needs to be tested is essentially the same for kstest, ks_1samp, and ks_2samp, but what is actually tested differs. Try to simplify the changes. We should see some symmetry in the testing (whetever is done for ks_1samp has a parallel for ks_2ssamp, and somehow we see that kstest is the same as those two).

Comment thread scipy/stats/tests/test_stats.py Outdated
@iosonofabio

Copy link
Copy Markdown
Contributor Author

@mdhaber Thanks for the comment. I disagree that symmetry is essential. The 1-sample KS test is against a continuous distribution, whereas the 2-sample test is against a discretization thereof, which incurs all kinds of aliasing effects. I have done my best to try and satisfy the conflicting requirements of simplicity and theoretical clarity. As of now, I unfortunately have no time left for this PR. I'm not trying to be rude, but I really have no more time for this.

Given we are at the finish line, I would be grateful if you could push any changes that you think would improve the PR, symmetry or otherwise, and merge when you think it's good enough.

Thank you

@mdhaber

mdhaber commented Oct 15, 2022

Copy link
Copy Markdown
Contributor

The comment about some symmetry was resolved by the two recent commits. (It really crossed paths with the first of them, even though it shows up afterwards.)

res3 = stats.kstest(xm, y, alternative=alternative, mode=mode)
assert_equal(np.asarray(res1), np.asarray(res3))

def test_nametuples_agree(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Rather than modifying this test, we can just remove it. It doesn't belong in test_mstats_basic.py if it's about stats objects anyway, and Ks_2sampResult is no longer used either in stats or mstats, so I think it's safe to remove this.

@mdhaber mdhaber left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved, but I'd like a second opinion before merging, since I had a lot to do with the documentation and tests.

@tirthasheshpatel tirthasheshpatel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good to me too! Thanks @iosonofabio @mdhaber! I initially thought that a new function would be better but, looking at this PR, it seems like either work just fine (with respect to API design choice). Since I haven't gone through all the reviews and comments on this PR, I would leave the decision to merge on @mdhaber.

@mdhaber

mdhaber commented Oct 23, 2022

Copy link
Copy Markdown
Contributor

Thanks @tirthasheshpatel! @tupui I'm going to merge now but if you have a chance for a quick glance, let me know if there is anything you'd like to change before the release and I'll open another PR.

@mdhaber
mdhaber merged commit 942679e into scipy:main Oct 23, 2022
@iosonofabio

Copy link
Copy Markdown
Contributor Author

Thanks @mdhaber and everyone for your hard work and guidance!

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.

Kolmogorov Smirnov 2 samples returning max distance location [Enhancement]

5 participants