[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a bug report for ndarray-stats\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Description**\nDescription of the bug.\n\n**Version Information**\n- `ndarray`: ???\n- `ndarray-stats`: ???\n- Rust: ???\n\nPlease make sure that:\n- the version of `ndarray-stats` you're using corresponds to the version of `ndarray` you're using\n- the version of the Rust compiler you're using is supported by the version of `ndarray-stats` you're using\n(See the \"Releases\" section of the README for correct version information.)\n\n**To Reproduce**\nExample code which doesn't work.\n\n**Expected behavior**\nDescription of what you expected to happen.\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: Continuous integration\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\nenv:\n  CARGO_TERM_COLOR: always\n  RUSTFLAGS: \"-D warnings\"\n\njobs:\n\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        rust:\n          - stable\n          - beta\n          - nightly\n          - 1.65.0  # MSRV\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ matrix.rust }}\n      - name: Build\n        run: cargo build --verbose\n      - name: Run tests\n        run: cargo test --verbose\n\n  cross_test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        include:\n          # 64-bit, big-endian\n          - rust: stable\n            target: s390x-unknown-linux-gnu\n          # 32-bit, little-endian\n          - rust: stable\n            target: i686-unknown-linux-gnu\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ matrix.rust }}\n          target: ${{ matrix.target }}\n      - name: Install cross\n        run: cargo install cross -f\n      - name: Build\n        run: cross build --verbose --target=${{ matrix.target }}\n      - name: Run tests\n        run: cross test --verbose --target=${{ matrix.target }}\n\n  format:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        rust:\n          - stable\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions-rs/toolchain@v1\n        with:\n          profile: minimal\n          toolchain: ${{ matrix.rust }}\n          override: true\n          components: rustfmt\n      - name: Rustfmt\n        run: cargo fmt -- --check\n\n  coverage:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        rust:\n          - nightly\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ matrix.rust }}\n      - name: Install tarpaulin\n        uses: taiki-e/cache-cargo-install-action@v2\n        with:\n          tool: cargo-tarpaulin\n      - name: Generate code coverage\n        run: cargo tarpaulin --verbose --all-features --workspace --timeout 120 --out Xml\n      - name: Upload to codecov.io\n        uses: codecov/codecov-action@v4\n        env:\n          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n        with:\n          fail_ci_if_error: true\n"
  },
  {
    "path": ".github/workflows/latest-deps.yml",
    "content": "name: Check Latest Dependencies\non:\n  schedule:\n    # Chosen so that it runs right before the international date line experiences the weekend.\n    # Since we're open source, that means globally we should be aware of it right when we have the most\n    # time to fix it.\n    #\n    # Sorry if this ruins your weekend, future maintainer...\n    - cron: '0 12 * * FRI'\n  workflow_dispatch: # For running manually\n  pull_request:\n    paths:\n      - '.github/workflows/latest-deps.yaml'\n\nenv:\n  CARGO_TERM_COLOR: always\n  HOST: x86_64-unknown-linux-gnu\n  RUSTFLAGS: \"-D warnings\"\n  MSRV: 1.65.0\n\njobs:\n  latest_deps_stable:\n    runs-on: ubuntu-latest\n    name: Check Latest Dependencies on Stable\n    steps:\n      - name: Check Out Repo\n        uses: actions/checkout@v4\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: stable\n      - name: Setup Mold Linker\n        uses: rui314/setup-mold@v1\n      - name: Setup Rust Cache\n        uses: Swatinem/rust-cache@v2\n      - name: Install nextest\n        uses: taiki-e/install-action@nextest\n      - name: Ensure latest dependencies\n        run: cargo update\n      - name: Run Tests\n        run: cargo nextest run\n\n  latest_deps_msrv:\n    runs-on: ubuntu-latest\n    name: Check Latest Dependencies on MSRV\n    steps:\n      - name: Check Out Repo\n        uses: actions/checkout@v4\n      - name: Install Stable Rust for Update\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: stable\n      - name: Setup Mold Linker\n        uses: rui314/setup-mold@v1\n      - name: Setup Rust Cache\n        uses: Swatinem/rust-cache@v2\n      - name: Install nextest\n        uses: taiki-e/install-action@nextest\n      - name: Ensure latest dependencies\n        # The difference is here between this and `latest_deps_stable`\n        run: CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS=\"fallback\" cargo update\n      - name: Install MSRV Rust for Test\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ env.MSRV }}\n      - name: Run Tests\n        run: cargo nextest run"
  },
  {
    "path": ".gitignore",
    "content": "/target\n**/*.rs.bk\n\n# IDE-related\ntags\nrusty-tags.vi\n.vscode"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"ndarray-stats\"\nversion = \"0.7.0\"\nauthors = [\n    \"Jim Turner <ndarray-stats@turner.link>\",\n    \"LukeMathWalker <rust@lpalmieri.com>\",\n]\nedition = \"2018\"\nrust-version = \"1.65.0\"\n\nlicense = \"MIT/Apache-2.0\"\n\nrepository = \"https://github.com/rust-ndarray/ndarray-stats\"\ndocumentation = \"https://docs.rs/ndarray-stats/\"\nreadme = \"README.md\"\n\ndescription = \"Statistical routines for the n-dimensional array data structures provided by ndarray.\"\n\nkeywords = [\"array\", \"multidimensional\", \"statistics\", \"matrix\", \"ndarray\"]\ncategories = [\"data-structures\", \"science\"]\n\n[dependencies]\nndarray = \"0.17.1\"\nnoisy_float = \"0.2.0\"\nnum-integer = \"0.1\"\nnum-traits = \"0.2\"\nrand = \"0.8.3\"\nitertools = { version = \"0.13\", default-features = false }\nindexmap = \"2.4\"\n\n[dev-dependencies]\nndarray = { version = \"0.17.1\", features = [\"approx\"] }\ncriterion = \"0.5.1\"\nquickcheck = { version = \"0.9.2\", default-features = false }\nndarray-rand = \"0.16.0\"\napprox = \"0.5\"\nquickcheck_macros = \"1.0.0\"\nnum-bigint = \"0.4.0\"\n\n[[bench]]\nname = \"sort\"\nharness = false\n\n[[bench]]\nname = \"summary_statistics\"\nharness = false\n\n[[bench]]\nname = \"deviation\"\nharness = false\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "Copyright 2018–2024 ndarray-stats developers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# ndarray-stats\n\n[![Coverage](https://codecov.io/gh/rust-ndarray/ndarray-stats/branch/master/graph/badge.svg)](https://codecov.io/gh/rust-ndarray/ndarray-stats)\n[![Dependencies status](https://deps.rs/repo/github/rust-ndarray/ndarray-stats/status.svg)](https://deps.rs/repo/github/rust-ndarray/ndarray-stats)\n[![Crate](https://img.shields.io/crates/v/ndarray-stats.svg)](https://crates.io/crates/ndarray-stats)\n[![Documentation](https://docs.rs/ndarray-stats/badge.svg)](https://docs.rs/ndarray-stats)\n\nThis crate provides statistical methods for [`ndarray`]'s `ArrayRef` type.\n\nCurrently available routines include:\n- order statistics (minimum, maximum, median, quantiles, etc.);\n- summary statistics (mean, skewness, kurtosis, central moments, etc.)\n- partitioning;\n- correlation analysis (covariance, pearson correlation);\n- measures from information theory (entropy, KL divergence, etc.);\n- deviation functions (distances, counts, errors, etc.);\n- histogram computation.\n\nSee the [documentation](https://docs.rs/ndarray-stats) for more information.\n\nPlease feel free to contribute new functionality! A roadmap can be found [here](https://github.com/rust-ndarray/ndarray-stats/issues/1).\n\n[`ndarray`]: https://github.com/rust-ndarray/ndarray\n\n## Using with Cargo\n\n```toml\n[dependencies]\nndarray = \"0.17.1\"\nndarray-stats = \"0.7.0\"\n```\n\n## Releases\n\n* **0.7.0**\n\n  * Breaking changes\n    * Minimum supported Rust version: `1.65.0`\n    * Updated to `ndarray:v0.17.1`\n\n* **0.6.0**\n\n  * Breaking changes\n    * Minimum supported Rust version: `1.64.0`\n    * Updated to `ndarray:v0.16.0`\n    * Updated to `approx:v0.5.0`\n\n  * Updated to `ndarray-rand:v0.15.0`\n  * Updated to `indexmap:v2.4`\n  * Updated to `itertools:v0.13`\n\n  *Contributors*: [@bluss](https://github.com/bluss)\n\n* **0.5.1**\n  * Fixed bug in implementation of `MaybeNaN::remove_nan_mut` for `f32` and\n    `f64` for views with non-standard layouts. Before this fix, the bug could\n    cause incorrect results, buffer overflows, etc., in this method and others\n    which use it. Thanks to [@JacekCzupyt](https://github.com/JacekCzupyt) for\n    reporting the issue (#89).\n  * Minor docs improvements.\n\n  *Contributors*: [@jturner314](https://github.com/jturner314), [@BenMoon](https://github.com/BenMoon)\n\n* **0.5.0**\n  * Breaking changes\n    * Minimum supported Rust version: `1.49.0`\n    * Updated to `ndarray:v0.15.0`\n\n  *Contributors*: [@Armavica](https://github.com/armavica), [@cassiersg](https://github.com/cassiersg)\n\n* **0.4.0**\n  * Breaking changes\n    * Minimum supported Rust version: `1.42.0`\n  * New functionality:\n    * Summary statistics:\n      * Weighted variance\n      * Weighted standard deviation\n  * Improvements / breaking changes:\n    * Documentation improvements for Histograms\n    * Updated to `ndarray:v0.14.0`\n \n  *Contributors*: [@munckymagik](https://github.com/munckymagik), [@nilgoyette](https://github.com/nilgoyette), [@LukeMathWalker](https://github.com/LukeMathWalker), [@lebensterben](https://github.com/lebensterben), [@xd009642](https://github.com/xd009642)\n\n* **0.3.0**\n\n  * Breaking changes\n    * Minimum supported Rust version: `1.37`\n  * New functionality:\n    * Deviation functions:\n      * Counts equal/unequal\n      * `l1`, `l2`, `linf` distances\n      * (Root) mean squared error\n      * Peak signal-to-noise ratio\n    * Summary statistics:\n      * Weighted sum\n      * Weighted mean\n  * Improvements / breaking changes:\n    * Updated to `ndarray:v0.13.0`\n  \n  *Contributors*: [@munckymagik](https://github.com/munckymagik), [@nilgoyette](https://github.com/nilgoyette), [@jturner314](https://github.com/jturner314), [@LukeMathWalker](https://github.com/LukeMathWalker)\n\n* **0.2.0**\n\n  * Breaking changes\n    * All `ndarray-stats`' extension traits are now impossible to implement by\n      users of the library (see [#34])\n    * Redesigned error handling across the whole crate, standardising on `Result`\n  * New functionality:\n    * Summary statistics:\n      * Harmonic mean\n      * Geometric mean\n      * Central moments\n      * Kurtosis\n      * Skewness\n    * Information theory:\n      * Entropy\n      * Cross-entropy\n      * Kullback-Leibler divergence\n    * Quantiles and order statistics:\n      * `argmin` / `argmin_skipnan`\n      * `argmax` / `argmax_skipnan`\n      * Optimized bulk quantile computation (`quantiles_mut`, `quantiles_axis_mut`)\n  * Fixes:\n    * Reduced occurrences of overflow for `interpolate::midpoint`\n\n  *Contributors*: [@jturner314](https://github.com/jturner314), [@LukeMathWalker](https://github.com/LukeMathWalker), [@phungleson](https://github.com/phungleson), [@munckymagik](https://github.com/munckymagik)\n\n  [#34]: https://github.com/rust-ndarray/ndarray-stats/issues/34\n\n* **0.1.0**\n\n  * Initial release by @LukeMathWalker and @jturner314.\n\n## Contributing\n\nPlease feel free to create issues and submit PRs.\n\n## License\n\nCopyright 2018–2024 `ndarray-stats` developers\n\nLicensed under the [Apache License, Version 2.0](LICENSE-APACHE), or the [MIT\nlicense](LICENSE-MIT), at your option. You may not use this project except in\ncompliance with those terms.\n"
  },
  {
    "path": "benches/deviation.rs",
    "content": "use criterion::{\n    black_box, criterion_group, criterion_main, AxisScale, Criterion, PlotConfiguration,\n};\nuse ndarray::prelude::*;\nuse ndarray_rand::rand_distr::Uniform;\nuse ndarray_rand::RandomExt;\nuse ndarray_stats::DeviationExt;\n\nfn sq_l2_dist(c: &mut Criterion) {\n    let lens = vec![10, 100, 1000, 10000];\n    let mut group = c.benchmark_group(\"sq_l2_dist\");\n    group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));\n    for len in &lens {\n        group.bench_with_input(format!(\"{}\", len), len, |b, &len| {\n            let data = Array::random(len, Uniform::new(0.0, 1.0).unwrap());\n            let data2 = Array::random(len, Uniform::new(0.0, 1.0).unwrap());\n\n            b.iter(|| black_box(data.sq_l2_dist(&data2).unwrap()))\n        });\n    }\n    group.finish();\n}\n\ncriterion_group! {\n    name = benches;\n    config = Criterion::default();\n    targets = sq_l2_dist\n}\ncriterion_main!(benches);\n"
  },
  {
    "path": "benches/sort.rs",
    "content": "use criterion::{\n    black_box, criterion_group, criterion_main, AxisScale, BatchSize, Criterion, PlotConfiguration,\n};\nuse ndarray::prelude::*;\nuse ndarray_stats::Sort1dExt;\nuse rand::prelude::*;\n\nfn get_from_sorted_mut(c: &mut Criterion) {\n    let lens = vec![10, 100, 1000, 10000];\n    let mut group = c.benchmark_group(\"get_from_sorted_mut\");\n    group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));\n    for len in &lens {\n        group.bench_with_input(format!(\"{}\", len), len, |b, &len| {\n            let mut rng = StdRng::seed_from_u64(42);\n            let mut data: Vec<_> = (0..len).collect();\n            data.shuffle(&mut rng);\n            let indices: Vec<_> = (0..len).step_by(len / 10).collect();\n            b.iter_batched(\n                || Array1::from(data.clone()),\n                |mut arr| {\n                    for &i in &indices {\n                        black_box(arr.get_from_sorted_mut(i));\n                    }\n                },\n                BatchSize::SmallInput,\n            )\n        });\n    }\n    group.finish();\n}\n\nfn get_many_from_sorted_mut(c: &mut Criterion) {\n    let lens = vec![10, 100, 1000, 10000];\n    let mut group = c.benchmark_group(\"get_many_from_sorted_mut\");\n    group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));\n    for len in &lens {\n        group.bench_with_input(format!(\"{}\", len), len, |b, &len| {\n            let mut rng = StdRng::seed_from_u64(42);\n            let mut data: Vec<_> = (0..len).collect();\n            data.shuffle(&mut rng);\n            let indices: Array1<_> = (0..len).step_by(len / 10).collect();\n            b.iter_batched(\n                || Array1::from(data.clone()),\n                |mut arr| {\n                    black_box(arr.get_many_from_sorted_mut(&indices));\n                },\n                BatchSize::SmallInput,\n            )\n        });\n    }\n    group.finish();\n}\n\ncriterion_group! {\n    name = benches;\n    config = Criterion::default();\n    targets = get_from_sorted_mut, get_many_from_sorted_mut\n}\ncriterion_main!(benches);\n"
  },
  {
    "path": "benches/summary_statistics.rs",
    "content": "use criterion::{\n    black_box, criterion_group, criterion_main, AxisScale, BatchSize, Criterion, PlotConfiguration,\n};\nuse ndarray::prelude::*;\nuse ndarray_rand::rand_distr::Uniform;\nuse ndarray_rand::RandomExt;\nuse ndarray_stats::SummaryStatisticsExt;\n\nfn weighted_std(c: &mut Criterion) {\n    let lens = vec![10, 100, 1000, 10000];\n    let mut group = c.benchmark_group(\"weighted_std\");\n    group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));\n    for len in &lens {\n        group.bench_with_input(format!(\"{}\", len), len, |b, &len| {\n            let data = Array::random(len, Uniform::new(0.0, 1.0).unwrap());\n            let mut weights = Array::random(len, Uniform::new(0.0, 1.0).unwrap());\n            weights /= weights.sum();\n            b.iter_batched(\n                || data.clone(),\n                |arr| {\n                    black_box(arr.weighted_std(&weights, 0.0).unwrap());\n                },\n                BatchSize::SmallInput,\n            )\n        });\n    }\n    group.finish();\n}\n\ncriterion_group! {\n    name = benches;\n    config = Criterion::default();\n    targets = weighted_std\n}\ncriterion_main!(benches);\n"
  },
  {
    "path": "codecov.yml",
    "content": "comment: off\ncoverage:\n  status:\n    project:\n      default:\n        target: auto\n        threshold: 2\n        base: auto\n    patch:\n      default:\n        target: auto\n        threshold: 2\n        base: auto\n"
  },
  {
    "path": "src/correlation.rs",
    "content": "use crate::errors::EmptyInput;\nuse ndarray::prelude::*;\nuse num_traits::{Float, FromPrimitive};\n\n/// Extension trait for `ndarray` providing functions\n/// to compute different correlation measures.\npub trait CorrelationExt<A> {\n    /// Return the covariance matrix `C` for a 2-dimensional\n    /// array of observations `M`.\n    ///\n    /// Let `(r, o)` be the shape of `M`:\n    /// - `r` is the number of random variables;\n    /// - `o` is the number of observations we have collected\n    /// for each random variable.\n    ///\n    /// Every column in `M` is an experiment: a single observation for each\n    /// random variable.\n    /// Each row in `M` contains all the observations for a certain random variable.\n    ///\n    /// The parameter `ddof` specifies the \"delta degrees of freedom\". For\n    /// example, to calculate the population covariance, use `ddof = 0`, or to\n    /// calculate the sample covariance (unbiased estimate), use `ddof = 1`.\n    ///\n    /// The covariance of two random variables is defined as:\n    ///\n    /// ```text\n    ///                1       n\n    /// cov(X, Y) = ――――――――   ∑ (xᵢ - x̅)(yᵢ - y̅)\n    ///             n - ddof  i=1\n    /// ```\n    ///\n    /// where\n    ///\n    /// ```text\n    ///     1   n\n    /// x̅ = ―   ∑ xᵢ\n    ///     n  i=1\n    /// ```\n    /// and similarly for ̅y.\n    ///\n    /// If `M` is empty (either zero observations or zero random variables), it returns `Err(EmptyInput)`.\n    ///\n    /// **Panics** if `ddof` is negative or greater than or equal to the number of\n    /// observations, or if the type cast of `n_observations` from `usize` to `A` fails.\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use ndarray::{aview2, arr2};\n    /// use ndarray_stats::CorrelationExt;\n    ///\n    /// let a = arr2(&[[1., 3., 5.],\n    ///                [2., 4., 6.]]);\n    /// let covariance = a.cov(1.).unwrap();\n    /// assert_eq!(\n    ///    covariance,\n    ///    aview2(&[[4., 4.], [4., 4.]])\n    /// );\n    /// ```\n    fn cov(&self, ddof: A) -> Result<Array2<A>, EmptyInput>\n    where\n        A: Float + FromPrimitive;\n\n    /// Return the [Pearson correlation coefficients](https://en.wikipedia.org/wiki/Pearson_correlation_coefficient)\n    /// for a 2-dimensional array of observations `M`.\n    ///\n    /// Let `(r, o)` be the shape of `M`:\n    /// - `r` is the number of random variables;\n    /// - `o` is the number of observations we have collected\n    /// for each random variable.\n    ///\n    /// Every column in `M` is an experiment: a single observation for each\n    /// random variable.\n    /// Each row in `M` contains all the observations for a certain random variable.\n    ///\n    /// The Pearson correlation coefficient of two random variables is defined as:\n    ///\n    /// ```text\n    ///              cov(X, Y)\n    /// rho(X, Y) = ――――――――――――\n    ///             std(X)std(Y)\n    /// ```\n    ///\n    /// Let `R` be the matrix returned by this function. Then\n    /// ```text\n    /// R_ij = rho(X_i, X_j)\n    /// ```\n    ///\n    /// If `M` is empty (either zero observations or zero random variables), it returns `Err(EmptyInput)`.\n    ///\n    /// **Panics** if the type cast of `n_observations` from `usize` to `A` fails or\n    /// if the standard deviation of one of the random variables is zero and\n    /// division by zero panics for type A.\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use approx;\n    /// use ndarray::arr2;\n    /// use ndarray_stats::CorrelationExt;\n    /// use approx::AbsDiffEq;\n    ///\n    /// let a = arr2(&[[1., 3., 5.],\n    ///                [2., 4., 6.]]);\n    /// let corr = a.pearson_correlation().unwrap();\n    /// let epsilon = 1e-7;\n    /// assert!(\n    ///     corr.abs_diff_eq(\n    ///         &arr2(&[\n    ///             [1., 1.],\n    ///             [1., 1.],\n    ///         ]),\n    ///         epsilon\n    ///     )\n    /// );\n    /// ```\n    fn pearson_correlation(&self) -> Result<Array2<A>, EmptyInput>\n    where\n        A: Float + FromPrimitive;\n\n    private_decl! {}\n}\n\nimpl<A: 'static> CorrelationExt<A> for ArrayRef2<A> {\n    fn cov(&self, ddof: A) -> Result<Array2<A>, EmptyInput>\n    where\n        A: Float + FromPrimitive,\n    {\n        let observation_axis = Axis(1);\n        let n_observations = A::from_usize(self.len_of(observation_axis)).unwrap();\n        let dof = if ddof >= n_observations {\n            panic!(\n                \"`ddof` needs to be strictly smaller than the \\\n                 number of observations provided for each \\\n                 random variable!\"\n            )\n        } else {\n            n_observations - ddof\n        };\n        let mean = self.mean_axis(observation_axis);\n        match mean {\n            Some(mean) => {\n                let denoised = self - mean.insert_axis(observation_axis);\n                let covariance = denoised.dot(&denoised.t());\n                Ok(covariance.mapv_into(|x| x / dof))\n            }\n            None => Err(EmptyInput),\n        }\n    }\n\n    fn pearson_correlation(&self) -> Result<Array2<A>, EmptyInput>\n    where\n        A: Float + FromPrimitive,\n    {\n        match self.dim() {\n            (n, m) if n > 0 && m > 0 => {\n                let observation_axis = Axis(1);\n                // The ddof value doesn't matter, as long as we use the same one\n                // for computing covariance and standard deviation\n                // We choose 0 as it is the smallest number admitted by std_axis\n                let ddof = A::zero();\n                let cov = self.cov(ddof).unwrap();\n                let std = self\n                    .std_axis(observation_axis, ddof)\n                    .insert_axis(observation_axis);\n                let std_matrix = std.dot(&std.t());\n                // element-wise division\n                Ok(cov / std_matrix)\n            }\n            _ => Err(EmptyInput),\n        }\n    }\n\n    private_impl! {}\n}\n\n#[cfg(test)]\nmod cov_tests {\n    use super::*;\n    use ndarray::array;\n    use ndarray_rand::rand;\n    use ndarray_rand::rand_distr::Uniform;\n    use ndarray_rand::RandomExt;\n    use quickcheck_macros::quickcheck;\n\n    #[quickcheck]\n    fn constant_random_variables_have_zero_covariance_matrix(value: f64) -> bool {\n        let n_random_variables = 3;\n        let n_observations = 4;\n        let a = Array::from_elem((n_random_variables, n_observations), value);\n        abs_diff_eq!(\n            a.cov(1.).unwrap(),\n            &Array::zeros((n_random_variables, n_random_variables)),\n            epsilon = 1e-8,\n        )\n    }\n\n    #[quickcheck]\n    fn covariance_matrix_is_symmetric(bound: f64) -> bool {\n        let n_random_variables = 3;\n        let n_observations = 4;\n        let a = Array::random(\n            (n_random_variables, n_observations),\n            Uniform::new(-bound.abs(), bound.abs()).unwrap(),\n        );\n        let covariance = a.cov(1.).unwrap();\n        abs_diff_eq!(covariance, &covariance.t(), epsilon = 1e-8)\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_invalid_ddof() {\n        let n_random_variables = 3;\n        let n_observations = 4;\n        let a = Array::random(\n            (n_random_variables, n_observations),\n            Uniform::new(0., 10.).unwrap(),\n        );\n        let invalid_ddof = (n_observations as f64) + rand::random::<f64>().abs();\n        let _ = a.cov(invalid_ddof);\n    }\n\n    #[test]\n    fn test_covariance_zero_variables() {\n        let a = Array2::<f32>::zeros((0, 2));\n        let cov = a.cov(1.);\n        assert!(cov.is_ok());\n        assert_eq!(cov.unwrap().shape(), &[0, 0]);\n    }\n\n    #[test]\n    fn test_covariance_zero_observations() {\n        let a = Array2::<f32>::zeros((2, 0));\n        // Negative ddof (-1 < 0) to avoid invalid-ddof panic\n        let cov = a.cov(-1.);\n        assert_eq!(cov, Err(EmptyInput));\n    }\n\n    #[test]\n    fn test_covariance_zero_variables_zero_observations() {\n        let a = Array2::<f32>::zeros((0, 0));\n        // Negative ddof (-1 < 0) to avoid invalid-ddof panic\n        let cov = a.cov(-1.);\n        assert_eq!(cov, Err(EmptyInput));\n    }\n\n    #[test]\n    fn test_covariance_for_random_array() {\n        let a = array![\n            [0.72009497, 0.12568055, 0.55705966, 0.5959984, 0.69471457],\n            [0.56717131, 0.47619486, 0.21526298, 0.88915366, 0.91971245],\n            [0.59044195, 0.10720363, 0.76573717, 0.54693675, 0.95923036],\n            [0.24102952, 0.131347, 0.11118028, 0.21451351, 0.30515539],\n            [0.26952473, 0.93079841, 0.8080893, 0.42814155, 0.24642258]\n        ];\n        let numpy_covariance = array![\n            [0.05786248, 0.02614063, 0.06446215, 0.01285105, -0.06443992],\n            [0.02614063, 0.08733569, 0.02436933, 0.01977437, -0.06715555],\n            [0.06446215, 0.02436933, 0.10052129, 0.01393589, -0.06129912],\n            [0.01285105, 0.01977437, 0.01393589, 0.00638795, -0.02355557],\n            [\n                -0.06443992,\n                -0.06715555,\n                -0.06129912,\n                -0.02355557,\n                0.09909855\n            ]\n        ];\n        assert_eq!(a.ndim(), 2);\n        assert_abs_diff_eq!(a.cov(1.).unwrap(), &numpy_covariance, epsilon = 1e-8);\n    }\n\n    #[test]\n    #[should_panic]\n    // We lose precision, hence the failing assert\n    fn test_covariance_for_badly_conditioned_array() {\n        let a: Array2<f64> = array![[1e12 + 1., 1e12 - 1.], [1e-6 + 1e-12, 1e-6 - 1e-12],];\n        let expected_covariance = array![[2., 2e-12], [2e-12, 2e-24]];\n        assert_abs_diff_eq!(a.cov(1.).unwrap(), &expected_covariance, epsilon = 1e-24);\n    }\n}\n\n#[cfg(test)]\nmod pearson_correlation_tests {\n    use super::*;\n    use ndarray::array;\n    use ndarray::Array;\n    use ndarray_rand::rand_distr::Uniform;\n    use ndarray_rand::RandomExt;\n    use quickcheck_macros::quickcheck;\n\n    #[quickcheck]\n    fn output_matrix_is_symmetric(bound: f64) -> bool {\n        let n_random_variables = 3;\n        let n_observations = 4;\n        let a = Array::random(\n            (n_random_variables, n_observations),\n            Uniform::new(-bound.abs(), bound.abs()).unwrap(),\n        );\n        let pearson_correlation = a.pearson_correlation().unwrap();\n        abs_diff_eq!(\n            pearson_correlation.view(),\n            pearson_correlation.t(),\n            epsilon = 1e-8\n        )\n    }\n\n    #[quickcheck]\n    fn constant_random_variables_have_nan_correlation(value: f64) -> bool {\n        let n_random_variables = 3;\n        let n_observations = 4;\n        let a = Array::from_elem((n_random_variables, n_observations), value);\n        let pearson_correlation = a.pearson_correlation();\n        pearson_correlation\n            .unwrap()\n            .iter()\n            .map(|x| x.is_nan())\n            .fold(true, |acc, flag| acc & flag)\n    }\n\n    #[test]\n    fn test_zero_variables() {\n        let a = Array2::<f32>::zeros((0, 2));\n        let pearson_correlation = a.pearson_correlation();\n        assert_eq!(pearson_correlation, Err(EmptyInput))\n    }\n\n    #[test]\n    fn test_zero_observations() {\n        let a = Array2::<f32>::zeros((2, 0));\n        let pearson = a.pearson_correlation();\n        assert_eq!(pearson, Err(EmptyInput));\n    }\n\n    #[test]\n    fn test_zero_variables_zero_observations() {\n        let a = Array2::<f32>::zeros((0, 0));\n        let pearson = a.pearson_correlation();\n        assert_eq!(pearson, Err(EmptyInput));\n    }\n\n    #[test]\n    fn test_for_random_array() {\n        let a = array![\n            [0.16351516, 0.56863268, 0.16924196, 0.72579120],\n            [0.44342453, 0.19834387, 0.25411802, 0.62462382],\n            [0.97162731, 0.29958849, 0.17338142, 0.80198342],\n            [0.91727132, 0.79817799, 0.62237124, 0.38970998],\n            [0.26979716, 0.20887228, 0.95454999, 0.96290785]\n        ];\n        let numpy_corrcoeff = array![\n            [1., 0.38089376, 0.08122504, -0.59931623, 0.1365648],\n            [0.38089376, 1., 0.80918429, -0.52615195, 0.38954398],\n            [0.08122504, 0.80918429, 1., 0.07134906, -0.17324776],\n            [-0.59931623, -0.52615195, 0.07134906, 1., -0.8743213],\n            [0.1365648, 0.38954398, -0.17324776, -0.8743213, 1.]\n        ];\n        assert_eq!(a.ndim(), 2);\n        assert_abs_diff_eq!(\n            a.pearson_correlation().unwrap(),\n            numpy_corrcoeff,\n            epsilon = 1e-7\n        );\n    }\n}\n"
  },
  {
    "path": "src/deviation.rs",
    "content": "use ndarray::{ArrayRef, Dimension, Zip};\nuse num_traits::{Signed, ToPrimitive};\nuse std::convert::Into;\nuse std::ops::AddAssign;\n\nuse crate::errors::MultiInputError;\n\n/// An extension trait for `ndarray` providing functions\n/// to compute different deviation measures.\npub trait DeviationExt<A, D>\nwhere\n    D: Dimension,\n{\n    /// Counts the number of indices at which the elements of the arrays `self`\n    /// and `other` are equal.\n    ///\n    /// The following **errors** may be returned:\n    ///\n    /// * `MultiInputError::EmptyInput` if `self` is empty\n    /// * `MultiInputError::ShapeMismatch` if `self` and `other` don't have the same shape\n    fn count_eq(&self, other: &ArrayRef<A, D>) -> Result<usize, MultiInputError>\n    where\n        A: PartialEq;\n\n    /// Counts the number of indices at which the elements of the arrays `self`\n    /// and `other` are not equal.\n    ///\n    /// The following **errors** may be returned:\n    ///\n    /// * `MultiInputError::EmptyInput` if `self` is empty\n    /// * `MultiInputError::ShapeMismatch` if `self` and `other` don't have the same shape\n    fn count_neq(&self, other: &ArrayRef<A, D>) -> Result<usize, MultiInputError>\n    where\n        A: PartialEq;\n\n    /// Computes the [squared L2 distance] between `self` and `other`.\n    ///\n    /// ```text\n    ///  n\n    ///  ∑  |aᵢ - bᵢ|²\n    /// i=1\n    /// ```\n    ///\n    /// where `self` is `a` and `other` is `b`.\n    ///\n    /// The following **errors** may be returned:\n    ///\n    /// * `MultiInputError::EmptyInput` if `self` is empty\n    /// * `MultiInputError::ShapeMismatch` if `self` and `other` don't have the same shape\n    ///\n    /// [squared L2 distance]: https://en.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance\n    fn sq_l2_dist(&self, other: &ArrayRef<A, D>) -> Result<A, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed;\n\n    /// Computes the [L2 distance] between `self` and `other`.\n    ///\n    /// ```text\n    ///      n\n    /// √ (  ∑  |aᵢ - bᵢ|² )\n    ///     i=1\n    /// ```\n    ///\n    /// where `self` is `a` and `other` is `b`.\n    ///\n    /// The following **errors** may be returned:\n    ///\n    /// * `MultiInputError::EmptyInput` if `self` is empty\n    /// * `MultiInputError::ShapeMismatch` if `self` and `other` don't have the same shape\n    ///\n    /// **Panics** if the type cast from `A` to `f64` fails.\n    ///\n    /// [L2 distance]: https://en.wikipedia.org/wiki/Euclidean_distance\n    fn l2_dist(&self, other: &ArrayRef<A, D>) -> Result<f64, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed + ToPrimitive;\n\n    /// Computes the [L1 distance] between `self` and `other`.\n    ///\n    /// ```text\n    ///  n\n    ///  ∑  |aᵢ - bᵢ|\n    /// i=1\n    /// ```\n    ///\n    /// where `self` is `a` and `other` is `b`.\n    ///\n    /// The following **errors** may be returned:\n    ///\n    /// * `MultiInputError::EmptyInput` if `self` is empty\n    /// * `MultiInputError::ShapeMismatch` if `self` and `other` don't have the same shape\n    ///\n    /// [L1 distance]: https://en.wikipedia.org/wiki/Taxicab_geometry\n    fn l1_dist(&self, other: &ArrayRef<A, D>) -> Result<A, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed;\n\n    /// Computes the [L∞ distance] between `self` and `other`.\n    ///\n    /// ```text\n    /// max(|aᵢ - bᵢ|)\n    ///  ᵢ\n    /// ```\n    ///\n    /// where `self` is `a` and `other` is `b`.\n    ///\n    /// The following **errors** may be returned:\n    ///\n    /// * `MultiInputError::EmptyInput` if `self` is empty\n    /// * `MultiInputError::ShapeMismatch` if `self` and `other` don't have the same shape\n    ///\n    /// [L∞ distance]: https://en.wikipedia.org/wiki/Chebyshev_distance\n    fn linf_dist(&self, other: &ArrayRef<A, D>) -> Result<A, MultiInputError>\n    where\n        A: Clone + PartialOrd + Signed;\n\n    /// Computes the [mean absolute error] between `self` and `other`.\n    ///\n    /// ```text\n    ///        n\n    /// 1/n *  ∑  |aᵢ - bᵢ|\n    ///       i=1\n    /// ```\n    ///\n    /// where `self` is `a` and `other` is `b`.\n    ///\n    /// The following **errors** may be returned:\n    ///\n    /// * `MultiInputError::EmptyInput` if `self` is empty\n    /// * `MultiInputError::ShapeMismatch` if `self` and `other` don't have the same shape\n    ///\n    /// **Panics** if the type cast from `A` to `f64` fails.\n    ///\n    /// [mean absolute error]: https://en.wikipedia.org/wiki/Mean_absolute_error\n    fn mean_abs_err(&self, other: &ArrayRef<A, D>) -> Result<f64, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed + ToPrimitive;\n\n    /// Computes the [mean squared error] between `self` and `other`.\n    ///\n    /// ```text\n    ///        n\n    /// 1/n *  ∑  |aᵢ - bᵢ|²\n    ///       i=1\n    /// ```\n    ///\n    /// where `self` is `a` and `other` is `b`.\n    ///\n    /// The following **errors** may be returned:\n    ///\n    /// * `MultiInputError::EmptyInput` if `self` is empty\n    /// * `MultiInputError::ShapeMismatch` if `self` and `other` don't have the same shape\n    ///\n    /// **Panics** if the type cast from `A` to `f64` fails.\n    ///\n    /// [mean squared error]: https://en.wikipedia.org/wiki/Mean_squared_error\n    fn mean_sq_err(&self, other: &ArrayRef<A, D>) -> Result<f64, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed + ToPrimitive;\n\n    /// Computes the unnormalized [root-mean-square error] between `self` and `other`.\n    ///\n    /// ```text\n    /// √ mse(a, b)\n    /// ```\n    ///\n    /// where `self` is `a`, `other` is `b` and `mse` is the mean-squared-error.\n    ///\n    /// The following **errors** may be returned:\n    ///\n    /// * `MultiInputError::EmptyInput` if `self` is empty\n    /// * `MultiInputError::ShapeMismatch` if `self` and `other` don't have the same shape\n    ///\n    /// **Panics** if the type cast from `A` to `f64` fails.\n    ///\n    /// [root-mean-square error]: https://en.wikipedia.org/wiki/Root-mean-square_deviation\n    fn root_mean_sq_err(&self, other: &ArrayRef<A, D>) -> Result<f64, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed + ToPrimitive;\n\n    /// Computes the [peak signal-to-noise ratio] between `self` and `other`.\n    ///\n    /// ```text\n    /// 10 * log10(maxv^2 / mse(a, b))\n    /// ```\n    ///\n    /// where `self` is `a`, `other` is `b`, `mse` is the mean-squared-error\n    /// and `maxv` is the maximum possible value either array can take.\n    ///\n    /// The following **errors** may be returned:\n    ///\n    /// * `MultiInputError::EmptyInput` if `self` is empty\n    /// * `MultiInputError::ShapeMismatch` if `self` and `other` don't have the same shape\n    ///\n    /// **Panics** if the type cast from `A` to `f64` fails.\n    ///\n    /// [peak signal-to-noise ratio]: https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio\n    fn peak_signal_to_noise_ratio(\n        &self,\n        other: &ArrayRef<A, D>,\n        maxv: A,\n    ) -> Result<f64, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed + ToPrimitive;\n\n    private_decl! {}\n}\n\nimpl<A, D> DeviationExt<A, D> for ArrayRef<A, D>\nwhere\n    D: Dimension,\n{\n    fn count_eq(&self, other: &ArrayRef<A, D>) -> Result<usize, MultiInputError>\n    where\n        A: PartialEq,\n    {\n        return_err_if_empty!(self);\n        return_err_unless_same_shape!(self, other);\n\n        let mut count = 0;\n\n        Zip::from(self).and(other).for_each(|a, b| {\n            if a == b {\n                count += 1;\n            }\n        });\n\n        Ok(count)\n    }\n\n    fn count_neq(&self, other: &ArrayRef<A, D>) -> Result<usize, MultiInputError>\n    where\n        A: PartialEq,\n    {\n        self.count_eq(other).map(|n_eq| self.len() - n_eq)\n    }\n\n    fn sq_l2_dist(&self, other: &ArrayRef<A, D>) -> Result<A, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed,\n    {\n        return_err_if_empty!(self);\n        return_err_unless_same_shape!(self, other);\n\n        let mut result = A::zero();\n\n        Zip::from(self).and(other).for_each(|self_i, other_i| {\n            let (a, b) = (self_i.clone(), other_i.clone());\n            let diff = a - b;\n            result += diff.clone() * diff;\n        });\n\n        Ok(result)\n    }\n\n    fn l2_dist(&self, other: &ArrayRef<A, D>) -> Result<f64, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed + ToPrimitive,\n    {\n        let sq_l2_dist = self\n            .sq_l2_dist(other)?\n            .to_f64()\n            .expect(\"failed cast from type A to f64\");\n\n        Ok(sq_l2_dist.sqrt())\n    }\n\n    fn l1_dist(&self, other: &ArrayRef<A, D>) -> Result<A, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed,\n    {\n        return_err_if_empty!(self);\n        return_err_unless_same_shape!(self, other);\n\n        let mut result = A::zero();\n\n        Zip::from(self).and(other).for_each(|self_i, other_i| {\n            let (a, b) = (self_i.clone(), other_i.clone());\n            result += (a - b).abs();\n        });\n\n        Ok(result)\n    }\n\n    fn linf_dist(&self, other: &ArrayRef<A, D>) -> Result<A, MultiInputError>\n    where\n        A: Clone + PartialOrd + Signed,\n    {\n        return_err_if_empty!(self);\n        return_err_unless_same_shape!(self, other);\n\n        let mut max = A::zero();\n\n        Zip::from(self).and(other).for_each(|self_i, other_i| {\n            let (a, b) = (self_i.clone(), other_i.clone());\n            let diff = (a - b).abs();\n            if diff > max {\n                max = diff;\n            }\n        });\n\n        Ok(max)\n    }\n\n    fn mean_abs_err(&self, other: &ArrayRef<A, D>) -> Result<f64, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed + ToPrimitive,\n    {\n        let l1_dist = self\n            .l1_dist(other)?\n            .to_f64()\n            .expect(\"failed cast from type A to f64\");\n        let n = self.len() as f64;\n\n        Ok(l1_dist / n)\n    }\n\n    fn mean_sq_err(&self, other: &ArrayRef<A, D>) -> Result<f64, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed + ToPrimitive,\n    {\n        let sq_l2_dist = self\n            .sq_l2_dist(other)?\n            .to_f64()\n            .expect(\"failed cast from type A to f64\");\n        let n = self.len() as f64;\n\n        Ok(sq_l2_dist / n)\n    }\n\n    fn root_mean_sq_err(&self, other: &ArrayRef<A, D>) -> Result<f64, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed + ToPrimitive,\n    {\n        let msd = self.mean_sq_err(other)?;\n        Ok(msd.sqrt())\n    }\n\n    fn peak_signal_to_noise_ratio(\n        &self,\n        other: &ArrayRef<A, D>,\n        maxv: A,\n    ) -> Result<f64, MultiInputError>\n    where\n        A: AddAssign + Clone + Signed + ToPrimitive,\n    {\n        let maxv_f = maxv.to_f64().expect(\"failed cast from type A to f64\");\n        let msd = self.mean_sq_err(&other)?;\n        let psnr = 10. * f64::log10(maxv_f * maxv_f / msd);\n\n        Ok(psnr)\n    }\n\n    private_impl! {}\n}\n"
  },
  {
    "path": "src/entropy.rs",
    "content": "//! Information theory (e.g. entropy, KL divergence, etc.).\nuse crate::errors::{EmptyInput, MultiInputError, ShapeMismatch};\nuse ndarray::{Array, ArrayRef, Dimension, Zip};\nuse num_traits::Float;\n\n/// Extension trait for `ndarray` providing methods\n/// to compute information theory quantities\n/// (e.g. entropy, Kullback–Leibler divergence, etc.).\npub trait EntropyExt<A, D>\nwhere\n    D: Dimension,\n{\n    /// Computes the [entropy] *S* of the array values, defined as\n    ///\n    /// ```text\n    ///       n\n    /// S = - ∑ xᵢ ln(xᵢ)\n    ///      i=1\n    /// ```\n    ///\n    /// If the array is empty, `Err(EmptyInput)` is returned.\n    ///\n    /// **Panics** if `ln` of any element in the array panics (which can occur for negative values for some `A`).\n    ///\n    /// ## Remarks\n    ///\n    /// The entropy is a measure used in [Information Theory]\n    /// to describe a probability distribution: it only make sense\n    /// when the array values sum to 1, with each entry between\n    /// 0 and 1 (extremes included).\n    ///\n    /// The array values are **not** normalised by this function before\n    /// computing the entropy to avoid introducing potentially\n    /// unnecessary numerical errors (e.g. if the array were to be already normalised).\n    ///\n    /// By definition, *xᵢ ln(xᵢ)* is set to 0 if *xᵢ* is 0.\n    ///\n    /// [entropy]: https://en.wikipedia.org/wiki/Entropy_(information_theory)\n    /// [Information Theory]: https://en.wikipedia.org/wiki/Information_theory\n    fn entropy(&self) -> Result<A, EmptyInput>\n    where\n        A: Float;\n\n    /// Computes the [Kullback-Leibler divergence] *Dₖₗ(p,q)* between two arrays,\n    /// where `self`=*p*.\n    ///\n    /// The Kullback-Leibler divergence is defined as:\n    ///\n    /// ```text\n    ///              n\n    /// Dₖₗ(p,q) = - ∑ pᵢ ln(qᵢ/pᵢ)\n    ///             i=1\n    /// ```\n    ///\n    /// If the arrays are empty, `Err(MultiInputError::EmptyInput)` is returned.\n    /// If the array shapes are not identical,\n    /// `Err(MultiInputError::ShapeMismatch)` is returned.\n    ///\n    /// **Panics** if, for a pair of elements *(pᵢ, qᵢ)* from *p* and *q*, computing\n    /// *ln(qᵢ/pᵢ)* is a panic cause for `A`.\n    ///\n    /// ## Remarks\n    ///\n    /// The Kullback-Leibler divergence is a measure used in [Information Theory]\n    /// to describe the relationship between two probability distribution: it only make sense\n    /// when each array sums to 1 with entries between 0 and 1 (extremes included).\n    ///\n    /// The array values are **not** normalised by this function before\n    /// computing the entropy to avoid introducing potentially\n    /// unnecessary numerical errors (e.g. if the array were to be already normalised).\n    ///\n    /// By definition, *pᵢ ln(qᵢ/pᵢ)* is set to 0 if *pᵢ* is 0.\n    ///\n    /// [Kullback-Leibler divergence]: https://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence\n    /// [Information Theory]: https://en.wikipedia.org/wiki/Information_theory\n    fn kl_divergence(&self, q: &ArrayRef<A, D>) -> Result<A, MultiInputError>\n    where\n        A: Float;\n\n    /// Computes the [cross entropy] *H(p,q)* between two arrays,\n    /// where `self`=*p*.\n    ///\n    /// The cross entropy is defined as:\n    ///\n    /// ```text\n    ///            n\n    /// H(p,q) = - ∑ pᵢ ln(qᵢ)\n    ///           i=1\n    /// ```\n    ///\n    /// If the arrays are empty, `Err(MultiInputError::EmptyInput)` is returned.\n    /// If the array shapes are not identical,\n    /// `Err(MultiInputError::ShapeMismatch)` is returned.\n    ///\n    /// **Panics** if any element in *q* is negative and taking the logarithm of a negative number\n    /// is a panic cause for `A`.\n    ///\n    /// ## Remarks\n    ///\n    /// The cross entropy is a measure used in [Information Theory]\n    /// to describe the relationship between two probability distributions: it only makes sense\n    /// when each array sums to 1 with entries between 0 and 1 (extremes included).\n    ///\n    /// The array values are **not** normalised by this function before\n    /// computing the entropy to avoid introducing potentially\n    /// unnecessary numerical errors (e.g. if the array were to be already normalised).\n    ///\n    /// The cross entropy is often used as an objective/loss function in\n    /// [optimization problems], including [machine learning].\n    ///\n    /// By definition, *pᵢ ln(qᵢ)* is set to 0 if *pᵢ* is 0.\n    ///\n    /// [cross entropy]: https://en.wikipedia.org/wiki/Cross-entropy\n    /// [Information Theory]: https://en.wikipedia.org/wiki/Information_theory\n    /// [optimization problems]: https://en.wikipedia.org/wiki/Cross-entropy_method\n    /// [machine learning]: https://en.wikipedia.org/wiki/Cross_entropy#Cross-entropy_error_function_and_logistic_regression\n    fn cross_entropy(&self, q: &ArrayRef<A, D>) -> Result<A, MultiInputError>\n    where\n        A: Float;\n\n    private_decl! {}\n}\n\nimpl<A, D> EntropyExt<A, D> for ArrayRef<A, D>\nwhere\n    D: Dimension,\n{\n    fn entropy(&self) -> Result<A, EmptyInput>\n    where\n        A: Float,\n    {\n        if self.is_empty() {\n            Err(EmptyInput)\n        } else {\n            let entropy = -self\n                .mapv(|x| {\n                    if x == A::zero() {\n                        A::zero()\n                    } else {\n                        x * x.ln()\n                    }\n                })\n                .sum();\n            Ok(entropy)\n        }\n    }\n\n    fn kl_divergence(&self, q: &ArrayRef<A, D>) -> Result<A, MultiInputError>\n    where\n        A: Float,\n    {\n        if self.is_empty() {\n            return Err(MultiInputError::EmptyInput);\n        }\n        if self.shape() != q.shape() {\n            return Err(ShapeMismatch {\n                first_shape: self.shape().to_vec(),\n                second_shape: q.shape().to_vec(),\n            }\n            .into());\n        }\n\n        let mut temp = Array::zeros(self.raw_dim());\n        Zip::from(&mut temp)\n            .and(self)\n            .and(q)\n            .for_each(|result, &p, &q| {\n                *result = {\n                    if p == A::zero() {\n                        A::zero()\n                    } else {\n                        p * (q / p).ln()\n                    }\n                }\n            });\n        let kl_divergence = -temp.sum();\n        Ok(kl_divergence)\n    }\n\n    fn cross_entropy(&self, q: &ArrayRef<A, D>) -> Result<A, MultiInputError>\n    where\n        A: Float,\n    {\n        if self.is_empty() {\n            return Err(MultiInputError::EmptyInput);\n        }\n        if self.shape() != q.shape() {\n            return Err(ShapeMismatch {\n                first_shape: self.shape().to_vec(),\n                second_shape: q.shape().to_vec(),\n            }\n            .into());\n        }\n\n        let mut temp = Array::zeros(self.raw_dim());\n        Zip::from(&mut temp)\n            .and(self)\n            .and(q)\n            .for_each(|result, &p, &q| {\n                *result = {\n                    if p == A::zero() {\n                        A::zero()\n                    } else {\n                        p * q.ln()\n                    }\n                }\n            });\n        let cross_entropy = -temp.sum();\n        Ok(cross_entropy)\n    }\n\n    private_impl! {}\n}\n\n#[cfg(test)]\nmod tests {\n    use super::EntropyExt;\n    use crate::errors::{EmptyInput, MultiInputError};\n    use approx::assert_abs_diff_eq;\n    use ndarray::{array, Array1};\n    use noisy_float::types::n64;\n    use std::f64;\n\n    #[test]\n    fn test_entropy_with_nan_values() {\n        let a = array![f64::NAN, 1.];\n        assert!(a.entropy().unwrap().is_nan());\n    }\n\n    #[test]\n    fn test_entropy_with_empty_array_of_floats() {\n        let a: Array1<f64> = array![];\n        assert_eq!(a.entropy(), Err(EmptyInput));\n    }\n\n    #[test]\n    fn test_entropy_with_array_of_floats() {\n        // Array of probability values - normalized and positive.\n        let a: Array1<f64> = array![\n            0.03602474, 0.01900344, 0.03510129, 0.03414964, 0.00525311, 0.03368976, 0.00065396,\n            0.02906146, 0.00063687, 0.01597306, 0.00787625, 0.00208243, 0.01450896, 0.01803418,\n            0.02055336, 0.03029759, 0.03323628, 0.01218822, 0.0001873, 0.01734179, 0.03521668,\n            0.02564429, 0.02421992, 0.03540229, 0.03497635, 0.03582331, 0.026558, 0.02460495,\n            0.02437716, 0.01212838, 0.00058464, 0.00335236, 0.02146745, 0.00930306, 0.01821588,\n            0.02381928, 0.02055073, 0.01483779, 0.02284741, 0.02251385, 0.00976694, 0.02864634,\n            0.00802828, 0.03464088, 0.03557152, 0.01398894, 0.01831756, 0.0227171, 0.00736204,\n            0.01866295,\n        ];\n        // Computed using scipy.stats.entropy\n        let expected_entropy = 3.721606155686918;\n\n        assert_abs_diff_eq!(a.entropy().unwrap(), expected_entropy, epsilon = 1e-6);\n    }\n\n    #[test]\n    fn test_cross_entropy_and_kl_with_nan_values() -> Result<(), MultiInputError> {\n        let a = array![f64::NAN, 1.];\n        let b = array![2., 1.];\n        assert!(a.cross_entropy(&b)?.is_nan());\n        assert!(b.cross_entropy(&a)?.is_nan());\n        assert!(a.kl_divergence(&b)?.is_nan());\n        assert!(b.kl_divergence(&a)?.is_nan());\n        Ok(())\n    }\n\n    #[test]\n    fn test_cross_entropy_and_kl_with_same_n_dimension_but_different_n_elements() {\n        let p = array![f64::NAN, 1.];\n        let q = array![2., 1., 5.];\n        assert!(q.cross_entropy(&p).is_err());\n        assert!(p.cross_entropy(&q).is_err());\n        assert!(q.kl_divergence(&p).is_err());\n        assert!(p.kl_divergence(&q).is_err());\n    }\n\n    #[test]\n    fn test_cross_entropy_and_kl_with_different_shape_but_same_n_elements() {\n        // p: 3x2, 6 elements\n        let p = array![[f64::NAN, 1.], [6., 7.], [10., 20.]];\n        // q: 2x3, 6 elements\n        let q = array![[2., 1., 5.], [1., 1., 7.],];\n        assert!(q.cross_entropy(&p).is_err());\n        assert!(p.cross_entropy(&q).is_err());\n        assert!(q.kl_divergence(&p).is_err());\n        assert!(p.kl_divergence(&q).is_err());\n    }\n\n    #[test]\n    fn test_cross_entropy_and_kl_with_empty_array_of_floats() {\n        let p: Array1<f64> = array![];\n        let q: Array1<f64> = array![];\n        assert!(p.cross_entropy(&q).unwrap_err().is_empty_input());\n        assert!(p.kl_divergence(&q).unwrap_err().is_empty_input());\n    }\n\n    #[test]\n    fn test_cross_entropy_and_kl_with_negative_qs() -> Result<(), MultiInputError> {\n        let p = array![1.];\n        let q = array![-1.];\n        let cross_entropy: f64 = p.cross_entropy(&q)?;\n        let kl_divergence: f64 = p.kl_divergence(&q)?;\n        assert!(cross_entropy.is_nan());\n        assert!(kl_divergence.is_nan());\n        Ok(())\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_cross_entropy_with_noisy_negative_qs() {\n        let p = array![n64(1.)];\n        let q = array![n64(-1.)];\n        let _ = p.cross_entropy(&q);\n    }\n\n    #[test]\n    #[should_panic]\n    fn test_kl_with_noisy_negative_qs() {\n        let p = array![n64(1.)];\n        let q = array![n64(-1.)];\n        let _ = p.kl_divergence(&q);\n    }\n\n    #[test]\n    fn test_cross_entropy_and_kl_with_zeroes_p() -> Result<(), MultiInputError> {\n        let p = array![0., 0.];\n        let q = array![0., 0.5];\n        assert_eq!(p.cross_entropy(&q)?, 0.);\n        assert_eq!(p.kl_divergence(&q)?, 0.);\n        Ok(())\n    }\n\n    #[test]\n    fn test_cross_entropy_and_kl_with_zeroes_q_and_different_data_ownership(\n    ) -> Result<(), MultiInputError> {\n        let p = array![0.5, 0.5];\n        let mut q = array![0.5, 0.];\n        assert_eq!(p.cross_entropy(&q.view_mut())?, f64::INFINITY);\n        assert_eq!(p.kl_divergence(&q.view_mut())?, f64::INFINITY);\n        Ok(())\n    }\n\n    #[test]\n    fn test_cross_entropy() -> Result<(), MultiInputError> {\n        // Arrays of probability values - normalized and positive.\n        let p: Array1<f64> = array![\n            0.05340169, 0.02508511, 0.03460454, 0.00352313, 0.07837615, 0.05859495, 0.05782189,\n            0.0471258, 0.05594036, 0.01630048, 0.07085162, 0.05365855, 0.01959158, 0.05020174,\n            0.03801479, 0.00092234, 0.08515856, 0.00580683, 0.0156542, 0.0860375, 0.0724246,\n            0.00727477, 0.01004402, 0.01854399, 0.03504082,\n        ];\n        let q: Array1<f64> = array![\n            0.06622616, 0.0478948, 0.03227816, 0.06460884, 0.05795974, 0.01377489, 0.05604812,\n            0.01202684, 0.01647579, 0.03392697, 0.01656126, 0.00867528, 0.0625685, 0.07381292,\n            0.05489067, 0.01385491, 0.03639174, 0.00511611, 0.05700415, 0.05183825, 0.06703064,\n            0.01813342, 0.0007763, 0.0735472, 0.05857833,\n        ];\n        // Computed using scipy.stats.entropy(p) + scipy.stats.entropy(p, q)\n        let expected_cross_entropy = 3.385347705020779;\n\n        assert_abs_diff_eq!(p.cross_entropy(&q)?, expected_cross_entropy, epsilon = 1e-6);\n        Ok(())\n    }\n\n    #[test]\n    fn test_kl() -> Result<(), MultiInputError> {\n        // Arrays of probability values - normalized and positive.\n        let p: Array1<f64> = array![\n            0.00150472, 0.01388706, 0.03495376, 0.03264211, 0.03067355, 0.02183501, 0.00137516,\n            0.02213802, 0.02745017, 0.02163975, 0.0324602, 0.03622766, 0.00782343, 0.00222498,\n            0.03028156, 0.02346124, 0.00071105, 0.00794496, 0.0127609, 0.02899124, 0.01281487,\n            0.0230803, 0.01531864, 0.00518158, 0.02233383, 0.0220279, 0.03196097, 0.03710063,\n            0.01817856, 0.03524661, 0.02902393, 0.00853364, 0.01255615, 0.03556958, 0.00400151,\n            0.01335932, 0.01864965, 0.02371322, 0.02026543, 0.0035375, 0.01988341, 0.02621831,\n            0.03564644, 0.01389121, 0.03151622, 0.03195532, 0.00717521, 0.03547256, 0.00371394,\n            0.01108706,\n        ];\n        let q: Array1<f64> = array![\n            0.02038386, 0.03143914, 0.02630206, 0.0171595, 0.0067072, 0.00911324, 0.02635717,\n            0.01269113, 0.0302361, 0.02243133, 0.01902902, 0.01297185, 0.02118908, 0.03309548,\n            0.01266687, 0.0184529, 0.01830936, 0.03430437, 0.02898924, 0.02238251, 0.0139771,\n            0.01879774, 0.02396583, 0.03019978, 0.01421278, 0.02078981, 0.03542451, 0.02887438,\n            0.01261783, 0.01014241, 0.03263407, 0.0095969, 0.01923903, 0.0051315, 0.00924686,\n            0.00148845, 0.00341391, 0.01480373, 0.01920798, 0.03519871, 0.03315135, 0.02099325,\n            0.03251755, 0.00337555, 0.03432165, 0.01763753, 0.02038337, 0.01923023, 0.01438769,\n            0.02082707,\n        ];\n        // Computed using scipy.stats.entropy(p, q)\n        let expected_kl = 0.3555862567800096;\n\n        assert_abs_diff_eq!(p.kl_divergence(&q)?, expected_kl, epsilon = 1e-6);\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/errors.rs",
    "content": "//! Custom errors returned from our methods and functions.\nuse noisy_float::types::N64;\nuse std::error::Error;\nuse std::fmt;\n\n/// An error that indicates that the input array was empty.\n#[derive(Clone, Debug, Eq, PartialEq)]\npub struct EmptyInput;\n\nimpl fmt::Display for EmptyInput {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"Empty input.\")\n    }\n}\n\nimpl Error for EmptyInput {}\n\n/// An error computing a minimum/maximum value.\n#[derive(Clone, Debug, Eq, PartialEq)]\npub enum MinMaxError {\n    /// The input was empty.\n    EmptyInput,\n    /// The ordering between a tested pair of values was undefined.\n    UndefinedOrder,\n}\n\nimpl fmt::Display for MinMaxError {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            MinMaxError::EmptyInput => write!(f, \"Empty input.\"),\n            MinMaxError::UndefinedOrder => {\n                write!(f, \"Undefined ordering between a tested pair of values.\")\n            }\n        }\n    }\n}\n\nimpl Error for MinMaxError {}\n\nimpl From<EmptyInput> for MinMaxError {\n    fn from(_: EmptyInput) -> MinMaxError {\n        MinMaxError::EmptyInput\n    }\n}\n\n/// An error used by methods and functions that take two arrays as argument and\n/// expect them to have exactly the same shape\n/// (e.g. `ShapeMismatch` is raised when `a.shape() == b.shape()` evaluates to `False`).\n#[derive(Clone, Debug, PartialEq)]\npub struct ShapeMismatch {\n    pub first_shape: Vec<usize>,\n    pub second_shape: Vec<usize>,\n}\n\nimpl fmt::Display for ShapeMismatch {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n            f,\n            \"Array shapes do not match: {:?} and {:?}.\",\n            self.first_shape, self.second_shape\n        )\n    }\n}\n\nimpl Error for ShapeMismatch {}\n\n/// An error for methods that take multiple non-empty array inputs.\n#[derive(Clone, Debug, PartialEq)]\npub enum MultiInputError {\n    /// One or more of the arrays were empty.\n    EmptyInput,\n    /// The arrays did not have the same shape.\n    ShapeMismatch(ShapeMismatch),\n}\n\nimpl MultiInputError {\n    /// Returns whether `self` is the `EmptyInput` variant.\n    pub fn is_empty_input(&self) -> bool {\n        match self {\n            MultiInputError::EmptyInput => true,\n            _ => false,\n        }\n    }\n\n    /// Returns whether `self` is the `ShapeMismatch` variant.\n    pub fn is_shape_mismatch(&self) -> bool {\n        match self {\n            MultiInputError::ShapeMismatch(_) => true,\n            _ => false,\n        }\n    }\n}\n\nimpl fmt::Display for MultiInputError {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            MultiInputError::EmptyInput => write!(f, \"Empty input.\"),\n            MultiInputError::ShapeMismatch(e) => write!(f, \"Shape mismatch: {}\", e),\n        }\n    }\n}\n\nimpl Error for MultiInputError {}\n\nimpl From<EmptyInput> for MultiInputError {\n    fn from(_: EmptyInput) -> Self {\n        MultiInputError::EmptyInput\n    }\n}\n\nimpl From<ShapeMismatch> for MultiInputError {\n    fn from(err: ShapeMismatch) -> Self {\n        MultiInputError::ShapeMismatch(err)\n    }\n}\n\n/// An error computing a quantile.\n#[derive(Debug, Clone, Eq, PartialEq)]\npub enum QuantileError {\n    /// The input was empty.\n    EmptyInput,\n    /// The `q` was not between `0.` and `1.` (inclusive).\n    InvalidQuantile(N64),\n}\n\nimpl fmt::Display for QuantileError {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            QuantileError::EmptyInput => write!(f, \"Empty input.\"),\n            QuantileError::InvalidQuantile(q) => {\n                write!(f, \"{:} is not between 0. and 1. (inclusive).\", q)\n            }\n        }\n    }\n}\n\nimpl Error for QuantileError {}\n\nimpl From<EmptyInput> for QuantileError {\n    fn from(_: EmptyInput) -> QuantileError {\n        QuantileError::EmptyInput\n    }\n}\n"
  },
  {
    "path": "src/histogram/bins.rs",
    "content": "#![warn(missing_docs, clippy::all, clippy::pedantic)]\n\nuse ndarray::prelude::*;\nuse std::ops::{Index, Range};\n\n/// A sorted collection of type `A` elements used to represent the boundaries of intervals, i.e.\n/// [`Bins`] on a 1-dimensional axis.\n///\n/// **Note** that all intervals are left-closed and right-open. See examples below.\n///\n/// # Examples\n///\n/// ```\n/// use ndarray_stats::histogram::{Bins, Edges};\n/// use noisy_float::types::n64;\n///\n/// let unit_edges = Edges::from(vec![n64(0.), n64(1.)]);\n/// let unit_interval = Bins::new(unit_edges);\n/// // left-closed\n/// assert_eq!(\n///     unit_interval.range_of(&n64(0.)).unwrap(),\n///     n64(0.)..n64(1.),\n/// );\n/// // right-open\n/// assert_eq!(\n///     unit_interval.range_of(&n64(1.)),\n///     None\n/// );\n/// ```\n///\n/// [`Bins`]: struct.Bins.html\n#[derive(Clone, Debug, Eq, PartialEq)]\npub struct Edges<A: Ord> {\n    edges: Vec<A>,\n}\n\nimpl<A: Ord> From<Vec<A>> for Edges<A> {\n    /// Converts a `Vec<A>` into an `Edges<A>`, consuming the edges.\n    /// The vector will be sorted in increasing order using an unstable sorting algorithm, with\n    /// duplicates removed.\n    ///\n    /// # Current implementation\n    ///\n    /// The current sorting algorithm is the same as [`std::slice::sort_unstable()`][sort],\n    /// which is based on [pattern-defeating quicksort][pdqsort].\n    ///\n    /// This sort is unstable (i.e., may reorder equal elements), in-place (i.e., does not allocate)\n    /// , and O(n log n) worst-case.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use ndarray::array;\n    /// use ndarray_stats::histogram::Edges;\n    ///\n    /// let edges = Edges::from(array![1, 15, 10, 10, 20]);\n    /// // The array gets sorted!\n    /// assert_eq!(\n    ///     edges[2],\n    ///     15\n    /// );\n    /// ```\n    ///\n    /// [sort]: https://doc.rust-lang.org/stable/std/primitive.slice.html#method.sort_unstable\n    /// [pdqsort]: https://github.com/orlp/pdqsort\n    fn from(mut edges: Vec<A>) -> Self {\n        // sort the array in-place\n        edges.sort_unstable();\n        // remove duplicates\n        edges.dedup();\n        Edges { edges }\n    }\n}\n\nimpl<A: Ord + Clone> From<Array1<A>> for Edges<A> {\n    /// Converts an `Array1<A>` into an `Edges<A>`, consuming the 1-dimensional array.\n    /// The array will be sorted in increasing order using an unstable sorting algorithm, with\n    /// duplicates removed.\n    ///\n    /// # Current implementation\n    ///\n    /// The current sorting algorithm is the same as [`std::slice::sort_unstable()`][sort],\n    /// which is based on [pattern-defeating quicksort][pdqsort].\n    ///\n    /// This sort is unstable (i.e., may reorder equal elements), in-place (i.e., does not allocate)\n    /// , and O(n log n) worst-case.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use ndarray_stats::histogram::Edges;\n    ///\n    /// let edges = Edges::from(vec![1, 15, 10, 20]);\n    /// // The vec gets sorted!\n    /// assert_eq!(\n    ///     edges[1],\n    ///     10\n    /// );\n    /// ```\n    ///\n    /// [sort]: https://doc.rust-lang.org/stable/std/primitive.slice.html#method.sort_unstable\n    /// [pdqsort]: https://github.com/orlp/pdqsort\n    fn from(edges: Array1<A>) -> Self {\n        let edges = edges.to_vec();\n        Self::from(edges)\n    }\n}\n\nimpl<A: Ord> Index<usize> for Edges<A> {\n    type Output = A;\n\n    /// Returns a reference to the `i`-th edge in `self`.\n    ///\n    /// # Panics\n    ///\n    /// Panics if the index `i` is out of bounds.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use ndarray_stats::histogram::Edges;\n    ///\n    /// let edges = Edges::from(vec![1, 5, 10, 20]);\n    /// assert_eq!(\n    ///     edges[1],\n    ///     5\n    /// );\n    /// ```\n    fn index(&self, i: usize) -> &Self::Output {\n        &self.edges[i]\n    }\n}\n\nimpl<A: Ord> Edges<A> {\n    /// Returns the number of edges in `self`.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use ndarray_stats::histogram::Edges;\n    /// use noisy_float::types::n64;\n    ///\n    /// let edges = Edges::from(vec![n64(0.), n64(1.), n64(3.)]);\n    /// assert_eq!(\n    ///     edges.len(),\n    ///     3\n    /// );\n    /// ```\n    #[must_use]\n    pub fn len(&self) -> usize {\n        self.edges.len()\n    }\n\n    /// Returns `true` if `self` contains no edges.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use ndarray_stats::histogram::Edges;\n    /// use noisy_float::types::{N64, n64};\n    ///\n    /// let edges = Edges::<N64>::from(vec![]);\n    /// assert_eq!(edges.is_empty(), true);\n    ///\n    /// let edges = Edges::from(vec![n64(0.), n64(2.), n64(5.)]);\n    /// assert_eq!(edges.is_empty(), false);\n    /// ```\n    #[must_use]\n    pub fn is_empty(&self) -> bool {\n        self.edges.is_empty()\n    }\n\n    /// Returns an immutable 1-dimensional array view of edges.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use ndarray::array;\n    /// use ndarray_stats::histogram::Edges;\n    ///\n    /// let edges = Edges::from(vec![0, 5, 3]);\n    /// assert_eq!(\n    ///     edges.as_array_view(),\n    ///     array![0, 3, 5].view()\n    /// );\n    /// ```\n    #[must_use]\n    pub fn as_array_view(&self) -> ArrayView1<'_, A> {\n        ArrayView1::from(&self.edges)\n    }\n\n    /// Returns indices of two consecutive `edges` in `self`, if the interval they represent\n    /// contains the given `value`, or returns `None` otherwise.\n    ///\n    /// That is to say, it returns\n    /// - `Some((left, right))`, where `left` and `right` are the indices of two consecutive edges\n    /// in `self` and `right == left + 1`, if `self[left] <= value < self[right]`;\n    /// - `None`, otherwise.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use ndarray_stats::histogram::Edges;\n    ///\n    /// let edges = Edges::from(vec![0, 2, 3]);\n    /// // `1` is in the interval [0, 2), whose indices are (0, 1)\n    /// assert_eq!(\n    ///     edges.indices_of(&1),\n    ///     Some((0, 1))\n    /// );\n    /// // `5` is not in any of intervals\n    /// assert_eq!(\n    ///     edges.indices_of(&5),\n    ///     None\n    /// );\n    /// ```\n    pub fn indices_of(&self, value: &A) -> Option<(usize, usize)> {\n        // binary search for the correct bin\n        let n_edges = self.len();\n        match self.edges.binary_search(value) {\n            Ok(i) if i == n_edges - 1 => None,\n            Ok(i) => Some((i, i + 1)),\n            Err(i) => match i {\n                0 => None,\n                j if j == n_edges => None,\n                j => Some((j - 1, j)),\n            },\n        }\n    }\n\n    /// Returns an iterator over the `edges` in `self`.\n    pub fn iter(&self) -> impl Iterator<Item = &A> {\n        self.edges.iter()\n    }\n}\n\n/// A sorted collection of non-overlapping 1-dimensional intervals.\n///\n/// **Note** that all intervals are left-closed and right-open.\n///\n/// # Examples\n///\n/// ```\n/// use ndarray_stats::histogram::{Edges, Bins};\n/// use noisy_float::types::n64;\n///\n/// let edges = Edges::from(vec![n64(0.), n64(1.), n64(2.)]);\n/// let bins = Bins::new(edges);\n/// // first bin\n/// assert_eq!(\n///     bins.index(0),\n///     n64(0.)..n64(1.) // n64(1.) is not included in the bin!\n/// );\n/// // second bin\n/// assert_eq!(\n///     bins.index(1),\n///     n64(1.)..n64(2.)\n/// );\n/// ```\n#[derive(Clone, Debug, Eq, PartialEq)]\npub struct Bins<A: Ord> {\n    edges: Edges<A>,\n}\n\nimpl<A: Ord> Bins<A> {\n    /// Returns a `Bins` instance where each bin corresponds to two consecutive members of the given\n    /// [`Edges`], consuming the edges.\n    ///\n    /// [`Edges`]: struct.Edges.html\n    #[must_use]\n    pub fn new(edges: Edges<A>) -> Self {\n        Bins { edges }\n    }\n\n    /// Returns the number of bins in `self`.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use ndarray_stats::histogram::{Edges, Bins};\n    /// use noisy_float::types::n64;\n    ///\n    /// let edges = Edges::from(vec![n64(0.), n64(1.), n64(2.)]);\n    /// let bins = Bins::new(edges);\n    /// assert_eq!(\n    ///     bins.len(),\n    ///     2\n    /// );\n    /// ```\n    #[must_use]\n    pub fn len(&self) -> usize {\n        match self.edges.len() {\n            0 => 0,\n            n => n - 1,\n        }\n    }\n\n    /// Returns `true` if the number of bins is zero, i.e. if the number of edges is 0 or 1.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use ndarray_stats::histogram::{Edges, Bins};\n    /// use noisy_float::types::{N64, n64};\n    ///\n    /// // At least 2 edges is needed to represent 1 interval\n    /// let edges = Edges::from(vec![n64(0.), n64(1.), n64(3.)]);\n    /// let bins = Bins::new(edges);\n    /// assert_eq!(bins.is_empty(), false);\n    ///\n    /// // No valid interval == Empty\n    /// let edges = Edges::<N64>::from(vec![]);\n    /// let bins = Bins::new(edges);\n    /// assert_eq!(bins.is_empty(), true);\n    /// let edges = Edges::from(vec![n64(0.)]);\n    /// let bins = Bins::new(edges);\n    /// assert_eq!(bins.is_empty(), true);\n    /// ```\n    #[must_use]\n    pub fn is_empty(&self) -> bool {\n        self.len() == 0\n    }\n\n    /// Returns the index of the bin in `self` that contains the given `value`,\n    /// or returns `None` if `value` does not belong to any bins in `self`.\n    ///\n    /// # Examples\n    ///\n    /// Basic usage:\n    ///\n    /// ```\n    /// use ndarray_stats::histogram::{Edges, Bins};\n    ///\n    /// let edges = Edges::from(vec![0, 2, 4, 6]);\n    /// let bins = Bins::new(edges);\n    /// let value = 1;\n    /// // The first bin [0, 2) contains `1`\n    /// assert_eq!(\n    ///     bins.index_of(&1),\n    ///     Some(0)\n    /// );\n    /// // No bin contains 100\n    /// assert_eq!(\n    ///     bins.index_of(&100),\n    ///     None\n    /// )\n    /// ```\n    ///\n    /// Chaining [`Bins::index`] and [`Bins::index_of`] to get the boundaries of the bin containing\n    /// the value:\n    ///\n    /// ```\n    /// # use ndarray_stats::histogram::{Edges, Bins};\n    /// # let edges = Edges::from(vec![0, 2, 4, 6]);\n    /// # let bins = Bins::new(edges);\n    /// # let value = 1;\n    /// assert_eq!(\n    ///     // using `Option::map` to avoid panic on index out-of-bounds\n    ///     bins.index_of(&1).map(|i| bins.index(i)),\n    ///     Some(0..2)\n    /// );\n    /// ```\n    pub fn index_of(&self, value: &A) -> Option<usize> {\n        self.edges.indices_of(value).map(|t| t.0)\n    }\n\n    /// Returns a range as the bin which contains the given `value`, or returns `None` otherwise.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use ndarray_stats::histogram::{Edges, Bins};\n    ///\n    /// let edges = Edges::from(vec![0, 2, 4, 6]);\n    /// let bins = Bins::new(edges);\n    /// // [0, 2) contains `1`\n    /// assert_eq!(\n    ///     bins.range_of(&1),\n    ///     Some(0..2)\n    /// );\n    /// // `10` is not in any interval\n    /// assert_eq!(\n    ///     bins.range_of(&10),\n    ///     None\n    /// );\n    /// ```\n    pub fn range_of(&self, value: &A) -> Option<Range<A>>\n    where\n        A: Clone,\n    {\n        let edges_indexes = self.edges.indices_of(value);\n        edges_indexes.map(|(left, right)| Range {\n            start: self.edges[left].clone(),\n            end: self.edges[right].clone(),\n        })\n    }\n\n    /// Returns a range as the bin at the given `index` position.\n    ///\n    /// # Panics\n    ///\n    /// Panics if `index` is out of bounds.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use ndarray_stats::histogram::{Edges, Bins};\n    ///\n    /// let edges = Edges::from(vec![1, 5, 10, 20]);\n    /// let bins = Bins::new(edges);\n    /// assert_eq!(\n    ///     bins.index(1),\n    ///     5..10\n    /// );\n    /// ```\n    #[must_use]\n    pub fn index(&self, index: usize) -> Range<A>\n    where\n        A: Clone,\n    {\n        // It was not possible to implement this functionality\n        // using the `Index` trait unless we were willing to\n        // allocate a `Vec<Range<A>>` in the struct.\n        // Index, in fact, forces you to return a reference.\n        Range {\n            start: self.edges[index].clone(),\n            end: self.edges[index + 1].clone(),\n        }\n    }\n}\n\n#[cfg(test)]\nmod edges_tests {\n    use super::{Array1, Edges};\n    use quickcheck_macros::quickcheck;\n    use std::collections::BTreeSet;\n    use std::iter::FromIterator;\n\n    #[quickcheck]\n    fn check_sorted_from_vec(v: Vec<i32>) -> bool {\n        let edges = Edges::from(v);\n        let n = edges.len();\n        for i in 1..n {\n            if edges[i - 1] > edges[i] {\n                return false;\n            }\n        }\n        true\n    }\n\n    #[quickcheck]\n    fn check_sorted_from_array(v: Vec<i32>) -> bool {\n        let a = Array1::from(v);\n        let edges = Edges::from(a);\n        let n = edges.len();\n        for i in 1..n {\n            if edges[i - 1] > edges[i] {\n                return false;\n            }\n        }\n        true\n    }\n\n    #[quickcheck]\n    fn edges_are_right_open(v: Vec<i32>) -> bool {\n        let edges = Edges::from(v);\n        let view = edges.as_array_view();\n        if view.is_empty() {\n            true\n        } else {\n            let last = view[view.len() - 1];\n            edges.indices_of(&last).is_none()\n        }\n    }\n\n    #[quickcheck]\n    fn edges_are_left_closed(v: Vec<i32>) -> bool {\n        let edges = Edges::from(v);\n        if let 1 = edges.len() {\n            true\n        } else {\n            let view = edges.as_array_view();\n            if view.is_empty() {\n                true\n            } else {\n                let first = view[0];\n                edges.indices_of(&first).is_some()\n            }\n        }\n    }\n\n    #[quickcheck]\n    #[allow(clippy::needless_pass_by_value)]\n    fn edges_are_deduped(v: Vec<i32>) -> bool {\n        let unique_elements = BTreeSet::from_iter(v.iter());\n        let edges = Edges::from(v.clone());\n        let view = edges.as_array_view();\n        let unique_edges = BTreeSet::from_iter(view.iter());\n        unique_edges == unique_elements\n    }\n}\n\n#[cfg(test)]\nmod bins_tests {\n    use super::{Bins, Edges};\n\n    #[test]\n    #[should_panic]\n    #[allow(unused_must_use)]\n    fn get_panics_for_out_of_bounds_indexes() {\n        let edges = Edges::from(vec![0]);\n        let bins = Bins::new(edges);\n        // we need at least two edges to make a valid bin!\n        bins.index(0);\n    }\n}\n"
  },
  {
    "path": "src/histogram/errors.rs",
    "content": "use crate::errors::{EmptyInput, MinMaxError};\nuse std::error;\nuse std::fmt;\n\n/// Error to denote that no bin has been found for a certain observation.\n#[derive(Debug, Clone)]\npub struct BinNotFound;\n\nimpl fmt::Display for BinNotFound {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"No bin has been found.\")\n    }\n}\n\nimpl error::Error for BinNotFound {\n    fn description(&self) -> &str {\n        \"No bin has been found.\"\n    }\n}\n\n/// Error computing the set of histogram bins.\n#[derive(Debug, Clone)]\npub enum BinsBuildError {\n    /// The input array was empty.\n    EmptyInput,\n    /// The strategy for computing appropriate bins failed.\n    Strategy,\n    #[doc(hidden)]\n    __NonExhaustive,\n}\n\nimpl BinsBuildError {\n    /// Returns whether `self` is the `EmptyInput` variant.\n    pub fn is_empty_input(&self) -> bool {\n        match self {\n            BinsBuildError::EmptyInput => true,\n            _ => false,\n        }\n    }\n\n    /// Returns whether `self` is the `Strategy` variant.\n    pub fn is_strategy(&self) -> bool {\n        match self {\n            BinsBuildError::Strategy => true,\n            _ => false,\n        }\n    }\n}\n\nimpl fmt::Display for BinsBuildError {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"The strategy failed to determine a non-zero bin width.\")\n    }\n}\n\nimpl error::Error for BinsBuildError {\n    fn description(&self) -> &str {\n        \"The strategy failed to determine a non-zero bin width.\"\n    }\n}\n\nimpl From<EmptyInput> for BinsBuildError {\n    fn from(_: EmptyInput) -> Self {\n        BinsBuildError::EmptyInput\n    }\n}\n\nimpl From<MinMaxError> for BinsBuildError {\n    fn from(err: MinMaxError) -> BinsBuildError {\n        match err {\n            MinMaxError::EmptyInput => BinsBuildError::EmptyInput,\n            MinMaxError::UndefinedOrder => BinsBuildError::Strategy,\n        }\n    }\n}\n"
  },
  {
    "path": "src/histogram/grid.rs",
    "content": "#![warn(missing_docs, clippy::all, clippy::pedantic)]\n\nuse super::{bins::Bins, errors::BinsBuildError, strategies::BinsBuildingStrategy};\nuse itertools::izip;\nuse ndarray::{ArrayRef, Axis, Ix1, Ix2};\nuse std::ops::Range;\n\n/// An orthogonal partition of a rectangular region in an *n*-dimensional space, e.g.\n/// [*a*<sub>0</sub>, *b*<sub>0</sub>) × ⋯ × [*a*<sub>*n*−1</sub>, *b*<sub>*n*−1</sub>),\n/// represented as a collection of rectangular *n*-dimensional bins.\n///\n/// The grid is **solely determined by the Cartesian product of its projections** on each coordinate\n/// axis. Therefore, each element in the product set should correspond to a sub-region in the grid.\n///\n/// For example, this partition can be represented as a `Grid` struct:\n///\n/// ```text\n///\n/// g +---+-------+---+\n///   | 3 |   4   | 5 |\n/// f +---+-------+---+\n///   |   |       |   |\n///   | 0 |   1   | 2 |\n///   |   |       |   |\n/// e +---+-------+---+\n///   a   b       c   d\n///\n/// R0:    [a, b) × [e, f)\n/// R1:    [b, c) × [e, f)\n/// R2:    [c, d) × [e, f)\n/// R3:    [a, b) × [f, g)\n/// R4:    [b, d) × [f, g)\n/// R5:    [c, d) × [f, g)\n/// Grid:  { [a, b), [b, c), [c, d) } × { [e, f), [f, g) } == { R0, R1, R2, R3, R4, R5 }\n/// ```\n///\n/// while the next one can't:\n///\n/// ```text\n///  g  +---+-----+---+\n///     |   |  2  | 3 |\n/// (f) |   +-----+---+\n///     | 0 |         |\n///     |   |    1    |\n///     |   |         |\n///  e  +---+-----+---+\n///     a   b     c   d\n///\n/// R0:    [a, b) × [e, g)\n/// R1:    [b, d) × [e, f)\n/// R2:    [b, c) × [f, g)\n/// R3:    [c, d) × [f, g)\n/// // 'f', as long as 'R1', 'R2', or 'R3', doesn't appear on LHS\n/// // [b, c) × [e, g), [c, d) × [e, g) doesn't appear on RHS\n/// Grid:  { [a, b), [b, c), [c, d) } × { [e, g) } != { R0, R1, R2, R3 }\n/// ```\n///\n/// # Examples\n///\n/// Basic usage, building a `Grid` via [`GridBuilder`], with optimal grid layout determined by\n/// a given [`strategy`], and generating a [`histogram`]:\n///\n/// ```\n/// use ndarray::{Array, array};\n/// use ndarray_stats::{\n///     histogram::{strategies::Auto, Bins, Edges, Grid, GridBuilder},\n///     HistogramExt,\n/// };\n///\n/// // 1-dimensional observations, as a (n_observations, n_dimension) 2-d matrix\n/// let observations = Array::from_shape_vec(\n///     (12, 1),\n///     vec![1, 4, 5, 2, 100, 20, 50, 65, 27, 40, 45, 23],\n/// ).unwrap();\n///\n/// // The optimal grid layout is inferred from the data, given a chosen strategy, Auto in this case\n/// let grid = GridBuilder::<Auto<usize>>::from_array(&observations).unwrap().build();\n///\n/// let histogram = observations.histogram(grid);\n///\n/// let histogram_matrix = histogram.counts();\n/// // Bins are left-closed, right-open!\n/// let expected = array![4, 3, 3, 1, 0, 1];\n/// assert_eq!(histogram_matrix, expected.into_dyn());\n/// ```\n///\n/// [`histogram`]: trait.HistogramExt.html\n/// [`GridBuilder`]: struct.GridBuilder.html\n/// [`strategy`]: strategies/index.html\n#[derive(Clone, Debug, Eq, PartialEq)]\npub struct Grid<A: Ord> {\n    projections: Vec<Bins<A>>,\n}\n\nimpl<A: Ord> From<Vec<Bins<A>>> for Grid<A> {\n    /// Converts a `Vec<Bins<A>>` into a `Grid<A>`, consuming the vector of bins.\n    ///\n    /// The `i`-th element in `Vec<Bins<A>>` represents the projection of the bin grid onto the\n    /// `i`-th axis.\n    ///\n    /// Alternatively, a `Grid` can be built directly from data using a [`GridBuilder`].\n    ///\n    /// [`GridBuilder`]: struct.GridBuilder.html\n    fn from(projections: Vec<Bins<A>>) -> Self {\n        Grid { projections }\n    }\n}\n\nimpl<A: Ord> Grid<A> {\n    /// Returns the number of dimensions of the region partitioned by the grid.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use ndarray_stats::histogram::{Edges, Bins, Grid};\n    ///\n    /// let edges = Edges::from(vec![0, 1]);\n    /// let bins = Bins::new(edges);\n    /// let square_grid = Grid::from(vec![bins.clone(), bins.clone()]);\n    ///\n    /// assert_eq!(square_grid.ndim(), 2usize)\n    /// ```\n    #[must_use]\n    pub fn ndim(&self) -> usize {\n        self.projections.len()\n    }\n\n    /// Returns the numbers of bins along each coordinate axis.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use ndarray_stats::histogram::{Edges, Bins, Grid};\n    ///\n    /// let edges_x = Edges::from(vec![0, 1]);\n    /// let edges_y = Edges::from(vec![-1, 0, 1]);\n    /// let bins_x = Bins::new(edges_x);\n    /// let bins_y = Bins::new(edges_y);\n    /// let square_grid = Grid::from(vec![bins_x, bins_y]);\n    ///\n    /// assert_eq!(square_grid.shape(), vec![1usize, 2usize]);\n    /// ```\n    #[must_use]\n    pub fn shape(&self) -> Vec<usize> {\n        self.projections.iter().map(Bins::len).collect()\n    }\n\n    /// Returns the grid projections on each coordinate axis as a slice of immutable references.\n    #[must_use]\n    pub fn projections(&self) -> &[Bins<A>] {\n        &self.projections\n    }\n\n    /// Returns an `n-dimensional` index, of bins along each axis that contains the point, if one\n    /// exists.\n    ///\n    /// Returns `None` if the point is outside the grid.\n    ///\n    /// # Panics\n    ///\n    /// Panics if dimensionality of the point doesn't equal the grid's.\n    ///\n    /// # Examples\n    ///\n    /// Basic usage:\n    ///\n    /// ```\n    /// use ndarray::array;\n    /// use ndarray_stats::histogram::{Edges, Bins, Grid};\n    /// use noisy_float::types::n64;\n    ///\n    /// let edges = Edges::from(vec![n64(-1.), n64(0.), n64(1.)]);\n    /// let bins = Bins::new(edges);\n    /// let square_grid = Grid::from(vec![bins.clone(), bins.clone()]);\n    ///\n    /// // (0., -0.7) falls in 1st and 0th bin respectively\n    /// assert_eq!(\n    ///     square_grid.index_of(&array![n64(0.), n64(-0.7)]),\n    ///     Some(vec![1, 0]),\n    /// );\n    /// // Returns `None`, as `1.` is outside the grid since bins are right-open\n    /// assert_eq!(\n    ///     square_grid.index_of(&array![n64(0.), n64(1.)]),\n    ///     None,\n    /// );\n    /// ```\n    ///\n    /// A panic upon dimensionality mismatch:\n    ///\n    /// ```should_panic\n    /// # use ndarray::array;\n    /// # use ndarray_stats::histogram::{Edges, Bins, Grid};\n    /// # use noisy_float::types::n64;\n    /// # let edges = Edges::from(vec![n64(-1.), n64(0.), n64(1.)]);\n    /// # let bins = Bins::new(edges);\n    /// # let square_grid = Grid::from(vec![bins.clone(), bins.clone()]);\n    /// // the point has 3 dimensions, the grid expected 2 dimensions\n    /// assert_eq!(\n    ///     square_grid.index_of(&array![n64(0.), n64(-0.7), n64(0.5)]),\n    ///     Some(vec![1, 0, 1]),\n    /// );\n    /// ```\n    pub fn index_of(&self, point: &ArrayRef<A, Ix1>) -> Option<Vec<usize>> {\n        assert_eq!(\n            point.len(),\n            self.ndim(),\n            \"Dimension mismatch: the point has {:?} dimensions, the grid \\\n             expected {:?} dimensions.\",\n            point.len(),\n            self.ndim()\n        );\n        point\n            .iter()\n            .zip(self.projections.iter())\n            .map(|(v, e)| e.index_of(v))\n            .collect()\n    }\n}\n\nimpl<A: Ord + Clone> Grid<A> {\n    /// Given an `n`-dimensional index, `i = (i_0, ..., i_{n-1})`, returns an `n`-dimensional bin,\n    /// `I_{i_0} x ... x I_{i_{n-1}}`, where `I_{i_j}` is the `i_j`-th interval on the `j`-th\n    /// projection of the grid on the coordinate axes.\n    ///\n    /// # Panics\n    ///\n    /// Panics if at least one in the index, `(i_0, ..., i_{n-1})`, is out of bounds on the\n    /// corresponding coordinate axis, i.e. if there exists `j` s.t.\n    /// `i_j >= self.projections[j].len()`.\n    ///\n    /// # Examples\n    ///\n    /// Basic usage:\n    ///\n    /// ```\n    /// use ndarray::array;\n    /// use ndarray_stats::histogram::{Edges, Bins, Grid};\n    ///\n    /// let edges_x = Edges::from(vec![0, 1]);\n    /// let edges_y = Edges::from(vec![2, 3, 4]);\n    /// let bins_x = Bins::new(edges_x);\n    /// let bins_y = Bins::new(edges_y);\n    /// let square_grid = Grid::from(vec![bins_x, bins_y]);\n    ///\n    /// // Query the 0-th bin on x-axis, and 1-st bin on y-axis\n    /// assert_eq!(\n    ///     square_grid.index(&[0, 1]),\n    ///     vec![0..1, 3..4],\n    /// );\n    /// ```\n    ///\n    /// A panic upon out-of-bounds:\n    ///\n    /// ```should_panic\n    /// # use ndarray::array;\n    /// # use ndarray_stats::histogram::{Edges, Bins, Grid};\n    /// # let edges_x = Edges::from(vec![0, 1]);\n    /// # let edges_y = Edges::from(vec![2, 3, 4]);\n    /// # let bins_x = Bins::new(edges_x);\n    /// # let bins_y = Bins::new(edges_y);\n    /// # let square_grid = Grid::from(vec![bins_x, bins_y]);\n    /// // out-of-bound on y-axis\n    /// assert_eq!(\n    ///     square_grid.index(&[0, 2]),\n    ///     vec![0..1, 3..4],\n    /// );\n    /// ```\n    #[must_use]\n    pub fn index(&self, index: &[usize]) -> Vec<Range<A>> {\n        assert_eq!(\n            index.len(),\n            self.ndim(),\n            \"Dimension mismatch: the index has {0:?} dimensions, the grid \\\n             expected {1:?} dimensions.\",\n            index.len(),\n            self.ndim()\n        );\n        izip!(&self.projections, index)\n            .map(|(bins, &i)| bins.index(i))\n            .collect()\n    }\n}\n\n/// A builder used to create [`Grid`] instances for [`histogram`] computations.\n///\n/// # Examples\n///\n/// Basic usage, creating a `Grid` with some observations and a given [`strategy`]:\n///\n/// ```\n/// use ndarray::Array;\n/// use ndarray_stats::histogram::{strategies::Auto, Bins, Edges, Grid, GridBuilder};\n///\n/// // 1-dimensional observations, as a (n_observations, n_dimension) 2-d matrix\n/// let observations = Array::from_shape_vec(\n///     (12, 1),\n///     vec![1, 4, 5, 2, 100, 20, 50, 65, 27, 40, 45, 23],\n/// ).unwrap();\n///\n/// // The optimal grid layout is inferred from the data, given a chosen strategy, Auto in this case\n/// let grid = GridBuilder::<Auto<usize>>::from_array(&observations).unwrap().build();\n/// // Equivalently, build a Grid directly\n/// let expected_grid = Grid::from(vec![Bins::new(Edges::from(vec![1, 20, 39, 58, 77, 96, 115]))]);\n///\n/// assert_eq!(grid, expected_grid);\n/// ```\n///\n/// [`Grid`]: struct.Grid.html\n/// [`histogram`]: trait.HistogramExt.html\n/// [`strategy`]: strategies/index.html\n#[allow(clippy::module_name_repetitions)]\npub struct GridBuilder<B: BinsBuildingStrategy> {\n    bin_builders: Vec<B>,\n}\n\nimpl<A, B> GridBuilder<B>\nwhere\n    A: Ord,\n    B: BinsBuildingStrategy<Elem = A>,\n{\n    /// Returns a `GridBuilder` for building a [`Grid`] with a given [`strategy`] and some\n    /// observations in a 2-dimensionalarray with shape `(n_observations, n_dimension)`.\n    ///\n    /// # Errors\n    ///\n    /// It returns [`BinsBuildError`] if it is not possible to build a [`Grid`] given\n    /// the observed data according to the chosen [`strategy`].\n    ///\n    /// # Examples\n    ///\n    /// See [Trait-level examples] for basic usage.\n    ///\n    /// [`Grid`]: struct.Grid.html\n    /// [`strategy`]: strategies/index.html\n    /// [`BinsBuildError`]: errors/enum.BinsBuildError.html\n    /// [Trait-level examples]: struct.GridBuilder.html#examples\n    pub fn from_array(array: &ArrayRef<A, Ix2>) -> Result<Self, BinsBuildError> {\n        let bin_builders = array\n            .axis_iter(Axis(1))\n            .map(|data| B::from_array(&data))\n            .collect::<Result<Vec<B>, BinsBuildError>>()?;\n        Ok(Self { bin_builders })\n    }\n\n    /// Returns a [`Grid`] instance, with building parameters infered in [`from_array`], according\n    /// to the specified [`strategy`] and observations provided.\n    ///\n    /// # Examples\n    ///\n    /// See [Trait-level examples] for basic usage.\n    ///\n    /// [`Grid`]: struct.Grid.html\n    /// [`strategy`]: strategies/index.html\n    /// [`from_array`]: #method.from_array.html\n    #[must_use]\n    pub fn build(&self) -> Grid<A> {\n        let projections: Vec<_> = self.bin_builders.iter().map(|b| b.build()).collect();\n        Grid::from(projections)\n    }\n}\n"
  },
  {
    "path": "src/histogram/histograms.rs",
    "content": "use super::errors::BinNotFound;\nuse super::grid::Grid;\nuse ndarray::prelude::*;\n\n/// Histogram data structure.\npub struct Histogram<A: Ord> {\n    counts: ArrayD<usize>,\n    grid: Grid<A>,\n}\n\nimpl<A: Ord> Histogram<A> {\n    /// Returns a new instance of Histogram given a [`Grid`].\n    ///\n    /// [`Grid`]: struct.Grid.html\n    pub fn new(grid: Grid<A>) -> Self {\n        let counts = ArrayD::zeros(grid.shape());\n        Histogram { counts, grid }\n    }\n\n    /// Adds a single observation to the histogram.\n    ///\n    /// **Panics** if dimensions do not match: `self.ndim() != observation.len()`.\n    ///\n    /// # Example:\n    /// ```\n    /// use ndarray::array;\n    /// use ndarray_stats::histogram::{Edges, Bins, Histogram, Grid};\n    /// use noisy_float::types::n64;\n    ///\n    /// let edges = Edges::from(vec![n64(-1.), n64(0.), n64(1.)]);\n    /// let bins = Bins::new(edges);\n    /// let square_grid = Grid::from(vec![bins.clone(), bins.clone()]);\n    /// let mut histogram = Histogram::new(square_grid);\n    ///\n    /// let observation = array![n64(0.5), n64(0.6)];\n    ///\n    /// histogram.add_observation(&observation)?;\n    ///\n    /// let histogram_matrix = histogram.counts();\n    /// let expected = array![\n    ///     [0, 0],\n    ///     [0, 1],\n    /// ];\n    /// assert_eq!(histogram_matrix, expected.into_dyn());\n    /// # Ok::<(), Box<std::error::Error>>(())\n    /// ```\n    pub fn add_observation(&mut self, observation: &ArrayRef<A, Ix1>) -> Result<(), BinNotFound> {\n        match self.grid.index_of(observation) {\n            Some(bin_index) => {\n                self.counts[&*bin_index] += 1;\n                Ok(())\n            }\n            None => Err(BinNotFound),\n        }\n    }\n\n    /// Returns the number of dimensions of the space the histogram is covering.\n    pub fn ndim(&self) -> usize {\n        debug_assert_eq!(self.counts.ndim(), self.grid.ndim());\n        self.counts.ndim()\n    }\n\n    /// Borrows a view on the histogram counts matrix.\n    pub fn counts(&self) -> ArrayViewD<'_, usize> {\n        self.counts.view()\n    }\n\n    /// Borrows an immutable reference to the histogram grid.\n    pub fn grid(&self) -> &Grid<A> {\n        &self.grid\n    }\n}\n\n/// Extension trait for `ArrayRef` providing methods to compute histograms.\npub trait HistogramExt<A> {\n    /// Returns the [histogram](https://en.wikipedia.org/wiki/Histogram)\n    /// for a 2-dimensional array of points `M`.\n    ///\n    /// Let `(n, d)` be the shape of `M`:\n    /// - `n` is the number of points;\n    /// - `d` is the number of dimensions of the space those points belong to.\n    /// It follows that every column in `M` is a `d`-dimensional point.\n    ///\n    /// For example: a (3, 4) matrix `M` is a collection of 3 points in a\n    /// 4-dimensional space.\n    ///\n    /// Important: points outside the grid are ignored!\n    ///\n    /// **Panics** if `d` is different from `grid.ndim()`.\n    ///\n    /// # Example:\n    ///\n    /// ```\n    /// use ndarray::array;\n    /// use ndarray_stats::{\n    ///     HistogramExt,\n    ///     histogram::{\n    ///         Histogram, Grid, GridBuilder,\n    ///         Edges, Bins,\n    ///         strategies::Sqrt},\n    /// };\n    /// use noisy_float::types::{N64, n64};\n    ///\n    /// let observations = array![\n    ///     [n64(1.), n64(0.5)],\n    ///     [n64(-0.5), n64(1.)],\n    ///     [n64(-1.), n64(-0.5)],\n    ///     [n64(0.5), n64(-1.)]\n    /// ];\n    /// let grid = GridBuilder::<Sqrt<N64>>::from_array(&observations).unwrap().build();\n    /// let expected_grid = Grid::from(\n    ///     vec![\n    ///         Bins::new(Edges::from(vec![n64(-1.), n64(0.), n64(1.), n64(2.)])),\n    ///         Bins::new(Edges::from(vec![n64(-1.), n64(0.), n64(1.), n64(2.)])),\n    ///     ]\n    /// );\n    /// assert_eq!(grid, expected_grid);\n    ///\n    /// let histogram = observations.histogram(grid);\n    ///\n    /// let histogram_matrix = histogram.counts();\n    /// // Bins are left inclusive, right exclusive!\n    /// let expected = array![\n    ///     [1, 0, 1],\n    ///     [1, 0, 0],\n    ///     [0, 1, 0],\n    /// ];\n    /// assert_eq!(histogram_matrix, expected.into_dyn());\n    /// ```\n    fn histogram(&self, grid: Grid<A>) -> Histogram<A>\n    where\n        A: Ord;\n\n    private_decl! {}\n}\n\nimpl<A> HistogramExt<A> for ArrayRef<A, Ix2>\nwhere\n    A: Ord,\n{\n    fn histogram(&self, grid: Grid<A>) -> Histogram<A> {\n        let mut histogram = Histogram::new(grid);\n        for point in self.axis_iter(Axis(0)) {\n            let _ = histogram.add_observation(&point);\n        }\n        histogram\n    }\n\n    private_impl! {}\n}\n"
  },
  {
    "path": "src/histogram/mod.rs",
    "content": "//! Histogram functionalities.\npub use self::bins::{Bins, Edges};\npub use self::grid::{Grid, GridBuilder};\npub use self::histograms::{Histogram, HistogramExt};\n\nmod bins;\npub mod errors;\nmod grid;\nmod histograms;\npub mod strategies;\n"
  },
  {
    "path": "src/histogram/strategies.rs",
    "content": "//! Strategies used by [`GridBuilder`] to infer optimal parameters from data for building [`Bins`]\n//! and [`Grid`] instances.\n//!\n//! The docs for each strategy have been taken almost verbatim from [`NumPy`].\n//!\n//! Each strategy specifies how to compute the optimal number of [`Bins`] or the optimal bin width.\n//! For those strategies that prescribe the optimal number of [`Bins`], the optimal bin width is\n//! computed by `bin_width = (max - min)/n`.\n//!\n//! Since all bins are left-closed and right-open, it is guaranteed to add an extra bin to include\n//! the maximum value from the given data when necessary, so that no data is discarded.\n//!\n//! # Strategies\n//!\n//! Currently, the following strategies are implemented:\n//!\n//! - [`Auto`]: Maximum of the [`Sturges`] and [`FreedmanDiaconis`] strategies. Provides good all\n//!   around performance.\n//! - [`FreedmanDiaconis`]: Robust (resilient to outliers) strategy that takes into account data\n//!   variability and data size.\n//! - [`Rice`]: A strategy that does not take variability into account, only data size. Commonly\n//!   overestimates number of bins required.\n//! - [`Sqrt`]: Square root (of data size) strategy, used by Excel and other programs\n//!   for its speed and simplicity.\n//! - [`Sturges`]: R’s default strategy, only accounts for data size. Only optimal for gaussian data\n//!   and underestimates number of bins for large non-gaussian datasets.\n//!\n//! # Notes\n//!\n//! In general, successful infererence on optimal bin width and number of bins relies on\n//! **variability** of data. In other word, the provided ovservations should not be empty or\n//! constant.\n//!\n//! In addition, [`Auto`] and [`FreedmanDiaconis`] requires the [`interquartile range (IQR)`][iqr],\n//! i.e. the difference between upper and lower quartiles, to be positive.\n//!\n//! [`GridBuilder`]: ../struct.GridBuilder.html\n//! [`Bins`]: ../struct.Bins.html\n//! [`Grid`]: ../struct.Grid.html\n//! [`NumPy`]: https://docs.scipy.org/doc/numpy/reference/generated/numpy.histogram_bin_edges.html#numpy.histogram_bin_edges\n//! [`Auto`]: struct.Auto.html\n//! [`Sturges`]: struct.Sturges.html\n//! [`FreedmanDiaconis`]: struct.FreedmanDiaconis.html\n//! [`Rice`]: struct.Rice.html\n//! [`Sqrt`]: struct.Sqrt.html\n//! [iqr]: https://www.wikiwand.com/en/Interquartile_range\n#![warn(missing_docs, clippy::all, clippy::pedantic)]\n\nuse crate::{\n    histogram::{errors::BinsBuildError, Bins, Edges},\n    quantile::{interpolate::Nearest, Quantile1dExt, QuantileExt},\n};\nuse ndarray::prelude::*;\nuse noisy_float::types::n64;\nuse num_traits::{FromPrimitive, NumOps, Zero};\n\n/// A trait implemented by all strategies to build [`Bins`] with parameters inferred from\n/// observations.\n///\n/// This is required by [`GridBuilder`] to know how to build a [`Grid`]'s projections on the\n/// coordinate axes.\n///\n/// [`Bins`]: ../struct.Bins.html\n/// [`GridBuilder`]: ../struct.GridBuilder.html\n/// [`Grid`]: ../struct.Grid.html\npub trait BinsBuildingStrategy {\n    #[allow(missing_docs)]\n    type Elem: Ord;\n    /// Returns a strategy that has learnt the required parameter fo building [`Bins`] for given\n    /// 1-dimensional array, or an `Err` if it is not possible to infer the required parameter\n    /// with the given data and specified strategy.\n    ///\n    /// # Errors\n    ///\n    /// See each of the struct-level documentation for details on errors an implementor may return.\n    ///\n    /// [`Bins`]: ../struct.Bins.html\n    fn from_array(array: &ArrayRef<Self::Elem, Ix1>) -> Result<Self, BinsBuildError>\n    where\n        Self: std::marker::Sized;\n\n    /// Returns a [`Bins`] instance, according to parameters inferred from observations.\n    ///\n    /// [`Bins`]: ../struct.Bins.html\n    fn build(&self) -> Bins<Self::Elem>;\n\n    /// Returns the optimal number of bins, according to parameters inferred from observations.\n    fn n_bins(&self) -> usize;\n}\n\n#[derive(Debug)]\nstruct EquiSpaced<T> {\n    bin_width: T,\n    min: T,\n    max: T,\n}\n\n/// Square root (of data size) strategy, used by Excel and other programs for its speed and\n/// simplicity.\n///\n/// Let `n` be the number of observations. Then\n///\n/// `n_bins` = `sqrt(n)`\n///\n/// # Notes\n///\n/// This strategy requires the data\n///\n/// - not being empty\n/// - not being constant\n#[derive(Debug)]\npub struct Sqrt<T> {\n    builder: EquiSpaced<T>,\n}\n\n/// A strategy that does not take variability into account, only data size. Commonly\n/// overestimates number of bins required.\n///\n/// Let `n` be the number of observations and `n_bins` be the number of bins.\n///\n/// `n_bins` = 2`n`<sup>1/3</sup>\n///\n/// `n_bins` is only proportional to cube root of `n`. It tends to overestimate\n/// the `n_bins` and it does not take into account data variability.\n///\n/// # Notes\n///\n/// This strategy requires the data\n///\n/// - not being empty\n/// - not being constant\n#[derive(Debug)]\npub struct Rice<T> {\n    builder: EquiSpaced<T>,\n}\n\n/// R’s default strategy, only accounts for data size. Only optimal for gaussian data and\n/// underestimates number of bins for large non-gaussian datasets.\n///\n/// Let `n` be the number of observations.\n/// The number of bins is 1 plus the base 2 log of `n`. This estimator assumes normality of data and\n/// is too conservative for larger, non-normal datasets.\n///\n/// This is the default method in R’s hist method.\n///\n/// # Notes\n///\n/// This strategy requires the data\n///\n/// - not being empty\n/// - not being constant\n#[derive(Debug)]\npub struct Sturges<T> {\n    builder: EquiSpaced<T>,\n}\n\n/// Robust (resilient to outliers) strategy that takes into account data variability and data size.\n///\n/// Let `n` be the number of observations.\n///\n/// `bin_width` = 2 × `IQR` × `n`<sup>−1/3</sup>\n///\n/// The bin width is proportional to the interquartile range ([`IQR`]) and inversely proportional to\n/// cube root of `n`. It can be too conservative for small datasets, but it is quite good for large\n/// datasets.\n///\n/// The [`IQR`] is very robust to outliers.\n///\n/// # Notes\n///\n/// This strategy requires the data\n///\n/// - not being empty\n/// - not being constant\n/// - having positive [`IQR`]\n///\n/// [`IQR`]: https://en.wikipedia.org/wiki/Interquartile_range\n#[derive(Debug)]\npub struct FreedmanDiaconis<T> {\n    builder: EquiSpaced<T>,\n}\n\n#[derive(Debug)]\nenum SturgesOrFD<T> {\n    Sturges(Sturges<T>),\n    FreedmanDiaconis(FreedmanDiaconis<T>),\n}\n\n/// Maximum of the [`Sturges`] and [`FreedmanDiaconis`] strategies. Provides good all around\n/// performance.\n///\n/// A compromise to get a good value. For small datasets the [`Sturges`] value will usually be\n/// chosen, while larger datasets will usually default to [`FreedmanDiaconis`]. Avoids the overly\n/// conservative behaviour of [`FreedmanDiaconis`] and [`Sturges`] for small and large datasets\n/// respectively.\n///\n/// # Notes\n///\n/// This strategy requires the data\n///\n/// - not being empty\n/// - not being constant\n/// - having positive [`IQR`]\n///\n/// [`Sturges`]: struct.Sturges.html\n/// [`FreedmanDiaconis`]: struct.FreedmanDiaconis.html\n/// [`IQR`]: https://en.wikipedia.org/wiki/Interquartile_range\n#[derive(Debug)]\npub struct Auto<T> {\n    builder: SturgesOrFD<T>,\n}\n\nimpl<T> EquiSpaced<T>\nwhere\n    T: Ord + Clone + FromPrimitive + NumOps + Zero,\n{\n    /// Returns `Err(BinsBuildError::Strategy)` if `bin_width<=0` or `min` >= `max`.\n    /// Returns `Ok(Self)` otherwise.\n    fn new(bin_width: T, min: T, max: T) -> Result<Self, BinsBuildError> {\n        if (bin_width <= T::zero()) || (min >= max) {\n            Err(BinsBuildError::Strategy)\n        } else {\n            Ok(Self {\n                bin_width,\n                min,\n                max,\n            })\n        }\n    }\n\n    fn build(&self) -> Bins<T> {\n        let n_bins = self.n_bins();\n        let mut edges: Vec<T> = vec![];\n        for i in 0..=n_bins {\n            let edge = self.min.clone() + T::from_usize(i).unwrap() * self.bin_width.clone();\n            edges.push(edge);\n        }\n        Bins::new(Edges::from(edges))\n    }\n\n    fn n_bins(&self) -> usize {\n        let mut max_edge = self.min.clone();\n        let mut n_bins = 0;\n        while max_edge <= self.max {\n            max_edge = max_edge + self.bin_width.clone();\n            n_bins += 1;\n        }\n        n_bins\n    }\n\n    fn bin_width(&self) -> T {\n        self.bin_width.clone()\n    }\n}\n\nimpl<T> BinsBuildingStrategy for Sqrt<T>\nwhere\n    T: Ord + Clone + FromPrimitive + NumOps + Zero,\n{\n    type Elem = T;\n\n    /// Returns `Err(BinsBuildError::Strategy)` if the array is constant.\n    /// Returns `Err(BinsBuildError::EmptyInput)` if `a.len()==0`.\n    /// Returns `Ok(Self)` otherwise.\n    fn from_array(a: &ArrayRef<T, Ix1>) -> Result<Self, BinsBuildError> {\n        let n_elems = a.len();\n        // casting `n_elems: usize` to `f64` may casus off-by-one error here if `n_elems` > 2 ^ 53,\n        // but it's not relevant here\n        #[allow(clippy::cast_precision_loss)]\n        // casting the rounded square root from `f64` to `usize` is safe\n        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]\n        let n_bins = (n_elems as f64).sqrt().round() as usize;\n        let min = a.min()?;\n        let max = a.max()?;\n        let bin_width = compute_bin_width(min.clone(), max.clone(), n_bins);\n        let builder = EquiSpaced::new(bin_width, min.clone(), max.clone())?;\n        Ok(Self { builder })\n    }\n\n    fn build(&self) -> Bins<T> {\n        self.builder.build()\n    }\n\n    fn n_bins(&self) -> usize {\n        self.builder.n_bins()\n    }\n}\n\nimpl<T> Sqrt<T>\nwhere\n    T: Ord + Clone + FromPrimitive + NumOps + Zero,\n{\n    /// The bin width (or bin length) according to the fitted strategy.\n    pub fn bin_width(&self) -> T {\n        self.builder.bin_width()\n    }\n}\n\nimpl<T> BinsBuildingStrategy for Rice<T>\nwhere\n    T: Ord + Clone + FromPrimitive + NumOps + Zero,\n{\n    type Elem = T;\n\n    /// Returns `Err(BinsBuildError::Strategy)` if the array is constant.\n    /// Returns `Err(BinsBuildError::EmptyInput)` if `a.len()==0`.\n    /// Returns `Ok(Self)` otherwise.\n    fn from_array(a: &ArrayRef<T, Ix1>) -> Result<Self, BinsBuildError> {\n        let n_elems = a.len();\n        // casting `n_elems: usize` to `f64` may casus off-by-one error here if `n_elems` > 2 ^ 53,\n        // but it's not relevant here\n        #[allow(clippy::cast_precision_loss)]\n        // casting the rounded cube root from `f64` to `usize` is safe\n        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]\n        let n_bins = (2. * (n_elems as f64).powf(1. / 3.)).round() as usize;\n        let min = a.min()?;\n        let max = a.max()?;\n        let bin_width = compute_bin_width(min.clone(), max.clone(), n_bins);\n        let builder = EquiSpaced::new(bin_width, min.clone(), max.clone())?;\n        Ok(Self { builder })\n    }\n\n    fn build(&self) -> Bins<T> {\n        self.builder.build()\n    }\n\n    fn n_bins(&self) -> usize {\n        self.builder.n_bins()\n    }\n}\n\nimpl<T> Rice<T>\nwhere\n    T: Ord + Clone + FromPrimitive + NumOps + Zero,\n{\n    /// The bin width (or bin length) according to the fitted strategy.\n    pub fn bin_width(&self) -> T {\n        self.builder.bin_width()\n    }\n}\n\nimpl<T> BinsBuildingStrategy for Sturges<T>\nwhere\n    T: Ord + Clone + FromPrimitive + NumOps + Zero,\n{\n    type Elem = T;\n\n    /// Returns `Err(BinsBuildError::Strategy)` if the array is constant.\n    /// Returns `Err(BinsBuildError::EmptyInput)` if `a.len()==0`.\n    /// Returns `Ok(Self)` otherwise.\n    fn from_array(a: &ArrayRef<T, Ix1>) -> Result<Self, BinsBuildError> {\n        let n_elems = a.len();\n        // casting `n_elems: usize` to `f64` may casus off-by-one error here if `n_elems` > 2 ^ 53,\n        // but it's not relevant here\n        #[allow(clippy::cast_precision_loss)]\n        // casting the rounded base-2 log from `f64` to `usize` is safe\n        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]\n        let n_bins = (n_elems as f64).log2().round() as usize + 1;\n        let min = a.min()?;\n        let max = a.max()?;\n        let bin_width = compute_bin_width(min.clone(), max.clone(), n_bins);\n        let builder = EquiSpaced::new(bin_width, min.clone(), max.clone())?;\n        Ok(Self { builder })\n    }\n\n    fn build(&self) -> Bins<T> {\n        self.builder.build()\n    }\n\n    fn n_bins(&self) -> usize {\n        self.builder.n_bins()\n    }\n}\n\nimpl<T> Sturges<T>\nwhere\n    T: Ord + Clone + FromPrimitive + NumOps + Zero,\n{\n    /// The bin width (or bin length) according to the fitted strategy.\n    pub fn bin_width(&self) -> T {\n        self.builder.bin_width()\n    }\n}\n\nimpl<T> BinsBuildingStrategy for FreedmanDiaconis<T>\nwhere\n    T: Ord + Clone + FromPrimitive + NumOps + Zero,\n{\n    type Elem = T;\n\n    /// Returns `Err(BinsBuildError::Strategy)` if `IQR==0`.\n    /// Returns `Err(BinsBuildError::EmptyInput)` if `a.len()==0`.\n    /// Returns `Ok(Self)` otherwise.\n    fn from_array(a: &ArrayRef<T, Ix1>) -> Result<Self, BinsBuildError> {\n        let n_points = a.len();\n        if n_points == 0 {\n            return Err(BinsBuildError::EmptyInput);\n        }\n\n        let mut a_copy = a.to_owned();\n        let first_quartile = a_copy.quantile_mut(n64(0.25), &Nearest).unwrap();\n        let third_quartile = a_copy.quantile_mut(n64(0.75), &Nearest).unwrap();\n        let iqr = third_quartile - first_quartile;\n\n        let bin_width = FreedmanDiaconis::compute_bin_width(n_points, iqr);\n        let min = a.min()?;\n        let max = a.max()?;\n        let builder = EquiSpaced::new(bin_width, min.clone(), max.clone())?;\n        Ok(Self { builder })\n    }\n\n    fn build(&self) -> Bins<T> {\n        self.builder.build()\n    }\n\n    fn n_bins(&self) -> usize {\n        self.builder.n_bins()\n    }\n}\n\nimpl<T> FreedmanDiaconis<T>\nwhere\n    T: Ord + Clone + FromPrimitive + NumOps + Zero,\n{\n    fn compute_bin_width(n_bins: usize, iqr: T) -> T {\n        // casting `n_bins: usize` to `f64` may casus off-by-one error here if `n_bins` > 2 ^ 53,\n        // but it's not relevant here\n        #[allow(clippy::cast_precision_loss)]\n        let denominator = (n_bins as f64).powf(1. / 3.);\n        T::from_usize(2).unwrap() * iqr / T::from_f64(denominator).unwrap()\n    }\n\n    /// The bin width (or bin length) according to the fitted strategy.\n    pub fn bin_width(&self) -> T {\n        self.builder.bin_width()\n    }\n}\n\nimpl<T> BinsBuildingStrategy for Auto<T>\nwhere\n    T: Ord + Clone + FromPrimitive + NumOps + Zero,\n{\n    type Elem = T;\n\n    /// Returns `Err(BinsBuildError::Strategy)` if `IQR==0`.\n    /// Returns `Err(BinsBuildError::EmptyInput)` if `a.len()==0`.\n    /// Returns `Ok(Self)` otherwise.\n    fn from_array(a: &ArrayRef<T, Ix1>) -> Result<Self, BinsBuildError> {\n        let fd_builder = FreedmanDiaconis::from_array(&a);\n        let sturges_builder = Sturges::from_array(&a);\n        match (fd_builder, sturges_builder) {\n            (Err(_), Ok(sturges_builder)) => {\n                let builder = SturgesOrFD::Sturges(sturges_builder);\n                Ok(Self { builder })\n            }\n            (Ok(fd_builder), Err(_)) => {\n                let builder = SturgesOrFD::FreedmanDiaconis(fd_builder);\n                Ok(Self { builder })\n            }\n            (Ok(fd_builder), Ok(sturges_builder)) => {\n                let builder = if fd_builder.bin_width() > sturges_builder.bin_width() {\n                    SturgesOrFD::Sturges(sturges_builder)\n                } else {\n                    SturgesOrFD::FreedmanDiaconis(fd_builder)\n                };\n                Ok(Self { builder })\n            }\n            (Err(err), Err(_)) => Err(err),\n        }\n    }\n\n    fn build(&self) -> Bins<T> {\n        // Ugly\n        match &self.builder {\n            SturgesOrFD::FreedmanDiaconis(b) => b.build(),\n            SturgesOrFD::Sturges(b) => b.build(),\n        }\n    }\n\n    fn n_bins(&self) -> usize {\n        // Ugly\n        match &self.builder {\n            SturgesOrFD::FreedmanDiaconis(b) => b.n_bins(),\n            SturgesOrFD::Sturges(b) => b.n_bins(),\n        }\n    }\n}\n\nimpl<T> Auto<T>\nwhere\n    T: Ord + Clone + FromPrimitive + NumOps + Zero,\n{\n    /// The bin width (or bin length) according to the fitted strategy.\n    pub fn bin_width(&self) -> T {\n        // Ugly\n        match &self.builder {\n            SturgesOrFD::FreedmanDiaconis(b) => b.bin_width(),\n            SturgesOrFD::Sturges(b) => b.bin_width(),\n        }\n    }\n}\n\n/// Returns the `bin_width`, given the two end points of a range (`max`, `min`), and the number of\n/// bins, consuming endpoints\n///\n/// `bin_width = (max - min)/n`\n///\n/// **Panics** if `n_bins == 0` and division by 0 panics for `T`.\nfn compute_bin_width<T>(min: T, max: T, n_bins: usize) -> T\nwhere\n    T: Ord + Clone + FromPrimitive + NumOps + Zero,\n{\n    let range = max - min;\n    range / T::from_usize(n_bins).unwrap()\n}\n\n#[cfg(test)]\nmod equispaced_tests {\n    use super::EquiSpaced;\n\n    #[test]\n    fn bin_width_has_to_be_positive() {\n        assert!(EquiSpaced::new(0, 0, 200).is_err());\n    }\n\n    #[test]\n    fn min_has_to_be_strictly_smaller_than_max() {\n        assert!(EquiSpaced::new(10, 0, 0).is_err());\n    }\n}\n\n#[cfg(test)]\nmod sqrt_tests {\n    use super::{BinsBuildingStrategy, Sqrt};\n    use ndarray::array;\n\n    #[test]\n    fn constant_array_are_bad() {\n        assert!(Sqrt::from_array(&array![1, 1, 1, 1, 1, 1, 1])\n            .unwrap_err()\n            .is_strategy());\n    }\n\n    #[test]\n    fn empty_arrays_are_bad() {\n        assert!(Sqrt::<usize>::from_array(&array![])\n            .unwrap_err()\n            .is_empty_input());\n    }\n}\n\n#[cfg(test)]\nmod rice_tests {\n    use super::{BinsBuildingStrategy, Rice};\n    use ndarray::array;\n\n    #[test]\n    fn constant_array_are_bad() {\n        assert!(Rice::from_array(&array![1, 1, 1, 1, 1, 1, 1])\n            .unwrap_err()\n            .is_strategy());\n    }\n\n    #[test]\n    fn empty_arrays_are_bad() {\n        assert!(Rice::<usize>::from_array(&array![])\n            .unwrap_err()\n            .is_empty_input());\n    }\n}\n\n#[cfg(test)]\nmod sturges_tests {\n    use super::{BinsBuildingStrategy, Sturges};\n    use ndarray::array;\n\n    #[test]\n    fn constant_array_are_bad() {\n        assert!(Sturges::from_array(&array![1, 1, 1, 1, 1, 1, 1])\n            .unwrap_err()\n            .is_strategy());\n    }\n\n    #[test]\n    fn empty_arrays_are_bad() {\n        assert!(Sturges::<usize>::from_array(&array![])\n            .unwrap_err()\n            .is_empty_input());\n    }\n}\n\n#[cfg(test)]\nmod fd_tests {\n    use super::{BinsBuildingStrategy, FreedmanDiaconis};\n    use ndarray::array;\n\n    #[test]\n    fn constant_array_are_bad() {\n        assert!(FreedmanDiaconis::from_array(&array![1, 1, 1, 1, 1, 1, 1])\n            .unwrap_err()\n            .is_strategy());\n    }\n\n    #[test]\n    fn zero_iqr_is_bad() {\n        assert!(\n            FreedmanDiaconis::from_array(&array![-20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20])\n                .unwrap_err()\n                .is_strategy()\n        );\n    }\n\n    #[test]\n    fn empty_arrays_are_bad() {\n        assert!(FreedmanDiaconis::<usize>::from_array(&array![])\n            .unwrap_err()\n            .is_empty_input());\n    }\n}\n\n#[cfg(test)]\nmod auto_tests {\n    use super::{Auto, BinsBuildingStrategy};\n    use ndarray::array;\n\n    #[test]\n    fn constant_array_are_bad() {\n        assert!(Auto::from_array(&array![1, 1, 1, 1, 1, 1, 1])\n            .unwrap_err()\n            .is_strategy());\n    }\n\n    #[test]\n    fn zero_iqr_is_handled_by_sturged() {\n        assert!(Auto::from_array(&array![-20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20]).is_ok());\n    }\n\n    #[test]\n    fn empty_arrays_are_bad() {\n        assert!(Auto::<usize>::from_array(&array![])\n            .unwrap_err()\n            .is_empty_input());\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//! The [`ndarray-stats`] crate exposes statistical routines for `ArrayRef`,\n//! the *n*-dimensional array data structure provided by [`ndarray`].\n//!\n//! Currently available routines include:\n//! - [order statistics] (minimum, maximum, median, quantiles, etc.);\n//! - [summary statistics] (mean, skewness, kurtosis, central moments, etc.)\n//! - [partitioning];\n//! - [correlation analysis] (covariance, pearson correlation);\n//! - [measures from information theory] (entropy, KL divergence, etc.);\n//! - [measures of deviation] (count equal, L1, L2 distances, mean squared err etc.)\n//! - [histogram computation].\n//!\n//! Please feel free to contribute new functionality! A roadmap can be found [here].\n//!\n//! Our work is inspired by other existing statistical packages such as\n//! [`NumPy`] (Python) and [`StatsBase.jl`] (Julia) - any contribution bringing us closer to\n//! feature parity is more than welcome!\n//!\n//! [`ndarray-stats`]: https://github.com/rust-ndarray/ndarray-stats/\n//! [`ndarray`]: https://github.com/rust-ndarray/ndarray\n//! [order statistics]: trait.QuantileExt.html\n//! [partitioning]: trait.Sort1dExt.html\n//! [summary statistics]: trait.SummaryStatisticsExt.html\n//! [correlation analysis]: trait.CorrelationExt.html\n//! [measures of deviation]: trait.DeviationExt.html\n//! [measures from information theory]: trait.EntropyExt.html\n//! [histogram computation]: histogram/index.html\n//! [here]: https://github.com/rust-ndarray/ndarray-stats/issues/1\n//! [`NumPy`]: https://docs.scipy.org/doc/numpy-1.14.1/reference/routines.statistics.html\n//! [`StatsBase.jl`]: https://juliastats.github.io/StatsBase.jl/latest/\n\npub use crate::correlation::CorrelationExt;\npub use crate::deviation::DeviationExt;\npub use crate::entropy::EntropyExt;\npub use crate::histogram::HistogramExt;\npub use crate::maybe_nan::{MaybeNan, MaybeNanExt};\npub use crate::quantile::{interpolate, Quantile1dExt, QuantileExt};\npub use crate::sort::Sort1dExt;\npub use crate::summary_statistics::SummaryStatisticsExt;\n\n#[cfg(test)]\n#[macro_use]\nextern crate approx;\n\n#[macro_use]\nmod multi_input_error_macros {\n    macro_rules! return_err_if_empty {\n        ($arr:expr) => {\n            if $arr.len() == 0 {\n                return Err(MultiInputError::EmptyInput);\n            }\n        };\n    }\n    macro_rules! return_err_unless_same_shape {\n        ($arr_a:expr, $arr_b:expr) => {\n            use crate::errors::{MultiInputError, ShapeMismatch};\n            if $arr_a.shape() != $arr_b.shape() {\n                return Err(MultiInputError::ShapeMismatch(ShapeMismatch {\n                    first_shape: $arr_a.shape().to_vec(),\n                    second_shape: $arr_b.shape().to_vec(),\n                })\n                .into());\n            }\n        };\n    }\n}\n\n#[macro_use]\nmod private {\n    /// This is a public type in a private module, so it can be included in\n    /// public APIs, but other crates can't access it.\n    pub struct PrivateMarker;\n\n    /// Defines an associated function for a trait that is impossible for other\n    /// crates to implement. This makes it possible to add new associated\n    /// types/functions/consts/etc. to the trait without breaking changes.\n    macro_rules! private_decl {\n        () => {\n            /// This method makes this trait impossible to implement outside of\n            /// `ndarray-stats` so that we can freely add new methods, etc., to\n            /// this trait without breaking changes.\n            ///\n            /// We don't anticipate any other crates needing to implement this\n            /// trait, but if you do have such a use-case, please let us know.\n            ///\n            /// **Warning** This method is not considered part of the public\n            /// API, and client code should not rely on it being present. It\n            /// may be removed in a non-breaking release.\n            fn __private__(&self, _: crate::private::PrivateMarker);\n        };\n    }\n\n    /// Implements the associated function defined by `private_decl!`.\n    macro_rules! private_impl {\n        () => {\n            fn __private__(&self, _: crate::private::PrivateMarker) {}\n        };\n    }\n}\n\nmod correlation;\nmod deviation;\nmod entropy;\npub mod errors;\npub mod histogram;\nmod maybe_nan;\nmod quantile;\nmod sort;\nmod summary_statistics;\n"
  },
  {
    "path": "src/maybe_nan/impl_not_none.rs",
    "content": "use super::NotNone;\nuse num_traits::{FromPrimitive, ToPrimitive};\nuse std::cmp;\nuse std::fmt;\nuse std::ops::{Add, Deref, DerefMut, Div, Mul, Rem, Sub};\n\nimpl<T> Deref for NotNone<T> {\n    type Target = T;\n    fn deref(&self) -> &T {\n        match self.0 {\n            Some(ref inner) => inner,\n            None => unsafe { ::std::hint::unreachable_unchecked() },\n        }\n    }\n}\n\nimpl<T> DerefMut for NotNone<T> {\n    fn deref_mut(&mut self) -> &mut T {\n        match self.0 {\n            Some(ref mut inner) => inner,\n            None => unsafe { ::std::hint::unreachable_unchecked() },\n        }\n    }\n}\n\nimpl<T: fmt::Display> fmt::Display for NotNone<T> {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        self.deref().fmt(f)\n    }\n}\n\nimpl<T: Eq> Eq for NotNone<T> {}\n\nimpl<T: PartialEq> PartialEq for NotNone<T> {\n    fn eq(&self, other: &Self) -> bool {\n        self.deref().eq(other)\n    }\n}\n\nimpl<T: Ord> Ord for NotNone<T> {\n    fn cmp(&self, other: &Self) -> cmp::Ordering {\n        self.deref().cmp(other)\n    }\n}\n\nimpl<T: PartialOrd> PartialOrd for NotNone<T> {\n    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {\n        self.deref().partial_cmp(other)\n    }\n    fn lt(&self, other: &Self) -> bool {\n        self.deref().lt(other)\n    }\n    fn le(&self, other: &Self) -> bool {\n        self.deref().le(other)\n    }\n    fn gt(&self, other: &Self) -> bool {\n        self.deref().gt(other)\n    }\n    fn ge(&self, other: &Self) -> bool {\n        self.deref().ge(other)\n    }\n}\n\nimpl<T: Add> Add for NotNone<T> {\n    type Output = NotNone<T::Output>;\n    #[inline]\n    fn add(self, rhs: Self) -> Self::Output {\n        self.map(|v| v.add(rhs.unwrap()))\n    }\n}\n\nimpl<T: Sub> Sub for NotNone<T> {\n    type Output = NotNone<T::Output>;\n    #[inline]\n    fn sub(self, rhs: Self) -> Self::Output {\n        self.map(|v| v.sub(rhs.unwrap()))\n    }\n}\n\nimpl<T: Mul> Mul for NotNone<T> {\n    type Output = NotNone<T::Output>;\n    #[inline]\n    fn mul(self, rhs: Self) -> Self::Output {\n        self.map(|v| v.mul(rhs.unwrap()))\n    }\n}\n\nimpl<T: Div> Div for NotNone<T> {\n    type Output = NotNone<T::Output>;\n    #[inline]\n    fn div(self, rhs: Self) -> Self::Output {\n        self.map(|v| v.div(rhs.unwrap()))\n    }\n}\n\nimpl<T: Rem> Rem for NotNone<T> {\n    type Output = NotNone<T::Output>;\n    #[inline]\n    fn rem(self, rhs: Self) -> Self::Output {\n        self.map(|v| v.rem(rhs.unwrap()))\n    }\n}\n\nimpl<T: ToPrimitive> ToPrimitive for NotNone<T> {\n    #[inline]\n    fn to_isize(&self) -> Option<isize> {\n        self.deref().to_isize()\n    }\n    #[inline]\n    fn to_i8(&self) -> Option<i8> {\n        self.deref().to_i8()\n    }\n    #[inline]\n    fn to_i16(&self) -> Option<i16> {\n        self.deref().to_i16()\n    }\n    #[inline]\n    fn to_i32(&self) -> Option<i32> {\n        self.deref().to_i32()\n    }\n    #[inline]\n    fn to_i64(&self) -> Option<i64> {\n        self.deref().to_i64()\n    }\n    #[inline]\n    fn to_i128(&self) -> Option<i128> {\n        self.deref().to_i128()\n    }\n    #[inline]\n    fn to_usize(&self) -> Option<usize> {\n        self.deref().to_usize()\n    }\n    #[inline]\n    fn to_u8(&self) -> Option<u8> {\n        self.deref().to_u8()\n    }\n    #[inline]\n    fn to_u16(&self) -> Option<u16> {\n        self.deref().to_u16()\n    }\n    #[inline]\n    fn to_u32(&self) -> Option<u32> {\n        self.deref().to_u32()\n    }\n    #[inline]\n    fn to_u64(&self) -> Option<u64> {\n        self.deref().to_u64()\n    }\n    #[inline]\n    fn to_u128(&self) -> Option<u128> {\n        self.deref().to_u128()\n    }\n    #[inline]\n    fn to_f32(&self) -> Option<f32> {\n        self.deref().to_f32()\n    }\n    #[inline]\n    fn to_f64(&self) -> Option<f64> {\n        self.deref().to_f64()\n    }\n}\n\nimpl<T: FromPrimitive> FromPrimitive for NotNone<T> {\n    #[inline]\n    fn from_isize(n: isize) -> Option<Self> {\n        Self::try_new(T::from_isize(n))\n    }\n    #[inline]\n    fn from_i8(n: i8) -> Option<Self> {\n        Self::try_new(T::from_i8(n))\n    }\n    #[inline]\n    fn from_i16(n: i16) -> Option<Self> {\n        Self::try_new(T::from_i16(n))\n    }\n    #[inline]\n    fn from_i32(n: i32) -> Option<Self> {\n        Self::try_new(T::from_i32(n))\n    }\n    #[inline]\n    fn from_i64(n: i64) -> Option<Self> {\n        Self::try_new(T::from_i64(n))\n    }\n    #[inline]\n    fn from_i128(n: i128) -> Option<Self> {\n        Self::try_new(T::from_i128(n))\n    }\n    #[inline]\n    fn from_usize(n: usize) -> Option<Self> {\n        Self::try_new(T::from_usize(n))\n    }\n    #[inline]\n    fn from_u8(n: u8) -> Option<Self> {\n        Self::try_new(T::from_u8(n))\n    }\n    #[inline]\n    fn from_u16(n: u16) -> Option<Self> {\n        Self::try_new(T::from_u16(n))\n    }\n    #[inline]\n    fn from_u32(n: u32) -> Option<Self> {\n        Self::try_new(T::from_u32(n))\n    }\n    #[inline]\n    fn from_u64(n: u64) -> Option<Self> {\n        Self::try_new(T::from_u64(n))\n    }\n    #[inline]\n    fn from_u128(n: u128) -> Option<Self> {\n        Self::try_new(T::from_u128(n))\n    }\n    #[inline]\n    fn from_f32(n: f32) -> Option<Self> {\n        Self::try_new(T::from_f32(n))\n    }\n    #[inline]\n    fn from_f64(n: f64) -> Option<Self> {\n        Self::try_new(T::from_f64(n))\n    }\n}\n"
  },
  {
    "path": "src/maybe_nan/mod.rs",
    "content": "use ndarray::prelude::*;\nuse ndarray::{s, RemoveAxis};\nuse noisy_float::types::{N32, N64};\nuse std::mem;\n\n/// A number type that can have not-a-number values.\npub trait MaybeNan: Sized {\n    /// A type that is guaranteed not to be a NaN value.\n    type NotNan;\n\n    /// Returns `true` if the value is a NaN value.\n    fn is_nan(&self) -> bool;\n\n    /// Tries to convert the value to `NotNan`.\n    ///\n    /// Returns `None` if the value is a NaN value.\n    fn try_as_not_nan(&self) -> Option<&Self::NotNan>;\n\n    /// Converts the value.\n    ///\n    /// If the value is `None`, a NaN value is returned.\n    fn from_not_nan(_: Self::NotNan) -> Self;\n\n    /// Converts the value.\n    ///\n    /// If the value is `None`, a NaN value is returned.\n    fn from_not_nan_opt(_: Option<Self::NotNan>) -> Self;\n\n    /// Converts the value.\n    ///\n    /// If the value is `None`, a NaN value is returned.\n    fn from_not_nan_ref_opt(_: Option<&Self::NotNan>) -> &Self;\n\n    /// Returns a view with the NaN values removed.\n    ///\n    /// This modifies the input view by moving elements as necessary. The final\n    /// order of the elements is unspecified. However, this method is\n    /// idempotent, and given the same input data, the result is always ordered\n    /// the same way.\n    fn remove_nan_mut(_: ArrayViewMut1<'_, Self>) -> ArrayViewMut1<'_, Self::NotNan>;\n}\n\n/// Returns a view with the NaN values removed.\n///\n/// This modifies the input view by moving elements as necessary.\nfn remove_nan_mut<A: MaybeNan>(mut view: ArrayViewMut1<'_, A>) -> ArrayViewMut1<'_, A> {\n    if view.is_empty() {\n        return view.slice_move(s![..0]);\n    }\n    let mut i = 0;\n    let mut j = view.len() - 1;\n    loop {\n        // At this point, `i == 0 || !view[i-1].is_nan()`\n        // and `j == view.len() - 1 || view[j+1].is_nan()`.\n        while i <= j && !view[i].is_nan() {\n            i += 1;\n        }\n        // At this point, `view[i].is_nan() || i == j + 1`.\n        while j > i && view[j].is_nan() {\n            j -= 1;\n        }\n        // At this point, `!view[j].is_nan() || j == i`.\n        if i >= j {\n            return view.slice_move(s![..i]);\n        } else {\n            view.swap(i, j);\n            i += 1;\n            j -= 1;\n        }\n    }\n}\n\n/// Casts a view from one element type to another.\n///\n/// # Panics\n///\n/// Panics if `T` and `U` differ in size or alignment.\n///\n/// # Safety\n///\n/// The caller must ensure that qll elements in `view` are valid values for type `U`.\nunsafe fn cast_view_mut<T, U>(mut view: ArrayViewMut1<'_, T>) -> ArrayViewMut1<'_, U> {\n    assert_eq!(mem::size_of::<T>(), mem::size_of::<U>());\n    assert_eq!(mem::align_of::<T>(), mem::align_of::<U>());\n    let ptr: *mut U = view.as_mut_ptr().cast();\n    let len: usize = view.len_of(Axis(0));\n    let stride: isize = view.stride_of(Axis(0));\n    if len <= 1 {\n        // We can use a stride of `0` because the stride is irrelevant for the `len == 1` case.\n        let stride = 0;\n        ArrayViewMut1::from_shape_ptr([len].strides([stride]), ptr)\n    } else if stride >= 0 {\n        let stride = stride as usize;\n        ArrayViewMut1::from_shape_ptr([len].strides([stride]), ptr)\n    } else {\n        // At this point, stride < 0. We have to construct the view by using the inverse of the\n        // stride and then inverting the axis, since `ArrayViewMut::from_shape_ptr` requires the\n        // stride to be nonnegative.\n        let neg_stride = stride.checked_neg().unwrap() as usize;\n        // This is safe because `ndarray` guarantees that it's safe to offset the\n        // pointer anywhere in the array.\n        let neg_ptr = ptr.offset((len - 1) as isize * stride);\n        let mut v = ArrayViewMut1::from_shape_ptr([len].strides([neg_stride]), neg_ptr);\n        v.invert_axis(Axis(0));\n        v\n    }\n}\n\nmacro_rules! impl_maybenan_for_fxx {\n    ($fxx:ident, $Nxx:ident) => {\n        impl MaybeNan for $fxx {\n            type NotNan = $Nxx;\n\n            fn is_nan(&self) -> bool {\n                $fxx::is_nan(*self)\n            }\n\n            fn try_as_not_nan(&self) -> Option<&$Nxx> {\n                $Nxx::try_borrowed(self)\n            }\n\n            fn from_not_nan(value: $Nxx) -> $fxx {\n                value.raw()\n            }\n\n            fn from_not_nan_opt(value: Option<$Nxx>) -> $fxx {\n                match value {\n                    None => ::std::$fxx::NAN,\n                    Some(num) => num.raw(),\n                }\n            }\n\n            fn from_not_nan_ref_opt(value: Option<&$Nxx>) -> &$fxx {\n                match value {\n                    None => &::std::$fxx::NAN,\n                    Some(num) => num.as_ref(),\n                }\n            }\n\n            fn remove_nan_mut(view: ArrayViewMut1<'_, $fxx>) -> ArrayViewMut1<'_, $Nxx> {\n                let not_nan = remove_nan_mut(view);\n                // This is safe because `remove_nan_mut` has removed the NaN values, and `$Nxx` is\n                // a thin wrapper around `$fxx`.\n                unsafe { cast_view_mut(not_nan) }\n            }\n        }\n    };\n}\nimpl_maybenan_for_fxx!(f32, N32);\nimpl_maybenan_for_fxx!(f64, N64);\n\nmacro_rules! impl_maybenan_for_opt_never_nan {\n    ($ty:ty) => {\n        impl MaybeNan for Option<$ty> {\n            type NotNan = NotNone<$ty>;\n\n            fn is_nan(&self) -> bool {\n                self.is_none()\n            }\n\n            fn try_as_not_nan(&self) -> Option<&NotNone<$ty>> {\n                if self.is_none() {\n                    None\n                } else {\n                    // This is safe because we have checked for the `None`\n                    // case, and `NotNone<$ty>` is a thin wrapper around `Option<$ty>`.\n                    Some(unsafe { &*(self as *const Option<$ty> as *const NotNone<$ty>) })\n                }\n            }\n\n            fn from_not_nan(value: NotNone<$ty>) -> Option<$ty> {\n                value.into_inner()\n            }\n\n            fn from_not_nan_opt(value: Option<NotNone<$ty>>) -> Option<$ty> {\n                value.and_then(|v| v.into_inner())\n            }\n\n            fn from_not_nan_ref_opt(value: Option<&NotNone<$ty>>) -> &Option<$ty> {\n                match value {\n                    None => &None,\n                    // This is safe because `NotNone<$ty>` is a thin wrapper around\n                    // `Option<$ty>`.\n                    Some(num) => unsafe { &*(num as *const NotNone<$ty> as *const Option<$ty>) },\n                }\n            }\n\n            fn remove_nan_mut(view: ArrayViewMut1<'_, Self>) -> ArrayViewMut1<'_, Self::NotNan> {\n                let not_nan = remove_nan_mut(view);\n                // This is safe because `remove_nan_mut` has removed the `None`\n                // values, and `NotNone<$ty>` is a thin wrapper around `Option<$ty>`.\n                unsafe {\n                    ArrayViewMut1::from_shape_ptr(\n                        not_nan.dim(),\n                        not_nan.as_ptr() as *mut NotNone<$ty>,\n                    )\n                }\n            }\n        }\n    };\n}\nimpl_maybenan_for_opt_never_nan!(u8);\nimpl_maybenan_for_opt_never_nan!(u16);\nimpl_maybenan_for_opt_never_nan!(u32);\nimpl_maybenan_for_opt_never_nan!(u64);\nimpl_maybenan_for_opt_never_nan!(u128);\nimpl_maybenan_for_opt_never_nan!(i8);\nimpl_maybenan_for_opt_never_nan!(i16);\nimpl_maybenan_for_opt_never_nan!(i32);\nimpl_maybenan_for_opt_never_nan!(i64);\nimpl_maybenan_for_opt_never_nan!(i128);\nimpl_maybenan_for_opt_never_nan!(N32);\nimpl_maybenan_for_opt_never_nan!(N64);\n\n/// A thin wrapper around `Option` that guarantees that the value is not\n/// `None`.\n#[derive(Clone, Copy, Debug)]\n#[repr(transparent)]\npub struct NotNone<T>(Option<T>);\n\nimpl<T> NotNone<T> {\n    /// Creates a new `NotNone` containing the given value.\n    pub fn new(value: T) -> NotNone<T> {\n        NotNone(Some(value))\n    }\n\n    /// Creates a new `NotNone` containing the given value.\n    ///\n    /// Returns `None` if `value` is `None`.\n    pub fn try_new(value: Option<T>) -> Option<NotNone<T>> {\n        if value.is_some() {\n            Some(NotNone(value))\n        } else {\n            None\n        }\n    }\n\n    /// Returns the underling option.\n    pub fn into_inner(self) -> Option<T> {\n        self.0\n    }\n\n    /// Moves the value out of the inner option.\n    ///\n    /// This method is guaranteed not to panic.\n    pub fn unwrap(self) -> T {\n        match self.0 {\n            Some(inner) => inner,\n            None => unsafe { ::std::hint::unreachable_unchecked() },\n        }\n    }\n\n    /// Maps an `NotNone<T>` to `NotNone<U>` by applying a function to the\n    /// contained value.\n    pub fn map<U, F>(self, f: F) -> NotNone<U>\n    where\n        F: FnOnce(T) -> U,\n    {\n        NotNone::new(f(self.unwrap()))\n    }\n}\n\n/// Extension trait for `ArrayRef` providing NaN-related functionality.\npub trait MaybeNanExt<A, D>\nwhere\n    A: MaybeNan,\n    D: Dimension,\n{\n    /// Traverse the non-NaN array elements and apply a fold, returning the\n    /// resulting value.\n    ///\n    /// Elements are visited in arbitrary order.\n    fn fold_skipnan<'a, F, B>(&'a self, init: B, f: F) -> B\n    where\n        A: 'a,\n        F: FnMut(B, &'a A::NotNan) -> B;\n\n    /// Traverse the non-NaN elements and their indices and apply a fold,\n    /// returning the resulting value.\n    ///\n    /// Elements are visited in arbitrary order.\n    fn indexed_fold_skipnan<'a, F, B>(&'a self, init: B, f: F) -> B\n    where\n        A: 'a,\n        F: FnMut(B, (D::Pattern, &'a A::NotNan)) -> B;\n\n    /// Visit each non-NaN element in the array by calling `f` on each element.\n    ///\n    /// Elements are visited in arbitrary order.\n    fn visit_skipnan<'a, F>(&'a self, f: F)\n    where\n        A: 'a,\n        F: FnMut(&'a A::NotNan);\n\n    /// Fold non-NaN values along an axis.\n    ///\n    /// Combine the non-NaN elements of each subview with the previous using\n    /// the fold function and initial value init.\n    fn fold_axis_skipnan<B, F>(&self, axis: Axis, init: B, fold: F) -> Array<B, D::Smaller>\n    where\n        D: RemoveAxis,\n        F: FnMut(&B, &A::NotNan) -> B,\n        B: Clone;\n\n    /// Reduce the values along an axis into just one value, producing a new\n    /// array with one less dimension.\n    ///\n    /// The NaN values are removed from the 1-dimensional lanes, then they are\n    /// passed as mutable views to the reducer, allowing for side-effects.\n    ///\n    /// **Warnings**:\n    ///\n    /// * The lanes are visited in arbitrary order.\n    ///\n    /// * The order of the elements within the lanes is unspecified. However,\n    ///   if `mapping` is idempotent, this method is idempotent. Additionally,\n    ///   given the same input data, the lane is always ordered the same way.\n    ///\n    /// **Panics** if `axis` is out of bounds.\n    fn map_axis_skipnan_mut<'a, B, F>(&'a mut self, axis: Axis, mapping: F) -> Array<B, D::Smaller>\n    where\n        A: 'a,\n        D: RemoveAxis,\n        F: FnMut(ArrayViewMut1<'a, A::NotNan>) -> B;\n\n    private_decl! {}\n}\n\nimpl<A, D> MaybeNanExt<A, D> for ArrayRef<A, D>\nwhere\n    A: MaybeNan,\n    D: Dimension,\n{\n    fn fold_skipnan<'a, F, B>(&'a self, init: B, mut f: F) -> B\n    where\n        A: 'a,\n        F: FnMut(B, &'a A::NotNan) -> B,\n    {\n        self.fold(init, |acc, elem| {\n            if let Some(not_nan) = elem.try_as_not_nan() {\n                f(acc, not_nan)\n            } else {\n                acc\n            }\n        })\n    }\n\n    fn indexed_fold_skipnan<'a, F, B>(&'a self, init: B, mut f: F) -> B\n    where\n        A: 'a,\n        F: FnMut(B, (D::Pattern, &'a A::NotNan)) -> B,\n    {\n        self.indexed_iter().fold(init, |acc, (idx, elem)| {\n            if let Some(not_nan) = elem.try_as_not_nan() {\n                f(acc, (idx, not_nan))\n            } else {\n                acc\n            }\n        })\n    }\n\n    fn visit_skipnan<'a, F>(&'a self, mut f: F)\n    where\n        A: 'a,\n        F: FnMut(&'a A::NotNan),\n    {\n        self.for_each(|elem| {\n            if let Some(not_nan) = elem.try_as_not_nan() {\n                f(not_nan)\n            }\n        })\n    }\n\n    fn fold_axis_skipnan<B, F>(&self, axis: Axis, init: B, mut fold: F) -> Array<B, D::Smaller>\n    where\n        D: RemoveAxis,\n        F: FnMut(&B, &A::NotNan) -> B,\n        B: Clone,\n    {\n        self.fold_axis(axis, init, |acc, elem| {\n            if let Some(not_nan) = elem.try_as_not_nan() {\n                fold(acc, not_nan)\n            } else {\n                acc.clone()\n            }\n        })\n    }\n\n    fn map_axis_skipnan_mut<'a, B, F>(\n        &'a mut self,\n        axis: Axis,\n        mut mapping: F,\n    ) -> Array<B, D::Smaller>\n    where\n        A: 'a,\n        D: RemoveAxis,\n        F: FnMut(ArrayViewMut1<'a, A::NotNan>) -> B,\n    {\n        self.map_axis_mut(axis, |lane| mapping(A::remove_nan_mut(lane)))\n    }\n\n    private_impl! {}\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use quickcheck_macros::quickcheck;\n\n    #[quickcheck]\n    fn remove_nan_mut_idempotent(is_nan: Vec<bool>) -> bool {\n        let mut values: Vec<_> = is_nan\n            .into_iter()\n            .map(|is_nan| if is_nan { None } else { Some(1) })\n            .collect();\n        let view = ArrayViewMut1::from_shape(values.len(), &mut values).unwrap();\n        let removed = remove_nan_mut(view);\n        removed == remove_nan_mut(removed.to_owned().view_mut())\n    }\n\n    #[quickcheck]\n    fn remove_nan_mut_only_nan_remaining(is_nan: Vec<bool>) -> bool {\n        let mut values: Vec<_> = is_nan\n            .into_iter()\n            .map(|is_nan| if is_nan { None } else { Some(1) })\n            .collect();\n        let view = ArrayViewMut1::from_shape(values.len(), &mut values).unwrap();\n        remove_nan_mut(view).iter().all(|elem| !elem.is_nan())\n    }\n\n    #[quickcheck]\n    fn remove_nan_mut_keep_all_non_nan(is_nan: Vec<bool>) -> bool {\n        let non_nan_count = is_nan.iter().filter(|&&is_nan| !is_nan).count();\n        let mut values: Vec<_> = is_nan\n            .into_iter()\n            .map(|is_nan| if is_nan { None } else { Some(1) })\n            .collect();\n        let view = ArrayViewMut1::from_shape(values.len(), &mut values).unwrap();\n        remove_nan_mut(view).len() == non_nan_count\n    }\n}\n\nmod impl_not_none;\n"
  },
  {
    "path": "src/quantile/interpolate.rs",
    "content": "//! Interpolation strategies.\nuse noisy_float::types::N64;\nuse num_traits::{Float, FromPrimitive, NumOps, ToPrimitive};\n\nfn float_quantile_index(q: N64, len: usize) -> N64 {\n    q * ((len - 1) as f64)\n}\n\n/// Returns the fraction that the quantile is between the lower and higher indices.\n///\n/// This ranges from 0, where the quantile exactly corresponds the lower index,\n/// to 1, where the quantile exactly corresponds to the higher index.\nfn float_quantile_index_fraction(q: N64, len: usize) -> N64 {\n    float_quantile_index(q, len).fract()\n}\n\n/// Returns the index of the value on the lower side of the quantile.\npub(crate) fn lower_index(q: N64, len: usize) -> usize {\n    float_quantile_index(q, len).floor().to_usize().unwrap()\n}\n\n/// Returns the index of the value on the higher side of the quantile.\npub(crate) fn higher_index(q: N64, len: usize) -> usize {\n    float_quantile_index(q, len).ceil().to_usize().unwrap()\n}\n\n/// Used to provide an interpolation strategy to [`quantile_axis_mut`].\n///\n/// [`quantile_axis_mut`]: ../trait.QuantileExt.html#tymethod.quantile_axis_mut\npub trait Interpolate<T> {\n    /// Returns `true` iff the lower value is needed to compute the\n    /// interpolated value.\n    #[doc(hidden)]\n    fn needs_lower(q: N64, len: usize) -> bool;\n\n    /// Returns `true` iff the higher value is needed to compute the\n    /// interpolated value.\n    #[doc(hidden)]\n    fn needs_higher(q: N64, len: usize) -> bool;\n\n    /// Computes the interpolated value.\n    ///\n    /// **Panics** if `None` is provided for the lower value when it's needed\n    /// or if `None` is provided for the higher value when it's needed.\n    #[doc(hidden)]\n    fn interpolate(lower: Option<T>, higher: Option<T>, q: N64, len: usize) -> T;\n\n    private_decl! {}\n}\n\n/// Select the higher value.\npub struct Higher;\n/// Select the lower value.\npub struct Lower;\n/// Select the nearest value.\npub struct Nearest;\n/// Select the midpoint of the two values (`(lower + higher) / 2`).\npub struct Midpoint;\n/// Linearly interpolate between the two values\n/// (`lower + (higher - lower) * fraction`, where `fraction` is the\n/// fractional part of the index surrounded by `lower` and `higher`).\npub struct Linear;\n\nimpl<T> Interpolate<T> for Higher {\n    fn needs_lower(_q: N64, _len: usize) -> bool {\n        false\n    }\n    fn needs_higher(_q: N64, _len: usize) -> bool {\n        true\n    }\n    fn interpolate(_lower: Option<T>, higher: Option<T>, _q: N64, _len: usize) -> T {\n        higher.unwrap()\n    }\n    private_impl! {}\n}\n\nimpl<T> Interpolate<T> for Lower {\n    fn needs_lower(_q: N64, _len: usize) -> bool {\n        true\n    }\n    fn needs_higher(_q: N64, _len: usize) -> bool {\n        false\n    }\n    fn interpolate(lower: Option<T>, _higher: Option<T>, _q: N64, _len: usize) -> T {\n        lower.unwrap()\n    }\n    private_impl! {}\n}\n\nimpl<T> Interpolate<T> for Nearest {\n    fn needs_lower(q: N64, len: usize) -> bool {\n        float_quantile_index_fraction(q, len) < 0.5\n    }\n    fn needs_higher(q: N64, len: usize) -> bool {\n        !<Self as Interpolate<T>>::needs_lower(q, len)\n    }\n    fn interpolate(lower: Option<T>, higher: Option<T>, q: N64, len: usize) -> T {\n        if <Self as Interpolate<T>>::needs_lower(q, len) {\n            lower.unwrap()\n        } else {\n            higher.unwrap()\n        }\n    }\n    private_impl! {}\n}\n\nimpl<T> Interpolate<T> for Midpoint\nwhere\n    T: NumOps + Clone + FromPrimitive,\n{\n    fn needs_lower(_q: N64, _len: usize) -> bool {\n        true\n    }\n    fn needs_higher(_q: N64, _len: usize) -> bool {\n        true\n    }\n    fn interpolate(lower: Option<T>, higher: Option<T>, _q: N64, _len: usize) -> T {\n        let denom = T::from_u8(2).unwrap();\n        let lower = lower.unwrap();\n        let higher = higher.unwrap();\n        lower.clone() + (higher.clone() - lower.clone()) / denom.clone()\n    }\n    private_impl! {}\n}\n\nimpl<T> Interpolate<T> for Linear\nwhere\n    T: NumOps + Clone + FromPrimitive + ToPrimitive,\n{\n    fn needs_lower(_q: N64, _len: usize) -> bool {\n        true\n    }\n    fn needs_higher(_q: N64, _len: usize) -> bool {\n        true\n    }\n    fn interpolate(lower: Option<T>, higher: Option<T>, q: N64, len: usize) -> T {\n        let fraction = float_quantile_index_fraction(q, len).to_f64().unwrap();\n        let lower = lower.unwrap();\n        let higher = higher.unwrap();\n        let lower_f64 = lower.to_f64().unwrap();\n        let higher_f64 = higher.to_f64().unwrap();\n        lower.clone() + T::from_f64(fraction * (higher_f64 - lower_f64)).unwrap()\n    }\n    private_impl! {}\n}\n"
  },
  {
    "path": "src/quantile/mod.rs",
    "content": "use self::interpolate::{higher_index, lower_index, Interpolate};\nuse super::sort::get_many_from_sorted_mut_unchecked;\nuse crate::errors::QuantileError;\nuse crate::errors::{EmptyInput, MinMaxError, MinMaxError::UndefinedOrder};\nuse crate::{MaybeNan, MaybeNanExt};\nuse ndarray::prelude::*;\nuse ndarray::{RemoveAxis, Zip};\nuse noisy_float::types::N64;\nuse std::cmp;\n\n/// Quantile methods for `ArrayRef`.\npub trait QuantileExt<A, D>\nwhere\n    D: Dimension,\n{\n    /// Finds the index of the minimum value of the array.\n    ///\n    /// Returns `Err(MinMaxError::UndefinedOrder)` if any of the pairwise\n    /// orderings tested by the function are undefined. (For example, this\n    /// occurs if there are any floating-point NaN values in the array.)\n    ///\n    /// Returns `Err(MinMaxError::EmptyInput)` if the array is empty.\n    ///\n    /// Even if there are multiple (equal) elements that are minima, only one\n    /// index is returned. (Which one is returned is unspecified and may depend\n    /// on the memory layout of the array.)\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use ndarray::array;\n    /// use ndarray_stats::QuantileExt;\n    ///\n    /// let a = array![[1., 3., 5.],\n    ///                [2., 0., 6.]];\n    /// assert_eq!(a.argmin(), Ok((1, 1)));\n    /// ```\n    fn argmin(&self) -> Result<D::Pattern, MinMaxError>\n    where\n        A: PartialOrd;\n\n    /// Finds the index of the minimum value of the array skipping NaN values.\n    ///\n    /// Returns `Err(EmptyInput)` if the array is empty or none of the values in the array\n    /// are non-NaN values.\n    ///\n    /// Even if there are multiple (equal) elements that are minima, only one\n    /// index is returned. (Which one is returned is unspecified and may depend\n    /// on the memory layout of the array.)\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use ndarray::array;\n    /// use ndarray_stats::QuantileExt;\n    ///\n    /// let a = array![[::std::f64::NAN, 3., 5.],\n    ///                [2., 0., 6.]];\n    /// assert_eq!(a.argmin_skipnan(), Ok((1, 1)));\n    /// ```\n    fn argmin_skipnan(&self) -> Result<D::Pattern, EmptyInput>\n    where\n        A: MaybeNan,\n        A::NotNan: Ord;\n\n    /// Finds the elementwise minimum of the array.\n    ///\n    /// Returns `Err(MinMaxError::UndefinedOrder)` if any of the pairwise\n    /// orderings tested by the function are undefined. (For example, this\n    /// occurs if there are any floating-point NaN values in the array.)\n    ///\n    /// Returns `Err(MinMaxError::EmptyInput)` if the array is empty.\n    ///\n    /// Even if there are multiple (equal) elements that are minima, only one\n    /// is returned. (Which one is returned is unspecified and may depend on\n    /// the memory layout of the array.)\n    fn min(&self) -> Result<&A, MinMaxError>\n    where\n        A: PartialOrd;\n\n    /// Finds the elementwise minimum of the array, skipping NaN values.\n    ///\n    /// Even if there are multiple (equal) elements that are minima, only one\n    /// is returned. (Which one is returned is unspecified and may depend on\n    /// the memory layout of the array.)\n    ///\n    /// **Warning** This method will return a NaN value if none of the values\n    /// in the array are non-NaN values. Note that the NaN value might not be\n    /// in the array.\n    fn min_skipnan(&self) -> &A\n    where\n        A: MaybeNan,\n        A::NotNan: Ord;\n\n    /// Finds the index of the maximum value of the array.\n    ///\n    /// Returns `Err(MinMaxError::UndefinedOrder)` if any of the pairwise\n    /// orderings tested by the function are undefined. (For example, this\n    /// occurs if there are any floating-point NaN values in the array.)\n    ///\n    /// Returns `Err(MinMaxError::EmptyInput)` if the array is empty.\n    ///\n    /// Even if there are multiple (equal) elements that are maxima, only one\n    /// index is returned. (Which one is returned is unspecified and may depend\n    /// on the memory layout of the array.)\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use ndarray::array;\n    /// use ndarray_stats::QuantileExt;\n    ///\n    /// let a = array![[1., 3., 7.],\n    ///                [2., 5., 6.]];\n    /// assert_eq!(a.argmax(), Ok((0, 2)));\n    /// ```\n    fn argmax(&self) -> Result<D::Pattern, MinMaxError>\n    where\n        A: PartialOrd;\n\n    /// Finds the index of the maximum value of the array skipping NaN values.\n    ///\n    /// Returns `Err(EmptyInput)` if the array is empty or none of the values in the array\n    /// are non-NaN values.\n    ///\n    /// Even if there are multiple (equal) elements that are maxima, only one\n    /// index is returned. (Which one is returned is unspecified and may depend\n    /// on the memory layout of the array.)\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use ndarray::array;\n    /// use ndarray_stats::QuantileExt;\n    ///\n    /// let a = array![[::std::f64::NAN, 3., 5.],\n    ///                [2., 0., 6.]];\n    /// assert_eq!(a.argmax_skipnan(), Ok((1, 2)));\n    /// ```\n    fn argmax_skipnan(&self) -> Result<D::Pattern, EmptyInput>\n    where\n        A: MaybeNan,\n        A::NotNan: Ord;\n\n    /// Finds the elementwise maximum of the array.\n    ///\n    /// Returns `Err(MinMaxError::UndefinedOrder)` if any of the pairwise\n    /// orderings tested by the function are undefined. (For example, this\n    /// occurs if there are any floating-point NaN values in the array.)\n    ///\n    /// Returns `Err(EmptyInput)` if the array is empty.\n    ///\n    /// Even if there are multiple (equal) elements that are maxima, only one\n    /// is returned. (Which one is returned is unspecified and may depend on\n    /// the memory layout of the array.)\n    fn max(&self) -> Result<&A, MinMaxError>\n    where\n        A: PartialOrd;\n\n    /// Finds the elementwise maximum of the array, skipping NaN values.\n    ///\n    /// Even if there are multiple (equal) elements that are maxima, only one\n    /// is returned. (Which one is returned is unspecified and may depend on\n    /// the memory layout of the array.)\n    ///\n    /// **Warning** This method will return a NaN value if none of the values\n    /// in the array are non-NaN values. Note that the NaN value might not be\n    /// in the array.\n    fn max_skipnan(&self) -> &A\n    where\n        A: MaybeNan,\n        A::NotNan: Ord;\n\n    /// Return the qth quantile of the data along the specified axis.\n    ///\n    /// `q` needs to be a float between 0 and 1, bounds included.\n    /// The qth quantile for a 1-dimensional lane of length `N` is defined\n    /// as the element that would be indexed as `(N-1)q` if the lane were to be sorted\n    /// in increasing order.\n    /// If `(N-1)q` is not an integer the desired quantile lies between\n    /// two data points: we return the lower, nearest, higher or interpolated\n    /// value depending on the `interpolate` strategy.\n    ///\n    /// Some examples:\n    /// - `q=0.` returns the minimum along each 1-dimensional lane;\n    /// - `q=0.5` returns the median along each 1-dimensional lane;\n    /// - `q=1.` returns the maximum along each 1-dimensional lane.\n    /// (`q=0` and `q=1` are considered improper quantiles)\n    ///\n    /// The array is shuffled **in place** along each 1-dimensional lane in\n    /// order to produce the required quantile without allocating a copy\n    /// of the original array. Each 1-dimensional lane is shuffled independently\n    /// from the others.\n    /// No assumptions should be made on the ordering of the array elements\n    /// after this computation.\n    ///\n    /// Complexity ([quickselect](https://en.wikipedia.org/wiki/Quickselect)):\n    /// - average case: O(`m`);\n    /// - worst case: O(`m`^2);\n    /// where `m` is the number of elements in the array.\n    ///\n    /// Returns `Err(EmptyInput)` when the specified axis has length 0.\n    ///\n    /// Returns `Err(InvalidQuantile(q))` if `q` is not between `0.` and `1.` (inclusive).\n    ///\n    /// **Panics** if `axis` is out of bounds.\n    fn quantile_axis_mut<I>(\n        &mut self,\n        axis: Axis,\n        q: N64,\n        interpolate: &I,\n    ) -> Result<Array<A, D::Smaller>, QuantileError>\n    where\n        D: RemoveAxis,\n        A: Ord + Clone,\n        I: Interpolate<A>;\n\n    /// A bulk version of [`quantile_axis_mut`], optimized to retrieve multiple\n    /// quantiles at once.\n    ///\n    /// Returns an `Array`, where subviews along `axis` of the array correspond\n    /// to the elements of `qs`.\n    ///\n    /// See [`quantile_axis_mut`] for additional details on quantiles and the algorithm\n    /// used to retrieve them.\n    ///\n    /// Returns `Err(EmptyInput)` when the specified axis has length 0.\n    ///\n    /// Returns `Err(InvalidQuantile(q))` if any `q` in `qs` is not between `0.` and `1.` (inclusive).\n    ///\n    /// **Panics** if `axis` is out of bounds.\n    ///\n    /// [`quantile_axis_mut`]: #tymethod.quantile_axis_mut\n    ///\n    /// # Example\n    ///\n    /// ```rust\n    /// use ndarray::{array, aview1, Axis};\n    /// use ndarray_stats::{QuantileExt, interpolate::Nearest};\n    /// use noisy_float::types::n64;\n    ///\n    /// let mut data = array![[3, 4, 5], [6, 7, 8]];\n    /// let axis = Axis(1);\n    /// let qs = &[n64(0.3), n64(0.7)];\n    /// let quantiles = data.quantiles_axis_mut(axis, &aview1(qs), &Nearest).unwrap();\n    /// for (&q, quantile) in qs.iter().zip(quantiles.axis_iter(axis)) {\n    ///     assert_eq!(quantile, data.quantile_axis_mut(axis, q, &Nearest).unwrap());\n    /// }\n    /// ```\n    fn quantiles_axis_mut<I>(\n        &mut self,\n        axis: Axis,\n        qs: &ArrayRef<N64, Ix1>,\n        interpolate: &I,\n    ) -> Result<Array<A, D>, QuantileError>\n    where\n        D: RemoveAxis,\n        A: Ord + Clone,\n        I: Interpolate<A>;\n\n    /// Return the `q`th quantile of the data along the specified axis, skipping NaN values.\n    ///\n    /// See [`quantile_axis_mut`](#tymethod.quantile_axis_mut) for details.\n    fn quantile_axis_skipnan_mut<I>(\n        &mut self,\n        axis: Axis,\n        q: N64,\n        interpolate: &I,\n    ) -> Result<Array<A, D::Smaller>, QuantileError>\n    where\n        D: RemoveAxis,\n        A: MaybeNan,\n        A::NotNan: Clone + Ord,\n        I: Interpolate<A::NotNan>;\n\n    private_decl! {}\n}\n\nimpl<A, D> QuantileExt<A, D> for ArrayRef<A, D>\nwhere\n    D: Dimension,\n{\n    fn argmin(&self) -> Result<D::Pattern, MinMaxError>\n    where\n        A: PartialOrd,\n    {\n        let mut current_min = self.first().ok_or(EmptyInput)?;\n        let mut current_pattern_min = D::zeros(self.ndim()).into_pattern();\n\n        for (pattern, elem) in self.indexed_iter() {\n            if elem.partial_cmp(current_min).ok_or(UndefinedOrder)? == cmp::Ordering::Less {\n                current_pattern_min = pattern;\n                current_min = elem\n            }\n        }\n\n        Ok(current_pattern_min)\n    }\n\n    fn argmin_skipnan(&self) -> Result<D::Pattern, EmptyInput>\n    where\n        A: MaybeNan,\n        A::NotNan: Ord,\n    {\n        let mut pattern_min = D::zeros(self.ndim()).into_pattern();\n        let min = self.indexed_fold_skipnan(None, |current_min, (pattern, elem)| {\n            Some(match current_min {\n                Some(m) if (m <= elem) => m,\n                _ => {\n                    pattern_min = pattern;\n                    elem\n                }\n            })\n        });\n        if min.is_some() {\n            Ok(pattern_min)\n        } else {\n            Err(EmptyInput)\n        }\n    }\n\n    fn min(&self) -> Result<&A, MinMaxError>\n    where\n        A: PartialOrd,\n    {\n        let first = self.first().ok_or(EmptyInput)?;\n        self.fold(Ok(first), |acc, elem| {\n            let acc = acc?;\n            match elem.partial_cmp(acc).ok_or(UndefinedOrder)? {\n                cmp::Ordering::Less => Ok(elem),\n                _ => Ok(acc),\n            }\n        })\n    }\n\n    fn min_skipnan(&self) -> &A\n    where\n        A: MaybeNan,\n        A::NotNan: Ord,\n    {\n        let first = self.first().and_then(|v| v.try_as_not_nan());\n        A::from_not_nan_ref_opt(self.fold_skipnan(first, |acc, elem| {\n            Some(match acc {\n                Some(acc) => acc.min(elem),\n                None => elem,\n            })\n        }))\n    }\n\n    fn argmax(&self) -> Result<D::Pattern, MinMaxError>\n    where\n        A: PartialOrd,\n    {\n        let mut current_max = self.first().ok_or(EmptyInput)?;\n        let mut current_pattern_max = D::zeros(self.ndim()).into_pattern();\n\n        for (pattern, elem) in self.indexed_iter() {\n            if elem.partial_cmp(current_max).ok_or(UndefinedOrder)? == cmp::Ordering::Greater {\n                current_pattern_max = pattern;\n                current_max = elem\n            }\n        }\n\n        Ok(current_pattern_max)\n    }\n\n    fn argmax_skipnan(&self) -> Result<D::Pattern, EmptyInput>\n    where\n        A: MaybeNan,\n        A::NotNan: Ord,\n    {\n        let mut pattern_max = D::zeros(self.ndim()).into_pattern();\n        let max = self.indexed_fold_skipnan(None, |current_max, (pattern, elem)| {\n            Some(match current_max {\n                Some(m) if m >= elem => m,\n                _ => {\n                    pattern_max = pattern;\n                    elem\n                }\n            })\n        });\n        if max.is_some() {\n            Ok(pattern_max)\n        } else {\n            Err(EmptyInput)\n        }\n    }\n\n    fn max(&self) -> Result<&A, MinMaxError>\n    where\n        A: PartialOrd,\n    {\n        let first = self.first().ok_or(EmptyInput)?;\n        self.fold(Ok(first), |acc, elem| {\n            let acc = acc?;\n            match elem.partial_cmp(acc).ok_or(UndefinedOrder)? {\n                cmp::Ordering::Greater => Ok(elem),\n                _ => Ok(acc),\n            }\n        })\n    }\n\n    fn max_skipnan(&self) -> &A\n    where\n        A: MaybeNan,\n        A::NotNan: Ord,\n    {\n        let first = self.first().and_then(|v| v.try_as_not_nan());\n        A::from_not_nan_ref_opt(self.fold_skipnan(first, |acc, elem| {\n            Some(match acc {\n                Some(acc) => acc.max(elem),\n                None => elem,\n            })\n        }))\n    }\n\n    fn quantiles_axis_mut<I>(\n        &mut self,\n        axis: Axis,\n        qs: &ArrayRef<N64, Ix1>,\n        interpolate: &I,\n    ) -> Result<Array<A, D>, QuantileError>\n    where\n        D: RemoveAxis,\n        A: Ord + Clone,\n        I: Interpolate<A>,\n    {\n        // Minimize number of type parameters to avoid monomorphization bloat.\n        fn quantiles_axis_mut<A, D, I>(\n            mut data: ArrayViewMut<'_, A, D>,\n            axis: Axis,\n            qs: ArrayView1<'_, N64>,\n            _interpolate: &I,\n        ) -> Result<Array<A, D>, QuantileError>\n        where\n            D: RemoveAxis,\n            A: Ord + Clone,\n            I: Interpolate<A>,\n        {\n            for &q in qs {\n                if !((q >= 0.) && (q <= 1.)) {\n                    return Err(QuantileError::InvalidQuantile(q));\n                }\n            }\n\n            let axis_len = data.len_of(axis);\n            if axis_len == 0 {\n                return Err(QuantileError::EmptyInput);\n            }\n\n            let mut results_shape = data.raw_dim();\n            results_shape[axis.index()] = qs.len();\n            if results_shape.size() == 0 {\n                return Ok(Array::from_shape_vec(results_shape, Vec::new()).unwrap());\n            }\n\n            let mut searched_indexes = Vec::with_capacity(2 * qs.len());\n            for &q in &qs {\n                if I::needs_lower(q, axis_len) {\n                    searched_indexes.push(lower_index(q, axis_len));\n                }\n                if I::needs_higher(q, axis_len) {\n                    searched_indexes.push(higher_index(q, axis_len));\n                }\n            }\n            searched_indexes.sort();\n            searched_indexes.dedup();\n\n            let mut results = Array::from_elem(results_shape, data.first().unwrap().clone());\n            Zip::from(results.lanes_mut(axis))\n                .and(data.lanes_mut(axis))\n                .for_each(|mut results, mut data| {\n                    let index_map =\n                        get_many_from_sorted_mut_unchecked(&mut data, &searched_indexes);\n                    for (result, &q) in results.iter_mut().zip(qs) {\n                        let lower = if I::needs_lower(q, axis_len) {\n                            Some(index_map[&lower_index(q, axis_len)].clone())\n                        } else {\n                            None\n                        };\n                        let higher = if I::needs_higher(q, axis_len) {\n                            Some(index_map[&higher_index(q, axis_len)].clone())\n                        } else {\n                            None\n                        };\n                        *result = I::interpolate(lower, higher, q, axis_len);\n                    }\n                });\n            Ok(results)\n        }\n\n        quantiles_axis_mut(self.view_mut(), axis, qs.view(), interpolate)\n    }\n\n    fn quantile_axis_mut<I>(\n        &mut self,\n        axis: Axis,\n        q: N64,\n        interpolate: &I,\n    ) -> Result<Array<A, D::Smaller>, QuantileError>\n    where\n        D: RemoveAxis,\n        A: Ord + Clone,\n        I: Interpolate<A>,\n    {\n        self.quantiles_axis_mut(axis, &aview1(&[q]), interpolate)\n            .map(|a| a.index_axis_move(axis, 0))\n    }\n\n    fn quantile_axis_skipnan_mut<I>(\n        &mut self,\n        axis: Axis,\n        q: N64,\n        interpolate: &I,\n    ) -> Result<Array<A, D::Smaller>, QuantileError>\n    where\n        D: RemoveAxis,\n        A: MaybeNan,\n        A::NotNan: Clone + Ord,\n        I: Interpolate<A::NotNan>,\n    {\n        if !((q >= 0.) && (q <= 1.)) {\n            return Err(QuantileError::InvalidQuantile(q));\n        }\n\n        if self.len_of(axis) == 0 {\n            return Err(QuantileError::EmptyInput);\n        }\n\n        let quantile = self.map_axis_mut(axis, |lane| {\n            let mut not_nan = A::remove_nan_mut(lane);\n            A::from_not_nan_opt(if not_nan.is_empty() {\n                None\n            } else {\n                Some(\n                    not_nan\n                        .quantile_axis_mut::<I>(Axis(0), q, interpolate)\n                        .unwrap()\n                        .into_scalar(),\n                )\n            })\n        });\n        Ok(quantile)\n    }\n\n    private_impl! {}\n}\n\n/// Quantile methods for 1-D arrays.\npub trait Quantile1dExt<A> {\n    /// Return the qth quantile of the data.\n    ///\n    /// `q` needs to be a float between 0 and 1, bounds included.\n    /// The qth quantile for a 1-dimensional array of length `N` is defined\n    /// as the element that would be indexed as `(N-1)q` if the array were to be sorted\n    /// in increasing order.\n    /// If `(N-1)q` is not an integer the desired quantile lies between\n    /// two data points: we return the lower, nearest, higher or interpolated\n    /// value depending on the `interpolate` strategy.\n    ///\n    /// Some examples:\n    /// - `q=0.` returns the minimum;\n    /// - `q=0.5` returns the median;\n    /// - `q=1.` returns the maximum.\n    /// (`q=0` and `q=1` are considered improper quantiles)\n    ///\n    /// The array is shuffled **in place** in order to produce the required quantile\n    /// without allocating a copy.\n    /// No assumptions should be made on the ordering of the array elements\n    /// after this computation.\n    ///\n    /// Complexity ([quickselect](https://en.wikipedia.org/wiki/Quickselect)):\n    /// - average case: O(`m`);\n    /// - worst case: O(`m`^2);\n    /// where `m` is the number of elements in the array.\n    ///\n    /// Returns `Err(EmptyInput)` if the array is empty.\n    ///\n    /// Returns `Err(InvalidQuantile(q))` if `q` is not between `0.` and `1.` (inclusive).\n    fn quantile_mut<I>(&mut self, q: N64, interpolate: &I) -> Result<A, QuantileError>\n    where\n        A: Ord + Clone,\n        I: Interpolate<A>;\n\n    /// A bulk version of [`quantile_mut`], optimized to retrieve multiple\n    /// quantiles at once.\n    ///\n    /// Returns an `Array`, where the elements of the array correspond to the\n    /// elements of `qs`.\n    ///\n    /// Returns `Err(EmptyInput)` if the array is empty.\n    ///\n    /// Returns `Err(InvalidQuantile(q))` if any `q` in\n    /// `qs` is not between `0.` and `1.` (inclusive).\n    ///\n    /// See [`quantile_mut`] for additional details on quantiles and the algorithm\n    /// used to retrieve them.\n    ///\n    /// [`quantile_mut`]: #tymethod.quantile_mut\n    fn quantiles_mut<I>(\n        &mut self,\n        qs: &ArrayRef<N64, Ix1>,\n        interpolate: &I,\n    ) -> Result<Array1<A>, QuantileError>\n    where\n        A: Ord + Clone,\n        I: Interpolate<A>;\n\n    private_decl! {}\n}\n\nimpl<A> Quantile1dExt<A> for ArrayRef<A, Ix1> {\n    fn quantile_mut<I>(&mut self, q: N64, interpolate: &I) -> Result<A, QuantileError>\n    where\n        A: Ord + Clone,\n        I: Interpolate<A>,\n    {\n        Ok(self\n            .quantile_axis_mut(Axis(0), q, interpolate)?\n            .into_scalar())\n    }\n\n    fn quantiles_mut<I>(\n        &mut self,\n        qs: &ArrayRef<N64, Ix1>,\n        interpolate: &I,\n    ) -> Result<Array1<A>, QuantileError>\n    where\n        A: Ord + Clone,\n        I: Interpolate<A>,\n    {\n        self.quantiles_axis_mut(Axis(0), qs, interpolate)\n    }\n\n    private_impl! {}\n}\n\npub mod interpolate;\n"
  },
  {
    "path": "src/sort.rs",
    "content": "use indexmap::IndexMap;\nuse ndarray::prelude::*;\nuse ndarray::Slice;\nuse rand::prelude::*;\nuse rand::thread_rng;\n\n/// Methods for sorting and partitioning 1-D arrays.\npub trait Sort1dExt<A> {\n    /// Return the element that would occupy the `i`-th position if\n    /// the array were sorted in increasing order.\n    ///\n    /// The array is shuffled **in place** to retrieve the desired element:\n    /// no copy of the array is allocated.\n    /// After the shuffling, all elements with an index smaller than `i`\n    /// are smaller than the desired element, while all elements with\n    /// an index greater or equal than `i` are greater than or equal\n    /// to the desired element.\n    ///\n    /// No other assumptions should be made on the ordering of the\n    /// elements after this computation.\n    ///\n    /// Complexity ([quickselect](https://en.wikipedia.org/wiki/Quickselect)):\n    /// - average case: O(`n`);\n    /// - worst case: O(`n`^2);\n    /// where n is the number of elements in the array.\n    ///\n    /// **Panics** if `i` is greater than or equal to `n`.\n    fn get_from_sorted_mut(&mut self, i: usize) -> A\n    where\n        A: Ord + Clone;\n\n    /// A bulk version of [`get_from_sorted_mut`], optimized to retrieve multiple\n    /// indexes at once.\n    /// It returns an `IndexMap`, with indexes as keys and retrieved elements as\n    /// values.\n    /// The `IndexMap` is sorted with respect to indexes in increasing order:\n    /// this ordering is preserved when you iterate over it (using `iter`/`into_iter`).\n    ///\n    /// **Panics** if any element in `indexes` is greater than or equal to `n`,\n    /// where `n` is the length of the array..\n    ///\n    /// [`get_from_sorted_mut`]: #tymethod.get_from_sorted_mut\n    fn get_many_from_sorted_mut(&mut self, indexes: &ArrayRef1<usize>) -> IndexMap<usize, A>\n    where\n        A: Ord + Clone;\n\n    /// Partitions the array in increasing order based on the value initially\n    /// located at `pivot_index` and returns the new index of the value.\n    ///\n    /// The elements are rearranged in such a way that the value initially\n    /// located at `pivot_index` is moved to the position it would be in an\n    /// array sorted in increasing order. The return value is the new index of\n    /// the value after rearrangement. All elements smaller than the value are\n    /// moved to its left and all elements equal or greater than the value are\n    /// moved to its right. The ordering of the elements in the two partitions\n    /// is undefined.\n    ///\n    /// `self` is shuffled **in place** to operate the desired partition:\n    /// no copy of the array is allocated.\n    ///\n    /// The method uses Hoare's partition algorithm.\n    /// Complexity: O(`n`), where `n` is the number of elements in the array.\n    /// Average number of element swaps: n/6 - 1/3 (see\n    /// [link](https://cs.stackexchange.com/questions/11458/quicksort-partitioning-hoare-vs-lomuto/11550))\n    ///\n    /// **Panics** if `pivot_index` is greater than or equal to `n`.\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use ndarray::array;\n    /// use ndarray_stats::Sort1dExt;\n    ///\n    /// let mut data = array![3, 1, 4, 5, 2];\n    /// let pivot_index = 2;\n    /// let pivot_value = data[pivot_index];\n    ///\n    /// // Partition by the value located at `pivot_index`.\n    /// let new_index = data.partition_mut(pivot_index);\n    /// // The pivot value is now located at `new_index`.\n    /// assert_eq!(data[new_index], pivot_value);\n    /// // Elements less than that value are moved to the left.\n    /// for i in 0..new_index {\n    ///     assert!(data[i] < pivot_value);\n    /// }\n    /// // Elements greater than or equal to that value are moved to the right.\n    /// for i in (new_index + 1)..data.len() {\n    ///      assert!(data[i] >= pivot_value);\n    /// }\n    /// ```\n    fn partition_mut(&mut self, pivot_index: usize) -> usize\n    where\n        A: Ord + Clone;\n\n    private_decl! {}\n}\n\nimpl<A> Sort1dExt<A> for ArrayRef<A, Ix1> {\n    fn get_from_sorted_mut(&mut self, i: usize) -> A\n    where\n        A: Ord + Clone,\n    {\n        let n = self.len();\n        if n == 1 {\n            self[0].clone()\n        } else {\n            let mut rng = thread_rng();\n            let pivot_index = rng.gen_range(0..n);\n            let partition_index = self.partition_mut(pivot_index);\n            if i < partition_index {\n                self.slice_axis_mut(Axis(0), Slice::from(..partition_index))\n                    .get_from_sorted_mut(i)\n            } else if i == partition_index {\n                self[i].clone()\n            } else {\n                self.slice_axis_mut(Axis(0), Slice::from(partition_index + 1..))\n                    .get_from_sorted_mut(i - (partition_index + 1))\n            }\n        }\n    }\n\n    fn get_many_from_sorted_mut(&mut self, indexes: &ArrayRef1<usize>) -> IndexMap<usize, A>\n    where\n        A: Ord + Clone,\n    {\n        let mut deduped_indexes: Vec<usize> = indexes.to_vec();\n        deduped_indexes.sort_unstable();\n        deduped_indexes.dedup();\n\n        get_many_from_sorted_mut_unchecked(self, &deduped_indexes)\n    }\n\n    fn partition_mut(&mut self, pivot_index: usize) -> usize\n    where\n        A: Ord + Clone,\n    {\n        let pivot_value = self[pivot_index].clone();\n        self.swap(pivot_index, 0);\n        let n = self.len();\n        let mut i = 1;\n        let mut j = n - 1;\n        loop {\n            loop {\n                if i > j {\n                    break;\n                }\n                if self[i] >= pivot_value {\n                    break;\n                }\n                i += 1;\n            }\n            while pivot_value <= self[j] {\n                if j == 1 {\n                    break;\n                }\n                j -= 1;\n            }\n            if i >= j {\n                break;\n            } else {\n                self.swap(i, j);\n                i += 1;\n                j -= 1;\n            }\n        }\n        self.swap(0, i - 1);\n        i - 1\n    }\n\n    private_impl! {}\n}\n\n/// To retrieve multiple indexes from the sorted array in an optimized fashion,\n/// [get_many_from_sorted_mut] first of all sorts and deduplicates the\n/// `indexes` vector.\n///\n/// `get_many_from_sorted_mut_unchecked` does not perform this sorting and\n/// deduplication, assuming that the user has already taken care of it.\n///\n/// Useful when you have to call [get_many_from_sorted_mut] multiple times\n/// using the same indexes.\n///\n/// [get_many_from_sorted_mut]: ../trait.Sort1dExt.html#tymethod.get_many_from_sorted_mut\npub(crate) fn get_many_from_sorted_mut_unchecked<A>(\n    array: &mut ArrayRef1<A>,\n    indexes: &[usize],\n) -> IndexMap<usize, A>\nwhere\n    A: Ord + Clone,\n{\n    if indexes.is_empty() {\n        return IndexMap::new();\n    }\n\n    // Since `!indexes.is_empty()` and indexes must be in-bounds, `array` must\n    // be non-empty.\n    let mut values = vec![array[0].clone(); indexes.len()];\n    _get_many_from_sorted_mut_unchecked(array.view_mut(), &mut indexes.to_owned(), &mut values);\n\n    // We convert the vector to a more search-friendly `IndexMap`.\n    indexes.iter().cloned().zip(values.into_iter()).collect()\n}\n\n/// This is the recursive portion of `get_many_from_sorted_mut_unchecked`.\n///\n/// `indexes` is the list of indexes to get. `indexes` is mutable so that it\n/// can be used as scratch space for this routine; the value of `indexes` after\n/// calling this routine should be ignored.\n///\n/// `values` is a pre-allocated slice to use for writing the output. Its\n/// initial element values are ignored.\nfn _get_many_from_sorted_mut_unchecked<A>(\n    mut array: ArrayViewMut1<'_, A>,\n    indexes: &mut [usize],\n    values: &mut [A],\n) where\n    A: Ord + Clone,\n{\n    let n = array.len();\n    debug_assert!(n >= indexes.len()); // because indexes must be unique and in-bounds\n    debug_assert_eq!(indexes.len(), values.len());\n\n    if indexes.is_empty() {\n        // Nothing to do in this case.\n        return;\n    }\n\n    // At this point, `n >= 1` since `indexes.len() >= 1`.\n    if n == 1 {\n        // We can only reach this point if `indexes.len() == 1`, so we only\n        // need to assign the single value, and then we're done.\n        debug_assert_eq!(indexes.len(), 1);\n        values[0] = array[0].clone();\n        return;\n    }\n\n    // We pick a random pivot index: the corresponding element is the pivot value\n    let mut rng = thread_rng();\n    let pivot_index = rng.gen_range(0..n);\n\n    // We partition the array with respect to the pivot value.\n    // The pivot value moves to `array_partition_index`.\n    // Elements strictly smaller than the pivot value have indexes < `array_partition_index`.\n    // Elements greater or equal to the pivot value have indexes > `array_partition_index`.\n    let array_partition_index = array.partition_mut(pivot_index);\n\n    // We use a divide-and-conquer strategy, splitting the indexes we are\n    // searching for (`indexes`) and the corresponding portions of the output\n    // slice (`values`) into pieces with respect to `array_partition_index`.\n    let (found_exact, index_split) = match indexes.binary_search(&array_partition_index) {\n        Ok(index) => (true, index),\n        Err(index) => (false, index),\n    };\n    let (smaller_indexes, other_indexes) = indexes.split_at_mut(index_split);\n    let (smaller_values, other_values) = values.split_at_mut(index_split);\n    let (bigger_indexes, bigger_values) = if found_exact {\n        other_values[0] = array[array_partition_index].clone(); // Write exactly found value.\n        (&mut other_indexes[1..], &mut other_values[1..])\n    } else {\n        (other_indexes, other_values)\n    };\n\n    // We search recursively for the values corresponding to strictly smaller\n    // indexes to the left of `partition_index`.\n    _get_many_from_sorted_mut_unchecked(\n        array.slice_axis_mut(Axis(0), Slice::from(..array_partition_index)),\n        smaller_indexes,\n        smaller_values,\n    );\n\n    // We search recursively for the values corresponding to strictly bigger\n    // indexes to the right of `partition_index`. Since only the right portion\n    // of the array is passed in, the indexes need to be shifted by length of\n    // the removed portion.\n    bigger_indexes\n        .iter_mut()\n        .for_each(|x| *x -= array_partition_index + 1);\n    _get_many_from_sorted_mut_unchecked(\n        array.slice_axis_mut(Axis(0), Slice::from(array_partition_index + 1..)),\n        bigger_indexes,\n        bigger_values,\n    );\n}\n"
  },
  {
    "path": "src/summary_statistics/means.rs",
    "content": "use super::SummaryStatisticsExt;\nuse crate::errors::{EmptyInput, MultiInputError, ShapeMismatch};\nuse ndarray::{Array, ArrayBase, ArrayRef, Axis, Data, Dimension, Ix1, RemoveAxis};\nuse num_integer::IterBinomial;\nuse num_traits::{Float, FromPrimitive, Zero};\nuse std::ops::{Add, AddAssign, Div, Mul};\n\nimpl<A, D> SummaryStatisticsExt<A, D> for ArrayRef<A, D>\nwhere\n    D: Dimension,\n{\n    fn mean(&self) -> Result<A, EmptyInput>\n    where\n        A: Clone + FromPrimitive + Add<Output = A> + Div<Output = A> + Zero,\n    {\n        let n_elements = self.len();\n        if n_elements == 0 {\n            Err(EmptyInput)\n        } else {\n            let n_elements = A::from_usize(n_elements)\n                .expect(\"Converting number of elements to `A` must not fail.\");\n            Ok(self.sum() / n_elements)\n        }\n    }\n\n    fn weighted_mean(&self, weights: &Self) -> Result<A, MultiInputError>\n    where\n        A: Copy + Div<Output = A> + Mul<Output = A> + Zero,\n    {\n        return_err_if_empty!(self);\n        let weighted_sum = self.weighted_sum(weights)?;\n        Ok(weighted_sum / weights.sum())\n    }\n\n    fn weighted_sum(&self, weights: &ArrayRef<A, D>) -> Result<A, MultiInputError>\n    where\n        A: Copy + Mul<Output = A> + Zero,\n    {\n        return_err_unless_same_shape!(self, weights);\n        Ok(self\n            .iter()\n            .zip(weights)\n            .fold(A::zero(), |acc, (&d, &w)| acc + d * w))\n    }\n\n    fn weighted_mean_axis(\n        &self,\n        axis: Axis,\n        weights: &ArrayRef<A, Ix1>,\n    ) -> Result<Array<A, D::Smaller>, MultiInputError>\n    where\n        A: Copy + Div<Output = A> + Mul<Output = A> + Zero,\n        D: RemoveAxis,\n    {\n        return_err_if_empty!(self);\n        let mut weighted_sum = self.weighted_sum_axis(axis, weights)?;\n        let weights_sum = weights.sum();\n        weighted_sum.mapv_inplace(|v| v / weights_sum);\n        Ok(weighted_sum)\n    }\n\n    fn weighted_sum_axis(\n        &self,\n        axis: Axis,\n        weights: &ArrayRef<A, Ix1>,\n    ) -> Result<Array<A, D::Smaller>, MultiInputError>\n    where\n        A: Copy + Mul<Output = A> + Zero,\n        D: RemoveAxis,\n    {\n        if self.shape()[axis.index()] != weights.len() {\n            return Err(MultiInputError::ShapeMismatch(ShapeMismatch {\n                first_shape: self.shape().to_vec(),\n                second_shape: weights.shape().to_vec(),\n            }));\n        }\n\n        // We could use `lane.weighted_sum` here, but we're avoiding 2\n        // conditions and an unwrap per lane.\n        Ok(self.map_axis(axis, |lane| {\n            lane.iter()\n                .zip(weights)\n                .fold(A::zero(), |acc, (&d, &w)| acc + d * w)\n        }))\n    }\n\n    fn harmonic_mean(&self) -> Result<A, EmptyInput>\n    where\n        A: Float + FromPrimitive,\n    {\n        self.map(|x| x.recip())\n            .mean()\n            .map(|x| x.recip())\n            .ok_or(EmptyInput)\n    }\n\n    fn geometric_mean(&self) -> Result<A, EmptyInput>\n    where\n        A: Float + FromPrimitive,\n    {\n        self.map(|x| x.ln())\n            .mean()\n            .map(|x| x.exp())\n            .ok_or(EmptyInput)\n    }\n\n    fn weighted_var(&self, weights: &Self, ddof: A) -> Result<A, MultiInputError>\n    where\n        A: AddAssign + Float + FromPrimitive,\n    {\n        return_err_if_empty!(self);\n        return_err_unless_same_shape!(self, weights);\n        let zero = A::from_usize(0).expect(\"Converting 0 to `A` must not fail.\");\n        let one = A::from_usize(1).expect(\"Converting 1 to `A` must not fail.\");\n        assert!(\n            !(ddof < zero || ddof > one),\n            \"`ddof` must not be less than zero or greater than one\",\n        );\n        inner_weighted_var(self, weights, ddof, zero)\n    }\n\n    fn weighted_std(&self, weights: &Self, ddof: A) -> Result<A, MultiInputError>\n    where\n        A: AddAssign + Float + FromPrimitive,\n    {\n        Ok(self.weighted_var(weights, ddof)?.sqrt())\n    }\n\n    fn weighted_var_axis(\n        &self,\n        axis: Axis,\n        weights: &ArrayRef<A, Ix1>,\n        ddof: A,\n    ) -> Result<Array<A, D::Smaller>, MultiInputError>\n    where\n        A: AddAssign + Float + FromPrimitive,\n        D: RemoveAxis,\n    {\n        return_err_if_empty!(self);\n        if self.shape()[axis.index()] != weights.len() {\n            return Err(MultiInputError::ShapeMismatch(ShapeMismatch {\n                first_shape: self.shape().to_vec(),\n                second_shape: weights.shape().to_vec(),\n            }));\n        }\n        let zero = A::from_usize(0).expect(\"Converting 0 to `A` must not fail.\");\n        let one = A::from_usize(1).expect(\"Converting 1 to `A` must not fail.\");\n        assert!(\n            !(ddof < zero || ddof > one),\n            \"`ddof` must not be less than zero or greater than one\",\n        );\n\n        // `weights` must be a view because `lane` is a view in this context.\n        let weights = weights.view();\n        Ok(self.map_axis(axis, |lane| {\n            inner_weighted_var(&lane, &weights, ddof, zero).unwrap()\n        }))\n    }\n\n    fn weighted_std_axis(\n        &self,\n        axis: Axis,\n        weights: &ArrayRef<A, Ix1>,\n        ddof: A,\n    ) -> Result<Array<A, D::Smaller>, MultiInputError>\n    where\n        A: AddAssign + Float + FromPrimitive,\n        D: RemoveAxis,\n    {\n        Ok(self\n            .weighted_var_axis(axis, weights, ddof)?\n            .mapv_into(|x| x.sqrt()))\n    }\n\n    fn kurtosis(&self) -> Result<A, EmptyInput>\n    where\n        A: Float + FromPrimitive,\n    {\n        let central_moments = self.central_moments(4)?;\n        Ok(central_moments[4] / central_moments[2].powi(2))\n    }\n\n    fn skewness(&self) -> Result<A, EmptyInput>\n    where\n        A: Float + FromPrimitive,\n    {\n        let central_moments = self.central_moments(3)?;\n        Ok(central_moments[3] / central_moments[2].sqrt().powi(3))\n    }\n\n    fn central_moment(&self, order: u16) -> Result<A, EmptyInput>\n    where\n        A: Float + FromPrimitive,\n    {\n        if self.is_empty() {\n            return Err(EmptyInput);\n        }\n        match order {\n            0 => Ok(A::one()),\n            1 => Ok(A::zero()),\n            n => {\n                let mean = self.mean().unwrap();\n                let shifted_array = self.mapv(|x| x - mean);\n                let shifted_moments = moments(shifted_array, n);\n                let correction_term = -shifted_moments[1];\n\n                let coefficients = central_moment_coefficients(&shifted_moments);\n                Ok(horner_method(coefficients, correction_term))\n            }\n        }\n    }\n\n    fn central_moments(&self, order: u16) -> Result<Vec<A>, EmptyInput>\n    where\n        A: Float + FromPrimitive,\n    {\n        if self.is_empty() {\n            return Err(EmptyInput);\n        }\n        match order {\n            0 => Ok(vec![A::one()]),\n            1 => Ok(vec![A::one(), A::zero()]),\n            n => {\n                // We only perform these operations once, and then reuse their\n                // result to compute all the required moments\n                let mean = self.mean().unwrap();\n                let shifted_array = self.mapv(|x| x - mean);\n                let shifted_moments = moments(shifted_array, n);\n                let correction_term = -shifted_moments[1];\n\n                let mut central_moments = vec![A::one(), A::zero()];\n                for k in 2..=n {\n                    let coefficients =\n                        central_moment_coefficients(&shifted_moments[..=(k as usize)]);\n                    let central_moment = horner_method(coefficients, correction_term);\n                    central_moments.push(central_moment)\n                }\n                Ok(central_moments)\n            }\n        }\n    }\n\n    private_impl! {}\n}\n\n/// Private function for `weighted_var` without conditions and asserts.\nfn inner_weighted_var<A, D>(\n    arr: &ArrayRef<A, D>,\n    weights: &ArrayRef<A, D>,\n    ddof: A,\n    zero: A,\n) -> Result<A, MultiInputError>\nwhere\n    A: AddAssign + Float + FromPrimitive,\n    D: Dimension,\n{\n    let mut weight_sum = zero;\n    let mut mean = zero;\n    let mut s = zero;\n    for (&x, &w) in arr.iter().zip(weights.iter()) {\n        weight_sum += w;\n        let x_minus_mean = x - mean;\n        mean += (w / weight_sum) * x_minus_mean;\n        s += w * x_minus_mean * (x - mean);\n    }\n    Ok(s / (weight_sum - ddof))\n}\n\n/// Returns a vector containing all moments of the array elements up to\n/// *order*, where the *p*-th moment is defined as:\n///\n/// ```text\n/// 1  n\n/// ―  ∑ xᵢᵖ\n/// n i=1\n/// ```\n///\n/// The returned moments are ordered by power magnitude: 0th moment, 1st moment, etc.\n///\n/// **Panics** if `A::from_usize()` fails to convert the number of elements in the array.\nfn moments<A, S, D>(a: ArrayBase<S, D>, order: u16) -> Vec<A>\nwhere\n    A: Float + FromPrimitive,\n    S: Data<Elem = A>,\n    D: Dimension,\n{\n    let n_elements =\n        A::from_usize(a.len()).expect(\"Converting number of elements to `A` must not fail\");\n    let order = i32::from(order);\n\n    // When k=0, we are raising each element to the 0th power\n    // No need to waste CPU cycles going through the array\n    let mut moments = vec![A::one()];\n\n    if order >= 1 {\n        // When k=1, we don't need to raise elements to the 1th power (identity)\n        moments.push(a.sum() / n_elements)\n    }\n\n    for k in 2..=order {\n        moments.push(a.map(|x| x.powi(k)).sum() / n_elements)\n    }\n    moments\n}\n\n/// Returns the coefficients in the polynomial expression to compute the *p*th\n/// central moment as a function of the sample mean.\n///\n/// It takes as input all moments up to order *p*, ordered by power magnitude - *p* is\n/// inferred to be the length of the *moments* array.\nfn central_moment_coefficients<A>(moments: &[A]) -> Vec<A>\nwhere\n    A: Float + FromPrimitive,\n{\n    let order = moments.len();\n    IterBinomial::new(order)\n        .zip(moments.iter().rev())\n        .map(|(binom, &moment)| A::from_usize(binom).unwrap() * moment)\n        .collect()\n}\n\n/// Uses [Horner's method] to evaluate a polynomial with a single indeterminate.\n///\n/// Coefficients are expected to be sorted by ascending order\n/// with respect to the indeterminate's exponent.\n///\n/// If the array is empty, `A::zero()` is returned.\n///\n/// Horner's method can evaluate a polynomial of order *n* with a single indeterminate\n/// using only *n-1* multiplications and *n-1* sums - in terms of number of operations,\n/// this is an optimal algorithm for polynomial evaluation.\n///\n/// [Horner's method]: https://en.wikipedia.org/wiki/Horner%27s_method\nfn horner_method<A>(coefficients: Vec<A>, indeterminate: A) -> A\nwhere\n    A: Float,\n{\n    let mut result = A::zero();\n    for coefficient in coefficients.into_iter().rev() {\n        result = coefficient + indeterminate * result\n    }\n    result\n}\n"
  },
  {
    "path": "src/summary_statistics/mod.rs",
    "content": "//! Summary statistics (e.g. mean, variance, etc.).\nuse crate::errors::{EmptyInput, MultiInputError};\nuse ndarray::{Array, ArrayRef, Axis, Dimension, Ix1, RemoveAxis};\nuse num_traits::{Float, FromPrimitive, Zero};\nuse std::ops::{Add, AddAssign, Div, Mul};\n\n/// Extension trait for `ArrayRef` providing methods\n/// to compute several summary statistics (e.g. mean, variance, etc.).\npub trait SummaryStatisticsExt<A, D>\nwhere\n    D: Dimension,\n{\n    /// Returns the [`arithmetic mean`] x̅ of all elements in the array:\n    ///\n    /// ```text\n    ///     1   n\n    /// x̅ = ―   ∑ xᵢ\n    ///     n  i=1\n    /// ```\n    ///\n    /// If the array is empty, `Err(EmptyInput)` is returned.\n    ///\n    /// **Panics** if `A::from_usize()` fails to convert the number of elements in the array.\n    ///\n    /// [`arithmetic mean`]: https://en.wikipedia.org/wiki/Arithmetic_mean\n    fn mean(&self) -> Result<A, EmptyInput>\n    where\n        A: Clone + FromPrimitive + Add<Output = A> + Div<Output = A> + Zero;\n\n    /// Returns the [`arithmetic weighted mean`] x̅ of all elements in the array. Use `weighted_sum`\n    /// if the `weights` are normalized (they sum up to 1.0).\n    ///\n    /// ```text\n    ///       n\n    ///       ∑ wᵢxᵢ\n    ///      i=1\n    /// x̅ = ―――――――――\n    ///        n\n    ///        ∑ wᵢ\n    ///       i=1\n    /// ```\n    ///\n    /// **Panics** if division by zero panics for type A.\n    ///\n    /// The following **errors** may be returned:\n    ///\n    /// * `MultiInputError::EmptyInput` if `self` is empty\n    /// * `MultiInputError::ShapeMismatch` if `self` and `weights` don't have the same shape\n    ///\n    /// [`arithmetic weighted mean`] https://en.wikipedia.org/wiki/Weighted_arithmetic_mean\n    fn weighted_mean(&self, weights: &Self) -> Result<A, MultiInputError>\n    where\n        A: Copy + Div<Output = A> + Mul<Output = A> + Zero;\n\n    /// Returns the weighted sum of all elements in the array, that is, the dot product of the\n    /// arrays `self` and `weights`. Equivalent to `weighted_mean` if the `weights` are normalized.\n    ///\n    /// ```text\n    ///      n\n    /// x̅ =  ∑ wᵢxᵢ\n    ///     i=1\n    /// ```\n    ///\n    /// The following **errors** may be returned:\n    ///\n    /// * `MultiInputError::ShapeMismatch` if `self` and `weights` don't have the same shape\n    fn weighted_sum(&self, weights: &Self) -> Result<A, MultiInputError>\n    where\n        A: Copy + Mul<Output = A> + Zero;\n\n    /// Returns the [`arithmetic weighted mean`] x̅ along `axis`. Use `weighted_mean_axis ` if the\n    /// `weights` are normalized.\n    ///\n    /// ```text\n    ///       n\n    ///       ∑ wᵢxᵢ\n    ///      i=1\n    /// x̅ = ―――――――――\n    ///        n\n    ///        ∑ wᵢ\n    ///       i=1\n    /// ```\n    ///\n    /// **Panics** if `axis` is out of bounds.\n    ///\n    /// The following **errors** may be returned:\n    ///\n    /// * `MultiInputError::EmptyInput` if `self` is empty\n    /// * `MultiInputError::ShapeMismatch` if `self` length along axis is not equal to `weights` length\n    ///\n    /// [`arithmetic weighted mean`] https://en.wikipedia.org/wiki/Weighted_arithmetic_mean\n    fn weighted_mean_axis(\n        &self,\n        axis: Axis,\n        weights: &ArrayRef<A, Ix1>,\n    ) -> Result<Array<A, D::Smaller>, MultiInputError>\n    where\n        A: Copy + Div<Output = A> + Mul<Output = A> + Zero,\n        D: RemoveAxis;\n\n    /// Returns the weighted sum along `axis`, that is, the dot product of `weights` and each lane\n    /// of `self` along `axis`. Equivalent to `weighted_mean_axis` if the `weights` are normalized.\n    ///\n    /// ```text\n    ///      n\n    /// x̅ =  ∑ wᵢxᵢ\n    ///     i=1\n    /// ```\n    ///\n    /// **Panics** if `axis` is out of bounds.\n    ///\n    /// The following **errors** may be returned\n    ///\n    /// * `MultiInputError::ShapeMismatch` if `self` and `weights` don't have the same shape\n    fn weighted_sum_axis(\n        &self,\n        axis: Axis,\n        weights: &ArrayRef<A, Ix1>,\n    ) -> Result<Array<A, D::Smaller>, MultiInputError>\n    where\n        A: Copy + Mul<Output = A> + Zero,\n        D: RemoveAxis;\n\n    /// Returns the [`harmonic mean`] `HM(X)` of all elements in the array:\n    ///\n    /// ```text\n    ///           ⎛ n     ⎞⁻¹\n    /// HM(X) = n ⎜ ∑ xᵢ⁻¹⎟\n    ///           ⎝i=1    ⎠\n    /// ```\n    ///\n    /// If the array is empty, `Err(EmptyInput)` is returned.\n    ///\n    /// **Panics** if `A::from_usize()` fails to convert the number of elements in the array.\n    ///\n    /// [`harmonic mean`]: https://en.wikipedia.org/wiki/Harmonic_mean\n    fn harmonic_mean(&self) -> Result<A, EmptyInput>\n    where\n        A: Float + FromPrimitive;\n\n    /// Returns the [`geometric mean`] `GM(X)` of all elements in the array:\n    ///\n    /// ```text\n    ///         ⎛ n   ⎞¹⁄ₙ\n    /// GM(X) = ⎜ ∏ xᵢ⎟\n    ///         ⎝i=1  ⎠\n    /// ```\n    ///\n    /// If the array is empty, `Err(EmptyInput)` is returned.\n    ///\n    /// **Panics** if `A::from_usize()` fails to convert the number of elements in the array.\n    ///\n    /// [`geometric mean`]: https://en.wikipedia.org/wiki/Geometric_mean\n    fn geometric_mean(&self) -> Result<A, EmptyInput>\n    where\n        A: Float + FromPrimitive;\n\n    /// Return weighted variance of all elements in the array.\n    ///\n    /// The weighted variance is computed using the [`West, D. H. D.`] incremental algorithm.\n    /// Equivalent to `var_axis` if the `weights` are normalized.\n    ///\n    /// The parameter `ddof` specifies the \"delta degrees of freedom\". For example, to calculate the\n    /// population variance, use `ddof = 0`, or to calculate the sample variance, use `ddof = 1`.\n    ///\n    /// **Panics** if `ddof` is less than zero or greater than one, or if `axis` is out of bounds,\n    /// or if `A::from_usize()` fails for zero or one.\n    ///\n    /// [`West, D. H. D.`]: https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Weighted_incremental_algorithm\n    fn weighted_var(&self, weights: &Self, ddof: A) -> Result<A, MultiInputError>\n    where\n        A: AddAssign + Float + FromPrimitive;\n\n    /// Return weighted standard deviation of all elements in the array.\n    ///\n    /// The weighted standard deviation is computed using the [`West, D. H. D.`] incremental\n    /// algorithm. Equivalent to `var_axis` if the `weights` are normalized.\n    ///\n    /// The parameter `ddof` specifies the \"delta degrees of freedom\". For example, to calculate the\n    /// population variance, use `ddof = 0`, or to calculate the sample variance, use `ddof = 1`.\n    ///\n    /// **Panics** if `ddof` is less than zero or greater than one, or if `axis` is out of bounds,\n    /// or if `A::from_usize()` fails for zero or one.\n    ///\n    /// [`West, D. H. D.`]: https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Weighted_incremental_algorithm\n    fn weighted_std(&self, weights: &Self, ddof: A) -> Result<A, MultiInputError>\n    where\n        A: AddAssign + Float + FromPrimitive;\n\n    /// Return weighted variance along `axis`.\n    ///\n    /// The weighted variance is computed using the [`West, D. H. D.`] incremental algorithm.\n    /// Equivalent to `var_axis` if the `weights` are normalized.\n    ///\n    /// The parameter `ddof` specifies the \"delta degrees of freedom\". For example, to calculate the\n    /// population variance, use `ddof = 0`, or to calculate the sample variance, use `ddof = 1`.\n    ///\n    /// **Panics** if `ddof` is less than zero or greater than one, or if `axis` is out of bounds,\n    /// or if `A::from_usize()` fails for zero or one.\n    ///\n    /// [`West, D. H. D.`]: https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Weighted_incremental_algorithm\n    fn weighted_var_axis(\n        &self,\n        axis: Axis,\n        weights: &ArrayRef<A, Ix1>,\n        ddof: A,\n    ) -> Result<Array<A, D::Smaller>, MultiInputError>\n    where\n        A: AddAssign + Float + FromPrimitive,\n        D: RemoveAxis;\n\n    /// Return weighted standard deviation along `axis`.\n    ///\n    /// The weighted standard deviation is computed using the [`West, D. H. D.`] incremental\n    /// algorithm. Equivalent to `var_axis` if the `weights` are normalized.\n    ///\n    /// The parameter `ddof` specifies the \"delta degrees of freedom\". For example, to calculate the\n    /// population variance, use `ddof = 0`, or to calculate the sample variance, use `ddof = 1`.\n    ///\n    /// **Panics** if `ddof` is less than zero or greater than one, or if `axis` is out of bounds,\n    /// or if `A::from_usize()` fails for zero or one.\n    ///\n    /// [`West, D. H. D.`]: https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Weighted_incremental_algorithm\n    fn weighted_std_axis(\n        &self,\n        axis: Axis,\n        weights: &ArrayRef<A, Ix1>,\n        ddof: A,\n    ) -> Result<Array<A, D::Smaller>, MultiInputError>\n    where\n        A: AddAssign + Float + FromPrimitive,\n        D: RemoveAxis;\n\n    /// Returns the [kurtosis] `Kurt[X]` of all elements in the array:\n    ///\n    /// ```text\n    /// Kurt[X] = μ₄ / σ⁴\n    /// ```\n    ///\n    /// where μ₄ is the fourth central moment and σ is the standard deviation of\n    /// the elements in the array.\n    ///\n    /// This is sometimes referred to as _Pearson's kurtosis_. Fisher's kurtosis can be\n    /// computed by subtracting 3 from Pearson's kurtosis.\n    ///\n    /// If the array is empty, `Err(EmptyInput)` is returned.\n    ///\n    /// **Panics** if `A::from_usize()` fails to convert the number of elements in the array.\n    ///\n    /// [kurtosis]: https://en.wikipedia.org/wiki/Kurtosis\n    fn kurtosis(&self) -> Result<A, EmptyInput>\n    where\n        A: Float + FromPrimitive;\n\n    /// Returns the [Pearson's moment coefficient of skewness] γ₁ of all elements in the array:\n    ///\n    /// ```text\n    /// γ₁ = μ₃ / σ³\n    /// ```\n    ///\n    /// where μ₃ is the third central moment and σ is the standard deviation of\n    /// the elements in the array.\n    ///\n    /// If the array is empty, `Err(EmptyInput)` is returned.\n    ///\n    /// **Panics** if `A::from_usize()` fails to convert the number of elements in the array.\n    ///\n    /// [Pearson's moment coefficient of skewness]: https://en.wikipedia.org/wiki/Skewness\n    fn skewness(&self) -> Result<A, EmptyInput>\n    where\n        A: Float + FromPrimitive;\n\n    /// Returns the *p*-th [central moment] of all elements in the array, μₚ:\n    ///\n    /// ```text\n    ///      1  n\n    /// μₚ = ―  ∑ (xᵢ-x̅)ᵖ\n    ///      n i=1\n    /// ```\n    ///\n    /// If the array is empty, `Err(EmptyInput)` is returned.\n    ///\n    /// The *p*-th central moment is computed using a corrected two-pass algorithm (see Section 3.5\n    /// in [Pébay et al., 2016]). Complexity is *O(np)* when *n >> p*, *p > 1*.\n    ///\n    /// **Panics** if `A::from_usize()` fails to convert the number of elements\n    /// in the array or if `order` overflows `i32`.\n    ///\n    /// [central moment]: https://en.wikipedia.org/wiki/Central_moment\n    /// [Pébay et al., 2016]: https://www.osti.gov/pages/servlets/purl/1427275\n    fn central_moment(&self, order: u16) -> Result<A, EmptyInput>\n    where\n        A: Float + FromPrimitive;\n\n    /// Returns the first *p* [central moments] of all elements in the array, see [central moment]\n    /// for more details.\n    ///\n    /// If the array is empty, `Err(EmptyInput)` is returned.\n    ///\n    /// This method reuses the intermediate steps for the *k*-th moment to compute the *(k+1)*-th,\n    /// being thus more efficient than repeated calls to [central moment] if the computation\n    /// of central moments of multiple orders is required.\n    ///\n    /// **Panics** if `A::from_usize()` fails to convert the number of elements\n    /// in the array or if `order` overflows `i32`.\n    ///\n    /// [central moments]: https://en.wikipedia.org/wiki/Central_moment\n    /// [central moment]: #tymethod.central_moment\n    fn central_moments(&self, order: u16) -> Result<Vec<A>, EmptyInput>\n    where\n        A: Float + FromPrimitive;\n\n    private_decl! {}\n}\n\nmod means;\n"
  },
  {
    "path": "tests/deviation.rs",
    "content": "use ndarray_stats::errors::{MultiInputError, ShapeMismatch};\nuse ndarray_stats::DeviationExt;\n\nuse approx::assert_abs_diff_eq;\nuse ndarray::{array, Array1};\nuse num_bigint::BigInt;\nuse num_traits::Float;\n\nuse std::f64;\n\n#[test]\nfn test_count_eq() -> Result<(), MultiInputError> {\n    let a = array![0., 0.];\n    let b = array![1., 0.];\n    let c = array![0., 1.];\n    let d = array![1., 1.];\n\n    assert_eq!(a.count_eq(&a)?, 2);\n    assert_eq!(a.count_eq(&b)?, 1);\n    assert_eq!(a.count_eq(&c)?, 1);\n    assert_eq!(a.count_eq(&d)?, 0);\n\n    Ok(())\n}\n\n#[test]\nfn test_count_neq() -> Result<(), MultiInputError> {\n    let a = array![0., 0.];\n    let b = array![1., 0.];\n    let c = array![0., 1.];\n    let d = array![1., 1.];\n\n    assert_eq!(a.count_neq(&a)?, 0);\n    assert_eq!(a.count_neq(&b)?, 1);\n    assert_eq!(a.count_neq(&c)?, 1);\n    assert_eq!(a.count_neq(&d)?, 2);\n\n    Ok(())\n}\n\n#[test]\nfn test_sq_l2_dist() -> Result<(), MultiInputError> {\n    let a = array![0., 1., 4., 2.];\n    let b = array![1., 1., 2., 4.];\n\n    assert_eq!(a.sq_l2_dist(&b)?, 9.);\n\n    Ok(())\n}\n\n#[test]\nfn test_l2_dist() -> Result<(), MultiInputError> {\n    let a = array![0., 1., 4., 2.];\n    let b = array![1., 1., 2., 4.];\n\n    assert_eq!(a.l2_dist(&b)?, 3.);\n\n    Ok(())\n}\n\n#[test]\nfn test_l1_dist() -> Result<(), MultiInputError> {\n    let a = array![0., 1., 4., 2.];\n    let b = array![1., 1., 2., 4.];\n\n    assert_eq!(a.l1_dist(&b)?, 5.);\n\n    Ok(())\n}\n\n#[test]\nfn test_linf_dist() -> Result<(), MultiInputError> {\n    let a = array![0., 0.];\n    let b = array![1., 0.];\n    let c = array![1., 2.];\n\n    assert_eq!(a.linf_dist(&a)?, 0.);\n\n    assert_eq!(a.linf_dist(&b)?, 1.);\n    assert_eq!(b.linf_dist(&a)?, 1.);\n\n    assert_eq!(a.linf_dist(&c)?, 2.);\n    assert_eq!(c.linf_dist(&a)?, 2.);\n\n    Ok(())\n}\n\n#[test]\nfn test_mean_abs_err() -> Result<(), MultiInputError> {\n    let a = array![1., 1.];\n    let b = array![3., 5.];\n\n    assert_eq!(a.mean_abs_err(&a)?, 0.);\n    assert_eq!(a.mean_abs_err(&b)?, 3.);\n    assert_eq!(b.mean_abs_err(&a)?, 3.);\n\n    Ok(())\n}\n\n#[test]\nfn test_mean_sq_err() -> Result<(), MultiInputError> {\n    let a = array![1., 1.];\n    let b = array![3., 5.];\n\n    assert_eq!(a.mean_sq_err(&a)?, 0.);\n    assert_eq!(a.mean_sq_err(&b)?, 10.);\n    assert_eq!(b.mean_sq_err(&a)?, 10.);\n\n    Ok(())\n}\n\n#[test]\nfn test_root_mean_sq_err() -> Result<(), MultiInputError> {\n    let a = array![1., 1.];\n    let b = array![3., 5.];\n\n    assert_eq!(a.root_mean_sq_err(&a)?, 0.);\n    assert_abs_diff_eq!(a.root_mean_sq_err(&b)?, 10.0.sqrt());\n    assert_abs_diff_eq!(b.root_mean_sq_err(&a)?, 10.0.sqrt());\n\n    Ok(())\n}\n\n#[test]\nfn test_peak_signal_to_noise_ratio() -> Result<(), MultiInputError> {\n    let a = array![1., 1.];\n    assert!(a.peak_signal_to_noise_ratio(&a, 1.)?.is_infinite());\n\n    let a = array![1., 2., 3., 4., 5., 6., 7.];\n    let b = array![1., 3., 3., 4., 6., 7., 8.];\n    let maxv = 8.;\n    let expected = 20. * Float::log10(maxv) - 10. * Float::log10(a.mean_sq_err(&b)?);\n    let actual = a.peak_signal_to_noise_ratio(&b, maxv)?;\n\n    assert_abs_diff_eq!(actual, expected);\n\n    Ok(())\n}\n\n#[test]\nfn test_deviations_with_n_by_m_ints() -> Result<(), MultiInputError> {\n    let a = array![[0, 1], [4, 2]];\n    let b = array![[1, 1], [2, 4]];\n\n    assert_eq!(a.count_eq(&a)?, 4);\n    assert_eq!(a.count_neq(&a)?, 0);\n\n    assert_eq!(a.sq_l2_dist(&b)?, 9);\n    assert_eq!(a.l2_dist(&b)?, 3.);\n    assert_eq!(a.l1_dist(&b)?, 5);\n    assert_eq!(a.linf_dist(&b)?, 2);\n\n    assert_abs_diff_eq!(a.mean_abs_err(&b)?, 1.25);\n    assert_abs_diff_eq!(a.mean_sq_err(&b)?, 2.25);\n    assert_abs_diff_eq!(a.root_mean_sq_err(&b)?, 1.5);\n    assert_abs_diff_eq!(a.peak_signal_to_noise_ratio(&b, 4)?, 8.519374645445623);\n\n    Ok(())\n}\n\n#[test]\nfn test_deviations_with_empty_receiver() {\n    let a: Array1<f64> = array![];\n    let b: Array1<f64> = array![1.];\n\n    assert_eq!(a.count_eq(&b), Err(MultiInputError::EmptyInput));\n    assert_eq!(a.count_neq(&b), Err(MultiInputError::EmptyInput));\n\n    assert_eq!(a.sq_l2_dist(&b), Err(MultiInputError::EmptyInput));\n    assert_eq!(a.l2_dist(&b), Err(MultiInputError::EmptyInput));\n    assert_eq!(a.l1_dist(&b), Err(MultiInputError::EmptyInput));\n    assert_eq!(a.linf_dist(&b), Err(MultiInputError::EmptyInput));\n\n    assert_eq!(a.mean_abs_err(&b), Err(MultiInputError::EmptyInput));\n    assert_eq!(a.mean_sq_err(&b), Err(MultiInputError::EmptyInput));\n    assert_eq!(a.root_mean_sq_err(&b), Err(MultiInputError::EmptyInput));\n    assert_eq!(\n        a.peak_signal_to_noise_ratio(&b, 0.),\n        Err(MultiInputError::EmptyInput)\n    );\n}\n\n#[test]\nfn test_deviations_do_not_panic_if_nans() -> Result<(), MultiInputError> {\n    let a: Array1<f64> = array![1., f64::NAN, 3., f64::NAN];\n    let b: Array1<f64> = array![1., f64::NAN, 3., 4.];\n\n    assert_eq!(a.count_eq(&b)?, 2);\n    assert_eq!(a.count_neq(&b)?, 2);\n\n    assert!(a.sq_l2_dist(&b)?.is_nan());\n    assert!(a.l2_dist(&b)?.is_nan());\n    assert!(a.l1_dist(&b)?.is_nan());\n    assert_eq!(a.linf_dist(&b)?, 0.);\n\n    assert!(a.mean_abs_err(&b)?.is_nan());\n    assert!(a.mean_sq_err(&b)?.is_nan());\n    assert!(a.root_mean_sq_err(&b)?.is_nan());\n    assert!(a.peak_signal_to_noise_ratio(&b, 0.)?.is_nan());\n\n    Ok(())\n}\n\n#[test]\nfn test_deviations_with_empty_argument() {\n    let a: Array1<f64> = array![1.];\n    let b: Array1<f64> = array![];\n\n    let shape_mismatch_err = MultiInputError::ShapeMismatch(ShapeMismatch {\n        first_shape: a.shape().to_vec(),\n        second_shape: b.shape().to_vec(),\n    });\n    let expected_err_usize = Err(shape_mismatch_err.clone());\n    let expected_err_f64 = Err(shape_mismatch_err);\n\n    assert_eq!(a.count_eq(&b), expected_err_usize);\n    assert_eq!(a.count_neq(&b), expected_err_usize);\n\n    assert_eq!(a.sq_l2_dist(&b), expected_err_f64);\n    assert_eq!(a.l2_dist(&b), expected_err_f64);\n    assert_eq!(a.l1_dist(&b), expected_err_f64);\n    assert_eq!(a.linf_dist(&b), expected_err_f64);\n\n    assert_eq!(a.mean_abs_err(&b), expected_err_f64);\n    assert_eq!(a.mean_sq_err(&b), expected_err_f64);\n    assert_eq!(a.root_mean_sq_err(&b), expected_err_f64);\n    assert_eq!(a.peak_signal_to_noise_ratio(&b, 0.), expected_err_f64);\n}\n\n#[test]\nfn test_deviations_with_non_copyable() -> Result<(), MultiInputError> {\n    let a: Array1<BigInt> = array![0.into(), 1.into(), 4.into(), 2.into()];\n    let b: Array1<BigInt> = array![1.into(), 1.into(), 2.into(), 4.into()];\n\n    assert_eq!(a.count_eq(&a)?, 4);\n    assert_eq!(a.count_neq(&a)?, 0);\n\n    assert_eq!(a.sq_l2_dist(&b)?, 9.into());\n    assert_eq!(a.l2_dist(&b)?, 3.);\n    assert_eq!(a.l1_dist(&b)?, 5.into());\n    assert_eq!(a.linf_dist(&b)?, 2.into());\n\n    assert_abs_diff_eq!(a.mean_abs_err(&b)?, 1.25);\n    assert_abs_diff_eq!(a.mean_sq_err(&b)?, 2.25);\n    assert_abs_diff_eq!(a.root_mean_sq_err(&b)?, 1.5);\n    assert_abs_diff_eq!(\n        a.peak_signal_to_noise_ratio(&b, 4.into())?,\n        8.519374645445623\n    );\n\n    Ok(())\n}\n\n#[test]\nfn test_deviation_computation_for_mixed_ownership() {\n    // It's enough to check that the code compiles!\n    let a = array![0., 0.];\n    let b = array![1., 0.];\n\n    let _ = a.count_eq(&b.view());\n    let _ = a.count_neq(&b.view());\n    let _ = a.l2_dist(&b.view());\n    let _ = a.sq_l2_dist(&b.view());\n    let _ = a.l1_dist(&b.view());\n    let _ = a.linf_dist(&b.view());\n    let _ = a.mean_abs_err(&b.view());\n    let _ = a.mean_sq_err(&b.view());\n    let _ = a.root_mean_sq_err(&b.view());\n    let _ = a.peak_signal_to_noise_ratio(&b.view(), 10.);\n}\n"
  },
  {
    "path": "tests/maybe_nan.rs",
    "content": "use ndarray::prelude::*;\nuse ndarray_stats::MaybeNan;\nuse noisy_float::types::{n64, N64};\n\n#[test]\nfn remove_nan_mut_nonstandard_layout() {\n    fn eq_unordered(mut a: Vec<N64>, mut b: Vec<N64>) -> bool {\n        a.sort();\n        b.sort();\n        a == b\n    }\n    let a = aview1(&[1., 2., f64::NAN, f64::NAN, 3., f64::NAN, 4., 5.]);\n    {\n        let mut a = a.to_owned();\n        let v = f64::remove_nan_mut(a.slice_mut(s![..;2]));\n        assert!(eq_unordered(v.to_vec(), vec![n64(1.), n64(3.), n64(4.)]));\n    }\n    {\n        let mut a = a.to_owned();\n        let v = f64::remove_nan_mut(a.slice_mut(s![..;-1]));\n        assert!(eq_unordered(\n            v.to_vec(),\n            vec![n64(5.), n64(4.), n64(3.), n64(2.), n64(1.)],\n        ));\n    }\n    {\n        let mut a = a.to_owned();\n        let v = f64::remove_nan_mut(a.slice_mut(s![..;-2]));\n        assert!(eq_unordered(v.to_vec(), vec![n64(5.), n64(2.)]));\n    }\n}\n"
  },
  {
    "path": "tests/quantile.rs",
    "content": "use itertools::izip;\nuse ndarray::array;\nuse ndarray::prelude::*;\nuse ndarray_stats::{\n    errors::{EmptyInput, MinMaxError, QuantileError},\n    interpolate::{Higher, Interpolate, Linear, Lower, Midpoint, Nearest},\n    Quantile1dExt, QuantileExt,\n};\nuse noisy_float::types::{n64, N64};\nuse quickcheck_macros::quickcheck;\n\n#[test]\nfn test_argmin() {\n    let a = array![[1, 5, 3], [2, 0, 6]];\n    assert_eq!(a.argmin(), Ok((1, 1)));\n\n    let a = array![[1., 5., 3.], [2., 0., 6.]];\n    assert_eq!(a.argmin(), Ok((1, 1)));\n\n    let a = array![[1., 5., 3.], [2., ::std::f64::NAN, 6.]];\n    assert_eq!(a.argmin(), Err(MinMaxError::UndefinedOrder));\n\n    let a: Array2<i32> = array![[], []];\n    assert_eq!(a.argmin(), Err(MinMaxError::EmptyInput));\n}\n\n#[quickcheck]\nfn argmin_matches_min(data: Vec<f32>) -> bool {\n    let a = Array1::from(data);\n    a.argmin().map(|i| &a[i]) == a.min()\n}\n\n#[test]\nfn test_argmin_skipnan() {\n    let a = array![[1., 5., 3.], [2., 0., 6.]];\n    assert_eq!(a.argmin_skipnan(), Ok((1, 1)));\n\n    let a = array![[1., 5., 3.], [2., ::std::f64::NAN, 6.]];\n    assert_eq!(a.argmin_skipnan(), Ok((0, 0)));\n\n    let a = array![[::std::f64::NAN, 5., 3.], [2., ::std::f64::NAN, 6.]];\n    assert_eq!(a.argmin_skipnan(), Ok((1, 0)));\n\n    let a: Array2<f64> = array![[], []];\n    assert_eq!(a.argmin_skipnan(), Err(EmptyInput));\n\n    let a = arr2(&[[::std::f64::NAN; 2]; 2]);\n    assert_eq!(a.argmin_skipnan(), Err(EmptyInput));\n}\n\n#[quickcheck]\nfn argmin_skipnan_matches_min_skipnan(data: Vec<Option<i32>>) -> bool {\n    let a = Array1::from(data);\n    let min = a.min_skipnan();\n    let argmin = a.argmin_skipnan();\n    if min.is_none() {\n        argmin == Err(EmptyInput)\n    } else {\n        a[argmin.unwrap()] == *min\n    }\n}\n\n#[test]\nfn test_min() {\n    let a = array![[1, 5, 3], [2, 0, 6]];\n    assert_eq!(a.min(), Ok(&0));\n\n    let a = array![[1., 5., 3.], [2., 0., 6.]];\n    assert_eq!(a.min(), Ok(&0.));\n\n    let a = array![[1., 5., 3.], [2., ::std::f64::NAN, 6.]];\n    assert_eq!(a.min(), Err(MinMaxError::UndefinedOrder));\n}\n\n#[test]\nfn test_min_skipnan() {\n    let a = array![[1., 5., 3.], [2., 0., 6.]];\n    assert_eq!(a.min_skipnan(), &0.);\n\n    let a = array![[1., 5., 3.], [2., ::std::f64::NAN, 6.]];\n    assert_eq!(a.min_skipnan(), &1.);\n}\n\n#[test]\nfn test_min_skipnan_all_nan() {\n    let a = arr2(&[[::std::f64::NAN; 3]; 2]);\n    assert!(a.min_skipnan().is_nan());\n}\n\n#[test]\nfn test_argmax() {\n    let a = array![[1, 5, 3], [2, 0, 6]];\n    assert_eq!(a.argmax(), Ok((1, 2)));\n\n    let a = array![[1., 5., 3.], [2., 0., 6.]];\n    assert_eq!(a.argmax(), Ok((1, 2)));\n\n    let a = array![[1., 5., 3.], [2., ::std::f64::NAN, 6.]];\n    assert_eq!(a.argmax(), Err(MinMaxError::UndefinedOrder));\n\n    let a: Array2<i32> = array![[], []];\n    assert_eq!(a.argmax(), Err(MinMaxError::EmptyInput));\n}\n\n#[quickcheck]\nfn argmax_matches_max(data: Vec<f32>) -> bool {\n    let a = Array1::from(data);\n    a.argmax().map(|i| &a[i]) == a.max()\n}\n\n#[test]\nfn test_argmax_skipnan() {\n    let a = array![[1., 5., 3.], [2., 0., 6.]];\n    assert_eq!(a.argmax_skipnan(), Ok((1, 2)));\n\n    let a = array![[1., 5., 3.], [2., ::std::f64::NAN, ::std::f64::NAN]];\n    assert_eq!(a.argmax_skipnan(), Ok((0, 1)));\n\n    let a = array![\n        [::std::f64::NAN, ::std::f64::NAN, 3.],\n        [2., ::std::f64::NAN, 6.]\n    ];\n    assert_eq!(a.argmax_skipnan(), Ok((1, 2)));\n\n    let a: Array2<f64> = array![[], []];\n    assert_eq!(a.argmax_skipnan(), Err(EmptyInput));\n\n    let a = arr2(&[[::std::f64::NAN; 2]; 2]);\n    assert_eq!(a.argmax_skipnan(), Err(EmptyInput));\n}\n\n#[quickcheck]\nfn argmax_skipnan_matches_max_skipnan(data: Vec<Option<i32>>) -> bool {\n    let a = Array1::from(data);\n    let max = a.max_skipnan();\n    let argmax = a.argmax_skipnan();\n    if max.is_none() {\n        argmax == Err(EmptyInput)\n    } else {\n        a[argmax.unwrap()] == *max\n    }\n}\n\n#[test]\nfn test_max() {\n    let a = array![[1, 5, 7], [2, 0, 6]];\n    assert_eq!(a.max(), Ok(&7));\n\n    let a = array![[1., 5., 7.], [2., 0., 6.]];\n    assert_eq!(a.max(), Ok(&7.));\n\n    let a = array![[1., 5., 7.], [2., ::std::f64::NAN, 6.]];\n    assert_eq!(a.max(), Err(MinMaxError::UndefinedOrder));\n}\n\n#[test]\nfn test_max_skipnan() {\n    let a = array![[1., 5., 7.], [2., 0., 6.]];\n    assert_eq!(a.max_skipnan(), &7.);\n\n    let a = array![[1., 5., 7.], [2., ::std::f64::NAN, 6.]];\n    assert_eq!(a.max_skipnan(), &7.);\n}\n\n#[test]\nfn test_max_skipnan_all_nan() {\n    let a = arr2(&[[::std::f64::NAN; 3]; 2]);\n    assert!(a.max_skipnan().is_nan());\n}\n\n#[test]\nfn test_quantile_axis_mut_with_odd_axis_length() {\n    let mut a = arr2(&[[1, 3, 2, 10], [2, 4, 3, 11], [3, 5, 6, 12]]);\n    let p = a.quantile_axis_mut(Axis(0), n64(0.5), &Lower).unwrap();\n    assert!(p == a.index_axis(Axis(0), 1));\n}\n\n#[test]\nfn test_quantile_axis_mut_with_zero_axis_length() {\n    let mut a = Array2::<i32>::zeros((5, 0));\n    assert_eq!(\n        a.quantile_axis_mut(Axis(1), n64(0.5), &Lower),\n        Err(QuantileError::EmptyInput)\n    );\n}\n\n#[test]\nfn test_quantile_axis_mut_with_empty_array() {\n    let mut a = Array2::<i32>::zeros((5, 0));\n    let p = a.quantile_axis_mut(Axis(0), n64(0.5), &Lower).unwrap();\n    assert_eq!(p.shape(), &[0]);\n}\n\n#[test]\nfn test_quantile_axis_mut_with_even_axis_length() {\n    let mut b = arr2(&[[1, 3, 2, 10], [2, 4, 3, 11], [3, 5, 6, 12], [4, 6, 7, 13]]);\n    let q = b.quantile_axis_mut(Axis(0), n64(0.5), &Lower).unwrap();\n    assert!(q == b.index_axis(Axis(0), 1));\n}\n\n#[test]\nfn test_quantile_axis_mut_to_get_minimum() {\n    let mut b = arr2(&[[1, 3, 22, 10]]);\n    let q = b.quantile_axis_mut(Axis(1), n64(0.), &Lower).unwrap();\n    assert!(q == arr1(&[1]));\n}\n\n#[test]\nfn test_quantile_axis_mut_to_get_maximum() {\n    let mut b = arr1(&[1, 3, 22, 10]);\n    let q = b.quantile_axis_mut(Axis(0), n64(1.), &Lower).unwrap();\n    assert!(q == arr0(22));\n}\n\n#[test]\nfn test_quantile_axis_skipnan_mut_higher_opt_i32() {\n    let mut a = arr2(&[[Some(4), Some(2), None, Some(1), Some(5)], [None; 5]]);\n    let q = a\n        .quantile_axis_skipnan_mut(Axis(1), n64(0.6), &Higher)\n        .unwrap();\n    assert_eq!(q.shape(), &[2]);\n    assert_eq!(q[0], Some(4));\n    assert!(q[1].is_none());\n}\n\n#[test]\nfn test_quantile_axis_skipnan_mut_nearest_opt_i32() {\n    let mut a = arr2(&[[Some(4), Some(2), None, Some(1), Some(5)], [None; 5]]);\n    let q = a\n        .quantile_axis_skipnan_mut(Axis(1), n64(0.6), &Nearest)\n        .unwrap();\n    assert_eq!(q.shape(), &[2]);\n    assert_eq!(q[0], Some(4));\n    assert!(q[1].is_none());\n}\n\n#[test]\nfn test_quantile_axis_skipnan_mut_midpoint_opt_i32() {\n    let mut a = arr2(&[[Some(4), Some(2), None, Some(1), Some(5)], [None; 5]]);\n    let q = a\n        .quantile_axis_skipnan_mut(Axis(1), n64(0.6), &Midpoint)\n        .unwrap();\n    assert_eq!(q.shape(), &[2]);\n    assert_eq!(q[0], Some(3));\n    assert!(q[1].is_none());\n}\n\n#[test]\nfn test_quantile_axis_skipnan_mut_linear_f64() {\n    let mut a = arr2(&[[1., 2., ::std::f64::NAN, 3.], [::std::f64::NAN; 4]]);\n    let q = a\n        .quantile_axis_skipnan_mut(Axis(1), n64(0.75), &Linear)\n        .unwrap();\n    assert_eq!(q.shape(), &[2]);\n    assert!((q[0] - 2.5).abs() < 1e-12);\n    assert!(q[1].is_nan());\n}\n\n#[test]\nfn test_quantile_axis_skipnan_mut_linear_opt_i32() {\n    let mut a = arr2(&[[Some(2), Some(4), None, Some(1)], [None; 4]]);\n    let q = a\n        .quantile_axis_skipnan_mut(Axis(1), n64(0.75), &Linear)\n        .unwrap();\n    assert_eq!(q.shape(), &[2]);\n    assert_eq!(q[0], Some(3));\n    assert!(q[1].is_none());\n}\n\n#[test]\nfn test_midpoint_overflow() {\n    // Regression test\n    // This triggered an overflow panic with a naive Midpoint implementation: (a+b)/2\n    let mut a: Array1<u8> = array![129, 130, 130, 131];\n    let median = a.quantile_mut(n64(0.5), &Midpoint).unwrap();\n    let expected_median = 130;\n    assert_eq!(median, expected_median);\n}\n\n#[quickcheck]\nfn test_quantiles_mut(xs: Vec<i64>) -> bool {\n    let v = Array::from(xs.clone());\n\n    // Unordered list of quantile indexes to look up, with a duplicate\n    let quantile_indexes = Array::from(vec![\n        n64(0.75),\n        n64(0.90),\n        n64(0.95),\n        n64(0.99),\n        n64(1.),\n        n64(0.),\n        n64(0.25),\n        n64(0.5),\n        n64(0.5),\n    ]);\n    let mut correct = true;\n    correct &= check_one_interpolation_method_for_quantiles_mut(\n        v.clone(),\n        quantile_indexes.view(),\n        &Linear,\n    );\n    correct &= check_one_interpolation_method_for_quantiles_mut(\n        v.clone(),\n        quantile_indexes.view(),\n        &Higher,\n    );\n    correct &= check_one_interpolation_method_for_quantiles_mut(\n        v.clone(),\n        quantile_indexes.view(),\n        &Lower,\n    );\n    correct &= check_one_interpolation_method_for_quantiles_mut(\n        v.clone(),\n        quantile_indexes.view(),\n        &Midpoint,\n    );\n    correct &= check_one_interpolation_method_for_quantiles_mut(\n        v.clone(),\n        quantile_indexes.view(),\n        &Nearest,\n    );\n    correct\n}\n\nfn check_one_interpolation_method_for_quantiles_mut(\n    mut v: Array1<i64>,\n    quantile_indexes: ArrayView1<'_, N64>,\n    interpolate: &impl Interpolate<i64>,\n) -> bool {\n    let bulk_quantiles = v.clone().quantiles_mut(&quantile_indexes, interpolate);\n\n    if v.len() == 0 {\n        bulk_quantiles.is_err()\n    } else {\n        let bulk_quantiles = bulk_quantiles.unwrap();\n        izip!(quantile_indexes, &bulk_quantiles).all(|(&quantile_index, &quantile)| {\n            quantile == v.quantile_mut(quantile_index, interpolate).unwrap()\n        })\n    }\n}\n\n#[quickcheck]\nfn test_quantiles_axis_mut(mut xs: Vec<u64>) -> bool {\n    // We want a square matrix\n    let axis_length = (xs.len() as f64).sqrt().floor() as usize;\n    xs.truncate(axis_length * axis_length);\n    let m = Array::from_shape_vec((axis_length, axis_length), xs).unwrap();\n\n    // Unordered list of quantile indexes to look up, with a duplicate\n    let quantile_indexes = Array::from(vec![\n        n64(0.75),\n        n64(0.90),\n        n64(0.95),\n        n64(0.99),\n        n64(1.),\n        n64(0.),\n        n64(0.25),\n        n64(0.5),\n        n64(0.5),\n    ]);\n\n    // Test out all interpolation methods\n    let mut correct = true;\n    correct &= check_one_interpolation_method_for_quantiles_axis_mut(\n        m.clone(),\n        quantile_indexes.view(),\n        Axis(0),\n        &Linear,\n    );\n    correct &= check_one_interpolation_method_for_quantiles_axis_mut(\n        m.clone(),\n        quantile_indexes.view(),\n        Axis(0),\n        &Higher,\n    );\n    correct &= check_one_interpolation_method_for_quantiles_axis_mut(\n        m.clone(),\n        quantile_indexes.view(),\n        Axis(0),\n        &Lower,\n    );\n    correct &= check_one_interpolation_method_for_quantiles_axis_mut(\n        m.clone(),\n        quantile_indexes.view(),\n        Axis(0),\n        &Midpoint,\n    );\n    correct &= check_one_interpolation_method_for_quantiles_axis_mut(\n        m.clone(),\n        quantile_indexes.view(),\n        Axis(0),\n        &Nearest,\n    );\n    correct\n}\n\nfn check_one_interpolation_method_for_quantiles_axis_mut(\n    mut v: Array2<u64>,\n    quantile_indexes: ArrayView1<'_, N64>,\n    axis: Axis,\n    interpolate: &impl Interpolate<u64>,\n) -> bool {\n    let bulk_quantiles = v\n        .clone()\n        .quantiles_axis_mut(axis, &quantile_indexes, interpolate);\n\n    if v.len() == 0 {\n        bulk_quantiles.is_err()\n    } else {\n        let bulk_quantiles = bulk_quantiles.unwrap();\n        izip!(quantile_indexes, bulk_quantiles.axis_iter(axis)).all(\n            |(&quantile_index, quantile)| {\n                quantile\n                    == v.quantile_axis_mut(axis, quantile_index, interpolate)\n                        .unwrap()\n            },\n        )\n    }\n}\n"
  },
  {
    "path": "tests/sort.rs",
    "content": "use ndarray::prelude::*;\nuse ndarray_stats::Sort1dExt;\nuse quickcheck_macros::quickcheck;\n\n#[test]\nfn test_partition_mut() {\n    let mut l = vec![\n        arr1(&[1, 1, 1, 1, 1]),\n        arr1(&[1, 3, 2, 10, 10]),\n        arr1(&[2, 3, 4, 1]),\n        arr1(&[\n            355, 453, 452, 391, 289, 343, 44, 154, 271, 44, 314, 276, 160, 469, 191, 138, 163, 308,\n            395, 3, 416, 391, 210, 354, 200,\n        ]),\n        arr1(&[\n            84, 192, 216, 159, 89, 296, 35, 213, 456, 278, 98, 52, 308, 418, 329, 173, 286, 106,\n            366, 129, 125, 450, 23, 463, 151,\n        ]),\n    ];\n    for a in l.iter_mut() {\n        let n = a.len();\n        let pivot_index = n - 1;\n        let pivot_value = a[pivot_index].clone();\n        let partition_index = a.partition_mut(pivot_index);\n        for i in 0..partition_index {\n            assert!(a[i] < pivot_value);\n        }\n        assert_eq!(a[partition_index], pivot_value);\n        for j in (partition_index + 1)..n {\n            assert!(pivot_value <= a[j]);\n        }\n    }\n}\n\n#[test]\nfn test_sorted_get_mut() {\n    let a = arr1(&[1, 3, 2, 10]);\n    let j = a.clone().view_mut().get_from_sorted_mut(2);\n    assert_eq!(j, 3);\n    let j = a.clone().view_mut().get_from_sorted_mut(1);\n    assert_eq!(j, 2);\n    let j = a.clone().view_mut().get_from_sorted_mut(3);\n    assert_eq!(j, 10);\n}\n\n#[quickcheck]\nfn test_sorted_get_many_mut(mut xs: Vec<i64>) -> bool {\n    let n = xs.len();\n    if n == 0 {\n        true\n    } else {\n        let mut v = Array::from(xs.clone());\n\n        // Insert each index twice, to get a set of indexes with duplicates, not sorted\n        let mut indexes: Vec<usize> = (0..n).into_iter().collect();\n        indexes.append(&mut (0..n).collect());\n\n        let mut sorted_v = Vec::with_capacity(n);\n        for (i, (key, value)) in v\n            .get_many_from_sorted_mut(&Array::from(indexes))\n            .into_iter()\n            .enumerate()\n        {\n            if i != key {\n                return false;\n            }\n            sorted_v.push(value);\n        }\n        xs.sort();\n        println!(\"Sorted: {:?}. Truth: {:?}\", sorted_v, xs);\n        xs == sorted_v\n    }\n}\n\n#[quickcheck]\nfn test_sorted_get_mut_as_sorting_algorithm(mut xs: Vec<i64>) -> bool {\n    let n = xs.len();\n    if n == 0 {\n        true\n    } else {\n        let mut v = Array::from(xs.clone());\n        let sorted_v: Vec<_> = (0..n).map(|i| v.get_from_sorted_mut(i)).collect();\n        xs.sort();\n        xs == sorted_v\n    }\n}\n"
  },
  {
    "path": "tests/summary_statistics.rs",
    "content": "use approx::{abs_diff_eq, assert_abs_diff_eq};\nuse ndarray::{arr0, array, Array, Array1, Array2, Axis};\nuse ndarray_rand::rand_distr::Uniform;\nuse ndarray_rand::RandomExt;\nuse ndarray_stats::{\n    errors::{EmptyInput, MultiInputError, ShapeMismatch},\n    SummaryStatisticsExt,\n};\nuse noisy_float::types::N64;\nuse quickcheck::{quickcheck, TestResult};\nuse std::f64;\n\n#[test]\nfn test_with_nan_values() {\n    let a = array![f64::NAN, 1.];\n    let weights = array![1.0, f64::NAN];\n    assert!(a.mean().unwrap().is_nan());\n    assert!(a.weighted_mean(&weights).unwrap().is_nan());\n    assert!(a.weighted_sum(&weights).unwrap().is_nan());\n    assert!(a\n        .weighted_mean_axis(Axis(0), &weights)\n        .unwrap()\n        .into_scalar()\n        .is_nan());\n    assert!(a\n        .weighted_sum_axis(Axis(0), &weights)\n        .unwrap()\n        .into_scalar()\n        .is_nan());\n    assert!(a.harmonic_mean().unwrap().is_nan());\n    assert!(a.geometric_mean().unwrap().is_nan());\n    assert!(a.weighted_var(&weights, 0.0).unwrap().is_nan());\n    assert!(a.weighted_std(&weights, 0.0).unwrap().is_nan());\n    assert!(a\n        .weighted_var_axis(Axis(0), &weights, 0.0)\n        .unwrap()\n        .into_scalar()\n        .is_nan());\n    assert!(a\n        .weighted_std_axis(Axis(0), &weights, 0.0)\n        .unwrap()\n        .into_scalar()\n        .is_nan());\n}\n\n#[test]\nfn test_with_empty_array_of_floats() {\n    let a: Array1<f64> = array![];\n    let weights = array![1.0];\n    assert_eq!(a.mean(), None);\n    assert_eq!(a.weighted_mean(&weights), Err(MultiInputError::EmptyInput));\n    assert_eq!(\n        a.weighted_mean_axis(Axis(0), &weights),\n        Err(MultiInputError::EmptyInput)\n    );\n    assert_eq!(a.harmonic_mean(), Err(EmptyInput));\n    assert_eq!(a.geometric_mean(), Err(EmptyInput));\n    assert_eq!(\n        a.weighted_var(&weights, 0.0),\n        Err(MultiInputError::EmptyInput)\n    );\n    assert_eq!(\n        a.weighted_std(&weights, 0.0),\n        Err(MultiInputError::EmptyInput)\n    );\n    assert_eq!(\n        a.weighted_var_axis(Axis(0), &weights, 0.0),\n        Err(MultiInputError::EmptyInput)\n    );\n    assert_eq!(\n        a.weighted_std_axis(Axis(0), &weights, 0.0),\n        Err(MultiInputError::EmptyInput)\n    );\n\n    // The sum methods accept empty arrays\n    assert_eq!(a.weighted_sum(&array![]), Ok(0.0));\n    assert_eq!(a.weighted_sum_axis(Axis(0), &array![]), Ok(arr0(0.0)));\n}\n\n#[test]\nfn test_with_empty_array_of_noisy_floats() {\n    let a: Array1<N64> = array![];\n    let weights = array![];\n    assert_eq!(a.mean(), None);\n    assert_eq!(a.weighted_mean(&weights), Err(MultiInputError::EmptyInput));\n    assert_eq!(\n        a.weighted_mean_axis(Axis(0), &weights),\n        Err(MultiInputError::EmptyInput)\n    );\n    assert_eq!(a.harmonic_mean(), Err(EmptyInput));\n    assert_eq!(a.geometric_mean(), Err(EmptyInput));\n    assert_eq!(\n        a.weighted_var(&weights, N64::new(0.0)),\n        Err(MultiInputError::EmptyInput)\n    );\n    assert_eq!(\n        a.weighted_std(&weights, N64::new(0.0)),\n        Err(MultiInputError::EmptyInput)\n    );\n    assert_eq!(\n        a.weighted_var_axis(Axis(0), &weights, N64::new(0.0)),\n        Err(MultiInputError::EmptyInput)\n    );\n    assert_eq!(\n        a.weighted_std_axis(Axis(0), &weights, N64::new(0.0)),\n        Err(MultiInputError::EmptyInput)\n    );\n\n    // The sum methods accept empty arrays\n    assert_eq!(a.weighted_sum(&weights), Ok(N64::new(0.0)));\n    assert_eq!(\n        a.weighted_sum_axis(Axis(0), &weights),\n        Ok(arr0(N64::new(0.0)))\n    );\n}\n\n#[test]\nfn test_with_array_of_floats() {\n    let a: Array1<f64> = array![\n        0.99889651, 0.0150731, 0.28492482, 0.83819218, 0.48413156, 0.80710412, 0.41762936,\n        0.22879429, 0.43997224, 0.23831807, 0.02416466, 0.6269962, 0.47420614, 0.56275487,\n        0.78995021, 0.16060581, 0.64635041, 0.34876609, 0.78543249, 0.19938356, 0.34429457,\n        0.88072369, 0.17638164, 0.60819363, 0.250392, 0.69912532, 0.78855523, 0.79140914,\n        0.85084218, 0.31839879, 0.63381769, 0.22421048, 0.70760302, 0.99216018, 0.80199153,\n        0.19239188, 0.61356023, 0.31505352, 0.06120481, 0.66417377, 0.63608897, 0.84959691,\n        0.43599069, 0.77867775, 0.88267754, 0.83003623, 0.67016118, 0.67547638, 0.65220036,\n        0.68043427\n    ];\n    // Computed using NumPy\n    let expected_mean = 0.5475494059146699;\n    let expected_weighted_mean = 0.6782420496397121;\n    let expected_weighted_var = 0.04306695637838332;\n    // Computed using SciPy\n    let expected_harmonic_mean = 0.21790094950226022;\n    let expected_geometric_mean = 0.4345897639796527;\n\n    assert_abs_diff_eq!(a.mean().unwrap(), expected_mean, epsilon = 1e-9);\n    assert_abs_diff_eq!(\n        a.harmonic_mean().unwrap(),\n        expected_harmonic_mean,\n        epsilon = 1e-7\n    );\n    assert_abs_diff_eq!(\n        a.geometric_mean().unwrap(),\n        expected_geometric_mean,\n        epsilon = 1e-12\n    );\n\n    // Input array used as weights, normalized\n    let weights = &a / a.sum();\n    assert_abs_diff_eq!(\n        a.weighted_sum(&weights).unwrap(),\n        expected_weighted_mean,\n        epsilon = 1e-12\n    );\n    assert_abs_diff_eq!(\n        a.weighted_var(&weights, 0.0).unwrap(),\n        expected_weighted_var,\n        epsilon = 1e-12\n    );\n    assert_abs_diff_eq!(\n        a.weighted_std(&weights, 0.0).unwrap(),\n        expected_weighted_var.sqrt(),\n        epsilon = 1e-12\n    );\n\n    let data = a.into_shape_with_order((2, 5, 5)).unwrap();\n    let weights = array![0.1, 0.5, 0.25, 0.15, 0.2];\n    assert_abs_diff_eq!(\n        data.weighted_mean_axis(Axis(1), &weights).unwrap(),\n        array![\n            [0.50202721, 0.53347361, 0.29086033, 0.56995637, 0.37087139],\n            [0.58028328, 0.50485216, 0.59349973, 0.70308937, 0.72280630]\n        ],\n        epsilon = 1e-8\n    );\n    assert_abs_diff_eq!(\n        data.weighted_mean_axis(Axis(2), &weights).unwrap(),\n        array![\n            [0.33434378, 0.38365259, 0.56405781, 0.48676574, 0.55016179],\n            [0.71112376, 0.55134174, 0.45566513, 0.74228516, 0.68405851]\n        ],\n        epsilon = 1e-8\n    );\n    assert_abs_diff_eq!(\n        data.weighted_sum_axis(Axis(1), &weights).unwrap(),\n        array![\n            [0.60243266, 0.64016833, 0.34903240, 0.68394765, 0.44504567],\n            [0.69633993, 0.60582259, 0.71219968, 0.84370724, 0.86736757]\n        ],\n        epsilon = 1e-8\n    );\n    assert_abs_diff_eq!(\n        data.weighted_sum_axis(Axis(2), &weights).unwrap(),\n        array![\n            [0.40121254, 0.46038311, 0.67686937, 0.58411889, 0.66019415],\n            [0.85334851, 0.66161009, 0.54679815, 0.89074219, 0.82087021]\n        ],\n        epsilon = 1e-8\n    );\n}\n\n#[test]\nfn weighted_sum_dimension_zero() {\n    let a = Array2::<usize>::zeros((0, 20));\n    assert_eq!(\n        a.weighted_sum_axis(Axis(0), &Array1::zeros(0)).unwrap(),\n        Array1::from_elem(20, 0)\n    );\n    assert_eq!(\n        a.weighted_sum_axis(Axis(1), &Array1::zeros(20)).unwrap(),\n        Array1::from_elem(0, 0)\n    );\n    assert_eq!(\n        a.weighted_sum_axis(Axis(0), &Array1::zeros(1)),\n        Err(MultiInputError::ShapeMismatch(ShapeMismatch {\n            first_shape: vec![0, 20],\n            second_shape: vec![1]\n        }))\n    );\n    assert_eq!(\n        a.weighted_sum(&Array2::zeros((10, 20))),\n        Err(MultiInputError::ShapeMismatch(ShapeMismatch {\n            first_shape: vec![0, 20],\n            second_shape: vec![10, 20]\n        }))\n    );\n}\n\n#[test]\nfn mean_eq_if_uniform_weights() {\n    fn prop(a: Vec<f64>) -> TestResult {\n        if a.len() < 1 {\n            return TestResult::discard();\n        }\n        let a = Array1::from(a);\n        let weights = Array1::from_elem(a.len(), 1.0 / a.len() as f64);\n        let m = a.mean().unwrap();\n        let wm = a.weighted_mean(&weights).unwrap();\n        let ws = a.weighted_sum(&weights).unwrap();\n        TestResult::from_bool(\n            abs_diff_eq!(m, wm, epsilon = 1e-9) && abs_diff_eq!(wm, ws, epsilon = 1e-9),\n        )\n    }\n    quickcheck(prop as fn(Vec<f64>) -> TestResult);\n}\n\n#[test]\nfn mean_axis_eq_if_uniform_weights() {\n    fn prop(mut a: Vec<f64>) -> TestResult {\n        if a.len() < 24 {\n            return TestResult::discard();\n        }\n        let depth = a.len() / 12;\n        a.truncate(depth * 3 * 4);\n        let weights = Array1::from_elem(depth, 1.0 / depth as f64);\n        let a = Array1::from(a)\n            .into_shape_with_order((depth, 3, 4))\n            .unwrap();\n        let ma = a.mean_axis(Axis(0)).unwrap();\n        let wm = a.weighted_mean_axis(Axis(0), &weights).unwrap();\n        let ws = a.weighted_sum_axis(Axis(0), &weights).unwrap();\n        TestResult::from_bool(\n            abs_diff_eq!(ma, wm, epsilon = 1e-12) && abs_diff_eq!(wm, ws, epsilon = 1e12),\n        )\n    }\n    quickcheck(prop as fn(Vec<f64>) -> TestResult);\n}\n\n#[test]\nfn weighted_var_eq_var_if_uniform_weight() {\n    fn prop(a: Vec<f64>) -> TestResult {\n        if a.len() < 1 {\n            return TestResult::discard();\n        }\n        let a = Array1::from(a);\n        let weights = Array1::from_elem(a.len(), 1.0 / a.len() as f64);\n        let weighted_var = a.weighted_var(&weights, 0.0).unwrap();\n        let var = a.var_axis(Axis(0), 0.0).into_scalar();\n        TestResult::from_bool(abs_diff_eq!(weighted_var, var, epsilon = 1e-10))\n    }\n    quickcheck(prop as fn(Vec<f64>) -> TestResult);\n}\n\n#[test]\nfn weighted_var_algo_eq_simple_algo() {\n    fn prop(mut a: Vec<f64>) -> TestResult {\n        if a.len() < 24 {\n            return TestResult::discard();\n        }\n        let depth = a.len() / 12;\n        a.truncate(depth * 3 * 4);\n        let a = Array1::from(a)\n            .into_shape_with_order((depth, 3, 4))\n            .unwrap();\n        let mut success = true;\n        for axis in 0..3 {\n            let axis = Axis(axis);\n\n            let weights = Array::random(a.len_of(axis), Uniform::new(0.0, 1.0).unwrap());\n            let mean = a\n                .weighted_mean_axis(axis, &weights)\n                .unwrap()\n                .insert_axis(axis);\n            let res_1_pass = a.weighted_var_axis(axis, &weights, 0.0).unwrap();\n            let res_2_pass = (&a - &mean)\n                .mapv_into(|v| v.powi(2))\n                .weighted_mean_axis(axis, &weights)\n                .unwrap();\n            success &= abs_diff_eq!(res_1_pass, res_2_pass, epsilon = 1e-10);\n        }\n        TestResult::from_bool(success)\n    }\n    quickcheck(prop as fn(Vec<f64>) -> TestResult);\n}\n\n#[test]\nfn test_central_moment_with_empty_array_of_floats() {\n    let a: Array1<f64> = array![];\n    for order in 0..=3 {\n        assert_eq!(a.central_moment(order), Err(EmptyInput));\n        assert_eq!(a.central_moments(order), Err(EmptyInput));\n    }\n}\n\n#[test]\nfn test_zeroth_central_moment_is_one() {\n    let n = 50;\n    let bound: f64 = 200.;\n    let a = Array::random(n, Uniform::new(-bound.abs(), bound.abs()).unwrap());\n    assert_eq!(a.central_moment(0).unwrap(), 1.);\n}\n\n#[test]\nfn test_first_central_moment_is_zero() {\n    let n = 50;\n    let bound: f64 = 200.;\n    let a = Array::random(n, Uniform::new(-bound.abs(), bound.abs()).unwrap());\n    assert_eq!(a.central_moment(1).unwrap(), 0.);\n}\n\n#[test]\nfn test_central_moments() {\n    let a: Array1<f64> = array![\n        0.07820559, 0.5026185, 0.80935324, 0.39384033, 0.9483038, 0.62516215, 0.90772261,\n        0.87329831, 0.60267392, 0.2960298, 0.02810356, 0.31911966, 0.86705506, 0.96884832,\n        0.2222465, 0.42162446, 0.99909868, 0.47619762, 0.91696979, 0.9972741, 0.09891734,\n        0.76934818, 0.77566862, 0.7692585, 0.2235759, 0.44821286, 0.79732186, 0.04804275,\n        0.87863238, 0.1111003, 0.6653943, 0.44386445, 0.2133176, 0.39397086, 0.4374617, 0.95896624,\n        0.57850146, 0.29301706, 0.02329879, 0.2123203, 0.62005503, 0.996492, 0.5342986, 0.97822099,\n        0.5028445, 0.6693834, 0.14256682, 0.52724704, 0.73482372, 0.1809703,\n    ];\n    // Computed using scipy.stats.moment\n    let expected_moments = vec![\n        1.,\n        0.,\n        0.09339920262960291,\n        -0.0026849636727735186,\n        0.015403769257729755,\n        -0.001204176487006564,\n        0.002976822584939186,\n    ];\n    for (order, expected_moment) in expected_moments.iter().enumerate() {\n        assert_abs_diff_eq!(\n            a.central_moment(order as u16).unwrap(),\n            expected_moment,\n            epsilon = 1e-8\n        );\n    }\n}\n\n#[test]\nfn test_bulk_central_moments() {\n    // Test that the bulk method is coherent with the non-bulk method\n    let n = 50;\n    let bound: f64 = 200.;\n    let a = Array::random(n, Uniform::new(-bound.abs(), bound.abs()).unwrap());\n    let order = 10;\n    let central_moments = a.central_moments(order).unwrap();\n    for i in 0..=order {\n        assert_eq!(a.central_moment(i).unwrap(), central_moments[i as usize]);\n    }\n}\n\n#[test]\nfn test_kurtosis_and_skewness_is_none_with_empty_array_of_floats() {\n    let a: Array1<f64> = array![];\n    assert_eq!(a.skewness(), Err(EmptyInput));\n    assert_eq!(a.kurtosis(), Err(EmptyInput));\n}\n\n#[test]\nfn test_kurtosis_and_skewness() {\n    let a: Array1<f64> = array![\n        0.33310096, 0.98757449, 0.9789796, 0.96738114, 0.43545674, 0.06746873, 0.23706562,\n        0.04241815, 0.38961714, 0.52421271, 0.93430327, 0.33911604, 0.05112372, 0.5013455,\n        0.05291507, 0.62511183, 0.20749633, 0.22132433, 0.14734804, 0.51960608, 0.00449208,\n        0.4093339, 0.2237519, 0.28070469, 0.7887231, 0.92224523, 0.43454188, 0.18335111,\n        0.08646856, 0.87979847, 0.25483457, 0.99975627, 0.52712442, 0.41163279, 0.85162594,\n        0.52618733, 0.75815023, 0.30640695, 0.14205781, 0.59695813, 0.851331, 0.39524328,\n        0.73965373, 0.4007615, 0.02133069, 0.92899207, 0.79878191, 0.38947334, 0.22042183,\n        0.77768353,\n    ];\n    // Computed using scipy.stats.kurtosis(a, fisher=False)\n    let expected_kurtosis = 1.821933711687523;\n    // Computed using scipy.stats.skew\n    let expected_skewness = 0.2604785422878771;\n\n    let kurtosis = a.kurtosis().unwrap();\n    let skewness = a.skewness().unwrap();\n\n    assert_abs_diff_eq!(kurtosis, expected_kurtosis, epsilon = 1e-12);\n    assert_abs_diff_eq!(skewness, expected_skewness, epsilon = 1e-8);\n}\n"
  }
]