Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compositional Data Analysis modules #3763

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

mortonjt
Copy link
Contributor

This addresses #3560 and #3537

Things that need to be touched up in this PR

  • Docs
  • Changelog

@josef-pkt not sure exactly where this module should belong - do you have any better recommendations?

@mortonjt mortonjt changed the title Composition Compositional Data Analysis modules Jun 19, 2017
@josef-pkt
Copy link
Member

It doesn't really fit into stats because we will use it mostly in support of models and not just simple statistics. Neither does it fit anywhere else very well. We have some generic transformation like box_cox in base, but this looks more specific.

So, I think we should start a new statsmodels composition or similarly named subfolder. We should be able to get enough functionality to justify it based on our discussion and my readings afterwards.
(I would prefer some more general model category but I don't have a clear idea yet. Most likely a similar topic for circular statistic will also need it's own subfolder, statsmodels.circular, if or when we get it.)

The main thing we will eventually have to look out for are circular imports. But this is a bit easier for us because of our structure of empty __init__.py and objects in and user imports from api.py.

I'm leaving for Europe in a few days and don't know when I will have time to look at it. I did enough background readings last time that I don't expect problems reviewing and merging this, although I won't try to understand all the details.

@josef-pkt
Copy link
Member

unit tests fail because of a typo in "statsmodels"

  File "/home/travis/miniconda2/envs/statsmodels-test/lib/python3.5/site-packages/statsmodels-0.8.0-py3.5-linux-x86_64.egg/statsmodels/composition/__init__.py", line 1, in <module>
    from statstmodels.composition._composition import (closure,
ImportError: No module named 'statstmodels'

I was still thinking about the directory name: I think the adjective "compositional" instead of "compositions" would be better. It sounds more like compositional data or models for compositional data. (e.g. circular versus circles)
(compositions sounds a bit like a writing class or Beethoven :)

@mortonjt
Copy link
Contributor Author

I think the name change makes sense. It'll also be good to have this not be confused with function compositions.

The imports have been fixed - let's see how that compiles in travis.

@josef-pkt
Copy link
Member

A semi-random thought:
Do you know what we would need to get loglikelihood and similar for the simplex-normal distribution (I don't remember what it was called be Aitchinson.)

I expect that we need to do some generic refactoring for better support of multivariate models in late summer or early fall, After that it would be a good time to see what elements we need beyond multivariate linear/Gaussian models to support the transformed endog y.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.1%) to 90.557% when pulling be960a7 on mortonjt:composition into ac25464 on statsmodels:master.

@mortonjt
Copy link
Contributor Author

That's actually a good question - I'm not entirely sure what that would involve. If I had to hazard guess, I would think that the log-likelihood for the normal distribution can be completely recycled, since the ilr transform is an isomorphism / isometry from S^D to R^D-1

But I'm not entirely sure - and I'm having trouble finding literature to back this up.

@mortonjt
Copy link
Contributor Author

Also, it looks like the tests are failing on python=2.7 tests. Having a bit of trouble reproducing these errors on my python=2.7 build. Is there an easy way to list the errors in the travis build?

@josef-pkt
Copy link
Member

add the bottom of the test log is the link to the "raw log" file. We use verbose mode which produces test output that is too long for the Travis display

https://s3.amazonaws.com/archive.travis-ci.org/jobs/246657849/log.txt?X-Amz-Expires=30&X-Amz-Date=20170626T163545Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJRYRXRSVGNKPKO5A/20170626/us-east-1/s3/aws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=840f0d95492bd9867fa221615c9d1ced415dd09540e3a118fa0be20c6bbd2c9d

maybe you forgot a from __future__ import division

======================================================================
ERROR: test_centralize (test_composition.CompositionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/tests/test_composition.py", line 260, in test_centralize
    cmat = centralize(closure(self.cdata1))
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 653, in centralize
    mat = closure(mat)
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 131, in closure
    raise ValueError("Input matrix cannot have rows with all zeros")
ValueError: Input matrix cannot have rows with all zeros

======================================================================
ERROR: test_clr (test_composition.CompositionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/tests/test_composition.py", line 218, in test_clr
    cmat = clr(closure(self.cdata1))
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 433, in clr
    mat = closure(mat)
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 131, in closure
    raise ValueError("Input matrix cannot have rows with all zeros")
ValueError: Input matrix cannot have rows with all zeros

======================================================================
ERROR: test_inner (test_composition.CompositionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/tests/test_composition.py", line 159, in test_inner
    a = inner(self.cdata5, self.cdata5)
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 387, in inner
    a, b = clr(x), clr(y)
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 433, in clr
    mat = closure(mat)
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 131, in closure
    raise ValueError("Input matrix cannot have rows with all zeros")
ValueError: Input matrix cannot have rows with all zeros

======================================================================
ERROR: test_multiplicative_replacement (test_composition.CompositionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/tests/test_composition.py", line 180, in test_multiplicative_replacement
    amat = multiplicative_replacement(closure(self.cdata3))
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 188, in multiplicative_replacement
    mat = closure(mat)
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 131, in closure
    raise ValueError("Input matrix cannot have rows with all zeros")
ValueError: Input matrix cannot have rows with all zeros

======================================================================
ERROR: test_perturb (test_composition.CompositionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/tests/test_composition.py", line 80, in test_perturb
    closure(np.array([1, 1, 1])))
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 250, in perturb
    x, y = closure(x), closure(y)
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 131, in closure
    raise ValueError("Input matrix cannot have rows with all zeros")
ValueError: Input matrix cannot have rows with all zeros

======================================================================
ERROR: test_perturb_inv (test_composition.CompositionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/tests/test_composition.py", line 137, in test_perturb_inv
    closure([.1, .1, .1]))
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 299, in perturb_inv
    x, y = closure(x), closure(y)
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 131, in closure
    raise ValueError("Input matrix cannot have rows with all zeros")
ValueError: Input matrix cannot have rows with all zeros

======================================================================
ERROR: test_power (test_composition.CompositionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/tests/test_composition.py", line 115, in test_power
    pmat = power(closure(self.cdata1), 2)
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 345, in power
    x = closure(x)
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/_composition.py", line 131, in closure
    raise ValueError("Input matrix cannot have rows with all zeros")
ValueError: Input matrix cannot have rows with all zeros

======================================================================
FAIL: test_closure (test_composition.CompositionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/tests/test_composition.py", line 54, in test_closure
    [.4, .4, .2]]))
  File "/home/travis/miniconda2/envs/statsmodels-test/lib/python2.7/site-packages/numpy/testing/utils.py", line 1392, in assert_allclose
    verbose=verbose, header=header)
  File "/home/travis/miniconda2/envs/statsmodels-test/lib/python2.7/site-packages/numpy/testing/utils.py", line 739, in assert_array_compare
    raise AssertionError(msg)
AssertionError: 
Not equal to tolerance rtol=1e-07, atol=0

(mismatch 100.0%)
 x: array([[0, 0, 0],
       [0, 0, 0]])
 y: array([[ 0.2,  0.2,  0.6],
       [ 0.4,  0.4,  0.2]])

======================================================================
FAIL: test_ilr (test_composition.CompositionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/tests/test_composition.py", line 283, in test_ilr
    np.array([0.70710678, 0.40824829]))
  File "/home/travis/miniconda2/envs/statsmodels-test/lib/python2.7/site-packages/numpy/testing/utils.py", line 918, in assert_array_almost_equal
    precision=decimal)
  File "/home/travis/miniconda2/envs/statsmodels-test/lib/python2.7/site-packages/numpy/testing/utils.py", line 739, in assert_array_compare
    raise AssertionError(msg)
AssertionError: 
Arrays are not almost equal to 6 decimals

(mismatch 100.0%)
 x: array([ 0.,  0.])
 y: array([ 0.707107,  0.408248])

======================================================================
FAIL: test_ilr_basis (test_composition.CompositionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/tests/test_composition.py", line 312, in test_ilr_basis
    npt.assert_allclose(res, exp)
  File "/home/travis/miniconda2/envs/statsmodels-test/lib/python2.7/site-packages/numpy/testing/utils.py", line 1392, in assert_allclose
    verbose=verbose, header=header)
  File "/home/travis/miniconda2/envs/statsmodels-test/lib/python2.7/site-packages/numpy/testing/utils.py", line 702, in assert_array_compare
    chk_same_position(x_isnan, y_isnan, hasval='nan')
  File "/home/travis/miniconda2/envs/statsmodels-test/lib/python2.7/site-packages/numpy/testing/utils.py", line 682, in chk_same_position
    raise AssertionError(msg)
AssertionError: 
Not equal to tolerance rtol=1e-07, atol=0

x and y nan location mismatch:
 x: array([-1.628174, -1.528188, -1.439082, -1.35856 , -1.284982])
 y: array([ nan,  -0.,  -0.,  -0.,  -0.])

======================================================================
FAIL: test_ilr_inv (test_composition.CompositionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/tests/test_composition.py", line 326, in test_ilr_inv
    npt.assert_array_almost_equal(ilr_inv(ilr(mat)), mat)
  File "/home/travis/miniconda2/envs/statsmodels-test/lib/python2.7/site-packages/numpy/testing/utils.py", line 918, in assert_array_almost_equal
    precision=decimal)
  File "/home/travis/miniconda2/envs/statsmodels-test/lib/python2.7/site-packages/numpy/testing/utils.py", line 739, in assert_array_compare
    raise AssertionError(msg)
AssertionError: 
Arrays are not almost equal to 6 decimals

(mismatch 100.0%)
 x: array([ 0.333333,  0.333333,  0.333333])
 y: array([ 0.576117,  0.211942,  0.211942])

======================================================================
FAIL: test_ilr_inv_basis (test_composition.CompositionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/build/statsmodels/statsmodels/statsmodels/compositional/tests/test_composition.py", line 374, in test_ilr_inv_basis
    npt.assert_allclose(res, exp)
  File "/home/travis/miniconda2/envs/statsmodels-test/lib/python2.7/site-packages/numpy/testing/utils.py", line 1392, in assert_allclose
    verbose=verbose, header=header)
  File "/home/travis/miniconda2/envs/statsmodels-test/lib/python2.7/site-packages/numpy/testing/utils.py", line 702, in assert_array_compare
    chk_same_position(x_isnan, y_isnan, hasval='nan')
  File "/home/travis/miniconda2/envs/statsmodels-test/lib/python2.7/site-packages/numpy/testing/utils.py", line 682, in chk_same_position
    raise AssertionError(msg)
AssertionError: 
Not equal to tolerance rtol=1e-07, atol=0

x and y nan location mismatch:
 x: array([[ nan,  nan],
       [ 0.5,  0.5],
       [ 0.5,  0.5],...
 y: array([[ 0.090909,  0.909091],
       [ 0.103291,  0.896709],
       [ 0.11556 ,  0.88444 ],...

----------------------------------------------------------------------
Ran 5920 tests in 851.236s

FAILED (SKIP=55, errors=7, failures=5)

@josef-pkt
Copy link
Member

About the likelihood: One thing that wasn't clear to me is if we use the likelihood for the transformed variable (which they might call the Aitchinson measure) or whether we want the likelihood for the original data which would need the determinant of the Jacobian of the transformation. If we want to compare models with different transformations, then we would need the likelihood in the original endog y space.

But this can also wait until we have some explicit models that use these transformations. From some applications that I have read about they just use standard multivariate methods designed for the normal distribution case.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.1%) to 90.568% when pulling 0bd7794 on mortonjt:composition into ac25464 on statsmodels:master.

1 similar comment
@coveralls
Copy link

Coverage Status

Coverage increased (+0.1%) to 90.568% when pulling 0bd7794 on mortonjt:composition into ac25464 on statsmodels:master.

@josef-pkt
Copy link
Member

I canceled and restarted two of the jobs on travis because they seemed to be stuck in the waiting line.

All green now. The one appveyor fail is the test run getting stuck in statespace models, unrelated to this PR.

@mortonjt
Copy link
Contributor Author

For ilr normal models, it doesn't looks too bad to calculate the Jacobian
http://www.statistik.tuwien.ac.at/public/filz/papers/2011JAPS.pdf

Not sure what exactly this will involve when fitting this into the existing statsmodels framework - but I don't see any reason why we can't do this in another PR in the near future.

@mortonjt
Copy link
Contributor Author

mortonjt commented Jul 2, 2017

@josef-pkt it looks like that statespace module is still failing - but there are still other PRs in statsmodels that are passing. Any idea what is going on?

@josef-pkt
Copy link
Member

The statespace unit test have random functions that hang sometimes, in one out of three appveyor python in three quarters of the test runs (rough guess). It's somewhere inside linear algebra packages or in garbage collection, but it's essentially impossible to reproduce locally and we have no clear idea what might be the problem. We ignore those for now, and will eventually skip them on Windows.

@josef-pkt
Copy link
Member

I was reading partially through this PR (without trying to check/understand the math)
I think what we will need additional to the functions is a class that can be attached to a model like the link functions in GLM.
for example, when a base is selected for the transformation, then it would be more convenient to have an instance of a transformation class that keeps track of or attaches all the transformation arguments.

@mortonjt
Copy link
Contributor Author

mortonjt commented Jul 12, 2017

Awesome! Where is a good place to get started with that? Can definitely boot up another PR to handle the link functions.

While we are on the topic, note that the multinomial logistic regression can be rephrased as a GLM with an alr link function. Similarly, a GLM with an ilr link function should boil down to simplicial linear regression. So maybe the GLM with a ilr link function should behave similarly to MNLogit. Or maybe we could even refactor MNLogit so that it can be represented as a GLM. Any thoughts?

@josef-pkt
Copy link
Member

The connection to MNLogit is what triggered our initial discussion.
Currently GEE has MNLogit type link function and models but not GLM, discrete models including MNLogit have the link function and distribution hard coded as part of and integrated into the model.

I think it will be easier to focus initially on the compositional use case for e.g. ilr and alr link classes. Even if the structure and transformation is basically the same, there are differences in the details. I don't have a good enough overview of the common structure and different use cases or model types to be able to tell how a common generic design would look like at the current stage.

One difference in the underlying model structure is that GLM, GEE and discrete models have a model for the conditional expectation of y: E(y | x) = link_inverse(x b), while the logistic normal model works with E(link(y) | x) = x b.

So, I think what we need is something like ILRLink attached to a MultivariateOLS or VAR to transform endog for the estimation, and backtransform for results as needed

e.g.
MultivariateOLS(endog, exog, link_endog=IRLLink())
result.predict(which='original")
or result.predict(linear=True)
???

@mortonjt
Copy link
Contributor Author

mortonjt commented Jul 22, 2017

@josef-pkt that sounds like a plan. I just pushed in the ilr link function (had to derive the derivative by hand -- but it seems to work).

It would be totally awesome to have this linked to VAR! Haven't even considered that. It would also be awesome to have other multivariate regression methods with dependent responses, or multivariate response linear mixed effects models(but maybe a discussion for future PRs).

Yes, I agree -- the link function should be able to specify the transformations back and forth.

The only minor technical issue with this is that the ilr transform can't handle zeros.
while small pseudocounts are the standard to correct for this, pseudocounts that are too small can actually add tremendous bias. This discussion is a bit out of the scope of this PR, but having good imputation methods for the endog matrix will be preferable (a whole list of promising imputation techniques can be found here ). May even want to consider using MICE for this in the future.

Question : should we enable the ilr link function for the Gaussian family? That way, we can enable Gaussian distributions in the simplex? Also, are preferred ways to test this code? I see that the test_link.py just iterates through all of the link functions, but I don't see hard coded test cases for the individual link functions.

Are there any other gapping holes that need to be addressed in this PR?

@coveralls
Copy link

coveralls commented Jul 23, 2017

Coverage Status

Coverage increased (+0.5%) to 90.896% when pulling 96930f4 on mortonjt:composition into ac25464 on statsmodels:master.

@josef-pkt
Copy link
Member

I have too many different topics, but I'm still thinking on and off how to organize things.

another possible renaming suggestion:
I was thinking how to structure the future modules in the compositional directory
The main user facing addition will come in compositional_model (in analogy to other model directories).
The name of the current module would be better if it indicates tools or transformation. However, there is no clear pattern, eg. genmod and robust have topic module names, regression has _tools, stats has stattools, tsa has tsatools. Does compositional.compositions indicate the content of this module or is there a better name to indicate that it's mainly the supporting transformation functions?

I looked briefly at multivariate MultivariateOLS which is currently a stub model in support of MANOVA. I think we can create a subclass of it in compositional.compositional_model and add missing methods to _MultivariateOLS and the transformation specific parts to the compositional model and results classes.
This would work as multivariate version of your current use case using OLS, as far as I remember or understand your initial comments in the compositional issue.

@josef-pkt josef-pkt mentioned this pull request Oct 24, 2017
@mortonjt
Copy link
Contributor Author

Hi @josef-pkt how do you recommend moving forward with this module? Do you think the blocking factor here is the name of the module, or the MultivariateOLS refactor?

In particularly, do you still want the link functions in this PR? It isn't clear how applicable these are until the Multivariate Regression objects get refactored.

@josef-pkt
Copy link
Member

The main thing I thought at the end was confirming the name before merging.

Link functions and usage can wait for another PR, and when we know more what we need in those cases like for MultivariateOLS or similar.

BTW: I just spend a bit of time on SUR (seemingly unrelated regression, similar to MultivariateOLS but different regressors for different endog), and I should have a relatively compact version that should also allow for sparse or regularized correlation. (It is still a single estimation problem so cannot be very large.)
Other multivariate news: I looked again at multivariate mean and covariance hypothesis tests, this time with code.

@mortonjt
Copy link
Contributor Author

Ok! Knocked out the ilr link function!

Concerning the naming, that other thread mentioned having a tools folder.
What do you think about having a folder called statsmodels.compositional.comptools?

@coveralls
Copy link

coveralls commented Nov 14, 2017

Coverage Status

Coverage decreased (-8.5%) to 81.968% when pulling b898c94 on mortonjt:composition into ac25464 on statsmodels:master.

@codecov-io
Copy link

codecov-io commented Nov 14, 2017

Codecov Report

Merging #3763 into master will increase coverage by 0.37%.
The diff coverage is 96.64%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3763      +/-   ##
==========================================
+ Coverage      79%   79.37%   +0.37%     
==========================================
  Files         541      550       +9     
  Lines       80886    82074    +1188     
  Branches     9191     9281      +90     
==========================================
+ Hits        63901    65146    +1245     
+ Misses      14907    14829      -78     
- Partials     2078     2099      +21
Impacted Files Coverage Δ
statsmodels/compositional/api.py 0% <0%> (ø)
statsmodels/compositional/__init__.py 100% <100%> (ø)
statsmodels/compositional/_composition.py 93.75% <93.75%> (ø)
...tatsmodels/compositional/tests/test_composition.py 98.91% <98.91%> (ø)
statsmodels/tsa/statespace/tests/test_models.py 71.87% <0%> (-19.62%) ⬇️
statsmodels/nonparametric/_kernel_base.py 75.4% <0%> (-12.57%) ⬇️
statsmodels/tsa/filters/hp_filter.py 90.47% <0%> (-9.53%) ⬇️
statsmodels/tsa/filters/bk_filter.py 90.9% <0%> (-9.1%) ⬇️
statsmodels/tools/parallel.py 52% <0%> (-8%) ⬇️
statsmodels/tsa/filters/cf_filter.py 82.75% <0%> (-6.9%) ⬇️
... and 80 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ac25464...db69edd. Read the comment docs.

@josef-pkt
Copy link
Member

comptools sounds fine to me (I thought also about transformation, but this contains additional "tools" code.

I think it can be public, I don't remember if we had a discussion about leading underscores for this.

One thing: move the imports from __init__.py into an api.py.
If comptools is public, then we only need to add the main functions to the api.py. Helper functions can, when needed, be directly imported from statsmodels.composition.comptools

@mortonjt
Copy link
Contributor Author

Ok - I just copied the imports from __init__.py to api.py. Just to clarify, we want to enable both imports from statsmodels.compositional.comptools and statsmodels.compositional.comptools.api correct?

@josef-pkt
Copy link
Member

the api path is statsmodels.compositional.api (and not the extra statsmodels.compositional.comptools.api )

Once there are more functions/classes that users want to use with the general api, then the compositional.api will be imported into statsmodels/api.py like other subpackages, so we can also use

import statsmodels.api as sm
sm.compositional.<Tab>
sm.CompositionaMainModel

recommended path for library use that avoids loading unnecessary other parts is the explicit module path, e.g. import statsmodels.compositional.comptools as compt or the local api for all compositional objects import statsmodels.compositional.api as comp

(That's the pattern that we have in all main subpackages)

@coveralls
Copy link

Coverage Status

Coverage decreased (-8.5%) to 81.931% when pulling db69edd on mortonjt:composition into ac25464 on statsmodels:master.

1 similar comment
@coveralls
Copy link

Coverage Status

Coverage decreased (-8.5%) to 81.931% when pulling db69edd on mortonjt:composition into ac25464 on statsmodels:master.

@josef-pkt
Copy link
Member

The never ending naming game, before names protected by backwards compatibility policy:

I was thinking now of compotools while thinking also about other xxxtools. compotools would avoid being mistaken for compu(tational)tools, although it doesn't matter so much because of the import path..

I think we can merge this soon (included for 0.9). (After a detour to some multivariate statistics in the last two-three weeks, I should be back to merging.)

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

Successfully merging this pull request may close these issues.

None yet

4 participants